#--------------------------------------------------------------------- # onlinetime.tcl # TCL script for IRC bot eggdrop # # channel usage: !online # channel usage: !online nickname # # With this script the bot retrieves the online time of a nick upon # a public "!online nickname" on a channel. # The nick must be on the channel where the public !online is issued. # The user typing the "!online nick" must have a global or # channel o flag on the bot. # The bot must support RAW bindings. # # v0: 21-Mar-2002 # v1: 22-Mar-2002 # v2: 31-Jan-2003 #--------------------------------------------------------------------- package require eggdrop 1.6.13 package require Tcl 8.0 #--------------------------------------------------------------------- # public trigger for online time #--------------------------------------------------------------------- bind pub o|o !online onlinetime:pubtrigger bind pub o|o !signon onlinetime:pubtrigger proc onlinetime:pubtrigger { nick uhost hand chan targ } { global reqonlinetime # scan for a target nick # if no nick is given, set it to the requester if { [scan $targ "%32s" targ] != 1} { set targ $nick } # target nick is on the channel? if { ![onchan $targ $chan] } { putserv "PRIVMSG $nick :I can't find $targ on $chan." return 1 } # limit the number of pending requests set tgtl [string tolower $targ] if { [array exists reqonlinetime] } { if { [llength [array names reqonlinetime]] >= 2 } { # a request for a NEW nick is not ok if { ![info exists reqonlinetime($tgtl)] } { set string "There are already two requests pending!" putserv "PRIVMSG $nick :$string" return 1 } } } # check the existence of an !online request for a nick... # if it does exist, check the channel where it was requested to # avoid double notifications of the online time. # if it does not exist, send a WHOIS if { [info exists reqonlinetime($tgtl)] } { # signon for nick already requested... # determine on which channels. foreach item $reqonlinetime($tgtl) { set reqchan [lindex $item 0] if { [string compare $reqchan $chan] == 0 } { # a request from this channel already exists for the nick. # no need to add it to the "reqonlinetime" list set string "There is a pending !online request for $targ." putserv "PRIVMSG $nick :$string" return 1 } } } else { # a WHOIS request for the nick does not exist: create one putserv "WHOIS $tgtl $tgtl" } # add a sublist to the list for the nick set sublist [list $chan $nick [unixtime]] lappend reqonlinetime($tgtl) $sublist # log the !online request return 1 } #--------------------------------------------------------------------- # RAW reply 317 by the server #--------------------------------------------------------------------- bind RAW - 317 onlinetime:raw317 proc onlinetime:raw317 { server keyword arg } { global botnick global reqonlinetime # Extract nick and check for pending request. # Also scan out the variables: # itime = idle time in seconds # ctime = connect time set scanrule "$botnick %s %i %i" set scancount [scan $arg $scanrule nick itime ctime] if { $scancount < 2 } { return 0 } # Check if a request is pending for nick. set nktl [string tolower $nick] if { ![info exists reqonlinetime($nktl)] } { # may occur right after the bot connects to the server. putlog "ONLINETIME: WARNING: received 317, but didn't request." return 0 } #------------------------------------------------------------------ # Case I: signon timestamp and idletime available. #------------------------------------------------------------------ set maskrule "$botnick * * * :seconds idle, signon time" if { [string match $maskrule $arg] && $scancount == 3 } { # idletime if { $itime < 0 } { set itime 0 } set idletime [onlinetime:hrsminsec $itime] # connect time set sec [expr [unixtime] - int($ctime)] # server clock or shell clock not synched? if { $sec < 0 || $itime > $ctime } { # clocks out of synch... # report signon time date idletime set format "%H:%M:%S %d/%b/%Y" set signon [clock format $ctime -format $format -gmt YES] set string "$nick signed on $signon GMT ($idletime idle)." } else { set onlinetime [onlinetime:hrsminsec $sec] set string "$nick is online for $onlinetime on $server" set string "$string ($idletime idle)." } # report signon and idletime onlinetime:report $nktl $string return 0 } #------------------------------------------------------------------ # Case II: RFC 1459 compliant? #------------------------------------------------------------------ set maskrule "$botnick * * :seconds idle" if {![string match $maskrule $arg] && $scancount == 2 } { # prepare idletime if { $itime < 0 } { set itime 0 } set idletime [onlinetime:hrsminsec $itime] set string "$nick is $idletime idle on $server (unknown\ signon time)." # report idletime onlinetime:report $nktl $string return 0 } #------------------------------------------------------------------ # Case III: The server response isn't RFC 1459 compliant! #------------------------------------------------------------------ putlog "ONLINETIME: ERROR: 317 argument doesn't match RFC." set string "Foo bar. Cannot determine signon/idletime for $nick." onlinetime:report $nktl $string return 0 } #--------------------------------------------------------------------- # Once every minute check the array of pending requests and purge # if a reply was not received in 100 seconds. #--------------------------------------------------------------------- bind time - "* * * * *" onlinetime:timecheck proc onlinetime:timecheck { args } { global reqonlinetime if {![array exists reqonlinetime]} { return } foreach nick [array names reqonlinetime] { # the first sublist is also chronologically first set sublist [lindex $reqonlinetime($nick) 0] # check the logtime of the first sublist # set reqchan [lindex $sublist 0] # set reqnick [lindex $sublist 1] set reqtime [lindex $sublist 2] # if the first request is less than 100 seconds old: # go on to next nick ... if { [expr [unixtime] - $reqtime] < 100 } { continue } # ... if older than 100 seconds: # notify the channels where a request was made ... set string "Couldn't retrieve signon time for $nick. Try again!" onlinetime:report $nick $string } } #--------------------------------------------------------------------- # onlinetime:hrsminsec returns the onlinetime string. # Output examples: # 1 second # 10 seconds # 1 minute and 0 seconds # 5 minutes and 12 seconds # 1 hour, 0 seconds # 1 hour, 1 second # 3 hours, 5 minutes and 0 seconds #--------------------------------------------------------------------- proc onlinetime:hrsminsec { sec } { if { $sec < 0 } { set sec 0 } # awkward... set spm 60.0 set sph 3600.0 set hrs [expr int($sec/$sph)] set sec [expr int($sec - $hrs*$sph)] set min [expr int($sec/$spm)] set sec [expr int($sec - $min*$spm)] # awkward... switch -- $hrs { 0 { set hrs "" } 1 { set hrs "1 hour, " } default { set hrs "$hrs hours, " } } switch -- $min { 0 { set min "" } 1 { set min "1 minute and " } default { set min "$min minutes and " } } switch -- $sec { 1 { set sec "1 second" } default { set sec "$sec seconds" } } return $hrs$min$sec } #--------------------------------------------------------------------- # report a string to the request channels for nick. #--------------------------------------------------------------------- proc onlinetime:report { nick string } { global reqonlinetime # just to make sure ... set nick [string tolower $nick] if {![info exists reqonlinetime($nick)]} { return } # output to the requested channels foreach item $reqonlinetime($nick) { set chan [lindex $item 0] if {![validchan $chan]} { continue } if {![botonchan $chan]} { continue } putserv "PRIVMSG $chan :$string" } # unset the requests for this nick unset reqonlinetime($nick) } #--------------------------------------------------------------------- # On restart/rehash check the pending requests. #--------------------------------------------------------------------- onlinetime:timecheck putlog "Loaded (version 2): Onlinetime."