#---------------------------------------------------------------------# # incith:qrz $Rev:: 94 $ # # $Id:: incith-qrz.tcl 94 2009-01-21 05:53:33Z incith $ # # # # esarches for ham radio handles @ http://qrz.com # # tested on Eggdrop 1.6.19 & Windrop v1.6.17 # # # # Usage: # # .chanset #channel +qrz # # !qrz # # returns some information about if they exist. # # # # ChangeLog (m/d/y): # # 1/20/09: fixed flaw with botnet-nick. # # 1/19/09: script requested and released. # # # # TODO: # # - 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 qrz # 0 (zero) will disable an optional variable, 1 or above enables # namespace eval incith::qrz { # the bind prefix/command char(s) ({!} or {! .} etc, separate with space) variable command_chars {! .} # binds {one two three} variable binds {qrz} # 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 # levels required to use the binds, global|channel. variable public_flags {-|-} variable private_flag {-|-} # only the global flag matters. # only send script 'errors' as notices? (not enough input etc) variable notice_errors_only 0 # this will be used to separate items (Foo; Bar; Baz) variable separator {; } # make use of bolding where appropriate? variable bold 1 # if you're using a proxy, enter it here {example.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 qrz 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::qrz { global botnet-nick nick if {${botnet-nick} == ""} { set botnet-nick ${nick} } set botnet-nick [string tolower ${botnet-nick}] variable version "incith:qrz-SVN" variable debug 0 array set static {} if {${incith::qrz::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::qrz::command_chars} " "] { foreach bind [split ${incith::qrz::binds} " "] { # public message binds bind pub ${incith::qrz::public_flags} "${command_char}${bind}" incith::qrz::message_handler # private message binds if {${incith::qrz::private_messages} >= 1} { bind msg ${incith::qrz::private_flag} "${command_char}${bind}" incith::qrz::message_handler } } } # bind the botnet binds if {${incith::qrz::botnet} >= 1} { bind bot - incith:qrz incith::qrz::bot_msg bind link - * incith::qrz::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::qrz::bot_disc } namespace eval incith::qrz { proc bot_msg {from cmd text} { global botnet-nick set from [string tolower $from] ; set botnet-nick [string tolower ${botnet-nick}] upvar #0 incith::qrz::static static if {${incith::qrz::debug} >= 1} { putlog "${incith::qrz::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::qrz::version} (botnet): ${from} has joined the incith:qrz 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 set bot [string tolower $bot] ; set botnet-nick [string tolower ${botnet-nick}] upvar #0 incith::qrz::static static # send our time to the bots putallbots "incith:qrz time $static(botnet,${botnet-nick},time)" } proc bot_disc {bot} { global botnet-nick set bot [string tolower $bot] ; set botnet-nick [string tolower ${botnet-nick}] upvar #0 incith::qrz::static static if {[string match "*${bot}*" $static(botnet,bots)]} { if {$static(botnet,${bot},time) != "noswarm"} { putlog "${incith::qrz::version} (botnet): ${bot} has left the incith:qrz 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::qrz { # [message_handler] : handles public & private messages # proc message_handler {nick uhand hand args} { global botnet-nick lastbind set botnet-nick [string tolower ${botnet-nick}] upvar #0 incith::qrz::static static set input(who) $nick if {[llength $args] >= 2} { # public message set input(where) [lindex $args 0] if {${incith::qrz::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) qrz] != 1} { return } } else { # private message set input(where) "private" set input(chan) $input(who) set input(query) [lindex $args 0] if {${incith::qrz::private_messages} <= 0} { return } } # botnet if {${incith::qrz::botnet} >= 1 && $input(where) != "private"} { foreach bot [split $static(botnet,bots) ";"] { set bot [string tolower $bot] # 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::qrz::debug} >= 1} { putlog "${incith::qrz::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::qrz::debug} >= 1} { putlog "${incith::qrz::version} (botnet): $bot had the same load time as me, fixing." } set static(botnet,${botnet-nick},time) [expr [clock seconds] + int(rand()*60)+1] putallbots "incith:qrz time $static(botnet,${botnet-nick},time)" return } } # looks like we're serving, make sure we keep the botnet up to date putallbots "incith:qrz time $static(botnet,${botnet-nick},time)" } # log it if {[regexp -- {^\s*$} $input(query)]} { ipl "<${input(who)}/${input(where)}>" send_output $input(chan) "Syntax: $lastbind " return } else { ipl ${input(who)} ${input(where)} "$lastbind ${input(query)}" } # fetch the html set input(url) {http://www.qrz.com/db/} fetch_html [array get input] } # [fetch_html] : fetch html of a given url # proc fetch_html {tmpInput} { upvar #0 incith::qrz::static static array set input $tmpInput # setup the timeout, for use below set timeout [expr round(1000 * ${incith::qrz::timeout})] # setup proxy information, if any if {[string match {*:*} ${incith::qrz::proxy}] == 1} { set proxy_info [split ${incith::qrz::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::qrz::callback >= 1} { catch {set token [::http::geturl "$input(url)" -query "callsign=${input(query)}" -command incith::qrz::httpCommand -timeout $timeout]} output(status) } else { catch {set token [::http::geturl "$input(url)" -query "callsign=${input(query)}" -timeout $timeout]} output(status) } # need to check for some errors here: if {[string match "couldn't open socket: host is unreachable*" $output(status)]} { send_output $input(chan) "Unknown host '${input(query)}'." $input(who) return } elseif {[string match "Missing host part:*" $output(status)]} { send_output $input(chan) "Invalid host '${input(query)}'." $input(who) return } elseif {[string match "Unsupported URL type*" $output(status)]} { send_output $input(chan) "Unsupported URL type '${input(query)}'." $input(who) return } elseif {![string match "::http::*" $output(status)]} { send_output $input(chan) "Unknown Error: $output(status)" $input(who) return } # no errors, move on: set static($token,input) [array get input] # manually call our callback procedure if we're not using callbacks if {$incith::qrz::callback <= 0} { httpCommand $token } } # [httpCommand] : makes sure the http request succeeded # proc httpCommand {token} { upvar #0 $token state upvar #0 incith::qrz::static static # build the output array array set output $static($token,input) switch -exact [::http::status $token] { "timeout" { if {$incith::qrz::debug >= 1} { ipl $output(who) $output(where) "status = timeout (url = $state(url))" } set output(error) "Operation timed out after ${incith::qrz::timeout} seconds." } "error" { if {$incith::qrz::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::qrz::debug >= 1} { ipl $output(who) $output(where) "redirecting to $meta(Location)" } set output(url) $meta(Location) fetch_html [array get output] return } 200 { if {$incith::qrz::debug >= 1} { ipl $output(who) $output(where) "parsing $state(url)" } } default { if {$incith::qrz::debug >= 1} { ipl $output(who) $output(where) "status = default, error" } set output(error) "An unknown error occurred. (Error #02)" } } } default { if {$incith::qrz::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::qrz::static static array set output $static($token,output) # get the html set html $state(body) # html cleanups regsub -all {\n} $html {} html regsub -all {\t} $html {} html regsub -all { } $html { } html regsub -all {>} $html {>} html regsub -all {<} $html {<} html # make the html even easier to parse regexp -nocase -- {(.*)} $html {} html # store the HTML to a file if {$incith::qrz::debug >= 2} { set fopen [open incith-qrz.html w] puts $fopen $html close $fopen } # html parsing # # fetch the title of the website, and store it into the output array regexp -nocase -- {(.+?)(.+?)

\s+

(.+?)

\s+

(.+?)

\s+

(.+?)

.+?} $html - output(callsign) output(name) output(address) output(city) regexp -nocase -- {Lookups:\s+(\d+)} $html - output(hits) regexp -nocase -- {Class:(.+?)\s*(?:Codes:\s*(.+?))} $html - output(class) output(class_codes) if {[info exists output(class)]} { if {[info exists output(class_codes)]} { if {$output(class_codes) != ""} { append output(class) " (${output(class_codes)})" } } } # check for errors if {![info exists output(callsign)] || ![info exists output(location)]} { set output(error) "Could not fetch callsign information." } if {![info exists output(hits)]} { set output(hits) "Unknown" } # process the output array set static($token,output) [array get output] process_output $token } # [process_output] : create the output and send it # proc process_output {token} { upvar #0 $token state upvar #0 incith::qrz::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) "[ibold "Callsign:"] $output(callsign)${incith::qrz::separator}[ibold "Name:"] $output(name)${incith::qrz::separator}[ibold "Class:"] $output(class)${incith::qrz::separator}[ibold "Address:"] ${output(address)}, ${output(city)}${incith::qrz::separator}[ibold "Viewed:"] $output(hits) times" # 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 == ""} { # first argument only = data only putlog "${incith::qrz::version}: ${who}" } elseif {$where != "" && $what == ""} { # two arguments = who and data putlog "${incith::qrz::version}: <${who}> ${where}" } else { # all three... putlog "${incith::qrz::version}: <${who}/${where}> ${what}" } } # [send_output] : sends $data appropriately out to $where # proc send_output {where data {isErrorNick {}}} { if {${incith::qrz::notices} >= 1} { foreach line [incith::qrz::line_wrap $data] { putquick "NOTICE $where :${line}" } } elseif {${incith::qrz::notice_errors_only} >= 1 && $isErrorNick != ""} { foreach line [incith::qrz::line_wrap $data] { putquick "NOTICE $isErrorNick :${line}" } } else { foreach line [incith::qrz::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 400 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] } # [ibold] : bolds some text, if bolding is enabled # proc ibold {input} { if {${incith::qrz::bold} >= 1} { return "\002${input}\002" } return $input } } # the script has loaded. namespace eval incith::qrz { global botnet-nick set botnet-nick [string tolower ${botnet-nick}] putallbots "incith:qrz time $static(botnet,${botnet-nick},time)" } incith::qrz::ipl "loaded." # EOF