defset out_lag_lag 5000
defset out_lag_very 25000
+defset marktime_min 300
+defset marktime_join_startdelay 5000
+
proc manyset {list args} {
foreach val $list var $args {
upvar 1 $var my
}
}
+proc usererror {emsg} { error $emsg {} {BLIGHT USER} }
+
proc out__vars {} {
uplevel 1 {
global out_queue out_creditms out_creditat out_interval out_maxburst
[regexp {^[&#+!]} [lindex $params 0]] &&
![regexp {^![a-z][-a-z]*[a-z]( .*)?$} [lindex $params 1]]} {
# on-channel message, ignore
- catch {
- recordlastseen_p $prefix "talking on [lindex $params 0]" 1
- }
+ set chan [lindex $params 0]
+ upvar #0 chan_lastactivity([irctolower $chan]) la
+ set la [clock seconds]
+ catch { recordlastseen_p $prefix "talking on $chan" 1 }
return
}
log "[clock seconds] <- $org"
}
}
-proc showintervalsecs {howlong} {
- return [showintervalsecs/[opt timeformat] $howlong]
+proc showintervalsecs {howlong abbrev} {
+ return [showintervalsecs/[opt timeformat] $howlong $abbrev]
}
-proc showintervalsecs/ks {howlong} {
+proc showintervalsecs/ks {howlong abbrev} {
if {$howlong < 1000} {
return "${howlong}s"
} else {
}
}
-proc format_qty {qty unit} {
+proc format_qty {qty unit abbrev} {
set o $qty
- append o " "
- append o $unit
- if {$qty != 1} { append o s }
+ if {$abbrev} {
+ append o [string range $unit 0 0]
+ } else {
+ append o " "
+ append o $unit
+ if {$qty != 1} { append o s }
+ }
return $o
}
-proc showintervalsecs/hms {qty} {
+proc showintervalsecs/hms {qty abbrev} {
set ul {second 60 minute 60 hour 24 day 7 week}
set remainv 0
while {[llength $ul] > 1 && $qty >= [set uv [lindex $ul 1]]} {
set qty [expr {($qty-$remainv)/$uv}]
set ul [lreplace $ul 0 1]
}
- set o [format_qty $qty [lindex $ul 0]]
+ set o [format_qty $qty [lindex $ul 0] $abbrev]
if {$remainv} {
- append o " "
- append o [format_qty $remainv $remainu]
+ if {!$abbrev} { append o " " }
+ append o [format_qty $remainv $remainu $abbrev]
}
return $o
}
if {$howlong <= 0} {
return {just now}
} else {
- return "[showintervalsecs $howlong] ago"
+ return "[showintervalsecs $howlong 0] ago"
}
}
}
upvar #0 chan_nicks($lchan) nlist
unset nlist
+ upvar #0 chan_lastactivity($lchan) la
+ catch { unset la }
}
proc doleave {lchan} {
# nick_username($luser) -> <securely known local username>
# nick_unique($luser) -> <counter>
# nick_case($luser) -> $user (valid even if no longer visible)
+# nick_markid($luser) -> <after id for marktime>
# chan_nicks($lchan) -> [list ... $luser ...]
+# chan_lastactivity($lchan) -> [clock seconds]
proc lnick_forget {luser} {
global nick_arys chan_nicks
+ lnick_marktime_cancel $luser
foreach ary $nick_arys {
upvar #0 nick_${ary}($luser) av
catch { unset av }
recordlastseen_n $n "changing nicks to $newnick" 0
recordlastseen_n $newnick "changing nicks from $n" 1
set luser [irctolower $n]
+ lnick_marktime_cancel $luser
set lusernew [irctolower $newnick]
foreach ary $nick_arys {
upvar #0 nick_${ary}($luser) old
set nlist [grep tn {"$tn" != "$luser"} $nlist]
lappend nlist $lusernew
}
+ lnick_marktime_start $lusernew "Hi." 500
nick_case $newnick
}
set lchan [irctolower $chan]
upvar #0 nick_onchans($nl) oc
upvar #0 chan_nicks($lchan) nlist
+ if {![info exists oc]} {
+ global marktime_join_startdelay
+ lnick_marktime_start $nl "Welcome." $marktime_join_startdelay
+ }
lappend oc $lchan
lappend nlist $nl
nick_ishere $n
}
proc msg_PRIVMSG {p c dest text} {
+ global errorCode
+
prefix_nick
if {[ischan $dest]} {
recordlastseen_n $n "invoking me in $dest" 1
set ucmd [ta_word]
set procname ucmd/[string tolower $ucmd]
if {[catch { info body $procname }]} {
- error "unknown command; try help for help"
+ usererror "Unknown command; try help for help."
}
$procname $p $dest
} rv]} {
- sendprivmsg $n "error: $rv"
+ if {"$errorCode" != "BLIGHT USER"} { set rv "error: $rv" }
+ sendprivmsg $n $rv
} else {
manyset $rv priv_msgs pub_msgs priv_acts pub_acts
foreach {td val} [list $n $priv_acts $output $pub_acts] {
file delete $idfn
}
-set default_settings_nick {timeformat ks}
+set default_settings_nick {timeformat ks marktime off}
set default_settings_chan {
autojoin 1
mode *
proc check_notonchan {} {
upvar 1 dest dest
- if {[ischan $dest]} { error "that command must be sent privately" }
+ if {[ischan $dest]} { usererror "That command must be sent privately." }
}
proc nick_securitycheck {strict} {
upvar 1 n n
- if {![nickdb_exists $n]} { error "you are unknown to me, use `register'." }
+ if {![nickdb_exists $n]} {
+ usererror "You are unknown to me, use `register'."
+ }
set wantu [nickdb_get $n username]
if {![string length $wantu]} {
if {$strict} {
- error "that feature is only available to secure users, sorry."
+ usererror "That feature is only available to secure users, sorry."
} else {
return
}
set luser [irctolower $n]
upvar #0 nick_username($luser) nu
if {![info exists nu]} {
- error "nick $n is secure, you must identify yourself first."
+ usererror "Nick $n is secure, you must identify yourself first."
}
if {"$wantu" != "$nu"} {
- error "you are the wrong user - the nick $n belongs to $wantu, not $nu"
+ usererror "You are the wrong user -\
+ the nick $n belongs to $wantu, not $nu."
}
}
-proc channel_securitycheck {channel n} {
- # You must also call `nick_securitycheck 1'
+proc channel_ismanager {channel n} {
set mgrs [chandb_get $channel managers]
- if {[lsearch -exact [irctolower $mgrs] [irctolower $n]] < 0} {
- error "you are not a manager of $channel"
+ return [expr {[lsearch -exact [irctolower $mgrs] [irctolower $n]] >= 0}]
+}
+
+proc channel_securitycheck {channel} {
+ upvar n n
+ if {![channel_ismanager $channel $n]} {
+ usererror "You are not a manager of $channel."
}
+ nick_securitycheck 1
}
proc def_chancmd {name body} {
ucmdr "Topic $what list for $chan: $ta_listop_ev" {}
}
default {
- error "unknown channel topic subcommand - see help channel"
+ usererror "Unknown channel topic subcommand - see help channel."
}
}
}
upvar 1 target target
global chan_nicks
+ prefix_nick
+
if {[ischan $dest]} { set target $dest }
if {[ta_anymore]} { set target [ta_word] }
ta_nomore
if {![info exists target]} {
- error "you must specify, or invoke me on, the relevant channel"
+ usererror "You must specify, or invoke me on, the relevant channel."
}
if {![info exists chan_nicks([irctolower $target])]} {
- error "I am not on $target."
+ usererror "I am not on $target."
}
if {![ischan $target]} { error "not a valid channel" }
- if {![chandb_exists $target]} { error "$target is not a managed channel." }
- prefix_nick
- nick_securitycheck 1
- channel_securitycheck $target $n
+
+ if {![chandb_exists $target]} {
+ usererror "$target is not a managed channel."
+ }
+ channel_securitycheck $target
}
def_ucmd op {
}
def_ucmd invite {
- global chan_nicks
+ global chan_nicks errorCode errorInfo
+ prefix_nick
if {[ischan $dest]} {
set target $dest
set onchan 0
}
set ltarget [irctolower $target]
- if {![ischan $target]} { error "$target is not a channel." }
- if {![info exists chan_nicks($ltarget)]} { error "I am not on $target." }
- set ui [chandb_get $ltarget userinvite]
- if {"$ui" == "pub" && !$onchan} {
- error "Invitations to $target must be made with !invite."
+ if {![ischan $target]} { error "$target is not a channel" }
+ if {![info exists chan_nicks($ltarget)]} {
+ usererror "I am not on $target."
}
- if {"$ui" != "all"} {
- prefix_nick
- if {[lsearch -exact $chan_nicks($ltarget) [irctolower $n]] < 0} {
- error "Invitations to $target may only be made by a user on the channel."
+ set ui [chandb_get $ltarget userinvite]
+ if {[catch {
+ if {"$ui" == "pub" && !$onchan} {
+ usererror "Invitations to $target must be made there with !invite."
+ }
+ if {"$ui" != "all"} {
+ if {[lsearch -exact $chan_nicks($ltarget) [irctolower $n]] < 0} {
+ usererror "Invitations to $target may only be made\
+ by a user on the channel."
+ }
+ }
+ if {"$ui" == "none"} {
+ usererror "Sorry, I've not been authorised\
+ to invite people to $target."
+ }
+ } emsg]} {
+ if {"$errorCode" == "BLIGHT USER" && [channel_ismanager $target $n]} {
+ if {[catch {
+ nick_securitycheck 1
+ } emsg2]} {
+ if {"$errorCode" == "BLIGHT USER"} {
+ usererror "$emsg2 Therefore you can't use your\
+ channel manager privilege. $emsg"
+ } else {
+ error $error $errorInfo $errorCode
+ }
+ }
+ } else {
+ error $emsg $errorInfo $errorCode
}
- }
- if {"$ui" == "none"} {
- error "Sorry, I've not been authorised to invite people to $target."
}
if {![ta_anymore]} {
- error "You have to say who to invite."
+ usererror "You have to say who to invite."
}
set invitees {}
while {[ta_anymore]} {
append who " and [lindex $invitees [llength $invitees]]"
}
}
- ucmdr {} "invites $who to $target."
+ ucmdr {} {} {} "invites $who to $target."
}
def_ucmd channel {
if {![info exists target]} { error "privately, you must specify a channel" }
set procname channel/$subcmd
if {"$subcmd" != "show"} {
- if {[catch { info body $procname }]} { error "unknown channel setting $subcmd" }
+ if {[catch { info body $procname }]} {
+ usererror "unknown channel setting $subcmd."
+ }
prefix_nick
- nick_securitycheck 1
if {[chandb_exists $target]} {
- channel_securitycheck $target $n
+ channel_securitycheck $target
} else {
+ nick_securitycheck 1
upvar #0 chan_initialop([irctolower $target]) io
upvar #0 nick_unique([irctolower $n]) u
- if {![info exists io]} { error "$target is not a managed channel" }
- if {"$io" != "$u"} { error "you are not the interim manager of $target" }
- if {"$subcmd" != "manager"} { error "use `channel manager' first" }
+ if {![info exists io]} {
+ usererror "$target is not a managed channel."
+ }
+ if {"$io" != "$u"} {
+ usererror "You are not the interim manager of $target."
+ }
+ if {"$subcmd" != "manager"} {
+ usererror "Please use `channel manager' first."
+ }
}
}
channel/$subcmd
ucmdr {} $desc
}
+proc marktime_desc {mt} {
+ if {"$mt" == "off"} {
+ return "I will not send you periodic messages."
+ } elseif {"$mt" == "once"} {
+ return "I will send you one informational message when I see you."
+ } else {
+ return "I'll send you a message every [showintervalsecs $mt 0]."
+ }
+}
+
+def_setting marktime {
+ set mt [nickdb_get $n marktime]
+ set p $mt
+ if {[string match {[0-9]*} $mt]} { append p s }
+ append p ": "
+ append p [marktime_desc $mt]
+ return $p
+} {
+ global marktime_min
+ set mt [string tolower [ta_word]]
+ ta_nomore
+
+ if {"$mt" == "off" || "$mt" == "once"} {
+ } elseif {[regexp {^([0-9]+)([a-z]+)$} $mt dummy value unit]} {
+ switch -exact $unit {
+ s { set u 1 }
+ ks { set u 1000 }
+ m { set u 60 }
+ h { set u 3600 }
+ default { error "unknown unit of time $unit" }
+ }
+ if {$value > 86400*21/$u} { error "marktime interval too large" }
+ set mt [expr {$value*$u}]
+ if {$mt < $marktime_min} { error "marktime interval too small" }
+ } else {
+ error "invalid syntax for marktime"
+ }
+ nickdb_set $n marktime $mt
+ lnick_marktime_start [irctolower $n] "So:" 500
+ ucmdr {} [marktime_desc $mt]
+}
+
def_setting security {
set s [nickdb_get $n username]
if {[string length $s]} {
set idletime [expr {$now - $idlesince}]
set ls $now
ucmdr {} {} {} "invites $target ($tty[expr {
- $idletime > 10 ? ", idle for [showintervalsecs $idletime]" : ""
+ $idletime > 10 ? ", idle for [showintervalsecs $idletime 0]" : ""
}]) to [expr {
[ischan $dest] ? "join us here" : "talk to you"
}]."
ta_nomore
set now [clock seconds]
if {"$nlower" == "[irctolower $nick]"} {
- error "I am not self-aware."
+ usererror "I am not self-aware."
} elseif {![info exists lastseen($nlower)]} {
set rstr "I've never seen $ncase."
} else {
ucmdr {} $rstr
}
+proc lnick_marktime_cancel {luser} {
+ upvar #0 nick_markid($luser) mi
+ if {![info exists mi]} return
+ catch { after cancel $mi }
+ catch { unset mi }
+}
+
+proc lnick_marktime_doafter {luser why ms} {
+ lnick_marktime_cancel $luser
+ upvar #0 nick_markid($luser) mi
+ set mi [after $ms [list lnick_marktime_now $luser $why]]
+}
+
+proc lnick_marktime_reset {luser} {
+ set mt [nickdb_get $luser marktime]
+ if {"$mt" == "off" || "$mt" == "once"} return
+ lnick_marktime_doafter $luser "Time passes." [expr {$mt*1000}]
+}
+
+proc lnick_marktime_start {luser why ms} {
+ set mt [nickdb_get $luser marktime]
+ if {"$mt" == "off"} {
+ lnick_marktime_cancel $luser
+ } else {
+ lnick_marktime_doafter $luser $why $ms
+ }
+}
+
+proc lnick_marktime_now {luser why} {
+ upvar #0 nick_onchans($luser) oc
+ global calling_nick
+ set calling_nick $luser
+ sendprivmsg $luser [lnick_pingstring $why $oc ""]
+ lnick_marktime_reset $luser
+}
+
+proc lnick_pingstring {why oc apstring} {
+ global nick_onchans
+ catch { exec uptime } uptime
+ set nnicks [llength [array names nick_onchans]]
+ if {[regexp \
+ {^ *([0-9:apm]+) +up.*, +(\d+) users, +load average: +([0-9., ]+) *$} \
+ $uptime dummy time users load]} {
+ regsub -all , $load {} load
+ set uptime "$time $nnicks/$users $load"
+ } else {
+ append uptime ", $nnicks nicks"
+ }
+ if {[llength $oc]} {
+ set best_la 0
+ set activity quiet
+ foreach ch $oc {
+ upvar #0 chan_lastactivity($ch) la
+ if {![info exists la]} continue
+ if {$la <= $best_la} continue
+ set since [showintervalsecs [expr {[clock seconds]-$la}] 1]
+ set activity "$ch $since"
+ set best_la $la
+ }
+ } else {
+ set activity unseen
+ }
+ set str $why
+ append str " " $uptime " " $activity
+ if {[string length $apstring]} { append str " " $apstring }
+ return $str
+}
+
+def_ucmd ping {
+ if {[ischan $dest]} {
+ set oc [irctolower $dest]
+ } else {
+ global nick_onchans
+ prefix_nick
+ set ln [irctolower $n]
+ if {[info exists nick_onchans($ln)]} {
+ set oc $nick_onchans($ln)
+ } else {
+ set oc {}
+ }
+ if {[llength $oc]} { lnick_marktime_reset $ln }
+ }
+ ucmdr {} [lnick_pingstring "Pong!" $oc $text]
+}
+
proc ensure_globalsecret {} {
global globalsecret