1 # maintains local list of users to userv-slurp config from
2 # each user provides list of
10 # nicks ignore presence
12 # time for `a while ago'
13 # time for `very-recently'
15 # nick ignore|nopresence|prefer <glob-pattern> [...]
16 # times <very-recently> <a-while-ago> (default 120 450)
17 # (affect subsequent `monitor' directives)
18 # monitor <monname> <#chan>[,<#chan>...]
19 # <monname> must start with <username>:
21 # a deviceset specifies
26 # leds <led-group> <monname> <state>=<value>
27 # where state is one of
28 # [pref]talk[now] any non-ignored (with `pref', only any preferred)
29 # nick(s) spoke at least somewhat recently
30 # (with `now', only if they spoke very recently)
31 # present at least some non-nopresence nicks present
32 # default always matches
33 # where the first matching state wins; if none, no LEDs are set
43 defset retry_after 900000
44 defset chan_after 1500
45 defset chans_retry 3600000
46 defset debug_reset_after 86400000
52 # monitor/$monname(chans) -> [list $chan1 $chan2 ...]
53 # monitor/$monname(ignore) -> [list $regexp ...]
54 # monitor/$monname(prefer) -> [list $regexp ...]
55 # monitor/$monname(present-$chan) -> [list $lnick ...]
56 # monitor/$monname(last-talk) -> $time_t
57 # monitor/$monname(last-talkpref) -> $time_t
58 # monitor/$monname(time-recent) -> $seconds
59 # monitor/$monname(time-recentnow) -> $seconds
60 # monitor/$monname(talkchange) -> [after ...] or unset
62 # deviceset/$username:$lno(monname) -> $monname
63 # deviceset/$username:$lno(group) -> $led_group
64 # deviceset/$username:$lno(username) -> $username
65 # deviceset/$username:$lno(values) -> $valuestring
66 # deviceset/$username:$lno(states) -> [list $state1 $value1 $state2 ...]
67 # deviceset/$username:$lno(ochan) -> [open remoteleds ... | r] or unset
68 # deviceset/$username:$lno(ichan) -> fifo for remoteleds input or unset
69 # deviceset/$username:$lno(retry) -> [after ... ] or unset
71 # onchans($chan) [list mustleave] # in config_chane
72 # onchans($chan) [list idle]
73 # onchans($chan) [list forced] # for errchan
74 # onchans($chan) [list shortly [after ...]] # do a NAMES
76 proc ldebug {facil m} {
83 if {![llength $debugusers]} return
84 if {[regexp {[mdu]([^:]+)\:} $facil dummy username] &&
85 [lsearch -exact $debugusers $username]==-1} return
87 regsub {^(.)} $facil {\1 } cc
88 reporterr "DEBUG $cc $m"
93 foreach v [info globals] {
94 if {![regsub ^$vp/ $v {} v]} continue
100 proc privmsg_unlogged {p ischan params} {
102 if {!$ischan} { return 0 }
107 foreach m [list_objs monitor] {
108 mon_speech $m [irctolower [lindex $params 0]] [irctolower $n]
111 log "processing error: $emsg\n$errorInfo"
118 sendprivmsg $errchan $m
121 proc msg_PRIVMSG {p c dest text} {
124 execute_usercommand $p $c $n $errchan $dest $text
127 proc proc_mon {name argl body} {
128 proc mon_$name [concat m $argl] "
129 upvar #0 monitor/\$m mm
133 proc mon_nick_is {globlist ln} {
134 foreach gl $globlist {
135 if {[string match $gl $ln]} { return 1 }
140 proc_mon gotchanlist {ch nll} {
142 if {[lsearch -exact $mm(chans) $ch] == -1} return
145 if {![string compare $nl [irctolower $nick]]} continue
146 if {[mon_nick_is $mm(nopresence) $nl]} continue
149 ldebug m$m "$ch names: $l"
150 set mm(present-$ch) $l
154 proc_mon speech {chan ln} {
155 if {[lsearch -exact $mm(chans) $chan] == -1} return
156 if {[mon_nick_is $mm(ignore) $ln]} return
157 set now [clock seconds]
158 set mm(last-talk) $now
159 if {[mon_nick_is $mm(prefer) $ln]} { set mm(last-talkpref) $now }
163 proc_mon calcstate {} {
165 foreach ch $mm(chans) {
166 if {[llength $mm(present-$ch)]} { append s "present "; break }
168 set now [clock seconds]
169 set valid_until [expr {$now + 86400}]
171 catch { after cancel $mm(talkchange) }
172 foreach p {{} pref} {
174 set vu [expr {$mm(last-talk$p) + $mm(time-recent$t)}]
175 if {$vu < $now} continue
176 append s "${p}talk${t} "
178 if {$vu < $valid_until} { set valid_until $vu }
181 regsub {^ default } $s { } ss
182 set ds [string trim $ss]
183 if {$refresh_later} {
184 set interval [expr {$valid_until - $now + 2}]
185 set ivms [expr {$interval*1000}]
186 set mm(talkchange) [after $ivms [list mon_updateall $m]]
187 ldebug m$m "until now+${interval}: $ds"
189 ldebug m$m "indefinitely: $ds"
194 proc_mon updateall {} {
195 set s [mon_calcstate $m]
196 foreach d [list_objs deviceset] {
197 upvar #0 deviceset/$d dd
198 if {[string compare $m $dd(monname)]} continue
199 dset_setbystate $d $s
203 proc_mon destroy {} {
204 ldebug m$m "destroying"
205 catch { after cancel $mm(talkchange) }
209 proc proc_dset {name argl body} {
210 proc dset_$name [concat d $argl] "
211 upvar #0 deviceset/\$d dd
215 reporterr \"error on \$d: \$emsg\"
220 log "[clock seconds] $m"
223 proc_dset setbystate {s} {
224 foreach {sq v} $dd(states) {
225 if {![string match *$sq* $s]} continue
228 if {![info exists dd(ichan)]} return
229 if {![info exists lv]} {
230 reporterr "no state for $d matching$s"
233 ldebug d$d "matches $sq: $v"
235 set dd(values) "$sq=$lv"
239 proc_dset destroy {} {
240 ldebug d$d "destroying"
241 catch { after cancel $dd(retry) }
243 if {[info exists dd(ochan)]} { timed_log ">\$$d destroy" }
250 proc modvar_save_copy {cv defv} {
254 if {[info exists save($cv)]} {
255 set mm($cv) $save($cv)
261 proc reloaduser {username} {
262 check_username $username
263 ldebug u$username "reloading"
265 set cfg [exec userv --timeout 3 $username irc-ledcontrol-config \
268 regsub "\n" $emsg " // " emsg
269 reporterr "error reloading $username: $emsg"
272 foreach d [list_objs deviceset] {
273 if {![string match $username:* $d]} continue
276 foreach m [list_objs monitor] {
277 if {![string match $username* $m]} continue
278 upvar #0 monitor/$m mm
279 foreach cv [array names mm] { set save/${m}($cv) $mm($cv) }
281 if {![string length $cfg]} {
282 file delete pwdb/$username
283 return "no config from $username"
285 exec userv --timeout 3 $username irc-ledcontrol-passwords \
286 < /dev/null > pwdb/p$username
288 reporterr "error reading passwords for $username: $emsg"
291 ldebug u$username "parsing"
292 foreach cv {ignore nopresence prefer} { set cc($cv) {} }
293 set cc(time-recentnow) 120
294 set cc(time-recent) 450
297 foreach l [split $cfg "\n"] {
299 append contin [string trim $l]
300 if {[regsub {\\$} $contin { } contin]} continue
303 if {[regexp {^\#} $l]} {
304 } elseif {[regexp {^nick\s+(ignore|nopresence|prefer)\s+(\S.*)$} \
305 $l dummy kind globs]} {
307 foreach gl [split $globs " "] {
308 if {![string length $gl]} continue
310 lappend cc($kind) $gl
312 } elseif {[regexp {^times\s+(\d+)\s+(\d+)$} $l dummy r rnow]} {
313 foreach cv {{} now} { set cc(time-recent$cv) [set r$cv] }
314 } elseif {[regexp {^monitor\s+(\S+)\s+(\S.*)$} $l dummy m cl]} {
316 if {![string match $username:* $m]} {
317 error "monname must start with $username:"
320 foreach ch [split $cl " "] {
321 if {![string length $ch]} continue
323 if {![ischan $ch]} { error "invalid channel $ch" }
324 lappend cc(chans) [irctolower $ch]
327 upvar #0 monitor/$m mm
328 foreach cv [array names cc] { set mm($cv) $cc($cv) }
329 foreach cv {{} pref} {
330 modvar_save_copy last-talk$cv 0
332 foreach cv [array names mm(chans)] {
333 modvar_save_copy present-$cv {}
337 {^leds\s+([0-9A-Za-z][-.:/0-9A-Za-z]+)\s+(\S+)\s+(\S+.*)$} \
338 $l dummy g m states]} {
339 set d $username:$lno:$g
342 foreach sv [split $states " "] {
343 if {![string length $sv]} continue
345 {^((?:pref)?talk(?:now)?|present|default)\=([0-9a-z][,/+0-9A-Za-z]*)$} \
346 $sv dummy lhs rhs]} {
347 error "invalid state spec"
351 upvar #0 deviceset/$d dd
355 set dd(values) startup
356 set dd(username) $username
360 error "invalid directive or syntax"
363 if {[string length $contin]} {
364 error "continuation line at end of file"
367 reporterr "setup error $username:$lno:$emsg"
370 return "reloaded $username"
374 proc check_monname {m} {
375 if {[regexp {[^-_+:.#0-9a-zA-Z]} $m badchar]} {
376 error "char $badchar not allowed in monnames"
378 if {![regexp {^[0-9a-zA-Z]} $m]} {
379 error "monname must start with alphanum"
384 catch { unset dd(retry) }
385 set username $dd(username)
386 ldebug d$d "starting"
388 set cmdl [list remoteleds --pipe $dd(group) \
389 --human --passfile-only pwdb/p$username]
390 timed_log "!-$d [join $cmdl " "]"
391 lappend cmdl < pwdb/fifo |& cat
392 catch { file delete pwdb/fifo }
393 exec mkfifo -m 0600 pwdb/fifo
394 set ichan [open pwdb/fifo r+]
395 set ochan [open |$cmdl r]
396 fconfigure $ichan -blocking 0 -buffering line
397 fconfigure $ochan -blocking 0 -buffering line
400 fileevent $ochan readable [list dset_rledout $d]
402 reporterr "remoteleds startup $d: $emsg"
403 catch { close $ichan }
404 catch { close $ochan }
409 proc_dset rledout {} {
411 while {[gets $dd(ochan) l] != -1} {
412 reporterr "on $d: $dd(values): $l"
414 if {[fblocked $dd(ochan)]} return
415 timed_log ">\$$d failure";
416 catch { close $dd(ichan) }
417 catch { close $dd(ochan) }
420 reporterr "on $d died"
424 proc_dset trylater {} {
426 ldebug d$d "will try again later"
427 set dd(retry) [after $retry_after [list dset_start $d]]
430 proc config_change {} {
431 global onchans chans_retry errchan config_retry_after
432 ldebug {} "rechecking configuration etc"
433 foreach ch [array names onchans] {
434 manyset $onchans($ch) status after
435 if {"$status" == "shortly"} {
436 catch { after cancel $after }
438 set onchans($ch) mustleave
440 sendout JOIN $errchan
441 chan_shortly $errchan
442 foreach m [list_objs monitor] {
443 upvar #0 monitor/$m mm
444 foreach ch $mm(chans) {
449 foreach ch [array names onchans] {
450 if {"[lindex $onchans($ch) 0]" != "mustleave"} continue
454 catch { after cancel $config_retry_after }
455 set config_retry_after [after $chans_retry config_change]
458 proc allchans_shortly {} {
460 foreach ch [array names onchans] { chan_shortly $ch }
463 proc chan_shortly {ch} {
465 set ch [irctolower $ch]
466 upvar #0 onchans($ch) oc
467 if {[info exists oc]} {
468 manyset $oc status after
469 if {"$status" == "shortly"} {
470 ldebug c$ch "queued check already pending"
474 ldebug c$ch "queueing check"
475 set oc [list shortly [after $chan_after chan_sendnames $ch]]
478 proc msg_353 {p c dest type chan nicklist} {
479 set lchan [irctolower $chan]
480 set nll [irctolower $nicklist]
481 regsub -all {[=@*]} $nll {} nll
482 ldebug c$lchan "all names: $nll"
483 foreach m [list_objs monitor] {
484 mon_gotchanlist $m $lchan $nll
488 proc chan_sendnames {ch} {
489 upvar #0 onchans($ch) oc
490 ldebug c$ch "asking for namelist"
496 set username [ta_word]
498 set m [reloaduser $username]
503 proc debug_reset {} {
504 global debugusers debug_cancelling
505 unset debug_cancelling
507 reporterr "debug mode timed out"
512 global debugusers debug_cancelling debug_reset_after
513 if {![string length $text]} { error "must give list of usernames" }
516 catch { after cancel $debug_cancelling }
517 set debug_cancelling [after $debug_reset_after debug_reset]
518 reporterr "debug enabled by $n: $debugusers"
524 global debugusers debug_cancelling
526 catch { after cancel $debug_cancelling }
527 catch { unset debug_cancelling }
528 reporterr "debug disabled by $n"
533 foreach m [list_objs monitor] {
534 upvar #0 monitor/$m mm
535 lappend r "monitoring $mm(chans) for $m"
537 foreach d [list_objs deviceset] {
538 upvar #0 deviceset/$d dd
539 regexp {^[^:]*\:[^:]*} $dd(group) dest
540 lappend r "sending $dd(monname) to $dest"
542 ucmdr [join $r "\n"] {}
546 ldebug {} "connected"
547 foreach f [glob -nocomplain pwdb/p*] {
548 regexp {^pwdb/p(.*)$} $f dummy username
549 set m [reloaduser $username]
554 proc msg_JOIN {p c chan} { chan_shortly $chan }
555 proc msg_PART {p c chan} { chan_shortly $chan }
556 proc msg_KILL {p c user why} { allchans_shortly }
557 proc msg_QUIT {p c why} { allchans_shortly }
558 proc msg_NICK {p c newnick} { allchans_shortly }
559 proc msg_KICK {p c chans users comment} {
560 if {[llength $chans] > 1} {
563 chan_shortly [lindex $chans 0]
571 fail "startup: $emsg"