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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 577 - (show 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 /**
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