X-Git-Url: https://git.distorted.org.uk/~mdw/ircbot/blobdiff_plain/33718ee256f0e45bedbf1a0d6a1104a38462a010..8c7d57b0f8de4eeee27ecb6a5b7cb95824626d7f:/bot.tcl diff --git a/bot.tcl b/bot.tcl index 5b73d6a..e021af7 100755 --- a/bot.tcl +++ b/bot.tcl @@ -1,280 +1,22 @@ -# Core bot code +# Actual IRC bot code -proc defset {varname val} { - upvar #0 $varname var - if {![info exists var]} { set var $val } -} - -# must set host -defset port 6667 - -defset nick testbot -defset ownfullname "testing bot" -defset ownmailaddr test-irc-bot@example.com +set helpfile helpinfos -defset musthaveping_ms 10000 -defset out_maxburst 6 -defset out_interval 2100 -defset out_lag_lag 5000 -defset out_lag_very 25000 +source irccore.tcl +source parsecmd.tcl +source stdhelp.tcl -defset marktime_min 300 -defset marktime_join_startdelay 5000 - -proc manyset {list args} { - foreach val $list var $args { - upvar 1 $var my - set my $val - } -} - -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 usererror {emsg} { error $emsg {} {BLIGHT USER} } - -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 { +proc privmsg_unlogged {prefix ischan params} { + if {!$ischan || + [regexp {^![a-z][-a-z]*[a-z]( .*)?$} [lindex $params 1]]} { 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] - foreach i $args { - if {[regexp {[: ]} $i]} { - error "bad argument in output $i ($command $args)" - } - } - lappend args :$la - } - set args [lreplace $args 0 -1 $command] - set string [join $args { }] - 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 -} - -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 nick calling_nick errorInfo errorCode - - 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]} { - 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 params {} - while {[regexp {^([^ :]+) *(.*)} $line dummy thisword line]} { - lappend params $thisword - } - if {[regexp {^:(.*)} $line dummy thisword]} { - lappend params $thisword - } elseif {[string length $line]} { - log "junk at end: $org" - return - } - if {"$command" == "PRIVMSG" && - [regexp {^[&#+!]} [lindex $params 0]] && - ![regexp {^![a-z][-a-z]*[a-z]( .*)?$} [lindex $params 1]]} { - # on-channel message, ignore - 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" - 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)" - saveeic - } -} - -proc sendprivmsg {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 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} { - global musthaveping_after - prefix_none - sendout PONG $s1 - if {[info exists musthaveping_after]} connected -} - -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" {} {} - } + # on-channel message, ignore + 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 1 } proc showintervalsecs {howlong abbrev} { @@ -634,9 +376,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 @@ -679,9 +422,14 @@ proc msg_JOIN {p c chan} { 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} { @@ -703,30 +451,7 @@ proc msg_PRIVMSG {p c dest text} { } nick_case $n - if {[catch { - regsub {^! *} $text {} text - set ucmd [ta_word] - set procname ucmd/[string tolower $ucmd] - if {[catch { info body $procname }]} { - usererror "Unknown command; try help for help." - } - $procname $p $dest - } 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] { - foreach l [split $val "\n"] { - sendaction_priority 0 $td $l - } - } - foreach {td val} [list $n $priv_msgs $output $pub_msgs] { - foreach l [split $val "\n"] { - sendprivmsg $td $l - } - } - } + execute_usercommand $p $c $n $output $dest $text } proc msg_INVITE {p c n chan} { @@ -781,111 +506,6 @@ proc msg_366 {p c args} { 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 errorInfo - - 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 {^\:\:} $l]} { - } 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 - regsub -all {([^\\])\!\$?} _$l {\1} l - regsub -all {\\(.)} $l {\1} l - regsub {^_} $l {} l - lappend lines [string trimright $l] - } else { - error "eh ? $lno: $l" - } - } - if {[info exists topic]} { error "unfinished topic $topic" } - } { - set errorInfo "in helpinfos line $lno\n$errorInfo" - } { - close $f - } -} - -def_ucmd help { - upvar 1 n n - - set topic [irctolower [string trim $text]] - if {[string length $topic]} { - set ontopic " on `$topic'" - } else { - set ontopic "" - } - 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$ontopic again later." - ucmdr {} {} - } else { - sendaction_priority 1 $replyto \ - "is lagged. Your help$ontopic will arrive shortly ..." - } - } - - upvar #0 help_topics($topic) info - if {![info exists info]} { ucmdr "No help on $topic, sorry." {} } - ucmdr $info {} -} - -def_ucmd ? { - global help_topics - ucmdr $help_topics() {} -} - proc check_username {target} { if { [string length $target] > 8 || @@ -1412,6 +1032,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" + } } } @@ -1661,7 +1284,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" @@ -1717,50 +1340,12 @@ proc ensure_globalsecret {} { unset gsfile } -proc ensure_outqueue {} { - out__vars - if {[info exists out_queue]} return - set out_creditms 0 - 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 socketargs - global musthaveping_ms musthaveping_after - - if {[info exists sock]} return - set sock [eval socket $socketargs [list $host $port]] - fconfigure $sock -buffering line - 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"}] -} - proc connected {} { - global musthaveping_after - - 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