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

Contents of /branches/8.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: 7185 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 { 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