-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: Update Queue class and RestEventEmitter interface
- Loading branch information
Showing
14 changed files
with
529 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,8 @@ | ||
export * from "./structures/Node"; | ||
export * from "./types/Node"; | ||
export * from "./types/Node"; | ||
export * from "./structures/Manager"; | ||
export * from "./types/Manager"; | ||
export * from "./structures/Player"; | ||
export * from "./types/Player"; | ||
export * from "./structures/Rest"; | ||
export * from "./types/Rest"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,133 @@ | ||
import { TypedEmitter } from "tiny-typed-emitter"; | ||
import type { ManagerEventEmitter, ManagerOptions } from "../types/Manager"; | ||
import { Node } from "./Node"; | ||
import type { PlayerCreate } from "../types/Player"; | ||
import { Player } from "./Player"; | ||
import { Collection } from "@discordjs/collection"; | ||
import type { VoiceState } from "discord.js"; | ||
import type { VoicePacket, VoiceServer } from "../types/Discord"; | ||
|
||
export class Manager extends TypedEmitter { | ||
constructor() { | ||
export class Manager extends TypedEmitter<ManagerEventEmitter> { | ||
options: ManagerOptions; | ||
public readonly players = new Collection<string, Player>(); | ||
public readonly nodes = new Collection<string, Node>(); | ||
constructor(options: ManagerOptions) { | ||
super(); | ||
this.options = options; | ||
for (const node of options.nodes) { | ||
this.nodes.set(node.host, new Node(node)); | ||
} | ||
} | ||
|
||
private get priorityNode(): Node { | ||
const filteredNodes = this.nodes.filter((node) => node.connected && node.options.priority > 0); | ||
const totalWeight = filteredNodes.reduce((total, node) => total + node.options.priority, 0); | ||
const weightedNodes = filteredNodes.map((node) => ({ | ||
node, | ||
weight: node.options.priority / totalWeight, | ||
})); | ||
const randomNumber = Math.random(); | ||
|
||
let cumulativeWeight = 0; | ||
|
||
for (const { node, weight } of weightedNodes) { | ||
cumulativeWeight += weight; | ||
if (randomNumber <= cumulativeWeight) { | ||
return node; | ||
} | ||
} | ||
|
||
return this.options.useNode === "leastLoad" ? this.leastLoadNode.first() : this.leastPlayersNode.first(); | ||
} | ||
|
||
private get leastLoadNode(): Collection<string, Node> { | ||
return this.nodes | ||
.filter((node) => node.connected) | ||
.sort((a, b) => { | ||
const aload = a.stats.cpu ? (a.stats.cpu.lavalinkLoad / a.stats.cpu.cores) * 100 : 0; | ||
const bload = b.stats.cpu ? (b.stats.cpu.lavalinkLoad / b.stats.cpu.cores) * 100 : 0; | ||
return aload - bload; | ||
}); | ||
} | ||
|
||
private get leastPlayersNode(): Collection<string, Node> { | ||
return this.nodes.filter((node) => node.connected).sort((a, b) => a.stats.players - b.stats.players); | ||
} | ||
|
||
public get useableNodes(): Node { | ||
return this.options.usePriority ? this.priorityNode : this.options.useNode === "leastLoad" ? this.leastLoadNode.first() : this.leastPlayersNode.first(); | ||
} | ||
|
||
public create(options: PlayerCreate): Player { | ||
if (this.players.has(options.guild_id)) { | ||
return this.players.get(options.guild_id); | ||
} | ||
|
||
return new Player(this, options); | ||
} | ||
|
||
public init() { | ||
this.emit("raw", "Manager initialized"); | ||
for (const node of this.nodes.values()) { | ||
try { | ||
node.on("connect", () => { | ||
this.emit("NodeConnect", node); | ||
}).on("disconnect", () => { | ||
this.emit("NodeDisconnect", node); | ||
}).on("error", (err) => { | ||
this.emit("NodeError", node, err); | ||
}).on("stats", (stats) => { | ||
this.emit("NodeStats", node, stats); | ||
}).on("ready", () => { | ||
this.emit("NodeReady", node); | ||
}).on("raw", (data) => { | ||
this.emit("NodeRaw", node, data); | ||
}); | ||
node.connect(); | ||
} catch (err) { | ||
this.emit("NodeError", node, err); | ||
} | ||
} | ||
} | ||
public async updateVoiceState(data: VoicePacket | VoiceServer | VoiceState): Promise<void> { | ||
if ("t" in data && !["VOICE_STATE_UPDATE", "VOICE_SERVER_UPDATE"].includes(data.t)) return; | ||
const update = "d" in data ? data.d : data; | ||
if (!update || (!("token" in update) && !("session_id" in update))) return; | ||
const player = this.players.get(update.guild_id); | ||
if (!player) return; | ||
if ("token" in update) { | ||
// @ts-ignore | ||
if (!player.voiceState) player.voiceState = { event: {} }; | ||
player.voiceState.event = update; | ||
|
||
const { | ||
sessionId, | ||
event: { token, endpoint }, | ||
} = player.voiceState; | ||
console.log(player.voiceState) | ||
await player.node.rest.updatePlayer({ | ||
guildId: player.guild_id, | ||
data: { voice: { token, endpoint, sessionId } }, | ||
}); | ||
|
||
return; | ||
} | ||
|
||
if (update.user_id !== this.options.clientId) return; | ||
if (update.channel_id) { | ||
if (player.voiceChannel !== update.channel_id) { | ||
this.emit("PlayerMove", player, player.voiceChannel, update.channel_id); | ||
} | ||
|
||
player.voiceState.sessionId = update.session_id; | ||
player.voiceChannel = update.channel_id; | ||
return; | ||
} | ||
|
||
this.emit("PlayerDisconnect", player, player.voiceChannel); | ||
player.voiceChannel = null; | ||
player.voiceState = Object.assign({}); | ||
player.destroy(); | ||
return; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,107 @@ | ||
import type { VoiceState } from "../types/Discord"; | ||
import type { PlayerCreate } from "../types/Player"; | ||
import type { Manager } from "./Manager"; | ||
import type { Node } from "./Node"; | ||
import { Queue } from "./Queue"; | ||
|
||
export class Player { | ||
|
||
manager: Manager; | ||
options: PlayerCreate; | ||
node: Node; | ||
voiceChannel: string; | ||
state: "CONNECTING" | "CONNECTED" | "DISCONNECTED" | "DISCONNECTING" | "DESTROYING" = "DISCONNECTED"; | ||
guild_id: string; | ||
public voiceState: VoiceState; | ||
paused: boolean = false; | ||
playing: boolean = false; | ||
textChannel: string | ||
public readonly queue = new Queue(); | ||
constructor(manager: Manager, options: PlayerCreate) { | ||
this.manager = manager; | ||
if (!this.manager) throw new RangeError("Manager has not been initiated."); | ||
this.options = options; | ||
this.voiceChannel = options.voiceChannel; | ||
this.voiceState = Object.assign({ | ||
op: "voiceUpdate", | ||
guild_id: options.guild_id, | ||
}); | ||
if (options.voiceChannel) this.voiceChannel = options.voiceChannel; | ||
if (options.textChannel) this.textChannel = options.textChannel; | ||
const node = this.manager.nodes.get(options.node); | ||
this.node = node || this.manager.useableNodes; | ||
if (!this.node) throw new RangeError("No available nodes."); | ||
this.guild_id = options.guild_id; | ||
this.manager.players.set(options.guild_id, this); | ||
this.manager.emit("PlayerCreate", this); | ||
} | ||
|
||
public connect(): this { | ||
if (!this.voiceChannel) throw new RangeError("No voice channel has been set."); | ||
this.state = "CONNECTING"; | ||
|
||
this.manager.options.send(this.guild_id, { | ||
op: 4, | ||
d: { | ||
guild_id: this.guild_id, | ||
channel_id: this.voiceChannel, | ||
self_mute: this.options.selfMute || false, | ||
self_deaf: this.options.selfDeafen || false, | ||
}, | ||
}); | ||
|
||
this.state = "CONNECTED"; | ||
return this; | ||
} | ||
public disconnect(): this { | ||
if (this.voiceChannel === null) return this; | ||
this.state = "DISCONNECTING"; | ||
|
||
this.pause(true); | ||
this.manager.options.send(this.guild_id, { | ||
op: 4, | ||
d: { | ||
guild_id: this.guild_id, | ||
channel_id: null, | ||
self_mute: false, | ||
self_deaf: false, | ||
}, | ||
}); | ||
|
||
this.voiceChannel = null; | ||
this.state = "DISCONNECTED"; | ||
return this; | ||
} | ||
|
||
/** Destroys the player. */ | ||
public destroy(disconnect = true): void { | ||
this.state = "DESTROYING"; | ||
|
||
if (disconnect) { | ||
this.disconnect(); | ||
} | ||
|
||
//this.node.rest.destroyPlayer(this.guild); | ||
this.manager.emit("PlayerDestroy", this); | ||
this.manager.players.delete(this.guild_id); | ||
} | ||
public pause(pause: boolean): this { | ||
if (typeof pause !== "boolean") throw new RangeError('Pause can only be "true" or "false".'); | ||
|
||
if (this.paused === pause || !this.queue.totalSize) return this; | ||
|
||
const oldPlayer = { ...this }; | ||
|
||
this.playing = !pause; | ||
this.paused = pause; | ||
|
||
this.node.rest.updatePlayer({ | ||
guildId: this.guild_id, | ||
data: { | ||
paused: pause, | ||
}, | ||
}); | ||
|
||
this.manager.emit("PlayerStateUpdate", oldPlayer, this); | ||
return this; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export class Queue { | ||
totalSize: number = 0; | ||
constructor() { | ||
|
||
} | ||
} |
Oops, something went wrong.