diff --git a/.pnp.cjs b/.pnp.cjs index 0f8508b..02065f3 100644 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -34,6 +34,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@octokit/rest", "npm:20.0.2"],\ ["@types/ms", "npm:0.7.32"],\ ["@types/node", "npm:20.8.2"],\ + ["@types/node-cron", "npm:3.0.9"],\ ["canvas", "npm:2.11.2"],\ ["discord-player", "virtual:20c353e2d6536e37339997f03975c6a660f4d296e664d291bd43620c6162cca8eb5ef90b0998dc9db75ff6862e5da587d0530bae26805f5fadc8f17aaa4ff794#npm:6.6.4"],\ ["discord.js", "npm:14.13.0"],\ @@ -41,6 +42,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["moment", "npm:2.29.4"],\ ["mongoose", "npm:7.5.4"],\ ["ms", "npm:2.1.3"],\ + ["node-cron", "npm:3.0.2"],\ ["prism-media", "virtual:20c353e2d6536e37339997f03975c6a660f4d296e664d291bd43620c6162cca8eb5ef90b0998dc9db75ff6862e5da587d0530bae26805f5fadc8f17aaa4ff794#npm:1.3.5"],\ ["redis", "npm:4.6.10"],\ ["systeminformation", "npm:5.21.10"],\ @@ -677,6 +679,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["@types/node-cron", [\ + ["npm:3.0.9", {\ + "packageLocation": "./.yarn/cache/@types-node-cron-npm-3.0.9-13a60c6bb4-8335eb0a45.zip/node_modules/@types/node-cron/",\ + "packageDependencies": [\ + ["@types/node-cron", "npm:3.0.9"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@types/webidl-conversions", [\ ["npm:7.0.0", {\ "packageLocation": "./.yarn/cache/@types-webidl-conversions-npm-7.0.0-0903313151-60142c7ddd.zip/node_modules/@types/webidl-conversions/",\ @@ -1038,6 +1049,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@octokit/rest", "npm:20.0.2"],\ ["@types/ms", "npm:0.7.32"],\ ["@types/node", "npm:20.8.2"],\ + ["@types/node-cron", "npm:3.0.9"],\ ["canvas", "npm:2.11.2"],\ ["discord-player", "virtual:20c353e2d6536e37339997f03975c6a660f4d296e664d291bd43620c6162cca8eb5ef90b0998dc9db75ff6862e5da587d0530bae26805f5fadc8f17aaa4ff794#npm:6.6.4"],\ ["discord.js", "npm:14.13.0"],\ @@ -1045,6 +1057,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["moment", "npm:2.29.4"],\ ["mongoose", "npm:7.5.4"],\ ["ms", "npm:2.1.3"],\ + ["node-cron", "npm:3.0.2"],\ ["prism-media", "virtual:20c353e2d6536e37339997f03975c6a660f4d296e664d291bd43620c6162cca8eb5ef90b0998dc9db75ff6862e5da587d0530bae26805f5fadc8f17aaa4ff794#npm:1.3.5"],\ ["redis", "npm:4.6.10"],\ ["systeminformation", "npm:5.21.10"],\ @@ -2131,6 +2144,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["node-cron", [\ + ["npm:3.0.2", {\ + "packageLocation": "./.yarn/cache/node-cron-npm-3.0.2-5ee1c1c226-dd21585c0d.zip/node_modules/node-cron/",\ + "packageDependencies": [\ + ["node-cron", "npm:3.0.2"],\ + ["uuid", "npm:8.3.2"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["node-domexception", [\ ["npm:1.0.0", {\ "packageLocation": "./.yarn/cache/node-domexception-npm-1.0.0-e1e813b76f-ee1d37dd2a.zip/node_modules/node-domexception/",\ @@ -2899,6 +2922,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["uuid", [\ + ["npm:8.3.2", {\ + "packageLocation": "./.yarn/cache/uuid-npm-8.3.2-eca0baba53-5575a8a75c.zip/node_modules/uuid/",\ + "packageDependencies": [\ + ["uuid", "npm:8.3.2"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["web-streams-polyfill", [\ ["npm:3.2.1", {\ "packageLocation": "./.yarn/cache/web-streams-polyfill-npm-3.2.1-835bd3857e-b119c78574.zip/node_modules/web-streams-polyfill/",\ diff --git a/package.json b/package.json index 4a1be25..00784ae 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "moment": "2.29.4", "mongoose": "7.5.4", "ms": "2.1.3", + "node-cron": "3.0.2", "prism-media": "1.3.5", "redis": "4.6.10", "systeminformation": "5.21.10", @@ -50,6 +51,7 @@ }, "devDependencies": { "@types/ms": "0.7.32", - "@types/node": "20.8.2" + "@types/node": "20.8.2", + "@types/node-cron": "3.0.9" } } diff --git a/src/commands/rank.ts b/src/commands/rank.ts index ef555a5..4441dd9 100644 --- a/src/commands/rank.ts +++ b/src/commands/rank.ts @@ -1,5 +1,6 @@ import Discord from 'discord.js'; import TClient from '../client.js'; +import MessageTool from '../helpers/MessageTool.js'; import path from 'node:path'; import {readFileSync} from 'node:fs'; import canvas from 'canvas'; @@ -68,8 +69,15 @@ export default { let previousY: Array = []; ctx.strokeStyle = '#202225'; //'#555B63'; + if (chosen_interval === undefined) return interaction.reply({content: MessageTool.concatMessage( + 'No data to display for now. It is also possible that the following either happened:', + '- No interval was found for the graph. This is likely due to the fact that there is not enough data to generate a graph.', + '- The level system was recently reset.', + '- The graph is currently being generated in the background. Please try again in a few minutes.', + 'If you believe this is a mistake, please contact **Toast** or the **Discord Moderation** team.' + ), ephemeral: true}); for (let i = 0; i <= chosen_interval[1]; i++) { - const y = graphOrigin[1] + graphSize[1] - (i * (chosen_interval[0] / second_graph_top) * graphSize[1]); + const y = graphOrigin[1] + graphSize[1] - (i * (chosen_interval[0] / second_graph_top) * graphSize[1]); if (y < graphOrigin[1]) continue; const even = ((i + 1) % 2) === 0; if (even) ctx.strokeStyle = '#2c2f33'; //'#3E4245'; @@ -146,12 +154,13 @@ export default { }, notification: async()=>{ const findUserInMongo = await client.userLevels._content.findById(interaction.user.id); - if (!findUserInMongo.notificationPing ?? findUserInMongo.notificationPing === false) { + const textDeco = ' be pinged for level-up notification in the future.' + if (!findUserInMongo.notificationPing) { await findUserInMongo.updateOne({_id: interaction.user.id, notificationPing: true}) - interaction.reply({content: 'You will be pinged for level-up notification in the future.', ephemeral: true}) - } else if (findUserInMongo.notificationPing === true) { + interaction.reply({content: 'You will'+textDeco, ephemeral: true}) + } else if (findUserInMongo.notificationPing) { await findUserInMongo.updateOne({_id: interaction.user.id, notificationPing: false}) - interaction.reply({content: 'You won\'t be pinged for level-up notification in the future.', ephemeral: true}) + interaction.reply({content: 'You won\'t'+textDeco, ephemeral: true}) } } } as any)[interaction.options.getSubcommand()](); @@ -171,4 +180,4 @@ export default { .addSubcommand(x=>x .setName('notification') .setDescription('Allow the bot to ping you or not when you level up')) -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index b35bf99..ae1d0bf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,7 @@ import YTModule from './funcs/YTModule.js'; import MPModule from './funcs/MPModule.js'; import {Player} from 'discord-player'; const player = Player.singleton(client); +import {Punishment} from './typings/interfaces'; import MessageTool from './helpers/MessageTool.js'; import {writeFileSync, readFileSync} from 'node:fs'; @@ -30,8 +31,8 @@ if (client.config.botSwitches.music){ if (queue.tracks.size < 1) return queue.channel.send('There\'s no songs left in the queue, leaving voice channel in 15 seconds.').then(()=>setTimeout(()=>queue.connection.disconnect(), 15000)) }); player.events.on('playerPause', queue=>queue.channel.send({embeds:[MessageTool.embedMusic(client.config.embedColor, 'Player has been paused.\nRun the command to unpause it')]})); - player.events.on('playerError', (queue, error)=>DZ(error, 'playerError')); // I don't know if both of these actually works, because most - player.events.on('error', (queue, error)=>DZ(error, 'playerInternalError')); // errors from the player is coming from unhandledRejection + player.events.on('playerError', (_, error)=>DZ(error, 'playerError')); // I don't know if both of these actually works, because most + player.events.on('error', (_, error)=>DZ(error, 'playerInternalError')); // errors from the player is coming from unhandledRejection } // YouTube Upload notification and MP loop @@ -49,7 +50,7 @@ setInterval(async()=>{ const now = Date.now(); const punishments = await client.punishments.findInCache(); - punishments.filter(x=>x.endTime && x.endTime<= now && !x.expired).forEach(async punishment=>{ + punishments.filter((x:Punishment)=>x.endTime && x.endTime<= now && !x.expired).forEach(async (punishment:Punishment)=>{ Logger.forwardToConsole('log', 'Punishment', `${punishment.member}\'s ${punishment.type} should expire now`); Logger.forwardToConsole('log', 'Punishment', await client.punishments.removePunishment(punishment._id, client.user.id, 'Time\'s up!')); }); @@ -57,6 +58,8 @@ setInterval(async()=>{ const formattedDate = Math.floor((now - client.config.LRSstart)/1000/60/60/24); const dailyMsgs = JSON.parse(readFileSync('./src/database/dailyMsgs.json', 'utf8')) if (client.config.botSwitches.dailyMsgsBackup && !dailyMsgs.some((x:Array)=>x[0] === formattedDate)){ + client.userLevels.resetAllData(); // reset all data on 1st of January every year + let total = (await client.userLevels._content.find({})).reduce((a,b)=>a + b.messages, 0); // sum of all users const yesterday = dailyMsgs.find((x:Array)=>x[0] === formattedDate - 1); if (total < yesterday) total = yesterday // messages went down. diff --git a/src/models/userLevels.ts b/src/models/userLevels.ts index 6a0d683..0024513 100644 --- a/src/models/userLevels.ts +++ b/src/models/userLevels.ts @@ -1,6 +1,8 @@ import Discord from 'discord.js'; import TClient from '../client.js'; import mongoose from 'mongoose'; +import cron from 'node-cron'; +import {writeFileSync, readFileSync} from 'node:fs'; import Logger from '../helpers/Logger.js'; const Schema = mongoose.model('userLevels', new mongoose.Schema({ @@ -18,6 +20,30 @@ export default class userLevels extends Schema { this.client = client; this._content = Schema; } + async resetAllData(){ + // Every 1st of January at 00:00 + cron.schedule('0 0 1 1 *', async()=>{ + Logger.forwardToConsole('log', 'Cron', 'Running job "resetAllData", this is activated every 1st of January'); + const countDataBeforeReset = await this._content.countDocuments(); + Logger.forwardToConsole('log', 'Cron:resetAllData', `Counted ${countDataBeforeReset.toLocaleString()} documents before reset`); + await this._content.deleteMany(); + Logger.forwardToConsole('log', 'Cron:resetAllData', 'Deleted all documents, now resetting dailyMsgs'); + const dailyMsgsBak = readFileSync('src/database/dailyMsgs.json', 'utf-8'); + writeFileSync(`src/database/dailyMsgs_${new Date().getTime()}.json`, dailyMsgsBak); + writeFileSync('src/database/dailyMsgs.json', JSON.stringify([])); + // Send notification to mainServer's logs channel after cronjob is complete. + (this.client.channels.resolve(this.client.config.mainServer.channels.logs) as Discord.TextChannel).send({embeds: [new this.client.embed().setColor('#A3FFE3').setTitle('Yearly data reset has begun!').setDescription(`I have gone ahead and reset everyone's rank data. There was ${Intl.NumberFormat('en-US').format(await countDataBeforeReset)} documents in database before reset.`).setFooter({text: 'dailyMsgs has been backed up and wiped too.'}).setTimestamp()]}); + + // Reset LRSstart to current epoch and write it to config file + const newEpoch = new Date().getTime(); + this.client.config.LRSstart = newEpoch; + const logText = `Resetting LRSstart to \`${newEpoch}\`, saved to config file`; + Logger.forwardToConsole('log', 'DailyMsgs', logText); + (this.client.channels.resolve(this.client.config.mainServer.channels.logs) as Discord.TextChannel).send({embeds: [new this.client.embed().setColor(this.client.config.embedColorXmas).setTitle('Happy New Years! Level System is clean!').setDescription(logText).setTimestamp()]}).catch(err=>console.log(err)); + writeFileSync('./src/DB-Beta.config.json', JSON.stringify(this.client.config, null, 2)); + Logger.forwardToConsole('log', 'Cron:resetAllData', 'Job completed'); + }) + } async incrementUser(userid:string){ const userData = await this._content.findById(userid) if (userData){ diff --git a/tsconfig.json b/tsconfig.json index 96078d6..bcf240e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,8 +2,10 @@ "compilerOptions": { "skipLibCheck": true, "removeComments": true, + "noUnusedLocals": true, "esModuleInterop": true, "resolveJsonModule": true, + "noUnusedParameters": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, diff --git a/yarn.lock b/yarn.lock index c18ae06..eca2530 100644 --- a/yarn.lock +++ b/yarn.lock @@ -461,6 +461,13 @@ __metadata: languageName: node linkType: hard +"@types/node-cron@npm:3.0.9": + version: 3.0.9 + resolution: "@types/node-cron@npm:3.0.9" + checksum: 8335eb0a453b956cc2da5431269e4732e0063c6cca0763cf779c7c242d0dfe3eea1929951a0b11fb9e7a2eee838836ee8cda79e9d6098b1bee91d5a17dce1e73 + languageName: node + linkType: hard + "@types/node@npm:*": version: 20.4.8 resolution: "@types/node@npm:20.4.8" @@ -791,6 +798,7 @@ __metadata: "@octokit/rest": 20.0.2 "@types/ms": 0.7.32 "@types/node": 20.8.2 + "@types/node-cron": 3.0.9 canvas: 2.11.2 discord-player: 6.6.4 discord.js: 14.13.0 @@ -798,6 +806,7 @@ __metadata: moment: 2.29.4 mongoose: 7.5.4 ms: 2.1.3 + node-cron: ^3.0.2 prism-media: 1.3.5 redis: 4.6.10 systeminformation: 5.21.10 @@ -1735,6 +1744,15 @@ __metadata: languageName: node linkType: hard +"node-cron@npm:^3.0.2": + version: 3.0.2 + resolution: "node-cron@npm:3.0.2" + dependencies: + uuid: 8.3.2 + checksum: dd21585c0d4069a0752022dad9b8380a4393c4783ec78355ffa99ff32b018c3743a35d4ebf9d7c7863949e94e302b440f58c884eb4960e71c7260d817e2d3f25 + languageName: node + linkType: hard + "node-domexception@npm:^1.0.0": version: 1.0.0 resolution: "node-domexception@npm:1.0.0" @@ -2409,6 +2427,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:8.3.2": + version: 8.3.2 + resolution: "uuid@npm:8.3.2" + bin: + uuid: dist/bin/uuid + checksum: 5575a8a75c13120e2f10e6ddc801b2c7ed7d8f3c8ac22c7ed0c7b2ba6383ec0abda88c905085d630e251719e0777045ae3236f04c812184b7c765f63a70e58df + languageName: node + linkType: hard + "web-streams-polyfill@npm:^3.0.3": version: 3.2.1 resolution: "web-streams-polyfill@npm:3.2.1"