#---------------------------------------------------------------------# # incith:svn $Rev:: 94 $ # # $Id:: incith-svn.tcl 94 2009-01-21 05:53:33Z incith $ # # # # will monitor 'svn log' output on a respository(s), and output it # # tested on Eggdrop v1.6.19 & Windrop v1.6.17 # # # # Usage: # # .chanset #channel +svn # # !svn repo revision/head/etc # # returns the log for revision# # # # # Requirements: # # - svn (command line client): http://subversion.tigris.org/ # # # # For Windows/Windrop users: # # - http://www.sliksvn.com/en/download - seems a good choice for # # 32/64bit command line svn. All you need to install is the # # "Subversion Client". If for example you tell it to install to # # c:/svn, then {c:/svn/bin/svn.exe} would be your 'svn' variable. # # - If you are having problems with [exec], please see: # # http://forum.egghelp.org/viewtopic.php?p=86629#86629 # # # # ChangeLog (m/d/y): # # 1/18/2009: updated header information. # # 1/17/2009: 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 svn # 0 (zero) will disable an optional variable, 1 or above enables # namespace eval incith::svn { # the bind prefix/command char(s) ({!} or {! .} etc, separate with space) variable command_chars {! .} # binds {one two three} variable binds {svn} variable repos {svn://svn.incith.com/incith/trunk svn://svn.incith.com/incith/branches svn://svn.incith.com/incith/tags http://svn.madwifi-project.org/madwifi/trunk http://svn.irssi.org/repos/irssi/trunk} # 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? (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 # maximum length of a reply before breaking it up variable split_length 440 # 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 svn 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::svn { global botnet-nick nick if {${botnet-nick} == ""} { set botnet-nick ${nick} } variable version "incith:svn-SVN" variable debug 0 array set static {} if {${incith::svn::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 [string trim $incith::svn::command_chars] " "] { foreach bind [split [string trim $incith::svn::binds] " "] { # public message binds bind pub -|- "${command_char}${bind}" incith::svn::message_handler # private message binds if {${incith::svn::private_messages} >= 1} { bind msg -|- "${command_char}${bind}" incith::svn::message_handler } else { unbind msg -|- "${command_char}${bind}" incith::svn::message_handler } } } bind time - "00 * * * *" incith::svn::init_logs_timer bind time - "05 * * * *" incith::svn::init_logs_timer bind time - "10 * * * *" incith::svn::init_logs_timer bind time - "15 * * * *" incith::svn::init_logs_timer bind time - "20 * * * *" incith::svn::init_logs_timer bind time - "25 * * * *" incith::svn::init_logs_timer bind time - "30 * * * *" incith::svn::init_logs_timer bind time - "35 * * * *" incith::svn::init_logs_timer bind time - "40 * * * *" incith::svn::init_logs_timer bind time - "45 * * * *" incith::svn::init_logs_timer bind time - "50 * * * *" incith::svn::init_logs_timer bind time - "55 * * * *" incith::svn::init_logs_timer # bind the botnet binds if {${incith::svn::botnet} >= 1} { bind bot - incith:svn incith::svn::bot_msg bind link - * incith::svn::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::svn::bot_disc } namespace eval incith::svn { proc bot_msg {from cmd text} { global botnet-nick upvar #0 incith::svn::static static if {${incith::svn::debug} >= 1} { putlog "${incith::svn::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::svn::version} (botnet): ${from} has joined the incith:svn 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::svn::static static # send our time to the bots putallbots "incith:svn time $static(botnet,${botnet-nick},time)" } proc bot_disc {bot} { global botnet-nick upvar #0 incith::svn::static static if {[string match "*${bot}*" $static(botnet,bots)]} { if {$static(botnet,${bot},time) != "noswarm"} { putlog "${incith::svn::version} (botnet): ${bot} has left the incith:svn 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::svn { # [message_handler] : handles public & private messages # proc message_handler {nick uhand hand args} { global botnet-nick lastbind upvar #0 incith::svn::static static set input(who) $nick if {[llength $args] >= 2} { # public message set input(where) [lindex $args 0] if {${incith::svn::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) svn] != 1} { return } } else { # private message set input(where) "private" set input(chan) $input(who) set input(query) [lindex $args 0] if {${incith::svn::private_messages} <= 0} { return } } # botnet if {${incith::svn::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)} { putlog "${incith::svn::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)} { putlog "${incith::svn::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:svn time $static(botnet,${botnet-nick},time)" return } } # looks like we're serving, make sure we keep the botnet up to date putallbots "incith:svn time $static(botnet,${botnet-nick},time)" } # TODO: check flood protection here # log it if {[regexp -- {^\s*$} $input(query)]} { ipl "<${input(who)}/${input(where)}>" send_output $input(chan) "Please supply a URL to fetch the title from. (http:// protocol)" return } else { ipl ${input(who)} ${input(where)} "$lastbind ${input(query)}" } # do some things: set input(url) $input(query) send_output $input(where) [fetch_log [array get input]] } proc fetch_log {tmpInput} { if {[info exists tmpInput]} { array set input $tmpInput } if {$input(url) == "incith"} { set input(url) "svn://svn.incith.com/incith/trunk" } if {$input(url) == "incith/trunk"} { set input(url) "svn://svn.incith.com/incith/trunk" } if {$input(url) == "incith/branches"} { set input(url) "svn://svn.incith.com/incith/branches" } if {$input(url) == "incith/tags"} { set input(url) "svn://svn.incith.com/incith/tags" } if {$input(url) == "irssi"} { set input(url) "http://svn.irssi.org/repos/irssi/trunk" } if {$input(url) == "madwifi"} { set input(url) "http://svn.madwifi-project.org/madwifi/trunk" } if {![regexp {(?:http|svn)\:\/\/} $input(url)]} { set input(url) "svn://${input(url)}" } set svnrefirst {---\nr(.+?)\n-----} set svnre {(\d+)\s*\|\s*(.+?)\s*\|\s*(.+?)\s*\|\s*(\d+ lines?)\n\n(.+?)} catch {[exec svn log -l1 $input(url)]} data if {![regexp -- $svnrefirst $data - tmpline]} { return "$data" } regexp -- $svnre $tmpline - rev name date lines msg if {[info exists msg]} { if {$msg != ""} { set msg [string trimright $msg "\n"] ; set msg [string map "{\n} { }" $msg] } else { set msg "(no message)" } } set static(${input(url)},rev) $rev return "r${rev} committed to ${input(url)} by ${name}: ${msg}" } proc init_logs_timer {args} { init_logs 0 } proc init_logs {IsLoading} { upvar #0 incith::svn::static static set svnrefirst {---\nr(.+?)\n-----} set svnre {(\d+)\s*\|\s*(.+?)\s*\|\s*(.+?)\s*\|\s*(\d+ lines?)\n\n(.+?)} if {$IsLoading == 1} { ipl "Reading repository logs, this may take a moment..." } foreach log [split [string trim $incith::svn::repos] " "] { if {[info exists static($log,rev)] && $IsLoading == 1} { putlog " - $log is at r$static($log,rev)" continue } catch {[exec svn log -l1 $log]} data regexp -- $svnre $data - rev name date lines msg regsub -- $svnre $data {} data if {[info exists rev]} { if {$IsLoading == 1} { set static($log,rev) $rev putlog " - $log is at r$static($log,rev)" } else { if {[info exists static($log,rev)]} { if {$static($log,rev) < $rev} { unset data ; unset rev ; catch {[exec svn log -r$static($log,rev):HEAD $log]} data # remove the current revision from the beginning: regsub -- $svnrefirst $data {} data while {[regexp -- $svnrefirst $data]} { regexp -- $svnrefirst $data - tmpline regexp -- $svnre $tmpline - rev name date lines msg if {[info exists msg]} { if {$msg != ""} { set msg [string trimright $msg "\n"] ; set msg [string map "{\n} { }" $msg] } else { set msg "(no message)" } } regsub -nocase -- $svnrefirst $data {} data set reply "r${rev} committed to ${log} by ${name}: ${msg}" send_output ##incith $reply send_output #incith $reply set static($log,rev) $rev unset rev } } } } if {[info exists rev]} { unset rev } } } } # [ipl] : a neat/handy putlog procedure # proc ipl {who {where {}} {what {}}} { if {$where == "" && $what == ""} { # first argument only = data only putlog "${incith::svn::version}: ${who}" } elseif {$where != "" && $what == ""} { # two arguments = who and data putlog "${incith::svn::version}: <${who}> ${where}" } else { # all three... putlog "${incith::svn::version}: <${who}/${where}> ${what}" } } # [send_output] : sends $data appropriately out to $where # proc send_output {where data {isErrorNick {}}} { if {${incith::svn::notices} >= 1} { foreach line [incith::svn::parse_output $data] { putquick "NOTICE $where :${line}" } } elseif {${incith::svn::notice_errors_only} >= 1 && $isErrorNick != ""} { foreach line [incith::svn::parse_output $data] { putquick "NOTICE $isErrorNick :${line}" } } else { foreach line [incith::svn::parse_output $data] { putquick "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" $incith::svn::separator] == 1} { regsub {\n\s*$} $input "" input foreach newline [split $input "\n"] { foreach line [incith::svn::line_wrap $newline] { lappend parsed_output $line } } } else { regsub "(?:${incith::svn::separator}|\\|)\\s*$" $input {} input foreach line [incith::svn::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 $incith::svn::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] } # [ibold] : bolds some text, if bolding is enabled # proc ibold {input} { if {${incith::svn::bold} >= 1} { return "\002${input}\002" } return $input } } # the script has loaded. namespace eval incith::svn { putallbots "incith:svn time $static(botnet,${botnet-nick},time)" init_logs 1 } incith::svn::ipl "loaded." # EOF