X-Git-Url: https://git.distorted.org.uk/~mdw/ircbot/blobdiff_plain/9bc33297992655112490af5bfb98298294f47f67..17bfe24c496a0f6fe1332fcfc374c9f2a941db9f:/bot.tcl diff --git a/bot.tcl b/bot.tcl index b5be32b..09c4ca2 100755 --- a/bot.tcl +++ b/bot.tcl @@ -2,33 +2,59 @@ set host chiark set port 6667 -set nick Blight +if {![info exists nick]} { set nick Blight } +if {![info exists ownfullname]} { set ownfullname "here to Help" } +set ownmailaddr blight@chiark.greenend.org.uk -set sock [socket $host $port] -fconfigure $sock -buffering line -#fconfigure $sock -translation binary -fconfigure $sock -translation crlf +if {![info exists globalsecret]} { + set gsfile [open /dev/urandom r] + fconfigure $gsfile -translation binary + set globalsecret [read $gsfile 32] + binary scan $globalsecret H* globalsecret + close $gsfile + unset gsfile +} -proc sendout {prefix command args} { +proc try_except_finally {try except finally} { + global errorInfo errorCode + set er [catch { uplevel 1 $try } emsg] + if {$er} { + set ei $errorInfo + set ec $errorCode + if {[catch { uplevel 1 $except } emsg3]} { + append ei "\nALSO ERROR HANDLING ERROR:\n$emsg3" + } + } + set er2 [catch { uplevel 1 $finally } emsg2] + if {$er} { + if {$er2} { + append ei "\nALSO ERROR CLEANING UP:\n$emsg2" + } + return -code $er -errorinfo $ei -errorcode $ec $emsg + } elseif {$er2} { + return -code $er2 -errorinfo $errorInfo -errorcode $errorCode $emsg2 + } else { + return $emsg + } +} + +proc sendout {command args} { + global sock if {[llength $args]} { set la [lindex $args end] set args [lreplace $args end end] foreach i $args { if {[regexp {[: ]} $i]} { - error "bad argument in output $i ($prefix $command $args)" + error "bad argument in output $i ($command $args)" } } - lappend $args :$la + lappend args :$la } set args [lreplace $args 0 -1 $command] - if {[string length $prefix]} { - set args [lreplace $args - set string "$command" - puts $string + set string [join $args { }] + puts "[clock seconds] -> $string" puts $sock $string } -puts $sock "USER guest 0 * :chiark testing bot" -puts $sock "NICK $nick" proc log {data} { puts $data @@ -36,26 +62,53 @@ proc log {data} { proc logerror {data} { log $data -} +} + +proc saveeic {} { + global saveei saveec errorInfo errorCode + + set saveei $errorInfo + set saveec $errorCode + + puts ">$saveec|$saveei<" +} + +proc bgerror {msg} { + global save + logerror $msg + saveeic +} proc onread {args} { - global sock saveei saveec errorInfo errorCode + global sock nick calling_nick errorInfo errorCode - gets $sock line - regsub -all "\\[^ -\176\240-\376\\]" $line ? line + if {[gets $sock line] == -1} { set terminate 1; return } + regsub -all "\[^ -\176\240-\376\]" $line ? line set org $line + + set ei $errorInfo + set ec $errorCode + catch { unset calling_nick } + set errorInfo $ei + set errorCode $ec + if {[regexp -nocase {^:([^ ]+) (.*)} $line dummy prefix remain]} { set line $remain + if {[regexp {^([^!]+)!} $prefix dummy maybenick]} { + set calling_nick $maybenick + if {"[irctolower $maybenick]" == "[irctolower $nick]"} return + } } else { set prefix {} } + if {![string length $line]} { return } if {![regexp -nocase {^([0-9a-z]+) *(.*)} $line dummy command line]} { log "bad command: $org" return } - set command [string toupper command] + set command [string toupper $command] set params {} - while {[regexp {([^ :]+) *(.*)} $line dummy thisword line]} { + while {[regexp {^([^ :]+) *(.*)} $line dummy thisword line]} { lappend params $thisword } if {[regexp {^:(.*)} $line dummy thisword]} { @@ -64,27 +117,860 @@ proc onread {args} { log "junk at end: $org" return } + if {"$command" == "PRIVMSG" && + [regexp {^[&#+!]} [lindex $params 0]] && + ![regexp {^!} [lindex $params 1]]} { + # on-channel message, ignore + catch { + recordlastseen_p $prefix "talking on [lindex $params 0]" 1 + } + return + } + log "[clock seconds] <- $org" set procname msg_$command + if {[catch { info body $procname }]} { return } if {[catch { eval [list $procname $prefix $command] $params } emsg]} { logerror "error: $emsg ($prefix $command $params)" - if {![regexp {^invalid command name } $emsg]} { - set saveei $errorInfo - set saveec $errorCode - } + saveeic } } -proc noprefix {} { - upvar 1 p +proc sendprivmsg {dest l} { + sendout [expr {[ischan $dest] ? "PRIVMSG" : "NOTICE"}] $dest $l +} +proc sendaction {dest what} { sendout PRIVMSG $dest "\001ACTION $what\001" } +proc msendprivmsg {dest ll} { foreach l $ll { sendprivmsg $dest $l } } +proc msendprivmsg_delayed {delay dest ll} { after $delay [list msendprivmsg $dest $ll] } + +proc prefix_none {} { + upvar 1 p p if {[string length $p]} { error "prefix specified" } +} + +proc msg_PING {p c s1} { + prefix_none + sendout PONG $s1 +} + +proc check_nick {n} { + if {[regexp -nocase {[^][\\`_^{|}a-z0-9-]} $n]} { error "bad char in nick" } + if {[regexp {^[-0-9]} $n]} { error "bad nick start" } +} + +proc ischan {dest} { + return [regexp {^[&#+!]} $dest] +} + +proc irctolower {v} { + foreach {from to} [list "\\\[" "{" \ + "\\\]" "}" \ + "\\\\" "|" \ + "~" "^"] { + regsub -all $from $v $to v + } + return [string tolower $v] +} + +proc prefix_nick {} { + global nick + upvar 1 p p + upvar 1 n n + if {![regexp {^([^!]+)!} $p dummy n]} { error "not from nick" } + check_nick $n + if {"[irctolower $n]" == "[irctolower $nick]"} { + error "from myself" {} {} + } +} + +proc showintervalsecs {howlong} { + return [showintervalsecs/[opt timeformat] $howlong] +} + +proc showintervalsecs/ks {howlong} { + if {$howlong < 1000} { + return "${howlong}s" + } else { + if {$howlong < 1000000} { + set pfx k + set scale 1000 + } else { + set pfx M + 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] + } + } +} + +proc format_qty {qty unit} { + set o $qty + append o " " + append o $unit + if {$qty != 1} { append o s } + return $o +} + +proc showintervalsecs/hms {qty} { + 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 remainu [lindex $ul 0] + set remainv [expr {$qty % $uv}] + set qty [expr {($qty-$remainv)/$uv}] + set ul [lreplace $ul 0 1] + } + set o [format_qty $qty [lindex $ul 0]] + if {$remainv} { + append o " " + append o [format_qty $remainv $remainu] + } + return $o +} + +proc showinterval {howlong} { + if {$howlong <= 0} { + return {just now} + } else { + return "[showintervalsecs $howlong] ago" + } +} + +proc showtime {when} { + return [showinterval [expr {[clock seconds] - $when}]] +} + +proc def_msgproc {name argl body} { + proc msg_$name "varbase $argl" "\ + upvar #0 msg/\$varbase/dest d\n\ + upvar #0 msg/\$varbase/str s\n\ + upvar #0 msg/\$varbase/accum a\n\ +$body" +} + +def_msgproc begin {dest str} { + set d $dest + set s $str + set a {} +} + +def_msgproc append {str} { + set ns "$s$str" + if {[string length $s] && [string length $ns] > 65} { + msg__sendout $varbase + set s " [string trimleft $str]" + } else { + set s $ns + } +} + +def_msgproc finish {} { + msg__sendout $varbase + unset s + unset d + return $a +} + +def_msgproc _sendout {} { + lappend a [string trimright $s] + set s {} +} + +proc looking_whenwhere {when where} { + set str [showtime [expr {$when-1}]] + if {[string length $where]} { append str " on $where" } + return $str +} + +proc recordlastseen_n {n how here} { + global lastseen lookedfor + set lastseen([irctolower $n]) [list $n [clock seconds] $how] + if {!$here} return + upvar #0 lookedfor([irctolower $n]) lf + if {[info exists lf]} { + switch -exact [llength $lf] { + 0 { + set ml {} + } + 1 { + manyset [lindex $lf 0] when who where + set ml [list \ + "FYI, $who was looking for you [looking_whenwhere $when $where]."] + } + default { + msg_begin tosend $n "FYI, people have been looking for you:" + set i 0 + set fin "" + foreach e $lf { + incr i + if {$i == 1} { + msg_append tosend " " + } elseif {$i == [llength $lf]} { + msg_append tosend " and " + set fin . + } else { + msg_append tosend ", " + } + manyset $e when who where + msg_append tosend \ + "$who ([looking_whenwhere $when $where])$fin" + } + set ml [msg_finish tosend] + } + } + unset lf + msendprivmsg_delayed 1000 $n $ml + } +} + +proc recordlastseen_p {p how here} { + prefix_nick + recordlastseen_n $n $how $here +} + +proc chanmode_arg {} { + upvar 2 args cm_args + set rv [lindex $cm_args 0] + set cm_args [lreplace cm_args 0 0] + return $rv +} + +proc chanmode_o1 {m g p chan} { + global nick + prefix_nick + set who [chanmode_arg] + recordlastseen_n $n "being nice to $who" 1 + if {"[irctolower $who]" == "[irctolower $nick]"} { + sendprivmsg $n Thanks. + } +} + +proc chanmode_o0 {m g p chan} { + global nick chandeop + prefix_nick + set who [chanmode_arg] + recordlastseen_p $p "being mean to $who" 1 + if {"[irctolower $who]" == "[irctolower $nick]"} { + set chandeop($chan) [list [clock seconds] $p] + } +} + +proc msg_MODE {p c dest modelist args} { + if {![ischan $dest]} return + if {[regexp {^\-(.+)$} $modelist dummy modelist]} { + set give 0 + } elseif {[regexp {^\+(.+)$} $modelist dummy modelist]} { + set give 1 + } else { + error "invalid modelist" + } + foreach m [split $modelist] { + set procname chanmode_$m$give + if {[catch { info body $procname }]} { + recordlastseen_p $p "fiddling with $dest" 1 + } else { + $procname $m $give $p $dest + } + } +} + +proc channel_noone_seen {chan} { + global nick_onchans + foreach n [array names nick_onchans] { + upvar #0 nick_onchans($n) oc + set oc [grep tc {"$tc" != "$chan"} $oc] + } +} + +proc process_kickpart {chan user} { + global nick + check_nick $user + if {![ischan $chan]} { error "not a channel" } + if {"[irctolower $user]" == "[irctolower $nick]"} { + channel_noone_seen $chan + } + upvar #0 nick_onchans($user) oc + set lc [irctolower $chan] + set oc [grep tc {"$tc" != "$lc"} $oc] + if {![llength $oc]} { nick_forget $user } + nick_case $user +} + +proc msg_KICK {p c chans users comment} { + set chans [split $chans ,] + set users [split $users ,] + if {[llength $chans] > 1} { + foreach chan $chans user $users { process_kickpart $chan $user } + } else { + foreach user $users { process_kickpart [lindex $chans 0] $user } + } +} + +proc msg_KILL {p c user why} { + nick_forget $user +} + +set nick_arys {onchans username} + +proc nick_forget {n} { + global nick_arys + foreach ary $nick_arys { + upvar #0 nick_${ary}($n) av + catch { unset av } + } + nick_case $n +} + +proc nick_case {n} { + global nick_case + set nick_case([irctolower $n]) $n +} + +proc msg_NICK {p c newnick} { + global nick_arys nick_case + prefix_nick + recordlastseen_n $n "changing nicks to $newnick" 0 + recordlastseen_n $newnick "changing nicks from $n" 1 + foreach ary $nick_arys { + upvar #0 nick_${ary}($n) old + upvar #0 nick_${ary}($newnick) new + if {[info exists new]} { error "nick collision ?! $ary $n $newnick" } + if {[info exists old]} { set new $old; unset old } + } + nick_case $newnick +} + +proc msg_JOIN {p c chan} { + prefix_nick + recordlastseen_n $n "joining $chan" 1 + upvar #0 nick_onchans($n) oc + lappend oc [irctolower $chan] + nick_case $n +} +proc msg_PART {p c chan} { + prefix_nick + recordlastseen_n $n "leaving $chan" 1 + process_kickpart $chan $n +} +proc msg_QUIT {p c why} { + prefix_nick + recordlastseen_n $n "leaving ($why)" 0 + nick_forget $n +} + +proc msg_PRIVMSG {p c dest text} { + prefix_nick + if {[ischan $dest]} { + recordlastseen_n $n "invoking me in $dest" 1 + set output $dest + } else { + recordlastseen_n $n "talking to me" 1 + set output $n + } + nick_case $n + + if {[catch { + regsub {^! *} $text {} text + set ucmd [ta_word] + set procname ucmd/[string tolower $ucmd] + if {[catch { info body $procname }]} { + error "unknown command; try help for help" + } + $procname $p $dest + } rv]} { + sendprivmsg $n "error: $rv" + } else { + manyset $rv priv_msgs pub_msgs priv_acts pub_acts + foreach {td val} [list $n $priv_acts $output $pub_acts] { + foreach l [split $val "\n"] { + sendaction $td $l + } + } + foreach {td val} [list $n $priv_msgs $output $pub_msgs] { + foreach l [split $val "\n"] { + sendprivmsg $td $l + } + } + } +} + +proc msg_INVITE {p c n chan} { + after 1000 [list sendout JOIN $chan] +} + +proc grep {var predicate list} { + set o {} + upvar 1 $var v + foreach v $list { + if {[uplevel 1 [list expr $predicate]]} { lappend o $v } + } + return $o +} + +proc msg_353 {p c dest type chan nicklist} { + global names_chans nick_onchans + if {![info exists names_chans]} { set names_chans {} } + set chan [irctolower $chan] + lappend names_chans $chan + channel_noone_seen $chan + foreach n [split $nicklist { }] { + regsub {^[@+]} $n {} n + if {![string length $n]} continue + check_nick $n + upvar #0 nick_onchans($n) oc + lappend oc $chan + nick_case $n + } +} + +proc msg_366 {p c args} { + global names_chans nick_onchans + if {[llength names_chans] > 1} { + foreach n [array names nick_onchans] { + upvar #0 nick_onchans($n) oc + set oc [grep tc {[lsearch -exact $tc $names_chans] >= 0} $oc] + if {![llength $oc]} { nick_forget $n } + } + } + unset names_chans +} + +proc ta_anymore {} { + upvar 1 text text + return [expr {!![string length $text]}] +} + +proc ta_nomore {} { + upvar 1 text text + if {[string length $text]} { error "too many parameters" } +} + +proc ta_word {} { + upvar 1 text text + if {![regexp {^([^ ]+) *(.*)} $text dummy firstword text]} { + error "too few parameters" + } + return $firstword +} + +proc ta_nick {} { + upvar 1 text text + set v [ta_word] + check_nick $v + return $v +} + +proc def_ucmd {cmdname body} { + proc ucmd/$cmdname {p dest} " upvar 1 text text\n$body" +} + +proc ucmdr {priv pub args} { + return -code return [concat [list $priv $pub] $args] +} + +proc loadhelp {} { + global help_topics + + catch { unset help_topics } + set f [open helpinfos r] + try_except_finally { + set lno 0 + while {[gets $f l] >= 0} { + incr lno + if {[regexp {^#.*} $l]} { + } elseif {[regexp {^ *$} $l]} { + if {[info exists topic]} { + set help_topics($topic) [join $lines "\n"] + unset topic + unset lines + } + } elseif {[regexp {^!([-+._0-9a-z]*)$} $l dummy newtopic]} { + if {[info exists topic]} { + error "help $newtopic while in $topic" + } + set topic $newtopic + set lines {} + } elseif {[regexp {^[^!#]} $l]} { + set topic + lappend lines [string trimright $l] + } else { + error "eh ? $lno: $l" + } + } + if {[info exists topic]} { error "unfinished topic $topic" } + } {} { + close $f + } +} + +def_ucmd help { + upvar #0 help_topics([irctolower [string trim $text]]) info + if {![info exists info]} { ucmdr "No help on $text, sorry." {} } + ucmdr $info {} +} + +def_ucmd ? { + global help_topics + ucmdr $help_topics() {} +} + +proc manyset {list args} { + foreach val $list var $args { + upvar 1 $var my + set my $val + } +} + +proc check_username {target} { + if { + [string length $target] > 8 || + [regexp {[^-0-9a-z]} $target] || + ![regexp {^[a-z]} $target] + } { error "invalid username" } +} + +proc nickdb__head {} { + uplevel 1 { + set nl [irctolower $n] + upvar #0 nickdb($nl) ndbe + binary scan $nl H* nh + set nfn users/n$nh + if {![info exists ndbe] && [file exists $nfn]} { + set f [open $nfn r] + try_except_finally { set newval [read $f] } {} { close $f } + if {[llength $newval] % 2} { error "invalid length" } + set ndbe $newval + } + } +} + +proc def_nickdb {name arglist body} { + proc nickdb_$name $arglist "nickdb__head; $body" +} + +def_nickdb exists {n} { + return [info exists ndbe] +} + +def_nickdb delete {n} { + catch { unset ndbe } + file delete $nfn +} + +set default_settings {timeformat ks} + +def_nickdb set {n args} { + global default_settings + if {![info exists ndbe]} { set ndbe $default_settings } + foreach {key value} [concat $ndbe $args] { set a($key) $value } + set newval {} + foreach {key value} [array get a] { lappend newval $key $value } + set f [open $nfn.new w] + try_except_finally { + puts $f $newval + close $f + file rename -force $nfn.new $nfn + } { + } { + catch { close $f } + } + set ndbe $newval +} + +proc opt {key} { + global calling_nick + if {[info exists calling_nick]} { set n $calling_nick } { set n {} } + return [nickdb_opt $n $key] +} + +def_nickdb opt {n key} { + global default_settings + if {[info exists ndbe]} { + set l $ndbe + } else { + set l $default_settings + } + foreach {tkey value} $l { + if {"$tkey" == "$key"} { return $value } + } + error "unset setting $key" +} + +proc check_notonchan {} { + upvar 1 dest dest + if {[ischan $dest]} { error "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'." } + set wantu [nickdb_opt $n username] + if {![string length $wantu]} { + if {$strict} { + error "that feature is only available to secure users, sorry." + } else { + return + } + } + upvar #0 nick_username($n) nu + if {![info exists nu]} { + error "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" + } +} + +def_ucmd who { + if {[ta_anymore]} { + set target [ta_word]; ta_nomore + set myself 1 + } else { + prefix_nick + set target $n + set myself [expr {"$target" != "$n"}] + } + upvar #0 nick_case([irctolower $target]) nc + set nshow $target + if {[info exists nc]} { + upvar #0 nick_onchans($nc) oc + upvar #0 nick_username($nc) nu + if {[info exists oc]} { set nshow $nc } + } + if {![nickdb_exists $target]} { + set ol "$nshow is not a registered nick." + } elseif {[string length [set username [nickdb_opt $target username]]]} { + set ol "The nick $nshow belongs to the user $username." + } else { + set ol "The nick $nshow is registered (but not to a username)." + } + if {![info exists nc] || ![info exists oc]} { + if {$myself} { + append ol "\nI can't see $nshow on anywhere." + } else { + append ol "\nYou aren't on any channels with me." + } + } elseif {![info exists nu]} { + append ol "\n$nshow has not identified themselves." + } elseif {![info exists username]} { + append ol "\n$nshow has identified themselves as the user $nu." + } elseif {"$nu" != "$username"} { + append ol "\nHowever, $nshow is being used by the user $nu." + } else { + append ol "\n$nshow has identified themselves to me." + } + ucmdr {} $ol +} + +def_ucmd register { + prefix_nick + check_notonchan + set old [nickdb_exists $n] + if {$old} { nick_securitycheck 0 } + switch -exact [string tolower [string trim $text]] { + {} { + upvar #0 nick_username($n) nu + if {![info exists nu]} { + ucmdr {} \ + "You must identify yourself before using `register'. See `help identify', or use `register insecure'." + } + nickdb_set $n username $nu + ucmdr {} {} "makes a note of your username." {} + } + delete { + nickdb_delete $n + ucmdr {} {} "forgets your nickname." {} + } + insecure { + nickdb_set $n username {} + if {$old} { + ucmdr {} "Security is now disabled for your nickname !" + } else { + 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." + } + } + } +} + +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." } + default { error "invalid timeformat: $v" } + } +} + +proc def_setting {opt show_body set_body} { + proc set_show/$opt {} " + upvar 1 n n + set opt $opt + $show_body" + if {![string length $set_body]} return + proc set_set/$opt {} " + upvar 1 n n + upvar 1 text text + set opt $opt + $set_body" +} + +def_setting timeformat { + set tf [nickdb_opt $n timeformat] + return "$tf: [timeformat_desc $tf]" +} { + set tf [string tolower [ta_word]] + ta_nomore + set desc [timeformat_desc $tf] + nickdb_set $n timeformat $tf + ucmdr {} $desc +} + +def_setting security { + set s [nickdb_opt $n username] + if {[string length $s]} { + return "Your nick, $n, is controlled by the user $s." + } else { + return "Your nick, $n, is not secure." + } +} {} + +def_ucmd set { + prefix_nick + check_notonchan + if {![nickdb_exists $n]} { + ucmdr {} "You are unknown to me and so have no settings. (Use `register'.)" + } + if {![ta_anymore]} { + set ol {} + foreach proc [lsort [info procs]] { + if {![regexp {^set_show/(.*)$} $proc dummy opt]} continue + lappend ol [format "%-10s %s" $opt [set_show/$opt]] + } + ucmdr {} [join $ol "\n"] + } else { + set opt [ta_word] + if {[catch { info body set_show/$opt }]} { + error "no setting $opt" + } + if {![ta_anymore]} { + ucmdr {} "$opt [set_show/$opt]" + } else { + nick_securitycheck 0 + if {[catch { info body set_set/$opt }]} { + error "setting $opt cannot be set with `set'" + } + set_set/$opt + } + } +} + +def_ucmd identpass { + set username [ta_word] + set passmd5 [md5sum "[ta_word]\n"] + ta_nomore + prefix_nick + check_notonchan + upvar #0 nick_onchans($n) onchans + if {![info exists onchans] || ![llength $onchans]} { + ucmdr "You must be on a channel with me to identify yourself." {} + } + check_username $username + exec userv --timeout 3 $username << "$passmd5\n" > /dev/null \ + irc-identpass $n + upvar #0 nick_username($n) rec_username + set rec_username $username + ucmdr "Pleased to see you, $username." {} +} + +def_ucmd summon { + set target [ta_word] + ta_nomore + check_username $target + prefix_nick + + upvar #0 lastsummon($target) ls + set now [clock seconds] + if {[info exists ls]} { + set interval [expr {$now - $ls}] + if {$interval < 30} { + ucmdr {} \ + "Please be patient; $target was summoned only [showinterval $interval]." + } + } + regsub {^[^!]*!} $p {} path + if {[catch { + exec userv --timeout 3 $target irc-summon $n $path \ + [expr {[ischan $dest] ? "$dest" : ""}] \ + < /dev/null + } rv]} { + regsub -all "\n" $rv { / } rv + error $rv + } + if {[regexp {^problem (.*)} $rv dummy problem]} { + ucmdr {} "The user `$target' $problem." + } elseif {[regexp {^ok ([^ ]+) ([0-9]+)$} $rv dummy tty idlesince]} { + set idletime [expr {$now - $idlesince}] + set ls $now + ucmdr {} {} {} "invites $target ($tty[expr { + $idletime > 10 ? ", idle for [showintervalsecs $idletime]" : "" + }]) to [expr { + [ischan $dest] ? "join us here" : "talk to you" + }]." + } else { + error "unexpected response from userv service: $rv" + } +} + +proc md5sum {value} { exec md5sum << $value } + +def_ucmd seen { + global lastseen nick + prefix_nick + set ncase [ta_nick] + set nlower [irctolower $ncase] + ta_nomore + set now [clock seconds] + if {"$nlower" == "[irctolower $nick]"} { + error "I am not self-aware." + } elseif {![info exists lastseen($nlower)]} { + set rstr "I've never seen $ncase." + } else { + manyset $lastseen($nlower) realnick time what + set howlong [expr {$now - $time}] + set string [showinterval $howlong] + set rstr "I last saw $realnick $string, $what." + } + if {[ischan $dest]} { + set where $dest + } else { + set where {} + } + upvar #0 lookedfor($nlower) lf + 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 + lappend lf $v + } + ucmdr {} $rstr +} + +if {![info exists sock]} { + set sock [socket $host $port] + fconfigure $sock -buffering line + #fconfigure $sock -translation binary + fconfigure $sock -translation crlf -proc msg_PING {p s1} { - noprefix - puts "PONG $s1" + sendout USER blight 0 * $ownfullname + sendout NICK $nick + fileevent $sock readable onread } -fileevent $sock readable onread +loadhelp -vwait terminate +#if {![regexp {tclsh} $argv0]} { +# vwait terminate +#}