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

Annotation of /branches/6.x/src/services/PermissionManager.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: 7195 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 { PermissionOverwrite } from "@prisma/client";
21     import { log } from "console";
22     import { GuildMember, PermissionFlagsBits, PermissionResolvable, PermissionsBitField, Role, Snowflake } from "discord.js";
23     import Service from "../core/Service";
24     import AbstractPermissionManager from "../utils/AbstractPermissionManager";
25     import DiscordBasedPermissionManager from "../utils/DiscordBasedPermissionManager";
26     import LayerBasedPermissionManager from "../utils/LayerBasedPermissionManager";
27     import LevelBasedPermissionManager from "../utils/LevelBasedPermissionManager";
28     import { logInfo } from "../utils/logger";
29     import { GuildConfig } from "./ConfigManager";
30    
31     export const name = "permissionManager";
32    
33     export type GetMemberPermissionInGuildResult = {
34     permissions: PermissionsBitField;
35     } & (
36     | {
37     type: "levels";
38     level: number;
39     }
40     | {
41     type: "discord";
42     }
43     | {
44     type: "layered";
45     highestOverwrite?: PermissionOverwrite;
46     highestRoleHavingOverwrite?: Role;
47     overwriteIds: number[];
48     }
49     );
50    
51     export default class PermissionManager<M extends AbstractPermissionManager = AbstractPermissionManager> extends Service {
52     protected readonly cache: Record<`${Snowflake}_${Snowflake}`, object> = {};
53     protected readonly managers: Record<NonNullable<GuildConfig["permissions"]["mode"]>, AbstractPermissionManager | undefined> =
54     {
55     layered: new LayerBasedPermissionManager(this.client),
56     discord: new DiscordBasedPermissionManager(this.client),
57     levels: new LevelBasedPermissionManager(this.client)
58     };
59    
60     boot() {
61     if (!this.client.configManager.systemConfig.sync_permission_managers_on_boot) {
62     return;
63     }
64    
65     for (const managerName in this.managers) {
66     this.managers[managerName as keyof typeof this.managers]?.sync?.();
67     logInfo(`[${this.constructor.name}] Synchronizing ${managerName} permission manager`);
68     }
69     }
70    
71     async isImmuneToAutoMod(member: GuildMember, permission?: PermissionResolvable[] | PermissionResolvable) {
72     if (this.client.configManager.systemConfig.system_admins.includes(member.user.id)) return true;
73    
74     const config = this.client.configManager.config[member.guild.id];
75    
76     if (!config) return true;
77    
78     const { admin_role, mod_role, staff_role, check_discord_permissions, invincible_roles } = config.permissions ?? {};
79    
80     if (member.roles.cache.hasAny(admin_role ?? "_", mod_role ?? "_", staff_role ?? "_")) {
81     log("Member has roles that are immune to automod");
82     return true;
83     }
84    
85     for (const roleId of invincible_roles) {
86     if (member.roles.cache.has(roleId)) {
87     log("Member has roles that are immune to this action");
88     return false;
89     }
90     }
91    
92     if (check_discord_permissions === "automod" || check_discord_permissions === "both") {
93     const hasDiscordPerms =
94     member.permissions.has(PermissionFlagsBits.ManageGuild, true) ||
95     (permission && member.permissions.has(permission, true));
96    
97     if (hasDiscordPerms) {
98     log("Member has discord permissions that are immune to automod");
99     return true;
100     }
101     }
102    
103     const manager = await this.getManager(member.guild.id);
104     const { permissions } = manager.getMemberPermissions(member);
105     const hasPermissions = permissions.has("Administrator") || permissions.has("ManageGuild");
106    
107     if (hasPermissions) {
108     log("Member has permissions that are immune to automod");
109     return true;
110     }
111    
112     return manager.isImmuneToAutoMod(member, permission);
113     }
114    
115     // FIXME: Move these permission checks to DiscordBasedPermissionManager
116     async shouldModerate(member: GuildMember, moderator: GuildMember) {
117     if (this.client.configManager.systemConfig.system_admins.includes(moderator.user.id)) return true;
118    
119     const config = this.client.configManager.config[member.guild.id];
120    
121     if (!config) return false;
122    
123     if (member.guild.ownerId === member.user.id) return false;
124     if (member.guild.ownerId === moderator.user.id) return true;
125    
126     const { admin_role, mod_role, staff_role, invincible_roles, check_discord_permissions } = config.permissions ?? {};
127    
128     if (member.roles.cache.hasAny(admin_role ?? "_", mod_role ?? "_", staff_role ?? "_")) {
129     log("Member has roles that are immune to this action");
130     return false;
131     }
132    
133     for (const roleId of invincible_roles) {
134     if (member.roles.cache.has(roleId)) {
135     log("Member has roles that are immune to this action");
136     return false;
137     }
138     }
139    
140     if (
141     (check_discord_permissions === "manual_actions" || check_discord_permissions === "both") &&
142     member.roles.highest.position >= moderator.roles.highest.position
143     ) {
144     log("Member has higher/equal roles than moderator");
145     return false;
146     }
147    
148     const manager = await this.getManager(member.guild.id);
149     return await manager.shouldModerate(member, moderator);
150     }
151    
152     protected getMode(guildId: string) {
153     return this.client.configManager.config[guildId]?.permissions.mode ?? "discord";
154     }
155    
156     async getManager(guildId: string): Promise<M> {
157     const manager = this.managers[this.getMode(guildId)];
158    
159     if (!manager) {
160     throw new Error("Unknown/Unsupported permission mode: " + this.getMode(guildId));
161     }
162    
163     await manager.triggerSyncIfNeeded();
164     return manager as M;
165     }
166    
167     async getMemberPermissions(member: GuildMember, mergeWithDiscordPermissions?: boolean) {
168     console.log("before Getting member permissions");
169     const manager = await this.getManager(member.guild.id);
170     console.log("Getting member permissions");
171     return manager.getMemberPermissions(member, mergeWithDiscordPermissions);
172     }
173    
174     usesLayeredMode(guildId: string) {
175     return this.getMode(guildId) === "layered";
176     }
177    
178     usesLevelBasedMode(guildId: string): this is PermissionManager<LevelBasedPermissionManager> {
179     return this.getMode(guildId) === "levels";
180     }
181    
182     usesDiscordMode(guildId: string) {
183     return this.getMode(guildId) === "discord";
184     }
185     }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26