a47be46fa4d84d7651d7f0d2f7a834f284db4db3
[bashbot.git] / bashbot.sh
1 #!/bin/bash
2
3 set -u
4
5 version="0.0.1"
6
7 # make us a fifo...
8 rm -f .bashbotfifo{in,out}
9 mkfifo .bashbotfifoin
10 mkfifo .bashbotfifoout
11
12 # Setup some fds to talk to IRC
13 exec 3<> .bashbotfifoin
14 exec 4<> .bashbotfifoout
15
16 irc_process=
17 keeprunning=1
18 declare -A reminders
19
20 cleanup() {
21     send_line "QUIT :Going back to hell"
22     keeprunning=0
23     sleep 0.2
24     if [[ $irc_process -gt 0 ]]; then
25         if (ps -p $irc_process > /dev/null); then
26             kill $irc_process
27         fi
28     fi
29     rm .bashbotfifo{in,out}
30     trap - EXIT
31     exit 0
32 }
33
34 my_hostname="$(hostname)"
35 my_irc_channel="#alug"
36 my_irc_password=""
37 my_irc_nick="bashbot"
38 my_irc_server="irc.oftc.net"
39 my_irc_port="6697"
40
41 # now if there's a settings file, source that...
42 if [ -e "$(dirname $(readlink -f $0))/.botsettings.sh" ]; then
43     . "$(dirname $(readlink -f $0))/.botsettings.sh"
44 fi
45
46 connect_to_server() {
47     gnutls-cli --crlf $my_irc_server:$my_irc_port <&4 >&3 2>&3 &
48     irc_process=$!
49     connected=0
50     while [ $connected == 0 ]; do
51         read -t 0.2 -u 3 line
52         if [[ "$line" =~ NOTICE\  ]]; then
53             echo $line
54             send_line PASS $my_irc_password
55             send_line NICK $my_irc_nick
56             send_line USER $my_irc_nick $my_hostname $my_irc_server $my_irc_nick
57             send_line JOIN $my_irc_channel
58             connected=1
59         fi
60     done
61 }
62
63 # Format of server messages
64 #:thingsendingmessage command params
65 # e.g. PRIVMSG
66 #:iDunno!~brettp@mail.ipv6.sommitrealweird.co.uk PRIVMSG #bp :hello.
67
68 process_irc() {
69     line="$@"
70     echo "$line"
71     ping_regex="^PING "
72     privmsg_regex="^[^ ]* PRIVMSG"
73     if [[ "${line}" =~ $ping_regex ]]; then
74         send_line PONG
75         echo PONG
76     elif [[ "${line}" =~ $privmsg_regex ]]; then
77         parse_privmsg "$line"
78     fi
79 }
80
81 parse_privmsg() {
82     line="$@"
83     echo "Processing $line"
84     read -a parts <<-EOF
85 $line
86 EOF
87
88     # last character of the last element is going to be a \r so remove it
89     parts[-1]=${parts[-1]%$'\r'}
90
91     if [[ "${parts[1]}" == "PRIVMSG" ]]; then
92         sender=${parts[0]}
93         channel=${parts[2]}
94         do_command=0
95         case $channel in
96             $my_irc_channel)
97                 if [[ ${parts[3]} =~ [:]*$my_irc_nick[:]*$ ]]; then
98                     do_command=1
99                 fi
100                 bot_command=${parts[4]}
101                 options="${parts[@]:5}"
102                 ;;
103             $my_irc_nick)
104                 bot_command=${parts[3]}
105                 bot_command=${bot_command#:}
106                 channel=${parts[0]%\!*}
107                 channel=${channel#:}
108                 options="${parts[@]:4}"
109                 do_command=1
110                 ;;
111         esac
112         if [[ $do_command -gt 0 ]]; then
113             case $bot_command in
114                 help)
115                     send_help_message $channel
116                     ;;
117                 quit)
118                     cleanup
119                     ;;
120                 version)
121                     send_version $channel
122                     ;;
123                 add)
124                     add_reminder $channel "$options"
125                     ;;
126                 list)
127                     list_reminders $channel
128                     ;;
129                 *)
130                     send_action $channel "is sorry, they don't know how to $bot_command."
131                     ;;
132             esac
133         fi
134     fi
135 }
136
137 send_line() {
138     echo "$@" >&4
139     sleep 0.1
140 }
141
142 send_privmsg() {
143     channel="$1"
144     content="$2"
145
146     send_line "PRIVMSG $channel :$content"
147 }
148
149 send_action() {
150     channel="$1"
151     content="$2"
152     action_wrapper=$'\001'
153
154     content="${action_wrapper}ACTION ${content}${action_wrapper}"
155     send_privmsg "$channel" "$content"
156 }
157
158 send_help_message() {
159     channel=$1
160     echo "Sending help!"
161     send_privmsg $channel "Help:"
162     send_privmsg $channel "  An IRC bot written in bash!"
163     send_privmsg $channel "  help - display this message"
164     send_privmsg $channel "  version - display version number"
165     send_privmsg $channel "  quit - make the bot quit IRC"
166     send_privmsg $channel "  add YYYY-mm-dd HH:MM reminder text"
167     send_privmsg $channel "  list - list reminders"
168 }
169
170 send_version() {
171     channel=$1
172     send_privmsg $channel "Version: $version"
173 }
174
175 add_reminder() {
176     channel="$1"
177     data="$2"
178     read -a dataparts <<-EOF
179 $data
180 EOF
181     # we now have a list of parts, they should be of the form
182     # YYYY-mm-dd HH:MM the text of the reminder
183     timestamp=$(date +"%s" --date="${dataparts[0]} ${dataparts[1]}")
184     if [[ $? -ne 0 ]]; then
185         send_privmsg $channel "Couldn't parse date/time ${dataparts[0]} ${dataparts[1]}"
186     else
187         send_privmsg $channel "Added reminder for ${dataparts[0]} ${dataparts[1]}: ${dataparts[@]:2}"
188         reminders[$timestamp]="${dataparts[@]:2}"
189     fi
190 }
191
192 check_reminders() {
193     current_timestamp="$(date +"%s")"
194     # loop through the reminders keys to see if it's past and alert if so
195     for ts in ${!reminders[@]}; do
196         if [[ $ts -le $current_timestamp ]]; then
197             send_privmsg $my_irc_channel "${reminders[$ts]}"
198             # we need to remove this from the array now
199             unset "reminders[$ts]"
200         fi
201     done
202 }
203
204 list_reminders() {
205     channel=$1
206     for ts in ${!reminders[@]}; do
207         date_stamp="$(date --date="@$ts" +"%Y-%m-%d %H:%M")"
208         send_privmsg $channel "$date_stamp ${reminders[$ts]}"
209     done
210 }
211
212 connect_to_server
213 trap cleanup EXIT SIGINT
214
215 while true; do
216     # see if there's anything to read
217     read -t 0.2 -u 3 aline
218     if [ $? == 0 ]; then
219         process_irc "$aline"
220         sleep 0.2
221     fi
222     check_reminders
223     # check that the irc_process is still running, restart it if not
224     if ( ! kill -0 $irc_process ); then
225         connect_to_server
226     fi
227 done