/[sudobot]/branches/7.x/src/services/TriggerService.ts
ViewVC logotype

Annotation of /branches/7.x/src/services/TriggerService.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: 9306 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 {
21     ActionRowBuilder,
22     ActivityType,
23     ButtonBuilder,
24     ButtonStyle,
25     ClientEvents,
26     GuildMember,
27     Message,
28     Presence,
29     Snowflake
30     } from "discord.js";
31     import Service from "../core/Service";
32     import { GatewayEventListener } from "../decorators/GatewayEventListener";
33     import { HasEventListeners } from "../types/HasEventListeners";
34     import { TriggerType } from "../types/TriggerSchema";
35     import { logError } from "../utils/logger";
36    
37     export const name = "triggerService";
38    
39     const handlers = {
40     sticky_message: "triggerMessageSticky",
41     member_status_update: "triggerMemberStatusUpdate"
42     } satisfies Record<TriggerType["type"], Extract<keyof TriggerService, `trigger${string}`>>;
43    
44     const events = {
45     sticky_message: ["messageCreate"],
46     member_status_update: ["presenceUpdate"]
47     } satisfies Record<TriggerType["type"], (keyof ClientEvents)[]>;
48    
49     type TriggerHandlerContext<M extends boolean = false> = {
50     message: M extends false ? Message | undefined : M extends true ? Message : never;
51     member?: GuildMember;
52     newPresence?: Presence;
53     oldPresence?: Presence | null;
54     };
55    
56     export default class TriggerService extends Service implements HasEventListeners {
57     private readonly lastStickyMessages: Record<`${Snowflake}_${Snowflake}`, Message | undefined> = {};
58     private readonly lastStickyMessageQueues: Record<`${Snowflake}_${Snowflake}`, boolean> = {};
59    
60     private config(guildId: Snowflake) {
61     return this.client.configManager.config[guildId]?.auto_triggers;
62     }
63    
64     @GatewayEventListener("presenceUpdate")
65     onPresenceUpdate(oldPresence: Presence | null, newPresence: Presence) {
66     if (newPresence?.user?.bot) {
67     return false;
68     }
69    
70     const config = this.config(newPresence?.guild?.id ?? "");
71    
72     if (!config?.enabled) {
73     return false;
74     }
75    
76     this.processTriggers(
77     config.triggers,
78     {
79     roles: [...(newPresence?.member?.roles.cache.keys() ?? [])],
80     userId: newPresence.user?.id,
81     context: {
82     message: undefined,
83     oldPresence,
84     newPresence
85     }
86     },
87     ["presenceUpdate"]
88     );
89     }
90    
91     onMessageCreate(message: Message<boolean>) {
92     if (message.author.bot) {
93     return false;
94     }
95    
96     const config = this.config(message.guildId!);
97    
98     if (!config?.enabled || config?.global_disabled_channels?.includes(message.channelId!)) {
99     return false;
100     }
101    
102     this.processMessageTriggers(message, config.triggers);
103     return true;
104     }
105    
106     processTriggers(
107     triggers: TriggerType[],
108     data: Parameters<typeof this.processTrigger<false>>[1],
109     triggerEvents: (keyof ClientEvents)[] | undefined = undefined
110     ) {
111     loop: for (const trigger of triggers) {
112     if (triggerEvents !== undefined) {
113     for (const triggerEvent of triggerEvents) {
114     if (!(events[trigger.type] as any).includes(triggerEvent)) {
115     continue loop;
116     }
117     }
118     }
119    
120     this.processTrigger<boolean>(trigger, data).catch(logError);
121     }
122     }
123    
124     processMessageTriggers(message: Message, triggers: TriggerType[]) {
125     for (const trigger of triggers) {
126     if (!(events[trigger.type] as any).includes("messageCreate")) {
127     continue;
128     }
129    
130     this.processTrigger(trigger, {
131     channelId: message.channelId!,
132     roles: message.member!.roles.cache.keys(),
133     userId: message.author.id,
134     context: {
135     message
136     }
137     }).catch(logError);
138     }
139     }
140    
141     async processTrigger<B extends true | false>(
142     trigger: TriggerType,
143     {
144     channelId,
145     roles,
146     userId,
147     context
148     }: {
149     channelId?: string;
150     userId?: string;
151     roles?: IterableIterator<Snowflake> | Snowflake[];
152     context: TriggerHandlerContext<B>;
153     }
154     ) {
155     if (channelId && !trigger.enabled_channels.includes(channelId)) {
156     return;
157     }
158    
159     if (userId && trigger.ignore_users.includes(userId)) {
160     return;
161     }
162    
163     if (roles) {
164     for (const roleId of roles) {
165     if (trigger.ignore_roles.includes(roleId)) {
166     return;
167     }
168     }
169     }
170    
171     const callback = this[handlers[trigger.type]].bind(this);
172    
173     if (handlers[trigger.type].startsWith("triggerMessage")) {
174     if (!context.message) {
175     throw new Error(
176     "Attempting to call a message trigger without specifying a message object inside the context. This is an internal error."
177     );
178     }
179     }
180    
181     if (handlers[trigger.type].startsWith("trigger")) {
182     await callback(trigger as any, context as any);
183     }
184     }
185    
186     async triggerMemberStatusUpdate(
187     trigger: Extract<TriggerType, { type: "member_status_update" }>,
188     { newPresence, oldPresence }: TriggerHandlerContext<false>
189     ) {
190     if (!newPresence || !oldPresence || (!trigger.must_contain && !trigger.must_not_contain)) {
191     return;
192     }
193    
194     const oldStatus = oldPresence?.activities.find(a => a.type === ActivityType.Custom)?.state ?? "";
195     const newStatus = newPresence?.activities.find(a => a.type === ActivityType.Custom)?.state ?? "";
196    
197     if (newPresence.status === "offline" || newPresence.status === "invisible") {
198     return;
199     }
200    
201     if (oldStatus === newStatus) {
202     return;
203     }
204    
205     if (trigger.must_contain) {
206     for (const string of trigger.must_contain) {
207     if (!newStatus.includes(string)) {
208     return;
209     }
210     }
211     }
212    
213     if (trigger.must_not_contain) {
214     for (const string of trigger.must_not_contain) {
215     if (newStatus.includes(string)) {
216     return;
217     }
218     }
219     }
220    
221     try {
222     if (trigger.action === "assign_role") {
223     await newPresence.member?.roles.add(trigger.roles);
224     } else if (trigger.action === "take_away_role") {
225     await newPresence.member?.roles.remove(trigger.roles);
226     }
227     } catch (e) {
228     logError(e);
229     }
230     }
231    
232     async triggerMessageSticky(
233     trigger: Extract<TriggerType, { type: "sticky_message" }>,
234     { message }: TriggerHandlerContext<true>
235     ) {
236     if (!this.lastStickyMessageQueues[`${message.guildId!}_${message.channelId!}`]) {
237     this.lastStickyMessageQueues[`${message.guildId!}_${message.channelId!}`] = true;
238    
239     setTimeout(async () => {
240     const lastStickyMessage = this.lastStickyMessages[`${message.guildId!}_${message.channelId!}`];
241    
242     if (lastStickyMessage) {
243     try {
244     await lastStickyMessage.delete();
245     this.lastStickyMessages[`${message.guildId!}_${message.channelId!}`] = undefined;
246     } catch (e) {
247     logError(e);
248     return;
249     }
250     }
251    
252     try {
253     const sentMessage = await message.channel.send({
254     content: trigger.message,
255     components:
256     trigger.buttons.length === 0
257     ? undefined
258     : [
259     new ActionRowBuilder<ButtonBuilder>().addComponents(
260     ...trigger.buttons.map(({ label, url }) =>
261     new ButtonBuilder().setStyle(ButtonStyle.Link).setURL(url).setLabel(label)
262     )
263     )
264     ]
265     });
266    
267     this.lastStickyMessages[`${message.guildId!}_${message.channelId!}`] = sentMessage;
268     this.lastStickyMessageQueues[`${message.guildId!}_${message.channelId!}`] = false;
269     } catch (e) {
270     logError(e);
271     }
272     }, 2000);
273     }
274     }
275     }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26