X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ijackson/git?a=blobdiff_plain;f=bot.tcl;h=d89e18e76e5263d60b6b8d090b60e8c35037b5b3;hb=8302ed4182baca25b405db08a1e88ecba1dbb966;hp=fc98c2ee35e78e94f274a4353e5147159e1767d9;hpb=f036e61287133948faa8b0076251e866104febbd;p=ircbot.git diff --git a/bot.tcl b/bot.tcl index fc98c2e..d89e18e 100755 --- a/bot.tcl +++ b/bot.tcl @@ -5,6 +5,10 @@ set helpfile helpinfos source irccore.tcl source parsecmd.tcl source stdhelp.tcl +source userv.tcl + +defset marktime_min 300 +defset marktime_join_startdelay 5000 proc privmsg_unlogged {prefix ischan params} { if {!$ischan || @@ -15,7 +19,7 @@ proc privmsg_unlogged {prefix ischan params} { set chan [lindex $params 0] upvar #0 chan_lastactivity([irctolower $chan]) la set la [clock seconds] - catch { recordlastseen_p $prefix "talking on $chan" 1 } + catch_logged { recordlastseen_p $prefix "talking on $chan" 2 } return 1 } @@ -23,22 +27,47 @@ proc showintervalsecs {howlong abbrev} { return [showintervalsecs/[opt timeformat] $howlong $abbrev] } +proc formatsf {pfx value} { + foreach {min format} { 100 %.0f 10 %.1f 0 %.2f} { + set fval [format $format $value] + if {$fval < $min} continue + return [format "$fval${pfx}" $value] + } +} + +proc showintervalsecs/beat {howlong abbrev} { + # We split in two to avoid overflow problems. + if {$howlong < 86 } { + # mB's + set pfx mB + return [format "%.0fmB" [expr {round($howlong * 1.157)*10} ]] + } else { + if {$howlong < 86400 } { + # B's + set pfx B + set value [expr {$howlong / 86.4}] + } else { + # kB's + set pfx kB + set value [expr {$howlong / 86400.0}] + } + } + return [formatsf $pfx $value] +} + proc showintervalsecs/ks {howlong abbrev} { if {$howlong < 1000} { return "${howlong}s" } else { if {$howlong < 1000000} { - set pfx k + set pfx ks set scale 1000 } else { - set pfx M + set pfx Ms set scale 1000000 } set value [expr "$howlong.0 / $scale"] - foreach {min format} {100 %.0f 10 %.1f 1 %.2f} { - if {$value < $min} continue - return [format "$format${pfx}s" $value] - } + return [formatsf $pfx $value] } } @@ -83,6 +112,26 @@ proc showtime {when} { return [showinterval [expr {[clock seconds] - $when}]] } +proc parse_interval {specified min} { + if {![regexp {^([0-9]+)([a-z]+)$} $specified dummy value unit]} { + error "invalid syntax for interval" + } + switch -exact $unit { + s { set u 1 } + ks { set u 1000 } + m { set u 60 } + h { set u 3600 } + mb { set u 0.0864 } + b { set u 86.4 } + kb { set u 86400 } + default { error "unknown unit of time $unit" } + } + if {$value > 86400*21/$u} { error "interval too large" } + set result [expr {round($value*$u)}] + if {$result < $min} { error "interval too small (<${min}s)" } + return $result +} + proc def_msgproc {name argl body} { proc msg_$name "varbase $argl" "\ upvar #0 msg/\$varbase/dest d\n\ @@ -125,11 +174,249 @@ proc looking_whenwhere {when where} { return $str } +proc tell_getcstate {} { + # uses nl from caller's context + # imports telling (as the nick_telling) and u + # sets stt, telling_when + uplevel 1 { + upvar #0 nick_telling($nl) telling + upvar #0 nick_unique($nl) u + + if {[info exists telling]} { + manyset $telling u_last stt telling_when + if {![info exists u] || "$u_last" != "$u"} { + set stt undelivered + } + } else { + set stt undelivered + set telling_when 0 + } + } +} + +proc tell_event {nl event} { + # For `act' we *haven't* yet done the 750ms delay; we implement + # that here. Also, here we turn `talk' into `talk' now and `act' + # later. We also support the psuedo-event `none'. The del msg + # and new msg events are handled by the command procedures, not here. + global calling_nick + if {[info exists calling_nick]} { set save $calling_nick } + set r DELAYED + switch -exact $event { + none { } + talk { + tell_event_core $nl talk + tell_event $nl act + } + act { + after 750 [list tell_event_core $nl $event] + } + ident - msgsarrive { + tell_event_core $nl $event + } + tellme { + set r [tell_event_core $nl TELLME] + } + default { + error "tell_event $nl $event" + } + } + if {[info exists save]} { set calling_nick $save } + return $r +} + +proc tell_event_core {nl event} { + global tell_event_teventi errorInfo + set tell_event_teventi "*$event* $nl" + if {[catch { + tell_event_core1 $nl $event + } emsg]} { + log_intern "tell event error" "$emsg >$errorInfo<" + set emsg ERROR + } else { + if {"$emsg" != "nomsgs"} { + log_intern "tell event" "done $tell_event_teventi $emsg" + } + } + return $emsg +} + +proc tell_event_core1 {nl event} { + # event is `talk', `act', `ident', `msgsarrive' or `TELLME' + # When user talks we actually get talk now and act later + global calling_nick + set calling_nick $nl + set iml [msgsdb_get $nl inbound] + if {![llength $iml]} { return nomsgs } + + set now [clock seconds] + tell_getcstate + set ago [expr {$now - $telling_when}] + + # Now we have the components of a telling state + # u - nick_unique (unset if not visible) + # stt - state: undelivered, mentioned, passede + # ago - how long ago since we did anything + + # We compute an evstate to dispatch on as follows: + + # evstate is string of letters + # current state + # u UNDELIVERED (MESSAGES) + # m MENTIONED + # p PASSED + # event + # t talk + # a act + # i ident + # m msgsarrive + # T tellme (user command) + # security level and timing + # ii Insecure + # ss Secure and soon (before interval) + # sl Secure and late (after interval) + # current identification + # i Identified + # u Unidentified + # reliability and timing + # uu Unreliable + # rv Remind, very soon (before within-interval) + # rs Remind, soon (between) + # rl Remind, late (after every-interval) + # ps Pester, soon (before interval) + # pl Pester, late (after interval) + + set evstate {} + + append evstate [string range $stt 0 0] + append evstate [string range $event 0 0] + + manyset [nickdb_get_sec_effective $nl] sec secwhen + switch -exact $sec { + insecure { append evstate ii } + secure { append evstate [expr {$ago<$secwhen ? "ss" : "sl"}] } + default { append evstate "#$sec#" } + } + + upvar #0 nick_username($nl) nu + if {[info exists nu] && ![string compare $nu [nickdb_get_username $nl]]} { + append evstate i + } else { + append evstate u + } + + manyset [nickdb_get $nl tellrel] rel relint relwithin + switch -exact $rel { + unreliable { append evstate uu } + remind { append evstate [expr { + $ago<$relwithin ? "rv" : $ago<$relint ? "rs" : "rl" + }]} + pester { append evstate [expr {$ago<$relint ? "ps" : "pl"}] } + default { append evstate "#$rel#" } + } + + global tell_event_teventi + set tell_event_teventi "$evstate $ago $nl" + switch -glob $evstate { + pt???rv { + # consider delivered: + # (very recently passed, and the user talks) + set ndel [tell_delete_msgs {} $nl] + sendprivmsg $nl \ + "I'm assuming you got the $ndel message(s) I just passed on." + return delivered + } + pm????? { + # oops, messages passed are now out of date + catch_restoreei { unset telling } + return reset + } + ?m????? { + # ignore new msgs if we haven't passed yet + return ignorenew + } + ut????? - mt????? - + pt???uu - pt???rs - pt???rl - pt???p? { + # ignore (any other `talk's) - act handles these + return ignoretalk + } + ui????? - + uaii?uu - uaii??l - uas?iuu - uas?i?l - + mi????? - pa????l - + ?Tii??? - ?Ts?i?? { + # pass and then stuff + if {[llength $iml] == 3} { + manyset $iml sender sentwhen msg + sendprivmsg $nl \ + "$sender asked me, [showinterval [expr {$now-$sentwhen}]],\ + to tell you: $msg" + } else { + sendprivmsg $nl \ + "I have [expr {[llength $iml]/3}] messages for you:" + while {[llength $iml] >= 3} { + manyset [lrange $iml 0 2] sender sentwhen msg + set iml [lrange $iml 3 end] + sendprivmsg $nl \ + " [showintervalsecs [expr {$now-$sentwhen}] 1] <$sender> $msg" + } + } + if {![string compare $rel "unreliable"]} { + tell_delete_msgs {} $nl + return toldunreliable + } + set stt passed + set re passed + } + uaslu?? { + sendprivmsg $nl {You have messages (so identify yourself please).} + set stt mentioned + set re mentioned + } + ?Ts?u?? { + sendprivmsg $nl {You must identify yourself to see your messages.} + return ignoreuitm + } + masl??? { + sendprivmsg $nl {Don't forget about your messages.} + set re remind + } + pi????? { + return ignorepi + } + mass??? - pa????v - pa????s - + uaii??v - uaii??s - + uas?i?v - uas?i?s - + uassu?? { + # too soon + return ignoresoon + } + * { + error "tell_event_core nl=$nl evstate=$evstate ?" + } + } + if {![info exists u]} { + set telling [list {} undelivered $now] + } else { + set telling [list $u $stt $now] + } + return $re +} + proc recordlastseen_n {n how here} { + # here is: + # 0 - nick was seen leaving (or changing to another nicks or some such) + # 1 - nick was seen doing something else + # 2 - nick was seen talking on channel global lastseen lookedfor - set lastseen([irctolower $n]) [list $n [clock seconds] $how] + set nl [irctolower $n] + set now [clock seconds] + set lastseen($nl) [list $n $now $how] + if {!$here} return - upvar #0 lookedfor([irctolower $n]) lf + + tell_event $nl [lindex {none act talk} $here] + + upvar #0 lookedfor($nl) lf if {[info exists lf]} { switch -exact [llength $lf] { 0 { @@ -207,7 +494,7 @@ proc chanmode_o1 {m g p chan} { prefix_nick set who [chanmode_arg] recordlastseen_n $n "being nice to $who" 1 - if {"[irctolower $who]" == "[irctolower $nick]"} { + if {![ircnick_compare $who $nick]} { set nlower [irctolower $n] upvar #0 nick_unique($nlower) u if {[chandb_exists $chan]} { @@ -217,8 +504,8 @@ proc chanmode_o1 {m g p chan} { } else { set chan_initialop([irctolower $chan]) $u sendprivmsg $n \ - "Thanks. You can use `channel manager ...' to register this channel." - if {![nickdb_exists $n] || ![string length [nickdb_get $n username]]} { + "Thanks. You can use `channel manager ...' to register this channel." + if {![string length [nickdb_get_username $n username]]} { sendprivmsg $n \ "(But to do that you must register your nick securely first.)" } @@ -231,7 +518,7 @@ proc chanmode_o0 {m g p chan} { prefix_nick set who [chanmode_arg] recordlastseen_p $p "being mean to $who" 1 - if {"[irctolower $who]" == "[irctolower $nick]"} { + if {![ircnick_compare $who $nick]} { set chandeop($chan) [list [clock seconds] $p] } } @@ -256,6 +543,7 @@ proc msg_MODE {p c dest modelist args} { } proc leaving {lchan} { + global nick_onchans foreach luser [array names nick_onchans] { upvar #0 nick_onchans($luser) oc set oc [grep tc {"$tc" != "$lchan"} $oc] @@ -302,7 +590,7 @@ proc process_kickpart {chan user} { set luser [irctolower $user] set lchan [irctolower $chan] if {![ischan $chan]} { error "not a channel" } - if {"$luser" == "[irctolower $nick]"} { + if {![ircnick_compare $luser $nick]} { leaving $lchan } else { upvar #0 nick_onchans($luser) oc @@ -343,9 +631,10 @@ set nick_counter 0 set nick_arys {onchans username unique} # nick_onchans($luser) -> [list ... $lchan ...] # nick_username($luser) -> -# nick_unique($luser) -> +# nick_unique($luser) -> # nick_case($luser) -> $user (valid even if no longer visible) # nick_markid($luser) -> +# nick_telling($luser) -> mentioned|passed # chan_nicks($lchan) -> [list ... $luser ...] # chan_lastactivity($lchan) -> [clock seconds] @@ -376,9 +665,10 @@ proc nick_case {user} { } proc msg_NICK {p c newnick} { - global nick_arys nick_case + global nick_arys nick_case calling_nick prefix_nick recordlastseen_n $n "changing nicks to $newnick" 0 + set calling_nick $newnick recordlastseen_n $newnick "changing nicks from $n" 1 set luser [irctolower $n] lnick_marktime_cancel $luser @@ -395,7 +685,7 @@ proc msg_NICK {p c newnick} { set nlist [grep tn {"$tn" != "$luser"} $nlist] lappend nlist $lusernew } - lnick_marktime_start $lusernew "Hi." 500 + lnick_marktime_start $lusernew "Hi." 500 1 nick_case $newnick } @@ -408,6 +698,7 @@ proc nick_ishere {n} { proc msg_JOIN {p c chan} { prefix_nick + nick_ishere $n recordlastseen_n $n "joining $chan" 1 set nl [irctolower $n] set lchan [irctolower $chan] @@ -415,15 +706,19 @@ proc msg_JOIN {p c chan} { upvar #0 chan_nicks($lchan) nlist if {![info exists oc]} { global marktime_join_startdelay - lnick_marktime_start $nl "Welcome." $marktime_join_startdelay + lnick_marktime_start $nl "Welcome." $marktime_join_startdelay 1 } lappend oc $lchan lappend nlist $nl - nick_ishere $n } -proc msg_PART {p c chan} { +proc msg_PART {p c chan args} { prefix_nick - recordlastseen_n $n "leaving $chan" 1 + set msg "leaving $chan" + if {[llength $args]} { + set why [lindex $args 0] + if {"[irctolower $why]" != "[irctolower $n]"} { append msg " ($why)" } + } + recordlastseen_n $n $msg 1 process_kickpart $chan $n } proc msg_QUIT {p c why} { @@ -495,19 +790,11 @@ proc msg_366 {p c args} { if {[llength names_chans] > 1} { set oc [grep tc {[lsearch -exact $tc $names_chans] >= 0} $oc] } - if {![llength $oc]} { lnick_forget $n } + if {![llength $oc]} { lnick_forget $luser } } unset names_chans } -proc check_username {target} { - if { - [string length $target] > 8 || - [regexp {[^-0-9a-z]} $target] || - ![regexp {^[a-z]} $target] - } { error "invalid username" } -} - proc somedb__head {} { uplevel 1 { set idl [irctolower $id] @@ -524,7 +811,11 @@ proc somedb__head {} { } proc def_somedb {name arglist body} { - foreach {nickchan fprefix} {nick users/n chan chans/c} { + foreach {nickchan fprefix} { + nick users/n + chan chans/c + msgs users/m + } { proc ${nickchan}db_$name $arglist \ "set nickchan $nickchan; set fprefix $fprefix; $body" } @@ -553,7 +844,13 @@ def_somedb_id delete {} { file delete $idfn } -set default_settings_nick {timeformat ks marktime off} +set default_settings_nick { + timeformat ks + marktime off + tellsec {secure 600} + tellrel {remind 3600 30} +} + set default_settings_chan { autojoin 1 mode * @@ -563,6 +860,15 @@ set default_settings_chan { topictell {} } +set default_settings_msgs { + inbound {} + outbound {} +} +# inbound -> [ ] ... +# outbound -> [ ] ... +# neither are sorted particularly; only one entry per recipient in +# output; both sender and recipient are cased + def_somedb_id set {args} { upvar #0 default_settings_$nickchan def if {![info exists iddbe]} { set iddbe $def } @@ -589,7 +895,7 @@ def_somedb_id get {key} { set l $def } foreach {tkey value} $l { - if {"$tkey" == "$key"} { return $value } + if {![string compare $tkey $key]} { return $value } } error "unset setting $key" } @@ -768,7 +1074,7 @@ def_chancmd mode { error {channel mode must be * or match ([-+][imnpst]+)+} } chandb_set $chan mode $mode - if {"$mode" == "*"} { + if {![string compare $mode "*"]} { ucmdr "I won't ever change the mode of $chan." {} } else { ucmdr "Whenever I'm alone on $chan, I'll set the mode to $mode." {} @@ -835,11 +1141,13 @@ proc channelmgr_monoop {} { def_ucmd op { channelmgr_monoop sendout MODE $target +o $n + ucmdr {} {} } def_ucmd leave { channelmgr_monoop doleave $target + ucmdr {} {} } def_ucmd invite { @@ -860,7 +1168,7 @@ def_ucmd invite { } set ui [chandb_get $ltarget userinvite] if {[catch { - if {"$ui" == "pub" && !$onchan} { + if {![string compare $ui "pub"] && !$onchan} { usererror "Invitations to $target must be made there with !invite." } if {"$ui" != "all"} { @@ -869,16 +1177,17 @@ def_ucmd invite { by a user on the channel." } } - if {"$ui" == "none"} { + if {[!string compare $ui "none"]} { usererror "Sorry, I've not been authorised\ to invite people to $target." } } emsg]} { - if {"$errorCode" == "BLIGHT USER" && [channel_ismanager $target $n]} { + if {![string compare $errorCode "BLIGHT USER"] && + [channel_ismanager $target $n]} { if {[catch { nick_securitycheck 1 } emsg2]} { - if {"$errorCode" == "BLIGHT USER"} { + if {![string compare $errorCode "BLIGHT USER"]} { usererror "$emsg2 Therefore you can't use your\ channel manager privilege. $emsg" } else { @@ -894,8 +1203,7 @@ def_ucmd invite { } set invitees {} while {[ta_anymore]} { - set invitee [ta_word] - check_nick $invitee + set invitee [ta_nick] lappend invitees $invitee } foreach invitee $invitees { @@ -956,9 +1264,238 @@ def_ucmd channel { channel/$subcmd } +proc nickdb_get_username {n} { + if {![nickdb_exists $n]} { return "" } + return [nickdb_get $n username] +} + +proc nickdb_get_sec_effective {n} { + set l [nickdb_get $n tellsec] + set u [nickdb_get_username $n] + if {![string compare [lindex $l 0] "secure"] && + ![string length $u]} { set l insecure } + return $l +} + +proc tell_peernicks {text} { + global errorInfo errorCode + set text [irctolower [string trim $text]] + set senders [split $text " "] + foreach sender $senders { + if {[catch { check_nick $sender } emsg]} { + error "invalid nick `$sender': $emsg" $errorInfo $errorCode + } + } + return $senders +} + +proc msgsdb_set_maydelete {n key l otherkey} { + msgsdb_set $n $key $l + if {[llength $l]} return + if {[llength [msgsdb_get $n $otherkey]]} return + msgsdb_delete $n +} + +proc tell_delete_msgs {lsenders lrecip} { + set new_inbound {} + set ndel 0 + foreach {s t m} [msgsdb_get $lrecip inbound] { + if {[llength $lsenders]} { + if {[lsearch -exact $lsenders [irctolower $s]] == -1} { + lappend new_inbound $s $t $m + continue + } + } + set rsenders($s) 1 + incr ndel + } + msgsdb_set_maydelete $lrecip inbound $new_inbound outbound + if {![llength $new_inbound]} { + upvar #0 nick_telling($lrecip) telling + catch { unset telling } + } + foreach s [array names rsenders] { + set new_outbound {} + foreach {r t c} [msgsdb_get $s outbound] { + if {![ircnick_compare $r $lrecip]} continue + lappend new_outbound $r $t $c + } + msgsdb_set_maydelete $s outbound $new_outbound inbound + } + return $ndel +} + +def_ucmd untell { + prefix_nick + check_notonchan + if {[nickdb_exists $n]} { nick_securitycheck 0 } + set recipients [tell_peernicks $text] + if {![llength $recipients]} { + usererror "You must say which recipients' messages from you to forget." + } + set ndel 0 + foreach recip $recipients { + incr ndel [tell_delete_msgs [irctolower $n] $recip] + } + ucmdr "Removed $ndel as yet undelivered message(s)." {} +} + +def_ucmd_alias delmsgs delmsg +def_ucmd delmsg { + global errorInfo errorCode + prefix_nick + set nl [irctolower $n] + check_notonchan + manyset [nickdb_get_sec_effective $n] sec secwhen + switch -exact $sec { + insecure { } + reject - mailto { + usererror \ + "There are no messages to delete\ + because your message disposition prevents them from being left." + } + secure { + nick_securitycheck 1 + } + default { + error "delmsg sec $sec" + } + } + if {![llength [msgsdb_get $n inbound]]} { + ucmdr "No incoming messages to delete." {} + } + tell_getcstate + if {![info exists u]} { + usererror \ + "I can't delete your messages unless I can see you on a channel with me.\ + Otherwise I might delete a message I hadn't told you about yet." + } + if {"$stt" != "passed"} { + set telling [list $u undelivered 0] + usererror \ + "There are message(s) you may not yet have seen;\ + I'll deliver them to you now.\ + If you actually want to delete them, just tell me `delmsg' again." + } + set senders [tell_peernicks $text] + set ndel [tell_delete_msgs [irctolower $senders] [irctolower $n]] + if {!$ndel} { + if {[llength $senders]} { + ucmdr "No relevant incoming messages to delete." {} + } + } + switch -exact [llength $senders] { + 0 { ucmdr {} {} "deletes your $ndel message(s)." } + 1 { ucmdr {} {} "deletes your $ndel message(s) from $senders." } + default { + ucmdr {} {} "deletes your $ndel message(s) from\ + [lreplace $senders end end] and/or [lindex $senders end]." + } + } +} + +def_ucmd tellme { + prefix_nick + ta_nomore + check_notonchan + manyset [nickdb_get $n tellsec] sec + switch -exact $sec { + reject { ucmdr "But, you asked me to reject messages for you !" {} } + mailto { ucmdr "But, you asked me to mail your messages to you !" {} } + } + switch -exact [tell_event [irctolower $n] tellme] { + ERROR - INVALID { ucmdr {} {is ill. Help!} } + nomsgs { ucmdr {You have no messages.} {} } + default { } + } +} + +def_ucmd tell { + global nick_case ownmailaddr ownfullname + + prefix_nick + set target [ta_nick] + if {![string length $text]} { error "tell them what?" } + if {[string length $text] > 400} { error "message too long" } + + set ltarget [irctolower $target] + set ctarget $target + if {[info exists nick_case($ltarget)]} { set ctarget $nick_case($ltarget) } + + manyset [nickdb_get_sec_effective $target] sec mailtoint mailwhy + manyset [nickdb_get $target tellrel] rel relint relwithin + switch -exact $sec { + insecure - secure { + set now [clock seconds] + set inbound [msgsdb_get $ltarget inbound] + lappend inbound $n $now $text + msgsdb_set $ltarget inbound $inbound + + set outbound [msgsdb_get $n outbound] + set noutbound {} + set found 0 + foreach {recip time count} $outbound { + if {![ircnick_compare $recip $ltarget]} { + incr count + set recip $ctarget + set found 1 + } + lappend noutbound $recip $time $count + } + if {!$found} { + lappend noutbound $ctarget $now 1 + } + msgsdb_set $n outbound $noutbound + set msg "OK, I'll tell $ctarget" + if {$found} { append msg " that too" } + append msg ", " + if {"$sec" != "secure"} { + switch -exact $rel { + unreliable { append msg "neither reliably nor securely" } + remind { append msg "pretty reliably, but not securely" } + pester { append msg "reliably but not securely" } + } + } else { + switch -exact $rel { + unreliable { append msg "securely but not reliably" } + remind { append msg "securely and pretty reliably" } + pester { append msg "reliably and securely" } + } + } + append msg . + tell_event $ltarget msgsarrive + ucmdr $msg {} + } + mailto { + set fmtmsg [exec fmt << " $text"] + exec /usr/sbin/sendmail -odb -oi -t -oee -f $mailwhy \ + > /dev/null << \ + "From: $ownmailaddr ($ownfullname) +To: $mailtoint +Subject: IRC tell from $n + +$n asked me[expr {[ischan $dest] ? " on $dest" : ""}] to tell you: +[exec fmt << " $text"] + +(This message was for your nick $ctarget; your account $mailwhy + arranged for it to be forwarded to $mailtoint.) +" + ucmdr \ + "I've mailed $ctarget, which is what they prefer." \ + {} + } + reject { + usererror "Sorry, $ctarget does not want me to take messages." + } + default { + error "bad tellsec $sec" + } + } +} + def_ucmd who { if {[ta_anymore]} { - set target [ta_word]; ta_nomore + set target [ta_nick]; ta_nomore set myself 1 } else { prefix_nick @@ -1026,6 +1563,9 @@ def_ucmd register { ucmdr {} "This is fine, but bear in mind that people will be able to mess with your settings. Channel management features need a secure registration." "makes an insecure registration for your nick." } } + default { + error "you mean register / register delete / register insecure" + } } } @@ -1033,11 +1573,15 @@ proc timeformat_desc {tf} { switch -exact $tf { ks { return "Times will be displayed in seconds or kiloseconds." } hms { return "Times will be displayed in hours, minutes, etc." } + beat { return "Times will be displayed in beats (1000B = 1d)." } default { error "invalid timeformat: $v" } } } +set settings {} proc def_setting {opt show_body set_body} { + global settings + lappend settings $opt proc set_show/$opt {} " upvar 1 n n set opt $opt @@ -1050,6 +1594,56 @@ proc def_setting {opt show_body set_body} { $set_body" } +proc tellme_sec_desc {v n} { + manyset $v sec mailtoint + switch -exact $sec { + insecure { + return "I'll tell you your messages whenever I see you." + } + secure { + if {[string length [nickdb_get_username $n]]} { + return \ + "I'll keep the bodies of your messages private until you identify yourself, reminding you every [showintervalsecs $mailtoint 1]." + } else { + return \ + "I'll tell you your messages whenever I see you.\ + (Secure message delivery is enabled, but your nick is not registered\ + securely. See `help register'.)" + } + } + reject { + return "I shan't accept messages for you." + } + mailto { + return "I'll forward your messages by email to $mailtoint." + } + default { + error "bad tellsec $sec" + } + } +} + +proc tellme_rel_desc {v n} { + manyset $v rel every within + switch -exact $rel { + unreliable { + return "As soon as I've told you message(s), I'll forget them\ + - note that this means messages can get lost !" + } + pester { + set u {} + } + remind { + set u ", or talk on channel within [showintervalsecs $within 1] of me having told you" + } + default { + error "bad tellrel $rel" + } + } + return "After delivering messages, I'll remind you every\ + [showintervalsecs $every 1] until you say delmsg$u." +} + def_setting timeformat { set tf [nickdb_get $n timeformat] return "$tf: [timeformat_desc $tf]" @@ -1062,12 +1656,16 @@ def_setting timeformat { } 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]." + switch -exact $mt { + off { + return "I will not send you periodic messages." + } + once { + return "I will send you one informational message when I see you." + } + default { + return "I'll send you a message every [showintervalsecs $mt 0]." + } } } @@ -1083,23 +1681,15 @@ def_setting marktime { 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" + switch -exact $mt { + off - once { + } + default { + set mt [parse_interval $mt $marktime_min] + } } nickdb_set $n marktime $mt - lnick_marktime_start [irctolower $n] "So:" 500 + lnick_marktime_start [irctolower $n] "So:" 500 0 ucmdr {} [marktime_desc $mt] } @@ -1112,7 +1702,132 @@ def_setting security { } } {} +proc tellme_setting_sec_simple {} { + uplevel 1 { + ta_nomore + set sr sec + set v $setting + } +} + +proc tellme_setting_neednomsgs {} { + uplevel 1 { + if {[llength [msgsdb_get $n inbound]]} { + usererror "You must delete the incoming messages you have, first." + } + } +} + +def_setting tellme { + set secv [nickdb_get $n tellsec] + set ms [tellme_sec_desc $secv $n] + manyset $secv sec + switch -exact $sec { + insecure - secure { + set mr [tellme_rel_desc [nickdb_get $n tellrel] $n] + return "$ms $mr" + } + reject - mailto { + return $ms + } + } +} { + set setting [string tolower [ta_word]] + set nl [irctolower $n] + switch -exact $setting { + insecure { + tellme_setting_sec_simple + } + secure { + set every [ta_interval_optional 60 600] + ta_nomore + set sr sec + set v [list secure $every] + } + reject { + tellme_setting_neednomsgs + tellme_setting_sec_simple + } + mailto { + tellme_setting_neednomsgs + + upvar #0 nick_username($nl) nu + if {!([info exists nu] && [string length $nu])} { + usererror \ + "Sorry, you must register securely to have your messages mailed\ + (to prevent the use of this feature for spamming). See `help register'." + } + set sr sec + set v [list mailto [ta_word] $nu] + } + unreliable - pester - remind { + manyset [nickdb_get $n tellsec] sec + switch -exact $sec { + reject - mailto { + usererror \ + "Sorry, I shan't change when I'll consider a message delivered, because\ + you've asked me not to keep messages, or to mail them to you.\ + You should say `set tellme secure' or some such, first." + } + } + set sr rel + set v $setting + if {"$setting" != "unreliable"} { + set every [ta_interval_optional 300 3600] + lappend v $every + } + if {![string compare $setting remind]} { + set within [ta_interval_optional 5 30] + if {$within > $every} { + error "remind interval must be at least time to respond" + } + lappend v $within + } + ta_nomore + } + default { + error "invalid tellme setting $setting" + } + } + nickdb_set $n tell$sr $v + upvar #0 nick_telling($nl) telling + catch { unset telling } + ucmdr [tellme_${sr}_desc $v $n] {} +} + +proc lnick_checktold {luser} { + set ml [msgsdb_get $luser outbound] + if {![llength $ml]} return + set is1 [expr {[llength $ml]==3}] + set m1 "FYI, I haven't yet delivered your" + set ol {} + set now [clock seconds] + while {[llength $ml]} { + manyset $ml r t n + set ml [lreplace $ml 0 2] + set td [expr {$now-$t}] + if {$n == 1} { + set iv [showinterval $td] + set ifo "$r, $iv" + set if1 "message to $r, $iv." + } else { + set iv [showintervalsecs $td 0] + set ifo "$r, $n messages, oldest $iv" + set if1 "$n messages to $r, oldest $iv." + } + if {$is1} { + sendprivmsg $luser "$m1 $if1" + return + } else { + lappend ol " to $ifo[expr {[llength $ml] ? ";" : "."}]" + } + } + sendprivmsg $luser "$m1 messages:" + msendprivmsg $luser $ol +} + def_ucmd set { + global settings prefix_nick check_notonchan if {![nickdb_exists $n]} { @@ -1120,8 +1835,7 @@ def_ucmd set { } if {![ta_anymore]} { set ol {} - foreach proc [lsort [info procs]] { - if {![regexp {^set_show/(.*)$} $proc dummy opt]} continue + foreach opt $settings { lappend ol [format "%-10s %s" $opt [set_show/$opt]] } ucmdr {} [join $ol "\n"] @@ -1131,7 +1845,7 @@ def_ucmd set { error "no setting $opt" } if {![ta_anymore]} { - ucmdr {} "$opt [set_show/$opt]" + ucmdr {} "$opt: [set_show/$opt]" } else { nick_securitycheck 0 if {[catch { info body set_set/$opt }]} { @@ -1143,12 +1857,12 @@ def_ucmd set { } def_ucmd identpass { - set username [ta_word] - set passmd5 [md5sum "[ta_word]\n"] - ta_nomore prefix_nick check_notonchan set luser [irctolower $n] + set username [ta_word] + set passmd5 [md5sum "[ta_word]\n"] + ta_nomore upvar #0 nick_onchans($luser) onchans if {![info exists onchans] || ![llength $onchans]} { ucmdr "You must be on a channel with me to identify yourself." {} @@ -1158,12 +1872,32 @@ def_ucmd identpass { irc-identpass $n upvar #0 nick_username($luser) rec_username set rec_username $username + after 50 [list tell_event $luser ident] ucmdr "Pleased to see you, $username." {} } +def_ucmd kill { + global nick + prefix_nick + set target [ta_nick] + if {![nickdb_exists $target]} { error "$target is not a registered nick." } + set wantu [nickdb_get $target username] + if {![string length $wantu]} { error "$target is insecurely registred." } + upvar #0 nick_username([irctolower $n]) nu + if {![info exists nu]} { error "You must identify yourself first." } + if {"$wantu" != "$nu"} { + error "You are the wrong user, $nu - $target belongs to $wantu." + } + set reason "at request of user $nu" + if {[ta_anymore]} { append reason "; $text" } + sendout KILL $target $reason + ucmdr {} {} +} + def_ucmd summon { set target [ta_word] ta_nomore + # fixme would be nice if the rest of the text was passed on instead check_username $target prefix_nick @@ -1209,7 +1943,7 @@ def_ucmd seen { set nlower [irctolower $ncase] ta_nomore set now [clock seconds] - if {"$nlower" == "[irctolower $nick]"} { + if {![ircnick_compare $nlower $nick]} { usererror "I am not self-aware." } elseif {![info exists lastseen($nlower)]} { set rstr "I've never seen $ncase." @@ -1228,7 +1962,7 @@ def_ucmd seen { if {[info exists lf]} { set oldvalue $lf } else { set oldvalue {} } set lf [list [list $now $n $where]] foreach v $oldvalue { - if {"[irctolower [lindex $v 1]]" == "[irctolower $n]"} continue + if {![ircnick_compare [lindex $v 1] $n]} continue lappend lf $v } ucmdr {} $rstr @@ -1241,32 +1975,41 @@ proc lnick_marktime_cancel {luser} { catch { unset mi } } -proc lnick_marktime_doafter {luser why ms} { +proc lnick_marktime_doafter {luser why ms mentiontold} { lnick_marktime_cancel $luser upvar #0 nick_markid($luser) mi - set mi [after $ms [list lnick_marktime_now $luser $why]] + set mi [after $ms [list lnick_marktime_now $luser $why 0]] } 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}] + switch -exact $mt { + off - once { } + default { + lnick_marktime_doafter $luser "Time passes." [expr {$mt*1000}] 0 + } + } } -proc lnick_marktime_start {luser why ms} { +proc lnick_marktime_start {luser why ms mentiontold} { set mt [nickdb_get $luser marktime] - if {"$mt" == "off"} { - lnick_marktime_cancel $luser - } else { - lnick_marktime_doafter $luser $why $ms + switch -exact $mt { + off { + lnick_marktime_cancel $luser + if {$mentiontold} { after $ms [list lnick_checktold $luser] } + } + default { + lnick_marktime_doafter $luser $why $ms $mentiontold + } } } -proc lnick_marktime_now {luser why} { +proc lnick_marktime_now {luser why mentiontold} { upvar #0 nick_onchans($luser) oc global calling_nick set calling_nick $luser sendprivmsg $luser [lnick_pingstring $why $oc ""] + if {$mentiontold} { after 150 [list lnick_checktold $luser] } lnick_marktime_reset $luser } @@ -1275,7 +2018,7 @@ proc lnick_pingstring {why oc apstring} { catch { exec uptime } uptime set nnicks [llength [array names nick_onchans]] if {[regexp \ - {^ *([0-9:apm]+) +up.*, +(\d+) users, +load average: +([0-9., ]+) *$} \ + {^ *([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" @@ -1303,12 +2046,12 @@ proc lnick_pingstring {why oc apstring} { } def_ucmd ping { + prefix_nick + set ln [irctolower $n] 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 { @@ -1316,6 +2059,7 @@ def_ucmd ping { } if {[llength $oc]} { lnick_marktime_reset $ln } } + after 150 [list lnick_checktold $ln] ucmdr {} [lnick_pingstring "Pong!" $oc $text] } @@ -1332,6 +2076,10 @@ proc ensure_globalsecret {} { } proc connected {} { + global operuserpass + if {[info exists operuserpass]} { + eval sendout OPER $operuserpass + } foreach chan [chandb_list] { if {[chandb_get $chan autojoin]} { dojoin $chan } }