#---------------------------------------------------------------# # redeemer:torrentspy v1.0 # # # # performs TorrentSpy searches. # # tested on Eggdrop v1.6.17 # # # # Usage: # # .chanset #channel +torrentspy # # !torrentspy # # # # ChangeLog: # # 1.0: first public release # # # # BUGS: # # - If you find something quirky, my e-mail is at the bottom # # of this header. # # # # TODO: # # - nothing # # # # LICENSE: # # This code comes with ABSOLUTELY NO WARRANTY. # # # # 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. (http://www.gnu.org/copyleft/library.txt) # # # # Copyright (C) 2006, Redeemer # # redeemer@zion-mainframe.net # #---------------------------------------------------------------# package require http 2.3 setudef flag torrentspy # 0 will typically disable an option, otherwise a value 1 or # above will enable it. # namespace eval redeemer { namespace eval torrentspy { # set this to the command character you wish to use variable command_char "@" # set these to the trigger(s) you wish to use ("trig1 trig2 trig3") variable tspy_binds "tspy torrent torrents torrentspy torspy" # To restrict usage to Opped (3), HOpped (2) or Voiced (1) or All (0) users # on all +torrentspy channels, change the variable below variable chan_user_level 0 # you want to allow users to search via /msg, enable this variable private_messages 1 # as per emailed & forum requests, use the next two variables together # to determine the output type like so: # notice_reply 1 & force_private 1 = private notice reply only (this is as requested) # notice_reply 0 & force_private 1 = private msg reply only # notice_reply 1 & force_private 0 = regular channel OR private NOTICE # notice_reply 0 & force_private 0 = regular channel OR private MSG (default) # set to 1 to enable a /notice reply instead, 0 for normal text variable notice_reply 0 # set to 1 to force all replies to be private variable force_private 0 # number of search results/image links to return, 'define:' is always 1 as some defs are huge variable search_results 3 # what to use to seperate results, use "\n" for one result per line variable seperator "\n" # ** this is not an optional setting, if a string is too long to send, it won't be sent! ** # It should be set to the max amount of characters that will be received in a public # message by your IRC server. If you find you aren't receiving results, try lowering this. variable split_length 443 # Use these two settings to set colours, bold, reverse, underline etc on either descriptions or links # The following modes apply and you can use any combination of them: (NO SPACES!) # # Bold = \002 # Underline = \037 # Reverse = \026 # Colors: #RGB/Html code: # White = \00300 #FFFFFF # Black = \00301 #000000 # Blue = \00302 #00007F # Green = \00303 #008F00 # Light Red = \00304 #FF0000 # Brown = \00305 #7F0000 # Purple = \00306 #9F009F # Orange = \00307 #FF7F00 # Yellow = \00308 #F0FF00 # Light Green = \00309 #00F700 # Cyan = \00310 #008F8F # Light Cyan = \00311 #00F7FF # Light Blue = \00312 #0000FF # Pink = \00313 #FF00FF # Grey = \00314 #7F7F7F # Light Grey = \00315 #CFCFCF # # This example will do Bold, Underline and Light Blue: "\002\037\00312" # Note: This will affect *ALL* descs or links. Dont forget to use the \ too ! # Also note, abusing this this heavily increases the number of characters per line, # so your output lines will increase somewhat. variable desc_modes "\00307" variable link_modes "\037\00311" # number of minute(s) to ignore flooders, 0 to disable flood protection variable ignore 1 # how many requests in how many seconds is considered flooding? # by default, this allows 3 queries in 10 seconds, the 4th being ignored # and ignoring the flooder for 'variable ignore' minutes variable flood 4:10 } } # end of configuration, script begins namespace eval redeemer { namespace eval torrentspy { variable version "redeemer:torrentspy-1.0" } } # bind the public binds bind pubm -|- "*" redeemer::torrentspy::public_message # bind the private message binds, if wanted if {$redeemer::torrentspy::private_messages >= 1} { bind msgm -|- "*" redeemer::torrentspy::private_message } namespace eval redeemer { namespace eval torrentspy { #TorrentSpy - perform a search via TorrentSpy # proc torrentspy {input} { # local variable initialization set results 0 ; set break 0 ; set output "" # search_results is set to zero, stop now if {$redeemer::torrentspy::search_results <= 0} { return } # fetch the html set html [fetch_html $input] # parse the html while {$results < $redeemer::torrentspy::search_results} { # the regexp grabs the data and stores it using variables while # the regsub removes unnecessary data for the next loop to be clean # regular search regexp -- {16">.*?href="(.+?)".*?"(.+?)">.*?-<.*?nowrap>(.+?)<.*?nowrap>.*?nowrap>(.+?)<.*?nowrap>(.+?)<.*?icons/(.+?).gif} $html - link desc size seed peer health regsub -- {16">.*?href="(.+?)".*?"(.+?)">.*?-<.*?nowrap>(.+?)<.*?nowrap>.*?nowrap>(.+?)<.*?nowrap>(.+?)<.*?icons/(.+?).gif} $html {} html if {[info exists desc] == 0 { if {$output == ""} { set output "Sorry, no results found." } return $output } # remove trailing spaces from desc and convert %20 to space set desc [string trim $desc] regsub -all "%20" $desc " " desc if {[info exists desc] == 1 && $redeemer::torrentspy::desc_modes != ""} { set desc "$redeemer::torrentspy::desc_modes[string trim $desc]" } # add the search result if {$link != ""} { if {[info exists link] == 1 && $redeemer::torrentspy::link_modes !=""} { set link "$redeemer::torrentspy:link_modes[string trim $link]" } regsub -all "(?:\x93|\x94|"|\")" $link {} link append output "${desc}\017 \002|\002 ${link}\017${redeemer::torrentspy::seperator}" } # increment results and clear variables for next loop unset link ; unset desc ; unset seed ; unset size ; unset peer; unset health ; set break 0 incr results } # make sure there is data to output if {[info exists output] == 0} { set reply "Sorry, no results found." return $reply } return $output } # FETCH_HTML - fetches html for torrentspy.com site proc fetch_html {input} { # TorrentSpy global tcl_platform set query "http://www.torrentspy.com/search.asp?query=" # setup & search for the torrents set ua "TCL/[info tclversion] ([string totitle $tcl_platform(platform)]; $tcl_platform(os)/$tcl_platform(osVersion)) http/[package provide http]" set input [::http::formatQuery $input] set input [string trimleft $input "+"] set query "${query}${input}" set http [::http::config -useragent $ua] set http [::http::geturl $query] set html [::http::data $http] # generic pre-parsing regsub -all "\n" $html "" html regsub -all "(?:\x91|\x92|')" $html {'} html regsub -all "(?:\x93|\x94|")" $html {"} html regsub -all "&" $html {\&} html regsub -all -nocase {(.+?)} $html {^\1} html regsub -all -nocase {} $html "" html regsub -all -nocase {} $html "" html regsub -all -nocase {} $html "" html regsub -all -nocase {} $html "" html regsub -all -nocase {} $html "" html regsub -all -nocase {(?:|)} $html "" html # clean up our response regsub -all {(?:\n|\t)} $html {} html if {[info exists html]} { return $html } else { return "Something failed while attempting to connect to torrentspy.com." } } # PUBLIC_MESSAGE # decides what to do with binds that get triggered # proc public_message {nick uhand hand chan input} { if {[lsearch -exact [channel info $chan] +torrentspy] != -1} { if {$redeemer::torrentspy::chan_user_level == 3} { if {[isop $nick $chan] == 0} { return } } elseif {$redeemer::torrentspy::chan_user_level == 2} { if {[ishalfop $nick $chan] == 0 && [isop $nick $chan] == 0} { return } } elseif {$redeemer::torrentspy::chan_user_level == 1} { if {[isvoice $nick $chan] == 0 && [ishalfop $nick $chan] == 0 && [isop $nick $chan] == 0} { return } } send_output "$input" "$chan" "$nick" "$uhand" } } # PRIVATE_MESSAGE # decides what to do with binds that get triggered # proc private_message {nick uhand hand input} { if {$redeemer::torrentspy::private_messages >= 1} { send_output $input $nick $nick $uhand } } # SEND_OUTPUT # no point having two copies of this in public/private_message # proc send_output {input where nick uhand} { if {[encoding system] != "identity" && [lsearch [encoding names] "ascii"]} { set command_char [encoding convertfrom ascii ${redeemer::torrentspy::command_char}] set input [encoding convertfrom ascii $input] } elseif {[encoding system] == "identity"} { set command_char [encoding convertfrom identity ${redeemer::torrentspy::command_char}] set input [encoding convertfrom identity $input] } else { set command_char ${redeemer::torrentspy::command_char} } #Specifically retrieve only ONE (ascii) character, then check that matches the command_char first set trigger_char [string index $input 0] if {[encoding system] == "identity"} { set trigger_char [encoding convertfrom identity $trigger_char] } #Sanity check 1 - If no match, stop right here. No need to match every (first word) of # every line of channel data against every bind if the command_char doesnt even match. if {$trigger_char != $command_char} { return } set trigger [string range [lindex $input 0] 1 end] #Sanity check 2 - Stop if theres nothing to search for (quiet) set search [string trim [string range $input [string wordend $input 1] end]] if {$search == ""} { return } if {$redeemer::torrentspy::force_private == 1} { set where $nick } # check for torrentspy binds foreach bind [split $redeemer::torrentspy::tspy_binds " "] { if {[string match -nocase $bind $trigger] == 1} { # flood protection check if {[flood $nick $uhand]} { return } # call torrentspy foreach line [redeemer::torrentspy::parse_output [torrentspy $search]] { put_output $where $line } break } } } # PUT_OUTPUT # actually sends the output to the server proc put_output {where line} { if {$redeemer::torrentspy::notice_reply == 1} { putserv "NOTICE $where :$line" } else { putserv "PRIVMSG $where :$line" } } # PARSE_OUTPUT # prepares output for sending to a channel/user, calls line_wrap # proc parse_output {input} { set parsed_output [set parsed_current {}] if {[string match "\n" $redeemer::torrentspy::seperator] == 1} { regsub {\n\s*$} $input "" input foreach newline [split $input "\n"] { foreach line [redeemer::torrentspy::line_wrap $newline] { lappend parsed_output $line } } } else { regsub "(?:${redeemer::torrentspy::seperator}|\\|)\\s*$" $input {} input foreach line [redeemer::torrentspy::line_wrap $input] { lappend parsed_output $line } } return $parsed_output } # 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 $redeemer::torrentspy::split_length #regsub -all "\002" $str " $len} { #regsub -all "= 0} { set redeemer::torrentspy::flood_array($i) 0 incr i -1 } } ; flood_init # FLOOD # updates and returns a users flood status # proc flood {nick uhand} { if {$redeemer::torrentspy::ignore < 1} { return 0 } if {$redeemer::torrentspy::flood_data(flood_num) == 0} { return 0 } set i [expr ${redeemer::torrentspy::flood_data(flood_num)} - 1] while {$i >= 1} { set redeemer::torrentspy::flood_array($i) $redeemer::torrentspy::flood_array([expr $i - 1]) incr i -1 } set redeemer::torrentspy::flood_array(0) [unixtime] if {[expr [unixtime] - $redeemer::torrentspy::flood_array([expr ${redeemer::torrentspy::flood_data(flood_num)} - 1])] <= ${redeemer::torrentspy::flood_data(flood_time)}} { putlog "$incith::google::version: flood detected from ${nick}." newignore [join [maskhost *!*[string trimleft $uhand ~]]] $redeemer::torrentspy::version flooding $redeemer::torrentspy::ignore return 1 } else { return 0 } } } } putlog " - $redeemer::torrentspy::version loaded." # EOF