status-sharding
Version:
Welcome to Status Sharding! This package is designed to provide an efficient and flexible solution for sharding Discord bots, allowing you to scale your bot across multiple processes or workers.
152 lines (151 loc) • 6.67 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ShardingUtils = void 0;
const types_1 = require("../types");
const crypto_1 = require("crypto");
/** Sharding utils. */
class ShardingUtils {
/** Generates a nonce. */
static generateNonce() {
return (0, crypto_1.randomBytes)(10).toString('hex');
}
/** Chunks an array into smaller arrays. */
static chunkArray(array, chunkSize, equalize = false) {
const R = [];
if (equalize)
chunkSize = Math.ceil(array.length / (Math.ceil(array.length / chunkSize)));
for (let i = 0; i < array.length; i += chunkSize) {
R.push(array.slice(i, i + chunkSize));
}
return R;
}
/** Delays for a certain amount of time. */
static delayFor(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
/** Checks if a value is serializable. */
static isSerializable(value) {
if (typeof value === 'object' && value !== null && value.constructor !== Object && value.constructor !== Array)
return false;
else if (typeof value === 'function')
return false;
else if (typeof value === 'symbol')
return false;
return true;
}
/** Removes all non-existing values from an array. */
static removeNonExisting(array) {
return array.reduce((acc, item) => {
if (item !== undefined && item !== null)
acc.push(item);
return acc;
}, []);
}
/** Makes an error plain. */
static makePlainError(err) {
const removeStuff = (v) => v.replace(/(\n|\r|\t)/g, '').replace(/( )+/g, ' ').replace(/(\/\/.*)/g, '');
return {
name: removeStuff(err.name),
message: removeStuff(err.message),
stack: removeStuff(err.stack?.replace(': ' + err.message, '') || ''),
};
}
/** Merges two objects. */
static mergeObjects(main, toMerge) {
const merged = { ...toMerge };
for (const key in main) {
if (Object.prototype.hasOwnProperty.call(main, key)) {
if (typeof main[key] === 'object' && !Array.isArray(main[key])) {
merged[key] = ShardingUtils.mergeObjects(toMerge[key] ?? {}, main[key] ?? {});
}
else {
merged[key] = main[key];
}
}
}
return merged;
}
/** Gets the shard id for a guild id. */
static shardIdForGuildId(guildId, totalShards) {
if (!guildId?.match(/^[0-9]+$/))
throw new Error('No valid GuildId Provided (#1).');
else if (isNaN(totalShards) || totalShards < 1)
throw new Error('No valid TotalShards Provided (#1).');
const shard = Number(BigInt(guildId) >> BigInt(22)) % totalShards;
if (shard < 0)
throw new Error('SHARD_MISCALCULATION_SHARDID_SMALLER_THAN_0 ' + `Calculated Shard: ${shard}, guildId: ${guildId}, totalShards: ${totalShards}`);
return shard;
}
/** Gets the cluster id for a shard id. */
static clusterIdForShardId(shardId, totalShards, totalClusters) {
if (!shardId?.match(/^[0-9]+$/))
throw new Error('No valid Shard Id Provided.');
else if (isNaN(totalShards) || totalShards < 1)
throw new Error('No valid TotalShards Provided (#2).');
else if (isNaN(totalClusters) || totalClusters < 1)
throw new Error('No valid TotalClusters Provided (#1).');
const middlePart = Number(shardId) === 0 ? 0 : Number(shardId) / Math.ceil(totalShards / totalClusters);
return Number(shardId) === 0 ? 0 : (Math.ceil(middlePart) - (middlePart % 1 !== 0 ? 1 : 0));
}
/** Gets the cluster id for a guild id. */
static clusterIdForGuildId(guildId, totalShards, totalClusters) {
if (!guildId?.match(/^[0-9]+$/))
throw new Error('No valid GuildId Provided (#2).');
else if (isNaN(totalShards) || totalShards < 1)
throw new Error('No valid TotalShards Provided (#3).');
else if (isNaN(totalClusters) || totalClusters < 1)
throw new Error('No valid TotalClusters Provided (#2).');
const shardId = this.shardIdForGuildId(guildId, totalShards);
return this.clusterIdForShardId(shardId.toString(), totalShards, totalClusters);
}
/** Gets the cluster id for a shard id. */
static async getRecommendedShards(token, guildsPerShard = 1000, options = types_1.DefaultOptions) {
if (!token)
throw new Error('DISCORD_TOKEN_MISSING | No token was provided to ClusterManager options.');
const response = await fetch(`${options.http.api}/v${options.http.version}${types_1.Endpoints.botGateway}`, {
method: 'GET',
headers: { Authorization: `Bot ${token.replace(/^Bot\s*/i, '')}` },
}).then((res) => {
if (res.ok)
return res.json();
else if (res.status === 401)
throw new Error('DISCORD_TOKEN_INVALID | The provided token was invalid.');
throw res;
});
return response.shards * (1000 / guildsPerShard);
}
static parseInput(input, context, packageType, ...args) {
if (typeof input === 'string')
return input;
else if (typeof input === 'function') {
if (packageType === '@discordjs/core')
return `(${input.toString()})(client,${context ? JSON.stringify(context) : undefined}${args.length ? ',' + args.join(',') : ''})`;
return `(${input.toString()})(this,${context ? JSON.stringify(context) : undefined}${args.length ? ',' + args.join(',') : ''})`;
}
throw new Error('INVALID_INPUT_TYPE | The input provided was not a string or a function.');
}
static boolProp(input, key) {
return input ? key : `not ${key}`;
}
static relativeTime(time) {
if (!time)
return 'never';
const date = new Date(time);
const now = new Date();
const seconds = Math.floor((now.getTime() - date.getTime()) / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (seconds < 60)
return `${seconds} seconds ago`;
else if (minutes < 60)
return `${minutes} minutes ago`;
else if (hours < 24)
return `${hours} hours ago`;
else
return `${days} days ago`;
}
}
exports.ShardingUtils = ShardingUtils;