chiark / gitweb /
Many bugfixes from actual testing!
[ircbot] / irccore.tcl
CommitLineData
574abac6 1proc defset {varname val} {
281f2c0e 2 upvar 1 $varname var
574abac6
IJ
3 if {![info exists var]} { set var $val }
4}
5
6# must set host
7defset port 6667
8
9defset nick testbot
9e738c1d 10defset ident blight
574abac6
IJ
11defset ownfullname "testing bot"
12defset ownmailaddr test-irc-bot@example.com
13
0d3ea3aa 14defset muststartby_ms 10000
574abac6
IJ
15defset out_maxburst 6
16defset out_interval 2100
17defset out_lag_lag 5000
18defset out_lag_very 25000
ea990080 19defset ownping_every 300000
574abac6 20
574abac6
IJ
21proc manyset {list args} {
22 foreach val $list var $args {
23 upvar 1 $var my
24 set my $val
25 }
26}
27
28proc try_except_finally {try except finally} {
29 global errorInfo errorCode
30 set er [catch { uplevel 1 $try } emsg]
31 if {$er} {
32 set ei $errorInfo
33 set ec $errorCode
34 if {[catch { uplevel 1 $except } emsg3]} {
35 append ei "\nALSO ERROR HANDLING ERROR:\n$emsg3"
36 }
37 }
38 set er2 [catch { uplevel 1 $finally } emsg2]
39 if {$er} {
40 if {$er2} {
41 append ei "\nALSO ERROR CLEANING UP:\n$emsg2"
42 }
43 return -code $er -errorinfo $ei -errorcode $ec $emsg
44 } elseif {$er2} {
45 return -code $er2 -errorinfo $errorInfo -errorcode $errorCode $emsg2
46 } else {
47 return $emsg
48 }
49}
50
51proc out__vars {} {
52 uplevel 1 {
53 global out_queue out_creditms out_creditat out_interval out_maxburst
54 global out_lag_lag out_lag_very
55#set pr [lindex [info level 0] 0]
56#puts $pr>[clock seconds]|$out_creditat|$out_creditms|[llength $out_queue]<
57 }
58}
59
60proc out_lagged {} {
61 out__vars
62 if {[llength $out_queue]*$out_interval > $out_lag_very} {
63 return 2
64 } elseif {[llength $out_queue]*$out_interval > $out_lag_lag} {
65 return 1
66 } else {
67 return 0
68 }
69}
70
71proc out_restart {} {
72 out__vars
73
74 set now [clock seconds]
75 incr out_creditms [expr {($now - $out_creditat) * 1000}]
76 set out_creditat $now
77 if {$out_creditms > $out_maxburst*$out_interval} {
78 set out_creditms [expr {$out_maxburst*$out_interval}]
79 }
80 out_runqueue $now
81}
82
83proc out_runqueue {now} {
84 global sock
85 out__vars
86
87 while {[llength $out_queue] && $out_creditms >= $out_interval} {
88#puts rq>$now|$out_creditat|$out_creditms|[llength $out_queue]<
89 manyset [lindex $out_queue 0] orgwhen msg
90 set out_queue [lrange $out_queue 1 end]
91 if {[llength $out_queue]} {
92 append orgwhen "+[expr {$now - $orgwhen}]"
93 append orgwhen "([llength $out_queue])"
94 }
95 puts "$orgwhen -> $msg"
96 puts $sock $msg
97 incr out_creditms -$out_interval
98 }
99 if {[llength $out_queue]} {
100 after $out_interval out_nextmessage
101 }
102}
103
104proc out_nextmessage {} {
105 out__vars
106 set now [clock seconds]
107 incr out_creditms $out_interval
108 set out_creditat $now
109 out_runqueue $now
110}
111
112proc sendout_priority {priority command args} {
113 global sock out_queue
114 if {[llength $args]} {
115 set la [lindex $args end]
116 set args [lreplace $args end end]
117 foreach i $args {
118 if {[regexp {[: ]} $i]} {
119 error "bad argument in output $i ($command $args)"
120 }
121 }
122 lappend args :$la
123 }
124 set args [lreplace $args 0 -1 $command]
125 set string [join $args { }]
126 set now [clock seconds]
127 set newe [list $now $string]
128 if {$priority} {
129 set out_queue [concat [list $newe] $out_queue]
130 } else {
131 lappend out_queue $newe
132 }
133 if {[llength $out_queue] == 1} {
134 out_restart
135 }
136}
137
138proc sendout {command args} { eval sendout_priority [list 0 $command] $args }
139
140proc log {data} {
141 puts $data
142}
5d6d1db7
IJ
143
144proc log_intern {what data} {
145 puts "[clock seconds] ++ $what $data"
146}
574abac6
IJ
147
148proc logerror {data} {
149 log $data
150}
151
152proc saveeic {} {
153 global saveei saveec errorInfo errorCode
154
155 set saveei $errorInfo
156 set saveec $errorCode
157
158 puts ">$saveec|$saveei<"
159}
160
161proc bgerror {msg} {
162 global save
163 logerror $msg
164 saveeic
165}
166
167proc onread {args} {
892f5a82 168 global sock nick calling_nick errorInfo errorCode line_org_endchar
281f2c0e 169
30ea02af
IJ
170 if {[catch { gets $sock line } rv]} { fail "error on input: $rv" }
171 if {$rv == -1} { fail "EOF on input" }
172
892f5a82 173 set line_org_endchar [string range $line end end]
574abac6
IJ
174 regsub -all "\[^ -\176\240-\376\]" $line ? line
175 set org $line
176
281f2c0e 177 new_event
574abac6
IJ
178
179 if {[regexp -nocase {^:([^ ]+) (.*)} $line dummy prefix remain]} {
180 set line $remain
181 if {[regexp {^([^!]+)!} $prefix dummy maybenick]} {
182 set calling_nick $maybenick
183 if {"[irctolower $maybenick]" == "[irctolower $nick]"} return
184 }
185 } else {
186 set prefix {}
187 }
188 if {![string length $line]} { return }
189 if {![regexp -nocase {^([0-9a-z]+) *(.*)} $line dummy command line]} {
190 log "bad command: $org"
191 return
192 }
193 set command [string toupper $command]
194 set params {}
195 while {[regexp {^([^ :]+) *(.*)} $line dummy thisword line]} {
196 lappend params $thisword
197 }
198 if {[regexp {^:(.*)} $line dummy thisword]} {
199 lappend params $thisword
200 } elseif {[string length $line]} {
201 log "junk at end: $org"
202 return
203 }
281f2c0e
IJ
204 if {"$command" == "PRIVMSG" && \
205 [privmsg_unlogged $prefix [ischan [lindex $params 0]] $params]} {
574abac6
IJ
206 return
207 }
208 log "[clock seconds] <- $org"
209 set procname msg_$command
210 if {[catch { info body $procname }]} { return }
211 if {[catch {
212 eval [list $procname $prefix $command] $params
213 } emsg]} {
214 logerror "error: $emsg ($prefix $command $params)"
215 saveeic
216 }
217}
218
ed982e50
IJ
219proc catch_restoreei {body} {
220 global errorInfo errorCode
221 set l [list $errorInfo $errorCode]
222 catch { uplevel 1 $body }
223 manyset $l errorInfo errorCode
224}
225
65aff3a3 226proc catch_logged {body} {
6cc2e28a 227 global errorInfo
65aff3a3 228 if {[catch { uplevel 1 $body } emsg]} {
6cc2e28a 229 logerror "error (catch_logged): $emsg\n $errorInfo"
65aff3a3
IJ
230 }
231}
232
574abac6
IJ
233proc sendprivmsg {dest l} {
234 foreach v [split $l "\n"] {
235 sendout [expr {[ischan $dest] ? "PRIVMSG" : "NOTICE"}] $dest $v
236 }
237}
238proc sendaction_priority {priority dest what} {
239 sendout_priority $priority PRIVMSG $dest "\001ACTION $what\001"
240}
241proc msendprivmsg {dest ll} { foreach l $ll { sendprivmsg $dest $l } }
242proc msendprivmsg_delayed {delay dest ll} { after $delay [list msendprivmsg $dest $ll] }
243
244proc check_nick {n} {
245 if {[regexp -nocase {[^][\\`_^{|}a-z0-9-]} $n]} { error "bad char in nick" }
246 if {[regexp {^[-0-9]} $n]} { error "bad nick start" }
f5c00d97 247 if {[string length $n] > 18} { error "nick too long" }
574abac6
IJ
248}
249
250proc ischan {dest} {
251 return [regexp {^[&#+!]} $dest]
252}
253
254proc irctolower {v} {
255 foreach {from to} [list "\\\[" "{" \
256 "\\\]" "}" \
257 "\\\\" "|" \
258 "~" "^"] {
259 regsub -all $from $v $to v
260 }
261 return [string tolower $v]
262}
263
281f2c0e
IJ
264proc prefix_none {} {
265 upvar 1 p p
266 if {[string length $p]} { error "prefix specified" }
267}
268
269proc prefix_nick {} {
270 global nick
271 upvar 1 p p
272 upvar 1 n n
273 if {![regexp {^([^!]+)!} $p dummy n]} { error "not from nick" }
274 check_nick $n
275 if {"[irctolower $n]" == "[irctolower $nick]"} {
276 error "from myself" {} {}
277 }
278}
279
574abac6 280proc msg_PING {p c s1} {
574abac6
IJ
281 prefix_none
282 sendout PONG $s1
0d3ea3aa
IJ
283}
284
ea990080
IJ
285proc sendownping {} {
286 global ownping_every nick
287 sendout ping $nick
288 after $ownping_every sendownping
289}
290
0d3ea3aa
IJ
291proc msg_001 {args} {
292 global muststartby_after
293 if {[info exists muststartby_after]} {
294 after cancel $muststartby_after
295 unset muststartby_after
ea990080 296 sendownping
281f2c0e
IJ
297 connected
298 }
299}
300
301proc ensure_outqueue {} {
302 out__vars
303 if {[info exists out_queue]} return
304 set out_creditms 0
305 set out_creditat [clock seconds]
306 set out_queue {}
307 set out_lag_reported 0
308 set out_lag_reportwhen $out_creditat
309}
310
311proc fail {msg} {
312 logerror "failing: $msg"
313 exit 1
314}
315
316proc ensure_connecting {} {
9e738c1d 317 global sock ownfullname host port nick ident socketargs
0d3ea3aa 318 global muststartby_ms muststartby_after
281f2c0e
IJ
319
320 ensure_outqueue
321
322 if {[info exists sock]} return
323 set sock [eval socket $socketargs [list $host $port]]
324 fconfigure $sock -buffering line
325 fconfigure $sock -translation crlf
326
9e738c1d 327 sendout USER $ident 0 * $ownfullname
281f2c0e
IJ
328 sendout NICK $nick
329 fileevent $sock readable onread
330
0d3ea3aa
IJ
331 set muststartby_after [after $muststartby_ms \
332 {fail "no successfuly connect within timeout"}]
574abac6 333}