/[sudobot]/branches/5.x/src/services/PermissionManager.ts
ViewVC logotype

Contents of /branches/5.x/src/services/PermissionManager.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: 13690 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 { PermissionRole } from "@prisma/client";
21 import { GuildMember, PermissionFlagsBits, PermissionResolvable, PermissionsString, Snowflake } from "discord.js";
22 import Service from "../core/Service";
23 import { GatewayEventListener } from "../decorators/GatewayEventListener";
24 import { HasEventListeners } from "../types/HasEventListeners";
25 import { PermissionLevelsRecord, getPermissionLevels } from "../utils/PermissionLevels";
26 import { log, logWarn } from "../utils/logger";
27
28 export const name = "permissionManager";
29
30 export default class PermissionManager extends Service implements HasEventListeners {
31 public readonly users: Record<string, Map<string, PermissionRole> | undefined> = {};
32 public readonly roles: Record<string, Map<string, PermissionRole> | undefined> = {};
33 public readonly levels: PermissionLevelsRecord = getPermissionLevels();
34 public readonly guildPermissionLevels: Record<string, number | undefined> = {};
35
36 @GatewayEventListener("ready")
37 async onReady() {
38 await this.sync();
39 }
40
41 shouldModerate(member: GuildMember, moderator: GuildMember) {
42 if (this.client.configManager.systemConfig.system_admins.includes(moderator.user.id)) return true;
43
44 const config = this.client.configManager.config[member.guild.id];
45
46 if (!config) return false;
47
48 if (member.guild.ownerId === member.user.id) return false;
49 if (member.guild.ownerId === moderator.user.id) return true;
50
51 const { admin_role, mod_role, staff_role } = config.permissions ?? {};
52
53 if (member.roles.cache.hasAny(admin_role ?? "_", mod_role ?? "_", staff_role ?? "_")) {
54 log("Member has roles that are immune to this action");
55 return false;
56 }
57
58 if (member.roles.highest.position >= moderator.roles.highest.position) {
59 log("Member has higher roles than moderator");
60 return false;
61 }
62
63 return true;
64 }
65
66 isImmuneToAutoMod(member: GuildMember, permission?: PermissionResolvable[] | PermissionResolvable) {
67 if (this.client.configManager.systemConfig.system_admins.includes(member.user.id)) return true;
68
69 const config = this.client.configManager.config[member.guild.id];
70
71 if (!config) return true;
72
73 const { admin_role, mod_role, staff_role } = config.permissions ?? {};
74
75 if (member.roles.cache.hasAny(admin_role ?? "_", mod_role ?? "_", staff_role ?? "_")) {
76 log("Member has roles that are immune to automod");
77 return true;
78 }
79
80 const hasDiscordPerms =
81 member.permissions.has(PermissionFlagsBits.ManageGuild, true) || (permission && member.permissions.has(permission, true));
82
83 if (hasDiscordPerms) {
84 log("Member has discord permissions that are immune to automod");
85 return true;
86 }
87
88 const permissions = this.getMemberPermissions(member, false);
89 const hasPermissions = permissions.has("Administrator") || permissions.has("ManageGuild");
90
91 if (hasPermissions) log("Member has permissions that are immune to automod");
92
93 return hasPermissions;
94 }
95
96 getMemberPermissionFromLevel(member: GuildMember, mergeWithDiscordPermissions?: boolean) {
97 const level = this.getMemberPermissionLevel(member);
98
99 return new Set<PermissionsString>([...(mergeWithDiscordPermissions ? member.permissions.toArray() : []), ...this.levels[level]]);
100 }
101
102 getMemberPermissionLevel(member: GuildMember) {
103 let level = this.guildPermissionLevels[`${member.guild.id}_u_${member.user.id}`] ?? 0;
104
105 for (const [id] of member.roles.cache) {
106 const roleLevel = this.guildPermissionLevels[`${member.guild.id}_r_${id}`] ?? 0;
107
108 if (roleLevel > level) {
109 level = roleLevel;
110 }
111 }
112
113 return level < 0 ? 0 : level > 100 ? 100 : level;
114 }
115
116 getPermissionsFromPermissionRole(permissionRole: PermissionRole, guildId: string) {
117 const guildUsesPermissionLevels = this.client.configManager.config[guildId]?.permissions.mode === "levels";
118
119 if (guildUsesPermissionLevels && permissionRole.level !== undefined && permissionRole.level !== null) {
120 return this.levels[permissionRole.level];
121 }
122
123 return permissionRole.grantedPermissions as readonly PermissionsString[];
124 }
125
126 getPermissionsFromPermissionRoles(permissionRoles: PermissionRole[], guildId: string) {
127 const guildUsesPermissionLevels = this.client.configManager.config[guildId]?.permissions.mode === "levels";
128 const permissionStrings = new Set<PermissionsString>();
129
130 for (const permissionRole of permissionRoles) {
131 if (guildUsesPermissionLevels && permissionRole.level !== undefined && permissionRole.level !== null) {
132 for (const permission of this.levels[permissionRole.level]) {
133 permissionStrings.add(permission);
134 }
135 } else {
136 for (const permission of permissionRole.grantedPermissions as readonly PermissionsString[]) {
137 permissionStrings.add(permission);
138 }
139 }
140 }
141
142 return permissionStrings;
143 }
144
145 getPermissionsFromPermissionRoleSet(permissionRole: PermissionRole, guildId: string) {
146 return new Set(this.getPermissionsFromPermissionRole(permissionRole, guildId));
147 }
148
149 getMemberPermissions(member: GuildMember, mergeWithDiscordPermissions?: boolean) {
150 const guildUsesPermissionLevels = this.client.configManager.config[member.guild.id]?.permissions.mode === "levels";
151
152 if (guildUsesPermissionLevels) {
153 return this.getMemberPermissionFromLevel(member, mergeWithDiscordPermissions);
154 }
155
156 const allPermissions = new Set<PermissionsString>(mergeWithDiscordPermissions ? member.permissions.toArray() : []);
157
158 if (this.users[member.guild.id]) {
159 const permissions = this.users[`${member.guild.id}_${member.user.id}`];
160
161 if (permissions) {
162 for (const permission of permissions.values()) {
163 for (const key of permission.grantedPermissions) {
164 allPermissions.add(key as PermissionsString);
165 }
166 }
167 }
168 }
169
170 for (const [id] of member.roles.cache) {
171 const permissions = this.roles[`${member.guild.id}_${id}`];
172
173 if (!permissions) {
174 continue;
175 }
176
177 for (const permission of permissions.values()) {
178 for (const key of permission.grantedPermissions) {
179 allPermissions.add(key as PermissionsString);
180 }
181 }
182 }
183
184 return allPermissions;
185 }
186
187 async sync(guildId?: string) {
188 log("Started syncing permission levels", guildId ? `for guild ${guildId}` : "");
189
190 const permissionRoles = await this.client.prisma.permissionRole.findMany({
191 where: {
192 guild_id: guildId
193 }
194 });
195
196 for (const permissionRole of permissionRoles) {
197 const guildUsesPermissionLevels = this.client.configManager.config[permissionRole.guild_id]?.permissions.mode === "levels";
198
199 for (const roleId of permissionRole.roles) {
200 if (guildUsesPermissionLevels && typeof permissionRole.level === "number") {
201 this.guildPermissionLevels[`${permissionRole.guild_id}_r_${roleId}`] = permissionRole.level;
202 continue;
203 }
204
205 this.roles[`${permissionRole.guild_id}_${roleId}`] ??= new Map();
206 this.roles[`${permissionRole.guild_id}_${roleId}`]!.set(permissionRole.name, permissionRole);
207 }
208
209 for (const userId of permissionRole.users) {
210 if (guildUsesPermissionLevels && typeof permissionRole.level === "number") {
211 this.guildPermissionLevels[`${permissionRole.guild_id}_u_${userId}`] = permissionRole.level;
212 continue;
213 }
214
215 this.users[`${permissionRole.guild_id}_${userId}`] ??= new Map();
216 this.users[`${permissionRole.guild_id}_${userId}`]!.set(permissionRole.name, permissionRole);
217 }
218 }
219
220 log("Completed syncing permission levels", guildId ? `for guild ${guildId}` : "");
221 }
222
223 async createPermissionLevel({ guildId, name, permissions, roles, users }: CreatePermissionLevelPayload) {
224 const existingPermissionLevel = await this.client.prisma.permissionRole.findFirst({
225 where: {
226 name,
227 guild_id: guildId
228 }
229 });
230
231 if (existingPermissionLevel) {
232 return null;
233 }
234
235 const permissionLevel = await this.client.prisma.permissionRole.create({
236 data: {
237 name,
238 guild_id: guildId,
239 grantedPermissions: permissions,
240 roles,
241 users
242 }
243 });
244
245 for (const roleId of permissionLevel.roles) {
246 this.roles[`${permissionLevel.guild_id}_${roleId}`] ??= new Map();
247 this.roles[`${permissionLevel.guild_id}_${roleId}`]!.set(permissionLevel.name, permissionLevel);
248 }
249
250 for (const userId of permissionLevel.users) {
251 this.users[`${permissionLevel.guild_id}_${userId}`] ??= new Map();
252 this.users[`${permissionLevel.guild_id}_${userId}`]!.set(permissionLevel.name, permissionLevel);
253 }
254
255 return permissionLevel;
256 }
257
258 async deletePermissionLevel({ guildId, name }: Pick<CreatePermissionLevelPayload, "guildId" | "name">) {
259 const permissionLevel = await this.client.prisma.permissionRole.findFirst({
260 where: {
261 name,
262 guild_id: guildId
263 }
264 });
265
266 if (!permissionLevel) return null;
267
268 await this.client.prisma.permissionRole.delete({
269 where: {
270 id: permissionLevel.id
271 }
272 });
273
274 for (const roleId of permissionLevel.roles) {
275 this.roles[`${permissionLevel.guild_id}_${roleId}`] ??= new Map();
276
277 if (this.roles[`${permissionLevel.guild_id}_${roleId}`]?.has(permissionLevel.name)) {
278 this.roles[`${permissionLevel.guild_id}_${roleId}`]!.delete(permissionLevel.name);
279 }
280 }
281
282 for (const userId of permissionLevel.users) {
283 this.users[`${permissionLevel.guild_id}_${userId}`] ??= new Map();
284
285 if (this.users[`${permissionLevel.guild_id}_${userId}`]?.has(permissionLevel.name)) {
286 this.users[`${permissionLevel.guild_id}_${userId}`]!.delete(permissionLevel.name);
287 }
288 }
289
290 return permissionLevel;
291 }
292
293 async updatePermissionLevel({ guildId, name, permissions, newName, roles, users }: CreatePermissionLevelPayload & { newName?: string }) {
294 const existingPermissionLevel = await this.client.prisma.permissionRole.findFirst({
295 where: {
296 name,
297 guild_id: guildId
298 }
299 });
300
301 if (!existingPermissionLevel) {
302 return null;
303 }
304
305 if (newName !== name || roles || users || permissions) {
306 const permissionLevel = await this.client.prisma.permissionRole.update({
307 where: {
308 id: existingPermissionLevel.id
309 },
310 data: {
311 name: newName,
312 roles,
313 users,
314 grantedPermissions: permissions
315 }
316 });
317
318 for (const roleId of permissionLevel.roles) {
319 this.roles[`${permissionLevel.guild_id}_${roleId}`] ??= new Map();
320
321 if (this.roles[`${permissionLevel.guild_id}_${roleId}`]!.has(name)) {
322 this.roles[`${permissionLevel.guild_id}_${roleId}`]!.delete(name);
323 }
324
325 this.roles[`${permissionLevel.guild_id}_${roleId}`]!.set(permissionLevel.name, permissionLevel);
326 }
327
328 for (const userId of permissionLevel.users) {
329 this.users[`${permissionLevel.guild_id}_${userId}`] ??= new Map();
330
331 if (this.users[`${permissionLevel.guild_id}_${userId}`]!.has(name)) {
332 this.users[`${permissionLevel.guild_id}_${userId}`]!.delete(name);
333 }
334
335 this.users[`${permissionLevel.guild_id}_${userId}`]!.set(permissionLevel.name, permissionLevel);
336 }
337
338 return permissionLevel;
339 }
340
341 logWarn("Nothing updated");
342 return false;
343 }
344 }
345
346 interface CreatePermissionLevelPayload {
347 guildId: Snowflake;
348 name: string;
349 permissions: PermissionsString[];
350 roles?: Snowflake[];
351 users?: Snowflake[];
352 }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26