#---------------------------------------------------------------#
# 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"></a>.*?href="(.+?)".*?"(.+?)">.*?-<.*?nowrap>(.+?)<.*?nowrap>.*?nowrap>(.+?)<.*?nowrap>(.+?)<.*?icons/(.+?).gif} $html - link desc size seed peer health
regsub -- {16"></a>.*?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 {<sup>(.+?)</sup>} $html {^\1} html
regsub -all -nocase {<font.+?>} $html "" html
regsub -all -nocase {</font>} $html "" html
regsub -all -nocase {<span.+?>} $html "" html
regsub -all -nocase {</span>} $html "" html
regsub -all -nocase {<input.+?>} $html "" html
regsub -all -nocase {(?:<i>|</i>)} $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 "<ZQ" str
#regsub -all "\037" $str "<ZX" str
foreach word [split [set str][set str ""] $splitChr] {
if {[incr i [string len $word]] > $len} {
#regsub -all "<ZQ" $cur "\002" cur
#regsub -all "<ZX" $cur "\037" cur
lappend out [join $cur $splitChr]
set cur [list $word]
set i [string len $word]
} else {
lappend cur $word
}
incr i
}
#regsub -all "<ZQ" $cur "\002" cur
#regsub -all "<ZX" $cur "\037" cur
lappend out [join $cur $splitChr]
}
# FLOOD_INIT
# modified from bseen
#
variable flood_data
variable flood_array
proc flood_init {} {
if {$redeemer::torrentspy::ignore < 1} {
return 0
}
if {![string match *:* $redeemer::torrentspy::flood]} {
putlog "$redeemer::torrentspy::version: variable flood not set correctly."
return 1
}
set redeemer::torrentspy::flood_data(flood_num) [lindex [split $redeemer::torrentspy::flood :] 0]
set redeemer::torrentspy::flood_data(flood_time) [lindex [split $redeemer::torrentspy::flood :] 1]
set i [expr $redeemer::torrentspy::flood_data(flood_num) - 1]
while {$i >= 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