Implement DCProtocol for EventMachine
[dcbot.git] / dcppsocket.rb
blobc08045f3792f1a56364dc60e5920a606d389ad2e
1 require 'socket'
3 def debug(msg)
4   #STDERR.puts "DEBUG #{msg}"
5 end
7 class DCPPSocket
8   attr_reader :socket
9   attr :serverPort
10   
11   SERVER_PORT = 1412
12   
13   def initialize(server, port, nickname, serverPort = SERVER_PORT)
14     @nickname = nickname
15     @messages = []
16     @serverPort = serverPort
17     @clients = []
18     @messageCallback = Proc.new do |sender, message, isprivate|
19       STDERR.puts "ERROR: No message callback registered"
20     end
21     
22     setupCommands
23     
24     connect(server, port)
25   end
26   
27   def connect(server, port)
28     @hubsocket = TCPSocket.new(server, port)
29     @server = TCPServer.new('localhost', @serverPort)
30   end
31   
32   def startRunLoop()
33     catch :done do
34       while true
35         begin
36           readsockets, _, _ = IO.select([@hubsocket, @server, *@clients])
37           readsockets.each do |socket|
38             if socket == @hubsocket then
39               parseCommand()
40             elsif socket == @server then
41               clientsocket << @server.accept
42               # print the client and return for now
43               STDERR.puts "Client: #{clientsocket.peeraddr.inspect}"
44               clientsocket.close
45               # @clients << clientsocket
46             else
47               # socket is a client
48               
49             end
50           end
51         rescue StandardError => e
52           STDERR.puts "ERROR: #{e.to_s}"
53         end
54       end
55     end
56   end
57   
58   def registerMessageCallback(&block)
59     @messageCallback = block
60   end
61   
62   def sendPublicMessage(message)
63     sendMessage(@nickname, message)
64   end
65   
66   def sendPrivateMessage(recipient, message)
67     send("To:", recipient, "From:", @nickname, "$<#{@nickname}>", message)
68   end
69   
70   def close
71     @hubsocket.close
72   end
73   
74   def processMessage(sender, message, isprivate)
75     @messageCallback.call(sender, message, isprivate)
76   end
77   
78   # parses a given command
79   # errors out if expected is given and doesn't match the command
80   def parseCommand(expected = nil)
81     cmdstring = @hubsocket.gets("|")
82     if cmdstring.nil?
83       @hubsocket.close
84       throw :done
85     end
86     debug("<- #{cmdstring.inspect}")
87     cmdstring = cmdstring.chomp("|")
88     cmd, *args = cmdstring.split(" ")
89     
90     if cmd[0,1] == "<" and cmd[-1,1] == ">" then
91       # it's a public message
92       processMessage(cmd[1...-1], args.join(" "), false)
93     else
94       raise "Unexpected command data: #{cmdstring}" unless cmd[0,1] == "$"
95       cmd = cmd[1..-1]
96       raise "Unexpected command: #{cmdstring}" if expected != cmd unless expected.nil?
97       
98       raise "Unknown command: #{cmdstring}" unless @commands.has_key? cmd
99       
100       @commands[cmd].call(*args)
101     end
102   end
103   
104   def send(cmd, *args)
105     message = "$#{cmd}#{args.empty? ? "" : " "}#{args.join(" ")}|"
106     debug("-> #{message.inspect}")
107     @hubsocket.write(message)
108   end
109   
110   def sendMessage(nick, message)
111     str = "<#{nick}> #{message}|"
112     debug("-> #{str.inspect}")
113     @hubsocket.write(str)
114   end
115   
116   def lockToKey(lock)
117     key = String.new(lock)
118     1.upto(key.size - 1) do |i|
119       key[i] = lock[i] ^ lock[i-1]
120     end
121     key[0] = lock[0] ^ lock[-1] ^ lock[-2] ^ 5
122     
123     # nibble-swap
124     0.upto(key.size - 1) do |i|
125       key[i] = ((key[i]<<4) & 240) | ((key[i]>>4) & 15)
126     end
127     
128     0.upto(key.size - 1) do |i|
129       if [0,5,36,96,124,126].include?(key[i]) then
130         key[i,1] = ("/%%DCN%03d%%/" % key[i])
131       end
132     end
133     
134     key
135   end
136   
137   # Commands
138   def cmd(name, &block)
139     @commands[name] = block
140   end
141   
142   def setupCommands
143     @commands = Hash.new
144     cmd("Lock") do |lock,pk|
145       send("Key", lockToKey(lock))
146       send("ValidateNick", @nickname)
147     end
148     cmd("HubName") { |*name| @hubname = name.join(" ") }
149     cmd("Hello") do |nick|
150       if @nickname == nick
151         send("Version", "1,0091")
152         send("GetNickList")
153         send("MyINFO", "$ALL #{@nickname} Ruby Request Bot<RubyBot V:0.1>$", "$Bot\001$$0$")
154       end
155     end
156     cmd("MyINFO") { |*args| } # do nothing for MyINFO
157     cmd("ConnectToMe") do |*args|
158       STDERR.puts("ConnectToMe: #{args.inspect}")
159     end # also ignore this - we aren't a fileserver
160     cmd("RevConnectToMe") do |nick,remote|
161       STDERR.puts("RevConnectToMe: #{nick} #{remote}")
162       send("RevConnectToMe", remote, nick)
163     end # pretend to be passive
164     cmd("To:") do |me, from, sender, *message|
165       raise "Unexpected To: data" if from != "From:" or message[0][0,1] != "$"
166       processMessage(sender, message[1..-1].join(" "), true)
167     end
168     cmd("NickList") { |*args| } # ignore
169     cmd("OpList") { |*args| } # ignore
170     cmd("Quit") { |*args| } # ignore
171     cmd("Search") { |*args| } # ignore
172   end