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