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
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
|
#!/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/>. #
# #
###########################################################################
#---------------------------------------------------------------------
## This is the main file, it should be called with a wrapper (envbot)
#---------------------------------------------------------------------
###################
# #
# Sanity checks #
# #
###################
# Error to fail with for old bash.
fail_old_bash() {
echo "Sorry your bash version is too old!"
echo "You need at least version 3.2.10 of bash"
echo "Please install a newer version:"
echo " * Either use your distro's packages"
echo " * Or see http://www.gnu.org/software/bash/"
exit 2
}
# Check bash version. We need at least 3.2.10
# Lets not use anything like =~ here because
# that may not work on old bash versions.
if [[ "${BASH_VERSINFO[0]}${BASH_VERSINFO[1]}" -lt 32 ]]; then
fail_old_bash
elif [[ "${BASH_VERSINFO[0]}${BASH_VERSINFO[1]}" -eq 32 && "${BASH_VERSINFO[2]}" -lt 10 ]]; then
fail_old_bash
fi
# We should not run as root.
if [[ $EUID -eq 0 ]]; then
echo "ERROR: Don't run envbot as root. Please run it under a normal user. Really."
exit 1
fi
######################
# #
# Set up variables #
# #
######################
# Version and URL
#---------------------------------------------------------------------
## Version of envbot.
## @Type API
## @Read_only Yes
#---------------------------------------------------------------------
declare -r envbot_version='0.1-beta1'
#---------------------------------------------------------------------
## Homepage of envbot.
## @Type API
## @Read_only Yes
#---------------------------------------------------------------------
declare -r envbot_homepage='http://envbot.org'
##############
# #
# Sane env #
# #
##############
# Set some variables to make bot work sane
# For example tr + some LC_COLLATE = breaks in some cases.
unset LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY
unset LC_MESSAGES LC_PAPER LC_NAME LC_ADDRESS
unset LC_TELEPHONE LC_MEASUREMENT LC_IDENTIFICATION
export LC_ALL=C
export LANG=C
# Some of these may be overkill, but better be on
# safe side.
set +amu
set -f
shopt -u sourcepath hostcomplete progcomp xpg_echo dotglob
shopt -u nocasematch nocaseglob nullglob
shopt -s extquote promptvars extglob
# If you need some other PATH, override in top of config...
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
# To make set -x more usable
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]} : '
# This is needed when we run the bot with env -i as recommended.
declare -r tmp_home="$(mktemp -dt envbot.home.XXXXXXXXXX)"
# I don't want to end up with rm -rf $HOME in case it is something
# else at that point, so lets use another variable.
# Temp trap on ctrl-c until the next "stage" of trap gets loaded (at connect)
trap 'rm -rvf "$tmp_home"; exit 1' TERM INT
#---------------------------------------------------------------------
## Now create a temp function to quit on problems in a way that cleans up
## temp stuff until we have loaded enough to use the normal function bot_quit.
## @param Return status of bot
#---------------------------------------------------------------------
envbot_quit() {
rm -rf "$tmp_home"
exit "$1"
}
# Check for moreutils else we're doomed.
if ! which sponge
then
echo "moreutils is a dep, please install."
envbot_quit 1
fi
# Check for w3m because we use it to convert html entities.
if ! which w3m
then
echo "w3m is a dep, please install."
envbot_quit 1
fi
# And finally lets export this as $HOME
export HOME="$tmp_home"
#---------------------------------------------------------------------
## Will be set to 1 if -v or --verbose is passed
## on command line.
## @Type Private
#---------------------------------------------------------------------
force_verbose=0
#---------------------------------------------------------------------
## Store command line for later use
## @Type Private
#---------------------------------------------------------------------
command_line=( "$@" )
# Some constants used in different places
#---------------------------------------------------------------------
## Current config version.
## @Type API
## @Read_only Yes
#---------------------------------------------------------------------
declare -r config_current_version=17
#---------------------------------------------------------------------
## In progress of quitting? This is used to
## work around the issue in bug 25.<br />
## -1 means not even in main loop yet.
## @Type Private
#---------------------------------------------------------------------
envbot_quitting=-1
#---------------------------------------------------------------------
## If empty debugging is turned off. If not empty it is on.
#---------------------------------------------------------------------
envbot_debugging=''
#---------------------------------------------------------------------
## Print help message
## @Type Private
#---------------------------------------------------------------------
print_cmd_help() {
echo 'envbot is an advanced modular IRC bot coded in bash.'
echo ''
echo 'Usage: envbot [OPTION]...'
echo ''
echo 'Options:'
echo ' -c, --config file Use file instead of the default as config file.'
echo ' -l, --libdir directory Use directory instead of the default as library directory.'
echo ' -v, --verbose Force verbose output even if config_log_stdout is 0.'
echo ' -d, --debug Enable debugging code. Most likely pointless to anyone'
echo ' except envbot developers or module developers.'
echo ' -h, --help Display this help and exit'
echo ' -V, --version Output version information and exit'
echo ''
echo "Note that envbot can't handle short versions of options being written together like"
echo "-vv currently."
echo ''
echo 'Exit status is 0 if OK, 1 if minor problems, 2 if serious trouble.'
echo ''
echo 'Examples:'
echo ' envbot Runs envbot with default options.'
echo ' envbot -c bot.config Runs envbot with the config bot.config.'
echo ''
echo "Report bugs to ${envbot_homepage}/trac/simpleticket"
envbot_quit 0
}
#---------------------------------------------------------------------
## Print version message
## @Type Private
#---------------------------------------------------------------------
print_version() {
echo "envbot $envbot_version - An advanced modular IRC bot in bash."
echo ''
echo 'Copyright (C) 2007-2008 Arvid Norlander'
echo 'Copyright (C) 2007-2008 EmErgE'
echo 'This is free software; see the source for copying conditions. There is NO'
echo 'warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.'
echo ''
echo 'Written by Arvid Norlander and EmErgE.'
envbot_quit 0
}
# Parse any command line arguments.
if [[ $# -gt 0 ]]; then
while [[ $# -gt 0 ]]; do
case "$1" in
'--help'|'-help'|'--usage'|'-usage'|'-h')
print_cmd_help
;;
'--config'|'-c')
config_file="$2"
shift 2
;;
'--debug'|'-d')
envbot_debugging=1
shift 1
;;
'--libdir'|'-l')
library_dir="$2"
shift 2
;;
'--verbose'|'-v')
force_verbose=1
shift 1
;;
'--version'|'-V')
print_version
;;
*)
print_cmd_help
;;
esac
done
fi
echo "Loading... Please wait"
echo no > pbot_present
if [[ -z "$config_file" ]]; then
echo "ERROR: No config file set, you probably didn't use the wrapper program to start envbot"
envbot_quit 1
fi
if [[ ! -r "$config_file" ]]; then
echo "ERROR: Can't read config file ${config_file}."
echo "Check that it is really there and correct permissions are set."
echo "If you used --config to specify name of config file, check that you spelled it correctly."
envbot_quit 1
fi
echo "Loading config"
source "$config_file"
if [[ $? -ne 0 ]]; then
echo "Error: couldn't load config from $config_file"
envbot_quit 1
fi
# This is hackish, it should be in config.sh (config_validate)
# The reason is that we need to check some things before we can load config.sh
if [[ -z "$config_version" ]]; then
echo "ERROR: YOU MUST SET THE CORRECT config_version IN THE CONFIG"
envbot_quit 2
fi
if [[ $config_version -ne $config_current_version ]]; then
echo "ERROR: YOUR config_version IS $config_version BUT THE BOT'S CONFIG VERSION IS $config_current_version."
echo "PLEASE UPDATE YOUR CONFIG. Check bot_settings.sh.example for current format."
envbot_quit 2
fi
# Force verbose output if -v or --verbose was on
# command line.
if [[ $force_verbose -eq 1 ]]; then
config_log_stdout='1'
fi
# Must be checked here and not in validate_config because of
# loading order.
if [[ -z "$library_dir" ]]; then
echo "ERROR: No library directory set, you probably didn't use the wrapper program to start envbot"
envbot_quit 1
fi
if [[ ! -d "$library_dir" ]]; then
echo "ERROR: library directory $library_dir does not exist, is not a directory or can't be read for some other reason."
echo "Check that it is really there and correct permissions are set."
echo "If you used --libdir to specify location of library directory, check that you spelled it correctly."
envbot_quit 2
fi
echo "Loading library functions"
# Load library functions.
libraries="hash time log send feedback numerics channels parse \
access misc config commands modules server debug"
for library in $libraries; do
source "${library_dir}/${library}.sh"
done
unset library
# Validate other config variables.
config_validate
time_init
log_init
debug_init
log_info_stdout "Loading transport"
source "${config_transport_dir}/${config_transport}.sh"
if [[ $? -ne 0 ]]; then
log_fatal "Couldn't load transport. Couldn't load the file..."
envbot_quit 2
fi
if ! transport_check_support; then
log_fatal "The transport reported it can't work on this system or with this configuration."
log_fatal "Please read any other errors displayed above and consult documentation for the transport module you are using."
envbot_quit 2
fi
# Now logging functions can be used.
# Load modules
log_info_stdout "Loading modules"
# Load modules
modules_load_from_config
#---------------------------------------------------------------------
## This can be used when the code does not need exact time.
## It will be updated each time the bot get a new line of
## data.
## @Type API
#---------------------------------------------------------------------
envbot_time=''
server_connected_before=0
while true; do
# In progress of quitting? This is used to
# work around the issue in bug 25.
envbot_quitting=0
for module in $modules_before_connect; do
module_${module}_before_connect
done
if [[ $server_connected_before -ne 0 ]]; then
# We got here by being connected before and
# loosing connection, keep retrying
while true; do
if server_connect; then
server_connected_before=1
break
else
log_error "Failed to reconnect, trying again in 20 seconds"
sleep 20
fi
done
else
# In this case abort on failure to connect, likely bad config.
# and most likely the user is present to fix it.
# If someone disagrees I may change it.
server_connect || {
log_error "Connection failed"
envbot_quit 1
}
server_connected_before=1
fi
trap 'bot_quit "Interrupted (Ctrl-C)"' INT
trap 'bot_quit "Terminated (SIGTERM)"' TERM
for module in $modules_after_connect; do
module_${module}_after_connect
done
while true; do
line=
transport_read_line
transport_status="$?"
# Still connected?
if ! transport_alive; then
break
fi
time_get_current 'envbot_time'
# Did we timeout waiting for data
# or did we get data?
if [[ $transport_status -ne 0 ]]; then
continue
fi
log_raw_in "$line"
for module in $modules_on_raw; do
module_${module}_on_raw "$line"
if [[ $? -ne 0 ]]; then
# TODO: Check that this does what it should.
continue 2
fi
done
if [[ $line =~ ^:${server_name}\ +([0-9]{3})\ +([^ ]+)\ +(.*) ]]; then
# this is a numeric
numeric="${BASH_REMATCH[1]}"
numericdata="${BASH_REMATCH[3]}"
server_handle_numerics "$numeric" "${BASH_REMATCH[2]}" "$numericdata"
for module in $modules_on_numeric; do
module_${module}_on_numeric "$numeric" "$numericdata"
if [[ $? -ne 0 ]]; then
break
fi
done
elif [[ "$line" =~ ^:([^ ]*)\ +PRIVMSG\ +([^:]+)\ +:(.*) ]]; then
sender="${BASH_REMATCH[1]}"
target="${BASH_REMATCH[2]}"
query="${BASH_REMATCH[3]}"
# Check if there is a command.
commands_call_command "$sender" "$target" "$query"
################################################################################
################################################################################
# Hack of all hacks!!!
config_update_time=-100
time_n0w=$( date +%s )
# If it's been more than a minute since we updated the config.
if (( ( time_n0w - 60 ) > config_update_time ))
then
source hack_of_all_hacks
config_update_time=${time_n0w}
fi
l33t_codes
################################################################################
################################################################################
# Check return code
case $? in
1)
continue
;;
2)
if [[ $config_feedback_unknown_commands -eq 0 ]]; then
continue
elif [[ $config_feedback_unknown_commands -eq 1 ]]; then
feedback_unknown_command "$sender" "$target" "$query"
fi
;;
esac
for module in $modules_on_PRIVMSG; do
module_${module}_on_PRIVMSG "$sender" "$target" "$query"
if [[ $? -ne 0 ]]; then
break
fi
done
elif [[ "$line" =~ ^:([^ ]*)\ +NOTICE\ +([^:]+)\ +:(.*) ]]; then
sender="${BASH_REMATCH[1]}"
target="${BASH_REMATCH[2]}"
query="${BASH_REMATCH[3]}"
for module in $modules_on_NOTICE; do
module_${module}_on_PRIVMSG "$sender" "$target" "$query"
if [[ $? -ne 0 ]]; then
break
fi
done
elif [[ "$line" =~ ^:([^ ]*)\ +TOPIC\ +(#[^ ]+)(\ +:(.*))? ]]; then
sender="${BASH_REMATCH[1]}"
channel="${BASH_REMATCH[2]}"
topic="${BASH_REMATCH[4]}"
for module in $modules_on_TOPIC; do
module_${module}_on_TOPIC "$sender" "$channel" "$topic"
done
elif [[ "$line" =~ ^:([^ ]*)\ +MODE\ +(#[^ ]+)\ +(.+) ]]; then
sender="${BASH_REMATCH[1]}"
channel="${BASH_REMATCH[2]}"
modes="${BASH_REMATCH[3]}"
for module in $modules_on_channel_MODE ; do
module_${module}_on_channel_MODE "$sender" "$channel" "$modes"
done
elif [[ "$line" =~ ^:([^ ]*)\ +MODE\ +([^# ]+)\ +(.+) ]]; then
sender="${BASH_REMATCH[1]}"
target="${BASH_REMATCH[2]}"
modes="${BASH_REMATCH[3]}"
for module in $modules_on_user_MODE ; do
module_${module}_on_user_MODE "$sender" "$target" "$modes"
done
elif [[ "$line" =~ ^:([^ ]*)\ +INVITE\ +([^ ]+)\ +:?(.+) ]]; then
sender="${BASH_REMATCH[1]}"
target="${BASH_REMATCH[2]}"
channel="${BASH_REMATCH[3]}"
for module in $modules_on_INVITE; do
module_${module}_on_INVITE "$sender" "$target" "$channel"
done
elif [[ "$line" =~ ^:([^ ]*)\ +NICK\ +:?(.+) ]]; then
sender="${BASH_REMATCH[1]}"
newnick="${BASH_REMATCH[2]}"
# Check if it was our own nick
server_handle_nick "$sender" "$newnick"
for module in $modules_on_NICK; do
module_${module}_on_NICK "$sender" "$newnick"
done
elif [[ "$line" =~ ^:([^ ]*)\ +JOIN\ +:?(.*) ]]; then
sender="${BASH_REMATCH[1]}"
channel="${BASH_REMATCH[2]}"
# Check if it was our own nick that joined
channels_handle_join "$sender" "$channel"
for module in $modules_on_JOIN; do
module_${module}_on_JOIN "$sender" "$channel"
done
[[ ${sender%%!*} == pbot ]] && echo yes > pbot_present
elif [[ "$line" =~ ^:([^ ]*)\ +PART\ +(#[^ ]+)(\ +:(.*))? ]]; then
sender="${BASH_REMATCH[1]}"
channel="${BASH_REMATCH[2]}"
reason="${BASH_REMATCH[4]}"
# Check if it was our own nick that parted
channels_handle_part "$sender" "$channel" "$reason"
for module in $modules_on_PART; do
module_${module}_on_PART "$sender" "$channel" "$reason"
done
[[ ${sender%%!*} == pbot ]] && echo no > pbot_present
elif [[ "$line" =~ ^:([^ ]*)\ +KICK\ +(#[^ ]+)\ +([^ ]+)(\ +:(.*))? ]]; then
sender="${BASH_REMATCH[1]}"
channel="${BASH_REMATCH[2]}"
kicked="${BASH_REMATCH[3]}"
reason="${BASH_REMATCH[5]}"
# Check if it was our own nick that got kicked
channels_handle_kick "$sender" "$channel" "$kicked" "$reason"
for module in $modules_on_KICK; do
module_${module}_on_KICK "$sender" "$channel" "$kicked" "$reason"
done
elif [[ "$line" =~ ^:([^ ]*)\ +QUIT(\ +:(.*))? ]]; then
sender="${BASH_REMATCH[1]}"
reason="${BASH_REMATCH[3]}"
for module in $modules_on_QUIT; do
module_${module}_on_QUIT "$sender" "$reason"
done
elif [[ "$line" =~ ^:([^ ]*)\ +KILL\ +([^ ]*)\ +:([^ ]*)\ +\((.*)\) ]]; then
sender="${BASH_REMATCH[1]}"
target="${BASH_REMATCH[2]}"
path="${BASH_REMATCH[3]}"
reason="${BASH_REMATCH[4]}"
# I don't think we need to check if we were the target or not,
# the bot doesn't need to care as far as I can see.
for module in $modules_on_KILL; do
module_${module}_on_KILL "$sender" "$target" "$path" "$reason"
done
elif [[ "$line" =~ ^:([^ ]*)\ +PONG\ +([^ ]*)\ +:?(.*)$ ]]; then
sender="${BASH_REMATCH[1]}"
server2="${BASH_REMATCH[2]}"
data="${BASH_REMATCH[3]}"
for module in $modules_on_PONG; do
module_${module}_on_PONG "$sender" "$server2" "$data"
done
elif [[ $line =~ ^[^:] ]] ;then
# ERROR?
if [[ "$line" =~ ^ERROR\ +:(.*) ]]; then
error="${BASH_REMATCH[1]}"
log_error "Got ERROR from server: $error"
for module in $modules_on_server_ERROR; do
module_${module}_on_server_ERROR "$error"
done
# If we get an ERROR we can assume we are disconnected.
break
# PING? If not report as unhandled
elif ! server_handle_ping "$line"; then
log_info_file unknown_data.log "A non-sender prefixed line that didn't match any hook: $line"
fi
else
log_info_file unknown_data.log "Something that didn't match any hook: $line"
fi
done
if [[ $envbot_quitting -ne 0 ]]; then
# Hm, a trap got aborted it seems.
# Trying to handle this.
log_info "Quit trap got aborted: envbot_quitting=${envbot_quitting}. Recovering"
bot_quit
break
fi
log_error 'DIED FOR SOME REASON'
transport_disconnect
server_connected=0
for module in $modules_after_disconnect; do
module_${module}_after_disconnect
done
# Don't reconnect right away. We might get throttled and other nasty stuff.
sleep 10
done
rm -rf "$tmp_home"
|