From 5bb910dd3c8b00b4fc194032d0e65892a52875a4 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sun, 1 Sep 2024 02:54:48 -0500 Subject: [PATCH] feat: Write4, moving to version 4, giveaway command (#46) * fix compile errors * v4 * giveaway progress * forgot to fix warning * 15+ minute should work * giveaway command functional * should now exclude host * date-fns + host excluded * minor fixes + more exclude host --------- Co-authored-by: kingomes <83099848+kingomes@users.noreply.github.com> --- package.json | 4 +- src/commands/docs.ts | 5 +- src/commands/emoji.ts | 17 +- src/commands/eval.ts | 25 +- src/commands/github.ts | 26 +- src/commands/giveaway.ts | 165 ++++++++++++ src/commands/handlers/emojiAccept.ts | 3 +- src/commands/handlers/emojiDeny.ts | 6 +- src/commands/handlers/emojiModal.ts | 6 +- src/commands/handlers/tagCreate.ts | 8 +- src/commands/handlers/tagEdit.ts | 9 +- src/commands/logs.ts | 34 --- src/commands/menu.ts | 14 +- src/commands/plugin.ts | 4 +- src/commands/refresh.ts | 8 +- src/commands/solved.ts | 1 - src/commands/tag.ts | 25 +- src/commands/tags.ts | 2 +- src/commands/time.ts | 7 +- src/constants.ts | 5 + src/dependencies.d.ts | 15 +- src/events/embedReact.ts | 15 ++ src/events/forumCreate.ts | 37 +-- src/events/module.activate.ts | 15 +- src/index.ts | 18 +- src/plugins/cooldown.ts | 233 ++++++++++------- src/plugins/onCorrectThread.ts | 16 -- src/plugins/publish.ts | 1 - src/utils/SyncCommands.ts | 73 ------ src/utils/composable/slashCommand.ts | 15 +- src/utils/db.ts | 6 + yarn.lock | 375 ++++++++++++++++++++++++--- 32 files changed, 779 insertions(+), 414 deletions(-) create mode 100644 src/commands/giveaway.ts delete mode 100644 src/commands/logs.ts create mode 100644 src/events/embedReact.ts delete mode 100644 src/plugins/onCorrectThread.ts delete mode 100644 src/utils/SyncCommands.ts create mode 100644 src/utils/db.ts diff --git a/package.json b/package.json index 2b68656..ce175ce 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,9 @@ "license": "MIT", "dependencies": { "@octokit/rest": "19.0.7", - "@sern/handler": "^3.3.2", + "@sern/handler": "^4.0.0", + "better-sqlite3": "^11.1.2", + "date-fns": "^3.6.0", "discord.js": "^14.14.1", "dotenv": "16.0.3", "jsdoc-parse-plus": "1.3.0", diff --git a/src/commands/docs.ts b/src/commands/docs.ts index dab089b..97f2695 100644 --- a/src/commands/docs.ts +++ b/src/commands/docs.ts @@ -19,6 +19,7 @@ function handleComments(sum: PurpleSummary) { } const docHandler = new DocHandler(); docHandler.setup(); + export default commandModule({ type: CommandType.Slash, description: "Query documentation", @@ -43,8 +44,8 @@ export default commandModule({ }, }, ], - execute: async (context, options) => { - const option = options[1].getString("search", true); + execute: async (context) => { + const option = context.options.getString("search", true); const result = docHandler.DocTrie.search(option); if (!result.length) { diff --git a/src/commands/emoji.ts b/src/commands/emoji.ts index 0493ea1..da64c5b 100644 --- a/src/commands/emoji.ts +++ b/src/commands/emoji.ts @@ -44,15 +44,15 @@ export default slashCommand({ ], }, ], - execute: async (ctx, [, args]) => { - const command = args.getSubcommand(); + execute: async (ctx) => { + const command = ctx.options.getSubcommand(); await ctx.interaction.deferReply(); switch (command) { case "submit": { - const attachment = args.getAttachment("attachment"); - const urlString = args.getString("url"); - const name = args.getString("name", true); + const attachment = ctx.options.getAttachment("attachment"); + const urlString = ctx.options.getString("url"); + const name = ctx.options.getString("name", true); const send = sendTo("1014582281907753080", ctx.member as GuildMember, name); if (attachment) { @@ -60,11 +60,8 @@ export default slashCommand({ attachment, (a) => a.size <= 256_000, (a) => a.contentType?.startsWith("image/") || false, - (a) => - ["image/png", "image/jpg", "image/gif"].includes( - a.contentType ?? - "Something that is not png or jpg when contentType is null", - ), + (a) => ["image/png", "image/jpg", "image/gif"].includes( + a.contentType ?? "Something that is not png or jpg when contentType is null"), ); if (!isValidAttachment) { return ctx.interaction.editReply({ diff --git a/src/commands/eval.ts b/src/commands/eval.ts index bf251a6..a108f70 100644 --- a/src/commands/eval.ts +++ b/src/commands/eval.ts @@ -6,11 +6,10 @@ import { Evo, Seren, Mina } from "#constants"; export default commandModule({ type: CommandType.Text, description: "Eval something", - alias: ["ev"], - execute: async (ctx, args) => { + execute: async (ctx) => { if (![Evo, Seren, Mina].includes(ctx.user.id)) return; - let code: string[] | string = args[1]; + let code: string[] | string = ctx.options; code = code.join(" ") as string; if (code.includes("await")) { @@ -45,27 +44,7 @@ export default commandModule({ if ((result as string).length > 2000) { channel!.send("Result is too long to send"); } - ctx.channel!.send({ content: result as string }); - - function send(id: string, ping = false) { - const channel = client.channels.cache.get(id); - if (!channel) return; - const embed = new EmbedBuilder() - .setColor(0xcc5279) - .setTitle("v2 is out!") - .setThumbnail(client.user?.displayAvatarURL() ?? "") - .setImage("https://raw.githubusercontent.com/sern-handler/.github/main/banner.png") - .setAuthor({ name: "sern", url: "https://sern.dev/" }) - .setDescription( - `__**Quick Look:**__\n\n${text()}\n\nThank you all for being patient!`, - ) - .setFooter({ text: "Supports DJS v14.7 and above" }) - .setTimestamp(); - const content = ping ? "@everyone" : undefined; - channel.isTextBased() && channel.send({ content, embeds: [embed] }); - return "Done sir"; - } }, }); diff --git a/src/commands/github.ts b/src/commands/github.ts index abbea6e..dcdb818 100644 --- a/src/commands/github.ts +++ b/src/commands/github.ts @@ -1,10 +1,10 @@ import { publish } from "#plugins"; -import { CommandType, Service, commandModule } from "@sern/handler"; +import { CommandType, commandModule } from "@sern/handler"; import { ApplicationCommandOptionType } from "discord.js"; import { Timestamp } from "#utils"; import { Emojis } from "#constants"; const prefix = (t: unknown) => (t ? "$" : "#"); -const octokit = Service("octokit"); + export default commandModule({ type: CommandType.Slash, description: "Get info about a PR or issue", @@ -18,7 +18,7 @@ export default commandModule({ autocomplete: true, command: { onEvent: [], - async execute(ctx) { + async execute(ctx, { deps: { octokit } }) { const text = ctx.options.getFocused(); const org = await octokit.repos.listForOrg({ org: "sern-handler", @@ -32,10 +32,8 @@ export default commandModule({ const publicRepos = topRepos .filter((r) => !r.private) - .map((repo) => ({ - name: `sern/${repo.name}`, - value: repo.name, - })); + .map((repo) => ({ name: `sern/${repo.name}`, + value: repo.name })); if (!text.length) { return ctx.respond(publicRepos.slice(0, 25)).catch(() => null); @@ -60,7 +58,7 @@ export default commandModule({ autocomplete: true, command: { onEvent: [], - async execute(ctx) { + async execute(ctx, { deps }) { const text = ctx.options.getFocused(); const repo = ctx.options.getString("repo"); if (!repo) return ctx.respond([]); @@ -68,7 +66,7 @@ export default commandModule({ let search; if (text.length) { - search = await octokit.search + search = await deps.octokit.search .issuesAndPullRequests({ q: `repo:sern-handler/${repo} ${text} in:title`, }) @@ -76,7 +74,7 @@ export default commandModule({ } if (!text.length) { - const issues = await octokit.issues + const issues = await deps.octokit.issues .listForRepo({ owner: "sern-handler", repo, @@ -120,10 +118,10 @@ export default commandModule({ required: false, }, ], - async execute(ctx, [, options]) { - const repo = options.getString("repo", true); - const number = options.getInteger("number", true); - const target = options.getUser("target"); + async execute(ctx, { deps: { octokit }}) { + const repo = ctx.options.getString("repo", true); + const number = ctx.options.getInteger("number", true); + const target = ctx.options.getUser("target"); const issue = await octokit.issues .get({ diff --git a/src/commands/giveaway.ts b/src/commands/giveaway.ts new file mode 100644 index 0000000..7008be5 --- /dev/null +++ b/src/commands/giveaway.ts @@ -0,0 +1,165 @@ +import { commandModule, CommandType, scheduledTask } from "@sern/handler"; +import { publish } from "#plugins"; +import { ApplicationCommandOptionType, EmbedBuilder } from "discord.js"; +import { db } from "../utils/db.js"; +import { add, addDays, addHours, addMinutes, addSeconds } from "date-fns" +import { Timestamp } from "#utils"; + +export default commandModule({ + type: CommandType.Slash, + description: "Start a giveaway involving users who react to the embed", + plugins: [publish()], + options: [ + { + name: "item", + description: "The item that will be given away", + type: ApplicationCommandOptionType.String, + required: true + }, + { + name: "time", + description: "The amount of time that the giveaway will be up", + type: ApplicationCommandOptionType.String, + required: true + } + ], + execute: async (ctx, { deps }) => { + const item = ctx.options.getString("item") + const timeLeftString = ctx.options.getString("time") + + let timeUnit1 + let timeLeft1 + let timeUnit2 + let timeLeft2 + + const [part1, part2] = timeLeftString?.split("and") + timeUnit1 = part1?.split(" ")[1] + timeLeft1 = Number(part1?.split(" ")[0]) + + if (part2) { + const timeLeftStringPart2 = part2.replace(part2.substring(0, 1), "") + timeUnit2 = timeLeftStringPart2?.split(" ")[1] + timeLeft2 = Number(timeLeftStringPart2?.split(" ")[0]) + } + + const startTime = new Date() + + let endTime: Date + + const secondNames = ['seconds', 'second', 'sec', 'secs'] + const minuteNames = ['minutes', 'minute', 'min', 'mins'] + const hourNames = ['hours', 'hour', 'hr', 'hrs'] + const dayNames = ['days', 'day'] + + endTime = add(startTime, { + timeUnit1: timeLeft1, + timeUnit2: timeLeft2 + }) + + // This if chain uses date-fns to correctly calculate the time allocated to the giveaway based on what the + // user types (seconds, minutes, etc.) + + // if the time unit before the "and" is "seconds" or one of the other entries in the secondNames array, add the time entered + // to the startTime and save that in the endTime + if (secondNames.includes(timeUnit1!)) { + endTime = endTime === startTime ? addSeconds(startTime, timeLeft1) : addSeconds(endTime, timeLeft1) + } + // if the time unit after the "and" is "seconds" or one of the other entries in the secondNames array, add the time entered + // to the startTime and save that in the endTime + if (secondNames.includes(timeUnit2!)) { + endTime = endTime === startTime ? addSeconds(startTime, timeLeft2!) : addSeconds(endTime, timeLeft2!) + } + // if the time unit before the "and" is "minutes" or one of the other entries in the minuteNames array, add the time entered + // to the startTime and save that in the endTime + if (minuteNames.includes(timeUnit1!)) { + endTime = endTime === startTime ? addMinutes(startTime, timeLeft1) : addMinutes(endTime, timeLeft1) + } + // if the time unit after the "and" is "minutes" or one of the other entries in the minuteNames array, add the time entered + // to the startTime and save that in the endTime + if (minuteNames.includes(timeUnit2!)) { + endTime = endTime === startTime ? addMinutes(startTime, timeLeft2!) : addMinutes(endTime, timeLeft2!) + } + // if the time unit before the "and" is "hours" or one of the other entries in the hourNames array, add the time entered + // to the startTime and save that in the endTime + if (hourNames.includes(timeUnit1!)) { + endTime = endTime === startTime ? addHours(startTime, timeLeft1) : addHours(endTime, timeLeft1) + } + // if the time unit after the "and" is "hours" or one of the other entries in the hourNames array, add the time entered + // to the startTime and save that in the endTime + if (hourNames.includes(timeUnit2!)) { + endTime = endTime === startTime ? addHours(startTime, timeLeft2!) : addHours(endTime, timeLeft2!) + } + // if the time unit before the "and" is "days" or one of the other entries in the dayNames array, add the time entered + // to the startTime and save that in the endTime + if (dayNames.includes(timeUnit1!)) { + endTime = endTime === startTime ? addDays(startTime, timeLeft1) : addDays(endTime, timeLeft1) + } + // if the time unit after the "and" is "days" or one of the other entries in the dayNames array, add the time entered + // to the startTime and save that in the endTime + if (dayNames.includes(timeUnit2!)) { + endTime = endTime === startTime ? addDays(startTime, timeLeft2!) : addDays(endTime, timeLeft2!) + } + + const endTimeStamp: string = `` + const endTimeStamp2 = new Timestamp(endTime.getTime()).timestamp + + let embed = new EmbedBuilder() + .setTitle(`🥳 ${item} giveaway 🥳`) + .setDescription('React to enter the giveaway!') + .addFields( + {name: '\u200b', value: `Hosted by: <@${ctx.userId}>`}, + {name: '\u200b', value: `Ends: ${new Timestamp(Number(endTimeStamp2)).getRelativeTime()} (${endTimeStamp})`} + ) + + + await ctx.reply({ + embeds: [embed], + }).then(embedMessage => { + embedMessage.react("🎉") + + setInterval(() => { + const userReactions = embedMessage.reactions.cache.filter(reaction => reaction.users.cache.has(ctx.userId)); + + for (const reaction of userReactions.values()) { + reaction.users.remove(ctx.userId); + } + }, 1000) + + let intervalTime = endTime.getTime() - startTime.getTime() + + setTimeout(() => { + const stmt = db.prepare(`SELECT * FROM entrees`).all() + + let winnerIndex = Math.floor(Math.random() * stmt.length) + + if (stmt.length > 0 && stmt[winnerIndex].user_id !== ctx.userId) { + const winnerId = stmt[winnerIndex].user_id + + embed.setDescription('\u200b') + embed.setFields( + {name: '\u200b', value: `Winner: <@${winnerId}>`}, + {name: '\u200b', value: `Hosted by: <@${ctx.userId}>`}, + {name: '\u200b', value: `Ended: ${new Timestamp(Number(endTimeStamp2)).getRelativeTime()} (${endTimeStamp})`} + ) + + embedMessage.edit({embeds: [embed]}) + } + else if (stmt.length > 1 && stmt[winnerIndex].user_id === ctx.userId) { + winnerIndex = Math.floor(Math.random() * stmt.length) + } + else if ((stmt.length === 1 && stmt[winnerIndex].user_id === ctx.userId) || stmt.length === 0) { + embed.setDescription('\u200b') + embed.setFields( + {name: '\u200b', value: `Not enough eligible users`}, + {name: '\u200b', value: `Hosted by: <@${ctx.userId}>`}, + {name: '\u200b', value: `Ended: ${new Timestamp(Number(endTimeStamp2)).getRelativeTime()} (${endTimeStamp})`} + ) + + embedMessage.edit({embeds: [embed]}) + } + }, intervalTime) + }) + + db.prepare(`DELETE FROM entrees`).run() + } +}) \ No newline at end of file diff --git a/src/commands/handlers/emojiAccept.ts b/src/commands/handlers/emojiAccept.ts index 546996e..2d1230f 100644 --- a/src/commands/handlers/emojiAccept.ts +++ b/src/commands/handlers/emojiAccept.ts @@ -25,8 +25,7 @@ export default commandModule({ .setPlaceholder("Name of the emoji") .setRequired() .setStyle(TextInputStyle.Short) - .setValue(suggestedName), - ); + .setValue(suggestedName)); modal.setComponents(row); await ctx.showModal(modal); diff --git a/src/commands/handlers/emojiDeny.ts b/src/commands/handlers/emojiDeny.ts index 10b7460..525ec68 100644 --- a/src/commands/handlers/emojiDeny.ts +++ b/src/commands/handlers/emojiDeny.ts @@ -1,6 +1,6 @@ import { ownerIDs } from "#constants"; import { commandModule, CommandType } from "@sern/handler"; -import { ActionRowBuilder, ButtonBuilder, EmbedBuilder } from "discord.js"; +import { ActionRowBuilder, ButtonBuilder, ButtonComponentData, EmbedBuilder } from "discord.js"; export default commandModule({ type: CommandType.Button, @@ -17,9 +17,7 @@ export default commandModule({ const components = [ new ActionRowBuilder().setComponents( ctx.message!.components[0].components.map((c) => - new ButtonBuilder(c.data).setDisabled(), - ), - ), + new ButtonBuilder(c.data as ButtonComponentData).setDisabled())), ]; const embed = new EmbedBuilder(ctx.message?.embeds[0]?.data) // diff --git a/src/commands/handlers/emojiModal.ts b/src/commands/handlers/emojiModal.ts index cc885be..62ff024 100644 --- a/src/commands/handlers/emojiModal.ts +++ b/src/commands/handlers/emojiModal.ts @@ -1,5 +1,5 @@ import { commandModule, CommandType } from "@sern/handler"; -import { ActionRowBuilder, ButtonBuilder, EmbedBuilder } from "discord.js"; +import { ActionRowBuilder, ButtonBuilder, ButtonComponentData, EmbedBuilder } from "discord.js"; export default commandModule({ type: CommandType.Modal, @@ -33,9 +33,7 @@ export default commandModule({ const components = [ new ActionRowBuilder().setComponents( ctx.message!.components[0].components.map((c) => - new ButtonBuilder(c.data).setDisabled(), - ), - ), + new ButtonBuilder(c.data as Readonly).setDisabled())), ]; const embed = new EmbedBuilder(ctx.message?.embeds[0]?.data) // diff --git a/src/commands/handlers/tagCreate.ts b/src/commands/handlers/tagCreate.ts index bbba8c3..4c76c56 100644 --- a/src/commands/handlers/tagCreate.ts +++ b/src/commands/handlers/tagCreate.ts @@ -17,15 +17,11 @@ export default commandModule({ name: tagName, content: tagContent, keywords: keywords - ? [ - ...new Set( - keywords + ? [ ...new Set(keywords .trim() .split(",") .map((c) => c.trim()) - .filter((c) => !!c.length), - ), - ] + .filter((c) => !!c.length))] : [], }; const filePath = `./tags.json`; diff --git a/src/commands/handlers/tagEdit.ts b/src/commands/handlers/tagEdit.ts index 103002c..9e575fd 100644 --- a/src/commands/handlers/tagEdit.ts +++ b/src/commands/handlers/tagEdit.ts @@ -18,11 +18,10 @@ export default commandModule({ name: tagName, content: tagContent, keywords: keywords - ? keywords - .trim() - .split(",") - .map((c) => c.trim()) - .filter((c) => !!c.length) + ? keywords.trim() + .split(",") + .map((c) => c.trim()) + .filter((c) => !!c.length) : [], }; const filePath = `./tags.json`; diff --git a/src/commands/logs.ts b/src/commands/logs.ts deleted file mode 100644 index cafd514..0000000 --- a/src/commands/logs.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { slashCommand } from "#utils"; -import { readFile } from "fs/promises"; -import path from "path"; -import type { Context, ReplyOptions } from "@sern/handler"; -import { ownerOnly, publish } from "#plugins"; -import * as fs from "fs"; -import { AttachmentBuilder } from "discord.js"; - -async function ephemeral(ctx: Context, options: ReplyOptions) { - const resolvedOptions = typeof options == "string" ? { content: options } : options; - await ctx.interaction.editReply({ ...resolvedOptions }); -} - -export default slashCommand({ - description: "Fetch logs", - plugins: [publish(), ownerOnly()], - execute: async (ctx, args) => { - try { - const controller = new AbortController(); - const logPath = path.join(process.cwd(), "error.log"); - const readHandle = readFile(logPath, { signal: controller.signal }); - await ctx.interaction.deferReply({ ephemeral: true }); - if (!fs.existsSync(logPath)) { - controller.abort(); - } - const log = await readHandle; - await ephemeral(ctx, { - files: [new AttachmentBuilder(log).setName("error.log")], - }); - } catch (e) { - await ephemeral(ctx, "Couldn't find log. In dev mode?"); - } - }, -}); diff --git a/src/commands/menu.ts b/src/commands/menu.ts index e3442ff..5adb24c 100644 --- a/src/commands/menu.ts +++ b/src/commands/menu.ts @@ -19,7 +19,7 @@ export default slashCommand({ name: "channel", type: ApplicationCommandOptionType.Channel, description: "The channel to send the message to", - channelTypes: [ChannelType.GuildText], + channel_types: [ChannelType.GuildText], required: true, }, { @@ -35,7 +35,8 @@ export default slashCommand({ required: true, }, ], - async execute(ctx, [, options]) { + async execute(ctx) { + const options = ctx.options; const channel = options.getChannel("channel", true) as TextChannel; const role = new Resolver(options.getString("role", true), ctx.interaction).roles; const message = options.getString("message", true); @@ -71,14 +72,11 @@ function createMenu(channel: TextChannel, role: Collection) { .setMaxValues(role.size) .setMinValues(0) .setPlaceholder("Pick some roles here!") - .setOptions( - role.map((r) => { + .setOptions(role.map((r) => { return { label: r.name, value: r.id, }; - }), - ); - const row = new ActionRowBuilder().setComponents(menu); - return row; + })); + return new ActionRowBuilder().setComponents(menu); } diff --git a/src/commands/plugin.ts b/src/commands/plugin.ts index ec67231..fe70283 100644 --- a/src/commands/plugin.ts +++ b/src/commands/plugin.ts @@ -14,7 +14,6 @@ export default slashCommand({ required: true, autocomplete: true, command: { - onEvent: [], async execute(ctx) { const plugins = require(PluginList) as Plugin[]; @@ -45,7 +44,8 @@ export default slashCommand({ }), ), ], - async execute(ctx, [, options]) { + async execute(ctx) { + const { options } = ctx; const plugins = require(PluginList) as Plugin[]; if (!plugins.length) return ctx.reply("Plugins are uncached, contact Evo!"); diff --git a/src/commands/refresh.ts b/src/commands/refresh.ts index 41cac66..17151ee 100644 --- a/src/commands/refresh.ts +++ b/src/commands/refresh.ts @@ -6,10 +6,8 @@ import type { Plugin } from "typings"; export default slashCommand({ plugins: [ - publish({ - dmPermission: false, - defaultMemberPermissions: ["Administrator"], - }), + publish({ dmPermission: false, + defaultMemberPermissions: ["Administrator"], }), ownerOnly([Evo]), ], description: "refresh plugins cache", @@ -31,7 +29,7 @@ export async function cp(): Promise { const link = `https://raw.githubusercontent.com/sern-handler/awesome-plugins/main/pluginlist.json`; const resp = await fetch(link).catch(() => null); if (!resp) return null; - const dataArray = (await resp.json()) as Plugin[]; + const dataArray = await resp.json() as Plugin[]; writeFileSync(PluginList, JSON.stringify(dataArray, null, 2), { flag: "w" }); return dataArray.length; diff --git a/src/commands/solved.ts b/src/commands/solved.ts index 3d205d2..9299c04 100644 --- a/src/commands/solved.ts +++ b/src/commands/solved.ts @@ -19,7 +19,6 @@ export default slashCommand({ content: "Something bad happened, Please contact Evo!", ephemeral: true, }); - const memberCount = `• \`${ctx.channel.memberCount}\` member(s) participated in this post!`; const msgCount = `• \`${(ctx.channel.messageCount ?? 0) + 1}\` message(s) are present here`; const msgSent = `• \`${ diff --git a/src/commands/tag.ts b/src/commands/tag.ts index 336ca1e..e623a12 100644 --- a/src/commands/tag.ts +++ b/src/commands/tag.ts @@ -41,8 +41,7 @@ export default slashCommand({ ? t.toLowerCase().includes(focus.toLowerCase()) : true, ) - .map((t) => ({ name: t, value: t })), - ); + .map((t) => ({ name: t, value: t }))); } }, }, @@ -56,8 +55,8 @@ export default slashCommand({ ], }, ], - execute(ctx, args) { - const [, options] = args; + execute(ctx) { + const options = ctx.options; const subCmd = options.getSubcommand(); switch (subCmd) { case "list": { @@ -74,15 +73,15 @@ export default slashCommand({ .setTimestamp(); return embed; }); - const paginator = new Paginator({ embeds }).setSelectMenuOptions( - ...Array(embeds.length) - .fill(null) - .map((_, i) => ({ - label: embeds[i].data.title!, - value: `${i}`, - default: i === 0, - })), - ); + const paginator = new Paginator({ embeds }) + .setSelectMenuOptions( + ...Array(embeds.length) + .fill(null) + .map((_, i) => ({ + label: embeds[i].data.title!, + value: `${i}`, + default: i === 0, + }))); return paginator.run(ctx.interaction); } diff --git a/src/commands/tags.ts b/src/commands/tags.ts index b1fca8c..6676ac2 100644 --- a/src/commands/tags.ts +++ b/src/commands/tags.ts @@ -96,7 +96,7 @@ export default slashCommand({ }, ], execute: async (context, args) => { - const [, options] = args; + const { options } = context; const subcmd = options.getSubcommand(); const file: TagData[] = require(TagList); diff --git a/src/commands/time.ts b/src/commands/time.ts index 4efd77a..a409a42 100644 --- a/src/commands/time.ts +++ b/src/commands/time.ts @@ -20,10 +20,8 @@ export default slashCommand({ required: true, autocomplete: true, command: { - onEvent: [], execute: async (autocomplete) => { const input = autocomplete.options.getFocused(); - return autocomplete.respond(fuzz(input)).catch(() => null); }, }, @@ -49,7 +47,8 @@ export default slashCommand({ type: ApplicationCommandOptionType.Subcommand, }, ], - execute: async (ctx, [, options]) => { + execute: async (ctx) => { + const { options } = ctx; switch (options.getSubcommand()) { case "create": { const reqData = { @@ -84,7 +83,7 @@ export default slashCommand({ `https://api.srizan.dev/sern/getTime?userid=${option.id}`, ).catch(() => null); - const data = (await request?.json()) as APIResponse; + const data = await request?.json() as APIResponse; if (!data) return ctx.reply({ diff --git a/src/constants.ts b/src/constants.ts index c512059..76a055b 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -16,3 +16,8 @@ export const enum Emojis { export const PluginList = `${process.cwd()}/pluginlist.json`; export const TagList = `${process.cwd()}/tags.json`; + + +export const commands = './dist/src/commands'; +export const events = './dist/src/events' +export const defaultPrefix= "sern"; diff --git a/src/dependencies.d.ts b/src/dependencies.d.ts index b0416c2..9355b58 100644 --- a/src/dependencies.d.ts +++ b/src/dependencies.d.ts @@ -5,22 +5,17 @@ */ import type { - SernEmitter, - Logging, - CoreModuleStore, - ModuleManager, - ErrorHandling, CoreDependencies, - Singleton, } from "@sern/handler"; import type { Client } from "discord.js"; import type { SernLogger } from "./utils/Logger"; import type { Octokit } from "@octokit/rest"; declare global { - interface Dependencies extends Dependencies { - "@sern/client": Singleton; - "@sern/logger": Singleton; - octokit: Singleton; + interface Dependencies extends CoreDependencies { + "@sern/client": Client; + "@sern/logger": SernLogger; + octokit: Octokit; + process: NodeJS.Process } } diff --git a/src/events/embedReact.ts b/src/events/embedReact.ts new file mode 100644 index 0000000..da6b01a --- /dev/null +++ b/src/events/embedReact.ts @@ -0,0 +1,15 @@ +import { discordEvent } from "@sern/handler"; +import { db } from "../utils/db.js" + +export default discordEvent({ + name: 'messageReactionAdd', + execute: async (reaction, potentialWinners) => { + const startTime = reaction.message.createdTimestamp + + const stmt = db.prepare(`INSERT INTO entrees(timestamp, user_id) VALUES (?, ?)`) + + if (potentialWinners.bot === false) { + stmt.run([startTime, potentialWinners.id]) + } + } +}) diff --git a/src/events/forumCreate.ts b/src/events/forumCreate.ts index 44a6c1d..7e0ddb2 100644 --- a/src/events/forumCreate.ts +++ b/src/events/forumCreate.ts @@ -1,13 +1,25 @@ import { eventModule, EventType } from "@sern/handler"; -import { AnyThreadChannel, EmbedBuilder } from "discord.js"; -import { onCorrectThread } from "../plugins/onCorrectThread.js"; +import { AnyThreadChannel, ChannelType, EmbedBuilder } from "discord.js"; import { forumID } from "#constants"; +function onCorrectThread(thread: AnyThreadChannel, parentId: string, newlyMade: boolean) { + const isBadThread = + !thread.parent || + thread.parentId !== parentId || + thread.parent.type !== ChannelType.GuildForum || + !newlyMade; + if (!isBadThread) { + return true + } + return false; +} export default eventModule({ type: EventType.Discord, - plugins: [onCorrectThread(forumID)], name: "threadCreate", - async execute(thread: AnyThreadChannel, _: boolean) { + async execute(thread: AnyThreadChannel, newlyMade: boolean) { + if(!onCorrectThread(thread, forumID, newlyMade)) { + return; + } if (thread.appliedTags.length > 3) await thread.setAppliedTags(thread.appliedTags.slice(0, 3)); @@ -17,20 +29,15 @@ export default eventModule({ const list = `• What is your [\`discord.js\`](https://discord.js.org/) version?\n• What is your [\`@sern/handler\`](https://sern.dev) version?\n• If any error is occuring, what error?`; const embed = new EmbedBuilder() - .setDescription( - `Hello ${msg.author}! Thank you for creating a dedicated post for your issue\n` + - `Please make sure you've read the **__Post Guidelines__**!\n` + - `In the meanwhile you wait for your answer, It is recommended to provide details as much as possible\n` - .concat(list) - .concat(`\n\n\nIssue Solved? Run `), - ) + .setDescription(`Hello ${msg.author}! Thank you for creating a dedicated post for your issue\n` + + 'Please make sure you\'ve read the **__Post Guidelines__**!\n \ + In the meanwhile wait for the answer, It is recommended to provide details as much as possible\n' + .concat(list) + .concat(`\n\n\nIssue Solved? Run `)) .setColor("Random") .setTimestamp() .setThumbnail(msg.client.user!.displayAvatarURL({ size: 2048 })) .setTitle("Things you should know"); - - await msg.reply({ - embeds: [embed], - }); + await msg.reply({ embeds: [embed] }); }, }); diff --git a/src/events/module.activate.ts b/src/events/module.activate.ts index 52c75f2..d5968b5 100644 --- a/src/events/module.activate.ts +++ b/src/events/module.activate.ts @@ -1,18 +1,14 @@ import { - controller, - EventControlPlugin, eventModule, EventType, Payload, - PayloadType, Service, } from "@sern/handler"; export default eventModule({ name: "module.activate", type: EventType.Sern, - plugins: [filterFailedActivation()], - execute(payload: Payload & { type: PayloadType.Failure }) { + execute(payload: Payload & { type: 'failure' }) { const logger = Service("@sern/logger"); logger.warning({ message: `A module (${payload.module?.name} failed to execute: ${payload.reason}`, @@ -20,12 +16,3 @@ export default eventModule({ }, }); -function filterFailedActivation() { - return EventControlPlugin((payload) => { - if (payload.type == PayloadType.Failure) { - return controller.next(); - } else { - return controller.stop(); - } - }); -} diff --git a/src/index.ts b/src/index.ts index 19dc9a6..981134c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import "dotenv/config"; +import * as config from './config.js' import { Client, GatewayIntentBits, Partials } from "discord.js"; -import { Sern, single, makeDependencies, Service } from "@sern/handler"; +import { Sern, makeDependencies, Service } from "@sern/handler"; import { SernLogger } from "#utils"; import { Octokit } from "@octokit/rest"; import { cp } from "./commands/refresh.js"; @@ -11,6 +12,7 @@ const client = new Client({ GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, + GatewayIntentBits.GuildMessageReactions, ], partials: [Partials.GuildMember, Partials.Message, Partials.ThreadMember, Partials.Channel], sweepers: { @@ -21,15 +23,11 @@ const client = new Client({ }, }); -await makeDependencies({ - build: (root) => - root - .add({ "@sern/client": () => client }) - .upsert({ "@sern/logger": () => new SernLogger("info") }) - .add({ - process: () => process, - octokit: () => new Octokit({ auth: process.env.GITHUB_TOKEN }), - }), +await makeDependencies(root => { + root.add("@sern/client", client); + root.swap("@sern/logger", new SernLogger("info")) + root.add('octokit', new Octokit({ auth: process.env.GITHUB_TOKEN })) + root.add('process', process) }); Sern.init({ diff --git a/src/plugins/cooldown.ts b/src/plugins/cooldown.ts index 15098d7..6263fed 100644 --- a/src/plugins/cooldown.ts +++ b/src/plugins/cooldown.ts @@ -1,147 +1,180 @@ -// @ts-nocheck /** - * Allows you to set cooldowns (or "ratelimits") for commands - * limits user/channel/guild actions, + * @plugin + * Allows you to set cooldowns (or "ratelimits") for commands, limits user/channel/guild actions. + * An extra function cooldown2 is exported if you want your cooldown to be local to the command. * @author @trueharuu [<@504698587221852172>] * @version 1.0.0 * @example * ```ts - * import { cooldown } from "../plugins/cooldown"; + * import { cooldown, cooldown2 } from "../plugins/cooldown"; * import { commandModule } from "@sern/handler"; + * //IF you want this cooldown to be local to this command: + * const localCooldown = cooldown2() * export default commandModule({ - * plugins: [cooldown.add( [ ['channel', '1/4'] ] )], // limit to 1 action every 4 seconds per channel - * execute: (ctx) => { - * //your code here - * } + * plugins: [cooldown.add([['channel', '1/4']]), // limit to 1 action every 4 seconds per channel + * localCooldown.add([["user", "1/10"]])], // limit to 1 action every 10 seconds, local to command + * execute: (ctx) => { //your code here } * }) * ``` + * @end */ -import { CommandControlPlugin, CommandType, Context, controller } from "@sern/handler"; +import { + CommandControlPlugin, + CommandType, + Context, + controller, +} from "@sern/handler"; import { GuildMember } from "discord.js"; /** * actions/seconds */ export type CooldownString = `${number}/${number}`; export interface Cooldown { - location: CooldownLocation; - seconds: number; - actions: number; + location: CooldownLocation; + seconds: number; + actions: number; } export enum CooldownLocation { - channel = "channel", - user = "user", - guild = "guild", + channel = "channel", + user = "user", + guild = "guild", } export class ExpiryMap extends Map { - public readonly expiry: number; - constructor(expiry: number = Infinity, iterable: [K, V][] | ReadonlyMap = []) { - super(iterable); - this.expiry = expiry; - } + public readonly expiry: number; + constructor( + expiry: number = Infinity, + iterable: [K, V][] | ReadonlyMap = [], + ) { + super(iterable); + this.expiry = expiry; + } - public set(key: K, value: V, expiry: number = this.expiry): this { - super.set(key, value); - if (expiry !== Infinity) - setTimeout(() => { - super.delete(key); - }, expiry); - return this; - } + public set(key: K, value: V, expiry: number = this.expiry): this { + super.set(key, value); + if (expiry !== Infinity) + setTimeout(() => { + super.delete(key); + }, expiry); + return this; + } } -export const map = new ExpiryMap(); -function parseCooldown(location: CooldownLocation, cooldown: CooldownString): Cooldown { - const [actions, seconds] = cooldown.split("/").map((s) => Number(s)); +function parseCooldown( + location: CooldownLocation, + cooldown: CooldownString, +): Cooldown { + const [actions, seconds] = cooldown.split("/").map((s) => Number(s)); - if ( - !Number.isSafeInteger(actions) || - !Number.isSafeInteger(seconds) || - actions === undefined || - seconds === undefined - ) { - throw new Error(`Invalid cooldown string: ${cooldown}`); - } + if ( + !Number.isSafeInteger(actions) || + !Number.isSafeInteger(seconds) || + actions === undefined || + seconds === undefined + ) { + throw new Error(`Invalid cooldown string: ${cooldown}`); + } - return { - actions, - seconds, - location, - }; + return { + actions, + seconds, + location, + }; } function getPropertyForLocation(context: Context, location: CooldownLocation) { - switch (location) { - case CooldownLocation.channel: - return context.channel!.id; - case CooldownLocation.user: - if (!context.member || !(context.member instanceof GuildMember)) { - throw new Error("context.member is not a GuildMember"); - } - return context.member.id; - case CooldownLocation.guild: - return context.guildId; - } + switch (location) { + case CooldownLocation.channel: + return context.channel!.id; + case CooldownLocation.user: + if (!context.member || !(context.member instanceof GuildMember)) { + throw new Error("context.member is not a GuildMember"); + } + return context.member.id; + case CooldownLocation.guild: + return context.guildId; + } } export interface RecievedCooldown { - location: CooldownLocation; - actions: number; - maxActions: number; - seconds: number; - context: Context; + location: CooldownLocation; + actions: number; + maxActions: number; + seconds: number; + context: Context; } type CooldownResponse = (cooldown: RecievedCooldown) => any; -function add( - items: Array<[CooldownLocation | keyof typeof CooldownLocation, CooldownString] | Cooldown>, - message?: CooldownResponse, -) { - const raw = items.map((c) => { - if (!Array.isArray(c)) return c; - return parseCooldown(c[0] as CooldownLocation, c[1]); - }) as Array; - return CommandControlPlugin(async (context, args) => { - for (const { location, actions, seconds } of raw) { - const id = getPropertyForLocation(context, location); - const cooldown = map.get(id!); +function __add(map : ExpiryMap, + items: Array<| [CooldownLocation + | keyof typeof CooldownLocation, CooldownString] + | Cooldown>, + message?: CooldownResponse) { + const raw = items.map((c) => { + if (!Array.isArray(c)) return c; + return parseCooldown(c[0] as CooldownLocation, c[1]); + }) as Array; - if (!cooldown) { - map.set(id!, 1, seconds * 1000); - continue; - } + return CommandControlPlugin(async (context, args) => { + for (const { location, actions, seconds } of raw) { + const id = getPropertyForLocation(context, location); + const cooldown = map.get(id!); - if (cooldown >= actions) { - if (message) { - await message({ - location, - actions: cooldown, - maxActions: actions, - seconds, - context, - }); - } - return controller.stop(); - } + if (!cooldown) { + map.set(id!, 1, seconds * 1000); + continue; + } - map.set(id!, cooldown + 1, seconds * 1000); - } - return controller.next(); - }); + if (cooldown >= actions) { + if (message) { + await message({ + location, + actions: cooldown, + maxActions: actions, + seconds, + context, + }); + } + return controller.stop(); + } + + map.set(id!, cooldown + 1, seconds * 1000); + } + return controller.next(); + }); } -type Location = (value: CooldownString) => ReturnType; +type Location = (value: CooldownString) => ReturnType; -const locations: Record = { - [CooldownLocation.channel]: (value) => add([[CooldownLocation.channel, value]]), - [CooldownLocation.user]: (value) => add([[CooldownLocation.user, value]]), - [CooldownLocation.guild]: (value) => add([[CooldownLocation.guild, value]]), +const locationsFn = (map: ExpiryMap)=> ({ + [CooldownLocation.channel]: (value) => __add(map, [[CooldownLocation.channel, value]]), + [CooldownLocation.user]: (value) => __add(map, [[CooldownLocation.user, value]]), + [CooldownLocation.guild]: (value) => __add(map, [[CooldownLocation.guild, value]]), +} as Record); + + + +export const cooldown2 = () => { + const cooldownMap = new ExpiryMap(); + return { + add:(items: Array<| [CooldownLocation + | keyof typeof CooldownLocation, CooldownString] + | Cooldown>, + message?: CooldownResponse) => __add(cooldownMap, items, message), + locations: locationsFn(cooldownMap), + map: cooldownMap + } }; +const map = new ExpiryMap(); export const cooldown = { - add, - locations, map, -}; + locations: locationsFn(map), + add: (items: Array<| [CooldownLocation + | keyof typeof CooldownLocation, CooldownString] + | Cooldown>, + message?: CooldownResponse) => __add(map, items, message) +} + diff --git a/src/plugins/onCorrectThread.ts b/src/plugins/onCorrectThread.ts deleted file mode 100644 index 91f410d..0000000 --- a/src/plugins/onCorrectThread.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { controller, DiscordEventControlPlugin } from "@sern/handler"; -import { ChannelType } from "discord.js"; - -export function onCorrectThread(parentId: string) { - return DiscordEventControlPlugin("threadCreate", (thread, newlyMade) => { - const isBadThread = - !thread.parent || - thread.parentId !== parentId || - thread.parent.type !== ChannelType.GuildForum || - !newlyMade; - if (!isBadThread) { - return controller.next(); - } - return controller.stop(); - }); -} diff --git a/src/plugins/publish.ts b/src/plugins/publish.ts index fe84569..e816493 100644 --- a/src/plugins/publish.ts +++ b/src/plugins/publish.ts @@ -25,7 +25,6 @@ import { controller, SernOptionsData, SlashCommand, - Service, } from "@sern/handler"; import { ApplicationCommandData, diff --git a/src/utils/SyncCommands.ts b/src/utils/SyncCommands.ts deleted file mode 100644 index a664abb..0000000 --- a/src/utils/SyncCommands.ts +++ /dev/null @@ -1,73 +0,0 @@ -// import type { SernLogger } from "./Logger"; -// import { readdir } from "fs/promises"; -// import path from "path"; -// import type { Client } from "discord.js"; -// import { ApplicationCommandType, basename } from "discord.js"; -// import type { BothCommand, CommandModule, ContextMenuMsg, ContextMenuUser, SlashCommand } from "@sern/handler"; -// import { CommandType } from "@sern/handler"; -// -// async function* getFiles(dir: string): AsyncGenerator { -// const dirents = await readdir(dir, { withFileTypes: true }); -// for (const dirent of dirents) { -// const res = path.resolve(dir, dirent.name); -// if (dirent.isDirectory()) { -// yield* getFiles(res); -// } else { -// yield res; -// } -// } -// } -// -// export class CommandSyncer { -// private commandsPath = "dist/src/commands"; -// -// constructor( -// private logger: SernLogger, -// private client: Client, -// private scopedGuilds : string[] = [] -// ) { -// setTimeout(() => { -// this -// .sync() -// .catch((e) => logger.error({ message: e ?? "Something went wrong with syncing" })); -// }, 20_000) -// } -// private publishable(module: CommandModule) { -// const publishableTypes = (CommandType.Both | CommandType.CtxUser | CommandType.CtxMsg | CommandType.Slash) -// return ((publishableTypes & ~CommandType.Text) & module.type) != 0 -// } -// private async handleSlashCommand(module: SlashCommand | BothCommand, resolvedName: string) { -// this.logger.debug({ message: `Checking if ${resolvedName} is already registered` }); -// if (this.scopedGuilds.length) { -// for (const guildId of this.scopedGuilds) { -// const guild = await this.client.guilds.fetch(guildId).catch(() => null); -// -// if (!guild) throw new Error(`Found no Guild with id ${guildId}!`); -// console.log(guild.name) -// console.log(await guild.commands.fetch()) -// } -// } -// } -// private handleContextMenus(module: ContextMenuUser | ContextMenuMsg, resolvedName: string) { -// -// } -// private async sync() { -// this.logger.info({ message: "Syncing commands" }); -// for await(const path of getFiles(this.commandsPath)) { -// const module = await import("file:///"+path).then(imp => imp.default) as CommandModule; //i would retrieve from the module store, but its a little bugged since -// if(this.publishable(module)) { -// const resolvedName = module.name ?? basename(path).slice(0, -3) -// switch(module.type) { -// case CommandType.Both: -// case CommandType.Slash: { -// await this.handleSlashCommand(module, resolvedName) -// } break; -// case CommandType.CtxMsg: -// case CommandType.CtxUser: { -// this.handleContextMenus(module, resolvedName) -// } break; -// } -// } -// } -// } -// } diff --git a/src/utils/composable/slashCommand.ts b/src/utils/composable/slashCommand.ts index 09602e7..7a72ff6 100644 --- a/src/utils/composable/slashCommand.ts +++ b/src/utils/composable/slashCommand.ts @@ -1,22 +1,25 @@ import { - AnyCommandPlugin, - BaseOptions, commandModule, CommandType, Context, SernSubCommandData, SernSubCommandGroupData, - SlashOptions, + Plugin, + SDT, + SlashCommand } from "@sern/handler"; +import { ChatInputCommandInteraction } from "discord.js"; export function slashCommand(data: { name?: string; description: string; - plugins?: AnyCommandPlugin[]; - options?: (SernSubCommandData | SernSubCommandGroupData | BaseOptions)[] | undefined; - execute: (ctx: Context, args: ["slash", SlashOptions]) => any; + plugins?: Plugin[]; + options?: SlashCommand['options']; + execute: (ctx: Context & { get options(): ChatInputCommandInteraction['options'] }, args: SDT) => any; + }) { //Weird fix for explicit undefined fields in an object const resolvedData = { type: CommandType.Slash, ...data } as const; return commandModule(resolvedData); } + diff --git a/src/utils/db.ts b/src/utils/db.ts new file mode 100644 index 0000000..192033e --- /dev/null +++ b/src/utils/db.ts @@ -0,0 +1,6 @@ +import Database from 'better-sqlite3'; +export const db = new Database('giveaway.db'); +db.pragma('journal_mode = WAL'); + + +db.exec(`CREATE TABLE IF NOT EXISTS entrees(timestamp, user_id)`); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 4930cdc..37323e2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -479,15 +479,24 @@ __metadata: languageName: node linkType: hard -"@sern/handler@npm:^3.3.2": - version: 3.3.2 - resolution: "@sern/handler@npm:3.3.2" +"@sern/handler@npm:^4.0.0": + version: 4.0.1 + resolution: "@sern/handler@npm:4.0.1" dependencies: + "@sern/ioc": ^1.1.0 callsites: ^3.1.0 - iti: ^0.6.0 + cron: ^3.1.7 + deepmerge: ^4.3.1 rxjs: ^7.8.0 - ts-results-es: ^4.0.0 - checksum: a405f9f4e914245dc0c0a9091508871a2cdbb50947a71797ec4faa64ba0ec775dc8b9aa0f116c4699752520ca4b5f54b624898d7da0f53375e4534a698d0c3d4 + ts-results-es: ^4.1.0 + checksum: ab461fa953f2086b5b8ebc089078a55535d0cccd6f5b3210c45bde33022d3be1f3b47bb3ab251b129c7ed33bfe08b873416c70aace50c101ac03d0097206552a + languageName: node + linkType: hard + +"@sern/ioc@npm:^1.1.0": + version: 1.1.0 + resolution: "@sern/ioc@npm:1.1.0" + checksum: 0882ef51c3fcd28e7fe803762f2a8d7eb3dca4e494d3475ef7d4a43158d3b24243bda3679c3d58485c89bdc820719d22351007503e44b5cf8e6f2d0efe342921 languageName: node linkType: hard @@ -498,6 +507,13 @@ __metadata: languageName: node linkType: hard +"@types/luxon@npm:~3.4.0": + version: 3.4.2 + resolution: "@types/luxon@npm:3.4.2" + checksum: 6f92d5bd02e89f310395753506bcd9cef3a56f5940f7a50db2a2b9822bce753553ac767d143cb5b4f9ed5ddd4a84e64f89ff538082ceb4d18739af7781b56925 + languageName: node + linkType: hard + "@types/node@npm:*": version: 18.11.18 resolution: "@types/node@npm:18.11.18" @@ -643,6 +659,13 @@ __metadata: languageName: node linkType: hard +"base64-js@npm:^1.3.1": + version: 1.5.1 + resolution: "base64-js@npm:1.5.1" + checksum: 669632eb3745404c2f822a18fc3a0122d2f9a7a13f7fb8b5823ee19d1d2ff9ee5b52c53367176ea4ad093c332fd5ab4bd0ebae5a8e27917a4105a4cfc86b1005 + languageName: node + linkType: hard + "before-after-hook@npm:^2.2.0": version: 2.2.3 resolution: "before-after-hook@npm:2.2.3" @@ -650,6 +673,17 @@ __metadata: languageName: node linkType: hard +"better-sqlite3@npm:^11.1.2": + version: 11.1.2 + resolution: "better-sqlite3@npm:11.1.2" + dependencies: + bindings: ^1.5.0 + node-gyp: latest + prebuild-install: ^7.1.1 + checksum: fec9e5ea8236206ef2b334ae1d779217cbcd327f4d1822e745148c810fea3412c54483fff0cef5b3133b9a7fd1311ae7c7498b54b274074c6c04bd8e5c9dc54c + languageName: node + linkType: hard + "binary-extensions@npm:^2.0.0": version: 2.2.0 resolution: "binary-extensions@npm:2.2.0" @@ -657,6 +691,26 @@ __metadata: languageName: node linkType: hard +"bindings@npm:^1.5.0": + version: 1.5.0 + resolution: "bindings@npm:1.5.0" + dependencies: + file-uri-to-path: 1.0.0 + checksum: 65b6b48095717c2e6105a021a7da4ea435aa8d3d3cd085cb9e85bcb6e5773cf318c4745c3f7c504412855940b585bdf9b918236612a1c7a7942491de176f1ae7 + languageName: node + linkType: hard + +"bl@npm:^4.0.3": + version: 4.1.0 + resolution: "bl@npm:4.1.0" + dependencies: + buffer: ^5.5.0 + inherits: ^2.0.4 + readable-stream: ^3.4.0 + checksum: 9e8521fa7e83aa9427c6f8ccdcba6e8167ef30cc9a22df26effcc5ab682ef91d2cbc23a239f945d099289e4bbcfae7a192e9c28c84c6202e710a0dfec3722662 + languageName: node + linkType: hard + "bmp-js@npm:^0.1.0": version: 0.1.0 resolution: "bmp-js@npm:0.1.0" @@ -692,6 +746,16 @@ __metadata: languageName: node linkType: hard +"buffer@npm:^5.5.0": + version: 5.7.1 + resolution: "buffer@npm:5.7.1" + dependencies: + base64-js: ^1.3.1 + ieee754: ^1.1.13 + checksum: e2cf8429e1c4c7b8cbd30834ac09bd61da46ce35f5c22a78e6c2f04497d6d25541b16881e30a019c6fd3154150650ccee27a308eff3e26229d788bbdeb08ab84 + languageName: node + linkType: hard + "bundle-require@npm:^4.0.0": version: 4.0.1 resolution: "bundle-require@npm:4.0.1" @@ -771,6 +835,13 @@ __metadata: languageName: node linkType: hard +"chownr@npm:^1.1.1": + version: 1.1.4 + resolution: "chownr@npm:1.1.4" + checksum: 115648f8eb38bac5e41c3857f3e663f9c39ed6480d1349977c4d96c95a47266fcacc5a5aabf3cb6c481e22d72f41992827db47301851766c4fd77ac21a4f081d + languageName: node + linkType: hard + "chownr@npm:^2.0.0": version: 2.0.0 resolution: "chownr@npm:2.0.0" @@ -868,6 +939,16 @@ __metadata: languageName: node linkType: hard +"cron@npm:^3.1.7": + version: 3.1.7 + resolution: "cron@npm:3.1.7" + dependencies: + "@types/luxon": ~3.4.0 + luxon: ~3.4.0 + checksum: d98ee5297543c138221d96dd49270bf6576db80134e6041f4ce4a3c0cb6060863d76910209b34fee66fbf134461449ec3bd283d6a76d1c50da220cde7fc10c65 + languageName: node + linkType: hard + "cross-spawn@npm:^7.0.3": version: 7.0.3 resolution: "cross-spawn@npm:7.0.3" @@ -879,6 +960,13 @@ __metadata: languageName: node linkType: hard +"date-fns@npm:^3.6.0": + version: 3.6.0 + resolution: "date-fns@npm:3.6.0" + checksum: 0daa1e9a436cf99f9f2ae9232b55e11f3dd46132bee10987164f3eebd29f245b2e066d7d7db40782627411ecf18551d8f4c9fcdf2226e48bb66545407d448ab7 + languageName: node + linkType: hard + "debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.3.1, debug@npm:^4.3.3": version: 4.3.4 resolution: "debug@npm:4.3.4" @@ -891,6 +979,29 @@ __metadata: languageName: node linkType: hard +"decompress-response@npm:^6.0.0": + version: 6.0.0 + resolution: "decompress-response@npm:6.0.0" + dependencies: + mimic-response: ^3.1.0 + checksum: d377cf47e02d805e283866c3f50d3d21578b779731e8c5072d6ce8c13cc31493db1c2f6784da9d1d5250822120cefa44f1deab112d5981015f2e17444b763812 + languageName: node + linkType: hard + +"deep-extend@npm:^0.6.0": + version: 0.6.0 + resolution: "deep-extend@npm:0.6.0" + checksum: 7be7e5a8d468d6b10e6a67c3de828f55001b6eb515d014f7aeb9066ce36bd5717161eb47d6a0f7bed8a9083935b465bc163ee2581c8b128d29bf61092fdf57a7 + languageName: node + linkType: hard + +"deepmerge@npm:^4.3.1": + version: 4.3.1 + resolution: "deepmerge@npm:4.3.1" + checksum: 2024c6a980a1b7128084170c4cf56b0fd58a63f2da1660dcfe977415f27b17dbe5888668b59d0b063753f3220719d5e400b7f113609489c90160bb9a5518d052 + languageName: node + linkType: hard + "delegates@npm:^1.0.0": version: 1.0.0 resolution: "delegates@npm:1.0.0" @@ -912,6 +1023,13 @@ __metadata: languageName: node linkType: hard +"detect-libc@npm:^2.0.0": + version: 2.0.3 + resolution: "detect-libc@npm:2.0.3" + checksum: 2ba6a939ae55f189aea996ac67afceb650413c7a34726ee92c40fb0deb2400d57ef94631a8a3f052055eea7efb0f99a9b5e6ce923415daa3e68221f963cfc27d + languageName: node + linkType: hard + "dir-glob@npm:^3.0.1": version: 3.0.1 resolution: "dir-glob@npm:3.0.1" @@ -980,6 +1098,15 @@ __metadata: languageName: node linkType: hard +"end-of-stream@npm:^1.1.0, end-of-stream@npm:^1.4.1": + version: 1.4.4 + resolution: "end-of-stream@npm:1.4.4" + dependencies: + once: ^1.4.0 + checksum: 530a5a5a1e517e962854a31693dbb5c0b2fc40b46dad2a56a2deec656ca040631124f4795823acc68238147805f8b021abbe221f4afed5ef3c8e8efc2024908b + languageName: node + linkType: hard + "env-paths@npm:^2.2.0": version: 2.2.1 resolution: "env-paths@npm:2.2.1" @@ -1088,6 +1215,13 @@ __metadata: languageName: node linkType: hard +"expand-template@npm:^2.0.3": + version: 2.0.3 + resolution: "expand-template@npm:2.0.3" + checksum: 588c19847216421ed92befb521767b7018dc88f88b0576df98cb242f20961425e96a92cbece525ef28cc5becceae5d544ae0f5b9b5e2aa05acb13716ca5b3099 + languageName: node + linkType: hard + "fast-deep-equal@npm:3.1.3, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -1124,6 +1258,13 @@ __metadata: languageName: node linkType: hard +"file-uri-to-path@npm:1.0.0": + version: 1.0.0 + resolution: "file-uri-to-path@npm:1.0.0" + checksum: b648580bdd893a008c92c7ecc96c3ee57a5e7b6c4c18a9a09b44fb5d36d79146f8e442578bc0e173dc027adf3987e254ba1dfd6e3ec998b7c282873010502144 + languageName: node + linkType: hard + "fill-range@npm:^7.0.1": version: 7.0.1 resolution: "fill-range@npm:7.0.1" @@ -1140,6 +1281,13 @@ __metadata: languageName: node linkType: hard +"fs-constants@npm:^1.0.0": + version: 1.0.0 + resolution: "fs-constants@npm:1.0.0" + checksum: 18f5b718371816155849475ac36c7d0b24d39a11d91348cfcb308b4494824413e03572c403c86d3a260e049465518c4f0d5bd00f0371cdfcad6d4f30a85b350d + languageName: node + linkType: hard + "fs-minipass@npm:^2.0.0, fs-minipass@npm:^2.1.0": version: 2.1.0 resolution: "fs-minipass@npm:2.1.0" @@ -1198,6 +1346,13 @@ __metadata: languageName: node linkType: hard +"github-from-package@npm:0.0.0": + version: 0.0.0 + resolution: "github-from-package@npm:0.0.0" + checksum: 14e448192a35c1e42efee94c9d01a10f42fe790375891a24b25261246ce9336ab9df5d274585aedd4568f7922246c2a78b8a8cd2571bfe99c693a9718e7dd0e3 + languageName: node + linkType: hard + "glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": version: 5.1.2 resolution: "glob-parent@npm:5.1.2" @@ -1345,6 +1500,13 @@ __metadata: languageName: node linkType: hard +"ieee754@npm:^1.1.13": + version: 1.2.1 + resolution: "ieee754@npm:1.2.1" + checksum: 5144c0c9815e54ada181d80a0b810221a253562422e7c6c3a60b1901154184f49326ec239d618c416c1c5945a2e197107aee8d986a3dd836b53dffefd99b5e7e + languageName: node + linkType: hard + "ignore@npm:^5.2.0": version: 5.2.0 resolution: "ignore@npm:5.2.0" @@ -1383,13 +1545,20 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:^2.0.3": +"inherits@npm:2, inherits@npm:^2.0.3, inherits@npm:^2.0.4": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 4a48a733847879d6cf6691860a6b1e3f0f4754176e4d71494c41f3475553768b10f84b5ce1d40fbd0e34e6bfbb864ee35858ad4dd2cf31e02fc4a154b724d7f1 languageName: node linkType: hard +"ini@npm:~1.3.0": + version: 1.3.8 + resolution: "ini@npm:1.3.8" + checksum: dfd98b0ca3a4fc1e323e38a6c8eb8936e31a97a918d3b377649ea15bdb15d481207a0dda1021efbd86b464cae29a0d33c1d7dcaf6c5672bee17fa849bc50a1b3 + languageName: node + linkType: hard + "ip@npm:^2.0.0": version: 2.0.0 resolution: "ip@npm:2.0.0" @@ -1485,15 +1654,6 @@ __metadata: languageName: node linkType: hard -"iti@npm:^0.6.0": - version: 0.6.0 - resolution: "iti@npm:0.6.0" - dependencies: - utility-types: ^3.10.0 - checksum: 19e484aa8b00bf57642c73c56b658d06d70d7b5acf5725a6aca9948c6b3c8d1fab18d71fb25f482a13d8c6acac137799fa80e7dbdc97cc24ed5afc94f03811e3 - languageName: node - linkType: hard - "jclass@npm:^1.0.1": version: 1.2.1 resolution: "jclass@npm:1.2.1" @@ -1593,6 +1753,13 @@ __metadata: languageName: node linkType: hard +"luxon@npm:~3.4.0": + version: 3.4.4 + resolution: "luxon@npm:3.4.4" + checksum: 36c1f99c4796ee4bfddf7dc94fa87815add43ebc44c8934c924946260a58512f0fd2743a629302885df7f35ccbd2d13f178c15df046d0e3b6eb71db178f1c60c + languageName: node + linkType: hard + "magic-bytes.js@npm:^1.5.0": version: 1.8.0 resolution: "magic-bytes.js@npm:1.8.0" @@ -1655,6 +1822,13 @@ __metadata: languageName: node linkType: hard +"mimic-response@npm:^3.1.0": + version: 3.1.0 + resolution: "mimic-response@npm:3.1.0" + checksum: 25739fee32c17f433626bf19f016df9036b75b3d84a3046c7d156e72ec963dd29d7fc8a302f55a3d6c5a4ff24259676b15d915aad6480815a969ff2ec0836867 + languageName: node + linkType: hard + "minimatch@npm:^3.0.4, minimatch@npm:^3.1.1": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -1673,6 +1847,13 @@ __metadata: languageName: node linkType: hard +"minimist@npm:^1.2.0, minimist@npm:^1.2.3": + version: 1.2.8 + resolution: "minimist@npm:1.2.8" + checksum: 75a6d645fb122dad29c06a7597bddea977258957ed88d7a6df59b5cd3fe4a527e253e9bbf2e783e4b73657f9098b96a5fe96ab8a113655d4109108577ecf85b0 + languageName: node + linkType: hard + "minipass-collect@npm:^1.0.2": version: 1.0.2 resolution: "minipass-collect@npm:1.0.2" @@ -1743,6 +1924,13 @@ __metadata: languageName: node linkType: hard +"mkdirp-classic@npm:^0.5.2, mkdirp-classic@npm:^0.5.3": + version: 0.5.3 + resolution: "mkdirp-classic@npm:0.5.3" + checksum: 3f4e088208270bbcc148d53b73e9a5bd9eef05ad2cbf3b3d0ff8795278d50dd1d11a8ef1875ff5aea3fa888931f95bfcb2ad5b7c1061cfefd6284d199e6776ac + languageName: node + linkType: hard + "mkdirp@npm:^1.0.3, mkdirp@npm:^1.0.4": version: 1.0.4 resolution: "mkdirp@npm:1.0.4" @@ -1777,6 +1965,13 @@ __metadata: languageName: node linkType: hard +"napi-build-utils@npm:^1.0.1": + version: 1.0.2 + resolution: "napi-build-utils@npm:1.0.2" + checksum: 06c14271ee966e108d55ae109f340976a9556c8603e888037145d6522726aebe89dd0c861b4b83947feaf6d39e79e08817559e8693deedc2c94e82c5cbd090c7 + languageName: node + linkType: hard + "negotiator@npm:^0.6.3": version: 0.6.3 resolution: "negotiator@npm:0.6.3" @@ -1784,6 +1979,15 @@ __metadata: languageName: node linkType: hard +"node-abi@npm:^3.3.0": + version: 3.65.0 + resolution: "node-abi@npm:3.65.0" + dependencies: + semver: ^7.3.5 + checksum: 5a60f2b0c73fe0a1123e581bd99e43729f4aa3f4b9b19f1915567128d52540e8f812474410a446cd77d708a3a1139e0b2abf1d0823ba6b5f5d47aa4345931706 + languageName: node + linkType: hard + "node-fetch@npm:^2.6.7": version: 2.6.9 resolution: "node-fetch@npm:2.6.9" @@ -1878,7 +2082,7 @@ __metadata: languageName: node linkType: hard -"once@npm:^1.3.0, once@npm:^1.4.0": +"once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": version: 1.4.0 resolution: "once@npm:1.4.0" dependencies: @@ -1976,6 +2180,28 @@ __metadata: languageName: node linkType: hard +"prebuild-install@npm:^7.1.1": + version: 7.1.2 + resolution: "prebuild-install@npm:7.1.2" + dependencies: + detect-libc: ^2.0.0 + expand-template: ^2.0.3 + github-from-package: 0.0.0 + minimist: ^1.2.3 + mkdirp-classic: ^0.5.3 + napi-build-utils: ^1.0.1 + node-abi: ^3.3.0 + pump: ^3.0.0 + rc: ^1.2.7 + simple-get: ^4.0.0 + tar-fs: ^2.0.0 + tunnel-agent: ^0.6.0 + bin: + prebuild-install: bin.js + checksum: 543dadf8c60e004ae9529e6013ca0cbeac8ef38b5f5ba5518cb0b622fe7f8758b34e4b5cb1a791db3cdc9d2281766302df6088bd1a225f206925d6fee17d6c5c + languageName: node + linkType: hard + "promise-inflight@npm:^1.0.1": version: 1.0.1 resolution: "promise-inflight@npm:1.0.1" @@ -1993,6 +2219,16 @@ __metadata: languageName: node linkType: hard +"pump@npm:^3.0.0": + version: 3.0.0 + resolution: "pump@npm:3.0.0" + dependencies: + end-of-stream: ^1.1.0 + once: ^1.3.1 + checksum: e42e9229fba14732593a718b04cb5e1cfef8254544870997e0ecd9732b189a48e1256e4e5478148ecb47c8511dca2b09eae56b4d0aad8009e6fac8072923cfc9 + languageName: node + linkType: hard + "punycode@npm:^2.1.0": version: 2.1.1 resolution: "punycode@npm:2.1.1" @@ -2007,6 +2243,31 @@ __metadata: languageName: node linkType: hard +"rc@npm:^1.2.7": + version: 1.2.8 + resolution: "rc@npm:1.2.8" + dependencies: + deep-extend: ^0.6.0 + ini: ~1.3.0 + minimist: ^1.2.0 + strip-json-comments: ~2.0.1 + bin: + rc: ./cli.js + checksum: 2e26e052f8be2abd64e6d1dabfbd7be03f80ec18ccbc49562d31f617d0015fbdbcf0f9eed30346ea6ab789e0fdfe4337f033f8016efdbee0df5354751842080e + languageName: node + linkType: hard + +"readable-stream@npm:^3.1.1": + version: 3.6.2 + resolution: "readable-stream@npm:3.6.2" + dependencies: + inherits: ^2.0.3 + string_decoder: ^1.1.1 + util-deprecate: ^1.0.1 + checksum: bdcbe6c22e846b6af075e32cf8f4751c2576238c5043169a1c221c92ee2878458a816a4ea33f4c67623c0b6827c8a400409bfb3cf0bf3381392d0b1dfb52ac8d + languageName: node + linkType: hard + "readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0": version: 3.6.0 resolution: "readable-stream@npm:3.6.0" @@ -2098,7 +2359,7 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:~5.2.0": +"safe-buffer@npm:^5.0.1, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" checksum: b99c4b41fdd67a6aaf280fcd05e9ffb0813654894223afb78a31f14a19ad220bba8aba1cb14eddce1fcfb037155fe6de4e861784eb434f7d11ed58d1e70dd491 @@ -2135,9 +2396,11 @@ __metadata: resolution: "sern-community@workspace:." dependencies: "@octokit/rest": 19.0.7 - "@sern/handler": ^3.3.2 + "@sern/handler": ^4.0.0 "@types/node": 18.16.3 "@types/string-similarity": 4.0.0 + better-sqlite3: ^11.1.2 + date-fns: ^3.6.0 discord.js: ^14.14.1 dotenv: 16.0.3 jsdoc-parse-plus: 1.3.0 @@ -2181,6 +2444,24 @@ __metadata: languageName: node linkType: hard +"simple-concat@npm:^1.0.0": + version: 1.0.1 + resolution: "simple-concat@npm:1.0.1" + checksum: 4d211042cc3d73a718c21ac6c4e7d7a0363e184be6a5ad25c8a1502e49df6d0a0253979e3d50dbdd3f60ef6c6c58d756b5d66ac1e05cda9cacd2e9fc59e3876a + languageName: node + linkType: hard + +"simple-get@npm:^4.0.0": + version: 4.0.1 + resolution: "simple-get@npm:4.0.1" + dependencies: + decompress-response: ^6.0.0 + once: ^1.3.1 + simple-concat: ^1.0.0 + checksum: e4132fd27cf7af230d853fa45c1b8ce900cb430dd0a3c6d3829649fe4f2b26574c803698076c4006450efb0fad2ba8c5455fbb5755d4b0a5ec42d4f12b31d27e + languageName: node + linkType: hard + "simple-swizzle@npm:^0.2.2": version: 0.2.2 resolution: "simple-swizzle@npm:0.2.2" @@ -2300,6 +2581,13 @@ __metadata: languageName: node linkType: hard +"strip-json-comments@npm:~2.0.1": + version: 2.0.1 + resolution: "strip-json-comments@npm:2.0.1" + checksum: 1074ccb63270d32ca28edfb0a281c96b94dc679077828135141f27d52a5a398ef5e78bcf22809d23cadc2b81dfbe345eb5fd8699b385c8b1128907dec4a7d1e1 + languageName: node + linkType: hard + "sucrase@npm:^3.20.3": version: 3.25.0 resolution: "sucrase@npm:3.25.0" @@ -2317,6 +2605,31 @@ __metadata: languageName: node linkType: hard +"tar-fs@npm:^2.0.0": + version: 2.1.1 + resolution: "tar-fs@npm:2.1.1" + dependencies: + chownr: ^1.1.1 + mkdirp-classic: ^0.5.2 + pump: ^3.0.0 + tar-stream: ^2.1.4 + checksum: f5b9a70059f5b2969e65f037b4e4da2daf0fa762d3d232ffd96e819e3f94665dbbbe62f76f084f1acb4dbdcce16c6e4dac08d12ffc6d24b8d76720f4d9cf032d + languageName: node + linkType: hard + +"tar-stream@npm:^2.1.4": + version: 2.2.0 + resolution: "tar-stream@npm:2.2.0" + dependencies: + bl: ^4.0.3 + end-of-stream: ^1.4.1 + fs-constants: ^1.0.0 + inherits: ^2.0.3 + readable-stream: ^3.1.1 + checksum: 699831a8b97666ef50021c767f84924cfee21c142c2eb0e79c63254e140e6408d6d55a065a2992548e72b06de39237ef2b802b99e3ece93ca3904a37622a66f3 + languageName: node + linkType: hard + "tar@npm:^6.1.11, tar@npm:^6.1.2": version: 6.1.12 resolution: "tar@npm:6.1.12" @@ -2445,10 +2758,10 @@ __metadata: languageName: node linkType: hard -"ts-results-es@npm:^4.0.0": - version: 4.0.0 - resolution: "ts-results-es@npm:4.0.0" - checksum: 32a7059491e36d06c5a1084fe9be8021a0beb2d94a94b0c3fa85dc3e96561bf34fb8fd60ebe661064c9fc2bafcf437b6b65f119e8d7497af7f76cda9d9a2a945 +"ts-results-es@npm:^4.1.0": + version: 4.2.0 + resolution: "ts-results-es@npm:4.2.0" + checksum: ff475c2f6d44377e0204211e6eafdbcabddf3ad09d40540ad5dee3d817eefbd48c07a21f5ad86864ef82cd8a5542a266af9dd8dd4d58d4766fdd6e79370519bb languageName: node linkType: hard @@ -2502,6 +2815,15 @@ __metadata: languageName: node linkType: hard +"tunnel-agent@npm:^0.6.0": + version: 0.6.0 + resolution: "tunnel-agent@npm:0.6.0" + dependencies: + safe-buffer: ^5.0.1 + checksum: 05f6510358f8afc62a057b8b692f05d70c1782b70db86d6a1e0d5e28a32389e52fa6e7707b6c5ecccacc031462e4bc35af85ecfe4bbc341767917b7cf6965711 + languageName: node + linkType: hard + "typescript@npm:5.0.4": version: 5.0.4 resolution: "typescript@npm:5.0.4" @@ -2572,13 +2894,6 @@ __metadata: languageName: node linkType: hard -"utility-types@npm:^3.10.0": - version: 3.10.0 - resolution: "utility-types@npm:3.10.0" - checksum: 8f274415c6196ab62883b8bd98c9d2f8829b58016e4269aaa1ebd84184ac5dda7dc2ca45800c0d5e0e0650966ba063bf9a412aaeaea6850ca4440a391283d5c8 - languageName: node - linkType: hard - "wasm-feature-detect@npm:^1.2.11": version: 1.6.1 resolution: "wasm-feature-detect@npm:1.6.1"