# Script : TopicEngine v1 # Copyright 2001-3 James Seward # $Id: TopicEngine.tcl,v 1.11 2003/02/05 23:24:37 jamesoff Exp $ # # Testing # Platforms : Linux 2.4 # Eggdrop v1.6.4-13 # # Description : Advanced script to change/set/lock topics # See readme for full info # # # Author Contact : Email - james@jamesoff.net # Home Page - http://www.jamesoff.net # IRC - Nick: JamesOff (EFNet) # ICQ - 1094325 (mention this script in your auth req, else you'll get ignored :) # # Credit to : Dan Durrans, for coming up with the idea and feature list # #exeter and #ags people for testing it # # ############################################################################### # TopicEngine - a topic management TCL script for eggdrops # Copyright (C) James Michael Seward 2000-2003 # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ############################################################################### #Don't change this unless you want to RUIN the script and destroy the world :) set topicEngineLoad 0 # register the topicEngine channel flag setudef flag topicengine # # This is the path to the config file's directory # If you use a relative path, it starts from the directory your eggdrop runs in # No trailing slash! set topicConfigPath "scripts" catch { source "${topicConfigPath}/TopicEngineSettings.tcl" if {[info exists botnet-nick]} { #load a bot-specific file source "${topicConfigPath}/TopicEngineSettings_${botnet-nick}.tcl" } } ##### # Shouldn't need to change stuff below here (but you can if you need to and know # what you're doing) ################################################################################################### set revision "20060108.1" #init some info set topicInfo(pop,version) $revision set backupInfo(pop,version) $revision set topicChannels [list] #split tracking set topicSplitChans [list] set topicLastSplit [clock seconds] set topicSplitDirty 0 if {![info exists topicEngineOnline]} { set topicEngineOnline 0 } # init set loops 0 set bufferDirty 0 ##### # topicInitArray # # Builds the topicInfo array with the default values # Won't rebuild if topicEngineOnline is 1 (to stop it overwriting # data that's in use # proc topicInitArray { {loaded 0} } { global topicInfo topicEngineOnline set onChannels [channels] set hasError 0 #change the channels list to all lowercase set newChannels [list] foreach chan $onChannels { #check if this channel is +topicengine if [channel get $chan topicengine] { lappend newChannels [string tolower $chan] } } set topicChannels $newChannels foreach chan $topicChannels { set test "" catch { set test $topicInfo($chan,initialised) } if {$test == ""} { #loading for the first time putlog "topicengine: init topicInfo for $chan" # These are the defaults, you may change them # override them with values in TopicEngineSettings.tcl # explainations of these settings are also in that file set topicInfo($chan,leadIn) "" set topicInfo($chan,leadOut) "" #set topicInfo($chan,needOChan) 1 #set topicInfo($chan,needVChan) 0 #set topicInfo($chan,needOGlobal) 0 #set topicInfo($chan,needVGlobal) 0 #set topicInfo($chan,needOMode) 1 #set topicInfo($chan,needVMode) 0 #set topicInfo($chan,Tflag) 1 set topicInfo($chan,canFlags) "o|ov" set topicInfo($chan,canModes) "ov" set topicInfo($chan,cantFlags) "T" set topicInfo($chan,topicBits) [list] set topicInfo($chan,learnOnChange) 1 set topicInfo($chan,initialised) "1" #DO NOT CHANGE THESE set topicInfo($chan,topic) "" set topicInfo($chan,whoSet) [list] set topicInfo($chan,whenSet) [list] set topicInfo($chan,whenLastSet) 0 set topicInfo($chan,whoLastSet) 0 set topicInfo($chan,lock) [list 0 "" ""] } else { if {$loaded == 0} { #sync the topics if we're online catch { if {[lsearch $onChannels $chan] >= 0} { #putlog "Updating topic in $chan" setTopic $chan } set blah 0 } err if {$err != 0} { putlog "topicengine: $chan not initialised! (is it new?)" set topicInfo($chan,leadOut) "" #set topicInfo($chan,needOChan) 1 #set topicInfo($chan,needVChan) 0 #set topicInfo($chan,needOGlobal) 0 #set topicInfo($chan,needVGlobal) 0 #set topicInfo($chan,needOMode) 1 #set topicInfo($chan,needVMode) 0 #set topicInfo($chan,Tflag) 1 set topicInfo($chan,canFlags) "o|ov" set topicInfo($chan,canModes) "ov" set topicInfo($chan,cantFlags) "T" set topicInfo($chan,topicBits) [list] set topicInfo($chan,learnOnChange) 1 set topicInfo($chan,initialised) "1" #DO NOT CHANGE THESE set topicInfo($chan,topic) "" set topicInfo($chan,whoSet) [list] set topicInfo($chan,whenSet) [list] set topicInfo($chan,whenLastSet) 0 set topicInfo($chan,whoLastSet) 0 set topicInfo($chan,lock) [list 0 "" ""] set hasError 1 } } } } if {($topicEngineOnline == 0) || ($hasError == 1)} { # now load the user defaults # you should edit this file to give each channel the settings you want global topicConfigPath botnet-nick catch { set topicEngineLoad 1 source "${topicConfigPath}/TopicEngineSettings.tcl" if {[info exists botnet-nick]} { #load a bot-specific file source "${topicConfigPath}/TopicEngineSettings_${botnet-nick}.tcl" } } } } #### You won't need to change anything below this point ### ##### # checkTopic # # Tries out a new topic (=current topic | newbit) # returns 0 if it'll fit in the network topic length limit # else returns the number of characters over the limit # allows for the " | " between topic elements # proc checkTopic {channel newbit {noOldTopic 0}} { global topicLengthLimit topicInfo topicSeparator #append the new bit onto the topic list if {$noOldTopic == 0} { set thisTopic $topicInfo($channel,topicBits) } else { #don't count the old topic, so initalise the 'old' one set thisTopic [list] } if {$newbit != ""} { lappend thisTopic $newbit } if {$topicInfo($channel,leadIn) != ""} { set thisTopic [linsert $thisTopic 0 $topicInfo($channel,leadIn)] } if {$topicInfo($channel,leadOut) != ""} { set thisTopic [linsert $thisTopic end $topicInfo($channel,leadOut)] } set topicString "" foreach bit $thisTopic { append topicString $bit append topicString " $topicSeparator " } set topicString [string range $topicString 0 [expr [string length $topicString] - 4 ]] set topicLength [string length $topicString] if {$topicLength > $topicLengthLimit} { return [expr $topicLength - $topicLengthLimit] } return 0 } ##### # backupTopic {channel} # # saves the topic to another array so it can be recovered if needed # proc backupTopic { channel } { global topicInfo backupInfo set backupInfo($channel,topicBits) $topicInfo($channel,topicBits) set backupInfo($channel,topic) $topicInfo($channel,topic) set backupInfo($channel,whoSet) $topicInfo($channel,whoSet) set backupInfo($channel,whenSet) $topicInfo($channel,whenSet) set backupInfo($channel,whoLastSet) $topicInfo($channel,whoLastSet) set backupInfo($channel,whenLastSet) $topicInfo($channel,whenLastSet) return 0 } ##### # setTopic {channel, force = 0} # # sets the topic based on the topicBits list # will not change the topic if doesn't need it, unless force is 1 # proc setTopic {channel {force 0}} { global topicInfo loops topicSeparator bufferDirty topicEngineOnline putloglev d * "topicengine: updating topic for $channel (force = $force)" #debug for recursive stuff set loops 0 #if we're not opped, don't even bother if {![botisop $channel]} { putlog "topicengine: er, I'm not opped in $channel, so I can't set the topic :(" return 0 } set thisTopic $topicInfo($channel,topicBits) ##leadin and leadout if {$topicInfo($channel,leadIn) != ""} { putloglev 1 * "topicengine: adding prefix" set thisTopic [linsert $thisTopic 0 $topicInfo($channel,leadIn)] } if {$topicInfo($channel,leadOut) != ""} { putloglev 1 * "topicengine: adding postfix" set thisTopic [linsert $thisTopic end $topicInfo($channel,leadOut)] } ##build topic string set topicString "" foreach bit $thisTopic { append topicString $bit append topicString " $topicSeparator " } set topicString [string range $topicString 0 [expr [string length $topicString] - 4 ]] #interpolate time stuff set loops 0 while {[regexp "_TIME\{(.+?)\}" $topicString matches timeformat]} { incr loops if {$loops > 10} { set line "" putlog "topicengine: TREMENDOUS FAILURE! Topic for $channel couldn't be generated." return 1 } set timeString [clock format [clock seconds] -format $timeformat] regsub -all "_TIME{$timeformat}" $topicString $timeString topicString } #putlog "topicengine: final topic for $channel is $topicString" set topicInfo($channel,topic) $topicString if {([topic $channel] == $topicString) && ($force == 0)} { return 0 } if {$force == -1} { #just update the topic cache return 0 } putserv "TOPIC $channel :$topicString" set bufferDirty 0 return 0 } ##### # topicChanged # # called when the topic is changed on a channel # checks if the topic is locked and changes it back if it needs to # if topicInfo(channel,learnOnChange) is 1, will parse this topic and learn it # proc topicChanged {nick host handle channel text} { global topicInfo topicChannels #check the topic script is active in here if {![channel get $channel topicengine]} { return 0 } #this is because the array isn't initialised when the bot first starts topicInitArray 1 #if it's me, drop if [isbotnick $nick] { return 0 } #if it's the same topic as before, drop if {$text == $topicInfo($channel,topic)} { return 0 } #if it's an empty topic, redo it and don't learn it if {$text == ""} { setTopic $channel return 0 } #if it's a bot setting the topic, ignore it if [matchattr $handle b] { return 0 } if {[lindex $topicInfo($channel,lock) 0] == 0} { #learn it? if {$topicInfo($channel,learnOnChange) == 1} { putlog "Topic in $channel changed, learning it" #back it up backupTopic $channel topicParse $channel $text $nick } return 0 } #it's locked, put it back and notify setTopic $channel 1 set whoLocked [lindex $topicInfo($channel,lock) 1] set whenLocked [clock format [lindex $topicInfo($channel,lock) 2]] putserv "NOTICE $nick :Sorry, the topic for $channel was locked by $whoLocked on $whenLocked." putlog "Bouncing topic in $channel" return 0 } ##### # topicCommand # # interact with the user, accepts the !topic... commands # proc topicCommand {nick host handle channel text {silent 0} } { global topicInfo topicChannels loops bufferDirty set doBuffer 0 #putlog "topic command: $text ($channel)" #check the topic script is active in here if {![channel get $channel topicengine]} { return 0 } #check I'm opped there if {![botisop $channel]} { topicNotice "I'm not opped in $channel; I can't manage the topic." return 0 } #lowercase the channel (for case insensitivity in the array) set channel [string tolower $channel] set text [string trim $text] incr loops if {$loops > 5} { set loops 0 putlog "TopicEngine internal error: recursive looping. Aborting processing of this command." return 0 } #this is because the array isn't initialised when the bot first starts topicInitArray 1 #these commands can be used by anyone ################# INFO if [regexp -nocase "^info ?(.+)?" $text boom param] { set loops 0 if {$param == ""} { set updateTime [clock format $topicInfo($channel,whenLastSet)] if {[lindex $topicInfo($channel,lock) 0] != 0} { set whoLocked [lindex $topicInfo($channel,lock) 1] set whenLocked [clock format [lindex $topicInfo($channel,lock) 2]] set lockedString " ... locked \002\[\002 $whoLocked | $whenLocked \002\]\002" } else { set lockedString "" } set undoString " ... undo \002\[\002 no \002\]\002" catch { global backupInfo if {$backupInfo($channel,topicBits) != ""} { set undoString " ... undo \002\[\002 yes \002\]\002" } } if {$bufferDirty == 1} { set bufferString " ... buffer \002\[\002 dirty \002\]\002" } else { set bufferString "" } global topicInfoBroadcast topicType if {$topicInfoBroadcast == 0} { topicNotice "$channel: topic \002\[\002 $topicInfo($channel,topic) \002\]\002 ... changed \002\[\002 $topicInfo($channel,whoLastSet) | $updateTime \002\]\002${lockedString}${bufferString}${undoString}" } else { if {$topicType == "pub"} { putserv "PRIVMSG $channel :$channel: topic \002\[\002 $topicInfo($channel,topic) \002\]\002 ... changed \002\[\002 $topicInfo($channel,whoLastSet) | $updateTime \002\]\002${lockedString}${bufferString}${undoString}" } else { #msg and dcc topicNotice "$channel: topic \002\[\002 $topicInfo($channel,topic) \002\]\002 ... changed \002\[\002 $topicInfo($channel,whoLastSet) | $updateTime \002\]\002${lockedString}${bufferString}${undoString}" } } return 1 } if {$param == "undo"} { set undoString "\002\[\002 unavailable \002\]\002" catch { global backupInfo topicType if {$backupInfo($channel,topicBits) != ""} { set undoString "\002\[\002 $backupInfo($channel,topic) \002\]\002" } } if {$topicType == "pub"} { putserv "PRIVMSG $channel :$channel: topic undo $undoString" } else { #msg and dcc topicNotice "$channel: topic undo $undoString" } return 1 } set elementCount [llength $topicInfo($channel,topicBits)] if {$param < 1} { topicNotice "Error: topic index too low!" return 1 } if {$param > $elementCount} { topicNotice "Error: topic index too high!" return 1 } set actparam [expr $param - 1] set updateTime [clock format [lindex $topicInfo($channel,whenSet) $actparam]] global topicInfoBroadcast topicType if {$topicInfoBroadcast == 0} { topicNotice "$channel: element \002\[\002 $param = [lindex $topicInfo($channel,topicBits) $actparam] \002\]\002 ... set by \002\[\002 [lindex $topicInfo($channel,whoSet) $actparam] | $updateTime \002\]\002" } else { if {$topicType == "pub"} { putserv "PRIVMSG $channel :$channel: element \002\[\002 $param = [lindex $topicInfo($channel,topicBits) $actparam] \002\]\002 ... set by \002\[\002 [lindex $topicInfo($channel,whoSet) $actparam] | $updateTime \002\]\002" } else { #msg and dcc topicNotice "$channel: element \002\[\002 $param = [lindex $topicInfo($channel,topicBits) $actparam] \002\]\002 ... set by \002\[\002 [lindex $topicInfo($channel,whoSet) $actparam] | $updateTime \002\]\002" } } return 1 } ################# HELP if [regexp -nocase "^help" $text] { global botnick topicNotice "Please use \002/msg $botnick topic help\002 for a command list" return 1 } ################# VERSION if [string match -nocase "version" $text] { topicNotice "I am running JamesOff's TopicEngine version $topicInfo(pop,version)." return 1 } #these commands need permissions set canTopic 0 #first we need to check global O and V #if {($topicInfo($channel,needOGlobal) == 1) && [matchattr $handle o]} { set canTopic 1 } #if {($topicInfo($channel,needVGlobal) == 1) && [matchattr $handle v]} { set canTopic 1 } #now local O and V #if {($topicInfo($channel,needOChan) == 1) && [matchattr $handle -|o $channel]} { set canTopic 1 } #if {($topicInfo($channel,needVChan) == 1) && [matchattr $handle -|v $channel]} { set canTopic 1 } #now modes in the channel #if {($topicInfo($channel,needOMode) == 1) && [isop $nick $channel]} { set canTopic 1 } #if {($topicInfo($channel,needVMode) == 1) && [isvoice $nick $channel]} { set canTopic 1 } #now finally the T flag... this is different - if you HAVE it you can't set a topic #Hack by Artoo; users with T flag can change topic, users with U flag can not #if {($topicInfo($channel,Tflag) == 1) && [matchattr $handle T]} { set canTopic 1 } #if {($topicInfo($channel,Tflag) == 1) && [matchattr $handle -|U $channel]} { set canTopic 0 } #if {($topicInfo($channel,Tflag) == 1) && [matchattr $handle U]} { set canTopic 0 } #if {($topicInfo($channel,Tflag) == 1) && [matchattr $handle -|T $channel]} { set canTopic 1 } #New system: using channel settings # First we check canFlags, if the user has these eggdrop flags we'll allow use if [matchattr $handle $topicInfo($channel,canFlags) $channel] { putloglev d * "topicEngine: user $handle matches flags" set canTopic 1 } # Now check canModes, if the user has one of these modes we'll allow use set modesList [split $topicInfo($channel,canModes) {}] foreach modeChar $modesList { if {($modeChar == "o") && [isop $nick $channel]} { putloglev d * "topicEngine: user $nick is an op, allowed" set canTopic 1 break } if {($modeChar == "v") && [isvoice $nick $channel]} { putloglev d * "topicEngine: user $nick is a voice, allowed" set canTopic 1 break } if {($modeChar == "h") && [ishalfop $nick $channel]} { putloglev d * "topicEngine: user $nick is a halfop, allowed" set canTopic 1 break } } # Finally, if a user matches cantFlags, we'll disable the script for them (regardless of # any other match so far if [matchattr $handle $topicInfo($channel,cantFlags) $channel] { putloglev d * "topicEngine: user $handle matches flags, disallowed" set canTopic 0 } if {$canTopic == 0} { topicNotice "Sorry, you cannot use the !topic commands" return 1 } ################# UNDO if [regexp -nocase "^undo(.+)?" $text boom params] { if {([lindex $topicInfo($channel,lock) 0] != 0) && (![matchattr $handle |n $channel])} { set whoLocked [lindex $topicInfo($channel,lock) 1] topicNotice "Sorry, the topic has been locked by $whoLocked." return 1 } set loops 0 set params [string trim $params] if {([string first "~" $params] == 0) || [string match -nocase "buffer" $params]} { #buffer the topic set bufferDirty 1 set doBuffer 1 } global backupInfo if {$backupInfo($channel,topicBits) == ""} { topicNotice "Sorry, no undo is available for $channel" return 2 } set topicInfo($channel,topicBits) $backupInfo($channel,topicBits) set topicInfo($channel,whoSet) $backupInfo($channel,whoSet) set topicInfo($channel,whenSet) $backupInfo($channel,whenSet) set topicInfo($channel,whoLastSet) "$backupInfo($channel,whoLastSet)/undo:$nick" set topicInfo($channel,whenLastSet) [clock seconds] set backupInfo($channel,topicBits) "" set backupInfo($channel,topic) "" if {$doBuffer == 0} { setTopic $channel } else { setTopic $channel -1 } return 1 } ################# ADD if [regexp -nocase "^add (.+)" $text boom params] { if {([lindex $topicInfo($channel,lock) 0] != 0) && (![matchattr $handle |n $channel])} { set whoLocked [lindex $topicInfo($channel,lock) 1] topicNotice "Sorry, the topic has been locked by $whoLocked." return 1 } set loops 0 if {[string first "@" $params] == 0} { #lock the topic topicCommand $nick $host $handle $channel "lock" set params [string range $params 1 end] } if {[string first "~" $params] == 0} { #buffer the topic set bufferDirty 1 set doBuffer 1 set params [string range $params 1 end] } #remove extra spaces set params [string trim $params] regsub -all " +" $params " " params #check length set tooMany [checkTopic $channel $params] if {$tooMany} { topicNotice "Sorry, adding that to the topic would make it go over the length limit by $tooMany characters. Please try a shorter topic, or the 'append' (<<<) command instead of 'add' (+)" return 1 } backupTopic $channel lappend topicInfo($channel,topicBits) $params lappend topicInfo($channel,whoSet) $nick lappend topicInfo($channel,whenSet) [clock seconds] set topicInfo($channel,whoLastSet) $nick set topicInfo($channel,whenLastSet) [clock seconds] if {$doBuffer == 0} { setTopic $channel } else { setTopic $channel -1 } return 1 } ################# APPEND if [regexp -nocase "^append (.+)" $text boom params] { if {([lindex $topicInfo($channel,lock) 0] != 0) && (![matchattr $handle |n $channel])} { set whoLocked [lindex $topicInfo($channel,lock) 1] topicNotice "Sorry, the topic has been locked by $whoLocked." return 1 } set loops 0 if {[string first "@" $params] == 0} { #lock the topic topicCommand $nick $host $handle $channel "lock" set params [string range $params 1 end] } if {[string first "~" $params] == 0} { #buffer the topic set bufferDirty 1 set doBuffer 1 set params [string range $params 1 end] } #remove extra spaces set params [string trim $params] regsub -all " +" $params " " params #check length set tooMany [checkTopic $channel $params] set originalTopicBits $topicInfo($channel,topicBits) set originalTopicWho $topicInfo($channel,whoSet) set originalTopicWhen $topicInfo($channel,whenSet) set count 0 while {$tooMany} { #delete element 1 (return 2 = failed to delete) set result [topicCommand $nick $host $handle $channel "del ~1" 1] if {$result == 2} { #wahey topicNotice "Sorry, couldn't fit that in the topic. Please try something shorter" set topicInfo($channel,topicBits) $originalTopicBits set topicInfo($channel,topicWho) $originalTopicWho set topicInfo($channel,topicWhen) $originalTopicWhen return 2 } set tooMany [checkTopic $channel $params] incr count if {$count == 100} { puthelp "ALERT: Looping too much in TopicEngine (append)" return 0 } } backupTopic $channel lappend topicInfo($channel,topicBits) $params lappend topicInfo($channel,whoSet) $nick lappend topicInfo($channel,whenSet) [clock seconds] set topicInfo($channel,whoLastSet) $nick set topicInfo($channel,whenLastSet) [clock seconds] if {$doBuffer == 0} { setTopic $channel } else { setTopic $channel -1 } return 1 } ################# INSERT if [regexp -nocase "^insert (.+)" $text boom params] { if {([lindex $topicInfo($channel,lock) 0] != 0) && (![matchattr $handle |n $channel])} { set whoLocked [lindex $topicInfo($channel,lock) 1] topicNotice "Sorry, the topic has been locked by $whoLocked." return 1 } set loops 0 if {[string first "@" $params] == 0} { #lock the topic topicCommand $nick $host $handle $channel "lock" set params [string range $params 1 end] } if {[string first "~" $params] == 0} { #buffer the topic set bufferDirty 1 set doBuffer 1 set params [string range $params 1 end] } #remove extra spaces set params [string trim $params] regsub -all " +" $params " " params #check length set tooMany [checkTopic $channel $params] set originalTopicBits $topicInfo($channel,topicBits) set originalTopicWho $topicInfo($channel,whoSet) set originalTopicWhen $topicInfo($channel,whenSet) set count 0 while {$tooMany} { #delete last element (return 2 = failed to delete) set lastElement [llength $topicInfo($channel,topicBits)] set result [topicCommand $nick $host $handle $channel "del ~$lastElement" 1] if {$result == 2} { #wahey topicNotice "Sorry, couldn't fit that in the topic. Please try something shorter" set topicInfo($channel,topicBits) $originalTopicBits set topicInfo($channel,topicWho) $originalTopicWho set topicInfo($channel,topicWhen) $originalTopicWhen return 2 } set tooMany [checkTopic $channel $params] incr count if {$count == 100} { puthelp "ALERT: Looping too much in TopicEngine (insert)" return 0 } } backupTopic $channel set topicInfo($channel,topicBits) [linsert $topicInfo($channel,topicBits) 0 $params] set topicInfo($channel,whoSet) [linsert $topicInfo($channel,whoSet) 0 $nick] set topicInfo($channel,whenSet) [linsert $topicInfo($channel,whenSet) 0 [clock seconds]] set topicInfo($channel,whoLastSet) $nick set topicInfo($channel,whenLastSet) [clock seconds] if {$doBuffer == 0} { setTopic $channel } else { setTopic $channel -1 } return 1 } ################# SET if [regexp -nocase "^set (.+)" $text pop cmdString] { if {([lindex $topicInfo($channel,lock) 0] != 0) && (![matchattr $handle |n $channel])} { set whoLocked [lindex $topicInfo($channel,lock) 1] topicNotice "Sorry, the topic has been locked by $whoLocked." return 1 } set loops 0 if [regexp -nocase "^(pre|post)fix (.+)" $cmdString boom which what] { set which [string tolower $which] if {$what == "none"} { set what "" } if {$which == "pre"} { set tooMany [checkTopic $channel $what] if {$tooMany} { topicNotice "Sorry, adding that to the topic would make it go over the length limit by $tooMany characters. Please try a shorter topic." return 1 } set topicInfo($channel,leadIn) $what setTopic $channel return 1 } if {$which == "post"} { set tooMany [checkTopic $channel $what] if {$tooMany} { topicNotice "Sorry, adding that to the topic would make it go over the length limit by $tooMany characters. Please try a shorter topic." return 1 } set topicInfo($channel,leadOut) $what setTopic $channel return 1 } } #else set the topic as is if {$cmdString == "none"} { set cmdString "" } if {[string first "@" $cmdString] == 0} { #lock the topic topicCommand $nick $host $handle $channel "lock" set cmdString [string range $cmdString 1 end] } if {[string first "~" $cmdString] == 0} { #buffer the topic set bufferDirty 1 set doBuffer 1 set cmdString [string range $cmdString 1 end] } set tooMany [checkTopic $channel $cmdString 1] if {$tooMany} { topicNotice "Sorry, adding that to the topic would make it go over the length limit by $tooMany characters. Please try a shorter topic." return 1 } #remove extra spaces set cmdString [string trim $cmdString] regsub -all " +" $cmdString " " cmdString backupTopic $channel set topicInfo($channel,whoSet) [list $nick] set topicInfo($channel,whenSet) [list [clock seconds]] set topicInfo($channel,whoLastSet) $nick set topicInfo($channel,whenLastSet) [clock seconds] set topicInfo($channel,topicBits) [list $cmdString] if {$doBuffer == 0} { setTopic $channel } else { setTopic $channel -1 } return 1 } ################# DEL if [regexp -nocase "^del (.+)" $text boom param] { if {([lindex $topicInfo($channel,lock) 0] != 0) && (![matchattr $handle |n $channel])} { set whoLocked [lindex $topicInfo($channel,lock) 1] topicNotice "Sorry, the topic has been locked by $whoLocked." return 1 } set loops 0 #update the cached version setTopic $channel -1 if {[string first "~" $param] == 0} { #buffer the topic set bufferDirty 1 set doBuffer 1 set param [string range $param 1 end] } set elementCount [llength $topicInfo($channel,topicBits)] if {$param < 1} { if {$silent == 0} { topicNotice "Error: topic index too low!" } return 1 } if {$param > $elementCount} { if {$silent == 0} { topicNotice "Error: topic index too high!" } return 2 } backupTopic $channel set param [expr $param - 1] set topicInfo($channel,topicBits) [lreplace $topicInfo($channel,topicBits) $param $param] set topicInfo($channel,whoSet) [lreplace $topicInfo($channel,whoSet) $param $param] set topicInfo($channel,whenSet) [lreplace $topicInfo($channel,whenSet) $param $param] set topicInfo($channel,whoLastSet) $nick set topicInfo($channel,whenLastSet) [clock seconds] if {$doBuffer == 0} { setTopic $channel } else { setTopic $channel -1 } return 1 } ################# REGEXP if [regexp -nocase {^regexp ([^ ]+) (.+)} $text matches param re] { if {([lindex $topicInfo($channel,lock) 0] != 0) && (![matchattr $handle |n $channel])} { set whoLocked [lindex $topicInfo($channel,lock) 1] topicNotice "Sorry, the topic has been locked by $whoLocked." return 1 } set loops 0 #update the cached version setTopic $channel -1 if {[string first "~" $re] == 0} { #buffer the topic set bufferDirty 1 set doBuffer 1 set param [string range $param 1 end] } set elementCount [llength $topicInfo($channel,topicBits)] if {$param < 1} { if {$silent == 0} { topicNotice "Error: topic index too low!" } return 1 } if {$param > $elementCount} { if {$silent == 0} { topicNotice "Error: topic index too high!" } return 2 } if {![regexp {/(.+)/([^/]+)?/(.+)?} $re matches refirst resecond reopts]} { topicNotice "Error: not a valid regexp. Use \002/match/replace/options\002." return 2 } set param [expr $param - 1] set topicElement [lindex $topicInfo($channel,topicBits) $param] set options "" if [string match "*i*" $reopts] { set options "-nocase" } if [string match "*g*" $reopts] { append options "-all" } if {$options != ""} { regsub $options $refirst $topicElement $resecond topicElement } else { regsub $refirst $topicElement $resecond topicElement } set oldTopic $topicInfo($channel,topicBits) set limit [checkTopic $channel ""] if {$limit > 0} { topicNotice "Sorry, that would make the topic go over the limit by $limit characters." set topicInfo($channel,topicBits) $oldTopic return 2 } backupTopic $channel set topicInfo($channel,topicBits) [lreplace $topicInfo($channel,topicBits) $param $param $topicElement] set topicInfo($channel,whoSet) [lreplace $topicInfo($channel,whoSet) $param $param "$nick/regexp"] set topicInfo($channel,whenSet) [lreplace $topicInfo($channel,whenSet) $param $param [clock seconds]] set topicInfo($channel,whoLastSet) $nick set topicInfo($channel,whenLastSet) [clock seconds] if {$doBuffer == 0} { setTopic $channel } else { setTopic $channel -1 } return 1 } ################# REHASH if [regexp -nocase "^(rehash|redo)( force)?" $text pop whee force] { set mustRedo 0 if [string match -nocase " force" $force] { set mustRedo 1 } setTopic $channel $mustRedo return 1 } ################# RESET/CLEAR if [regexp -nocase "^(clear|reset)( (content|all))?" $text pop blblbl whee opt] { if {([lindex $topicInfo($channel,lock) 0] != 0) && (![matchattr $handle |n $channel])} { set whoLocked [lindex $topicInfo($channel,lock) 1] topicNotice "Sorry, the topic has been locked by $whoLocked." return 1 } set loops 0 if {$opt == "all"} { set topicInfo($channel,leadIn) "" set topicInfo($channel,leadOut) "" set topicInfo($channel,topicBits) [list] set topicInfo($channel,whoSet) [list] set topicInfo($channel,whenSet) [list] setTopic $channel set topicInfo($channel,whoLastSet) $nick set topicInfo($channel,whenLastSet) [clock seconds] return 1 } if {$opt == "content"} { set topicInfo($channel,topicBits) [list] set topicInfo($channel,whoSet) [list] set topicInfo($channel,whenSet) [list] setTopic $channel set topicInfo($channel,whoLastSet) $nick set topicInfo($channel,whenLastSet) [clock seconds] return 1 } set topicInfo($channel,topic) "" if { [topic $channel] != ""} { putserv "TOPIC $channel :" } return 1 } ################# LOCK if [string match -nocase "lock" $text] { if {![matchattr $handle |n $channel]} { topicNotice "Sorry, you cannot lock the topic." return 1 } set loops 0 if {[lindex $topicInfo($channel,lock) 0] == 1} { topicNotice "The topic is already locked." return 1 } set topicInfo($channel,lock) [list 1 $nick [clock seconds]] topicNotice "Locking topic for $channel." #check the topic is the cached one if {$topicInfo($channel,topic) != [topic $channel]} { setTopic $channel } # get any other bots that are locking this channel to unlock it putallbots "topicengine unlock $channel" return 1 } ################# UNLOCK if [string match -nocase "unlock" $text] { if {![matchattr $handle |n $channel]} { topicNotice "Sorry, you cannot unlock the topic." return 1 } set loops 0 if {[lindex $topicInfo($channel,lock) 0] == 0} { topicNotice "The topic is already unlocked." return 1 } set topicInfo($channel,lock) [list 0 "" ""] topicNotice "Unlocking topic in $channel." return 1 } ################# shortcuts if [regexp -nocase "^>>>(.+)" $text pop extra] { #append topicCommand $nick $host $handle $channel "insert $extra" return 1 } if [regexp -nocase "^<<<(.+)" $text pop extra] { #append topicCommand $nick $host $handle $channel "append $extra" return 1 } if [regexp -nocase "^\\\+(.+)" $text pop actual] { #add topicCommand $nick $host $handle $channel "add $actual" return 1 } if [regexp -nocase "^\\\-(.+)" $text pop actual] { #del topicCommand $nick $host $handle $channel "del $actual" return 1 } if [regexp -nocase "^\\\=(.+)" $text pop actual] { #set topicCommand $nick $host $handle $channel "set $actual" return 1 } if [regexp -nocase "^\\\?(.+)?" $text pop actual] { #info topicCommand $nick $host $handle $channel "info $actual" return 1 } if [regexp -nocase "^#(!)?" $text pop extra] { #rehash if {$extra == "!"} { set extra "force" } topicCommand $nick $host $handle $channel "rehash $extra" return 1 } if [regexp -nocase {^/([0-9]+)(.+)} $text matches index exp] { #regexp topicCommand $nick $host $handle $channel "regexp $index $exp" return 1 } ##If we got here, they used the command wrong # assume they meant set, and tell them how to get help ##Just double-check it's not that they left off all the text entirely, in which case we'll tell them the topic, and how to get help. if {$text == ""} { topicCommand $nick $host $handle $channel "info" global botnick topicNotice "\[FYI\] For help on the !topic commands, please do \002/msg $botnick topic help\002." return 1 } #topicCommand $nick $host $handle $channel "set $text" global botnick topicNotice "\[FYI\] Incorrect use of !topic command. You probably meant !topic set . Do \002/msg $botnick topic help\002 for more information." return 0 } ##### # topicHelp # # respond to /msg botnick topic help ... requests # proc topicHelp {nick host handle arg} { if [regexp -nocase "^help( .+)?" $arg boom helpon] { global botnick topicInfo set helpon [string tolower $helpon] if {$helpon == ""} { #command list puthelp "PRIVMSG $nick :\002TopicEngine Script v$topicInfo(pop,version)\002 by JamesOff (james@jamesoff.net) http://www.jamesoff.net/go/topicengine"; puthelp "PRIVMSG $nick :\037Commands available\037: (all from channel as !topic ..., or in query as /msg $botnick topic ..., or on partyline as .topic ..." puthelp "PRIVMSG $nick :Use \037/msg $botnick topic help \037 for more info)"; puthelp "PRIVMSG $nick : info set add" puthelp "PRIVMSG $nick : del rehash clear" puthelp "PRIVMSG $nick : lock unlock append" puthelp "PRIVMSG $nick : insert undo regexp" return 0 } set helpon [string range $helpon 1 [string length $helpon]] if {$helpon == "info"} { puthelp "PRIVMSG $nick :\002!topic info\002" puthelp "PRIVMSG $nick : This gives you summary information about what the topic is, and when it was last changed by whom" puthelp "PRIVMSG $nick :\002!topic info \002" puthelp "PRIVMSG $nick : This tells you about component n of the topic. The first component is 1. Be careful, the pre- and postfixes are not included in this." puthelp "PRIVMSG $nick :\002!topic info undo\002" puthelp "PRIVMSG $nick : This tells you the status of the undo buffer for the channel. (See !topic undo)" puthelp "PRIVMSG $nick :\002!topic ?\002 and \002!topic ?\002 are shortcuts for this command" return 0 } if {$helpon == "set"} { puthelp "PRIVMSG $nick :\002!topic set " puthelp "PRIVMSG $nick : This will remove all components of the topic (except the pre- and postfixes) and replace them with your string." puthelp "PRIVMSG $nick : can also be 'none' to clear the topic" puthelp "PRIVMSG $nick :\002!topic set prefix|postfix " puthelp "PRIVMSG $nick : This sets the prefix or the postfix to the string you give" puthelp "PRIVMSG $nick : Use 'none' to clear them" puthelp "PRIVMSG $nick :\002!topic =\002 and \002!topic =prefix|postfix \002 are shortcuts for this command" puthelp "PRIVMSG $nick :You can prefix with @ to make the bot lock the topic at the same time. (Not for pre/postfix)" return 0 } if {$helpon == "add"} { puthelp "PRIVMSG $nick :\002!topic add " puthelp "PRIVMSG $nick : Adds your string to the topic. Will fail if your string would make the topic go over the topic length limit." puthelp "PRIVMSG $nick :\002!topic +\002 is a shortcut for this command" puthelp "PRIVMSG $nick :You can prefix with @ to make the bot lock the topic at the same time." return 0 } if {$helpon == "append"} { puthelp "PRIVMSG $nick :\002!topic append " puthelp "PRIVMSG $nick : Adds your string to the topic. Will automatically drop elements from the start of the topic to try to fit your text in." puthelp "PRIVMSG $nick :\002!topic <<<\002 is a shortcut for this command (e.g. !topic << with @ to make the bot lock the topic at the same time." return 0 } if {$helpon == "append"} { puthelp "PRIVMSG $nick :\002!topic insert " puthelp "PRIVMSG $nick : Adds your string to the front of the topic. Will automatically drop elements from the end of the topic to try to fit your text in." puthelp "PRIVMSG $nick :\002!topic >>>\002 is a shortcut for this command (e.g. !topic >>>hello)." puthelp "PRIVMSG $nick :You can prefix with @ to make the bot lock the topic at the same time." return 0 } if {$helpon == "regexp"} { puthelp "PRIVMSG $nick :\002!topic regexp " puthelp "PRIVMSG $nick : Uses a regexp replace on the topic element at ." puthelp "PRIVMSG $nick : The correct form for the regexp is: /match/replace/options" puthelp "PRIVMSG $nick : Options is nothing, or a combination of \002i\002 for case-insensitive matching, and \002g\002 for global matching (match all occurances in string)." puthelp "PRIVMSG $nick :\002!topic ///\002 is a shortcut for this command (e.g. !topic /2/hello/goodbye/)." puthelp "PRIVMSG $nick :Collected terms can be used in the replacement with \\1, \\2, etc (if you don't understand, this command may not be for you :) See \002man regexp\002 for more information." return 0 } if {$helpon == "del"} { puthelp "PRIVMSG $nick :\002!topic del " puthelp "PRIVMSG $nick : Deletes topic component n from the topic, first is 1. You cannot use this on the pre- or postfixes (see \037!topic set\037 for info)" puthelp "PRIVMSG $nick :\002!topic -\002 is a shortcut for this command" return 0 } if {$helpon == "rehash"} { puthelp "PRIVMSG $nick :\002!topic rehash\002" puthelp "PRIVMSG $nick : Forces the bot to reset the topic to what it thinks it should be. Will do nothing if the actual topic matches" puthelp "PRIVMSG $nick : what the bot thinks it should be." puthelp "PRIVMSG $nick :\002!topic rehash force\002" puthelp "PRIVMSG $nick : Forces the bot to reset to the topic, whether it thinks it should or not." puthelp "PRIVMSG $nick : Note: redo is a synonym for rehash" return 0 } if {$helpon == "clear"} { puthelp "PRIVMSG $nick :\002!topic clear\002" puthelp "PRIVMSG $nick : Sets the channel topic to nothing, but keeps the settings in the bot. Use \037!topic rehash\037 to get it back." puthelp "PRIVMSG $nick :\002!topic clear content" puthelp "PRIVMSG $nick : Sets the content of the topic (not the pre- or postfix) to nothing" puthelp "PRIVMSG $nick :\002!topic clear all" puthelp "PRIVMSG $nick : Sets all of the topic, include the pre- and postfixes, to nothing" puthelp "PRIVMSG $nick :Note: reset is a synonym for clear" return 0 } if {$helpon == "lock"} { puthelp "PRIVMSG $nick :\002!topic lock\002" puthelp "PRIVMSG $nick : Locks the topic. The topic can still be changed using the !topic commands, but if anyone" puthelp "PRIVMSG $nick : changes the topic manually (/topic) the bot will set it back. See also \037!topic unlock\037" puthelp "PRIVMSG $nick :You can prefix a topic with @ when using !topic set or !topic add to make the bot lock the topic at the same time." } if {$helpon == "unlock"} { puthelp "PRIVMSG $nick :\002!topic unlock\002" puthelp "PRIVMSG $nick : Unlocks the topic after it has been locked with \037!topic lock\037" return 0 } if {$helpon == "undo"} { puthelp "PRIVMSG $nick :\002!topic undo\002" puthelp "PRIVMSG $nick : Restores the topic to its state before the last command. Currently only one level of undo is supported. Use \002!topic info undo\002 to see what the undo topic will be." return 0 } } } ##### # topicUnsplit # # called on a net-rejoin # forces the topic to be set to what it should be, in case some servers around the network have lost it # proc topicUnsplit {nick host handle channel} { global topicInfo topicChannels topicLastSplit topicSplitChans topicSplitDirty #check the topic script is active in here if {![channel get $channel topicengine]} { return 0 } set topicSplitDirty 1 set topicLastSplit [clock seconds] lappend $topicSplitChans $channel set topicSplitChans [lsort -unique $topicSplitChans] putloglev d * "topicengine: last split time updated to $topicLastSplit ([clock format $topicLastSplit])" putloglev d * "topicengine: dirty chans due to split: $topicSplitChans" return 0 } ##### # topicJoin # # (a misnomer) called when the bot is opped # similar to the rejoin one above, checks the topic and resets it if needs to be # if opped by a server, assume we've come back from a split, force the reset # proc topicJoin {nick host handle channel mode victim} { global topicInfo topicChannels #only do this if I've joined a channel if [isbotnick $victim] { #check the topic script is active in here if {![channel get $channel topicengine]} { return 0 } #check i got opped if {$mode != "+o"} { return 0 } #first check i haven't reset the topic in here myself in the last 120 seconds (stop multiple rejoins fucking up) set thirtySecAgo [expr [clock seconds] - 120] if {($thirtySecAgo <= $topicInfo($channel,whenSet)) && ($topicInfo($channel,whoSet) == "me")} { return 0 } putlog "Opped in $channel, auto-setting topic" #if it's a server-mode change, assume we just got un-netsplitted, force the topic if {$nick == ""} { setTopic $channel 1 } else { setTopic $channel } set topicInfo($channel,whoLastSet) "me" set topicInfo($channel,whenLastSet) [clock seconds] } return 0 } ##### # topicParse # # turns a string into a new set of topicBits # used when learning a topic # proc topicParse {channel topic nick} { global topicInfo topicSeparator #if we don't have a |, it's one topic if {[string first $topicSeparator $topic] == -1} { set topicInfo($channel,topicBits) [list $topic] putlog "Learned new topic $topic in $channel ... updating to check it has pre/postfixes for this channel" set topicInfo($channel,whoSet) [list $nick] set topicInfo($channel,whenSet) [list [clock seconds]] set topicInfo($channel,whoLastSet) $nick set topicInfo($channel,whenLastSet) [clock seconds] set topicInfo($channel,topic) $topic set willFit [checkTopic $channel ""] if {$willFit > 0} { putlog "Oops, I can't fit the new topic in with my pre/postfixes, not setting" return 0 } setTopic $channel 0 return 0 } #it's a multipart topic set topic "${topic}${topicSeparator}" set blah 0 set loopCount 0 set topicInfo($channel,topic) $topic while {[string match "*$topicSeparator*" $topic]} { set sentence [string range $topic 0 [expr [string first $topicSeparator $topic] -1]] if {$sentence != ""} { if {$blah == 0} { set topicInfo($channel,topicBits) [list [string trim $sentence]] set topicInfo($channel,whoSet) [list $nick] set topicInfo($channel,whenSet) [list [clock seconds]] set blah 1 } else { lappend topicInfo($channel,topicBits) [string trim $sentence] lappend topicInfo($channel,whoSet) $nick lappend topicInfo($channel,whenSet) [clock seconds] } } set topic [string range $topic [expr [string first $topicSeparator $topic] +1] end] incr loopCount if {$loopCount > 10} { putlog "Couldn't get all of the topic" return 0 } } if {[lindex $topicInfo($channel,topicBits) 0] == $topicInfo($channel,leadIn)} { #the prefix is already on this topic, drop it putlog "Prefix on this topic is the same as the one I have on record, dropping it" set topicInfo($channel,topicBits) [lreplace $topicInfo($channel,topicBits) 0 0] set topicInfo($channel,whoSet) [lreplace $topicInfo($channel,whoSet) 0 0] set topicInfo($channel,whenSet) [lreplace $topicInfo($channel,whenSet) 0 0] incr loopCount -1 } set lastElement [expr $loopCount - 1] if {[lindex $topicInfo($channel,topicBits) $lastElement] == $topicInfo($channel,leadOut)} { #the prefix is already on this topic, drop it putlog "Postfix on this topic is the same as the one I have on record, dropping it" set topicInfo($channel,topicBits) [lreplace $topicInfo($channel,topicBits) $lastElement $lastElement] set topicInfo($channel,whoSet) [lreplace $topicInfo($channel,whoSet) $lastElement $lastElement] set topicInfo($channel,whenSet) [lreplace $topicInfo($channel,whenSet) $lastElement $lastElement] } set topicInfo($channel,whoLastSet) $nick set topicInfo($channel,whenLastSet) [clock seconds] putlog "Learned topic with $loopCount elements in $channel ... updating to check it has pre/postfixes for this channel" set willFit [checkTopic $channel ""] if {$willFit > 0} { putlog "Oops, I can't fit the new topic in with my pre/postfixes, not setting" return 0 } setTopic $channel 0 return 0 } ##### # topicBotCommand # # handle a 'topicengine' command from another bot # proc topicBotCommand {fromBot cmd arg} { global topicInfo if {$cmd == "unlock"} { #need to unlock a channel if {[lindex $topicInfo($channel,lock) 0] == 1} { set topicInfo($arg,lock) [list 0 "" ""] putlog "Unlocked topic in $arg at request of $fromBot" putbot $fromBot "unlockok $arg" } return 0 } if {$cmd == "unlockok"} { #bot unlocked channel ok putlog "$fromBot unlocked channel in $arg for me" return 0 } } ##### # topicUpdate # # update the topic automagically at 00:01 every day # proc topicUpdate { min hour day month year } { global topicChannels putlog "topicEngine: auto-refreshing topics..." foreach chan $topicChannels { setTopic $chan } return 0 } ##### # topicNotice # # Puts text back to the current executing user (puthelp or putidx as needed) # proc topicNotice { text } { global topicType topicParameter #putlog "topicNotice: $text ($topicType = $topicParameter)" if {$topicType == ""} { putlog "topicengine: CRITIAL ALERT: couldn't work out where to send $text" return 1 } if {$topicParameter == ""} { putlog "topicengine: CRITICAL ALERT: couldn't work out to whom I should send $text" return 1 } if {$topicType == "dcc"} { putidx $topicParameter $text } else { puthelp "NOTICE $topicParameter :$text" } } ##### # topicCommandPub # # Wrapper for topicCommand from !topic in a channel # proc topicCommandPub {nick host handle channel text } { global topicType topicParameter set topicType "pub" set topicParameter $nick set result [topicCommand $nick $host $handle $channel $text] set topicType "" set topicParameter "" return $result } ##### # topicCommandMsg # # Wrapper for topicCommand from topic in a msg # proc topicCommandMsg {nick host handle text} { global topicType topicParameter botnick if [string match -nocase "help*" $text] { topicHelp $nick $host $handle $text return 0 } if [regexp -nocase {(#[^ ]+) (.+)} $text matches channel text2] { set topicType "msg" set topicParameter $nick set result [topicCommand "${nick}/msg" $host $handle $channel $text2] set topicType "" set topicParameter "" return $result } else { puthelp "NOTICE $nick :use: /msg $botnick topic #channel ..." } } ##### # topicCommandDCC # # Wrapper for topicCommand from .topic in DCC # proc topicCommandDCC {handle idx args} { global topicType topicParameter if [regexp -nocase "(#.+) (.+)\}" $args matches channel text] { set topicType "dcc" set topicParameter $idx #putlog "calling topicCommand $channel $text" set result [topicCommand "${handle}/dcc" "" $handle $channel $text] set topicType "" set topicParameter "" return $result } else { putidx $idx "use: .topic #channel ..." } } ##### # topicSplitCheck # handle splits intelligently # proc topicSplitCheck {hr min day month year} { global topicLastSplit topicSplitChans topicSplitDirty if {$topicSplitDirty == 0} { return } set splitDiff [expr [clock seconds] - $topicLastSplit] putloglev 1 * "topicengine: checking for splits: diff between now and last split is $splitDiff sec" if {$splitDiff > 120} { # redo topic in every chan #don't do this is the setting is off putlog "topicengine: re-setting topics for channels involved in splits..." global topicAnnounceReset foreach channel $topicSplitChans { if {$topicAnnounceReset == 1} { puthelp "PRIVMSG $channel :Don't mind me, just resetting the topic in case a netsplit lost it :)" } setTopic $channel 1 } set topicSplitChans [list] set topicSplitDirty 0 } } ##### # Start up stuff # # Initialise the variables at start topicInitArray # # set up the binds bind pub - !topic topicCommandPub bind dcc - topic topicCommandDCC bind topc - * topicChanged bind msg - topic topicCommandMsg #try to detect a net-unsplit and check the topic bind rejn - * topicUnsplit #set the topic to the cached one when I get opped bind mode - * topicJoin #auto unlock a channel if another bot locks it bind bot - "topicengine" topicBotCommand # update (change or comment out as needed) (syntax: min hour day month year) bind time - "01 00 * * *" topicUpdate #check for topics needing resettings after a split bind time - "* * * * *" topicSplitCheck # # log our existence putlog "TopicEngine v$topicInfo(pop,version) online."; # set the loaded variable so we don't overwrite topics on a .rehash set topicEngineOnline 1 # done :)