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
44 # monitor/$monname(chans) -> [list $chan1 $chan2 ...]
45 # monitor/$monname(ignore) -> [list $regexp ...]
46 # monitor/$monname(prefer) -> [list $regexp ...]
47 # monitor/$monname(present) -> [list $lnick ...]
48 # monitor/$monname(last-talk) -> $time_t
49 # monitor/$monname(last-talkpref) -> $time_t
50 # monitor/$monname(time-recent) -> $seconds
51 # monitor/$monname(time-recentnow) -> $seconds
53 # deviceset/$username:$lno(monname) -> $monname
54 # deviceset/$username:$lno(group) -> $led_group
55 # deviceset/$username:$lno(username) -> $username
56 # deviceset/$username:$lno(states) -> [list $state1 $value1 $state2 ...]
57 # deviceset/$username:$lno(fchan) -> [open remoteleds ... |] or unset
58 # deviceset/$username:$lno(retry) -> [after ... ] or unset
60 # onchans($chan) [list mustleave] # in config_chane
61 # onchans($chan) [list idle]
62 # onchans($chan) [list forced] # for errchan
63 # onchans($chan) [list shortly [after ...]] # do a NAMES
67 foreach v
[info globals
] {
68 if {![regsub ^
$vp/ $v {} v
]} continue
74 proc privmsg_unlogged
{p ischan params
} {
76 if {!$ischan} { return 0 }
81 foreach m
[list_objs monitor
] {
82 mon_speech
$m [irctolower
[lindex $params 0]] [irctolower
$n]
85 log
"processing error: $emsg\n$errorInfo"
92 sendprivmsg
$errchan $m
95 proc msg_PRIVMSG
{p c dest
text} {
98 execute_usercommand
$p $c $n $errchan $dest $text
101 proc proc_mon
{name argl body
} {
102 proc mon_
$name [concat m
$argl] "
103 upvar #0 monitor/\$m mm
107 proc mon_nick_is
{globlist ln
} {
108 foreach gl
$globlist {
109 if {[string match
$gl $ln]} { return 1 }
114 proc_mon speech
{chan ln
} {
115 if {[lsearch -exact $mm(chans
) $chan] == -1} return
116 if {[mon_nick_is
$mm(ignore
) $ln]} return
117 set now
[clock seconds
]
118 set mm
(last-talk
) $now
119 if {[mon_nick_is
$mm(prefer
) $ln]} { set mm
(last-talkpref
) $now }
123 proc_mon calcstate
{} {
125 if {[llength $mm(present
)]} { append s
"present " }
126 set now
[clock seconds
]
127 foreach p
{{} pref
} {
129 set since
[expr {$now - $mm(time-recent
$t)}]
130 if {[expr {$mm(last-talk
$p) < $since}]} continue
131 append s
"${p}talk${t} "
137 proc_mon updateall
{} {
138 set s
[mon_calcstate
$m]
139 foreach d
[list_objs deviceset
] {
140 upvar #0 deviceset/$d dd
141 if {[string compare
$m $dd(monname
)]} continue
142 dset_setbystate
$d $s
146 proc_mon
destroy {} {
150 proc proc_dset
{name argl body
} {
151 proc dset_
$name [concat d
$argl] "
152 upvar #0 deviceset/\$d dd
156 reporterr \"error on \$d: \$emsg\"
161 log
"[clock seconds] $m"
164 proc_dset setbystate
{s
} {
167 if {![string match
*$sq* $s]} continue
174 proc_dset
destroy {} {
175 catch { after cancel
$dd(retry
) }
177 if {[info exists dd
(fchan
)]} { timed_log
">\$$d destroy" }
183 proc reloaduser
{username
} {
184 check_username
$username
186 set cfg
[exec userv
--timeout 3 $username irc-ledcontrol-config
\
189 regsub "\n" $emsg " // " emsg
190 reporterr
"error reloading $username: $emsg"
193 foreach d
[list_objs deviceset
] {
194 if {![string match
$username:* $d]} continue
197 foreach m
[list_objs monitor
] {
198 if {![string match
$username* $m]} continue
201 if {![string length
$cfg]} {
202 file remove pwdb
/$username
203 return "no config from $username"
205 exec userv
--timeout 3 $username irc-ledcontrol-passwords
\
206 < /dev
/null
> pwdb
/p
$username
208 reporterr
"error reading passwords for $username: $emsg"
211 foreach cv
{ignore nopresence prefer
} { set cc
($cv) {} }
212 set cc
(time-recentnow
) 120
213 set cc
(time-recent
) 450
215 foreach l
[split $cfg "\n"] {
217 set l
[string trim
$l]
218 if {[regexp {^
\#} $l]} {
219 } elseif
{[regexp {^nick
\s
+(ignore|nopresence|prefer
)\s
+(\S.
*)$} \
220 $l dummy kind globs
]} {
222 foreach gl
[split $globs " "] {
223 if {![string length
$gl]} continue
225 lappend cc
($kind) $gl
227 } elseif
{[regexp {^times
\s
+(\d
+)\s
+(\d
+)$} $l dummy r rnow
]} {
228 foreach cv
{{} now
} { set cc
(time-recent
$cv) [set r
$cv] }
229 } elseif
{[regexp {^monitor
\s
+(\S
+)\s
+(\S.
*)$} $l dummy m cl
]} {
231 if {![string match
$username:* $m]} {
232 error "monname must start with $username:"
235 foreach ch
[split $cl " "] {
236 if {![string length
$ch]} continue
238 if {![ischan
$ch]} { error "invalid channel $ch" }
239 lappend cc
(chans
) [irctolower
$ch]
242 upvar #0 monitor/$m mm
243 foreach cv
[array names cc
] { set mm
($cv) $cc($cv) }
244 foreach cv
{{} pref
} { set mm
(last-talk
$cv) 0 }
247 {^leds
\s
+([0-9A-Za-z
][-:/0-9A-Za-z
]+)\s
+(\S
+)\s
+(\S
+.
*)$} \
248 $l dummy g m states
]} {
249 set d
$username:$lno:$g
252 foreach sv
[split $states " "] {
253 if {![string length
$sv]} continue
255 {^
((pref
)?talk
(now
)?|present|
default)\=([0-9a-z
][,/+0-9A-Za-z
]*)$} \
256 $sv dummy lhs dummy dummy rhs
]} {
257 error "invalid state spec"
261 upvar #0 deviceset/$d dd
265 set dd
(username
) $username
270 reporterr
"setup error $username:$lno:$emsg"
273 return "reloaded $username"
277 proc check_monname
{m
} {
278 if {[regexp {[^
-_+:.
#0-9a-zA-Z]} $m badchar]} {
279 error "char $badchar not allowed in monnames"
281 if {![regexp {^
[0-9a-zA-Z
]} $m]} {
282 error "monname must start with alphanum"
287 catch { unset dd
(retry
) }
288 set username
$dd(username
)
290 set cmdl
[list remoteleds
--pipe $dd(group
) \
291 --passfile-only pwdb
/p
$username]
292 timed_log
"!-$d [join $cmdl " "]"
293 set fchan
[open |
[concat $cmdl {|
& cat
}] r
+]
294 fconfigure $fchan -blocking 0
295 fileevent $fchan readable
[list dset_rledout
$d]
298 reporterr
"remoteleds startup $d: $emsg"
303 proc_dset rledout
{} {
305 while {[gets $dd(fchan
) l
] != -1} { reporterr
"remoteleds on $d: $l" }
306 if {[fblocked $dd(fchan
)]} return
308 timed_log
">\$$d failure";
312 reporterr
"remoteleds on $d died"
316 proc_dset trylater
{} {
318 set dd
(retry
) [after $retry_after [list dset_start
$d]]
321 proc config_change
{} {
322 global onchans chans_retry errchan
323 foreach ch
[array names onchans
] {
324 manyset
$onchans($ch) status
after
325 if {"$status" == "shortly"} {
326 catch { after cancel
$after }
328 set onchans
($ch) mustleave
330 sendout JOIN
$errchan
331 chan_shortly
$errchan
332 foreach m
[list_objs monitor
] {
333 upvar #0 monitor/$m mm
334 foreach ch
$mm(chans
) {
339 foreach ch
[array names onchans
] {
340 if {"[lindex $onchans($ch) 0]" != "mustleave"} continue
344 after $chans_retry config_change
347 proc chan_shortly
{ch
} {
349 upvar #0 onchans($ch) oc
350 if {[info exists oc
]} {
351 manyset
$oc status
after
352 if {"$status" == "shortly"} return
354 set oc
[list shortly
[after $chan_after chan_sendnames
$ch]]
357 proc chan_sendnames
{ch
} {
358 upvar #0 onchans($ch) oc
364 set username
[ta_word
]
366 set m
[reloaduser
$username]
373 foreach m
[list_objs monitor
] {
374 upvar #0 monitor/$m mm
375 lappend r
"monitoring $mm(chans) for $m"
377 foreach d
[list_objs deviceset
] {
378 upvar #0 deviceset/$d dd
379 regexp {^
[^
:]*\:[^
:]*} $dd(group
) dest
380 lappend r
"sending $dd(monname) to $dest"
385 foreach f
[glob -nocomplain pwdb
/p
*] {
386 regexp {^pwdb
/p
(.
*)$} $f dummy username
387 set m
[reloaduser
$username]
401 fail
"startup: $emsg"