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

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26