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