1
0
mirror of https://github.com/toast-ts/Daggerbot-TS.git synced 2024-11-18 04:40:59 -05:00

Compare commits

...

7 Commits

Author SHA1 Message Date
toast-ts
14077381ac Merge branch 'master' into sequelize-v7 2024-02-25 20:42:26 +11:00
toast-ts
306d330914 Clean up some of the stuff 2024-02-25 20:40:44 +11:00
toast-ts
424de10a8c Optimize functions in punishment model 2024-02-25 20:39:32 +11:00
toast-ts
cd40816e8f Log transaction failed const to console instead of nowhere 2024-02-25 20:19:27 +11:00
toast-ts
afc04047eb Update @types/pg 2024-02-25 20:17:14 +11:00
toast-ts
54d0db19b6 Merge branch 'master' into sequelize-v7 2024-02-25 20:15:08 +11:00
toast-ts
7ca33f1de8 Clean up some of the stuff 2024-02-25 20:14:17 +11:00
10 changed files with 46 additions and 44 deletions

12
.pnp.cjs generated
View File

@ -33,7 +33,7 @@ const RAW_RUNTIME_STATE =
["@types/ms", "npm:0.7.34"],\ ["@types/ms", "npm:0.7.34"],\
["@types/node", "npm:20.11.20"],\ ["@types/node", "npm:20.11.20"],\
["@types/node-cron", "npm:3.0.11"],\ ["@types/node-cron", "npm:3.0.11"],\
["@types/pg", "npm:8.11.0"],\ ["@types/pg", "npm:8.11.1"],\
["ansi-colors", "npm:4.1.3"],\ ["ansi-colors", "npm:4.1.3"],\
["dayjs", "npm:1.11.10"],\ ["dayjs", "npm:1.11.10"],\
["discord.js", "npm:14.14.1"],\ ["discord.js", "npm:14.14.1"],\
@ -645,7 +645,7 @@ const RAW_RUNTIME_STATE =
["@types/mariadb", null],\ ["@types/mariadb", null],\
["@types/mysql2", null],\ ["@types/mysql2", null],\
["@types/odbc", null],\ ["@types/odbc", null],\
["@types/pg", "npm:8.11.0"],\ ["@types/pg", "npm:8.11.1"],\
["@types/snowflake-sdk", null],\ ["@types/snowflake-sdk", null],\
["@types/sqlite3", null],\ ["@types/sqlite3", null],\
["@types/tedious", null],\ ["@types/tedious", null],\
@ -746,10 +746,10 @@ const RAW_RUNTIME_STATE =
}]\ }]\
]],\ ]],\
["@types/pg", [\ ["@types/pg", [\
["npm:8.11.0", {\ ["npm:8.11.1", {\
"packageLocation": "./.yarn/cache/@types-pg-npm-8.11.0-bf104da0ba-91a7ccc5dc.zip/node_modules/@types/pg/",\ "packageLocation": "./.yarn/cache/@types-pg-npm-8.11.1-5d06a4b9df-2fdcb0dc33.zip/node_modules/@types/pg/",\
"packageDependencies": [\ "packageDependencies": [\
["@types/pg", "npm:8.11.0"],\ ["@types/pg", "npm:8.11.1"],\
["@types/node", "npm:20.11.20"],\ ["@types/node", "npm:20.11.20"],\
["pg-protocol", "npm:1.6.0"],\ ["pg-protocol", "npm:1.6.0"],\
["pg-types", "npm:4.0.2"]\ ["pg-types", "npm:4.0.2"]\
@ -860,7 +860,7 @@ const RAW_RUNTIME_STATE =
["@types/ms", "npm:0.7.34"],\ ["@types/ms", "npm:0.7.34"],\
["@types/node", "npm:20.11.20"],\ ["@types/node", "npm:20.11.20"],\
["@types/node-cron", "npm:3.0.11"],\ ["@types/node-cron", "npm:3.0.11"],\
["@types/pg", "npm:8.11.0"],\ ["@types/pg", "npm:8.11.1"],\
["ansi-colors", "npm:4.1.3"],\ ["ansi-colors", "npm:4.1.3"],\
["dayjs", "npm:1.11.10"],\ ["dayjs", "npm:1.11.10"],\
["discord.js", "npm:14.14.1"],\ ["discord.js", "npm:14.14.1"],\

View File

@ -8,7 +8,7 @@
"url": "git+https://github.com/toast-ts/Daggerbot-TS.git" "url": "git+https://github.com/toast-ts/Daggerbot-TS.git"
}, },
"scripts": { "scripts": {
"dev": "yarn tsc && yarn node . src/DB-Beta.config.json daggerbotbeta", "dev": "yarn tsc && yarn node . src/DB-Beta.config.json daggerbotbeta true",
"sdk": "yarn dlx @yarnpkg/sdks vscode" "sdk": "yarn dlx @yarnpkg/sdks vscode"
}, },
"author": "Toast", "author": "Toast",
@ -54,7 +54,7 @@
"@types/ms": "0.7.34", "@types/ms": "0.7.34",
"@types/node": "20.11.20", "@types/node": "20.11.20",
"@types/node-cron": "3.0.11", "@types/node-cron": "3.0.11",
"@types/pg": "8.11.0", "@types/pg": "8.11.1",
"typescript": "5.3.3" "typescript": "5.3.3"
} }
} }

View File

@ -25,16 +25,16 @@ export default class Case {
update: async()=>{ update: async()=>{
const reason = interaction.options.getString('reason'); const reason = interaction.options.getString('reason');
await client.punishments.updateReason(caseId, reason); await client.punishments.updateReason(caseId, reason);
if (client.punishments.findCase(caseId)) { if (client.punishments.findCaseOrCancels('case_id', caseId)) {
await this.updateEntry(client, caseId, reason); await this.updateEntry(client, caseId, reason);
await interaction.reply({embeds: [new client.embed().setColor(client.config.embedColorGreen).setTitle('Case updated').setDescription(`Case #${caseId} has been successfully updated with new reason:\n\`${reason}\``)]}); await interaction.reply({embeds: [new client.embed().setColor(client.config.embedColorGreen).setTitle('Case updated').setDescription(`Case #${caseId} has been successfully updated with new reason:\n\`${reason}\``)]});
} else interaction.reply({embeds: [new client.embed().setColor(client.config.embedColorRed).setTitle('Case not updated').setDescription(`Case #${caseId} is not found in database, not updating the reason.`)]}); } else interaction.reply({embeds: [new client.embed().setColor(client.config.embedColorRed).setTitle('Case not updated').setDescription(`Case #${caseId} is not found in database, not updating the reason.`)]});
}, },
view: async()=>{ view: async()=>{
const punishment = await client.punishments.findCase(caseId); const punishment = await client.punishments.findCaseOrCancels('case_id', caseId);
if (!punishment) return interaction.reply('Case ID is not found in database.'); if (!punishment) return interaction.reply('Case ID is not found in database.');
const cancelledBy = punishment.dataValues.expired ? await client.punishments.findByCancels(punishment.dataValues.case_id) : null; const cancelledBy = punishment.dataValues.expired ? await client.punishments.findCaseOrCancels('cancels', punishment.dataValues.case_id) : null;
const cancels = punishment.dataValues.cancels ? await client.punishments.findCase(punishment.dataValues.cancels) : null; const cancels = punishment.dataValues.cancels ? await client.punishments.findCaseOrCancels('case_id', punishment.dataValues.cancels) : null;
const embed = new client.embed().setColor(client.config.embedColor).setTimestamp(Number(punishment.dataValues.time)).setTitle(`${punishment.dataValues.type[0].toUpperCase()+punishment.dataValues.type.slice(1)} | Case #${punishment.dataValues.case_id}`).addFields( const embed = new client.embed().setColor(client.config.embedColor).setTimestamp(Number(punishment.dataValues.time)).setTitle(`${punishment.dataValues.type[0].toUpperCase()+punishment.dataValues.type.slice(1)} | Case #${punishment.dataValues.case_id}`).addFields(
{name: 'User', value: `${punishment.member_name}\n${MessageTool.formatMention(punishment.dataValues.member, 'user')}\n\`${punishment.dataValues.member}\``, inline: true}, {name: 'User', value: `${punishment.member_name}\n${MessageTool.formatMention(punishment.dataValues.member, 'user')}\n\`${punishment.dataValues.member}\``, inline: true},
{name: 'Moderator', value: `${client.users.resolve(punishment.moderator).tag}\n${MessageTool.formatMention(punishment.dataValues.moderator, 'user')}\n\`${punishment.dataValues.moderator}\``, inline: true}, {name: 'Moderator', value: `${client.users.resolve(punishment.moderator).tag}\n${MessageTool.formatMention(punishment.dataValues.moderator, 'user')}\n\`${punishment.dataValues.moderator}\``, inline: true},

View File

@ -5,7 +5,7 @@ import MessageTool from '../helpers/MessageTool.js';
export default class Unpunish { export default class Unpunish {
static async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ static async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
if (!MessageTool.isModerator(interaction.member as Discord.GuildMember)) return MessageTool.youNeedRole(interaction, 'dcmod'); if (!MessageTool.isModerator(interaction.member as Discord.GuildMember)) return MessageTool.youNeedRole(interaction, 'dcmod');
const punishment = await client.punishments.findCase(interaction.options.getInteger('case_id', true)); const punishment = await client.punishments.findCaseOrCancels('case_id', interaction.options.getInteger('case_id', true));
if (!punishment) return interaction.reply({content: 'Case ID is not found in database.', ephemeral: true}); if (!punishment) return interaction.reply({content: 'Case ID is not found in database.', ephemeral: true});
if (['unban', 'unmute', 'punishmentOverride'].includes(punishment.dataValues.type)) return interaction.reply({content: 'This case ID is immutable. (Informative case)', ephemeral: true}); if (['unban', 'unmute', 'punishmentOverride'].includes(punishment.dataValues.type)) return interaction.reply({content: 'This case ID is immutable. (Informative case)', ephemeral: true});
if (punishment.dataValues.expired) return interaction.reply({content: 'This case ID is already expired.', ephemeral: true}); if (punishment.dataValues.expired) return interaction.reply({content: 'This case ID is already expired.', ephemeral: true});

View File

@ -2,6 +2,7 @@ import Discord from 'discord.js';
import TClient from '../client.js'; import TClient from '../client.js';
import Logger from '../helpers/Logger.js'; import Logger from '../helpers/Logger.js';
export default class Automoderator { export default class Automoderator {
private static logPrefix:string = 'Automod';
private static lockQuery:Set<Discord.Snowflake> = new Set(); private static lockQuery:Set<Discord.Snowflake> = new Set();
static scanMsg =(message:Discord.Message)=>message.content.toLowerCase().replaceAll(/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?\n?0-9]|[]|ing\b|ed\b/g, '').split(' ').join(''); static scanMsg =(message:Discord.Message)=>message.content.toLowerCase().replaceAll(/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?\n?0-9]|[]|ing\b|ed\b/g, '').split(' ').join('');
static async repeatedMessages(client:TClient, message:Discord.Message, action:'mute'|'ban'|'softban', thresholdTime:number, thresholdAmount:number, type:string, duration:string, reason:string) { static async repeatedMessages(client:TClient, message:Discord.Message, action:'mute'|'ban'|'softban', thresholdTime:number, thresholdAmount:number, type:string, duration:string, reason:string) {
@ -17,11 +18,11 @@ export default class Automoderator {
// If the count has reached the threshold amount, punish the user like most daddy would do to their child. // If the count has reached the threshold amount, punish the user like most daddy would do to their child.
if (!this.lockQuery.has(message.author.id)) { if (!this.lockQuery.has(message.author.id)) {
this.lockQuery.add(message.author.id); this.lockQuery.add(message.author.id);
Logger.console('log', 'AUTOMOD', `Lock acquired for ${message.author.tag} with reason: ${reason}`); Logger.console('log', this.logPrefix, `Lock acquired for ${message.author.tag} with reason: ${reason}`);
await client.punishments.punishmentAdd(action, {time: duration}, client.user.id, `AUTOMOD:${reason}`, message.author, message.member as Discord.GuildMember); await client.punishments.punishmentAdd(action, {time: duration}, client.user.id, `AUTOMOD:${reason}`, message.author, message.member as Discord.GuildMember);
setTimeout(()=>{ setTimeout(()=>{
this.lockQuery.delete(message.author.id); this.lockQuery.delete(message.author.id);
Logger.console('log', 'AUTOMOD', `Lock released for ${message.author.tag}`); Logger.console('log', this.logPrefix, `Lock released for ${message.author.tag}`);
}, 3500); // Wait 3.5 seconds before releasing the lock. }, 3500); // Wait 3.5 seconds before releasing the lock.
} }
delete client.repeatedMessages[message.author.id]; delete client.repeatedMessages[message.author.id];

View File

@ -4,22 +4,23 @@ import TSClient from '../helpers/TSClient.js';
const postgresUri = (await TSClient()).postgres_uri; const postgresUri = (await TSClient()).postgres_uri;
export default class DatabaseServer { export default class DatabaseServer {
private static logPrefix:string = 'Database';
public static seq:Sequelize = new Sequelize(postgresUri, {dialect: 'postgres', logging: false, ssl: false, pool: {max: 10, min: 0, acquire: 15000, idle: 8000}}) public static seq:Sequelize = new Sequelize(postgresUri, {dialect: 'postgres', logging: false, ssl: false, pool: {max: 10, min: 0, acquire: 15000, idle: 8000}})
public static async init() { public static async init() {
try { try {
await this.seq.authenticate(); await this.seq.authenticate();
this.healthCheck(); this.healthCheck();
} catch { } catch {
Logger.console('error', 'Database', 'Cannot initialize Sequelize -- is PostgreSQL running?'); Logger.console('error', this.logPrefix, 'Cannot initialize Sequelize -- is PostgreSQL running?');
process.exit(1); process.exit(1);
} }
} }
private static async healthCheck() { private static async healthCheck() {
try { try {
await this.seq.query('SELECT 1'); await this.seq.query('SELECT 1');
Logger.console('log', 'Database', 'Connection to PostgreSQL has been established'); Logger.console('log', this.logPrefix, 'Connection to PostgreSQL has been established');
} catch { } catch {
Logger.console('error', 'Database', 'Connection to PostgreSQL has been lost'); Logger.console('error', this.logPrefix, 'Connection to PostgreSQL has been lost');
} }
} }
} }

View File

@ -15,21 +15,22 @@ export default class MessageCreate {
if (client.config.botSwitches.automod && !message.member?.roles.cache.has(client.config.dcServer.roles.dcmod) && !message.member?.roles.cache.has(client.config.dcServer.roles.admin) && message.guildId === client.config.dcServer.id) { if (client.config.botSwitches.automod && !message.member?.roles.cache.has(client.config.dcServer.roles.dcmod) && !message.member?.roles.cache.has(client.config.dcServer.roles.admin) && message.guildId === client.config.dcServer.id) {
const automodFailReason = 'msg got possibly deleted by another bot.'; const automodFailReason = 'msg got possibly deleted by another bot.';
const automodLog = 'Automod:';
const automodRules = { const automodRules = {
phishingDetection: { phishingDetection: {
check: async()=>await __PRIVATE__.phishingDetection(message), check: async()=>await __PRIVATE__.phishingDetection(message),
action: async()=>{ action: async()=>{
automodded = true; automodded = true;
message.delete().catch(()=>Logger.console('log', 'AUTOMOD:PHISHING', automodFailReason)); message.delete().catch(()=>Logger.console('log', `${automodLog}Phishing`, automodFailReason));
message.channel.send('Phishing links aren\'t allowed here. Nice try though!').then(msg=>setTimeout(()=>msg.delete(), 15000)); message.channel.send('Phishing links aren\'t allowed here. Nice try though!').then(msg=>setTimeout(()=>msg.delete(), 15000));
await Automoderator.repeatedMessages(client, message, 'softban', 60000, 3, 'phish', '15m', 'Phishing/scam link'); await Automoderator.repeatedMessages(client, message, 'softban', 60000, 3, 'phish', '15m', 'Phishing scam link');
} }
}, },
prohibitedWords: { prohibitedWords: {
check: async()=>await client.prohibitedWords.findWord(Automoderator.scanMsg(message)), check: async()=>await client.prohibitedWords.findWord(Automoderator.scanMsg(message)),
action: async()=>{ action: async()=>{
automodded = true; automodded = true;
message.delete().catch(()=>Logger.console('log', 'AUTOMOD:PROHIBITEDWORDS', automodFailReason)); message.delete().catch(()=>Logger.console('log', `${automodLog}ProhibitedWords`, automodFailReason));
message.channel.send('That word isn\'t allowed here.').then(x=>setTimeout(()=>x.delete(), 15000)); message.channel.send('That word isn\'t allowed here.').then(x=>setTimeout(()=>x.delete(), 15000));
await Automoderator.repeatedMessages(client, message, 'mute', 30000, 3, 'bw', '30m', 'Prohibited word spam'); await Automoderator.repeatedMessages(client, message, 'mute', 30000, 3, 'bw', '30m', 'Prohibited word spam');
} }
@ -40,7 +41,7 @@ export default class MessageCreate {
const validInvite = await client.fetchInvite(message.content.split(' ').find(x=>x.includes('discord.gg/'))).catch(()=>null); const validInvite = await client.fetchInvite(message.content.split(' ').find(x=>x.includes('discord.gg/'))).catch(()=>null);
if (validInvite && validInvite.guild?.id !== client.config.dcServer.id) { if (validInvite && validInvite.guild?.id !== client.config.dcServer.id) {
automodded = true; automodded = true;
message.delete().catch(()=>Logger.console('log', 'AUTOMOD:ADVERTISEMENT', automodFailReason)); message.delete().catch(()=>Logger.console('log', `${automodLog}Advertisement`, automodFailReason));
message.channel.send('Please don\'t advertise other Discord servers.').then(x=>setTimeout(()=>x.delete(), 15000)); message.channel.send('Please don\'t advertise other Discord servers.').then(x=>setTimeout(()=>x.delete(), 15000));
await Automoderator.repeatedMessages(client, message, 'ban', 60000, 4, 'adv', null, 'Discord advertisement'); await Automoderator.repeatedMessages(client, message, 'ban', 60000, 4, 'adv', null, 'Discord advertisement');
} }

View File

@ -21,7 +21,7 @@ process.on('unhandledRejection', (error: Error)=>_(error, 'unhandledRejection'))
process.on('uncaughtException', (error: Error)=>_(error, 'uncaughtException')); process.on('uncaughtException', (error: Error)=>_(error, 'uncaughtException'));
process.on('error', (error: Error)=>_(error, 'processError')); process.on('error', (error: Error)=>_(error, 'processError'));
client.on('error', (error: Error)=>_(error, 'clientError')); client.on('error', (error: Error)=>_(error, 'clientError'));
if (process.argv[3] ?? null) client.on('debug', console.log); if ((typeof process.argv[4] === 'string' && process.argv[4] === 'true') ?? null) client.on('debug', console.log);
// Interval timers for modules // Interval timers for modules
setInterval(async()=>await MPModule(client), refreshTimerSecs); setInterval(async()=>await MPModule(client), refreshTimerSecs);
@ -33,9 +33,8 @@ setInterval(async()=>{
for await (const thread of forum.threads.cache.values()) { for await (const thread of forum.threads.cache.values()) {
await thread.messages.fetch(); await thread.messages.fetch();
if (!thread.archived && thread.lastMessage.createdTimestamp <= Date.now() - 1555200000) {// check if thread is inactive for over 18 days if (!thread.archived && thread.lastMessage.createdTimestamp <= Date.now() - 1555200000) {// check if thread is inactive for over 18 days
await thread.setLocked(true).catch(()=>null); await thread.delete('Thread has been inactive for 18 days');
await thread.setArchived(true, 'Inactive for over 18 days').catch(()=>null); Logger.console('log', 'ThreadTimer', `"#${thread.name}" has been deleted due to inactivity for 18 days`);
Logger.console('log', 'ThreadTimer', `${thread.name} has been archived and locked due to inactivity`);
} }
} }
}, 1200000); // 20 minutes }, 1200000); // 20 minutes
@ -100,15 +99,13 @@ if (!client.config.botSwitches.logs) {
rawSwitches.MESSAGE_UPDATE = true; rawSwitches.MESSAGE_UPDATE = true;
}; };
client.on('raw', async (packet:RawGatewayPacket<RawMessageUpdate>)=>{ client.on('raw', async (packet:RawGatewayPacket<RawMessageUpdate>)=>{
if (rawSwitches[packet.t]) return; if (rawSwitches[packet.t] || packet.t !== 'MESSAGE_UPDATE') return;
if (packet.t !== 'MESSAGE_UPDATE') return; if (packet.d.guild_id != client.config.dcServer.id || disabledChannels.includes(packet.d.channel_id) || typeof packet.d.content === 'undefined') return;
if (packet.d.guild_id != client.config.dcServer.id || disabledChannels.includes(packet.d.channel_id)) return;
if (typeof packet.d.content === 'undefined') return;
const channel = client.channels.cache.get(packet.d.channel_id) as Discord.TextBasedChannel; const channel = client.channels.cache.get(packet.d.channel_id) as Discord.TextBasedChannel;
const message = await channel.messages.fetch(packet.d.id);
client.emit('messageUpdate', message, message); // Switched to console.log to prevent useless embed creation that has same content as the original message.
if (!rawSwitches.MESSAGE_UPDATE) return Logger.console('log', 'RawEvent:Edit', `Message was edited in #${(channel as Discord.TextChannel).name}`);
}); });
client.on('raw', async (packet:RawGatewayPacket<RawMessageDelete>)=>{ client.on('raw', async (packet:RawGatewayPacket<RawMessageDelete>)=>{

View File

@ -96,15 +96,15 @@ export class PunishmentsSvc {
} }
query = async(pattern:string)=>await this.model.sequelize.query(pattern); query = async(pattern:string)=>await this.model.sequelize.query(pattern);
async updateReason(caseId:number, reason:string) { async updateReason(caseId:number, reason:string) {
const findCase = this.findCase(caseId); const findCase = this.findCaseOrCancels('case_id', caseId);
if (findCase) return this.model.update({reason: reason}, {where: {case_id: caseId}}); if (findCase) return this.model.update({reason}, {where: {case_id: caseId}});
} }
findCase =(caseId:number)=>this.model.findOne({where: {case_id: caseId}}); findCaseOrCancels = (column:'case_id'|'cancels', id:number)=>this.model.findOne({where: {[column]: id}});
findByCancels =(caseId:number)=>this.model.findOne({where: {cancels: caseId}})
getAllCases =()=>this.model.findAll(); getAllCases =()=>this.model.findAll();
async generateCaseId() { async generateCaseId() {
const result = await this.model.max('case_id'); const result = await this.model.max('case_id');
return (result as number ?? 0) + 1; if (typeof result === 'number') return result + 1;
else return 0;
} }
async findInCache():Promise<Punishment[]> { async findInCache():Promise<Punishment[]> {
const cacheKey = 'punishments'; const cacheKey = 'punishments';
@ -213,7 +213,8 @@ export class PunishmentsSvc {
}); });
} catch (err) { } catch (err) {
Logger.console('error', 'Punishment', err); Logger.console('error', 'Punishment', err);
return TRANSACTION_FAILED; Logger.console('log', 'Punishment:Transaction', TRANSACTION_FAILED);
return;
} }
if (interaction) return interaction.editReply({embeds: [embed]}); if (interaction) return interaction.editReply({embeds: [embed]});
@ -272,7 +273,8 @@ export class PunishmentsSvc {
}); });
} catch (err) { } catch (err) {
Logger.console('error', 'Punishment', err); Logger.console('error', 'Punishment', err);
return TRANSACTION_FAILED; Logger.console('log', 'Punishment:Transaction', TRANSACTION_FAILED);
return;
} }
if (interaction) return interaction.reply({embeds: [new this.client.embed() if (interaction) return interaction.reply({embeds: [new this.client.embed()

View File

@ -530,14 +530,14 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/pg@npm:8.11.0": "@types/pg@npm:8.11.1":
version: 8.11.0 version: 8.11.1
resolution: "@types/pg@npm:8.11.0" resolution: "@types/pg@npm:8.11.1"
dependencies: dependencies:
"@types/node": "npm:*" "@types/node": "npm:*"
pg-protocol: "npm:*" pg-protocol: "npm:*"
pg-types: "npm:^4.0.1" pg-types: "npm:^4.0.1"
checksum: 10/91a7ccc5dc8e162d9d181f48f699eb4628632e75bd9db1c0ca774ec9b055b177875fb4b426279961061912f25fb3948c48c96e63ac9e85efcf7ce6f2567b6485 checksum: 10/2fdcb0dc331c4f4e334061c30a14187a6d835a049be9e9a2d84d55935025c45be7f5f40fd6b854c19fcf72d17e854481b11a461d7679005d9813bf29c56ba380
languageName: node languageName: node
linkType: hard linkType: hard
@ -628,7 +628,7 @@ __metadata:
"@types/ms": "npm:0.7.34" "@types/ms": "npm:0.7.34"
"@types/node": "npm:20.11.20" "@types/node": "npm:20.11.20"
"@types/node-cron": "npm:3.0.11" "@types/node-cron": "npm:3.0.11"
"@types/pg": "npm:8.11.0" "@types/pg": "npm:8.11.1"
ansi-colors: "npm:4.1.3" ansi-colors: "npm:4.1.3"
dayjs: "npm:1.11.10" dayjs: "npm:1.11.10"
discord.js: "npm:14.14.1" discord.js: "npm:14.14.1"