Various fixes and improvements
- Added debug statements - Added option to limit commands to specific roles - Handle process exiting explicitly
This commit is contained in:
parent
1fc86a56c2
commit
2a3a034e59
21
@types/Controller.d.ts
vendored
21
@types/Controller.d.ts
vendored
@ -1,17 +1,6 @@
|
|||||||
import { LoggerMasterOptions } from '@navy.gif/logger';
|
import { LoggerMasterOptions } from '@navy.gif/logger';
|
||||||
import { ClientOptions } from './DiscordClient.js';
|
import { ClientOptions } from './DiscordClient.js';
|
||||||
|
|
||||||
export type ControllerOptions = {
|
|
||||||
rootDir: string,
|
|
||||||
logger: LoggerMasterOptions,
|
|
||||||
shardOptions: {
|
|
||||||
totalShards: 'auto' | number,
|
|
||||||
shardList?: 'auto' | number[]
|
|
||||||
respawn?: boolean,
|
|
||||||
},
|
|
||||||
discord: ClientOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ShardingOptions = {
|
export type ShardingOptions = {
|
||||||
shardList?: 'auto' | number[],
|
shardList?: 'auto' | number[],
|
||||||
totalShards?: 'auto' | number,
|
totalShards?: 'auto' | number,
|
||||||
@ -21,5 +10,13 @@ export type ShardingOptions = {
|
|||||||
execArgv?: string[],
|
execArgv?: string[],
|
||||||
token?: string,
|
token?: string,
|
||||||
path?: string,
|
path?: string,
|
||||||
clientOptions?: ClientOptions
|
clientOptions?: ClientOptions,
|
||||||
|
debug?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ControllerOptions = {
|
||||||
|
rootDir: string,
|
||||||
|
logger: LoggerMasterOptions,
|
||||||
|
shardOptions: ShardingOptions,
|
||||||
|
discord: ClientOptions
|
||||||
}
|
}
|
3
@types/Shard.d.ts
vendored
3
@types/Shard.d.ts
vendored
@ -5,7 +5,8 @@ export type ShardOptions = {
|
|||||||
args?: string[];
|
args?: string[];
|
||||||
respawn?: boolean,
|
respawn?: boolean,
|
||||||
clientOptions: ClientOptions
|
clientOptions: ClientOptions
|
||||||
totalShards: number
|
totalShards: number,
|
||||||
|
debug?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ShardMethod = 'eval' | 'fetchClientValue'
|
export type ShardMethod = 'eval' | 'fetchClientValue'
|
@ -17,5 +17,5 @@ COPY build build
|
|||||||
COPY --from=builder /musicbot/node_modules ./node_modules
|
COPY --from=builder /musicbot/node_modules ./node_modules
|
||||||
COPY package.json package.json
|
COPY package.json package.json
|
||||||
VOLUME [ "/musicbot/cache" ]
|
VOLUME [ "/musicbot/cache" ]
|
||||||
# CMD ["node", "--enable-source-maps", "build/index.js"]
|
CMD ["node", "--enable-source-maps", "build/index.js"]
|
||||||
CMD ["/bin/ash"]
|
# CMD ["/bin/ash"]
|
@ -68,8 +68,8 @@ class DiscordClient extends Client
|
|||||||
});
|
});
|
||||||
|
|
||||||
process.on('message', this.#handleMessage.bind(this));
|
process.on('message', this.#handleMessage.bind(this));
|
||||||
process.on('SIGINT', () => this.shutdown());
|
process.on('SIGINT', () => this.#logger.info('Received SIGINT'));
|
||||||
process.on('SIGTERM', () => this.shutdown());
|
process.on('SIGTERM', () => this.#logger.info('Received SIGTERM'));
|
||||||
|
|
||||||
this.#built = false;
|
this.#built = false;
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import similarity from 'similarity';
|
|||||||
import MusicDownloader from './MusicDownloader.js';
|
import MusicDownloader from './MusicDownloader.js';
|
||||||
import MusicPlayerError from '../../errors/MusicPlayerError.js';
|
import MusicPlayerError from '../../errors/MusicPlayerError.js';
|
||||||
import { DownloaderResult } from '../../../@types/Downloader.js';
|
import { DownloaderResult } from '../../../@types/Downloader.js';
|
||||||
|
import { inspect } from 'node:util';
|
||||||
|
|
||||||
const linkReg = /(https?:\/\/(www\.)?)?(?<domain>([a-z0-9-]{1,63}\.)?([a-z0-9-]{1,63})(\.[a-z0-9-]{2,63})(\.[a-z0-9-]{2,63})?)(\/[^()\s]*)?/iu;
|
const linkReg = /(https?:\/\/(www\.)?)?(?<domain>([a-z0-9-]{1,63}\.)?([a-z0-9-]{1,63})(\.[a-z0-9-]{2,63})(\.[a-z0-9-]{2,63})?)(\/[^()\s]*)?/iu;
|
||||||
const defaultStats: MusicStatsEntry = {
|
const defaultStats: MusicStatsEntry = {
|
||||||
@ -91,12 +92,14 @@ class MusicLibrary implements Initialisable
|
|||||||
if (!domain)
|
if (!domain)
|
||||||
throw new MusicPlayerError('Invalid link');
|
throw new MusicPlayerError('Invalid link');
|
||||||
|
|
||||||
|
this.#logger.debug(`Requesting download for ${keyword}`);
|
||||||
let result: DownloaderResult | null = null;
|
let result: DownloaderResult | null = null;
|
||||||
if (domain.includes('spotify.com'))
|
if (domain.includes('spotify.com'))
|
||||||
result = await this.#downloader.download('spotify', keyword);
|
result = await this.#downloader.download('spotify', keyword);
|
||||||
else
|
else
|
||||||
throw new MusicPlayerError('Unsupported domain');
|
throw new MusicPlayerError('Unsupported domain');
|
||||||
|
|
||||||
|
this.#logger.debug(`Result ${inspect(result)} for keyword ${keyword}`);
|
||||||
if (!result)
|
if (!result)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ class MusicPlayer implements Initialisable
|
|||||||
this.playNext();
|
this.playNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
stop (): void | Promise<void>
|
async stop (): Promise<void>
|
||||||
{
|
{
|
||||||
this.#ready = false;
|
this.#ready = false;
|
||||||
this.#logger.info('Stopping music player');
|
this.#logger.info('Stopping music player');
|
||||||
@ -91,9 +91,10 @@ class MusicPlayer implements Initialisable
|
|||||||
{
|
{
|
||||||
this.#logger.debug(`Disconnecting ${guildId}`);
|
this.#logger.debug(`Disconnecting ${guildId}`);
|
||||||
subscription.unsubscribe();
|
subscription.unsubscribe();
|
||||||
|
connection.removeAllListeners();
|
||||||
connection.disconnect();
|
connection.disconnect();
|
||||||
}
|
}
|
||||||
this.#library.stop();
|
await this.#library.stop();
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
volume: this.#volume,
|
volume: this.#volume,
|
||||||
@ -239,6 +240,7 @@ class MusicPlayer implements Initialisable
|
|||||||
guildId: guild.id,
|
guildId: guild.id,
|
||||||
adapterCreator: guild.voiceAdapterCreator
|
adapterCreator: guild.voiceAdapterCreator
|
||||||
});
|
});
|
||||||
|
connection.removeAllListeners();
|
||||||
|
|
||||||
const subscription = connection.subscribe(this.#player);
|
const subscription = connection.subscribe(this.#player);
|
||||||
if (!subscription)
|
if (!subscription)
|
||||||
@ -270,6 +272,8 @@ class MusicPlayer implements Initialisable
|
|||||||
async #handleDisconnect (oldState: VoiceConnectionState, newState: VoiceConnectionState, guild: Guild): Promise<void>
|
async #handleDisconnect (oldState: VoiceConnectionState, newState: VoiceConnectionState, guild: Guild): Promise<void>
|
||||||
{
|
{
|
||||||
this.#logger.debug(`Voice connection in ${guild.name} changed state from ${oldState.status} to ${newState.status}`);
|
this.#logger.debug(`Voice connection in ${guild.name} changed state from ${oldState.status} to ${newState.status}`);
|
||||||
|
if (!this.#ready)
|
||||||
|
return;
|
||||||
const connectionData = this.#connections.get(guild.id);
|
const connectionData = this.#connections.get(guild.id);
|
||||||
if (!connectionData)
|
if (!connectionData)
|
||||||
return;
|
return;
|
||||||
@ -279,16 +283,22 @@ class MusicPlayer implements Initialisable
|
|||||||
const { me } = guild.members;
|
const { me } = guild.members;
|
||||||
if (!me || !config)
|
if (!me || !config)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Promise.race([
|
await Promise.race([
|
||||||
entersState(connection, VoiceConnectionStatus.Signalling, 5_000),
|
entersState(connection, VoiceConnectionStatus.Signalling, 5_000),
|
||||||
entersState(connection, VoiceConnectionStatus.Connecting, 5_000)
|
entersState(connection, VoiceConnectionStatus.Connecting, 5_000)
|
||||||
]);
|
]);
|
||||||
|
this.#logger.info('Connection was restored, checking if in right VC');
|
||||||
|
// console.log(me.voice.channelId, config.voiceChannel);
|
||||||
// If we find ourselves here, the bot was successfully able to reconnect to a VC
|
// If we find ourselves here, the bot was successfully able to reconnect to a VC
|
||||||
// Now we check if it's the one it's meant to be in
|
// Now we check if it's the one it's meant to be in
|
||||||
if (me.voice.channelId !== config.voiceChannel)
|
if (me.voice.channelId !== config.voiceChannel)
|
||||||
|
{
|
||||||
|
this.#logger.info('Moving back to designated channel');
|
||||||
this.#joinVoiceChannel(guild, config.voiceChannel, config.textOutput);
|
this.#joinVoiceChannel(guild, config.voiceChannel, config.textOutput);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@ -329,6 +339,11 @@ class MusicPlayer implements Initialisable
|
|||||||
return this.#queue;
|
return this.#queue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get current ()
|
||||||
|
{
|
||||||
|
return this.#currentSong;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MusicPlayer;
|
export default MusicPlayer;
|
@ -20,7 +20,7 @@ class QueueCommand extends Command
|
|||||||
type: OptionType.INTEGER
|
type: OptionType.INTEGER
|
||||||
}, {
|
}, {
|
||||||
name: 'song'
|
name: 'song'
|
||||||
}],
|
}]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,12 +31,11 @@ class QueueCommand extends Command
|
|||||||
|
|
||||||
if (!Object.keys(args).length)
|
if (!Object.keys(args).length)
|
||||||
{
|
{
|
||||||
const { queue } = this.client.musicPlayer;
|
const { queue, current } = this.client.musicPlayer;
|
||||||
|
const base = `**Now playing:** \`${current?.title} by ${current?.artist}\`\n`;
|
||||||
if (!queue.length)
|
if (!queue.length)
|
||||||
return 'Queue empty';
|
return base + '**Queue empty**';
|
||||||
return `
|
return base + `**Music queue:**\n\`\`\`${queue.map((entry, idx) => `\t[${idx + 1}] ${entry.title} by ${entry.artist}`).join('\n')}\`\`\``;
|
||||||
**Music queue:**\n\`\`\`${queue.map(entry => `\t\\- ${entry.title} by ${entry.artist}`).join('\n')}\`\`\`
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!member?.voice || member.voice.channelId !== me?.voice.channelId)
|
if (!member?.voice || member.voice.channelId !== me?.voice.channelId)
|
||||||
|
@ -10,8 +10,9 @@ class SkipCommand extends Command
|
|||||||
name: 'skip',
|
name: 'skip',
|
||||||
description: 'Skips the current song.',
|
description: 'Skips the current song.',
|
||||||
guildOnly: true,
|
guildOnly: true,
|
||||||
restricted: true,
|
// restricted: true,
|
||||||
sameVc: true
|
sameVc: true,
|
||||||
|
limited: [ '1076274430520594514' ]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,8 +21,9 @@ class VolumeCommand extends Command
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
guildOnly: true,
|
guildOnly: true,
|
||||||
restricted: true,
|
// restricted: true,
|
||||||
sameVc: true,
|
sameVc: true,
|
||||||
|
limited: [ '1076274430520594514' ],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
34
src/client/components/inhibitors/Limited.ts
Normal file
34
src/client/components/inhibitors/Limited.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { Message } from 'discord.js';
|
||||||
|
import Inhibitor from '../../../interfaces/Inhibitor.js';
|
||||||
|
import DiscordClient from '../../DiscordClient.js';
|
||||||
|
import Command from '../../../interfaces/Command.js';
|
||||||
|
import { InhibitorResponse } from '../../../../@types/DiscordClient.js';
|
||||||
|
import Util from '../../../utilities/Util.js';
|
||||||
|
|
||||||
|
class LimitedInhibitor extends Inhibitor
|
||||||
|
{
|
||||||
|
constructor (client: DiscordClient)
|
||||||
|
{
|
||||||
|
super(client, {
|
||||||
|
name: 'Limited',
|
||||||
|
priority: 5
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
override async execute (message: Message<boolean>, command: Command): Promise<InhibitorResponse>
|
||||||
|
{
|
||||||
|
if (!command.limited?.length || this.client.isDeveloper(message.author))
|
||||||
|
return super._succeed();
|
||||||
|
const { member } = message;
|
||||||
|
if (!member)
|
||||||
|
return super._fail('Invalid member');
|
||||||
|
|
||||||
|
const allowedRoles = command.limited;
|
||||||
|
if (Util.hasAny(member.roles.cache.map(role => role.id), allowedRoles))
|
||||||
|
return super._succeed();
|
||||||
|
return super._fail('Missing permissions to do that');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LimitedInhibitor;
|
@ -23,7 +23,6 @@ class Controller
|
|||||||
|
|
||||||
constructor (options: ControllerOptions, version: string)
|
constructor (options: ControllerOptions, version: string)
|
||||||
{
|
{
|
||||||
const respawn = process.env.NODE_ENV !== 'development';
|
|
||||||
const clientPath = path.join(options.rootDir, 'client/DiscordClient.js');
|
const clientPath = path.join(options.rootDir, 'client/DiscordClient.js');
|
||||||
if (!fs.existsSync(clientPath))
|
if (!fs.existsSync(clientPath))
|
||||||
throw new Error(`Client path does not seem to exist: ${clientPath}`);
|
throw new Error(`Client path does not seem to exist: ${clientPath}`);
|
||||||
@ -32,7 +31,7 @@ class Controller
|
|||||||
this.#shards = new Collection();
|
this.#shards = new Collection();
|
||||||
|
|
||||||
// this.#options = options;
|
// this.#options = options;
|
||||||
const { shardList, totalShards, execArgv } = Controller.parseShardOptions(options.shardOptions);
|
const { shardList, totalShards, execArgv, respawn, debug } = Controller.parseShardOptions(options.shardOptions);
|
||||||
|
|
||||||
options.discord.rootDir = options.rootDir;
|
options.discord.rootDir = options.rootDir;
|
||||||
options.discord.logger = options.logger;
|
options.discord.logger = options.logger;
|
||||||
@ -46,6 +45,7 @@ class Controller
|
|||||||
execArgv,
|
execArgv,
|
||||||
token: process.env.DISCORD_TOKEN,
|
token: process.env.DISCORD_TOKEN,
|
||||||
clientOptions: options.discord,
|
clientOptions: options.discord,
|
||||||
|
debug
|
||||||
};
|
};
|
||||||
|
|
||||||
this.#version = version;
|
this.#version = version;
|
||||||
@ -141,15 +141,16 @@ class Controller
|
|||||||
|
|
||||||
#setListeners (shard: Shard)
|
#setListeners (shard: Shard)
|
||||||
{
|
{
|
||||||
shard.on('death', () => this.#logger.info(`Shard ${shard.id} has died`));
|
shard.on('death', () => this.#logger.info(`Shard ${shard.id} has died`))
|
||||||
shard.on('fatal', ({ error }) => this.#logger.warn(`Shard ${shard.id} has died fatally: ${inspect(error) ?? ''}`));
|
.on('fatal', ({ error }) => this.#logger.warn(`Shard ${shard.id} has died fatally: ${inspect(error) ?? ''}`))
|
||||||
shard.on('shutdown', () => this.#logger.info(`Shard ${shard.id} is shutting down gracefully`));
|
.on('shutdown', () => this.#logger.info(`Shard ${shard.id} is shutting down gracefully`))
|
||||||
shard.on('ready', () => this.#logger.info(`Shard ${shard.id} is ready`));
|
.on('ready', () => this.#logger.info(`Shard ${shard.id} is ready`))
|
||||||
shard.on('disconnect', () => this.#logger.warn(`Shard ${shard.id} has disconnected`));
|
.on('disconnect', () => this.#logger.warn(`Shard ${shard.id} has disconnected`))
|
||||||
shard.on('processDisconnect', () => this.#logger.warn(`Process for ${shard.id} has disconnected`));
|
.on('processDisconnect', () => this.#logger.warn(`Process for ${shard.id} has disconnected`))
|
||||||
shard.on('spawn', () => this.#logger.info(`Shard ${shard.id} spawned`));
|
.on('spawn', () => this.#logger.info(`Shard ${shard.id} spawned`))
|
||||||
shard.on('error', (err) => this.#logger.error(`Shard ${shard.id} ran into an error:\n${err.stack}`));
|
.on('error', (err) => this.#logger.error(`Shard ${shard.id} ran into an error:\n${err.stack}`))
|
||||||
shard.on('warn', (msg) => this.#logger.warn(`Warning from shard ${shard.id}: ${msg}`, { broadcast: true }));
|
.on('warn', (msg) => this.#logger.warn(`Warning from shard ${shard.id}: ${msg}`, { broadcast: true }))
|
||||||
|
.on('debug', msg => this.#logger.debug(msg));
|
||||||
|
|
||||||
// shard.on('message', (msg) => this.#handleMessage(shard, msg));
|
// shard.on('message', (msg) => this.#handleMessage(shard, msg));
|
||||||
}
|
}
|
||||||
@ -231,7 +232,10 @@ class Controller
|
|||||||
let { execArgv } = options;
|
let { execArgv } = options;
|
||||||
if (!execArgv)
|
if (!execArgv)
|
||||||
execArgv = [];
|
execArgv = [];
|
||||||
return { shardList, totalShards, execArgv };
|
|
||||||
|
const respawn = (process.env.NODE_ENV !== 'development' || options.respawn) ?? false;
|
||||||
|
|
||||||
|
return { shardList, totalShards, execArgv, respawn, debug: options.debug ?? false };
|
||||||
}
|
}
|
||||||
|
|
||||||
async shutdown (type: string)
|
async shutdown (type: string)
|
||||||
@ -239,16 +243,15 @@ class Controller
|
|||||||
if (this.#exiting)
|
if (this.#exiting)
|
||||||
return;
|
return;
|
||||||
this.#exiting = true;
|
this.#exiting = true;
|
||||||
this.#logger.info('Received SIGINT or SIGTERM, shutting down');
|
this.#logger.info(`Received ${type}, shutting down`);
|
||||||
setTimeout(process.exit, 90_000);
|
setTimeout(process.exit, 90_000);
|
||||||
const promises = this.#shards
|
const promises = this.#shards
|
||||||
.filter(shard => shard.ready)
|
.filter(shard => shard.ready)
|
||||||
.map(shard =>
|
.map(shard =>
|
||||||
{
|
{
|
||||||
if (type === 'SIGTERM')
|
return shard.kill();
|
||||||
return shard.kill();
|
// return shard.awaitShutdown()
|
||||||
return shard.awaitShutdown()
|
// .then(() => shard.removeAllListeners());
|
||||||
.then(() => shard.removeAllListeners());
|
|
||||||
});
|
});
|
||||||
if (promises.length)
|
if (promises.length)
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
|
@ -15,6 +15,7 @@ class Shard extends EventEmitter
|
|||||||
{
|
{
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
|
|
||||||
|
#debug: boolean;
|
||||||
#id: number;
|
#id: number;
|
||||||
#controller: Controller;
|
#controller: Controller;
|
||||||
#env: EnvObject; // { [key: string]: string | boolean | number };
|
#env: EnvObject; // { [key: string]: string | boolean | number };
|
||||||
@ -43,6 +44,7 @@ class Shard extends EventEmitter
|
|||||||
super();
|
super();
|
||||||
this.#controller = controller;
|
this.#controller = controller;
|
||||||
this.#id = id;
|
this.#id = id;
|
||||||
|
this.#debug = options.debug ?? false;
|
||||||
|
|
||||||
this.#args = options.args ?? [];
|
this.#args = options.args ?? [];
|
||||||
this.#execArgv = options.execArgv ?? [];
|
this.#execArgv = options.execArgv ?? [];
|
||||||
@ -150,19 +152,19 @@ class Shard extends EventEmitter
|
|||||||
const onDisconnect = () =>
|
const onDisconnect = () =>
|
||||||
{
|
{
|
||||||
cleanup();
|
cleanup();
|
||||||
reject(new Error(`[shard${this.id}] Shard disconnected while readying.`));
|
reject(new Error(`[SHARD-${this.id}] Shard disconnected while readying.`));
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDeath = () =>
|
const onDeath = () =>
|
||||||
{
|
{
|
||||||
cleanup();
|
cleanup();
|
||||||
reject(new Error(`[shard${this.id}] Shard died while readying.`));
|
reject(new Error(`[SHARD-${this.id}] Shard died while readying.`));
|
||||||
};
|
};
|
||||||
|
|
||||||
const onTimeout = () =>
|
const onTimeout = () =>
|
||||||
{
|
{
|
||||||
cleanup(true);
|
cleanup(true);
|
||||||
reject(new Error(`[shard${this.id}] Shard timed out while readying.`));
|
reject(new Error(`[SHARD-${this.id}] Shard timed out while readying.`));
|
||||||
};
|
};
|
||||||
|
|
||||||
const spawnTimeoutTimer = setTimeout(onTimeout, timeout);
|
const spawnTimeoutTimer = setTimeout(onTimeout, timeout);
|
||||||
@ -172,7 +174,7 @@ class Shard extends EventEmitter
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// When killing the process, give it an opportonity to gracefully shut down (i.e. clean up DB connections etc)
|
// When killing the process, give it an opportunity to gracefully shut down (i.e. clean up DB connections etc)
|
||||||
// It simply has to respond with a shutdown message to the shutdown event
|
// It simply has to respond with a shutdown message to the shutdown event
|
||||||
kill ()
|
kill ()
|
||||||
{
|
{
|
||||||
@ -180,6 +182,7 @@ class Shard extends EventEmitter
|
|||||||
{
|
{
|
||||||
return new Promise<void>((resolve) =>
|
return new Promise<void>((resolve) =>
|
||||||
{
|
{
|
||||||
|
this.debug('Shard.kill');
|
||||||
if (!this.#process)
|
if (!this.#process)
|
||||||
return resolve();
|
return resolve();
|
||||||
|
|
||||||
@ -187,6 +190,7 @@ class Shard extends EventEmitter
|
|||||||
|
|
||||||
const timeout = setTimeout(() =>
|
const timeout = setTimeout(() =>
|
||||||
{
|
{
|
||||||
|
this.debug('Shard.kill timeout');
|
||||||
if (!this.#process)
|
if (!this.#process)
|
||||||
return resolve();
|
return resolve();
|
||||||
this.#process.kill();
|
this.#process.kill();
|
||||||
@ -195,6 +199,7 @@ class Shard extends EventEmitter
|
|||||||
|
|
||||||
this.#process.once('exit', (code, signal) =>
|
this.#process.once('exit', (code, signal) =>
|
||||||
{
|
{
|
||||||
|
this.debug('Shard.kill exit listener');
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
this.#handleExit(code, signal, false);
|
this.#handleExit(code, signal, false);
|
||||||
resolve();
|
resolve();
|
||||||
@ -202,6 +207,7 @@ class Shard extends EventEmitter
|
|||||||
|
|
||||||
this.once('shutdown', () =>
|
this.once('shutdown', () =>
|
||||||
{
|
{
|
||||||
|
this.debug('Shard.kill shutdown listener');
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -457,6 +463,12 @@ class Shard extends EventEmitter
|
|||||||
if (respawn)
|
if (respawn)
|
||||||
this.spawn().catch((error: Error) => this.emit('error', error));
|
this.spawn().catch((error: Error) => this.emit('error', error));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private debug (msg: string)
|
||||||
|
{
|
||||||
|
if (this.#debug && this.listenerCount('debug'))
|
||||||
|
this.emit('debug', `[SHARD-${this.id}] ${msg}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Shard;
|
export default Shard;
|
@ -587,6 +587,21 @@ class Util
|
|||||||
return Object.keys(obj).some(key => [ 'title', 'fields', 'description', 'image', 'video' ].includes(key));
|
return Object.keys(obj).some(key => [ 'title', 'fields', 'description', 'image', 'video' ].includes(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an array contains any values from the reference array
|
||||||
|
* @date 4/13/2024 - 10:31:57 AM
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
* @template [T=unknown]
|
||||||
|
* @param {T[]} target
|
||||||
|
* @param {T[]} reference
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
static hasAny<T=unknown> (target: T[], reference: T[]): boolean
|
||||||
|
{
|
||||||
|
return target.some(entry => reference.includes(entry));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Util;
|
export default Util;
|
Loading…
Reference in New Issue
Block a user