X-Git-Url: https://git.distorted.org.uk/~mdw/ircbot/blobdiff_plain/d83fb8db41157cec76f22ed8d48f9d4f59505f92..534e26a9446218a12c0ac24ab6c95fb451d48a07:/bot.tcl diff --git a/bot.tcl b/bot.tcl index 602a5a3..140ff1c 100755 --- a/bot.tcl +++ b/bot.tcl @@ -1,18 +1,28 @@ -#!/usr/bin/tclsh8.2 +# Core bot code -set host chiark -set port 6667 -if {![info exists nick]} { set nick Blight } -if {![info exists ownfullname]} { set ownfullname "here to Help" } -set ownmailaddr blight@chiark.greenend.org.uk +proc defset {varname val} { + upvar #0 $varname var + if {![info exists var]} { set var $val } +} -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 +# must set host +defset port 6667 + +defset nick testbot +defset ownfullname "testing bot" +defset ownmailaddr test-irc-bot@example.com + +defset musthaveping_ms 10000 +defset out_maxburst 6 +defset out_interval 2100 +defset out_lag_lag 5000 +defset out_lag_very 25000 + +proc manyset {list args} { + foreach val $list var $args { + upvar 1 $var my + set my $val + } } proc try_except_finally {try except finally} { @@ -38,8 +48,69 @@ proc try_except_finally {try except finally} { } } -proc sendout {command args} { +proc out__vars {} { + uplevel 1 { + global out_queue out_creditms out_creditat out_interval out_maxburst + global out_lag_lag out_lag_very +#set pr [lindex [info level 0] 0] +#puts $pr>[clock seconds]|$out_creditat|$out_creditms|[llength $out_queue]< + } +} + +proc out_lagged {} { + out__vars + if {[llength $out_queue]*$out_interval > $out_lag_very} { + return 2 + } elseif {[llength $out_queue]*$out_interval > $out_lag_lag} { + return 1 + } else { + return 0 + } +} + +proc out_restart {} { + out__vars + + set now [clock seconds] + incr out_creditms [expr {($now - $out_creditat) * 1000}] + set out_creditat $now + if {$out_creditms > $out_maxburst*$out_interval} { + set out_creditms [expr {$out_maxburst*$out_interval}] + } + out_runqueue $now +} + +proc out_runqueue {now} { global sock + out__vars + + while {[llength $out_queue] && $out_creditms >= $out_interval} { +#puts rq>$now|$out_creditat|$out_creditms|[llength $out_queue]< + manyset [lindex $out_queue 0] orgwhen msg + set out_queue [lrange $out_queue 1 end] + if {[llength $out_queue]} { + append orgwhen "+[expr {$now - $orgwhen}]" + append orgwhen ([llength $out_queue])" + } + puts "$orgwhen -> $msg" + puts $sock $msg + incr out_creditms -$out_interval + } + if {[llength $out_queue]} { + after $out_interval out_nextmessage + } +} + +proc out_nextmessage {} { + out__vars + set now [clock seconds] + incr out_creditms $out_interval + set out_creditat $now + out_runqueue $now +} + +proc sendout_priority {priority command args} { + global sock out_queue if {[llength $args]} { set la [lindex $args end] set args [lreplace $args end end] @@ -52,10 +123,20 @@ proc sendout {command args} { } set args [lreplace $args 0 -1 $command] set string [join $args { }] - puts "[clock seconds] -> $string" - puts $sock $string + set now [clock seconds] + set newe [list $now $string] + if {$priority} { + set out_queue [concat [list $newe] $out_queue] + } else { + lappend out_queue $newe + } + if {[llength $out_queue] == 1} { + out_restart + } } +proc sendout {command args} { eval sendout_priority [list 0 $command] $args } + proc log {data} { puts $data } @@ -80,19 +161,26 @@ proc bgerror {msg} { } proc onread {args} { - global sock nick calling_nick + global sock nick calling_nick errorInfo errorCode - if {[gets $sock line] == -1} { set terminate 1; return } + if {[gets $sock line] == -1} { fail "EOF/error on input" } 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] && - "[irctolower $maybenick]" == "[irctolower $nick]"} return - set calling_nick $maybenick + if {[regexp {^([^!]+)!} $prefix dummy maybenick]} { + set calling_nick $maybenick + if {"[irctolower $maybenick]" == "[irctolower $nick]"} return + } } else { set prefix {} - catch { unset calling_nick } } if {![string length $line]} { return } if {![regexp -nocase {^([0-9a-z]+) *(.*)} $line dummy command line]} { @@ -131,9 +219,13 @@ proc onread {args} { } proc sendprivmsg {dest l} { - sendout [expr {[ischan $dest] ? "PRIVMSG" : "NOTICE"}] $dest $l + foreach v [split $l "\n"] { + sendout [expr {[ischan $dest] ? "PRIVMSG" : "NOTICE"}] $dest $v + } +} +proc sendaction_priority {priority dest what} { + sendout_priority $priority PRIVMSG $dest "\001ACTION $what\001" } -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] } @@ -143,8 +235,10 @@ proc prefix_none {} { } proc msg_PING {p c s1} { + global musthaveping_after prefix_none sendout PONG $s1 + if {[info exists musthaveping_after]} connected } proc check_nick {n} { @@ -333,12 +427,26 @@ proc chanmode_arg {} { } proc chanmode_o1 {m g p chan} { - global nick + global nick chan_initialop prefix_nick set who [chanmode_arg] recordlastseen_n $n "being nice to $who" 1 if {"[irctolower $who]" == "[irctolower $nick]"} { - sendprivmsg $n Thanks. + set nlower [irctolower $n] + upvar #0 nick_unique($nlower) u + if {[chandb_exists $chan]} { + sendprivmsg $n Thanks. + } elseif {![info exists u]} { + sendprivmsg $n {Op me while not on the channel, why don't you ?} + } 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]]} { + sendprivmsg $n \ + "(But to do that you must register your nick securely first.)" + } + } } } @@ -371,15 +479,58 @@ proc msg_MODE {p c dest modelist args} { } } +proc leaving {lchan} { + foreach luser [array names nick_onchans] { + upvar #0 nick_onchans($luser) oc + set oc [grep tc {"$tc" != "$lchan"} $oc] + } + upvar #0 chan_nicks($lchan) nlist + unset nlist +} + +proc dojoin {lchan} { + global chan_nicks + sendout JOIN $lchan + set chan_nicks($lchan) {} +} + +proc check_justme {lchan} { + global nick + upvar #0 chan_nicks($lchan) nlist + if {[llength $nlist] != 1} return + if {"[lindex $nlist 0]" != "$nick"} return + if {[chandb_exists $lchan]} { + set mode [chandb_get $lchan mode] + if {"$mode" != "*"} { + sendout MODE $lchan $mode + } + } else { + sendout PART $lchan + leaving $lchan + } +} + proc process_kickpart {chan user} { + global nick check_nick $user + set luser [irctolower $user] + set lchan [irctolower $chan] if {![ischan $chan]} { error "not a channel" } - - upvar #0 nick_onchans($user) oc - set lc [irctolower $chan] - set oc [grep tc {"$tc" != "$lc"} $oc] - if {![llength $oc]} { nick_forget $user } -} + if {"$luser" == "[irctolower $nick]"} { + leaving $lchan + } else { + upvar #0 nick_onchans($luser) oc + upvar #0 chan_nicks($lchan) nlist + set oc [grep tc {"$tc" != "$lchan"} $oc] + set nlist [grep tn {"$tn" != "$luser"} $nlist] + nick_case $user + if {![llength $oc]} { + nick_forget $luser + } else { + check_justme $lchan + } + } +} proc msg_KICK {p c chans users comment} { set chans [split $chans ,] @@ -395,18 +546,41 @@ proc msg_KILL {p c user why} { nick_forget $user } -set nick_arys {onchans username} +set nick_counter 0 +set nick_arys {onchans username unique} +# nick_onchans($luser) -> [list ... $lchan ...] +# nick_username($luser) -> +# nick_unique($luser) -> +# nick_case($luser) -> $user (valid even if no longer visible) -proc nick_forget {n} { - global nick_arys +# chan_nicks($lchan) -> [list ... $luser ...] + +proc lnick_forget {luser} { + global nick_arys chan_nicks foreach ary $nick_arys { - upvar #0 nick_${ary}($n) av + upvar #0 nick_${ary}($luser) av catch { unset av } } + foreach lch [array names chan_nicks] { + upvar #0 chan_nicks($lch) nlist + set nlist [grep tn {"$tn" != "$luser"} $nlist] + check_justme $lch + } +} + +proc nick_forget {user} { + global nick_arys chan_nicks + lnick_forget [irctolower $user] + nick_case $user +} + +proc nick_case {user} { + global nick_case + set nick_case([irctolower $user]) $user } proc msg_NICK {p c newnick} { - global nick_arys + global nick_arys nick_case prefix_nick recordlastseen_n $n "changing nicks to $newnick" 0 recordlastseen_n $newnick "changing nicks from $n" 1 @@ -416,13 +590,30 @@ proc msg_NICK {p c newnick} { if {[info exists new]} { error "nick collision ?! $ary $n $newnick" } if {[info exists old]} { set new $old; unset old } } + upvar #0 nick_onchans($new) + set luser [irctolower $n] + set lusernew [irctolower $newnick] + foreach ch $oc { + upvar #0 chan_nicks($ch) nlist + set nlist [grep tn {"$tn" != "$luser"} $nlist] + lappend nlist $lusernew + } + nick_case $newnick +} + +proc nick_ishere {n} { + global nick_counter + upvar #0 nick_unique([irctolower $n]) u + if {![info exists u]} { set u [incr nick_counter].$n.[clock seconds] } + nick_case $n } proc msg_JOIN {p c chan} { prefix_nick recordlastseen_n $n "joining $chan" 1 - upvar #0 nick_onchans($n) oc + upvar #0 nick_onchans([irctolower $n]) oc lappend oc [irctolower $chan] + nick_ishere $n } proc msg_PART {p c chan} { prefix_nick @@ -444,6 +635,7 @@ proc msg_PRIVMSG {p c dest text} { recordlastseen_n $n "talking to me" 1 set output $n } + nick_case $n if {[catch { regsub {^! *} $text {} text @@ -459,7 +651,7 @@ proc msg_PRIVMSG {p c dest text} { 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 + sendaction_priority 0 $td $l } } foreach {td val} [list $n $priv_msgs $output $pub_msgs] { @@ -471,7 +663,7 @@ proc msg_PRIVMSG {p c dest text} { } proc msg_INVITE {p c n chan} { - after 1000 [list sendout JOIN $chan] + after 1000 [list dojoin [irctolower $chan]] } proc grep {var predicate list} { @@ -485,30 +677,39 @@ proc grep {var predicate list} { 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] + set lchan [irctolower $chan] + upvar #0 chan_nicks($lchan) nlist + lappend names_chans $lchan + if {![info exists nlist]} { + # We don't think we're on this channel, so ignore it ! + # Unfortunately, because we don't get a reply to PART, + # we have to remember ourselves whether we're on a channel, + # and ignore stuff if we're not, to avoid races. Feh. + return } - foreach n [split $nicklist { }] { - regsub {^[@+]} $n {} n - check_nick $n - if {![string length $n]} continue - upvar #0 nick_onchans($n) oc - lappend oc $chan + set nlist_new {} + foreach user [split $nicklist { }] { + regsub {^[@+]} $user {} user + if {![string length $user]} continue + check_nick $user + set luser [irctolower $user] + upvar #0 nick_onchans($luser) oc + lappend oc $lchan + lappend nlist_new $luser + nick_ishere $user } + set nlist $nlist_new } 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 lchan [irctolower $c] + foreach luser [array names nick_onchans] { + upvar #0 nick_onchans($luser) oc + if {[llength names_chans] > 1} { set oc [grep tc {[lsearch -exact $tc $names_chans] >= 0} $oc] - if {![llength $oc]} { nick_forget $n } } + if {![llength $oc]} { lnick_forget $n } } unset names_chans } @@ -582,6 +783,18 @@ proc loadhelp {} { } def_ucmd help { + if {[set lag [out_lagged]]} { + if {[ischan $dest]} { set replyto $dest } else { set replyto $n } + if {$lag > 1} { + sendaction_priority 1 $replyto \ + "is very lagged. Please ask for help again later." + ucmdr {} {} + } else { + sendaction_priority 1 $replyto \ + "is lagged. Your help will arrive shortly ..." + } + } + upvar #0 help_topics([irctolower [string trim $text]]) info if {![info exists info]} { ucmdr "No help on $text, sorry." {} } ucmdr $info {} @@ -592,13 +805,6 @@ def_ucmd ? { 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 || @@ -607,66 +813,78 @@ proc check_username {target} { } { error "invalid username" } } -proc nickdb__head {} { +proc somedb__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] + set idl [irctolower $id] + upvar #0 ${nickchan}db($idl) ndbe + binary scan $idl H* idh + set idfn $fprefix$idh + if {![info exists iddbe] && [file exists $idfn]} { + set f [open $idfn r] try_except_finally { set newval [read $f] } {} { close $f } if {[llength $newval] % 2} { error "invalid length" } - set ndbe $newval + set iddbe $newval } } } -proc def_nickdb {name arglist body} { - proc nickdb_$name $arglist "nickdb__head; $body" +proc def_somedb {name arglist body} { + foreach {nickchan fprefix} {nick users/n chan chans/c} { + proc ${nickchan}db_$name $arglist \ + "set nickchan $nickchan; set fprefix $fprefix; $body" + } } -def_nickdb exists {n} { - return [info exists ndbe] +def_somedb list {} { + set list {} + foreach path [glob -nocomplain -path $fprefix *] { + binary scan $path "A[string length $fprefix]A*" afprefix thinghex + if {"$afprefix" != "$fprefix"} { error "wrong prefix $path $afprefix" } + lappend list [binary format H* $thinghex] + } + return $list } -def_nickdb delete {n} { - catch { unset ndbe } - file delete $nfn +proc def_somedb_id {name arglist body} { + def_somedb $name [concat id $arglist] "somedb__head; $body" } -set default_settings {timeformat ks} +def_somedb_id exists {} { + return [info exists iddbe] +} + +def_somedb_id delete {} { + catch { unset iddbe } + file delete $idfn +} -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 default_settings_nick {timeformat ks} +set default_settings_chan {autojoin 1 mode *} + +def_somedb_id set {args} { + upvar #0 default_settings_$nickchan def + if {![info exists iddbe]} { set iddbe $def } + foreach {key value} [concat $iddbe $args] { set a($key) $value } set newval {} foreach {key value} [array get a] { lappend newval $key $value } - set f [open $nfn.new w] + set f [open $idfn.new w] try_except_finally { puts $f $newval close $f - file rename -force $nfn.new $nfn + file rename -force $idfn.new $idfn } { } { 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] + set iddbe $newval } -def_nickdb opt {n key} { - global default_settings - if {[info exists ndbe]} { - set l $ndbe +def_somedb_id get {key} { + upvar #0 default_settings_$nickchan def + if {[info exists iddbe]} { + set l [concat $iddbe $def] } else { - set l $default_settings + set l $def } foreach {tkey value} $l { if {"$tkey" == "$key"} { return $value } @@ -674,6 +892,12 @@ def_nickdb opt {n key} { error "unset setting $key" } +proc opt {key} { + global calling_nick + if {[info exists calling_nick]} { set n $calling_nick } { set n {} } + return [nickdb_get $n $key] +} + proc check_notonchan {} { upvar 1 dest dest if {[ischan $dest]} { error "that command must be sent privately" } @@ -682,7 +906,7 @@ proc check_notonchan {} { 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] + set wantu [nickdb_get $n username] if {![string length $wantu]} { if {$strict} { error "that feature is only available to secure users, sorry." @@ -690,7 +914,8 @@ proc nick_securitycheck {strict} { return } } - upvar #0 nick_username($n) nu + set luser [irctolower $n] + upvar #0 nick_username($luser) nu if {![info exists nu]} { error "nick $n is secure, you must identify yourself first." } @@ -699,17 +924,192 @@ proc nick_securitycheck {strict} { } } +proc channel_securitycheck {channel n} { + # You must also call `nick_securitycheck 1' + set mgrs [chandb_get $channel managers] + if {[lsearch -exact [irctolower $mgrs] [irctolower $n]] < 0} { + error "you are not a manager of $channel" + } +} + +proc def_chancmd {name body} { + proc channel/$name {} \ + " upvar 1 target chan; upvar 1 n n; upvar 1 text text; $body" +} + +def_chancmd manager { + set opcode [ta_word] + switch -exact _$opcode { + _= { set ml {} } + _+ - _- { + if {[chandb_exists $chan]} { + set ml [chandb_get $chan managers] + } else { + set ml [list [irctolower $n]] + } + } + default { + error "`channel manager' opcode must be one of + - =" + } + } + foreach nn [split $text " "] { + if {![string length $nn]} continue + check_nick $nn + set nn [irctolower $nn] + if {"$opcode" != "-"} { + lappend ml $nn + } else { + set ml [grep nq {"$nq" != "$nn"} $ml] + } + } + if {[llength $ml]} { + chandb_set $chan managers $ml + ucmdr "Managers of $chan: $ml" {} + } else { + chandb_delete $chan + ucmdr {} {} "forgets about managing $chan." {} + } +} + +def_chancmd autojoin { + set yesno [ta_word] + switch -exact [string tolower $yesno] { + no { set nv 0 } + yes { set nv 1 } + default { error "channel autojoin must be `yes' or `no' } + } + chandb_set $chan autojoin $nv + ucmdr [expr {$nv ? "I will join #chan when I'm restarted " : \ + "I won't join #chan when I'm restarted "}] {} +} + +def_chancmd mode { + set mode [ta_word] + if {"$mode" != "*" && ![regexp {^(([-+][imnpst]+)+)$} $mode mode]} { + error {channel mode must be * or match ([-+][imnpst]+)+} + } + chandb_set $chan mode $mode + if {"$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." {} + } +} + +def_chancmd show { + if {[chandb_exists $chan]} { + set l "Settings for $chan: autojoin " + append l [lindex {no yes} [chandb_get $chan autojoin]] + append l ", mode " [chandb_get $chan mode] "." + append l "\nManagers: " + append l [join [chandb_get $chan managers] " "] + ucmdr {} $l + } else { + ucmdr {} "The channel $chan is not managed." + } +} + +def_ucmd op { + if {[ischan $dest]} { set target $dest } + if {[ta_anymore]} { set target [ta_word] } + ta_nomore + if {![info exists target]} { error "you must specify, or !... on, the channel" } + 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 + sendout MODE $target +o $n +} + +def_ucmd channel { + if {[ischan $dest]} { set target $dest } + if {![ta_anymore]} { + set subcmd show + } else { + set subcmd [ta_word] + } + if {[ischan $subcmd]} { + set target $subcmd + if {![ta_anymore]} { + set subcmd show + } else { + set subcmd [ta_word] + } + } + 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" } + prefix_nick + nick_securitycheck 1 + if {[chandb_exists $target]} { + channel_securitycheck $target $n + } else { + 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" } + } + } + channel/$subcmd +} + +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"}] + } + set ltarget [irctolower $target] + upvar #0 nick_case($ltarget) ctarget + set nshow $target + if {[info exists ctarget]} { + upvar #0 nick_onchans($ltarget) oc + upvar #0 nick_username($ltarget) nu + if {[info exists oc]} { set nshow $ctarget } + } + if {![nickdb_exists $ltarget]} { + set ol "$nshow is not a registered nick." + } elseif {[string length [set username [nickdb_get $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 ctarget] || ![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 } + set luser [irctolower $n] switch -exact [string tolower [string trim $text]] { {} { - upvar #0 nick_username($n) nu + upvar #0 nick_username($luser) nu if {![info exists nu]} { ucmdr {} \ - "You must identify yourself before using `register'. See `help identify'." + "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." {} @@ -731,7 +1131,7 @@ def_ucmd register { proc timeformat_desc {tf} { switch -exact $tf { - ks { return "Times will be displayed in kiloseconds or seconds." } + 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" } } @@ -751,7 +1151,7 @@ proc def_setting {opt show_body set_body} { } def_setting timeformat { - set tf [nickdb_opt $n timeformat] + set tf [nickdb_get $n timeformat] return "$tf: [timeformat_desc $tf]" } { set tf [string tolower [ta_word]] @@ -762,7 +1162,7 @@ def_setting timeformat { } def_setting security { - set s [nickdb_opt $n username] + set s [nickdb_get $n username] if {[string length $s]} { return "Your nick, $n, is controlled by the user $s." } else { @@ -791,6 +1191,7 @@ def_ucmd set { 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'" } @@ -805,14 +1206,15 @@ def_ucmd identpass { ta_nomore prefix_nick check_notonchan - upvar #0 nick_onchans($n) onchans + set luser [irctolower $n] + upvar #0 nick_onchans($luser) 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 + upvar #0 nick_username($luser) rec_username set rec_username $username ucmdr "Pleased to see you, $username." {} } @@ -890,19 +1292,62 @@ def_ucmd seen { ucmdr {} $rstr } -if {![info exists sock]} { +proc ensure_globalsecret {} { + global globalsecret + + if {[info exists globalsecret]} return + 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 ensure_outqueue {} { + out__vars + if {[info exists out_queue]} return + set out_creditms [expr {$out_maxburst*$out_interval}] + set out_creditat [clock seconds] + set out_queue {} + set out_lag_reported 0 + set out_lag_reportwhen $out_creditat +} + +proc fail {msg} { + logerror "failing: $msg" + exit 1 +} + +proc ensure_connecting {} { + global sock ownfullname host port nick + global musthaveping_ms musthaveping_after + + if {[info exists sock]} return set sock [socket $host $port] fconfigure $sock -buffering line - #fconfigure $sock -translation binary fconfigure $sock -translation crlf sendout USER blight 0 * $ownfullname sendout NICK $nick fileevent $sock readable onread + + set musthaveping_after [after $musthaveping_ms \ + {fail "no ping within timeout"}] } -loadhelp +proc connected {} { + global musthaveping_after -#if {![regexp {tclsh} $argv0]} { -# vwait terminate -#} + after cancel $musthaveping_after + unset musthaveping_after + + foreach chan [chandb_list] { + if {[chandb_get $chan autojoin]} { dojoin $chan } + } +} + +ensure_globalsecret +ensure_outqueue +loadhelp +ensure_connecting