32
32
PROFANITY_FILTER = False
33
33
PROFANITY_SILENT = True
34
34
PROFANITY_CUSTOM = None
35
+ ALPHABET_FILTERS = set ()
36
+ ALPHABET_SILENT = True
37
+ FILTER_MODS = False
35
38
REQUIRE_BLIND_KEYS = False
36
39
TEMPLATE_PATH = 'templates'
37
40
STATIC_PATH = 'static'
38
41
UPLOAD_PATH = 'uploads'
42
+ ROOM_OVERRIDES = {}
43
+ FILTER_SETTINGS = {}
39
44
40
45
# Will be true if we're running as a uwsgi app, false otherwise; used where we need to do things
41
46
# only in one case or another (e.g. database initialization only via app mode).
@@ -83,13 +88,35 @@ def days_to_seconds(v):
83
88
def days_to_seconds_or_none (v ):
84
89
return days_to_seconds (v ) if v else None
85
90
91
+ def set_of_strs (v ):
92
+ return {s for s in re .split ('[,\\ s]+' , v ) if s != '' }
93
+
86
94
truthy = ('y' , 'yes' , 'Y' , 'Yes' , 'true' , 'True' , 'on' , 'On' , '1' )
87
95
falsey = ('n' , 'no' , 'N' , 'No' , 'false' , 'False' , 'off' , 'Off' , '0' )
88
96
booly = truthy + falsey
89
97
90
98
def bool_opt (name ):
91
99
return (name , lambda x : x in booly , lambda x : x in truthy )
92
100
101
+ reply_fields = {
102
+ r'\@' : '{profile_at}' ,
103
+ r'\p' : '{profile_name}' ,
104
+ r'\r' : '{room_name}' ,
105
+ r'\t' : '{room token}' ,
106
+ '{' : '{{' ,
107
+ '}' : '}}' ,
108
+ r'\\' : '\\ ' ,
109
+ r'\n' : '\n ' ,
110
+ }
111
+ reply_fields_re = '(?:' + '|' .join (re .escape (k ) for k in reply_fields .keys ()) + ')'
112
+
113
+ def reply_to_format (v ):
114
+ return [
115
+ re .sub (reply_fields_re , lambda x : reply_fields [x .group (0 )], reply )
116
+ for reply in v .split ("\n " )
117
+ if reply != ''
118
+ ]
119
+
93
120
# Map of: section => { param => ('GLOBAL', test lambda, value lambda) }
94
121
# global is the string name of the global variable to set
95
122
# test lambda returns True/False for validation (if None/omitted, accept anything)
@@ -126,6 +153,9 @@ def bool_opt(name):
126
153
'profanity_filter' : bool_opt ('PROFANITY_FILTER' ),
127
154
'profanity_silent' : bool_opt ('PROFANITY_SILENT' ),
128
155
'profanity_custom' : ('PROFANITY_CUSTOM' , path_exists , val_or_none ),
156
+ 'alphabet_filters' : ('ALPHABET_FILTERS' , None , set_of_strs ),
157
+ 'alphabet_silent' : bool_opt ('ALPHABET_SILENT' ),
158
+ 'filter_mods' : bool_opt ('FILTER_MODS' ),
129
159
},
130
160
'web' : {
131
161
'template_path' : ('TEMPLATE_PATH' , path_exists , val_or_none ),
@@ -134,33 +164,73 @@ def bool_opt(name):
134
164
'log' : {'level' : ('LOG_LEVEL' ,)},
135
165
}
136
166
137
- for s in cp .sections ():
138
- if s not in setting_map :
139
- logger .warning (f"Ignoring unknown section [{ s } ] in { conf_ini } " )
140
- continue
141
- for opt in cp [s ]:
142
- if opt not in setting_map [s ]:
143
- logger .warning (f"Ignoring unknown config setting [{ s } ].{ opt } in { conf_ini } " )
144
- continue
167
+ room_setting_map = {
168
+ 'profanity_filter' : bool_opt ('profanity_filter' ),
169
+ 'profanity_silent' : bool_opt ('profanity_silent' ),
170
+ 'alphabet_filters' : ('alphabet_filters' , None , set_of_strs ),
171
+ }
172
+
173
+ filter_setting_map = {
174
+ 'public' : bool_opt ('public' ),
175
+ 'profile_name' : ('profile_name' ,),
176
+ 'reply' : ('reply' , None , reply_to_format ),
177
+ }
145
178
146
- value = cp [s ][opt ]
147
- conf = setting_map [s ][opt ]
179
+ def parse_option (fields , s , opt , * , room = None , filt = None ):
180
+ conf_type = 'room-specific ' if room else 'filter ' if filt else ''
181
+ if opt not in fields :
182
+ logger .warning (f"Ignoring unknown { conf_type } config setting [{ s } ].{ opt } in { conf_ini } " )
183
+ return
184
+ conf = fields [opt ]
185
+ value = cp [s ][opt ]
148
186
149
- assert isinstance (conf , tuple ) and 1 <= len (conf ) <= 3
187
+ assert isinstance (conf , tuple ) and 1 <= len (conf ) <= 3
188
+ if not room and not filt :
150
189
assert conf [0 ] in globals ()
151
190
152
- logger .debug (f"Loaded config setting [{ s } ].{ opt } ={ value } " )
191
+ logger .debug (f"Loaded { 'room-specific ' if room else '' } config setting [{ s } ].{ opt } ={ value } " )
153
192
154
- if len (conf ) >= 2 and conf [1 ]:
155
- if not conf [1 ](value ):
156
- raise RuntimeError (f"Invalid value [{ s } ].{ opt } ={ value } in { conf_ini } " )
193
+ if len (conf ) >= 2 and conf [1 ]:
194
+ if not conf [1 ](value ):
195
+ raise RuntimeError (f"Invalid value [{ s } ].{ opt } ={ value } in { conf_ini } " )
157
196
158
- if len (conf ) >= 3 and conf [2 ]:
159
- value = conf [2 ](value )
197
+ if len (conf ) >= 3 and conf [2 ]:
198
+ value = conf [2 ](value )
160
199
200
+ if room :
201
+ logger .debug (f"Set config.ROOM_OVERRIDES[{ room } ][{ conf [0 ]} ] = { value } " )
202
+ ROOM_OVERRIDES [room ][conf [0 ]] = value
203
+ elif filt :
204
+ logger .debug (f"Set config.FILTER_SETTINGS[{ filt [0 ]} ][{ filt [1 ]} ][{ conf [0 ]} ] = { value } " )
205
+ FILTER_SETTINGS .setdefault (filt [0 ], {}).setdefault (filt [1 ], {})[conf [0 ]] = value
206
+ else :
161
207
logger .debug (f"Set config.{ conf [0 ]} = { value } " )
162
208
globals ()[conf [0 ]] = value
163
209
210
+ for s in cp .sections ():
211
+ if len (s ) > 5 and s .startswith ('room:' ):
212
+ token = s [5 :]
213
+ if token not in ROOM_OVERRIDES :
214
+ ROOM_OVERRIDES [token ] = {}
215
+ for opt in cp [s ]:
216
+ parse_option (room_setting_map , s , opt , room = token )
217
+
218
+ elif s .startswith ('filter:' ):
219
+ filt = s .split (':' )[1 :]
220
+ if len (filt ) != 2 :
221
+ raise RuntimeError (
222
+ f"Invalid filter section [{ s } ] in { conf_ini } : expected [filter:TYPE:ROOM]"
223
+ )
224
+ for opt in cp [s ]:
225
+ parse_option (filter_setting_map , s , opt , filt = filt )
226
+
227
+ elif s in setting_map :
228
+ for opt in cp [s ]:
229
+ parse_option (setting_map [s ], s , opt )
230
+
231
+ else :
232
+ logger .warning (f"Ignoring unknown section [{ s } ] in { conf_ini } " )
233
+
164
234
165
235
try :
166
236
load_config ()
0 commit comments