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

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26