forked from Galactic/galactic-bot
Compare commits
6 Commits
main
...
developmen
Author | SHA1 | Date | |
---|---|---|---|
d637a197b2 | |||
2a43ad7feb | |||
a3e6793632 | |||
5f07fd3e15 | |||
c5c304d5d9 | |||
bc467e4dd9 |
26
.gitea/workflows/Tests.yaml
Normal file
26
.gitea/workflows/Tests.yaml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '*'
|
||||||
|
- '!master'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
name: Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Install node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: latest
|
||||||
|
registry-url: https://registry.corgi.wtf
|
||||||
|
cache: yarn
|
||||||
|
- name: Install deps
|
||||||
|
run: yarn install
|
||||||
|
- name: Lint project
|
||||||
|
run: yarn run eslint src/
|
||||||
|
- name: Build project
|
||||||
|
run: yarn build
|
||||||
|
env:
|
||||||
|
CI: false
|
1
@types/Guild.d.ts
vendored
1
@types/Guild.d.ts
vendored
@ -120,6 +120,7 @@ export type GuildData = {
|
|||||||
caseId?: number,
|
caseId?: number,
|
||||||
settings?: GuildSettings,
|
settings?: GuildSettings,
|
||||||
premium?: number,
|
premium?: number,
|
||||||
|
debug?: boolean,
|
||||||
_version?: string,
|
_version?: string,
|
||||||
_imported?: {
|
_imported?: {
|
||||||
[key: string]: boolean | undefined,
|
[key: string]: boolean | undefined,
|
||||||
|
10
README.md
Normal file
10
README.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Galactic Bot
|
||||||
|
|
||||||
|
Welcome to the Galactic Bot repository.
|
||||||
|
I've decided to open this up as I struggle to find motivation and time to work on this, so contributions welcome.
|
||||||
|
|
||||||
|
I do require that any contributions conform to the linter rules present and code should strive to be legible and follow generally good programming practises. Pull requests must explain what they change/add and why.
|
||||||
|
|
||||||
|
Discussion and questions are to be had in the [discord server](https://discord.gg/WDCTKGp).
|
||||||
|
|
||||||
|
I will hopefully expand on this file and add others to help document this project, as it stands there is very little internal documentation.
|
@ -69,6 +69,7 @@
|
|||||||
"tables": [
|
"tables": [
|
||||||
"guilds",
|
"guilds",
|
||||||
"attachments",
|
"attachments",
|
||||||
|
"messages",
|
||||||
"permissions",
|
"permissions",
|
||||||
"webhooks",
|
"webhooks",
|
||||||
"wordwatcher",
|
"wordwatcher",
|
||||||
|
@ -210,14 +210,6 @@ class DiscordClient extends Client
|
|||||||
this.emit('invalidRequestWarning', ...args);
|
this.emit('invalidRequestWarning', ...args);
|
||||||
});
|
});
|
||||||
|
|
||||||
// this.once('ready', () => {
|
|
||||||
// this._setActivity();
|
|
||||||
|
|
||||||
// setInterval(() => {
|
|
||||||
// this._setActivity();
|
|
||||||
// }, 1800000); // I think this is 30 minutes. I could be wrong.
|
|
||||||
// });
|
|
||||||
|
|
||||||
this.#loadEevents();
|
this.#loadEevents();
|
||||||
|
|
||||||
// process.on('uncaughtException', (err) => {
|
// process.on('uncaughtException', (err) => {
|
||||||
@ -230,7 +222,13 @@ 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.logger.info('Received SIGTERM'));
|
||||||
|
process.on('disconnect', () =>
|
||||||
|
{
|
||||||
|
this.logger.info('Process disconnected from parent for some reason, exiting');
|
||||||
|
this.shutdown(1);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async build ()
|
async build ()
|
||||||
@ -271,7 +269,7 @@ class DiscordClient extends Client
|
|||||||
this.#activityInterval = setInterval(() =>
|
this.#activityInterval = setInterval(() =>
|
||||||
{
|
{
|
||||||
this.setActivity();
|
this.setActivity();
|
||||||
}, Util.random(5, 10) * 60 * 60 * 1000);
|
}, Util.random(5, 10) * 60 * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#built = true;
|
this.#built = true;
|
||||||
@ -296,6 +294,8 @@ class DiscordClient extends Client
|
|||||||
// Handle misc. messages.
|
// Handle misc. messages.
|
||||||
if (message._mEvalResult)
|
if (message._mEvalResult)
|
||||||
this.evalResult(message);
|
this.evalResult(message);
|
||||||
|
if (message._shutdown)
|
||||||
|
this.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
@ -397,6 +397,10 @@ class DiscordClient extends Client
|
|||||||
this.user?.setActivity(`${userCount} users`, { type: ActivityType.Listening });
|
this.user?.setActivity(`${userCount} users`, { type: ActivityType.Listening });
|
||||||
},
|
},
|
||||||
async () =>
|
async () =>
|
||||||
|
{
|
||||||
|
this.user?.setActivity('out for troublemakers', { type: ActivityType.Watching });
|
||||||
|
},
|
||||||
|
async () =>
|
||||||
{
|
{
|
||||||
this.user?.setActivity('for /help', { type: ActivityType.Listening });
|
this.user?.setActivity('for /help', { type: ActivityType.Listening });
|
||||||
}
|
}
|
||||||
|
59
src/client/components/commands/developer/Debug.ts
Normal file
59
src/client/components/commands/developer/Debug.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { CommandOptionType, CommandParams } from '../../../../../@types/Client.js';
|
||||||
|
import DiscordClient from '../../../DiscordClient.js';
|
||||||
|
import { Command } from '../../../interfaces/index.js';
|
||||||
|
import InvokerWrapper from '../../wrappers/InvokerWrapper.js';
|
||||||
|
|
||||||
|
class DebugCommand extends Command
|
||||||
|
{
|
||||||
|
constructor (client: DiscordClient)
|
||||||
|
{
|
||||||
|
super(client, {
|
||||||
|
name: 'debug',
|
||||||
|
restricted: true,
|
||||||
|
moduleName: 'developer',
|
||||||
|
options: [
|
||||||
|
{ name: 'guild', required: true },
|
||||||
|
{ name: 'enabled', required: true, type: CommandOptionType.BOOLEAN }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute (_invoker: InvokerWrapper, options: CommandParams)
|
||||||
|
{
|
||||||
|
const guildId = options.guild!.asString;
|
||||||
|
return this.enableDebug(guildId, options.enabled?.asBool);
|
||||||
|
}
|
||||||
|
|
||||||
|
async enableDebug (guildId: string, enabled = false)
|
||||||
|
{
|
||||||
|
let output = '';
|
||||||
|
if (this.client.shard)
|
||||||
|
{
|
||||||
|
const result = await this.client.shard.broadcastEval(async (client, context) =>
|
||||||
|
{
|
||||||
|
const bot = client as DiscordClient;
|
||||||
|
const wrapper = await bot.getGuildWrapper(context.guildId);
|
||||||
|
if (!wrapper)
|
||||||
|
return false;
|
||||||
|
await wrapper.setDebug(context.enabled);
|
||||||
|
return `${wrapper.name} (${wrapper.id})`;
|
||||||
|
}, { context: { guildId, enabled } });
|
||||||
|
|
||||||
|
if (!result.some(val => val))
|
||||||
|
return 'No such guild';
|
||||||
|
output = result.find(entry => entry) as string;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const wrapper = await this.client.getGuildWrapper(guildId).catch(() => null);
|
||||||
|
if (!wrapper)
|
||||||
|
return 'No such guild';
|
||||||
|
await wrapper.setDebug(enabled);
|
||||||
|
output = `${wrapper.name} (${wrapper.id})`;
|
||||||
|
}
|
||||||
|
return `${enabled ? 'Enabled' : 'Disabled'} debug on **${output}**`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DebugCommand;
|
@ -1,7 +1,5 @@
|
|||||||
import { userInfo } from 'os';
|
import { userInfo } from 'os';
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
import { Util as U, FilterUtil as FU } from '../../../../utilities/index.js';
|
||||||
// @ts-ignore
|
|
||||||
import { Util, FilterUtil } from '../../../../utilities/index.js';
|
|
||||||
import { inspect } from 'util';
|
import { inspect } from 'util';
|
||||||
import { CommandOptionType, CommandParams } from '../../../../../@types/Client.js';
|
import { CommandOptionType, CommandParams } from '../../../../../@types/Client.js';
|
||||||
import DiscordClient from '../../../DiscordClient.js';
|
import DiscordClient from '../../../DiscordClient.js';
|
||||||
@ -12,6 +10,8 @@ import { ReplyOptions } from '../../../../../@types/Wrappers.js';
|
|||||||
const { username } = userInfo();
|
const { username } = userInfo();
|
||||||
class EvalCommand extends Command
|
class EvalCommand extends Command
|
||||||
{
|
{
|
||||||
|
static Util = U;
|
||||||
|
static FilterUtil = FU;
|
||||||
constructor (client: DiscordClient)
|
constructor (client: DiscordClient)
|
||||||
{
|
{
|
||||||
super(client, {
|
super(client, {
|
||||||
@ -32,6 +32,10 @@ class EvalCommand extends Command
|
|||||||
if (async?.asBool)
|
if (async?.asBool)
|
||||||
params = `(async () => {${params}})()`;
|
params = `(async () => {${params}})()`;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore // declared for ease of use
|
||||||
|
const { Util, FilterUtil } = EvalCommand;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore // declared for ease of use
|
// @ts-ignore // declared for ease of use
|
||||||
const { guild, author, member, client } = invoker;
|
const { guild, author, member, client } = invoker;
|
||||||
|
@ -8,7 +8,6 @@ import MemberWrapper from '../../wrappers/MemberWrapper.js';
|
|||||||
|
|
||||||
class AddroleCommand extends ModerationCommand
|
class AddroleCommand extends ModerationCommand
|
||||||
{
|
{
|
||||||
|
|
||||||
constructor (client: DiscordClient)
|
constructor (client: DiscordClient)
|
||||||
{
|
{
|
||||||
super(client, {
|
super(client, {
|
||||||
@ -43,12 +42,10 @@ class AddroleCommand extends ModerationCommand
|
|||||||
clientPermissions: [ 'ManageRoles' ],
|
clientPermissions: [ 'ManageRoles' ],
|
||||||
skipOptions: [ 'points', 'expiration', 'force', 'prune' ]
|
skipOptions: [ 'points', 'expiration', 'force', 'prune' ]
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute (invoker: InvokerWrapper<true>, { users, roles, ...args }: CommandParams)
|
async execute (invoker: InvokerWrapper<true>, { users, roles, ...args }: CommandParams)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (!users)
|
if (!users)
|
||||||
throw new CommandError(invoker, { index: 'MODERATION_MISSING_USERS' });
|
throw new CommandError(invoker, { index: 'MODERATION_MISSING_USERS' });
|
||||||
if (!roles)
|
if (!roles)
|
||||||
@ -66,7 +63,6 @@ class AddroleCommand extends ModerationCommand
|
|||||||
roles: roles.asString
|
roles: roles.asString
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -56,91 +56,76 @@ class PollCommand extends SlashCommand
|
|||||||
|
|
||||||
async execute (invoker: InvokerWrapper<true>, { choices, channel, duration, multichoice, message }: CommandParams)
|
async execute (invoker: InvokerWrapper<true>, { choices, channel, duration, multichoice, message }: CommandParams)
|
||||||
{
|
{
|
||||||
const { subcommand, author } = invoker;
|
const { subcommand } = invoker;
|
||||||
const guild = invoker.guild!;
|
|
||||||
const member = invoker.member!;
|
|
||||||
|
|
||||||
if (subcommand!.name === 'create')
|
if (subcommand!.name === 'create')
|
||||||
{
|
return this.#createPoll(invoker, { choices, channel, duration, multichoice });
|
||||||
const targetChannel = (channel?.asChannel || invoker.channel) as GuildTextBasedChannel;
|
|
||||||
if (!targetChannel?.isTextBased())
|
|
||||||
throw new CommandError(invoker, { index: 'ERR_INVALID_CHANNEL_TYPE' });
|
|
||||||
const botMissing = targetChannel.permissionsFor(this.client.user!)?.missing([ 'SendMessages', 'EmbedLinks' ]);
|
|
||||||
const userMissing = targetChannel.permissionsFor(member).missing([ 'SendMessages' ]);
|
|
||||||
if (!botMissing || botMissing.length)
|
|
||||||
return invoker.editReply({ index: 'COMMAND_POLL_BOT_PERMS', params: { missing: botMissing?.join(', ') ?? 'ALL', channel: targetChannel.id } });
|
|
||||||
if (userMissing.length)
|
|
||||||
return invoker.editReply({ index: 'COMMAND_POLL_USER_PERMS', params: { missing: userMissing.join(', '), channel: targetChannel.id } });
|
|
||||||
|
|
||||||
const questions = await this.#queryQuestions(invoker, choices?.asNumber ?? 1, targetChannel);
|
|
||||||
if (!questions)
|
|
||||||
return;
|
|
||||||
|
|
||||||
await invoker.editReply({ index: 'COMMAND_POLL_STARTING' });
|
|
||||||
|
|
||||||
const embed = {
|
|
||||||
title: guild.format('COMMAND_POLL_TITLE', { user: author.tag }),
|
|
||||||
description: guild.format('COMMAND_POLL_DESCRIPTION', {
|
|
||||||
multi: guild.format('COMMAND_POLL_MULTI', { multichoice: multichoice?.asBool && questions.length > 1 || false }, { code: true }),
|
|
||||||
duration: duration ? guild.format('COMMAND_POLL_DURATION', { time: Util.humanise(duration.asNumber), duration: Math.floor(Date.now() / 1000) + duration.asNumber }) : guild.format('COMMAND_POLL_INFINITE')
|
|
||||||
}),
|
|
||||||
fields: questions,
|
|
||||||
color: EmbedDefaultColor,
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
};
|
|
||||||
|
|
||||||
let pollMsg = null;
|
|
||||||
if (questions.length === 1)
|
|
||||||
{
|
|
||||||
questions[0].name = guild.format('COMMAND_POLL_FIELD_QUESTION');
|
|
||||||
pollMsg = await targetChannel.send({ embeds: [ embed ] });
|
|
||||||
await pollMsg.react('👍');
|
|
||||||
await pollMsg.react('👎');
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pollMsg = await targetChannel.send({ embeds: [ embed ] });
|
|
||||||
for (const question of questions)
|
|
||||||
await pollMsg.react(PollReactions.Multi[question.index + 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const poll: PollData = {
|
|
||||||
questions: questions.map(q => q.value),
|
|
||||||
duration: (duration?.asNumber ?? 0),
|
|
||||||
multiChoice: multichoice?.asBool && questions.length > 1 || false,
|
|
||||||
user: author.id,
|
|
||||||
channel: targetChannel.id,
|
|
||||||
startedIn: invoker.channel!.id,
|
|
||||||
message: pollMsg.id
|
|
||||||
};
|
|
||||||
|
|
||||||
if (duration)
|
|
||||||
await this.client.polls.create(poll, guild.id);
|
|
||||||
await invoker.editReply({ emoji: 'success', index: 'COMMAND_POLL_START', params: { channel: targetChannel.id } });
|
|
||||||
}
|
|
||||||
else if (subcommand!.name === 'delete')
|
else if (subcommand!.name === 'delete')
|
||||||
{
|
return this.#deletePoll(invoker, { message });
|
||||||
const poll = await this.client.polls.delete(message!.asString);
|
|
||||||
if (!poll)
|
|
||||||
return { index: 'COMMAND_POLL_404', emoji: 'failure' };
|
|
||||||
const pollChannel = await guild.resolveChannel<TextChannel>(poll.payload.channel);
|
|
||||||
if (!pollChannel)
|
|
||||||
return { index: 'COMMAND_POLL_MISSING_CHANNEL', emoji: 'failure' };
|
|
||||||
const msg = await pollChannel.messages.fetch(poll.payload.message).catch(() => null);
|
|
||||||
if (msg)
|
|
||||||
await msg.delete();
|
|
||||||
return { index: 'COMMAND_POLL_DELETED', emoji: 'success' };
|
|
||||||
}
|
|
||||||
else if (subcommand!.name === 'end')
|
else if (subcommand!.name === 'end')
|
||||||
{
|
return this.#endPoll(invoker, { message });
|
||||||
const poll = await this.client.polls.find(message!.asString);
|
|
||||||
if (!poll)
|
|
||||||
return { index: 'COMMAND_POLL_404', emoji: 'failure' };
|
|
||||||
await this.client.polls.end(poll.payload);
|
|
||||||
// await guild._poll(poll.data as PollData & CallbackData);
|
|
||||||
return { index: 'COMMAND_POLL_ENDED', emoji: 'success' };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async #createPoll (invoker: InvokerWrapper<true>, { choices, channel, duration, multichoice }: CommandParams)
|
||||||
|
{
|
||||||
|
const { guild, member, author } = invoker;
|
||||||
|
const targetChannel = (channel?.asChannel || invoker.channel) as GuildTextBasedChannel;
|
||||||
|
if (!targetChannel?.isTextBased())
|
||||||
|
throw new CommandError(invoker, { index: 'ERR_INVALID_CHANNEL_TYPE' });
|
||||||
|
const botMissing = targetChannel.permissionsFor(this.client.user!)?.missing([ 'SendMessages', 'EmbedLinks' ]);
|
||||||
|
const userMissing = targetChannel.permissionsFor(member).missing([ 'SendMessages' ]);
|
||||||
|
if (!botMissing || botMissing.length)
|
||||||
|
return invoker.editReply({ index: 'COMMAND_POLL_BOT_PERMS', params: { missing: botMissing?.join(', ') ?? 'ALL', channel: targetChannel.id } });
|
||||||
|
if (userMissing.length)
|
||||||
|
return invoker.editReply({ index: 'COMMAND_POLL_USER_PERMS', params: { missing: userMissing.join(', '), channel: targetChannel.id } });
|
||||||
|
|
||||||
|
const questions = await this.#queryQuestions(invoker, choices?.asNumber ?? 1, targetChannel);
|
||||||
|
if (!questions)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await invoker.editReply({ index: 'COMMAND_POLL_STARTING' });
|
||||||
|
|
||||||
|
const embed = {
|
||||||
|
title: guild.format('COMMAND_POLL_TITLE', { user: author.tag }),
|
||||||
|
description: guild.format('COMMAND_POLL_DESCRIPTION', {
|
||||||
|
multi: guild.format('COMMAND_POLL_MULTI', { multichoice: multichoice?.asBool && questions.length > 1 || false }, { code: true }),
|
||||||
|
duration: duration ? guild.format('COMMAND_POLL_DURATION', { time: Util.humanise(duration.asNumber), duration: Math.floor(Date.now() / 1000) + duration.asNumber }) : guild.format('COMMAND_POLL_INFINITE')
|
||||||
|
}),
|
||||||
|
fields: questions,
|
||||||
|
color: EmbedDefaultColor,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
let pollMsg = null;
|
||||||
|
if (questions.length === 1)
|
||||||
|
{
|
||||||
|
questions[0].name = guild.format('COMMAND_POLL_FIELD_QUESTION');
|
||||||
|
pollMsg = await targetChannel.send({ embeds: [ embed ] });
|
||||||
|
await pollMsg.react('👍');
|
||||||
|
await pollMsg.react('👎');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pollMsg = await targetChannel.send({ embeds: [ embed ] });
|
||||||
|
for (const question of questions)
|
||||||
|
await pollMsg.react(PollReactions.Multi[question.index + 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const poll: PollData = {
|
||||||
|
questions: questions.map(q => q.value),
|
||||||
|
duration: (duration?.asNumber ?? 0),
|
||||||
|
multiChoice: multichoice?.asBool && questions.length > 1 || false,
|
||||||
|
user: author.id,
|
||||||
|
channel: targetChannel.id,
|
||||||
|
startedIn: invoker.channel!.id,
|
||||||
|
message: pollMsg.id
|
||||||
|
};
|
||||||
|
|
||||||
|
if (duration)
|
||||||
|
await this.client.polls.create(poll, guild.id);
|
||||||
|
await invoker.editReply({ emoji: 'success', index: 'COMMAND_POLL_START', params: { channel: targetChannel.id } });
|
||||||
|
}
|
||||||
|
|
||||||
async #queryQuestions (invoker: InvokerWrapper<true>, choices: number, targetchannel: GuildTextBasedChannel)
|
async #queryQuestions (invoker: InvokerWrapper<true>, choices: number, targetchannel: GuildTextBasedChannel)
|
||||||
{
|
{
|
||||||
const { guild, member } = invoker;
|
const { guild, member } = invoker;
|
||||||
@ -166,6 +151,31 @@ class PollCommand extends SlashCommand
|
|||||||
}
|
}
|
||||||
return questions;
|
return questions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async #deletePoll (invoker: InvokerWrapper<true>, { message }: CommandParams)
|
||||||
|
{
|
||||||
|
const { guild } = invoker;
|
||||||
|
const poll = await this.client.polls.delete(message!.asString);
|
||||||
|
if (!poll)
|
||||||
|
return { index: 'COMMAND_POLL_404', emoji: 'failure' };
|
||||||
|
const pollChannel = await guild.resolveChannel<TextChannel>(poll.payload.channel);
|
||||||
|
if (!pollChannel)
|
||||||
|
return { index: 'COMMAND_POLL_MISSING_CHANNEL', emoji: 'failure' };
|
||||||
|
const msg = await pollChannel.messages.fetch(poll.payload.message).catch(() => null);
|
||||||
|
if (msg)
|
||||||
|
await msg.delete();
|
||||||
|
return { index: 'COMMAND_POLL_DELETED', emoji: 'success' };
|
||||||
|
}
|
||||||
|
|
||||||
|
async #endPoll (_invoker: InvokerWrapper<true>, { message }: CommandParams)
|
||||||
|
{
|
||||||
|
const poll = await this.client.polls.find(message!.asString);
|
||||||
|
if (!poll)
|
||||||
|
return { index: 'COMMAND_POLL_404', emoji: 'failure' };
|
||||||
|
await this.client.polls.end(poll._id, poll.payload);
|
||||||
|
// await guild._poll(poll.data as PollData & CallbackData);
|
||||||
|
return { index: 'COMMAND_POLL_ENDED', emoji: 'success' };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PollCommand;
|
export default PollCommand;
|
@ -185,9 +185,9 @@ class CallbackManager implements Initialisable
|
|||||||
*
|
*
|
||||||
* @async
|
* @async
|
||||||
* @param {string} id
|
* @param {string} id
|
||||||
* @returns {*}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async removeCallback (id: string)
|
async removeCallback (id: string): Promise<void>
|
||||||
{
|
{
|
||||||
await this.storage.deleteOne({ _id: id });
|
await this.storage.deleteOne({ _id: id });
|
||||||
const timeout = this.#timeouts.get(id);
|
const timeout = this.#timeouts.get(id);
|
||||||
|
@ -591,9 +591,9 @@ class ModerationManager implements Initialisable, CallbackClient
|
|||||||
return responses;
|
return responses;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleCallback (id: string)
|
async handleCallback (_id: string, infraction: InfractionJSON)
|
||||||
{
|
{
|
||||||
const infraction = await this.#client.mongodb.infractions.findOne({ id });
|
// const infraction = await this.#client.mongodb.infractions.findOne({ id });
|
||||||
if (!infraction)
|
if (!infraction)
|
||||||
return;
|
return;
|
||||||
this.#logger.debug(`Infraction callback: ${infraction.id} (${infraction.type})`);
|
this.#logger.debug(`Infraction callback: ${infraction.id} (${infraction.type})`);
|
||||||
@ -667,7 +667,7 @@ class ModerationManager implements Initialisable, CallbackClient
|
|||||||
if (expiresAt - currentDate <= 0)
|
if (expiresAt - currentDate <= 0)
|
||||||
{
|
{
|
||||||
this.#logger.debug(`Expired infraction:\n${inspect(infraction)}`);
|
this.#logger.debug(`Expired infraction:\n${inspect(infraction)}`);
|
||||||
return this.handleCallback(infraction.id);
|
return this.handleCallback(infraction.id, infraction);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#logger.debug(`Creating infraction callback for ${infraction.id} (${infraction.type}), expiring in ${Util.humanise(duration / 1000)}`);
|
this.#logger.debug(`Creating infraction callback for ${infraction.id} (${infraction.type}), expiring in ${Util.humanise(duration / 1000)}`);
|
||||||
|
@ -47,12 +47,12 @@ class PollManager implements CallbackClient
|
|||||||
return poll;
|
return poll;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleCallback (_id: string, payload: PollData): Promise<void>
|
async handleCallback (id: string, payload: PollData): Promise<void>
|
||||||
{
|
{
|
||||||
await this.end(payload);
|
await this.end(id, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
async end ({ user, channel, startedIn, message, multiChoice }: PollData)
|
async end (id: string, { user, channel, startedIn, message, multiChoice }: PollData)
|
||||||
{
|
{
|
||||||
const startChannel = await this.#client.resolveChannel(startedIn);
|
const startChannel = await this.#client.resolveChannel(startedIn);
|
||||||
const pollChannel = await this.#client.resolveChannel(channel);
|
const pollChannel = await this.#client.resolveChannel(channel);
|
||||||
@ -89,6 +89,7 @@ class PollManager implements CallbackClient
|
|||||||
if (startChannel && startChannel.isTextBased())
|
if (startChannel && startChannel.isTextBased())
|
||||||
await startChannel.send(guild.format('COMMAND_POLL_NOTIFY_STARTER', { user, channel }));
|
await startChannel.send(guild.format('COMMAND_POLL_NOTIFY_STARTER', { user, channel }));
|
||||||
}
|
}
|
||||||
|
await this.callbacks.removeCallback(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,89 +59,9 @@ class GuildWrapper
|
|||||||
this.#guild = guild;
|
this.#guild = guild;
|
||||||
this.#webhooks = new Collection();
|
this.#webhooks = new Collection();
|
||||||
this.#memberWrappers = new Collection();
|
this.#memberWrappers = new Collection();
|
||||||
this.#debugLog('Created wrapper');
|
this.debug('Created wrapper');
|
||||||
}
|
}
|
||||||
|
|
||||||
// async createPoll ({ user, duration, ...opts }: PollData)
|
|
||||||
// {
|
|
||||||
// // Idk polls that don't have a duration should still be stored somewhere so they can be ended at an arbitrary point
|
|
||||||
// const type = 'poll';
|
|
||||||
// const now = Date.now();
|
|
||||||
// const id = `${type}:${user}:${now}`;
|
|
||||||
// const data = { ...opts, user, id, guild: this.id, type, time: duration * 1000, created: now };
|
|
||||||
// if (duration)
|
|
||||||
// await this.createCallback(data satisfies CallbackData);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// async loadCallbacks ()
|
|
||||||
// {
|
|
||||||
// const data = await this.#client.mongodb.callbacks.find<CallbackData>({ guild: this.id });
|
|
||||||
// for (const cb of data)
|
|
||||||
// await this.createCallback(cb, false);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// async createCallback (data: CallbackData, update = true)
|
|
||||||
// {
|
|
||||||
// const handler = this[`_${data.type}`] as CallbackFn;// .bind(this);
|
|
||||||
// if (!handler)
|
|
||||||
// throw new Error('Invalid callback type');
|
|
||||||
|
|
||||||
// const now = Date.now();
|
|
||||||
// const time = data.created + data.time;
|
|
||||||
// const diff = time - now;
|
|
||||||
// if (diff < 5000)
|
|
||||||
// return handler.bind(this)(data);
|
|
||||||
|
|
||||||
// const cb = { timeout: setTimeout(handler.bind(this), diff, data), data };
|
|
||||||
// this.#callbacks.set(data.id, cb);
|
|
||||||
// if (update)
|
|
||||||
// await this.#client.mongodb.callbacks.updateOne({ id: data.id, guild: this.id }, { $set: data });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// async removeCallback (id: string)
|
|
||||||
// {
|
|
||||||
// const cb = this.#callbacks.get(id);
|
|
||||||
// if (cb)
|
|
||||||
// clearTimeout(cb.timeout);
|
|
||||||
// this.#callbacks.delete(id);
|
|
||||||
// await this.#client.mongodb.callbacks.deleteOne({ guild: this.id, id });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// async _poll ({ user, message, channel, id, questions, startedIn }: PollData & CallbackData)
|
|
||||||
// { // multichoice,
|
|
||||||
// const startedInChannel = await this.resolveChannel<TextChannel>(startedIn);
|
|
||||||
// const pollChannel = await this.resolveChannel<TextChannel>(channel);
|
|
||||||
// if (pollChannel)
|
|
||||||
// {
|
|
||||||
// const msg = await pollChannel.messages.fetch(message).catch(() => null);
|
|
||||||
// if (msg)
|
|
||||||
// {
|
|
||||||
// const { reactions } = msg;
|
|
||||||
// const reactionEmojis = questions.length ? PollReactions.Multi : PollReactions.Single;
|
|
||||||
// const result: {[key: string]: number} = {};
|
|
||||||
// for (const emoji of reactionEmojis)
|
|
||||||
// {
|
|
||||||
// let reaction = reactions.resolve(emoji);
|
|
||||||
// // eslint-disable-next-line max-depth
|
|
||||||
// if (!reaction)
|
|
||||||
// continue;
|
|
||||||
// // eslint-disable-next-line max-depth
|
|
||||||
// if (reaction.partial)
|
|
||||||
// reaction = await reaction.fetch();
|
|
||||||
// result[emoji] = reaction.count - 1;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const embed = msg.embeds[0].toJSON();
|
|
||||||
// const results = Object.entries(result).map(([ emoji, count ]) => `${emoji} - ${count}`).join('\n');
|
|
||||||
// embed.description = this.format('COMMAND_POLL_END', { results });
|
|
||||||
// await msg.edit({ embeds: [ embed ] });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// await this.removeCallback(id);
|
|
||||||
// if (startedInChannel)
|
|
||||||
// await startedInChannel.send(this.format('COMMAND_POLL_NOTIFY_STARTER', { user, channel }));
|
|
||||||
// }
|
|
||||||
|
|
||||||
async filterText (member: GuildMember, text: string)
|
async filterText (member: GuildMember, text: string)
|
||||||
{
|
{
|
||||||
const settings = await this.settings();
|
const settings = await this.settings();
|
||||||
@ -289,72 +209,6 @@ class GuildWrapper
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// async _attemptDataImport ()
|
|
||||||
// {
|
|
||||||
// const migratorOptions = {
|
|
||||||
// // host: MONGODB_V2_HOST,
|
|
||||||
// database: 'galacticbot',
|
|
||||||
// version: '2'
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const settingsMigrator = new SettingsMigrator(this.client, this, migratorOptions);
|
|
||||||
// const modlogsMigrator = new InfractionMigrator(this.client, this, migratorOptions);
|
|
||||||
|
|
||||||
// await settingsMigrator.connect();
|
|
||||||
// await modlogsMigrator.connect();
|
|
||||||
|
|
||||||
// let importedSettings = null;
|
|
||||||
// let importedModlogs = null;
|
|
||||||
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// importedSettings = await settingsMigrator.import();
|
|
||||||
// importedModlogs = await modlogsMigrator.import();
|
|
||||||
// importedModlogs.sort((a, b) => a.case - b.case);
|
|
||||||
// }
|
|
||||||
// catch (err)
|
|
||||||
// {
|
|
||||||
// await settingsMigrator.end();
|
|
||||||
// await modlogsMigrator.end();
|
|
||||||
// // Did not find old settings, marking as imported anyway
|
|
||||||
// if (err.message.includes('No old'))
|
|
||||||
// {
|
|
||||||
// await this.updateData({ _imported: { settings: true, modlogs: true } });
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// this.client.logger.error(err.stack);
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
// await settingsMigrator.end();
|
|
||||||
// await modlogsMigrator.end();
|
|
||||||
|
|
||||||
// await this.client.mongodb.infractions.deleteMany({ guild: this.id });
|
|
||||||
// await this.client.mongodb.infractions.insertMany(importedModlogs);
|
|
||||||
// this._data.caseId = importedModlogs[importedModlogs.length - 1].case;
|
|
||||||
// await this.updateData({
|
|
||||||
// caseId: this._data.caseId,
|
|
||||||
// premium: importedSettings.premium,
|
|
||||||
// _imported: { settings: true, modlogs: true }
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const { webhook, permissions, settings } = importedSettings;
|
|
||||||
// await this.updateSettings(settings);
|
|
||||||
// if (webhook)
|
|
||||||
// {
|
|
||||||
// const hooks = await this.fetchWebhooks().catch(() => null);
|
|
||||||
// const hook = hooks?.get(webhook);
|
|
||||||
// if (hook)
|
|
||||||
// await this.updateWebhook('messages', hook);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (permissions)
|
|
||||||
// await this.#client.mongodb.permissions.updateOne({ guildId: this.id }, permissions);
|
|
||||||
|
|
||||||
|
|
||||||
// return settings;
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update a webhook entry in the database
|
* Update a webhook entry in the database
|
||||||
*
|
*
|
||||||
@ -528,9 +382,16 @@ class GuildWrapper
|
|||||||
this.#logger.error(`Database error (guild:${this.id}) :\n${error.stack || error}`);
|
this.#logger.error(`Database error (guild:${this.id}) :\n${error.stack || error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
#debugLog (log: string)
|
debug (log: string)
|
||||||
{
|
{
|
||||||
this.#logger.debug(`[${this.name}]: ${log}`);
|
if (!this.#data || this.#data.debug)
|
||||||
|
this.#logger.debug(`[${this.name}]: ${log}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setDebug (enabled: boolean)
|
||||||
|
{
|
||||||
|
await this.#client.mongodb.guilds.updateOne({ guildId: this.id }, { $set: { debug: enabled } });
|
||||||
|
this.#data.debug = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Wrapper Functions */
|
/* Wrapper Functions */
|
||||||
|
@ -62,10 +62,12 @@ class AddroleInfraction extends Infraction
|
|||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
this.guild.debug(`Adding roles ${this.data.roleIds.join(', ')} to ${this.member.tag} (${this.member.id})`);
|
||||||
await this.member.roles.add(this.data.roleIds, this._reason);
|
await this.member.roles.add(this.data.roleIds, this._reason);
|
||||||
}
|
}
|
||||||
catch (error)
|
catch (error)
|
||||||
{
|
{
|
||||||
|
this.logger.error(`Failed to add roles (${this.data.roleIds.join(', ')}) to ${this.member.tag} (${this.member.id})\n${error}`);
|
||||||
return this._fail('INFRACTION_ERROR');
|
return this._fail('INFRACTION_ERROR');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,6 +65,7 @@ class BanInfraction extends Infraction
|
|||||||
}
|
}
|
||||||
catch (error)
|
catch (error)
|
||||||
{
|
{
|
||||||
|
this.logger.error(`Failed to ban ${(this.target as UserWrapper).tag} (${this.target!.id}) from ${this.guild.name} (${this.guild.id})\n${error}`);
|
||||||
return this._fail('INFRACTION_ERROR');
|
return this._fail('INFRACTION_ERROR');
|
||||||
}
|
}
|
||||||
await this.handle();
|
await this.handle();
|
||||||
|
@ -4,6 +4,7 @@ import DiscordClient from '../DiscordClient.js';
|
|||||||
import MemberWrapper from '../components/wrappers/MemberWrapper.js';
|
import MemberWrapper from '../components/wrappers/MemberWrapper.js';
|
||||||
import Infraction from '../interfaces/Infraction.js';
|
import Infraction from '../interfaces/Infraction.js';
|
||||||
import { KickData } from '../../../@types/Infractions.js';
|
import { KickData } from '../../../@types/Infractions.js';
|
||||||
|
import UserWrapper from '../components/wrappers/UserWrapper.js';
|
||||||
|
|
||||||
class KickInfraction extends Infraction
|
class KickInfraction extends Infraction
|
||||||
{
|
{
|
||||||
@ -56,6 +57,7 @@ class KickInfraction extends Infraction
|
|||||||
}
|
}
|
||||||
catch (error)
|
catch (error)
|
||||||
{
|
{
|
||||||
|
this.logger.error(`Failed to kick ${(this.target as UserWrapper).tag} (${this.target!.id}) from ${this.guild.name} (${this.guild.id})\n${error}`);
|
||||||
return this._fail('INFRACTION_ERROR');
|
return this._fail('INFRACTION_ERROR');
|
||||||
}
|
}
|
||||||
await this.handle();
|
await this.handle();
|
||||||
|
@ -59,6 +59,7 @@ class RemoveroleInfraction extends Infraction
|
|||||||
}
|
}
|
||||||
catch (error)
|
catch (error)
|
||||||
{
|
{
|
||||||
|
this.logger.error(`Failed to remove roles (${this.data.roleIds!.join(', ')}) to ${this.member!.tag} (${this.member!.id})\n${error}`);
|
||||||
return this._fail('INFRACTION_ERROR');
|
return this._fail('INFRACTION_ERROR');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +49,7 @@ class UnbanInfraction extends Infraction
|
|||||||
}
|
}
|
||||||
catch (error)
|
catch (error)
|
||||||
{
|
{
|
||||||
|
this.logger.error(`Failed to unban ${this.targetId} in ${this.guild.name} (${this.guild.id})\n${error}`);
|
||||||
return this._fail('INFRACTION_ERROR');
|
return this._fail('INFRACTION_ERROR');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,8 +107,9 @@ class UnmuteInfraction extends Infraction
|
|||||||
{
|
{
|
||||||
this.member!.roles.remove(role, this._reason);
|
this.member!.roles.remove(role, this._reason);
|
||||||
}
|
}
|
||||||
catch (e)
|
catch (error)
|
||||||
{
|
{
|
||||||
|
this.logger.error(`Failed to unmute ${this.member.tag} (${this.member.id})\n${error}`);
|
||||||
return this._fail('C_UNMUTE_1FAIL');
|
return this._fail('C_UNMUTE_1FAIL');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,8 +132,9 @@ class UnmuteInfraction extends Infraction
|
|||||||
{
|
{
|
||||||
this.member.roles.remove(role, this._reason);
|
this.member.roles.remove(role, this._reason);
|
||||||
}
|
}
|
||||||
catch (e)
|
catch (error)
|
||||||
{
|
{
|
||||||
|
this.logger.error(`Failed to unmute ${this.member.tag} (${this.member.id})\n${error}`);
|
||||||
return this._fail('C_UNMUTE_1FAIL');
|
return this._fail('C_UNMUTE_1FAIL');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -234,6 +234,10 @@ class Infraction
|
|||||||
}).catch(() => null);
|
}).catch(() => null);
|
||||||
this.#dmLogMessageId = logMessage?.id ?? null;
|
this.#dmLogMessageId = logMessage?.id ?? null;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.logger.debug(`Target not sendable? ${inspect(this.#target)}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.#duration)
|
if (this.#duration)
|
||||||
|
@ -1,27 +1,47 @@
|
|||||||
import { EventEmitter } from 'node:events';
|
import {
|
||||||
import { inspect } from 'node:util';
|
EventEmitter
|
||||||
|
} from 'node:events';
|
||||||
|
|
||||||
|
import {
|
||||||
|
inspect
|
||||||
|
} from 'node:util';
|
||||||
|
|
||||||
|
import {
|
||||||
|
existsSync
|
||||||
|
} from 'node:fs';
|
||||||
|
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
|
||||||
import { CommandsDef, IPCMessage } from '../../@types/Shared.js';
|
import {
|
||||||
import { BroadcastEvalOptions, ShardMethod, ShardingOptions } from '../../@types/Shard.js';
|
CommandsDef,
|
||||||
import { ControllerOptions } from '../../@types/Controller.js';
|
IPCMessage
|
||||||
|
} from '../../@types/Shared.js';
|
||||||
|
|
||||||
import { MasterLogger } from '@navy.gif/logger';
|
import {
|
||||||
import { Collection } from 'discord.js';
|
BroadcastEvalOptions,
|
||||||
|
ShardMethod,
|
||||||
|
ShardingOptions
|
||||||
|
} from '../../@types/Shard.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ControllerOptions
|
||||||
|
} from '../../@types/Controller.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
MasterLogger
|
||||||
|
} from '@navy.gif/logger';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Collection
|
||||||
|
} from 'discord.js';
|
||||||
|
|
||||||
// Available for evals
|
// Available for evals
|
||||||
import ClientUtils from './ClientUtils.js';
|
import ClientUtils from './ClientUtils.js';
|
||||||
import Metrics from './Metrics.js';
|
import Metrics from './Metrics.js';
|
||||||
// import ApiClientUtil from './ApiClientUtil.js';
|
// import ApiClientUtil from './ApiClientUtil.js';
|
||||||
import SlashCommandManager from './rest/SlashCommandManager.js';
|
import SlashCommandManager from './SlashCommandManager.js';
|
||||||
import { Shard } from './shard/index.js';
|
|
||||||
import { existsSync } from 'node:fs';
|
|
||||||
import Util from '../utilities/Util.js';
|
import Util from '../utilities/Util.js';
|
||||||
|
import Shard from './Shard.js';
|
||||||
// Placeholder
|
|
||||||
type GalacticAPI = {
|
|
||||||
init: () => Promise<void>
|
|
||||||
}
|
|
||||||
|
|
||||||
class Controller extends EventEmitter
|
class Controller extends EventEmitter
|
||||||
{
|
{
|
||||||
@ -39,7 +59,6 @@ class Controller extends EventEmitter
|
|||||||
#readyAt: number | null;
|
#readyAt: number | null;
|
||||||
#built: boolean;
|
#built: boolean;
|
||||||
|
|
||||||
#api?: GalacticAPI;
|
|
||||||
clientUtils: typeof ClientUtils;
|
clientUtils: typeof ClientUtils;
|
||||||
|
|
||||||
constructor (options: ControllerOptions, version: string)
|
constructor (options: ControllerOptions, version: string)
|
||||||
@ -92,28 +111,7 @@ class Controller extends EventEmitter
|
|||||||
async build ()
|
async build ()
|
||||||
{
|
{
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
// const API = this._options.api.load ? await import('/Documents/My programs/GBot/api/index.js')
|
|
||||||
// .catch(() => this.logger.warn(`Error importing API files, continuing without`)) : null;
|
|
||||||
|
|
||||||
// let API = null;
|
|
||||||
// if (this.#options.api.load)
|
|
||||||
// API = await import('../../api/index.js').catch(() => this.#logger.warn(`Error importing API files, continuing without`));
|
|
||||||
// if (API) {
|
|
||||||
// this.#logger.info('Booting up API');
|
|
||||||
// const { default: APIManager } = API;
|
|
||||||
// this.#api = new APIManager(this, this.#options.api) as GalacticAPI;
|
|
||||||
// await this.#api.init();
|
|
||||||
// const now = Date.now();
|
|
||||||
// this.#logger.info(`API ready. Took ${now - start} ms`);
|
|
||||||
// start = now;
|
|
||||||
// }
|
|
||||||
|
|
||||||
this.#logger.status('Starting bot shards');
|
this.#logger.status('Starting bot shards');
|
||||||
// await this.shardingManager.spawn().catch((error) => {
|
|
||||||
// this.#logger.error(`Fatal error during shard spawning:\n${error.stack || inspect(error)}`);
|
|
||||||
// // eslint-disable-next-line no-process-exit
|
|
||||||
// process.exit(); // Prevent a boot loop when shards die due to an error in the client
|
|
||||||
// });
|
|
||||||
|
|
||||||
const { totalShards, token } = this.#shardingOptions;
|
const { totalShards, token } = this.#shardingOptions;
|
||||||
let shardCount = 0;
|
let shardCount = 0;
|
||||||
@ -133,7 +131,6 @@ class Controller extends EventEmitter
|
|||||||
throw new TypeError('Amount of shards must be an integer.');
|
throw new TypeError('Amount of shards must be an integer.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// const promises = [];
|
|
||||||
const retry: Shard[] = [];
|
const retry: Shard[] = [];
|
||||||
for (let i = 0; i < shardCount; i++)
|
for (let i = 0; i < shardCount; i++)
|
||||||
{
|
{
|
||||||
@ -148,9 +145,9 @@ class Controller extends EventEmitter
|
|||||||
this.logger.info('Retrying shard', i);
|
this.logger.info('Retrying shard', i);
|
||||||
retry.push(shard);
|
retry.push(shard);
|
||||||
}
|
}
|
||||||
// promises.push();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retry failed spawns
|
||||||
for (const shard of retry)
|
for (const shard of retry)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -163,8 +160,6 @@ class Controller extends EventEmitter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// await Promise.all(promises);
|
|
||||||
|
|
||||||
this.#logger.status(`Shards spawned, spawned ${this.#shards.size} shards. Took ${Date.now() - start} ms`);
|
this.#logger.status(`Shards spawned, spawned ${this.#shards.size} shards. Took ${Date.now() - start} ms`);
|
||||||
|
|
||||||
this.#built = true;
|
this.#built = true;
|
||||||
@ -173,12 +168,11 @@ class Controller extends EventEmitter
|
|||||||
|
|
||||||
async shutdown ()
|
async shutdown ()
|
||||||
{
|
{
|
||||||
this.logger.info('Received SIGINT, shutting down');
|
this.logger.info('Received SIGINT or SIGTERM, 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 => shard.awaitShutdown()
|
.map(shard => shard.kill());
|
||||||
.then(() => shard.removeAllListeners()));
|
|
||||||
if (promises.length)
|
if (promises.length)
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
this.logger.status('Shutdown complete, goodbye');
|
this.logger.status('Shutdown complete, goodbye');
|
||||||
@ -406,11 +400,6 @@ class Controller extends EventEmitter
|
|||||||
return this.#logger;
|
return this.#logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
get api ()
|
|
||||||
{
|
|
||||||
return this.#api;
|
|
||||||
}
|
|
||||||
|
|
||||||
get shards ()
|
get shards ()
|
||||||
{
|
{
|
||||||
return this.#shards.clone();
|
return this.#shards.clone();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import BaseClient from './Controller.js';
|
import BaseClient from './Controller.js';
|
||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
import Shard from './shard/Shard.js';
|
import Shard from './Shard.js';
|
||||||
import { IPCMessage } from '../../@types/Shared.js';
|
import { IPCMessage } from '../../@types/Shared.js';
|
||||||
import djs, { GuildMessageManager } from 'discord.js';
|
import djs, { GuildMessageManager } from 'discord.js';
|
||||||
import DiscordClient from '../client/DiscordClient.js';
|
import DiscordClient from '../client/DiscordClient.js';
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import EventEmitter from 'node:events';
|
import EventEmitter from 'node:events';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { makePlainError, makeError, MakeErrorOptions } from 'discord.js';
|
|
||||||
import { Util } from '../../utilities/index.js';
|
|
||||||
import childProcess, { ChildProcess } from 'node:child_process';
|
import childProcess, { ChildProcess } from 'node:child_process';
|
||||||
|
import { makePlainError, makeError, MakeErrorOptions } from 'discord.js';
|
||||||
|
|
||||||
import { EnvObject, IPCMessage } from '../../../@types/Shared.js';
|
import Controller from './Controller.js';
|
||||||
import Controller from '../Controller.js';
|
import { Util } from '../utilities/index.js';
|
||||||
import { ShardOptions } from '../../../@types/Shard.js';
|
|
||||||
import { ClientOptions } from '../../../@types/Client.js';
|
import { ShardOptions } from '../../@types/Shard.js';
|
||||||
|
import { ClientOptions } from '../../@types/Client.js';
|
||||||
|
import { EnvObject, IPCMessage } from '../../@types/Shared.js';
|
||||||
|
|
||||||
const KillTO = 90 * 1000;
|
const KillTO = 90 * 1000;
|
||||||
|
|
||||||
@ -16,8 +17,8 @@ class Shard extends EventEmitter
|
|||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
|
|
||||||
#id: number;
|
#id: number;
|
||||||
#manager: Controller;
|
#controller: Controller;
|
||||||
#env: EnvObject; // { [key: string]: string | boolean | number };
|
#env: EnvObject;
|
||||||
#ready: boolean;
|
#ready: boolean;
|
||||||
#clientOptions: ClientOptions;
|
#clientOptions: ClientOptions;
|
||||||
|
|
||||||
@ -38,10 +39,10 @@ class Shard extends EventEmitter
|
|||||||
#crashes: number[];
|
#crashes: number[];
|
||||||
#fatal: boolean;
|
#fatal: boolean;
|
||||||
|
|
||||||
constructor (manager: Controller, id: number, options: ShardOptions)
|
constructor (controller: Controller, id: number, options: ShardOptions)
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
this.#manager = manager;
|
this.#controller = controller;
|
||||||
this.#id = id;
|
this.#id = id;
|
||||||
|
|
||||||
this.#args = options.args ?? [];
|
this.#args = options.args ?? [];
|
||||||
@ -370,7 +371,7 @@ class Shard extends EventEmitter
|
|||||||
if (message._sFetchProp)
|
if (message._sFetchProp)
|
||||||
{
|
{
|
||||||
const resp = { _sFetchProp: message._sFetchProp, _sFetchPropShard: message._sFetchPropShard };
|
const resp = { _sFetchProp: message._sFetchProp, _sFetchPropShard: message._sFetchPropShard };
|
||||||
this.#manager.fetchClientValues(message._sFetchProp, message._sFetchPropShard).then(
|
this.#controller.fetchClientValues(message._sFetchProp, message._sFetchPropShard).then(
|
||||||
(results: unknown) => this.send({ ...resp, _result: results }),
|
(results: unknown) => this.send({ ...resp, _result: results }),
|
||||||
(error: Error) => this.send({ ...resp, _error: makePlainError(error) })
|
(error: Error) => this.send({ ...resp, _error: makePlainError(error) })
|
||||||
);
|
);
|
||||||
@ -380,7 +381,7 @@ class Shard extends EventEmitter
|
|||||||
if (message._sEval)
|
if (message._sEval)
|
||||||
{
|
{
|
||||||
const resp = { _sEval: message._sEval, _sEvalShard: message._sEvalShard };
|
const resp = { _sEval: message._sEval, _sEvalShard: message._sEvalShard };
|
||||||
this.#manager._performOnShards('eval', [ message._sEval ], message._sEvalShard).then(
|
this.#controller._performOnShards('eval', [ message._sEval ], message._sEvalShard).then(
|
||||||
(results: unknown) => this.send({ ...resp, _result: results }),
|
(results: unknown) => this.send({ ...resp, _result: results }),
|
||||||
(error: Error) => this.send({ ...resp, _error: makePlainError(error) })
|
(error: Error) => this.send({ ...resp, _error: makePlainError(error) })
|
||||||
);
|
);
|
||||||
@ -390,7 +391,7 @@ class Shard extends EventEmitter
|
|||||||
if (message._sRespawnAll)
|
if (message._sRespawnAll)
|
||||||
{
|
{
|
||||||
const { shardDelay, respawnDelay, timeout } = message._sRespawnAll;
|
const { shardDelay, respawnDelay, timeout } = message._sRespawnAll;
|
||||||
this.#manager.respawnAll({ shardDelay, respawnDelay, timeout }).catch(() =>
|
this.#controller.respawnAll({ shardDelay, respawnDelay, timeout }).catch(() =>
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
});
|
});
|
@ -5,8 +5,8 @@ import fs from 'node:fs';
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { inspect } from 'node:util';
|
import { inspect } from 'node:util';
|
||||||
|
|
||||||
import BaseClient from '../Controller.js';
|
import BaseClient from './Controller.js';
|
||||||
import { Command, CommandOption, CommandsDef } from '../../../@types/Shared.js';
|
import { Command, CommandOption, CommandsDef } from '../../@types/Shared.js';
|
||||||
|
|
||||||
type CommandHashTable = {
|
type CommandHashTable = {
|
||||||
global: string | null,
|
global: string | null,
|
@ -1,6 +0,0 @@
|
|||||||
import Shard from './Shard.js';
|
|
||||||
// import ShardingManager from './ShardingManager.js';
|
|
||||||
export {
|
|
||||||
Shard,
|
|
||||||
// ShardingManager
|
|
||||||
};
|
|
@ -111,7 +111,7 @@
|
|||||||
},
|
},
|
||||||
"compileOnSave": true,
|
"compileOnSave": true,
|
||||||
"exclude": ["archive", "api"],
|
"exclude": ["archive", "api"],
|
||||||
"include": ["src", "index.ts", "@types"],
|
"include": ["src/**/*", "index.ts", "@types/*"],
|
||||||
"watchOptions": {
|
"watchOptions": {
|
||||||
"watchFile": "useFsEvents",
|
"watchFile": "useFsEvents",
|
||||||
"watchDirectory": "useFsEvents",
|
"watchDirectory": "useFsEvents",
|
||||||
|
Loading…
Reference in New Issue
Block a user