/[sudobot]/branches/6.x/src/services/BallotManager.ts
ViewVC logotype

Annotation of /branches/6.x/src/services/BallotManager.ts

Parent Directory Parent Directory | Revision Log Revision Log


Revision 577 - (hide annotations)
Mon Jul 29 18:52:37 2024 UTC (8 months ago) by rakinar2
File MIME type: application/typescript
File size: 9674 byte(s)
chore: add old version archive branches (2.x to 9.x-dev)
1 rakinar2 577 /**
2     * This file is part of SudoBot.
3     *
4     * Copyright (C) 2021-2023 OSN Developers.
5     *
6     * SudoBot is free software; you can redistribute it and/or modify it
7     * under the terms of the GNU Affero General Public License as published by
8     * the Free Software Foundation, either version 3 of the License, or
9     * (at your option) any later version.
10     *
11     * SudoBot is distributed in the hope that it will be useful, but
12     * WITHOUT ANY WARRANTY; without even the implied warranty of
13     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14     * GNU Affero General Public License for more details.
15     *
16     * You should have received a copy of the GNU Affero General Public License
17     * along with SudoBot. If not, see <https://www.gnu.org/licenses/>.
18     */
19    
20     import { Ballot } from "@prisma/client";
21     import { CacheType, EmbedBuilder, Interaction, Snowflake } from "discord.js";
22     import Service from "../core/Service";
23     import { GatewayEventListener } from "../decorators/GatewayEventListener";
24     import { HasEventListeners } from "../types/HasEventListeners";
25     import { log, logError } from "../utils/logger";
26     import { getEmoji } from "../utils/utils";
27    
28     export const name = "ballotManager";
29    
30     export default class BallotManager extends Service implements HasEventListeners {
31     protected readonly changedBallots = new Map<`${Snowflake}_${Snowflake}_${Snowflake}`, Ballot>();
32     protected readonly recentUsers = new Map<Snowflake, number>();
33     protected readonly cooldown = 3000;
34     protected readonly updateInterval = 10_000;
35     protected timeout: NodeJS.Timeout | null = null;
36     protected updateTimeout: NodeJS.Timeout | null = null;
37    
38     @GatewayEventListener("interactionCreate")
39     async onInteractionCreate(interaction: Interaction<CacheType>) {
40     if (!interaction.isButton() || !interaction.customId.startsWith("ballot__")) {
41     return;
42     }
43    
44     const [, mode] = interaction.customId.split("__");
45    
46     if (mode !== "upvote" && mode !== "downvote") {
47     return;
48     }
49    
50     const lastRequest = this.recentUsers.get(interaction.user.id);
51    
52     if (lastRequest !== undefined && lastRequest <= this.cooldown) {
53     await interaction.reply({
54     ephemeral: true,
55     content: `${getEmoji(this.client, "error")} Whoa there! Please wait for ${Math.ceil(
56     (this.cooldown - lastRequest) / 1000
57     )} seconds before trying to upvote/downvote again!`
58     });
59    
60     return;
61     }
62    
63     this.timeout ??= setTimeout(() => {
64     for (const [userId, lastRequest] of this.recentUsers) {
65     if (lastRequest >= this.cooldown) {
66     this.recentUsers.delete(userId);
67     }
68     }
69    
70     this.timeout = null;
71     }, this.cooldown);
72    
73     this.recentUsers.set(interaction.user.id, Date.now());
74    
75     await interaction.deferReply({
76     ephemeral: true
77     });
78    
79     const key = `${interaction.guildId!}_${interaction.channelId!}_${interaction.message.id}` as const;
80     const ballot =
81     this.changedBallots.get(key) ??
82     (await this.client.prisma.ballot.findFirst({
83     where: {
84     guildId: interaction.guildId!,
85     channelId: interaction.channelId!,
86     messageId: interaction.message.id
87     }
88     }));
89    
90     if (!ballot) {
91     return;
92     }
93    
94     log("Before", ballot.upvotes, ballot.downvotes);
95     const previousTotalVotes = ballot.upvotes.length - ballot.downvotes.length;
96    
97     const added = this.modifyBallotVotes(ballot, interaction.user.id, mode);
98    
99     log("After", ballot.upvotes, ballot.downvotes);
100    
101     this.changedBallots.set(key, ballot);
102    
103     this.updateTimeout ??= setTimeout(() => {
104     for (const [, ballot] of this.changedBallots) {
105     log("Updating ballot: ", ballot.id);
106     this.client.prisma.ballot
107     .updateMany({
108     where: {
109     id: ballot.id,
110     guildId: interaction.guildId!
111     },
112     data: {
113     upvotes: {
114     set: ballot.upvotes
115     },
116     downvotes: {
117     set: ballot.downvotes
118     }
119     }
120     })
121     .catch(logError);
122     }
123    
124     this.changedBallots.clear();
125     }, this.updateInterval);
126    
127     const newTotalVotes = ballot.upvotes.length - ballot.downvotes.length;
128    
129     if (newTotalVotes !== previousTotalVotes) {
130     await interaction.message.edit({
131     embeds: [
132     new EmbedBuilder(interaction.message.embeds[0].data).setFooter({
133     text: `${newTotalVotes} Vote${newTotalVotes === 1 ? "" : "s"} • React to vote!`
134     })
135     ]
136     });
137     }
138    
139     await interaction.editReply({
140     content: `Successfully ${
141     added === null ? "changed" : added ? "added" : "removed"
142     } your vote! If you want to ${added === false ? "add" : "remove"} your vote${added === false ? " back" : ""}, press the same button again.`
143     });
144     }
145    
146     private modifyBallotVotes(ballot: Ballot, userId: Snowflake, mode: "upvote" | "downvote") {
147     const upvoteIndex = ballot.upvotes.indexOf(userId);
148     const downvoteIndex = ballot.downvotes.indexOf(userId);
149    
150     if (upvoteIndex !== -1 && downvoteIndex !== -1) {
151     mode === "upvote" ? ballot.downvotes.splice(downvoteIndex, 1) : ballot.upvotes.splice(upvoteIndex, 1);
152     return false;
153     } else if (upvoteIndex === -1 && downvoteIndex === -1) {
154     mode === "upvote" ? ballot.upvotes.push(userId) : ballot.downvotes.push(userId);
155     return true;
156     } else {
157     if (mode === "upvote") {
158     upvoteIndex === -1 ? ballot.upvotes.push(userId) : ballot.upvotes.splice(upvoteIndex, 1);
159     downvoteIndex === -1 ? null : ballot.downvotes.splice(downvoteIndex, 1);
160     } else {
161     downvoteIndex === -1 ? ballot.downvotes.push(userId) : ballot.downvotes.splice(downvoteIndex, 1);
162     upvoteIndex === -1 ? null : ballot.upvotes.splice(upvoteIndex, 1);
163     }
164    
165     return mode === "upvote"
166     ? downvoteIndex !== -1
167     ? null
168     : upvoteIndex === -1
169     : upvoteIndex !== -1
170     ? null
171     : downvoteIndex === -1;
172     }
173     }
174    
175     create({
176     content,
177     guildId,
178     userId,
179     files,
180     channelId,
181     messageId,
182     anonymous
183     }: {
184     content: string;
185     guildId: Snowflake;
186     channelId: Snowflake;
187     messageId: Snowflake;
188     userId: Snowflake;
189     files?: string[];
190     anonymous?: boolean;
191     }) {
192     return this.client.prisma.ballot.create({
193     data: {
194     content,
195     guildId,
196     userId,
197     files,
198     channelId,
199     messageId,
200     anonymous
201     }
202     });
203     }
204    
205     delete({ id, guildId }: { id: number; guildId: Snowflake }) {
206     return this.client.prisma.ballot.deleteMany({
207     where: {
208     id,
209     guildId
210     }
211     });
212     }
213    
214     upvote({ id, guildId, userId }: { id: number; guildId: Snowflake; userId: Snowflake }) {
215     return this.client.prisma.ballot.updateMany({
216     where: {
217     id,
218     guildId
219     },
220     data: {
221     upvotes: {
222     push: userId
223     }
224     }
225     });
226     }
227    
228     downvote({ id, guildId, userId }: { id: number; guildId: Snowflake; userId: Snowflake }) {
229     return this.client.prisma.ballot.updateMany({
230     where: {
231     id,
232     guildId
233     },
234     data: {
235     downvotes: {
236     push: userId
237     }
238     }
239     });
240     }
241    
242     async upvoteRemove({ id, guildId, userId }: { id: number; guildId: Snowflake; userId: Snowflake }) {
243     const ballot = await this.get({ id, guildId });
244    
245     if (!ballot || ballot.upvotes.length === 0) {
246     return { count: 0 };
247     }
248    
249     return this.client.prisma.ballot.updateMany({
250     where: {
251     id,
252     guildId
253     },
254     data: {
255     upvotes: {
256     set: ballot.upvotes.filter(id => id !== userId)
257     }
258     }
259     });
260     }
261    
262     async downvoteRemove({ id, guildId, userId }: { id: number; guildId: Snowflake; userId: Snowflake }) {
263     const ballot = await this.get({ id, guildId });
264    
265     if (!ballot || ballot.downvotes.length === 0) {
266     return { count: 0 };
267     }
268    
269     return this.client.prisma.ballot.updateMany({
270     where: {
271     id,
272     guildId
273     },
274     data: {
275     downvotes: {
276     set: ballot.downvotes.filter(id => id !== userId)
277     }
278     }
279     });
280     }
281    
282     get({ id, guildId }: { id: number; guildId: Snowflake }) {
283     return this.client.prisma.ballot.findFirst({
284     where: {
285     id,
286     guildId
287     }
288     });
289     }
290     }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26