Change soft-fail to use the config, rather than env
[rbx.git] / tools / rubuildius / matzbot / lib / client.rb
blob39ff5eae5287b54fbf1d25c2a912fda68e379d39
1 require 'socket'
3 module MatzBot
4   module Client
5     extend self
7     attr_accessor :config, :socket, :last_nick, :authorized
9     def start(options)
10       self.config ||= {}
11       self.config.merge! Hash[*options.map { |k,v| [k.intern, v] }.flatten]
13       connect!
14       main_loop
15     end
17     def connect!
18       log "Connecting to #{config[:server]}:#{config[:port]}..."
19       self.socket = TCPSocket.new(config[:server], config[:port])
21       socket.puts "USER #{config[:user]} #{config[:nick]} #{config[:name]} :#{config[:name]} \r\n"
22       socket.puts "NICK #{config[:nick]} \r\n"
24       socket.puts "PRIVMSG NickServ :IDENTIFY #{config[:password]}" if config[:password]
26       # channel might not have a # in front of it, so add it
27       config[:channel] = config[:channel][/^#/] ? config[:channel] : '#' + config[:channel]
28       join config[:channel]
29     end
31     def reconnect!
32       socket.close
33       self.socket = nil
34       start
35     end
36     
37     def main_loop
38       while true
39         if IO.select([socket])
40           react_to socket.gets
41         end
42       end
43       #socket.each do |line|      
44       #  react_to line  # its own method so we can change it on the fly, without hurting the loop
45       #end
46     end
48     def react_to(line)
49       begin
50         MatzBot.reload!
51       rescue Exception => bang 
52         say "Class load error #{bang.class} (#{caller[1]}), #{bang}."
53       end      
55       self.authorized = false # not authorized
57       info = grab_info(line) # grabs the info from an PRIVMSG
58       puts line              # puts to the console
60       pong(line) if line[0..3] == "PING" # keep-alive
62       if info && info.last    # only called if grabbing the info was successful
63         log_message info    # logs in a friendly format, in chat.txt
64         execute(info.last, info.first) if info
65       elsif has_error?(line)
66         reconnect! 
67       end
68     end
70     def has_error?(line)
71       log "Error from server: #{line}" and return true if line[/^ERROR/]
72     end
73     
74     def execute(cmd, nick)
75       data = cmd.split
76       return false unless data && data.first && authorize?(data)
78       self.last_nick = nick
80       data.join(' ').split(' then ').each do |command|
81         # callbacks
82         filters(:listen).each do |filter|
83           filter.call(command) if command
84         end if filters(:listen).size.nonzero?
86         command = command.split(' ')
87         command.shift if command.first =~ /^#{config[:nick]}/i
89         if Commands.methods.include? command.first and !(EmptyModule.methods.include? command.first)
90           Commands.send(command.first, command[1..-1])      
91         #else
92         #  say "no command #{command}"
93         end
94       end
95     rescue Exception => bang
96       say "Command error #{bang.class}, #{bang}."
97       say " #{bang.backtrace.first}"
98     end
100     def say(message)
101       Commands.say message
102     end
104     def filters(type)
105       Commands.send(:filters, type)
106     end
107     
108     def pong(line)
109       line[0..3] = "PONG"
110       socket.puts "#{line}"
111       puts "#{line}"
112       Commands.poll(line) if Commands.methods.include? 'poll'
113     end
114     
115     def grab_info(text)
116       # The following is the format of what the bot recieves:
117       # :kyle!~kyle@X-24735511.lmdaca.adelphia.net PRIVMSG #youngcoders :for the most part
118       # :nick!~ident@host PRIVMSG #channel :message
119       text =~ /^\:(.+)\!\~?(.+)\@(.+) PRIVMSG \#?(\w+) \:(.+)/ ? [$1, $2, $3, $4, $5] : false
120     end
121     
122     def authorize?(data)
123       if self.config[:only_when_addressed] and data.first != "#{self.config[:nick]}:"
124         return false
125       end
126       
127       command, password = data.first, data[1]
128       if Commands.protected_instance_methods(false).include? command
129         self.authorized = config[:password] && password == config[:password]
130         data.delete_at(1) 
131         authorized
132       else true
133       end
134     end
135     
136     def join(channel, quit_prev = true)
137       socket.puts "PART #{config[:channel]}" if quit_prev
138       socket.puts "JOIN #{channel} \r\n"
139       config[:channel] = channel
140     end
141     
142     def log_message(array)
143       log "<#{array[0]}> : #{array[4]}"
144     end
146     def log(string)
147       File.open("chat.txt", "a") do |f|
148         f.puts "[#{Time.new.strftime "%m/%d/%Y %I:%M %p PST"}] #{string} \n"
149       end
150     end
151   end
154 module EmptyModule