From add510e6d0c5621a9b38e8136718cc768dfc831a Mon Sep 17 00:00:00 2001 From: Vermium Sifell Date: Thu, 1 Jun 2023 11:03:19 +0200 Subject: [PATCH 1/7] style: :technologist: change to 24*60*60 instead of 86400 --- src/commands/credits/subcommands/work/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/credits/subcommands/work/index.ts b/src/commands/credits/subcommands/work/index.ts index e26d3fc..88e372a 100644 --- a/src/commands/credits/subcommands/work/index.ts +++ b/src/commands/credits/subcommands/work/index.ts @@ -132,6 +132,6 @@ export const execute = async (interaction: ChatInputCommandInteraction) => { await generateCooldownName(interaction), guild, user, - 86400 + 24 * 60 * 60 ); }; From e2233097965de410acde563444f8a0b5dd4acd97 Mon Sep 17 00:00:00 2001 From: Vermium Sifell Date: Thu, 1 Jun 2023 12:13:59 +0200 Subject: [PATCH 2/7] feat: :sparkles: ability to set prorata cooldowns This should solve issues where some commands wants prorata cooldowns, that expire on midnight and such Ability to set a cooldown using seconds, now you need to call a Date constructing function. So you always pass a date as cooldownTime --- .../credits/groups/bonus/subcommands/daily/index.ts | 7 +++---- .../credits/groups/bonus/subcommands/monthly/index.ts | 7 +++---- .../credits/groups/bonus/subcommands/weekly/index.ts | 7 +++---- src/commands/credits/subcommands/work/index.ts | 3 ++- src/commands/dns/subcommands/lookup/index.ts | 8 +++----- src/commands/fun/subcommands/meme/index.ts | 7 +++---- src/commands/quotes/subcommands/post/index.ts | 3 ++- src/commands/reputation/subcommands/repute/index.ts | 3 ++- src/events/messageCreate/components/earnCredits.ts | 8 +++++++- src/handlers/CooldownManager.ts | 3 +-- 10 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/commands/credits/groups/bonus/subcommands/daily/index.ts b/src/commands/credits/groups/bonus/subcommands/daily/index.ts index 4ba70e1..db47423 100644 --- a/src/commands/credits/groups/bonus/subcommands/daily/index.ts +++ b/src/commands/credits/groups/bonus/subcommands/daily/index.ts @@ -1,3 +1,4 @@ +import { addDays } from "date-fns"; import { ChatInputCommandInteraction, EmbedBuilder, @@ -60,12 +61,10 @@ export const execute = async (interaction: ChatInputCommandInteraction) => { await sendResponse(interaction, { embeds: [embed] }); - const cooldownDuration = 24 * 60 * 60; // 24 hours in seconds - const cooldownName = await generateCooldownName(interaction); await cooldownManager.setCooldown( - cooldownName, + await generateCooldownName(interaction), guild, user, - cooldownDuration + addDays(new Date(), 1) ); }; diff --git a/src/commands/credits/groups/bonus/subcommands/monthly/index.ts b/src/commands/credits/groups/bonus/subcommands/monthly/index.ts index 51d58bf..26768d7 100644 --- a/src/commands/credits/groups/bonus/subcommands/monthly/index.ts +++ b/src/commands/credits/groups/bonus/subcommands/monthly/index.ts @@ -1,3 +1,4 @@ +import { addMonths } from "date-fns"; import { ChatInputCommandInteraction, EmbedBuilder, @@ -66,12 +67,10 @@ export const execute = async (interaction: ChatInputCommandInteraction) => { await sendResponse(interaction, { embeds: [embed] }); - const cooldownDuration = 4 * 7 * 24 * 60 * 60; // 1 month in seconds - const cooldownName = await generateCooldownName(interaction); await cooldownManager.setCooldown( - cooldownName, + await generateCooldownName(interaction), guild, user, - cooldownDuration + addMonths(new Date(), 1) ); }; diff --git a/src/commands/credits/groups/bonus/subcommands/weekly/index.ts b/src/commands/credits/groups/bonus/subcommands/weekly/index.ts index 7efeedb..da7bd3e 100644 --- a/src/commands/credits/groups/bonus/subcommands/weekly/index.ts +++ b/src/commands/credits/groups/bonus/subcommands/weekly/index.ts @@ -1,3 +1,4 @@ +import { addWeeks } from "date-fns"; import { ChatInputCommandInteraction, EmbedBuilder, @@ -62,12 +63,10 @@ export const execute = async (interaction: ChatInputCommandInteraction) => { await sendResponse(interaction, { embeds: [embed] }); - const cooldownDuration = 7 * 24 * 60 * 60; // 1 week in seconds - const cooldownName = await generateCooldownName(interaction); await cooldownManager.setCooldown( - cooldownName, + await generateCooldownName(interaction), guild, user, - cooldownDuration + addWeeks(new Date(), 1) ); }; diff --git a/src/commands/credits/subcommands/work/index.ts b/src/commands/credits/subcommands/work/index.ts index 88e372a..487b6fa 100644 --- a/src/commands/credits/subcommands/work/index.ts +++ b/src/commands/credits/subcommands/work/index.ts @@ -1,4 +1,5 @@ import Chance from "chance"; +import { addHours } from "date-fns"; import { ChatInputCommandInteraction, EmbedBuilder, @@ -132,6 +133,6 @@ export const execute = async (interaction: ChatInputCommandInteraction) => { await generateCooldownName(interaction), guild, user, - 24 * 60 * 60 + addHours(new Date(), 1) ); }; diff --git a/src/commands/dns/subcommands/lookup/index.ts b/src/commands/dns/subcommands/lookup/index.ts index d6d930b..db67197 100644 --- a/src/commands/dns/subcommands/lookup/index.ts +++ b/src/commands/dns/subcommands/lookup/index.ts @@ -1,4 +1,5 @@ import axios from "axios"; +import { addSeconds } from "date-fns"; import { ChatInputCommandInteraction, EmbedBuilder, @@ -70,14 +71,11 @@ export const execute = async ( ], }); - const cooldownName = await generateCooldownName(interaction); - const cooldownDuration = 5; - await cooldownManager.setCooldown( - cooldownName, + await generateCooldownName(interaction), guild || null, user, - cooldownDuration + addSeconds(new Date(), 5) ); } catch (error: unknown) { if ((error as NodeJS.ErrnoException).code === "ENOTFOUND") { diff --git a/src/commands/fun/subcommands/meme/index.ts b/src/commands/fun/subcommands/meme/index.ts index bd6d36f..1478ef0 100644 --- a/src/commands/fun/subcommands/meme/index.ts +++ b/src/commands/fun/subcommands/meme/index.ts @@ -1,4 +1,5 @@ import axios from "axios"; +import { addSeconds } from "date-fns"; import { ActionRowBuilder, ButtonBuilder, @@ -39,8 +40,6 @@ export const execute = async ( await deferReply(interaction, false); const { channel, guild, user } = interaction; - const cooldownItem = await generateCooldownName(interaction); - const cooldownDuration = 15; // 10 seconds try { const content: MemeContent = await fetchRandomMeme(); @@ -65,10 +64,10 @@ export const execute = async ( } await cooldownManager.setCooldown( - cooldownItem, + await generateCooldownName(interaction), guild || null, user, - cooldownDuration + addSeconds(new Date(), 5) ); }; diff --git a/src/commands/quotes/subcommands/post/index.ts b/src/commands/quotes/subcommands/post/index.ts index f5ec828..8ecb944 100644 --- a/src/commands/quotes/subcommands/post/index.ts +++ b/src/commands/quotes/subcommands/post/index.ts @@ -1,3 +1,4 @@ +import { addMinutes } from "date-fns"; import { ChannelType, ChatInputCommandInteraction, @@ -99,6 +100,6 @@ export const execute = async ( await generateCooldownName(interaction), guild, user, - 5 * 60 + addMinutes(new Date(), 5) ); }; diff --git a/src/commands/reputation/subcommands/repute/index.ts b/src/commands/reputation/subcommands/repute/index.ts index 180c6c3..3708b11 100644 --- a/src/commands/reputation/subcommands/repute/index.ts +++ b/src/commands/reputation/subcommands/repute/index.ts @@ -1,3 +1,4 @@ +import { addDays } from "date-fns"; import { ChatInputCommandInteraction, EmbedBuilder, @@ -82,6 +83,6 @@ export const execute = async (interaction: ChatInputCommandInteraction) => { await generateCooldownName(interaction), guild, user, - 24 * 60 * 60 + addDays(new Date(), 1) ); }; diff --git a/src/events/messageCreate/components/earnCredits.ts b/src/events/messageCreate/components/earnCredits.ts index 3579822..d79647c 100644 --- a/src/events/messageCreate/components/earnCredits.ts +++ b/src/events/messageCreate/components/earnCredits.ts @@ -1,3 +1,4 @@ +import { addSeconds } from "date-fns"; import { Channel, ChannelType, Guild, Message, User } from "discord.js"; import CooldownManager from "../../../handlers/CooldownManager"; import CreditsManager from "../../../handlers/CreditsManager"; @@ -93,5 +94,10 @@ async function isUserOnCooldown(guild: Guild, author: User): Promise { } async function setCooldown(guild: Guild, user: User) { - await cooldownManager.setCooldown(cooldownName, guild, user, 5); + await cooldownManager.setCooldown( + cooldownName, + guild, + user, + addSeconds(new Date(), 5) + ); } diff --git a/src/handlers/CooldownManager.ts b/src/handlers/CooldownManager.ts index 2a9a2d8..0f71357 100644 --- a/src/handlers/CooldownManager.ts +++ b/src/handlers/CooldownManager.ts @@ -8,9 +8,8 @@ class CooldownManager { cooldownItem: string, guild: Guild | null, user: User | null, - cooldownSeconds: number + expiresAt: Date ): Promise { - const expiresAt = new Date(Date.now() + cooldownSeconds * 1000); const data = { cooldownItem, expiresAt, From 26d03c282c22030864ea40329ae7ab40778bfdb0 Mon Sep 17 00:00:00 2001 From: Vermium Sifell Date: Thu, 1 Jun 2023 12:16:46 +0200 Subject: [PATCH 3/7] feat: :children_crossing: make bonus commands set their time at midnights This fixes all bonus commands to have their cooldowns set at midnight --- src/commands/credits/groups/bonus/subcommands/daily/index.ts | 4 ++-- .../credits/groups/bonus/subcommands/monthly/index.ts | 4 ++-- src/commands/credits/groups/bonus/subcommands/weekly/index.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/commands/credits/groups/bonus/subcommands/daily/index.ts b/src/commands/credits/groups/bonus/subcommands/daily/index.ts index db47423..bfd78f6 100644 --- a/src/commands/credits/groups/bonus/subcommands/daily/index.ts +++ b/src/commands/credits/groups/bonus/subcommands/daily/index.ts @@ -1,4 +1,4 @@ -import { addDays } from "date-fns"; +import { addDays, startOfDay } from "date-fns"; import { ChatInputCommandInteraction, EmbedBuilder, @@ -65,6 +65,6 @@ export const execute = async (interaction: ChatInputCommandInteraction) => { await generateCooldownName(interaction), guild, user, - addDays(new Date(), 1) + startOfDay(addDays(new Date(), 1)) ); }; diff --git a/src/commands/credits/groups/bonus/subcommands/monthly/index.ts b/src/commands/credits/groups/bonus/subcommands/monthly/index.ts index 26768d7..25af652 100644 --- a/src/commands/credits/groups/bonus/subcommands/monthly/index.ts +++ b/src/commands/credits/groups/bonus/subcommands/monthly/index.ts @@ -1,4 +1,4 @@ -import { addMonths } from "date-fns"; +import { addMonths, startOfDay } from "date-fns"; import { ChatInputCommandInteraction, EmbedBuilder, @@ -71,6 +71,6 @@ export const execute = async (interaction: ChatInputCommandInteraction) => { await generateCooldownName(interaction), guild, user, - addMonths(new Date(), 1) + startOfDay(addMonths(new Date(), 1)) ); }; diff --git a/src/commands/credits/groups/bonus/subcommands/weekly/index.ts b/src/commands/credits/groups/bonus/subcommands/weekly/index.ts index da7bd3e..5cca376 100644 --- a/src/commands/credits/groups/bonus/subcommands/weekly/index.ts +++ b/src/commands/credits/groups/bonus/subcommands/weekly/index.ts @@ -1,4 +1,4 @@ -import { addWeeks } from "date-fns"; +import { addWeeks, startOfDay } from "date-fns"; import { ChatInputCommandInteraction, EmbedBuilder, @@ -67,6 +67,6 @@ export const execute = async (interaction: ChatInputCommandInteraction) => { await generateCooldownName(interaction), guild, user, - addWeeks(new Date(), 1) + startOfDay(addWeeks(new Date(), 1)) ); }; From 828524d4c25621501f40af028a98ee810f3f1c0d Mon Sep 17 00:00:00 2001 From: Vermium Sifell Date: Thu, 1 Jun 2023 12:21:35 +0200 Subject: [PATCH 4/7] fix: :bug: fix reputation repute function Fixed the broken reputation repute command, so now it actualyl can give reputation to people --- src/handlers/ReputationManager.ts | 35 +++++++++++-------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/src/handlers/ReputationManager.ts b/src/handlers/ReputationManager.ts index 93693c3..486eedb 100644 --- a/src/handlers/ReputationManager.ts +++ b/src/handlers/ReputationManager.ts @@ -1,3 +1,4 @@ +import { UserReputation } from "@prisma/client"; import { User } from "discord.js"; import prisma from "./prisma"; @@ -38,42 +39,30 @@ class ReputationManager { } async repute(user: User, type: "positive" | "negative") { - const userData = await prisma.user.upsert({ - where: { id: user.id }, - update: {}, - create: { - id: user.id, - userReputation: { - create: { - positive: 0, - negative: 0, - }, - }, - }, - include: { - userReputation: true, - }, - }); - - let userReputation: any = {}; - - if (!userData.userReputation) return null; + let userReputation: UserReputation | null = null; if (type === "positive") { userReputation = await prisma.userReputation.upsert({ - where: { id: userData.userReputation.id }, + where: { id: user.id }, update: { positive: { increment: 1 } }, create: { positive: 1, negative: 0, - user: { connect: { id: user.id } }, + user: { + connectOrCreate: { + where: { + id: user.id, + }, + create: { id: user.id }, + }, + }, }, }); } if (type === "negative") { userReputation = await prisma.userReputation.upsert({ - where: { id: userData.userReputation.id }, + where: { id: user.id }, update: { negative: { increment: 1 } }, create: { positive: 0, From acdd5076e7cced95d0a68b275819c4c42c0cc08a Mon Sep 17 00:00:00 2001 From: Vermium Sifell Date: Thu, 1 Jun 2023 15:00:05 +0200 Subject: [PATCH 5/7] feat: :children_crossing: improve style for users on interaction error Moved error message into description and if its undefined then show a general error --- src/handlers/interactionErrorHandler.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/handlers/interactionErrorHandler.ts b/src/handlers/interactionErrorHandler.ts index 3345e1f..8905e37 100644 --- a/src/handlers/interactionErrorHandler.ts +++ b/src/handlers/interactionErrorHandler.ts @@ -25,18 +25,12 @@ export default async ( const errorEmbed = new EmbedBuilder() .setAuthor({ name: "⚠️ | Request Failed" }) .setDescription( - "An error occurred while processing your request. Please try again later." + error.message ?? + "An error occurred while processing your request. Please try again later." ) .setColor("#FFCC66") .setTimestamp(); - if (error.message !== undefined) { - errorEmbed.addFields({ - name: "Error Message", - value: codeBlock(error.message), - }); - } - if (process.env.NODE_ENV === "development" && error.stack !== undefined) { errorEmbed.addFields({ name: "Error Stack", From af14c7598e64a19f43e48af66a37bc415cfdce06 Mon Sep 17 00:00:00 2001 From: Vermium Sifell Date: Thu, 1 Jun 2023 15:01:33 +0200 Subject: [PATCH 6/7] fix: :sparkles: throw error if user tries to execute same command while one is processing If a user tries to execute same command twice at same time, then throw error on one of them to prevent them from go around the cooldown system --- .../handleCommandInteraction/index.ts | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/events/interactionCreate/interactionTypes/handleCommandInteraction/index.ts b/src/events/interactionCreate/interactionTypes/handleCommandInteraction/index.ts index c4004ee..68a2271 100644 --- a/src/events/interactionCreate/interactionTypes/handleCommandInteraction/index.ts +++ b/src/events/interactionCreate/interactionTypes/handleCommandInteraction/index.ts @@ -5,6 +5,9 @@ import generateCooldownName from "../../../../helpers/generateCooldownName"; import handleCooldown from "./handlers/handleCooldown"; import handleUnavailableCommand from "./handlers/handleUnavailableCommand"; +// Create a map to store locks for each identifier (guild ID + user ID + cooldown item) +const commandLocks = new Map(); + const cooldownManager = new CooldownManager(); export default async function handleCommandInteraction( @@ -24,6 +27,14 @@ export default async function handleCommandInteraction( try { const cooldownItem = await generateCooldownName(interaction); + + // Check if the identifier is already locked + if (commandLocks.has(cooldownItem)) { + throw new Error( + "You are unable to execute the same command simultaneously." + ); + } + const { guildCooldown, userCooldown, guildMemberCooldown } = await cooldownManager.checkCooldowns(cooldownItem, guild, user); @@ -38,10 +49,23 @@ export default async function handleCommandInteraction( userCooldown, guildMemberCooldown ); - } else { - await currentCommand.execute(interaction); + return; } + + // Create a promise that represents the current command execution + const commandExecutionPromise = currentCommand.execute(interaction); + + // Acquire the lock for the identifier and store the command execution promise + commandLocks.set(cooldownItem, commandExecutionPromise); + + // Wait for the current command execution to complete + await commandExecutionPromise; } catch (error) { await interactionErrorHandler(interaction, error); + } finally { + const cooldownItem = await generateCooldownName(interaction); + + // Release the lock for the identifier + commandLocks.delete(cooldownItem); } } From 6a21c9cd835e476f85428c209b64fe94d86a03f6 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 2 Jun 2023 10:56:44 +0000 Subject: [PATCH 7/7] chore(release): 2.3.0-dev.1 [skip ci] # [2.3.0-dev.1](https://github.com/ZynerOrg/xyter/compare/v2.2.1...v2.3.0-dev.1) (2023-06-02) ### Bug Fixes * :bug: fix reputation repute function ([828524d](https://github.com/ZynerOrg/xyter/commit/828524d4c25621501f40af028a98ee810f3f1c0d)) * :sparkles: throw error if user tries to execute same command while one is processing ([af14c75](https://github.com/ZynerOrg/xyter/commit/af14c7598e64a19f43e48af66a37bc415cfdce06)) ### Features * :children_crossing: improve style for users on interaction error ([acdd507](https://github.com/ZynerOrg/xyter/commit/acdd5076e7cced95d0a68b275819c4c42c0cc08a)) * :children_crossing: make bonus commands set their time at midnights ([26d03c2](https://github.com/ZynerOrg/xyter/commit/26d03c282c22030864ea40329ae7ab40778bfdb0)) * :sparkles: ability to set prorata cooldowns ([e223309](https://github.com/ZynerOrg/xyter/commit/e2233097965de410acde563444f8a0b5dd4acd97)) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3c40ed5..f011ddf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "xyter", - "version": "2.2.1", + "version": "2.3.0-dev.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "xyter", - "version": "2.2.1", + "version": "2.3.0-dev.1", "license": "GPL-3.0-only", "dependencies": { "@prisma/client": "^4.7.1", diff --git a/package.json b/package.json index 8beab7d..5dac7b6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xyter", - "version": "2.2.1", + "version": "2.3.0-dev.1", "private": true, "description": "A multi purpose Discord bot written in TypeScript with Discord.js", "main": "dist/index.js",