diff --git a/src/structures/Manager.ts b/src/structures/Manager.ts index e83678d..efe391c 100644 --- a/src/structures/Manager.ts +++ b/src/structures/Manager.ts @@ -228,8 +228,49 @@ export class Manager extends EventEmitter { }); } - /** - * Initiates the Manager class. + /** + * Returns the Node in same region as server. + * @param region + */ + public nearestNode(region: string): Node { + const nodes = this.nodes + .filter((node) => { + if (!node.options.region) + return node.connected + else if (Array.isArray(node.options.region)) + return node.connected && node.options.region.includes(region) + else + return node.connected && node.options.region.toLowerCase() == region.toLowerCase() + }); + + if (!nodes) + return null; + else if (nodes.size === 1) + return nodes.first(); + else if (nodes.size > 1) + return this.leastLoadNodesByRegion(nodes).first(); + } + + /** + * Returns the least system load Nodes from provided Nodes. + * @param nodes + */ + public leastLoadNodesByRegion(nodes: Collection): Collection { + return nodes + .filter((node) => node.connected) + .sort((a, b) => { + const aload = a.stats.cpu + ? (a.stats.cpu.systemLoad / a.stats.cpu.cores) * 100 + : 0; + const bload = b.stats.cpu + ? (b.stats.cpu.systemLoad / b.stats.cpu.cores) * 100 + : 0; + return aload - bload; + }); + } + + /** + * Initiates the Manager class. * @param options */ constructor(options: ManagerOptions) { diff --git a/src/structures/Node.ts b/src/structures/Node.ts index bc89226..3ebf37c 100644 --- a/src/structures/Node.ts +++ b/src/structures/Node.ts @@ -58,6 +58,11 @@ function check(options: NodeOptions) { typeof options.retryDelay !== "number" ) throw new TypeError('Node option "retryDelay" must be a number.'); + + if (typeof options.region !== "undefined" && + ((typeof options.region !== "string" || + !/.+/.test(options.region)) && (!Array.isArray(options.region) || options.region.length === 0 || !/.+/.test(options.region[0])))) + throw new TypeError('Node option "region" must be a non-empty string or array.'); } export class Node { @@ -390,6 +395,8 @@ export interface NodeOptions { retryAmount?: number; /** The retryDelay for the node. */ retryDelay?: number; + /** Regions for which the node can be used */ + region?: string | string[]; } export interface NodeStats { diff --git a/src/structures/Player.ts b/src/structures/Player.ts index bc904f9..de3bc1e 100644 --- a/src/structures/Player.ts +++ b/src/structures/Player.ts @@ -10,6 +10,9 @@ function check(options: PlayerOptions) { throw new TypeError( 'Player option "guild" must be present and be a non-empty string.' ); + + if (options.region && !/^\d+$/.test(options.region)) + throw new TypeError('Player option "region" must be a non-empty string.'); if (options.textChannel && !/^\d+$/.test(options.textChannel)) throw new TypeError( @@ -62,6 +65,8 @@ export class Player { public node: Node; /** The guild the player. */ public guild: string; + /** The region of Discord server. */ + public region: string | null = null; /** The voice channel for the player. */ public voiceChannel: string | null = null; /** The text channel for the player. */ @@ -119,7 +124,9 @@ export class Player { if (options.textChannel) this.textChannel = options.textChannel; const node = this.manager.nodes.get(options.node); - this.node = node || this.manager.leastLoadNodes.first(); + if (node) this.node = node + else if (options.region) this.node = this.manager.nearestNode(options.region); + if (!this.node) this.node = this.manager.leastLoadNodes.first() if (!this.node) throw new RangeError("No available nodes."); @@ -448,6 +455,8 @@ export class Player { export interface PlayerOptions { /** The guild the Player belongs to. */ guild: string; + /** The region of Discord server. */ + region?: string; /** The text channel the Player belongs to. */ textChannel: string; /** The voice channel the Player belongs to. */ diff --git a/src/structures/Utils.ts b/src/structures/Utils.ts index 1b3711a..2670bbd 100644 --- a/src/structures/Utils.ts +++ b/src/structures/Utils.ts @@ -397,4 +397,4 @@ export interface PlayerUpdate { time: number; }; guildId: string; -} \ No newline at end of file +}