/[sudobot]/trunk/src/api/controllers/ConfigController.ts
ViewVC logotype

Diff of /trunk/src/api/controllers/ConfigController.ts

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 326 by rakin, Mon Jul 29 17:29:33 2024 UTC revision 393 by rakin, Mon Jul 29 17:29:59 2024 UTC
# Line 1  Line 1 
1  import { Request } from "express";  /**
2    * This file is part of SudoBot.
3    *
4    * Copyright (C) 2021-2022 OSN Inc.
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 { dot, object } from "dot-object";
21    import { body } from "express-validator";
22    import { z as zod } from "zod";
23    import KeyValuePair from "../../types/KeyValuePair";
24  import Controller from "../Controller";  import Controller from "../Controller";
25    import RequireAuth from "../middleware/RequireAuth";
26    import ValidatorError from "../middleware/ValidatorError";
27    import Request from "../Request";
28    import merge from 'ts-deepmerge';
29    
30  export default class ConfigController extends Controller {  export default class ConfigController extends Controller {
31        globalMiddleware(): Function[] {
32            return [RequireAuth, ValidatorError];
33        }
34    
35        middleware(): KeyValuePair<Function[]> {
36            return {
37                update: [
38                    body(["config"]).isObject()
39                ]
40            };
41        }
42    
43        private zodSchema(id: string) {
44            const snowflake = zod.string().regex(/\d+/, { message: "The given value is not a Snowflake" });
45            const config = this.client.config.props[id];
46    
47            const schema = zod.object({
48                "prefix": zod.string().optional(),
49                "debug": zod.boolean().optional(),
50                "mute_role": snowflake.optional(),
51                "gen_role": snowflake.optional(),
52                "logging_channel": snowflake.optional(),
53                "logging_channel_join_leave": snowflake.optional(),
54                "mod_role": snowflake.optional(),
55                "announcement_channel": snowflake.optional(),
56                "admin": snowflake.optional(),
57                "lockall": zod.array(zod.string()).optional(),
58                "warn_notallowed": zod.boolean().optional(),
59                "role_commands": zod.record(
60                    snowflake,
61                    zod.array(zod.string().min(1))
62                ).optional().default({}),
63                "autoclear": zod.object({
64                    "enabled": zod.boolean().optional(),
65                    "channels": zod.array(snowflake).optional().default(config.autoclear.channels)
66                }).optional(),
67                "verification": zod.object({
68                    "enabled": zod.boolean().optional(),
69                    "role": snowflake.optional()
70                }).optional(),
71                "welcomer": zod.object({
72                    "enabled": zod.boolean().optional(),
73                    "channel": snowflake.optional(),
74                    "message": zod.string().min(1).or(zod.null()).optional(),
75                    "randomize": zod.boolean().optional()
76                }).optional(),
77                "cooldown": zod.object({
78                    "enabled": zod.boolean().optional(),
79                    "global": zod.any().optional(),
80                    "cmds": zod.object({}).optional()
81                }).optional(),
82                "starboard": zod.object({
83                    "enabled": zod.boolean().optional(),
84                    "reactions": zod.number().int().optional(),
85                    "channel": snowflake.optional()
86                }).optional(),
87                "autorole": zod.object({
88                    "enabled": zod.boolean().optional(),
89                    "roles": zod.array(snowflake).optional().default(config.autorole.roles)
90                }).optional(),
91                "spam_filter": zod.object({
92                    "enabled": zod.boolean().optional(),
93                    "limit": zod.number().int().optional(),
94                    "time": zod.number().optional(),
95                    "diff": zod.number().optional(),
96                    "exclude": zod.array(snowflake).optional().default(config.spam_filter.exclude),
97                    "samelimit": zod.number().int().optional(),
98                    "unmute_in": zod.number().optional()
99                }).optional(),
100                "raid": zod.object({
101                    "enabled": zod.boolean().optional(),
102                    "max_joins": zod.number().int().optional(),
103                    "time": zod.number().optional(),
104                    "channels": zod.array(snowflake).optional().default(config.raid.channels),
105                    "exclude": zod.boolean().optional()
106                }).optional(),
107                "global_commands": zod.array(zod.string()).optional().default(config.global_commands),
108                "filters": zod.object({
109                    "ignore_staff": zod.boolean().optional(),
110                    "chars_repeated": zod.number().int().optional(),
111                    "words_repeated": zod.number().int().optional(),
112                    "words": zod.array(zod.string()).optional().default(config.filters.words),
113                    "tokens": zod.array(zod.string()).optional().default(config.filters.tokens),
114                    "invite_message": zod.string().optional(),
115                    "words_excluded": zod.array(snowflake).optional().default(config.filters.words_excluded),
116                    "domain_excluded": zod.array(snowflake).optional().default(config.filters.domain_excluded),
117                    "invite_excluded": zod.array(snowflake).optional().default(config.filters.invite_excluded),
118                    "words_enabled": zod.boolean().optional(),
119                    "invite_enabled": zod.boolean().optional(),
120                    "domain_enabled": zod.boolean().optional(),
121                    "regex": zod.boolean().optional(),
122                    "file_mimes_excluded": zod.array(zod.string()).optional().default(config.filters.file_mimes_excluded),
123                    "file_types_excluded": zod.array(zod.string()).optional().default(config.filters.file_types_excluded),
124                    "domains": zod.array(zod.string()).optional().default(config.filters.domains),
125                    "regex_patterns": zod.array(zod.string()).optional().default(config.filters.regex_patterns),
126                    "rickrolls_enabled": zod.boolean().optional(),
127                    "pings": zod.number().int().optional()
128                }).optional()
129            });
130    
131            return schema;
132        }
133    
134      public async index(request: Request) {      public async index(request: Request) {
135                        const { id } = request.params;
136    
137            if (!request.user?.guilds.includes(id)) {
138                return this.response({ error: "You don't have permission to access configuration of this guild." }, 403);
139            }
140    
141            if (id === "global" || !this.client.config.props[id]) {
142                return this.response({ error: "No configuration found for the given guild ID" }, 404);
143            }
144    
145            return this.client.config.props[id];
146      }      }
147    
148      public async update(request: Request) {      public async update(request: Request) {
149                    const { id } = request.params;
150            const { config: origconfig } = request.body;
151    
152            console.log(origconfig);        
153    
154            try {
155                const currentConfigDotObject = this.client.config.props[id];
156        
157                console.log("Current config: ", currentConfigDotObject);
158    
159                const result = this.zodSchema(id).safeParse(object(origconfig));
160    
161                if (!result?.success) {
162                    return this.response({ error: "The data schema does not match.", error_type: 'validation', errors: result.error.errors }, 422);
163                }
164    
165                const config = result.data;
166                const newConfig = merge.withOptions({ mergeArrays: false }, currentConfigDotObject, config);
167                const result2 = this.zodSchema(id).safeParse(newConfig);
168    
169                if (!result2?.success) {
170                    console.log(result2.error.errors);                
171                    return this.response({ error: 'Internal Server Error (500)', error_type: 'internal' }, 500);
172                }
173    
174                console.log('Final', newConfig);
175                this.client.config.props[id] = newConfig;
176                this.client.config.write();      
177                return { message: "Configuration updated", previous: dot(currentConfigDotObject), new: dot(this.client.config.props[id]) };    
178            }
179            catch (e) {
180                console.log(e);
181                return this.response({ error: 'Internal Server Error', error_type: 'internal' }, 500);
182            }
183        }
184    
185        public async update2(request: Request) {
186            const { id } = request.params;
187            const { config: origconfig } = request.body;
188    
189            console.log(origconfig);        
190    
191            try {
192                const currentConfigDotObject = dot(this.client.config.props[id]);
193                let newConfigDotObject = {...currentConfigDotObject};
194        
195                console.log("Current config: ", currentConfigDotObject);
196    
197                const result = this.zodSchema(id).safeParse(object({...origconfig}));
198    
199                if (!result?.success) {
200                    return this.response({ error: "The data schema does not match.", error_type: 'validation', errors: result.error.errors }, 422);
201                }
202    
203                const config = result.data;
204    
205                console.log(config);            
206    
207                for (const key in config) {
208                    const configKey = key as keyof typeof config;
209                    const regexMatched = /(.+)\[\d+\]/g.test(configKey);
210                    
211                    if (typeof currentConfigDotObject[configKey] === 'undefined' && !regexMatched) {
212                        console.log(configKey, config[configKey]);
213                        
214                        if (currentConfigDotObject[configKey] instanceof Array && config[configKey] instanceof Array) {
215                            console.log('Array');                        
216                        }
217                        else
218                            return this.response({ error: `The key '${configKey}' is not allowed` }, 422);
219                    }
220    
221                    if (!regexMatched && config[configKey] !== null && config[configKey] !== null && typeof config[configKey] !== typeof currentConfigDotObject[configKey]) {
222                        console.log(typeof config[configKey], typeof currentConfigDotObject[configKey]);    
223                        
224                        if (typeof currentConfigDotObject[configKey] === 'number' && typeof config[configKey] === 'string') {
225                            const int = parseInt(config[configKey]!.toString());
226    
227                            if (int !== NaN) {
228                                newConfigDotObject[configKey] = int;
229                                console.log("Updating: ", configKey, config[configKey], newConfigDotObject[configKey]);
230                                continue;
231                            }
232                        }
233                        
234                        return this.response({ error: `The key '${configKey}' has incompatible value type '${config[configKey] === null ? 'null' : typeof config[configKey]}'` }, 422);
235                    }
236    
237                    console.log("Updating: ", configKey, config[configKey], newConfigDotObject[configKey]);
238                }
239    
240                const newObj = object({...newConfigDotObject});
241                const configObj = object({...config});
242    
243                // console.log("Newobj", newObj);
244                // console.log("Configobj", configObj);
245    
246                newConfigDotObject = merge.withOptions({ mergeArrays: false }, newObj, configObj);
247                console.log("Output: ", newConfigDotObject);
248    
249                this.client.config.props[id] = newConfigDotObject;
250                this.client.config.write();
251    
252                return { message: "Configuration updated", previous: currentConfigDotObject, new: dot(this.client.config.props[id]) };
253            }
254            catch (e) {
255                console.log(e);
256                return this.response({ error: 'Internal Server Error', error_type: 'internal' }, 500);
257            }
258      }      }
259  }  }

Legend:
Removed from v.326  
changed lines
  Added in v.393

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26