X-Git-Url: https://git.distorted.org.uk/~mdw/ircbot/blobdiff_plain/83dd12243f89e23ca1ddf90da9a55964b3b963b2..a056c4bd5009d73a491aa9336793eeab98e0fe79:/bot.tcl diff --git a/bot.tcl b/bot.tcl index 8d2083a..f171344 100755 --- a/bot.tcl +++ b/bot.tcl @@ -2,7 +2,18 @@ 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 + +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 {command args} { global sock @@ -46,13 +57,15 @@ proc bgerror {msg} { } proc onread {args} { - global sock + global sock nick if {[gets $sock line] == -1} { set terminate 1; return } regsub -all "\[^ -\176\240-\376\]" $line ? line set org $line if {[regexp -nocase {^:([^ ]+) (.*)} $line dummy prefix remain]} { set line $remain + if {[regexp {^([^!]+)!} $prefix dummy maybenick] && + "[irctolower $maybenick]" == "[irctolower $nick]"} return } else { set prefix {} } @@ -92,13 +105,12 @@ proc onread {args} { } } -proc msendprivmsg {dest ll} { - foreach l $ll { sendout PRIVMSG $dest $l } -} - -proc msendprivmsg_delayed {delay dest ll} { - after $delay [list msendprivmsg $dest $ll] +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 @@ -135,14 +147,14 @@ proc prefix_nick {} { upvar 1 n n if {![regexp {^([^!]+)!} $p dummy n]} { error "not from nick" } check_nick $n - if {"[irctolower $n]" == "[irctolower $nick]"} { error "from myself" } + if {"[irctolower $n]" == "[irctolower $nick]"} { + error "from myself" {} {} + } } -proc showinterval {howlong} { - if {$howlong <= 0} { - return {just now} - } elseif {$howlong < 1000} { - return "${howlong}s ago" +proc showintervalsecs {howlong} { + if {$howlong < 1000} { + return "${howlong}s" } else { if {$howlong < 1000000} { set pfx k @@ -154,11 +166,19 @@ proc showinterval {howlong} { set value [expr "$howlong.0 / $scale"] foreach {min format} {100 %.0f 10 %.1f 1 %.2f} { if {$value < $min} continue - return [format "$format${pfx}s ago" $value] + return [format "$format${pfx}s" $value] } } } +proc showinterval {howlong} { + if {$howlong <= 0} { + return {just now} + } else { + return "[showintervalsecs $howlong] ago" + } +} + proc showtime {when} { return [showinterval [expr {[clock seconds] - $when}]] } @@ -264,7 +284,7 @@ proc chanmode_o1 {m g p chan} { set who [chanmode_arg] recordlastseen_n $n "being nice to $who" 1 if {"[irctolower $who]" == "[irctolower $nick]"} { - sendout PRIVMSG $n Thanks. + sendprivmsg $n Thanks. } } @@ -297,15 +317,68 @@ proc msg_MODE {p c dest modelist args} { } } +proc process_kickpart {chan user} { + check_nick $user + if {![ischan $chan]} { error "not a channel" } + + upvar #0 nick_onchans($user) oc + set lc [irctolower $chan] + set oc [grep tc {"$tc" != "$lc"} $oc] +} + +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 } + } +} + proc msg_NICK {p c newnick} { + global nick_arys 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 } + } } -proc msg_JOIN {p c chan} { recordlastseen_p $p "joining $chan" 1 } -proc msg_PART {p c chan} { recordlastseen_p $p "leaving $chan" 1 } -proc msg_QUIT {p c why} { recordlastseen_p $p "leaving ($why)" 0 } +proc msg_JOIN {p c chan} { + prefix_nick + recordlastseen_n $n "joining $chan" 1 + upvar #0 nick_onchans($n) oc + lappend oc [irctolower $chan] +} +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 @@ -326,16 +399,65 @@ proc msg_PRIVMSG {p c dest text} { } $procname $p $dest } rv]} { - sendout PRIVMSG $n "error: $rv" + sendprivmsg $n "error: $rv" } else { - foreach {td val} [list $n [lindex $rv 0] $output [lindex $rv 1]] { + manyset $rv priv_msgs pub_msgs priv_acts pub_acts + foreach {td val} [list $n $priv_msgs $output $pub_msgs] { foreach l [split $val "\n"] { - sendout PRIVMSG $td $l + sendprivmsg $td $l } } + foreach {td val} [list $n $priv_acts $output $pub_acts] { + foreach l [split $val "\n"] { + sendaction $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 + foreach n [array names nick_onchans] { + upvar #0 nick_onchans($n) oc + set oc [grep tc {"$tc" != "$chan"} $oc] + } + foreach n [split $nicklist { }] { + regsub {^[@+]} $n {} n + check_nick $n + if {![string length $n]} continue + upvar #0 nick_onchans($n) oc + lappend oc $chan } } +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_nomore {} { upvar 1 text text if {[string length $text]} { error "too many parameters" } @@ -360,18 +482,26 @@ proc def_ucmd {cmdname body} { proc ucmd/$cmdname {p dest} " upvar 1 text text\n$body" } -proc ucmdr {priv pub} { - return -code return [list $priv $pub] +proc ucmdr {priv pub args} { + return -code return [concat [list $priv $pub] $args] } - -def_ucmd help { - ta_nomore + +proc ucmd_sendhelp {} { ucmdr \ {Commands currently understood: -help get this list of commands -seen ask after someone (I'll tell them you asked)} {} + help get this list of commands + seen ask after someone (I'll tell them you asked) + summon invite a logged-on user onto IRC +Send commands to me by /msg, or say them in channel with ! in front.} {} +# +# register register your nick (you must auth[*] first) +#[*]auth: /blight in ircII, or /msg blight authuser } +def_ucmd help { ta_nomore; ucmd_sendhelp } + +def_ucmd ? { ta_nomore; ucmd_sendhelp } + proc manyset {list args} { foreach val $list var $args { upvar 1 $var my @@ -379,6 +509,51 @@ proc manyset {list args} { } } +def_ucmd summon { + set target [ta_word] + ta_nomore + if { + [string length $target] > 8 || + [regexp {[^-0-9a-z]} $target] || + ![regexp {^[a-z]} $target] + } { error "invalid username" } + 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 @@ -417,7 +592,7 @@ if {![info exists sock]} { #fconfigure $sock -translation binary fconfigure $sock -translation crlf - sendout USER guest 0 * "chiark testing bot" + sendout USER blight 0 * $ownfullname sendout NICK $nick fileevent $sock readable onread }