#!/usr/bin/ruby # irc bot by Yoann Guillot, 2005 # stands on a public channel and automatically invites registered people on an invite-only private channel # customize initialize to your needs require 'socket' class Reloaded < Exception end class Invitebot def initialize ################################# # begin config # @hostfile = ENV['HOME'] + '/invitebot.hostlist' # public chan @arvchan = '#public' # private chan @invchan = '#private' # connection data @curnick = 'the_gatekeeper' @curname = 'the_gatekeeper the_gatekeeper the_gatekeeper :the gatekeeper' # noautoregister_mask @noautoreg_mask = /dhcp/i # # end config ################################# if $DEBUG @arvchan << '-test' @invchan << '-test' end @debugger = nil @auth_masks = Hash.new load_masks end def load_masks File.open(@hostfile).each_line { |l| @auth_masks[l.chomp.downcase] = true } end def save_masks File.open(@hostfile+'.tmp', 'w') { |fd| @auth_masks.keys.sort.each { |m| fd.puts m } } File.rename(@hostfile+'.tmp', @hostfile) end def debug(msg) puts msg if $DEBUG @s.write "PRIVMSG #@debugger :#{msg}\r\n" if @debugger end def send(msg) @s.write msg + "\r\n" debug "> #{msg}" end def privmsg(tg, msg) send "PRIVMSG #{tg} :#{msg}" end def connect @s = TCPSocket.open('irc.iiens.net', 6667) send "NICK #@curnick" send "USER #@curname" @s.each_line { |l| debug "< #{l}" l = l.split ' ' case l[1] when '433' debug "nick collision" sleep rand(8) @curnick += rand(1000).to_s send "NICK #@curnick" when '376' debug "connected" send "MODE #@curnick +F" send "JOIN #@arvchan" send "JOIN #@invchan" return end } end def run connect begin catch(:finished) { @s.each_line { |l| debug "< #{l}" handle l.chomp } } disconnect "I'll be back" rescue Reloaded debug 'reloaded' retry rescue Object disconnect "exception #{$!.class}::#{$!}" raise end end def handle(l) case l when /^PING (.*)/ send "PONG #$1" when /^([^ ]*) INVITE #@curnick :#@invchan/ debug "i am invited" send "JOIN #@invchan" when /^:([^!]*)!([^ ]*) JOIN :(#[a-zA-Z0-9]*)/ nick, mask, chan = $1, $2, $3 return if mask =~ /ircd.*goldorak.iiens.net/ if chan.downcase == @arvchan.downcase # quelqu'un arrive sur le chan publique if @auth_masks[mask.downcase] debug "inviting #{nick} (joined arvchan)" send "INVITE #{nick} #@invchan" end end if chan.downcase == @invchan.downcase # quelqu'un arrive sur le chan privé if not @auth_masks[mask.downcase] and mask !~ @noautoreg_mask debug "autoregistered #{mask}" @auth_masks[mask.downcase] = true save_masks privmsg invchan, "registered #{mask}" end end when /^:([^!]*)!([^ ]*) PRIVMSG (?:#{@arvchan}|#{@curnick}) :!(.*)/i author, host, msg = $1, $2, $3.split(' ') return if not @auth_masks[host.downcase] cmd = msg.shift case cmd when 'invite' debug "inviting #{author} (self request)" send "INVITE #{author} #@invchan" when 'debug' if @debugger if @debugger == author debug "no more debugging" @debugger = nil else debug "switching to new debugger: #{author}" old_debugger = @debugger @debugger = author debug "new debugger, you replace #{old_debugger}" debug "use !debug to stop debugging" end else @debugger = author debug "#{author}: you are now debugging me, use !debug to stop" end end return if author != @debugger case cmd when 'send_raw' send msg.join(' ') when 'quit' throw :finished when 'reload' debug 'reloading' $reloading = true begin load $0 rescue Object $reloading = nil debug "reload error: #{$!.class}::#{$!}, #{$!.backtrace.join(', ')}" else $reloading = nil raise Reloaded end when 'register' while not msg.empty? @auth_masks[msg.shift.downcase] = true end debug "done" when 'unregister' while m = msg.shift begin if m =~ /^\/(.*)\/[mix]*$/ re = Regexp.new($1, true) @auth_masks.delete_if { |k, v| k =~ re } end rescue RegexpError debug "invalid regex" end @auth_masks.delete(m.downcase) end debug "done" when 'list' mask = /.*/ mask = Regexp.new(msg.join(' ')) if msg[0] yield_list(mask) { |m| debug m } end when /^:([^!]*)!([^ ]*) PRIVMSG #{@invchan} :!(.*)/i author, authorhost, msg = $1, $2, $3.split(' ') case msg.shift when 'invite' debug "inviting #{msg[0]} (request from #{author})" send "INVITE #{msg[0]} #@invchan" when 'register' if msg.empty? debug "self-registering #{authorhost}" @auth_masks[authorhost.downcase] = true else while (m = msg.shift) debug "registering #{m} (request from #{author}" @auth_masks[m.downcase] = true end end save_masks privmsg @invchan, 'done' when 'unregister' if msg.empty? debug "self-unregistering #{authorhost}" @auth_masks.delete(autorhost.downcase) else while (m = msg.shift) debug "unregistering #{m} (request from #{author})" begin if m =~ /^\/(.*)\/[mix]*$/ re = Regexp.new($1, true) @auth_masks.delete_if { |k, v| k =~ re } end rescue RegexpError privmsg @invchan, "invalid regex" end @auth_masks.delete(m.downcase) end end save_masks privmsg @invchan, '(done)' when 'list' mask = /.*/ mask = Regexp.new(msg.join(' ')) if msg[0] debug "list request from #{author} for #{mask}" yield_list(mask) { |m| privmsg author, m } when 'help' helpmsg = < #@invchan. Commands valid on #@invchan: !register []: add masks to the bot !unregister [] | /regex/ : remove masks from the bot !list []: send the list of the masks matching (or all) to you in query \002(!)may flood(!)\002 !invite : invites somebody Commands valid in private or on #@arvchan: !invite: invites you on #@invchan EOHELP helpmsg.each{|m| privmsg author, m} end when /^:([^!]*)![^ ]* PRIVMSG [^ ]+ :\001VERSION\001/i send "NOTICE #$1 :\001VERSION gatekeeper v1.2 - by jj\001" when /^:#{@debugger}![^ ]* NICK :?(.*)/ @debugger = $1.chomp end end def yield_list(mask) parm = '' @auth_masks.keys.sort.each { |m| next if m !~ mask if parm.length + m.length >= 400 yield parm parm = '' sleep 1 sleep 1 if @debugger end parm << m << ' ' } parm << '(done)' if parm == '' yield parm end def disconnect(msg) debug "quitting: #{msg}" send "QUIT :#{msg}" @s.shutdown @s.close end end def crashhandler(e) File.open('invitebot.crashlog', 'a') { |fd| fd.puts Time.now.strftime('%d/%m/%y %H:%M:%S : ') + "#{$!.class}::#$!", $!.backtrace } end if not $reloading begin Invitebot.new.run rescue Object crashhandler($!) raise end end