Trap ^C and shut down cleanly
[dcbot.git] / dcbot.rb
blobf0c97e6c859814dce300659771614ec4e1ec18ab
1 #!/usr/bin/env ruby
3 require 'rubygems'
4 require 'optparse'
5 require 'eventmachine'
6 require './config'
7 require './dcprotocol'
8 require './keyboard'
9 require './plugin'
10 require 'pp'
12 SLEEP_TABLE = [1, 2, 5, 15, 30, 60, 120, 300]
14 RUBYBOT_VERSION = "0.1"
16 DCProtocol.registerClientVersion("RubyBot", RUBYBOT_VERSION)
18 def main
19   # parse args
20   options = {}
21   options[:config_file] = "dcbot.conf"
22   OptionParser.new do |opts|
23     opts.banner = "Usage: dcbot.rb [options]"
24     
25     opts.on("--config filename", "Use filename as the config file", "[default: dcbot.conf]") do |filename|
26       options[:config_file] = filename
27     end
28     opts.on("--[no-]debug", "Sets the debug flag") { |flag| options[:debug] = flag }
29     opts.on("--[no-]peer-debug", "Sets the debug flag for peer connections") { |flag| options[:peer_debug] = flag }
30   end.parse!
31   
32   config = IniReader.read(options[:config_file])
33   
34   global_config = config.find { |section| section[0] == "global" }
35   debug = false
36   if options.has_key? :debug then
37     debug = options[:debug]
38   elsif global_config and global_config[1].has_key? "debug" then
39     debug = global_config[1]["debug"].downcase
40     if debug == "true" then
41       debug = true
42     end
43   end
44   
45   connections = config.select { |section| section[0] == "connection" }
46   # FIXME: use all config sections
47   connection = connections[0][1]
48   host = connection["host"]
49   port = connection["port"].to_i
50   nickname = connection["nickname"]
51   description = connection["description"]
52   
53   if connection.has_key? "prefix" then
54     PluginBase.cmd_prefix = connection["prefix"]
55   end
56   
57   EventMachine::run do
58     EventMachine::open_keyboard KeyboardInput
59     sockopts = { :description => description,
60                  :debug => debug,
61                  :peer_debug => options[:peer_debug] }
62     setupConnection(host, port, nickname, sockopts, 0)
63   end
64   puts "Goodbye"
65 end
67 def setupConnection(host, port, nickname, sockopts, sleep)
68   $socket = DCClientProtocol.connect(host, port, nickname, sockopts) do |c|
69     c.registerCallback :message do |socket, sender, message, isprivate|
70       if isprivate or sender == "*Dtella" then
71         puts "<#{sender}> #{message}"
72       end
73       if message[0,1] == PluginBase::CMD_PREFIX then
74         cmd, args = message[1..-1].split(" ", 2)
75         args = "" if args.nil?
76         if cmd == "reload" and isprivate then
77           # special sekrit reload command
78           PluginBase.loadPlugins
79           socket.sendPrivateMessage(sender, "Plugins have been reloaded")
80         elsif cmd == "quit" and isprivate then
81           # super-sekrit quit command
82           socket.close
83         elsif PluginBase.has_command?(cmd) then
84           begin
85             PluginBase.dispatch(socket, cmd, sender, isprivate, args)
86           rescue StandardError => e
87             socket.sendPrivateMessage(sender, "An error occurred executing your command: #{e.to_s}")
88             STDERR.puts "ERROR: #{e.to_s}"
89             PP.pp(e.backtrace, STDERR)
90           end
91         elsif isprivate then
92           socket.sendPrivateMessage(sender, "Unknown command: #{PluginBase::CMD_PREFIX}#{cmd}")
93         end
94       end
95     end
96     c.registerCallback :error do |socket, message|
97       STDERR.puts "! #{message}"
98     end
99     c.registerCallback :peer_error do |socket, peer, message|
100       STDERR.puts "! Peer #{peer.host}:#{peer.port}: #{message}"
101     end
102     c.registerCallback :unbind do |socket|
103       if c.quit then
104         # this is our only socket for the moment
105         EventMachine.stop_event_loop
106       else
107         EventMachine::add_timer(SLEEP_TABLE[sleep]) do
108           sleep += 1 unless sleep == SLEEP_TABLE.size - 1
109           setupConnection(host, port, nickname, sockopts, sleep)
110         end
111       end
112     end
113     c.registerCallback :connected do |socket|
114       puts "Connected"
115       socket.registerCallback :unbind do |socket|
116         if c.quit then
117           # this is our only socket for the moment
118           EventMachine.stop_event_loop
119         else
120           setupConnection(host, port, nickname, sockopts, 0)
121         end
122       end
123     end
124     c.registerCallback :reverse_connection do |socket, user|
125       puts "* Bouncing RevConnectToMe back to user: #{user.nickname}"
126     end
127     c.registerCallback :reverse_connection_ignored do |socket, user|
128       puts "* Ignoring RevConnectToMe from user: #{user.nickname}"
129     end
130     c.registerCallback :peer_initialized do |socket, peer|
131       puts "* Connecting to peer: #{peer.host}:#{peer.port}"
132     end
133     c.registerCallback :peer_unbind do |socket, peer|
134       peer_id = "#{peer.host}:#{peer.port}"
135       peer_id << " (#{peer.remote_nick})" if peer.remote_nick
136       puts "* Connection to peer #{peer_id} closed"
137     end
138     c.registerCallback :peer_get do |socket,peer,filename|
139       peer_id = "#{peer.host}:#{peer.port}"
140       peer_id << " (#{peer.remote_nick})" if peer.remote_nick
141       puts "* Peer #{peer_id} requested: #{filename}"
142     end
143   end
146 Signal.trap 'INT' do
147   Signal.trap 'INT', 'DEFAULT'
148   STDERR.puts "\nShutting down..."
149   # TODO: cancel all client2client connections that are in a cancel-able state
150   EventMachine.stop_event_loop
153 main