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