diff --git a/src/commands/check-limit.ts b/src/commands/check-limit.ts new file mode 100644 index 0000000..a21c272 --- /dev/null +++ b/src/commands/check-limit.ts @@ -0,0 +1,68 @@ +import { ApplicationCommandType, ChatInputCommandInteraction, ApplicationCommandOption, ApplicationCommandOptionType } from "discord.js"; + +import Command from "../command"; +import { getUserLimit, getNthUseInLimitTimestamp } from "../execution"; + +export default class MyLimit extends Command implements Command { + name = "check-limit"; + description = "Checks your limits and remaining usage"; + type = ApplicationCommandType.ChatInput; + options: ApplicationCommandOption[] = [ + { + name: "recovery-for", + description: "Calculate the limit recovery time for given message count (default 1)", + type: ApplicationCommandOptionType.Integer, + required: false, + }, + { + name: "ephemeral", + description: "if true, only you can see the response (default true)", + type: ApplicationCommandOptionType.Boolean, + } + ]; + + async execute(interaction: ChatInputCommandInteraction) { + let recoveryFor = interaction.options.getInteger("recovery-for", false) ?? 1; + const ephemeral = interaction.options.getBoolean("ephemeral", false) ?? true; + + if (recoveryFor <= 0) recoveryFor = 1; + + const userLimitPromise = getUserLimit(interaction.user, interaction.createdAt); + const nthUseInLimitTimestampPromise = getNthUseInLimitTimestamp( + interaction.user, + interaction.createdAt, + recoveryFor, + ); + + const userLimit = await userLimitPromise; + const nthUseInLimitTimestamp = await nthUseInLimitTimestampPromise; + + if (userLimit === false || nthUseInLimitTimestamp === false) { + interaction.reply({ + embeds: [{ + author: { name: interaction.user.username, icon_url: interaction.user.displayAvatarURL({ size: 128 }) }, + description: "User is a VIP, so there is no limit", + }], + ephemeral: ephemeral, + }); + return; + } + + interaction.reply({ + embeds: [{ + author: { name: interaction.user.username, icon_url: interaction.user.displayAvatarURL({ size: 128 }) }, + fields: [ + { name: "Limit", inline: true, value: String(userLimit.limit) }, + { name: "Usage", inline: true, value: String(userLimit.limit - userLimit.remaining) }, + { + name: `Recovery for ${recoveryFor} message${recoveryFor>1 ? "s" : ""}`, + value: nthUseInLimitTimestamp === null ? "never" : + // timestamp of the nth use in limit + 24 hours + ``, + }, + ] + }], + ephemeral: ephemeral, + }); + } +} diff --git a/src/execution.ts b/src/execution.ts index e55e9b5..9104207 100644 --- a/src/execution.ts +++ b/src/execution.ts @@ -28,13 +28,11 @@ export function getAuthor(request: apiRequest) { * @param requestTimestamp the timestamp of the user request * @returns object containing the limit and remaining usage or `false` if there is no limit */ -async function getUserLimit(user: string | {id: string}, requestTimestamp: Date) { +export async function getUserLimit(user: string | { id: string }, requestTimestamp: Date) { const userId: string = typeof user === "string" ? user : user.id; const userLimits = await database.limits.findUnique({ - where: { - user: BigInt(userId) - } + where: { user: BigInt(userId) } }); if (userLimits?.vip) return false; @@ -54,6 +52,39 @@ async function getUserLimit(user: string | {id: string}, requestTimestamp: Date) return {limit: userLimits.limit, remaining: userLimits.limit - usedLimit}; } +/** + * gets the timestamp of nth use inside time limit + * @param user the user or id to check + * @param requestTimestamp the timestamp of the request (message/interaction createdAt) + * @param nth which timestamp in time limit to get (orderedd from oldest to newest) + * @returns `false` if user is vip + * @returns `null` if there is no request + * @returns `Date` timestamp of the nth request + */ +export async function getNthUseInLimitTimestamp(user: string | { id: string }, requestTimestamp: Date, nth = 1) { + const userId: string = typeof user === "string" ? user : user.id; + + const userLimits = await database.limits.findUnique({ + where: { user: BigInt(userId)} + }); + + if (userLimits?.vip) return false; + + const nthUseInLimit = await database.usage.findFirst({ + where: { + user: BigInt(userId), + timestamp: { + gte: new Date(requestTimestamp.getTime() - 1000 * 60 * 60 * 24 /* 24 hours */) + } + }, + orderBy: { timestamp: "asc" }, + skip: nth - 1, + }); + + if (!nthUseInLimit) return null; + return nthUseInLimit.timestamp; +} + /** * Replies to a request * @param request the request to reply to diff --git a/src/scripts/pushCommands.ts b/src/scripts/pushCommands.ts index 5b17fbb..e06cf18 100644 --- a/src/scripts/pushCommands.ts +++ b/src/scripts/pushCommands.ts @@ -22,13 +22,14 @@ const rest = new REST().setToken(config.tokens.Discord); (async () => { const me = await rest.get(Routes.oauth2CurrentApplication()) as RESTGetAPIOAuth2CurrentApplicationResult; - console.log(`Started refreshing ${post.length} application commands.`); - if (guildId && guildId != "") + if (guildId && guildId != "") { + console.log(`Started refreshing ${post.length} application guild (${guildId}) commands.`); await rest.put( Routes.applicationGuildCommands(me.id, guildId), { body: post }, ); - else { + } else { + console.log(`Started refreshing ${post.length} application global commands.`); await rest.put( Routes.applicationCommands(me.id), { body: post },