damonjs
Version:
A modified Shoukaku wrapper with enhanced queue support.
231 lines (230 loc) • 10.9 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DamonJs = void 0;
const events_1 = require("events");
const Interfaces_1 = require("./Modules/Interfaces");
const shoukaku_1 = require("shoukaku");
const DamonJsPlayer_1 = require("./Managers/DamonJsPlayer");
const DamonJsTrack_1 = require("./Managers/Supports/DamonJsTrack");
// Add other methods related to your base class
class DamonJs extends events_1.EventEmitter {
/**
* Initialize a DamonJs instance.
* @param DamonJsOptions DamonJsOptions
* @param connector Connector
* @param nodes NodeOption[]
* @param options ShoukakuOptions
*/
constructor(DamonJsOptions, shoukaku) {
super();
this.DamonJsOptions = DamonJsOptions;
this.shoukaku = shoukaku;
this.skipSpam = this.DamonJsOptions.skipSpam
? this.DamonJsOptions.skipSpam
: {
rule: { maxhits: 3, timeFrame: 30 * 1000, cooldown: 5 * 1000 },
destroy: { maxhits: 4, timeFrame: 30 * 1000 },
};
this.trackEnd = this.DamonJsOptions.trackEnd ? this.DamonJsOptions.trackEnd : { skip: true };
this.trackException = this.DamonJsOptions.trackException ? this.DamonJsOptions.trackException : { skip: true };
this.trackStuck = this.DamonJsOptions.trackStuck ? this.DamonJsOptions.trackStuck : { skip: true };
this.trackResolveError = this.DamonJsOptions.trackResolveError
? this.DamonJsOptions.trackResolveError
: { skip: true };
if (this.DamonJsOptions.plugins) {
for (const [, plugin] of this.DamonJsOptions.plugins.entries()) {
if (plugin.constructor.name !== 'DamonJsPlugin')
throw new Interfaces_1.DamonJsError(1, 'Plugin must be an instance of DamonJsPlugin');
plugin.load(this);
}
}
this.shoukaku.joinVoiceChannel = this.joinVoiceChannel.bind(this);
this.players = new Map();
}
on(event, listener) {
super.on(event, (...args) => listener(...args));
return this;
}
once(event, listener) {
super.once(event, (...args) => listener(...args));
return this;
}
off(event, listener) {
super.off(event, (...args) => listener(...args));
return this;
}
emit(event, ...data) {
return super.emit(event, ...data);
}
/**
* Create a player.
* @param options CreatePlayerOptions
* @returns Promise<DamonJsPlayer>
*/
createPlayer(options) {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
const exist = this.players.get(options.guildId);
if (exist)
return exist;
if (!options.deaf)
options.deaf = false;
if (!options.mute)
options.mute = false;
const shoukakuPlayer = yield this.shoukaku.joinVoiceChannel({
guildId: options.guildId,
channelId: options.voiceId,
deaf: options.deaf,
mute: options.mute,
shardId: options.shardId && !isNaN(options.shardId) ? options.shardId : 0,
});
const shoukakuConnection = this.shoukaku.connections.get(options.guildId);
if (!shoukakuConnection)
throw new Interfaces_1.DamonJsError(1, 'Cannot find the shoukaku connection');
const damonjsPlayer = new ((_b = (_a = this.DamonJsOptions.extends) === null || _a === void 0 ? void 0 : _a.player) !== null && _b !== void 0 ? _b : DamonJsPlayer_1.DamonJsPlayer)(this, this.shoukaku, shoukakuPlayer, shoukakuConnection, {
data: options.data,
textId: options.textId,
volume: isNaN(Number(options.volume)) ? 100 : options.volume,
});
yield damonjsPlayer.init();
this.players.set(options.guildId, damonjsPlayer);
this.emit(Interfaces_1.Events.PlayerCreate, damonjsPlayer);
return damonjsPlayer;
});
}
/**
* Get a player by guildId.
* @param guildId Guild ID
* @returns DamonJsPlayer | undefined
*/
getPlayer(guildId) {
return this.players.get(guildId);
}
/**
* Destroy a player.
* @param guildId Guild ID
* @returns void
*/
destroyPlayer(guildId) {
const player = this.getPlayer(guildId);
if (!player)
return;
player.destroy();
}
/**
* Search a track by query or uri.
* @param player DamonJs Player
* @param query Query
* @param options DamonJsOptions
* @returns Promise<DamonJsSearchResult>
*/
search(query, options, player) {
return __awaiter(this, void 0, void 0, function* () {
const source = Interfaces_1.SourceIDs[((options === null || options === void 0 ? void 0 : options.engine) && ['youtube', 'youtube_music', 'soundcloud'].includes(options.engine)
? options.engine
: null) ||
(!!this.DamonJsOptions.defaultSearchEngine &&
['youtube', 'youtube_music', 'soundcloud'].includes(this.DamonJsOptions.defaultSearchEngine)
? this.DamonJsOptions.defaultSearchEngine
: null) ||
'youtube'];
const isUrl = /^https?:\/\/.*/.test(query);
const node = player ? player.node : yield this.getLeastUsedNode().catch((_) => null);
if (!node)
throw new Interfaces_1.DamonJsError(2, 'No nodes are online');
const result = yield node.rest.resolve(!isUrl ? `${source}search:${query}` : query).catch((_) => null);
if ((result === null || result === void 0 ? void 0 : result.loadType) === shoukaku_1.LoadType.TRACK && result.data) {
return this.buildSearch(undefined, [new DamonJsTrack_1.DamonJsTrack(result.data, options.requester)], Interfaces_1.SearchResultTypes.Track);
}
else if ((result === null || result === void 0 ? void 0 : result.loadType) === shoukaku_1.LoadType.PLAYLIST && result.data.tracks.length) {
return this.buildSearch(result.data, result.data.tracks.map((track) => new DamonJsTrack_1.DamonJsTrack(track, options.requester)), Interfaces_1.SearchResultTypes.Playlist);
}
else if ((result === null || result === void 0 ? void 0 : result.loadType) === shoukaku_1.LoadType.SEARCH && result.data.length) {
return this.buildSearch(undefined, result.data.map((track) => new DamonJsTrack_1.DamonJsTrack(track, options.requester)), Interfaces_1.SearchResultTypes.Search);
}
else if ((result === null || result === void 0 ? void 0 : result.loadType) === shoukaku_1.LoadType.EMPTY) {
return this.buildSearch(undefined, [], Interfaces_1.SearchResultTypes.Empty);
}
else {
return this.buildSearch(undefined, undefined, Interfaces_1.SearchResultTypes.Error);
}
});
}
/**
* Retrieves the least used node from the list of nodes.
* inspired from kazagumo
* @return {Promise<Node>} The least used node
*/
getLeastUsedNode() {
return __awaiter(this, void 0, void 0, function* () {
const nodes = [...this.shoukaku.nodes.values()];
const onlineNodes = nodes.filter((node) => node.state === shoukaku_1.Constants.State.CONNECTED);
if (!onlineNodes.length)
throw new Interfaces_1.DamonJsError(2, 'No nodes are online');
const temp = yield Promise.all(onlineNodes.map((node) => __awaiter(this, void 0, void 0, function* () {
return ({
node,
players: (yield node.rest.getPlayers())
.filter((x) => this.players.get(x.guildId))
.map((x) => this.players.get(x.guildId))
.filter((x) => x.node.name === node.name).length,
});
})));
return temp.reduce((a, b) => (a.players + a.node.penalties < b.players + b.node.penalties ? a : b)).node;
});
}
buildSearch(playlistInfo, tracks = [], type) {
return {
playlistInfo,
tracks,
type: type !== null && type !== void 0 ? type : Interfaces_1.SearchResultTypes.Search,
};
}
joinVoiceChannel(options) {
return __awaiter(this, void 0, void 0, function* () {
if (this.shoukaku.connections.has(options.guildId))
throw new Error('This guild already have an existing connection');
const connection = new shoukaku_1.Connection(this.shoukaku, options);
this.shoukaku.connections.set(connection.guildId, connection);
try {
yield connection.connect();
}
catch (error) {
this.shoukaku.connections.delete(options.guildId);
throw error;
}
try {
const node = yield this.getLeastUsedNode().catch((_) => null);
if (!node)
throw new Error("Can't find any nodes to connect on");
const player = this.shoukaku.options.structures.player
? new this.shoukaku.options.structures.player(connection.guildId, node)
: new shoukaku_1.Player(connection.guildId, node);
const onUpdate = (state) => {
if (state !== shoukaku_1.Constants.VoiceState.SESSION_READY)
return;
player.sendServerUpdate(connection);
};
yield player.sendServerUpdate(connection);
connection.on('connectionUpdate', onUpdate);
this.shoukaku.players.set(player.guildId, player);
return player;
}
catch (error) {
connection.disconnect();
this.shoukaku.connections.delete(options.guildId);
throw error;
}
});
}
}
exports.DamonJs = DamonJs;