public
Description: Direct Connect bot written in Ruby
Clone URL: git://github.com/kballard/dcbot.git
Search Repo:
DCPeerProtocol now supports ADCGet and XmlBZList

DCPeerProtocol no longer supports DcLst files but instead supports 
XmlBZList and related commands.
It also supports ADCGet.
As part of this change, he3.rb is no longer required, and the LICENSE is 
modified accordingly.

Additionally, the client name/version has been lifted out of dcprotocol.rb 
and into dcbot.rb
kballard (author)
Fri Feb 22 17:06:49 -0800 2008
commit  0bf8305af7f0ddbf8767abb29976c8e5791bd8b7
tree    18bc6dbb35bc175715af6a18800a0c53c689c799
parent  019d15aa8676dc184cdc030fa03a83c0f2503213
...
38
39
40
41
42
43
44
 
45
46
47
...
38
39
40
 
 
 
 
41
42
43
44
0
@@ -38,10 +38,7 @@
0
        d) make other distribution arrangements with the author.
0
 
0
   4. You may modify and include the part of the software into any other
0
- software (possibly commercial). But some files in the distribution
0
- are not written by the author, so that they are not under this terms.
0
-
0
- The only file at this time is he3.rb.
0
+ software (possibly commercial).
0
 
0
   5. The scripts and library files supplied as input to or produced as
0
      output from the software do not automatically fall under the
...
13
14
15
 
 
16
17
18
...
56
57
58
59
60
 
61
62
63
64
...
131
132
133
134
135
136
 
137
138
139
140
 
 
 
141
142
143
...
13
14
15
16
17
18
19
20
...
58
59
60
 
 
61
62
63
64
65
...
132
133
134
 
 
 
135
136
137
138
 
139
140
141
142
143
144
0
@@ -13,6 +13,8 @@
0
 
0
 RUBYBOT_VERSION = "0.1"
0
 
0
+DCProtocol.registerClientVersion("RubyBot", RUBYBOT_VERSION)
0
+
0
 def main
0
   # parse args
0
   options = {}
0
@@ -56,8 +58,7 @@
0
     EventMachine::open_keyboard KeyboardInput
0
     sockopts = { :description => description,
0
                  :debug => debug,
0
- :peer_debug => options[:peer_debug],
0
- :version => RUBYBOT_VERSION }
0
+ :peer_debug => options[:peer_debug] }
0
     setupConnection(host, port, nickname, sockopts, 0)
0
   end
0
   puts "Shutting Down"
0
0
@@ -131,13 +132,13 @@
0
     end
0
     c.registerCallback :peer_unbind do |socket, peer|
0
       peer_id = "#{peer.host}:#{peer.port}"
0
- if peer.remote_user then
0
- peer_id << " (#{peer.remote_user.nickname})"
0
- end
0
+ peer_id << " (#{peer.remote_nick})" if peer.remote_nick
0
       puts "* Connection to peer #{peer_id} closed"
0
     end
0
     c.registerCallback :peer_get do |socket,peer,filename|
0
- puts "* Peer #{peer.remote_user.nickname} requested: #{filename}"
0
+ peer_id = "#{peer.host}:#{peer.port}"
0
+ peer_id < " (#{peer.remote_nick})" if peer.remote_nick
0
+ puts "* Peer #{peer_id} requested: #{filename}"
0
     end
0
   end
0
 end
...
1
2
3
 
4
5
6
7
8
 
 
 
 
 
 
 
 
9
10
11
...
45
46
47
48
 
49
50
51
...
64
65
66
67
 
68
69
70
...
114
115
116
117
118
119
120
...
126
127
128
129
130
131
132
...
193
194
195
196
 
197
198
199
200
201
202
203
204
205
206
207
208
...
356
357
358
359
 
360
361
362
363
364
365
366
367
 
368
369
 
370
 
 
371
372
 
 
 
 
 
 
373
374
375
376
 
377
378
379
380
381
382
383
 
384
385
386
387
388
389
390
391
 
 
 
 
 
392
393
394
 
 
 
 
 
 
395
396
397
 
398
399
400
401
402
403
...
410
411
412
 
413
414
415
416
 
 
417
418
419
420
 
421
422
423
424
425
 
 
426
427
428
429
430
...
430
431
432
433
434
 
435
436
437
438
439
 
440
441
442
443
444
445
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
446
447
448
...
1
2
 
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...
53
54
55
 
56
57
58
59
...
72
73
74
 
75
76
77
78
...
122
123
124
 
125
126
127
...
133
134
135
 
136
137
138
...
199
200
201
 
202
203
204
205
206
207
208
209
210
211
212
213
214
...
362
363
364
 
365
366
367
368
369
 
 
 
 
370
371
 
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
 
395
396
397
 
 
 
 
 
 
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
...
428
429
430
431
432
433
 
 
434
435
436
437
438
439
440
441
442
443
 
 
444
445
446
447
448
449
450
...
450
451
452
 
 
453
454
455
456
457
 
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
0
@@ -1,11 +1,19 @@
0
 require 'stringio'
0
 require 'cgi' # for entity-escaping
0
-require './he3'
0
+require 'bz2'
0
 require './dcuser'
0
 
0
 class DCProtocol < EventMachine::Connection
0
   include EventMachine::Protocols::LineText2
0
   
0
+ CLIENT_NAME = "RubyBot"
0
+ CLIENT_VERSION = "0.1"
0
+
0
+ def self.registerClientVersion(name, version)
0
+ CLIENT_NAME.replace name
0
+ CLIENT_VERSION.replace version
0
+ end
0
+
0
   def registerCallback(callback, &block)
0
     @callbacks[callback] << block
0
   end
0
@@ -45,7 +53,7 @@
0
   end
0
   
0
   def send_data(data)
0
- STDERR.puts "-> #{data}" if @debug
0
+ STDERR.puts "-> #{data.gsub(/[^\x20-\x7F]/, ".")}" if @debug
0
     super
0
   end
0
   
0
@@ -64,7 +72,7 @@
0
   end
0
   
0
   def receive_line(line)
0
- STDERR.puts "<- #{line}" if @debug
0
+ STDERR.puts "<- #{line.gsub(/[^\x20-\x7F]/, ".")}" if @debug
0
     line.chomp!("|")
0
     line = unsanitize(line)
0
     cmd = line.slice!(/^\S+/)
0
@@ -114,7 +122,6 @@
0
   # speed
0
   # speed_class
0
   # email
0
- # version - version number for the tag
0
   # slots - number of slots to declare as open
0
   def self.connect(host, port, nickname, args = {})
0
     EventMachine::connect(host, port, self) do |c|
0
@@ -126,7 +133,6 @@
0
         @config[:speed] ||= "Bot"
0
         @config[:speed_class] ||= 1
0
         @config[:email] ||= ""
0
- @config[:version] ||= "0.1"
0
         @config[:slots] ||= 0
0
       end
0
       yield c if block_given?
0
@@ -193,7 +199,7 @@
0
       # this is us, we should respond
0
       send_command "Version", "1,0091"
0
       send_command "GetNickList"
0
- send_command "MyINFO", "$ALL #{@nickname} #{@config[:description]}<RubyBot V:#{@config[:version]},M:P,H:1/0/0,S:#{@config[:slots]}>$", \
0
+ send_command "MyINFO", "$ALL #{@nickname} #{@config[:description]}<#{CLIENT_NAME} V:#{CLIENT_VERSION},M:P,H:1/0/0,S:#{@config[:slots]}>$", \
0
                              "$#{@config[:speed]}#{@config[:speed_class].chr}$#{@config[:email]}$0$"
0
     else
0
       user = DCUser.new(self, nick)
0
0
0
0
0
0
0
0
0
0
@@ -356,45 +362,57 @@
0
 class DCPeerProtocol < DCProtocol
0
   XML_FILE_LISTING = <<EOF
0
 <?xml version="1.0" encoding="utf-8"?>
0
-<FileListing Version="1" Generator="RubyBot">
0
+<FileListing Version="1" Generator="#{CLIENT_NAME} #{CLIENT_VERSION}">
0
 <Directory Name="Send a /pm with !help for help">
0
 </Directory>
0
 </FileListing>
0
 EOF
0
- DCLST_FILE_LISTING = <<EOF
0
-Send a /pm with !help for help
0
-EOF
0
- DCLST_FILE_LISTING_HE3 = he3_encode(DCLST_FILE_LISTING)
0
+ XML_FILE_LISTING_BZ2 = BZ2.bzip2(XML_FILE_LISTING)
0
   
0
- attr_reader :remote_user, :host, :port
0
+ SUPPORTED_EXTENSIONS = ["ADCGet", "XmlBZList", "TTHF"]
0
   
0
+ attr_reader :remote_nick, :host, :port, :state
0
+
0
   def post_init
0
     super
0
+ @state = :init
0
+ @supports = nil
0
+ self.registerCallback :error do |peer, message|
0
+ peer.send_command "Error", message unless peer.state == :data
0
+ peer.close_connection_after_writing
0
+ end
0
   end
0
   
0
   # callbacks triggered from the peer always begin with peer_
0
   def call_callback(name, *args)
0
+ super
0
     @parent.call_callback "peer_#{name.to_s}".to_sym, self, *args
0
   end
0
   
0
   def connection_completed
0
     super
0
     send_command "MyNick", @parent.nickname
0
- send_command "Lock", "FOO", "Pk=BAR"
0
+ send_command "Lock", "EXTENDEDPROTOCOLABCABCABCABCABCABC", "Pk=#{CLIENT_NAME}#{CLIENT_VERSION}ABCABC"
0
   end
0
   
0
- def cmd_MyNick(line)
0
- nick = line
0
- @remote_user = @parent.users[nick]
0
- if @remote_user.nil? then
0
- # why are we being connected to by someone not on the hub?
0
- @remote_user = DCUser.new(@parent, nick)
0
+ def get_file_io(filename)
0
+ if filename == "files.xml.bz2" then
0
+ StringIO.new(XML_FILE_LISTING_BZ2)
0
+ else
0
+ nil
0
     end
0
   end
0
   
0
+ # Protocol hooks
0
+
0
+ def cmd_MyNick(line)
0
+ @remote_nick = line
0
+ end
0
+
0
   def cmd_Lock(line)
0
     lock = line.split(" ")[0]
0
     key = lockToKey(lock)
0
+ send_command "Supports", *SUPPORTED_EXTENSIONS if lock =~ /^EXTENDEDPROTOCOL/
0
     send_command "Direction", "Upload", rand(0x7FFF)
0
     send_command "Key", key
0
   end
0
0
0
0
@@ -410,19 +428,21 @@
0
       call_callback :error, "Unexpected peer direction: #{direction}"
0
       # close_connection
0
     end
0
+ @state = :normal
0
   end
0
   
0
- def cmd_GetListLen(line)
0
- send_command "ListLen", DCLST_FILE_LISTING_HE3.length
0
+ def cmd_Supports(line)
0
+ @supports = line.split(" ")
0
   end
0
   
0
   def cmd_Get(line)
0
     if line =~ /^([^$]+)\$(\d+)$/ then
0
+ @state = :data
0
       @filename = $1
0
       offset = $2.to_i - 1 # it's 1-based
0
       call_callback :get, @filename
0
- if @filename == "MyList.DcLst" then
0
- @fileio = StringIO.new(DCLST_FILE_LISTING_HE3)
0
+ @fileio = get_file_io(@filename)
0
+ if @fileio then
0
         @fileio.pos = offset
0
         send_command "FileLength", @fileio.size - @fileio.pos
0
       else
0
0
0
@@ -430,19 +450,69 @@
0
         close_connection_after_writing
0
       end
0
     else
0
- send_command "Error", "Unknown $Get format"
0
- close_connection_after_writing
0
+ call_callback :error, "Unknown $Get format"
0
     end
0
   end
0
   
0
   def cmd_Send(line)
0
- if @fileio.nil? then
0
+ if @fileio.nil? or @state != :data then
0
       # we haven't been asked for the file yet
0
       send_command "Error", "Unexpected $Send"
0
       close_connection_after_writing
0
     else
0
       data = @fileio.read(40906)
0
       send_data data
0
+ if @fileio.eof? then
0
+ @state = :normal
0
+ end
0
+ end
0
+ end
0
+
0
+ def cmd_ADCGET(line)
0
+ if line =~ /^(\w+) (.+) (\d+) (-?\d+)(?: (.+))?$/ then
0
+ type = $1
0
+ identifier = $2
0
+ startpos = $3.to_i
0
+ length = $4.to_i
0
+ flags = ($5 || "").split(" ")
0
+ if flags.empty? then
0
+ if type == "file" then
0
+ fileio = get_file_io(identifier)
0
+ if fileio then
0
+ fileio.pos = startpos
0
+ length = fileio.size - fileio.pos if length == -1
0
+ send_command "ADCSND", "file", identifier, startpos, length
0
+ send_data fileio.read(length)
0
+ else
0
+ send_command "Error", "File Not Available"
0
+ end
0
+ else
0
+ send_command "Error", "Unknown $ADCGET type: #{type}"
0
+ end
0
+ else
0
+ send_command "Error", "Unknown $ADCGET flags: #{flags.join(" ")}"
0
+ end
0
+ else
0
+ send_command "Error", "Unknown $ADCGET format"
0
+ end
0
+ end
0
+
0
+ def cmd_UGetBlock(line)
0
+ if line =~ /^(\d+) (-?\d+) (.+)$/ then
0
+ startpos = $1.to_i
0
+ length = $2.to_i
0
+ filename = $3
0
+ fileio = get_file_io(filename)
0
+ if fileio then
0
+ fileio.pos = startpos
0
+ length = fileio.size - fileio.pos if length == -1
0
+ send_command "Sending", length
0
+ send_data fileio.read(length)
0
+ else
0
+ send_command "Failed", "File Not Available"
0
+ end
0
+ else
0
+ send_command "Failed", "Unknown $UGetBlock format"
0
     end
0
   end
0
   
0
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0
@@ -1,181 +1 @@
0
-# This file came from Asami (http://asami.rubyforge.org). There is no LICENSE
0
-# distributed with the Asami project. I have no idea what license the Asami project
0
-# is intended to be distributed under, but in the absence of other information I
0
-# will assume the Ruby license.
0
-
0
-def huff_insert(arr,node)
0
- return arr.unshift(node) if arr.length==0
0
- 0.upto(arr.length-1){|i|
0
- if arr[i].occur>node.occur
0
- if i>0
0
- return arr[0..i-1]+[node]+arr[i..arr.length]
0
- else
0
- return arr.unshift(node)
0
- end
0
- elsif arr[i].occur == node.occur && (node.left==nil)
0
- if i>0
0
- return arr[0..i-1]+[node]+arr[i..arr.length]
0
- else
0
- return arr.unshift(node)
0
- end
0
- end
0
- }
0
- return arr.push(node)
0
-end
0
-def add_bit(str,pos,value)
0
- str << 0 if pos&7==0
0
- if value!=0
0
- str[pos/8]|=(1<<(pos&7))
0
- end
0
-end
0
-def add_bits(str,pos,pattern,len)
0
- 0.upto(len-1){|i|
0
- add_bit(str,pos,pattern>>(len-1-i)&1)
0
- pos+=1
0
- }
0
-end
0
-def get_bit(str,pos)
0
- ((str[pos/8]) >> (pos&7))&1
0
-end
0
-def get_bits(str,start,num)
0
- res=0
0
- 0.upto(num-1){|i|res=res<<1|get_bit(str,start+i);}
0
- res
0
-end
0
-
0
-class HuffNode
0
- attr_accessor :left,:right,:occur,:value
0
- def initialize(occur,value)
0
- @occur=occur
0
- @value=value
0
- @left=nil
0
- @right=nil
0
- end
0
-end
0
-class HufEncode
0
- attr_reader :len,:bits
0
- def initialize(len,bits)
0
- @len=len
0
- @bits=bits
0
- end
0
-end
0
-
0
-def use_hufnode(tbl_enc,node,length,bits)
0
- if node.left!=nil
0
- use_hufnode(tbl_enc,node.left,length+1,(bits<<1)|0)
0
- use_hufnode(tbl_enc,node.right,length+1,(bits<<1)|1)
0
- else
0
- idx=node.value&255
0
- tbl_enc[idx]=HufEncode.new length,bits
0
- end
0
-end
0
-
0
-def he3_encode(str)
0
- tbl_enc=Array.new
0
- data=""
0
- list=Array.new
0
- nb_val=0
0
- occur=Array.new 256,0
0
- if str==nil||str.length==0
0
- puts "zero length or nil string"
0
- end
0
- parity=0
0
- 0.upto(str.length-1){|i|
0
- occur[(str[i]&255)]+=1
0
- parity^=str[i]
0
- }
0
- 0.upto(255){|i|
0
- if occur[i]!=0
0
- mw=HuffNode.new(occur[i],i)
0
- list=huff_insert(list,mw)
0
- nb_val+=1
0
- end
0
- }
0
- while(list.length>1)
0
- node=HuffNode.new(0,0)
0
- node.left=list.shift
0
- node.right=list.shift
0
- node.occur=(node.left.occur||0)+(node.right.occur||0)
0
- list=huff_insert(list,node)
0
- end
0
- root_huff=list.shift
0
- use_hufnode tbl_enc,root_huff,0,0
0
- header="HE3\r0000000"
0
- header[4]=(parity&255)
0
- header[5]=str.length&255
0
- header[6]=(str.length>>8)&255
0
- header[7]=(str.length>>16)&255
0
- header[8]=(str.length>>24)&255
0
- header[9]=nb_val&255
0
- header[10]=(nb_val>>8)&255
0
- data = header
0
- 0.upto(255){|i|
0
- if occur[i]!=0
0
- data << i
0
- data << tbl_enc[i].len
0
- end
0
- }
0
- bit_pos=data.length*8
0
- 0.upto(255){|i|
0
- if occur[i]!=0
0
- add_bits(data,bit_pos,tbl_enc[i].bits,tbl_enc[i].len)
0
- bit_pos+=tbl_enc[i].len;
0
- end
0
- }
0
- bit_pos=(bit_pos+7)&~7
0
- 0.upto(str.length-1){|i|
0
- idx=str[i]&255
0
- add_bits(data,bit_pos,tbl_enc[idx].bits,tbl_enc[idx].len)
0
- bit_pos+=tbl_enc[idx].len
0
- }
0
- return data
0
-end
0
-
0
-def he3_decode(input)
0
- unless input[0]==72 && input[1]==69 && input[2]==51 && input[3]==13
0
- puts "not a valid he3 i guess"
0
- exit
0
- end
0
- nb_output=0
0
- 8.downto(6){|i|
0
- nb_output|=(input[i]&255)
0
- nb_output<<=8
0
- }
0
- nb_output|=(input[5]&255)
0
- nb_couple = input[9]
0
- nb_couple += ((input[10]&255)<<8)
0
- max_len=0
0
- total_len=0
0
- 0.upto(nb_couple-1){|pos|
0
- v = input[12+pos*2]&255
0
- max_len = v if v>max_len
0
- total_len+=v
0
- }
0
- offset_pattern = 8 *(11+nb_couple*2)
0
- offset_encoded=offset_pattern + ((total_len+7)&~7)
0
- decode_array=Array.new max_len*32,0
0
- 0.upto(nb_couple-1){|pos|
0
- v_len = input[12+pos*2]&255
0
- value = get_bits(input,offset_pattern,v_len)
0
- offset_pattern+=v_len
0
- decode_array[(1<<v_len)+value] = input[11+pos*2]
0
- }
0
- output=""
0
- while output.length!=(nb_output) do
0
- cur_val = get_bit(input,offset_encoded)
0
- offset_encoded+=1
0
- nb_bit_val=1
0
- x=decode_array[(1<<nb_bit_val)+cur_val]
0
- while(x==0||x==nil) do
0
- cur_val=cur_val<<1|get_bit(input,offset_encoded)
0
- offset_encoded+=1
0
- nb_bit_val+=1
0
- x=decode_array[(1<<nb_bit_val)+cur_val]
0
- end
0
- output << (decode_array[(1<<nb_bit_val)+cur_val])
0
- end
0
- return output
0
-end
0
-
0
-

Comments

    No one has commented yet.