rgadu 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. data/lib/gg/dccserver.rb +3 -1
  2. data/lib/gg.rb +124 -93
  3. metadata +2 -2
data/lib/gg/dccserver.rb CHANGED
@@ -28,6 +28,7 @@ class DCCServer
28
28
  type=packet_raw.unpack('L')[0]
29
29
  if type==0x03
30
30
  if filename=@queue[client_uin]
31
+ @queue.delete(client_uin)
31
32
  init_send(client_uin, session, filename)
32
33
  end
33
34
  elsif type==0x02
@@ -36,6 +37,7 @@ class DCCServer
36
37
  elsif @queue[packet_raw]
37
38
  code=packet_raw
38
39
  direction, filename, filesize, offset = @queue[code]
40
+ @queue.delete(code)
39
41
  if direction == :out
40
42
  session.write(code)
41
43
  begin
@@ -110,7 +112,7 @@ class DCCServer
110
112
  if type==0x01
111
113
  packet_raw = session.read(332) or return
112
114
  packet_body = packet_raw.unpack('LLLLLLLLLLLLLLa262a14')
113
- filename = packet_body[14].sub(/\x00+/, '')
115
+ filename = packet_body[14].sub(/\x00.*/m, '').sub(%r{.*/}, '')
114
116
  filesize = packet_body[11]
115
117
  if @prepare_recv and local_filename=@prepare_recv.call(client_uin, filename, filesize)
116
118
  if local_filename.class == Array
data/lib/gg.rb CHANGED
@@ -21,13 +21,15 @@ class GG
21
21
  :invisible => 0x16,
22
22
  :notavail => 0x15}
23
23
  VERSION = {0x18 => '5.0', 0x19 => '5.0', 0x1b => '5.0', 0x1c => '5.7', 0x1e => '5.7',
24
- 0x20 => '6.0', 0x21 => '6.0', 0x22 => '6.0', 0x24 => '6.1',
25
- 0x25 => '7.0', 0x26 => '7.0', 0x27 => '7.0', 0x28 => '7.5', 0x29 => '7.6', 0x2a => '7.7'}
24
+ 0x20 => '6.0', 0x21 => '6.0', 0x22 => '6.0',
25
+ 0x24 => '7.0', 0x25 => '7.0', 0x26 => '7.0', 0x27 => '7.0', 0x28 => '7.5', 0x29 => '7.6', 0x2a => '7.7'}
26
26
  TO_GENDER = {:male => '2', :female => '1'}
27
27
  PATTERN = {0x02 => 'LLa*',
28
28
  0x0f => 'LCLSCCCa*',
29
+ 0x17 => 'LCLSCCCa*',
29
30
  0x0c => 'LCLSLSa*',
30
31
  0x11 => 'LCLSCCCCa*',
32
+ 0x18 => 'LCLSCCCCa*',
31
33
  0x0e => 'CLa*',
32
34
  0x0a => 'LLLLa*',
33
35
  0x23 => 'La8',
@@ -36,42 +38,64 @@ class GG
36
38
  0x1f => 'LLa8a21a43'}
37
39
 
38
40
  =begin rdoc
39
- Tworzy nowy obiekt GG i loguje sie na serwerze. Opcjonalnie uruchamia serwer DCC na porcie *dccport* i przesy�a serwerowi tablic� z list� kontakt�w.
41
+ Tworzy nowy obiekt GG i loguje sie na serwerze uzywajac numeru *uin* i hasla *password*. Dodatkowe parametry to: :server => serwer GG, :port => port na serwerze GG, :dccport => port dla polaczen bezposrednich, :contacts => tablica z lista kontaktow, :version => wersja protokolu.
40
42
  =end
41
- def initialize(uin, password, dccport=0, contacts=nil, server='217.17.45.146', port=8074)
43
+ def initialize(uin, password, params={})
44
+ server = params[:server] || '217.17.45.146'
45
+ port = params[:port] || 8074
46
+ dccport = params[:dccport]
47
+ contacts = params[:contacts] || []
48
+ version = (params[:version] || 6.0).to_f
42
49
  @uin=uin
43
50
  @dccport=dccport
44
51
  @socket=TCPSocket.new(server, port)
45
52
  type, length = read_header
46
53
  if type!=0x01
47
54
  raise "Packet not recognized"
48
- end
55
+ end
49
56
  seed=read_body(length, 'L')[0]
50
- hash=gg_login_hash(password, seed)
51
- if dccport>0
57
+ if dccport
52
58
  @dcc = DCCServer.new(dccport, uin)
53
59
  localip=@socket.addr[3].split('.').collect {|x| x.to_i }.pack('C4').unpack('L')[0]
54
60
  else
55
61
  localip=0
56
62
  end
57
- write(0x15, 'LLLLCLSLSCC', uin, hash, 0x02, 0x20, 0x00, localip, dccport, localip, dccport, 0x00, 0xbe)
63
+ if version >= 7.7
64
+ protocol = 0x2a
65
+ elsif version >= 7.6
66
+ protocol = 0x29
67
+ elsif version >= 7
68
+ protocol = 0x24
69
+ else
70
+ protocol = 0x20
71
+ end
72
+ if version >= 7
73
+ hash = Digest::SHA1.digest(password + [seed].pack('L'))
74
+ write(0x19, 'LCa64LLCLSLSCC', uin, 0x02, hash, 0x02, protocol, 0x00, localip, dccport, localip, dccport, 0x00, 0xbe)
75
+ else
76
+ hash = gg_login_hash(password, seed)
77
+ write(0x15, 'LLLLCLSLSCC', uin, hash, 0x02, 0x20, 0x00, localip, dccport, localip, dccport, 0x00, 0xbe)
78
+ end
58
79
  type, length = read_header
59
80
  read_body(length)
60
81
  unless type==0x03 or type==0x14
61
82
  raise "Authorization failed"
62
83
  end
63
- if contacts and contacts.length > 0
64
- write(0x10, 'LC'*contacts.length, *(contacts.map {|contact| [contact, 0x03] }.flatten))
84
+ if contacts.length > 0
85
+ contactpacket = []
86
+ contacts.each {|contact| contactpacket.push(contact, 0x03) }
87
+ write(0x10, 'LC'*contacts.length, *contactpacket)
65
88
  else
66
89
  write(0x12, '')
67
90
  end
68
91
  @action, @status, @search_reply, @dcc_code, @dcc_action, @dcc_client_action = {}, {}, {}, [], {}, {}
92
+ contacts.each {|contact| @status[contact] = {:status => :notavail} }
69
93
  @action_thread, @dcc_client, @dcc_client_thread = [], [], []
70
94
  @action[0x0a] = lambda {|sender, seq, time, cl, message| msg_received(sender, seq, time, cl, message) }
71
95
  @action[0x02] = lambda {|uin, status, description| status_changed(uin, status, description) }
72
- @action[0x0f] = lambda {|uin, status, ip, port, version, x, y, description| status_changed(uin, status, description, ip, port, version) }
96
+ @action[0x0f] = @action[0x17] = lambda {|uin, status, ip, port, version, x, y, description| status_changed(uin, status, description, ip, port, version) }
73
97
  @action[0x0c] = lambda {|uin, status, ip, port, version, x, description| status_changed(uin, status, description, ip, port, version) }
74
- @action[0x11] = lambda {|uin, status, ip, port, version, imgsize, x, descsize, description| status_changed(uin, status, description, ip, port, version) }
98
+ @action[0x11] = @action[0x18] = lambda {|uin, status, ip, port, version, imgsize, x, descsize, description| status_changed(uin, status, description, ip, port, version) }
75
99
  @action[0x0e] = lambda {|type, seq, response| @search_reply[seq] = response }
76
100
  @action[0x23] = lambda {|type, code| @dcc_code << code }
77
101
  @action[0x21] = lambda {|uin, code, offset, x| @dcc_action[code].call(offset) if @dcc_action[code] }
@@ -182,80 +206,59 @@ Wyszukiwanie w katalogu publicznym. *params* moze byc numerem GG lub tablica aso
182
206
  end
183
207
 
184
208
  =begin rdoc
185
- Wysyla plik *filename* do *uin*. Mo�na poda� hash SHA1 pliku w postaci ci�gu bajt�w, w przeciwnym razie hash jest obliczany w razie potrzeby. Jesli dolaczono blok kodu, blok ten bedzie wywolywany podczas wysylania pliku. Blok przyjmuje jeden parametr - aktualn� pozycj� pliku w bajtach, symbol :rejected je�li po��czenie zosta�o odrzucone lub :aborted w przypadku przerwania transferu. Funkcja w zaleznosci od wersji protokolu DCC zwraca kod transferu lub numer GG odbiorcy.
209
+ Wysyla plik *filename* do *uin*. Jesli dolaczono blok kodu, blok ten bedzie wywolywany podczas wysylania pliku. Blok przyjmuje jeden parametr - aktualn� pozycj� pliku w bajtach, symbol :rejected je�li po��czenie zosta�o odrzucone lub :aborted w przypadku przerwania transferu. Funkcja w zaleznosci od wersji protokolu DCC zwraca kod transferu lub numer GG odbiorcy.
186
210
  =end
187
211
  def dcc_request(uin, filename, hash=nil, &callback)
188
- raise "No DCC server" unless @dcc
189
- if File.exist?(filename)
190
- if @status[uin] and version=@status[uin][:version] and version.to_f >= 7.6
191
- write(0x23, 'L', 0x04)
192
- sleep 0.1 until code = @dcc_code.shift
193
- filesize=File.size(filename)
194
- unless hash
195
- checksum = Digest::SHA1.new
196
- file = File.open(filename, 'rb')
197
- while chunk = file.read(1048576)
198
- checksum << chunk
199
- end
200
- file.close
201
- hash = checksum.digest
202
- end
203
- write(0x20, 'a8LLLa226a29LLa20', code, @uin, uin, 0x04, File.basename(filename), '', filesize, 0, hash)
204
- @dcc_action[code] = lambda do |offset|
205
- if offset == -1
206
- callback.call(:rejected)
207
- else
208
- if callback
209
- @dcc.add_send_code(code, filename, filesize, offset) {|pos| callback.call(pos) }
210
- else
211
- @dcc.add_send_code(code, filename, filesize, offset)
212
- end
213
- write(0x1f, 'LLa8a21a43', uin, 0x01, code, @socket.addr[3]+' '+@dccport.to_s, '')
214
- if @socket.addr[3] =~ /^(10\.|172\.16\.|192\.168\.)/
215
- @dcc_client_action[code] = lambda do |ip, port|
216
- @dcc_client_thread << Thread.new(ip, port, @uin, uin, code, filename, filesize, offset, callback) do |ip, port, client_uin, server_uin, code, filename, filesize, offset, callback|
217
- begin
218
- client = DCCClient.new(ip, port, client_uin, server_uin, code)
219
- @dcc_client << client
220
- if callback
221
- client.client_send7(filename, filesize, offset) {|pos| callback.call(pos) }
222
- else
223
- client.client_send7(filename, filesize, offset)
224
- end
225
- rescue
226
- warn "Error: #{$!}"
227
- end
212
+ raise "dcc_request: no DCC server" unless @dcc
213
+ raise "dcc_request: file #{filename} not found" unless File.exist?(filename)
214
+ raise "dcc_request: #{uin} is not on the contact list" unless contact?(uin)
215
+ warn "dcc_request: optional parameter 'hash' is ignored and will be removed" if hash
216
+ return nil if get_status(uin) == :notavail
217
+ if version = get_version(uin) and version.to_f >= 7.6
218
+ write(0x23, 'L', 0x04)
219
+ sleep 0.1 until code = @dcc_code.shift
220
+ filesize=File.size(filename)
221
+ hash = filehash(filename, version)
222
+ write(0x20, 'a8LLLa226a29LLa20', code, @uin, uin, 0x04, File.basename(filename), '', filesize, 0, hash)
223
+ @dcc_action[code] = lambda do |offset|
224
+ if offset == -1
225
+ callback.call(:rejected) if callback
226
+ else
227
+ @dcc.add_send_code(code, filename, filesize, offset, &callback)
228
+ write(0x1f, 'LLa8a21a43', uin, 0x01, code, @socket.addr[3]+' '+@dccport.to_s, '')
229
+ if @socket.addr[3] =~ /^(10\.|172\.16\.|192\.168\.)/
230
+ @dcc_client_action[code] = lambda do |ip, port|
231
+ @dcc_client_thread << Thread.new(ip, port, @uin, uin, code, filename, filesize, offset, callback) do |ip, port, client_uin, server_uin, code, filename, filesize, offset, callback|
232
+ begin
233
+ client = DCCClient.new(ip, port, client_uin, server_uin, code)
234
+ @dcc_client << client
235
+ client.client_send7(filename, filesize, offset, &callback)
236
+ rescue
237
+ warn "Error: #{$!}"
228
238
  end
229
239
  end
230
240
  end
231
241
  end
232
242
  end
233
- code
234
- else
235
- if @socket.addr[3] =~ /^(10\.|172\.16\.|192\.168\.)/ and ip = get_ip(uin) and port = get_port(uin) and port > 1
236
- @dcc_client_thread << Thread.new(ip, port, @uin, uin, filename, callback) do |ip, port, client_uin, server_uin, filename, callback|
237
- begin
238
- client = DCCClient.new(ip, port, client_uin, server_uin)
239
- @dcc_client << client
240
- if callback
241
- client.client_send(filename) {|pos| callback.call(pos) }
242
- else
243
- client.client_send(filename)
244
- end
245
- rescue
246
- warn "Error: #{$!}"
247
- end
248
- end
249
- else
250
- if callback
251
- @dcc.add_send(uin, filename) {|pos| callback.call(pos) }
252
- else
253
- @dcc.add_send(uin, filename)
243
+ end
244
+ code
245
+ else
246
+ return nil if get_port(uin) == 2 or get_port(uin) == 0
247
+ if @socket.addr[3] =~ /^(10\.|172\.16\.|192\.168\.)/ and ip = get_ip(uin) and port = get_port(uin) and port > 2
248
+ @dcc_client_thread << Thread.new(ip, port, @uin, uin, filename, callback) do |ip, port, client_uin, server_uin, filename, callback|
249
+ begin
250
+ client = DCCClient.new(ip, port, client_uin, server_uin)
251
+ @dcc_client << client
252
+ client.client_send(filename, &callback)
253
+ rescue
254
+ warn "Error: #{$!}"
254
255
  end
255
- write(0x0b, 'LLLa*C', uin, rand(2**32), 0x10, "\x02", 0)
256
256
  end
257
- uin
257
+ else
258
+ @dcc.add_send(uin, filename, &callback)
259
+ write(0x0b, 'LLLa*C', uin, rand(2**32), 0x10, "\x02", 0)
258
260
  end
261
+ uin
259
262
  end
260
263
  end
261
264
 
@@ -279,12 +282,13 @@ Ustawia blok kodu jako zdarzenie wywolywane, gdy ktos zechce wyslac do nas plik.
279
282
  def on_dcc_recv(&action)
280
283
  raise "No DCC server" unless @dcc
281
284
  @dcc_recv_action = action
282
- @dcc.on_recv {|uin, filename, filesize| action.call(uin, filename, filesize) }
285
+ @dcc.on_recv(&action)
283
286
  @action[0x20] = lambda do |code, sender_uin, recv_uin, type, filename, x, filesize, y, hash|
284
287
  if type==0x04
285
288
  filename.sub!(/\x00.*/m, '')
289
+ filename.sub!(%r{.*/}, '')
286
290
  if local_filename=action.call(sender_uin, filename, filesize)
287
- if local_filename.class == Array
291
+ if local_filename.respond_to?(:to_ary)
288
292
  local_filename, callback = local_filename
289
293
  end
290
294
  if File.exist?(local_filename)
@@ -292,11 +296,7 @@ Ustawia blok kodu jako zdarzenie wywolywane, gdy ktos zechce wyslac do nas plik.
292
296
  else
293
297
  offset = 0
294
298
  end
295
- if callback
296
- @dcc.add_recv_code(code, local_filename, filesize, offset) {|pos| callback.call(pos) }
297
- else
298
- @dcc.add_recv_code(code, local_filename, filesize, offset)
299
- end
299
+ @dcc.add_recv_code(code, local_filename, filesize, offset, &callback)
300
300
  write(0x21, 'La8LL', sender_uin, code, offset, 0)
301
301
  write(0x1f, 'LLa8a21a43', sender_uin, 0x01, code, @socket.addr[3]+' '+@dccport.to_s, '')
302
302
  @dcc_client_action[code] = lambda do |ip, port|
@@ -304,11 +304,7 @@ Ustawia blok kodu jako zdarzenie wywolywane, gdy ktos zechce wyslac do nas plik.
304
304
  begin
305
305
  client = DCCClient.new(ip, port, client_uin, server_uin, code)
306
306
  @dcc_client << client
307
- if callback
308
- client.client_recv7(filename, filesize, offset) {|pos| callback.call(pos) }
309
- else
310
- client.client_recv7(filename, filesize, offset)
311
- end
307
+ client.client_recv7(filename, filesize, offset, &callback)
312
308
  rescue
313
309
  warn "Error: #{$!}"
314
310
  end
@@ -321,39 +317,46 @@ Ustawia blok kodu jako zdarzenie wywolywane, gdy ktos zechce wyslac do nas plik.
321
317
  end
322
318
  end
323
319
 
320
+ =begin rdoc
321
+ Sprawdza czy *uin* jest na liscie kontaktow. Zwraca true lub false.
322
+ =end
323
+ def contact?(uin)
324
+ @status[uin] ? true : false
325
+ end
326
+
324
327
  =begin rdoc
325
328
  Zwraca status *uin* jesli jest on na liscie kontaktow. Status moze byc jednym z symboli: :avail, :busy, :notavail.
326
329
  =end
327
330
  def get_status(uin)
328
- @status[uin][:status]
331
+ @status[uin][:status] if @status[uin]
329
332
  end
330
333
 
331
334
  =begin rdoc
332
335
  Zwraca opis *uin* jesli jest on na liscie kontaktow.
333
336
  =end
334
337
  def get_description(uin)
335
- @status[uin][:description]
338
+ @status[uin][:description] if @status[uin]
336
339
  end
337
340
 
338
341
  =begin rdoc
339
342
  Zwraca adres IP *uin* jako lancuch znakow w postaci 4 liczb oddzielonych kropkami.
340
343
  =end
341
344
  def get_ip(uin)
342
- @status[uin][:ip]
345
+ @status[uin][:ip] if @status[uin]
343
346
  end
344
347
 
345
348
  =begin rdoc
346
349
  Zwraca port, na ktorym *uin* ma serwer DCC.
347
350
  =end
348
351
  def get_port(uin)
349
- @status[uin][:port]
352
+ @status[uin][:port] if @status[uin]
350
353
  end
351
354
 
352
355
  =begin rdoc
353
356
  Zwraca wersje klienta *uin* jako liczbe Float lub nil jesli nie rozpoznano wersji.
354
357
  =end
355
358
  def get_version(uin)
356
- @status[uin][:version]
359
+ @status[uin][:version] if @status[uin]
357
360
  end
358
361
 
359
362
  =begin rdoc
@@ -380,6 +383,7 @@ Rozlacza sie z serwerem GG, wylacza serwer DCC i zamyka wszystkie polaczenia DCC
380
383
  @socket.close
381
384
  @dcc.close if @dcc
382
385
  @dcc_client_thread.each {|t| t.exit }
386
+ nil
383
387
  end
384
388
 
385
389
  def write(type, pattern, *params)
@@ -419,6 +423,10 @@ Rozlacza sie z serwerem GG, wylacza serwer DCC i zamyka wszystkie polaczenia DCC
419
423
  end
420
424
  end
421
425
 
426
+ def set_action(type, &action)
427
+ @action[type] = action
428
+ end
429
+
422
430
  def ping
423
431
  write(0x08, '')
424
432
  end
@@ -441,6 +449,29 @@ Rozlacza sie z serwerem GG, wylacza serwer DCC i zamyka wszystkie polaczenia DCC
441
449
  y
442
450
  end
443
451
 
452
+ def filehash(filename, version=7.7)
453
+ digest = Digest::SHA1.new
454
+ File.open(filename) do |f|
455
+ if File.size(filename) <= 10*1024*1024
456
+ while chunk = f.read(1024*1024)
457
+ digest << chunk
458
+ end
459
+ else
460
+ if version.to_s == '7.6'
461
+ chunksize = 0xfe000
462
+ else
463
+ chunksize = 0x100000
464
+ end
465
+ seekjump = (File.size(filename)-0x100000)/9
466
+ 9.times do |i|
467
+ f.seek i*seekjump
468
+ digest << f.read(chunksize)
469
+ end
470
+ end
471
+ end
472
+ digest.digest
473
+ end
474
+
444
475
  def msg_received(sender, seq, time, cl, message)
445
476
  if cl == 0x10 and message.chomp(0.chr) == 2.chr
446
477
  if @dcc_recv_action and ip = get_ip(sender) and port = get_port(sender) and port > 1
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: rgadu
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.1.1
7
- date: 2007-08-22 00:00:00 +02:00
6
+ version: 0.2.0
7
+ date: 2007-09-23 00:00:00 +02:00
8
8
  summary: A simple pure-Ruby implementation of the Gadu-Gadu protocol.
9
9
  require_paths:
10
10
  - lib