1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
|
#!/bin/bash
# -*- coding: utf-8 -*-
###########################################################################
# #
# envbot - an IRC bot in bash #
# Copyright (C) 2007-2008 Arvid Norlander #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## Modules management
#---------------------------------------------------------------------
#---------------------------------------------------------------------
## List of loaded modules. Don't change from other code.
## @Type Semi-private
#---------------------------------------------------------------------
modules_loaded=""
#---------------------------------------------------------------------
## Current module API version.
#---------------------------------------------------------------------
declare -r modules_current_API=2
#---------------------------------------------------------------------
## Call from after_load with a list of modules that you depend on
## @Type API
## @param What module you are calling from.
## @param Space separated list of modules you depend on
## @return 0 Success
## @return 1 Other error. You should return 1 from after_load.
## @return 2 One or several of the dependencies could found. You should return 1 from after_load.
## @return 3 Not all of the dependencies could be loaded (modules exist but did not load correctly). You should return 1 from after_load.
#---------------------------------------------------------------------
modules_depends_register() {
local callermodule="$1"
local dep
for dep in $2; do
if [[ $dep == $callermodule ]]; then
log_error_file modules.log "To the module author of $callermodule: You can't list yourself as a dependency of yourself!"
log_error_file modules.log "Aborting!"
return 1
fi
if ! list_contains "modules_loaded" "$dep"; then
log_info_file modules.log "Loading dependency of $callermodule: $dep"
modules_load "$dep"
local status="$?"
if [[ $status -eq 4 ]]; then
return 2
elif [[ $status -ne 0 ]]; then
return 3
fi
fi
if list_contains "modules_depends_${dep}" "$callermodule"; then
log_warning_file modules.log "Dependency ${callermodule} already listed as depending on ${dep}!?"
fi
# Use printf not eval here.
local listname="modules_depends_${dep}"
printf -v "modules_depends_${dep}" '%s' "${!listname} $callermodule"
done
}
#---------------------------------------------------------------------
## Call from after_load or INIT with a list of modules that you
## depend on optionally.
## @Type API
## @param What module you are calling from.
## @param The module you want to depend on optionally.
## @return 0 Success, module loaded
## @return 1 User didn't list it as loaded, don't use the features in question
## @return 2 Other error. You should return 1 from after_load.
## @return 3 One or several of the dependencies could found. You should return 1 from after_load.
## @return 4 Not all of the dependencies could be loaded (modules exist but did not load correctly). You should return 1 from after_load.
#---------------------------------------------------------------------
modules_depends_register_optional() {
local callermodule="$1"
local dep="$2"
if ! list_contains "modules_loaded" "$dep"; then
# So not loaded, now we need to find out if we should load it or not
# We use $config_modules for it
if ! list_contains 'config_modules' "$dep"; then
log_info_file modules.log "Optional dependency of $callermodule ($dep) not loaded."
return 1
fi
log_info_file modules.log "Loading optional dependency of $callermodule: ($dep)"
fi
# Ah we should load it then? Call modules_depends_register
modules_depends_register "$@"
}
#---------------------------------------------------------------------
## Semi internal!
## List modules that depend on another module.
## @Type Semi-private
## @param Module to check
## @Stdout List of modules that depend on this.
#---------------------------------------------------------------------
modules_depends_list_deps() {
# This is needed to be able to use indirect refs
local deplistname="modules_depends_${1}"
# Clean out spaces, fastest way
echo ${!deplistname}
}
###########################################################################
# Internal functions to core or this file below this line! #
# Module authors: go away #
# See doc/module_api.txt instead #
###########################################################################
#---------------------------------------------------------------------
## Used by unload to unregister from depends system
## (That is: remove from list of "depended on by" of other modules)
## @Type Private
## @param Module to unregister
#---------------------------------------------------------------------
modules_depends_unregister() {
local module newval
for module in $modules_loaded; do
if list_contains "modules_depends_${module}" "$1"; then
list_remove "modules_depends_${module}" "$1" "modules_depends_${module}"
fi
done
}
#---------------------------------------------------------------------
## Check if a module can be unloaded
## @Type Private
## @param Name of module to check
## @return Can be unloaded
## @return Is needed by some other module.
#---------------------------------------------------------------------
modules_depends_can_unload() {
# This is needed to be able to use indirect refs
local deplistname="modules_depends_${1}"
# Not empty/only whitespaces?
if ! [[ ${!deplistname} =~ ^\ *$ ]]; then
return 1
fi
return 0
}
#---------------------------------------------------------------------
## Add hooks for a module
## @Type Private
## @param Module name
## @param MODULE_BASE_PATH, exported to INIT as a part of the API
## @return 0 Success
## @return 1 module_modulename_INIT returned non-zero
## @return 2 Module wanted to register an unknown hook.
#---------------------------------------------------------------------
modules_add_hooks() {
local module="$1"
local modinit_HOOKS
local modinit_API
local MODULE_BASE_PATH="$2"
module_${module}_INIT "$module"
[[ $? -ne 0 ]] && { log_error_file modules.log "Failed to get initialize module \"$module\""; return 1; }
# Check if it didn't set any modinit_API, in that case it is a API 1 module.
if [[ -z $modinit_API ]]; then
log_error "Please upgrade \"$module\" to new module API $modules_current_API. This old API is obsolete and no longer supported."
return 1
elif [[ $modinit_API -ne $modules_current_API ]]; then
log_error "Current module API version is $modules_current_API, but the API version of \"$module\" is $module_API."
return 1
fi
local hook
for hook in $modinit_HOOKS; do
case $hook in
"FINALISE")
modules_FINALISE+=" $module"
;;
"after_load")
modules_after_load+=" $module"
;;
"before_connect")
modules_before_connect+=" $module"
;;
"on_connect")
modules_on_connect+=" $module"
;;
"after_connect")
modules_after_connect+=" $module"
;;
"before_disconnect")
modules_before_disconnect+=" $module"
;;
"after_disconnect")
modules_after_disconnect+=" $module"
;;
"on_module_UNLOAD")
modules_on_module_UNLOAD+=" $module"
;;
"on_server_ERROR")
modules_on_server_ERROR+=" $module"
;;
"on_NOTICE")
modules_on_NOTICE+=" $module"
;;
"on_PRIVMSG")
modules_on_PRIVMSG+=" $module"
;;
"on_TOPIC")
modules_on_TOPIC+=" $module"
;;
"on_channel_MODE")
modules_on_channel_MODE+=" $module"
;;
"on_user_MODE")
modules_on_user_MODE+=" $module"
;;
"on_INVITE")
modules_on_INVITE+=" $module"
;;
"on_JOIN")
modules_on_JOIN+=" $module"
;;
"on_PART")
modules_on_PART+=" $module"
;;
"on_KICK")
modules_on_KICK+=" $module"
;;
"on_QUIT")
modules_on_QUIT+=" $module"
;;
"on_KILL")
modules_on_KILL+=" $module"
;;
"on_NICK")
modules_on_NICK+=" $module"
;;
"on_numeric")
modules_on_numeric+=" $module"
;;
"on_PONG")
modules_on_PONG+=" $module"
;;
"on_raw")
modules_on_raw+=" $module"
;;
*)
log_error_file modules.log "Unknown hook $hook requested. Module may malfunction. Module will be unloaded"
return 2
;;
esac
done
}
#---------------------------------------------------------------------
## List of all the optional hooks.
## @Type Private
#---------------------------------------------------------------------
modules_hooks="FINALISE after_load before_connect on_connect after_connect before_disconnect after_disconnect on_module_UNLOAD on_server_ERROR on_NOTICE on_PRIVMSG on_TOPIC on_channel_MODE on_user_MODE on_INVITE on_JOIN on_PART on_KICK on_QUIT on_KILL on_NICK on_numeric on_PONG on_raw"
#---------------------------------------------------------------------
## Unload a module
## @Type Private
## @param Module name
## @return 0 Unloaded
## @return 2 Module not loaded
## @return 3 Can't unload, some other module depends on this.
## @Note If the unload fails for other reasons the bot will quit.
#---------------------------------------------------------------------
modules_unload() {
local module="$1"
local hook newval to_unset
if ! list_contains "modules_loaded" "$module"; then
log_warning_file modules.log "No such module as $1 is loaded."
return 2
fi
if ! modules_depends_can_unload "$module"; then
log_error_file modules.log "Can't unload $module because these module(s) depend(s) on it: $(modules_depends_list_deps "$module")"
return 3
fi
# Remove hooks from list first in case unloading fails so we can do quit hooks if something break.
for hook in $modules_hooks; do
# List so we can unset.
if list_contains "modules_${hook}" "$module"; then
to_unset+=" module_${module}_${hook}"
fi
list_remove "modules_${hook}" "$module" "modules_${hook}"
done
commands_unregister "$module" || {
log_fatal_file modules.log "Could not unregister commands for ${module}"
bot_quit "Fatal error in module unload, please see log"
}
module_${module}_UNLOAD || {
log_fatal_file modules.log "Could not unload ${module}, module_${module}_UNLOAD returned ${?}!"
bot_quit "Fatal error in module unload, please see log"
}
unset module_${module}_UNLOAD
unset module_${module}_INIT
unset module_${module}_REHASH
# Unset from list created above.
for hook in $to_unset; do
unset "$hook" || {
log_fatal_file modules.log "Could not unset the hook $hook of module $module!"
bot_quit "Fatal error in module unload, please see log"
}
done
modules_depends_unregister "$module"
list_remove "modules_loaded" "$module" "modules_loaded"
# Call any hooks for unloading modules.
local othermodule
for othermodule in $modules_on_module_UNLOAD; do
module_${othermodule}_on_module_UNLOAD "$module"
done
# Unset help string
unset helpentry_module_${module}_description
return 0
}
#---------------------------------------------------------------------
## Generate awk script to validate module functions.
## @param Module name
## @Type Private
## @return 0 If the file is OK
## @return 1 If the file lacks one of more of the functions.
#---------------------------------------------------------------------
modules_check_function() {
local module="$1"
# This is a one liner. Well mostly. ;)
# We check that the needed functions exist.
awk "function check_found() { if (init && unload && rehash) exit 0 }
/^declare -f module_${module}_INIT$/ { init=1; check_found() }
/^declare -f module_${module}_UNLOAD$/ { unload=1; check_found() }
/^declare -f module_${module}_REHASH$/ { rehash=1; check_found() }
END { if (! (init && unload && rehash)) exit 1 }"
}
#---------------------------------------------------------------------
## Load a module
## @Type Private
## @param Name of module to load
## @return 0 Loaded Ok
## @return 1 Other errors
## @return 2 Module already loaded
## @return 3 Failed to source it in safe subshell
## @return 4 Failed to source it
## @return 5 No such module
## @return 6 Getting hooks failed
## @return 7 after_load failed
## @Note If the load fails in a fatal way the bot will quit.
#---------------------------------------------------------------------
modules_load() {
local module="$1"
if list_contains "modules_loaded" "$module"; then
log_warning_file modules.log "Module ${module} is already loaded."
return 2
fi
# modulebase is exported as MODULE_BASE_PATH
# with ${config_modules_dir} prepended to the
# INIT function, useful for multi-file
# modules, but available for other modules too.
local modulefilename modulebase
if [[ -f "${config_modules_dir}/m_${module}.sh" ]]; then
modulefilename="m_${module}.sh"
modulebase="${modulefilename}"
elif [[ -d "${config_modules_dir}/m_${module}" && -f "${config_modules_dir}/m_${module}/__main__.sh" ]]; then
modulefilename="m_${module}/__main__.sh"
modulebase="m_${module}"
else
log_error_file modules.log "No such module as ${module} exists."
return 5
fi
( source "${config_modules_dir}/${modulefilename}" )
if [[ $? -ne 0 ]]; then
log_error_file modules.log "Could not load ${module}, failed to source it in safe subshell."
return 3
fi
( source "${config_modules_dir}/${modulefilename}" && declare -F ) | modules_check_function "$module"
if [[ $? -ne 0 ]]; then
log_error_file modules.log "Could not load ${module}, it lacks some important functions it should have."
return 3
fi
source "${config_modules_dir}/${modulefilename}"
if [[ $? -eq 0 ]]; then
modules_loaded+=" $module"
modules_add_hooks "$module" "${config_modules_dir}/${modulebase}" || \
{
log_error_file modules.log "Hooks failed for $module"
# Try to unload.
modules_unload "$module" || {
log_fatal_file modules.log "Failed Unloading of $module (that failed to load)."
bot_quit "Fatal error in module unload of failed module load, please see log"
}
return 6
}
if grep -qw "$module" <<< "$modules_after_load"; then
module_${module}_after_load
if [[ $? -ne 0 ]]; then
modules_unload ${module} || {
log_fatal_file modules.log "Unloading of $module that failed after_load failed."
bot_quit "Fatal error in module unload of failed module load (after_load), please see log"
}
return 7
fi
fi
else
log_error_file modules.log "Could not load ${module}, failed to source it."
return 4
fi
}
#---------------------------------------------------------------------
## Load modules from the config
## @Type Private
#---------------------------------------------------------------------
modules_load_from_config() {
local module
IFS=" "
for module in $modules_loaded; do
if ! list_contains config_modules "$module"; then
modules_unload "$module"
fi
done
unset IFS
for module in $config_modules; do
if [[ -f "${config_modules_dir}/m_${module}.sh" || -d "${config_modules_dir}/m_${module}" ]]; then
if ! list_contains modules_loaded "$module"; then
modules_load "$module"
fi
else
log_warning_file modules.log "$module doesn't exist! Removing it from list"
fi
done
}
|