#---------------------------------------------------------------------# # incith:exchange $Rev:: 99 $ # # $Id:: incith-exchange.tcl 99 2009-01-21 08:59:56Z incith $ # # # # currency converions from http://ca.finance.yahoo.com/currency # # tested on Eggdrop & Windrop v1.6.17 # # # # Usage: # # .chanset #channel +exchange # # !exchange # # # # ChangeLog (m/d/y): # # 1/20/09: allow 'in' or 'to' or 'into' (e.g. 5 cad into usd) # # 1/17/09: added botnet code. license changed to GPLv3. # # # # TODO: # # - max length variable for output, to prevent HTML floods # # - Suggestions/Thanks/Bugs/Ideas, e-mail at bottom of header. # # # # LICENSE (GPLv3): # # 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 3 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. # # (http://www.gnu.org/licenses/gpl-3.0.txt) # # # # Copyright (C) 2009, Jordan # # http://incith.com ~ incith@gmail.com ~ irc.freenode.net/#incith # #---------------------------------------------------------------------# package require http 2.3 setudef flag exchange # 0 (zero) will disable an optional variable, 1 or above enables # namespace eval incith::exchange { # the bind prefix/command char(s) ({!} or {! .} etc, separate with space) variable command_chars {! .} # binds {one two three} variable binds {exchange} # allow binds to be used in /msg's to the bot? variable private_messages 1 # send public/channel output to the user instead? variable public_to_private 0 # send replies as notices instead of private messages? variable notices 0 # only send script 'errors' as notices? (invalid input etc) variable notice_errors_only 0 # maximum length of a reply before breaking it up variable split_length 440 # if you're using a proxy, enter it here {hostname.com:3128} variable proxy {} # how long (in seconds) before the http request times out? variable timeout 15 # use the callback function for non-blocking http fetches? # note: your eggdrop must be patched or else this will slow # lookups down a lot and even break some things. variable callback 0 # if your bots participate in a botnet, you can enable this variable # and load the script on all of the bots, but only one bot will respond # to exchange requests. if that bot quits, the next bot in line will start. # there is no reason to disable this variable that I can think of. variable botnet 1 } # script begings namespace eval incith::exchange { global botnet-nick nick if {${botnet-nick} == ""} { set botnet-nick ${nick} } variable version "incith:exchange-SVN" variable debug 0 array set static {} if {${incith::exchange::botnet} >= 1} { set static(botnet,${botnet-nick},time) [clock seconds] } else { set static(botnet,${botnet-nick},time) "noswarm" } if {![info exists static(botnet,bots)]} { set static(botnet,bots) ${botnet-nick} } } # bind the binds foreach command_char [split ${incith::exchange::command_chars} " "] { foreach bind [split ${incith::exchange::binds} " "] { # public message binds bind pub -|- "${command_char}${bind}" incith::exchange::message_handler # private message binds if {${incith::exchange::private_messages} >= 1} { bind msg -|- "${command_char}${bind}" incith::exchange::message_handler } } } # bind the botnet binds if {${incith::exchange::botnet} >= 1} { bind bot - incith:exchange incith::exchange::bot_msg bind link - * incith::exchange::bot_link # it really depends what works better for you, checking if # the bot is onchan or just making sure they are linked. # bind disc - * incith::exchange::bot_disc } namespace eval incith::exchange { proc bot_msg {from cmd text} { global botnet-nick upvar #0 incith::exchange::static static if {${incith::exchange::debug} >= 1} { putlog "${incith::exchange::version} (botmsg): <${from}> ${cmd} ${text}" } # receiving a bots load time if {[string match "time ?*" $text]} { regexp -- {time (.*)} $text - time set static(botnet,${from},time) $time # make sure this bot is in our bot list if {![string match "*${from}*" $static(botnet,bots)]} { putlog "${incith::exchange::version} (botnet): ${from} has joined the incith:exchange swarm." append static(botnet,bots) ";${from}" regsub -all -- {;;} $static(botnet,bots) {;} static(botnet,bots) set static(botnet,bots) [string trimright $static(botnet,bots) {;}] } } } proc bot_link {bot hub} { global botnet-nick upvar #0 incith::exchange::static static # send our time to the bots putallbots "incith:exchange time $static(botnet,${botnet-nick},time)" } proc bot_disc {bot} { global botnet-nick upvar #0 incith::exchange::static static if {[string match "*${bot}*" $static(botnet,bots)]} { if {$static(botnet,${bot},time) != "noswarm"} { putlog "${incith::exchange::version} (botnet): ${bot} has left the incith:exchange swarm." } } # remove this bot from our bot list regsub -all -- $bot $static(botnet,bots) {} static(botnet,bots) regsub -all -- {;;} $static(botnet,bots) {;} static(botnet,bots) set static(botnet,bots) [string trimright $static(botnet,bots) {;}] # remove their time? If they just lost link, they might still be [onchan] # unset static(botnet,${bot},time) } } namespace eval incith::exchange { # [message_handler] : handles public & private messages # proc message_handler {nick uhand hand args} { global botnet-nick lastbind upvar #0 incith::exchange::static static set input(who) $nick if {[llength $args] >= 2} { # public message set input(where) [lindex $args 0] if {${incith::exchange::public_to_private} >= 1} { set input(chan) $input(who) } else { set input(chan) $input(where) } set input(query) [lindex $args 1] if {[channel get $input(where) exchange] != 1} { return } } else { # private message set input(where) "private" set input(chan) $input(who) set input(query) [lindex $args 0] if {${incith::exchange::private_messages} <= 0} { return } } # botnet if {${incith::exchange::botnet} >= 1 && $input(where) != "private"} { foreach bot [split $static(botnet,bots) ";"] { # skip ourselves, bots not on the input channel, and bots not participating if {${bot} == ${botnet-nick} || ![onchan ${bot} $input(where)] || $static(botnet,${bot},time) == "noswarm"} { continue # bots that load first will serve first. change < to > to reverse. } elseif {$static(botnet,$bot,time) < $static(botnet,${botnet-nick},time)} { if {${incith::exchange::debug} >= 1} { putlog "${incith::exchange::version} (botnet): $bot loaded before me." } return # should 2 bots have the same time, set a new random time (this did happen in testing) } elseif {$static(botnet,${bot},time) == $static(botnet,${botnet-nick},time)} { if {${incith::exchange::debug} >= 1} { putlog "${incith::exchange::version} (botnet): $bot had the same load time as me!" } set static(botnet,${botnet-nick},time) [expr [clock seconds] + int(rand()*60)+1] putallbots "incith:exchange time $static(botnet,${botnet-nick},time)" return } } # looks like we're serving, make sure we keep the botnet up to date putallbots "incith:exchange time $static(botnet,${botnet-nick},time)" } # log it ipl ${input(who)} ${input(where)} "${lastbind} ${input(query)}" # do some things: regsub -all -- {\s+(in)?(to)?\s+} $input(query) { } input(query) foreach {amount from into} $input(query) { break } if {[info exists amount] && $from != "" && $into != ""} { set input(amount) $amount set input(from) [string toupper $from] set input(into) [string toupper $into] set input(query) "http://ca.finance.yahoo.com/currency/convert?amt=$input(amount)&from=$input(from)&to=$input(into)&submit=Convert" } else { send_output $input(chan) "Syntax: ${lastbind} , see http://ca.finance.yahoo.com/currency for symbols." $input(who) return } fetch_html [array get input] } # [fetch_html] : fetch html of a given url # proc fetch_html {tmpInput} { upvar #0 incith::exchange::static static array set input $tmpInput # setup the timeout, for use below set timeout [expr round(1000 * ${incith::exchange::timeout})] # setup proxy information, if any if {[string match {*:*} ${incith::exchange::proxy}] == 1} { set proxy_info [split ${incith::exchange::proxy} ":"] } # the "browser" we are using # NT 5.1 - XP, NT 6.0 - Vista set ua "Opera/9.63 (Windows NT 6.0; U; en)" if {[info exists proxy_info] == 1} { ::http::config -useragent $ua -proxyhost [lindex $proxy_info 0] -proxyport [lindex $proxy_info 1] } else { ::http::config -useragent $ua } # retrieve the html if {$incith::exchange::callback >= 1} { catch {set token [::http::geturl "$input(query)" -command incith::exchange::httpCommand -timeout $timeout]} output(token_status) } else { catch {set token [::http::geturl "$input(query)" -timeout $timeout]} output(token_status) } # need to check for some errors here: if {[string match "couldn't open socket: host is unreachable*" $output(token_status)]} { send_output $input(chan) "Unknown host '${input(query)}'." $input(who) return } set static($token,input) [array get input] # manually call our callback procedure if we're not using callbacks if {$incith::exchange::callback <= 0} { httpCommand $token } } # [httpCommand] : makes sure the http request succeeded # proc httpCommand {token} { upvar #0 $token state upvar #0 incith::exchange::static static # build the output array array set output $static($token,input) switch -exact [::http::status $token] { "timeout" { if {$incith::exchange::debug >= 1} { ipl $output(who) $output(where) "status = timeout (url = $state(url))" } set output(error) "Operation timed out after ${incith::exchange::timeout} seconds." } "error" { if {$incith::exchange::debug >= 1} { ipl $output(who) $output(where) "status = error([::http::error $token]) (url = $state(url))" } set output(error) "An unknown error occurred. (Error #01)" } "ok" { switch -glob [::http::ncode $token] { 3* { array set meta $state(meta) if {$incith::exchange::debug >= 1} { ipl $output(who) $output(where) "redirecting to $meta(Location)" } set output(query) $meta(Location) # fetch_html $output(where) $output(who) $output(where) $meta(Location) fetch_html [array get output] return } 200 { if {$incith::exchange::debug >= 1} { ipl $output(who) $output(where) "parsing $state(url)" } } default { if {$incith::exchange::debug >= 1} { ipl $output(who) $output(where) "status = default, error" } set output(error) "An unknown error occurred. (Error #02)" } } } default { if {$incith::exchange::debug >= 1} { ipl $output(who) $output(where) "status = unknown, default, error" } set output(error) "An unknown error occurred. (Error #03)" } } set static($token,output) [array get output] process_html $token } # [process_html] : # proc process_html {token} { upvar #0 $token state upvar #0 incith::exchange::static static array set output $static($token,output) # get the html set html $state(body) # store the HTML to a file if {$incith::exchange::debug >= 1} { set fopen [open incith-exchange.html w] puts $fopen $html close $fopen } # html cleanups regsub -all {\n} $html {} html regsub -all {\t} $html {} html regsub -all { } $html { } html regsub -all {>} $html {>} html regsub -all {<} $html {<} html # html parsing # regexp {Symbol(.+?)(.+?).*(.+?)} $html - output(curfrom) output(curinto) output(curamount) # check for errors, don't overwrite any previous error if {![info exists output(error)]} { if {(![info exists output(curfrom)] || ![info exists output(curinto)] || ![info exists output(curamount)])} { set output(error) "Either '$output(from)' or '$output(into)' are invalid symbols, or something failed while attempting to parse the results." } } # process the output array set static($token,output) [array get output] process_output $token return 1 } # [process_output] : create the output and send it # proc process_output {token} { upvar #0 $token state upvar #0 incith::exchange::static static array set output $static($token,output) # check for errors if {[info exists output(error)]} { send_output $output(chan) $output(error) $output(who) return } # send the result send_output $output(chan) "$output(amount) $output(curfrom) makes $output(curamount) $output(curinto)." # clean the static array for this http session foreach value [array get static] { if {[info exists static($value)]} { if {[string match *${token}* $value]} { unset static($value) } } } } # [ipl] : a neat/handy putlog procedure proc ipl {who {where {}} {what {}}} { if {$where == "" && $what == ""} { putlog "${incith::exchange::version}: ${who}" } elseif {$where != "" && $what == ""} { putlog "${incith::exchange::version}: <${who}/${where}>" } else { putlog "${incith::exchange::version}: <${who}/${where}> ${what}" } } # [send_output] : sends $data appropriately out to $where # proc send_output {where data {isErrorNick {}}} { if {${incith::exchange::notices} >= 1} { foreach line [incith::exchange::line_wrap $data] { putquick "NOTICE $where :${line}" } } elseif {${incith::exchange::notice_errors_only} >= 1 && $isErrorNick != ""} { foreach line [incith::exchange::line_wrap $data] { putquick "NOTICE $isErrorNick :${line}" } } else { foreach line [incith::exchange::line_wrap $data] { putquick "PRIVMSG $where :${line}" } } } # [line_wrap] : takes a long line in, and chops it before the specified length # http://forum.egghelp.org/viewtopic.php?t=6690 # proc line_wrap {str {splitChr { }}} { set out [set cur {}] set i 0 set len $incith::exchange::split_length foreach word [split [set str][set str ""] $splitChr] { if {[incr i [string len $word]] > $len} { lappend out [join $cur $splitChr] set cur [list $word] set i [string len $word] } else { lappend cur $word } incr i } lappend out [join $cur $splitChr] } } # the script has loaded. namespace eval incith::exchange { putallbots "incith:exchange time $static(botnet,${botnet-nick},time)" } incith::exchange::ipl "loaded." # EOF