rgadu 0.1.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 (4) hide show
  1. data/lib/gg/dccclient.rb +48 -0
  2. data/lib/gg/dccserver.rb +214 -0
  3. data/lib/gg.rb +462 -0
  4. metadata +47 -0
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/ruby
2
+ require 'gg/dccserver'
3
+ require 'socket'
4
+
5
+ class DCCClient < DCCServer
6
+ def initialize(host, port, client_uin, server_uin, code=nil)
7
+ @client_uin, @server_uin, @code = client_uin, server_uin, code
8
+ @progress, @callback = {}, {}
9
+ @socket = TCPSocket.new(host, port)
10
+ if code
11
+ @socket.write(code)
12
+ @socket.read(8)
13
+ else
14
+ @socket.write([client_uin, server_uin].pack('LL'))
15
+ @socket.read(4)
16
+ end
17
+ end
18
+
19
+ def client_send(filename, &callback)
20
+ @callback[@server_uin] = callback if callback
21
+ @socket.write([0x02].pack('L'))
22
+ init_send(@server_uin, @socket, filename)
23
+ end
24
+
25
+ def client_recv(&action)
26
+ @prepare_recv = action
27
+ @socket.write([0x03].pack('L'))
28
+ init_recv(@socket, @server_uin)
29
+ end
30
+
31
+ def client_send7(filename, filesize, offset, &callback)
32
+ @callback[@code] = callback if callback
33
+ begin
34
+ send7(@code, @socket, filename, filesize, offset)
35
+ ensure
36
+ @progress.delete(@server_uin)
37
+ end
38
+ end
39
+
40
+ def client_recv7(filename, filesize, offset, &callback)
41
+ @callback[@code] = callback if callback
42
+ begin
43
+ recv7(@code, @socket, filename, filesize, offset)
44
+ ensure
45
+ @progress.delete(@server_uin)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,214 @@
1
+ #!/usr/bin/ruby
2
+ require 'socket'
3
+
4
+ class DCCServer
5
+ def initialize(port, uin)
6
+ @uin = uin
7
+ @queue, @progress, @callback, @thread = {}, {}, {}, []
8
+ @server = TCPServer.new(port)
9
+ @server_loop = Thread.new do
10
+ while session = @server.accept
11
+ @thread << Thread.new(session) do |sess|
12
+ begin
13
+ server(sess)
14
+ rescue
15
+ warn "Error: #{$!}"
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ def server(session)
23
+ packet_raw = session.read(8) or return
24
+ client_uin, server_uin = packet_raw.unpack('LL')
25
+ if client_uin==@uin or server_uin==@uin
26
+ session.write([0x47414455].pack('L'))
27
+ packet_raw=session.read(4) or return
28
+ type=packet_raw.unpack('L')[0]
29
+ if type==0x03
30
+ if filename=@queue[client_uin]
31
+ init_send(client_uin, session, filename)
32
+ end
33
+ elsif type==0x02
34
+ init_recv(session, client_uin)
35
+ end
36
+ elsif @queue[packet_raw]
37
+ code=packet_raw
38
+ direction, filename, filesize, offset = @queue[code]
39
+ if direction == :out
40
+ session.write(code)
41
+ begin
42
+ send7(code, session, filename, filesize, offset)
43
+ ensure
44
+ @progress[code]=@callback[code]=nil
45
+ end
46
+ elsif direction == :in
47
+ session.write(packet_raw)
48
+ begin
49
+ recv7(code, session, filename, filesize, offset)
50
+ ensure
51
+ @progress[code]=@callback[code]=nil
52
+ end
53
+ end
54
+ end
55
+ session.close
56
+ end
57
+
58
+ def init_send(client_uin, session, filename)
59
+ return unless File.exist?(filename)
60
+ fileattr=creationlow=creationhigh=accesslow=accesshigh=writelow=writehigh=sizehigh=0
61
+ filesize=File.size(filename)
62
+ base=File.basename(filename)
63
+ alternate=base[0..12].upcase
64
+ session.write([0x01, 0x03, 0, 0, fileattr, creationlow, creationhigh, accesslow, accesshigh, writelow, writehigh, sizehigh, filesize, 0, 0, base, alternate].pack('LLLLLLLLLLLLLLLa262a14'))
65
+ packet_raw = session.read(12) or return
66
+ type, offset = packet_raw.unpack('LLL')
67
+ if type==0x06 and offset<filesize
68
+ begin
69
+ send(client_uin, session, filename, filesize, offset)
70
+ ensure
71
+ @progress[client_uin]=@callback[client_uin]=nil
72
+ end
73
+ end
74
+ end
75
+
76
+ def send(client_uin, session, filename, filesize, offset)
77
+ @progress[client_uin] = bytes = offset
78
+ file=File.open(filename, 'r')
79
+ file.seek(offset) if offset>0
80
+ while bytes<filesize and data=file.read(4096)
81
+ length = data.length
82
+ if bytes+length < filesize
83
+ type=0x03
84
+ else
85
+ type=0x02
86
+ end
87
+ packet_raw=[type, length, 0, data].pack('LLLa*')
88
+ session.write(packet_raw) or return
89
+ bytes += length
90
+ @progress[client_uin] = bytes
91
+ @callback[client_uin].call(bytes) if @callback[client_uin]
92
+ end
93
+ end
94
+
95
+ def init_recv(session, client_uin)
96
+ packet_raw=session.read(4) or return
97
+ type=packet_raw.unpack('L')[0]
98
+ if type==0x01
99
+ packet_raw = session.read(332) or return
100
+ packet_body = packet_raw.unpack('LLLLLLLLLLLLLLa262a14')
101
+ filename = packet_body[14].sub(/\x00+/, '')
102
+ filesize = packet_body[11]
103
+ if @prepare_recv and local_filename=@prepare_recv.call(client_uin, filename, filesize)
104
+ if local_filename.class == Array
105
+ local_filename, @callback[client_uin] = local_filename
106
+ end
107
+ if File.exist?(local_filename)
108
+ if File.size(local_filename)<filesize
109
+ offset=File.size(local_filename)
110
+ else
111
+ return
112
+ end
113
+ else
114
+ offset=0
115
+ end
116
+ session.write([0x06, offset, 0].pack('LLL')) or return
117
+ begin
118
+ recv(client_uin, session, local_filename, filesize, offset)
119
+ ensure
120
+ @progress[client_uin]=@callback[client_uin]=nil
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+
127
+ def recv(client_uin, session, filename, filesize, offset)
128
+ if offset>0
129
+ mode='a'
130
+ else
131
+ mode='w'
132
+ end
133
+ file=File.open(filename, mode)
134
+ @progress[client_uin] = bytes = offset
135
+ while packet_raw=session.read(12)
136
+ type, length = packet_raw.unpack('LLL')
137
+ if bytes+length > filesize
138
+ break
139
+ end
140
+ if type==0x03 or type==0x02
141
+ packet=session.read(length) or return
142
+ file.write(packet)
143
+ bytes+=length
144
+ @progress[client_uin] = bytes
145
+ @callback[client_uin].call(bytes) if @callback[client_uin]
146
+ break if type==0x02
147
+ end
148
+ end
149
+ file.close
150
+ end
151
+
152
+ def send7(code, session, filename, filesize, offset)
153
+ return unless File.exist?(filename)
154
+ @progress[code]=bytes=offset
155
+ file=File.open(filename, 'r')
156
+ file.seek(offset) if offset>0
157
+ while bytes<filesize and data=file.read(4096)
158
+ session.write(data) or return
159
+ bytes += data.length
160
+ @progress[code] = bytes
161
+ @callback[code].call(bytes) if @callback[code]
162
+ end
163
+ end
164
+
165
+ def recv7(code, session, filename, filesize, offset)
166
+ if offset>0
167
+ mode='a'
168
+ else
169
+ mode='w'
170
+ end
171
+ file=File.open(filename, mode)
172
+ @progress[code]=bytes=offset
173
+ while bytes<filesize and data=session.read(4096)
174
+ file.write(data)
175
+ bytes += data.length
176
+ @progress[code] = bytes
177
+ @callback[code].call(bytes) if @callback[code]
178
+ end
179
+ file.close
180
+ end
181
+
182
+ def add_send(uin, filename, &callback)
183
+ @queue[uin] = filename
184
+ @callback[uin] = callback if callback
185
+ end
186
+
187
+ def add_send_code(code, filename, filesize, offset, &callback)
188
+ @queue[code] = [:out, filename, filesize, offset]
189
+ @callback[code] = callback if callback
190
+ end
191
+
192
+ def add_recv_code(code, filename, filesize, offset, &callback)
193
+ @queue[code] = [:in, filename, filesize, offset]
194
+ @callback[code] = callback if callback
195
+ end
196
+
197
+ def on_recv(&action)
198
+ @prepare_recv = action
199
+ end
200
+
201
+ def progress(code)
202
+ @progress[code]
203
+ end
204
+
205
+ def wait
206
+ @server_loop.join
207
+ end
208
+
209
+ def close
210
+ @server_loop.exit
211
+ @thread.each {|t| t.exit }
212
+ @server.close
213
+ end
214
+ end
data/lib/gg.rb ADDED
@@ -0,0 +1,462 @@
1
+ #!/usr/bin/ruby
2
+ require 'gg/dccserver'
3
+ require 'gg/dccclient'
4
+ require 'socket'
5
+ require 'digest/sha1'
6
+
7
+ =begin rdoc
8
+ Prosta implementacja protokolu Gadu-Gadu w czystym Ruby oparta na watkach i zdarzeniach. Pozwala bardzo szybko stworzyc dzialajacego klienta GG. Zawiera serwer DCC, obsluguje przesylanie plikow protokolem DCC w wersjach 6 i 7.
9
+ =end
10
+ class GG
11
+ STATUS = {0x02 => :avail, 0x04 => :avail,
12
+ 0x03 => :busy, 0x05 => :busy,
13
+ 0x14 => :invisible, 0x16 => :invisible,
14
+ 0x01 => :notavail, 0x15 => :notavail}
15
+ TO_STATUS = {:avail => 0x02,
16
+ :busy => 0x03,
17
+ :invisible => 0x14,
18
+ :notavail => 0x01}
19
+ TO_STATUS_DESCRIPTION = {:avail => 0x04,
20
+ :busy => 0x05,
21
+ :invisible => 0x16,
22
+ :notavail => 0x15}
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'}
26
+ TO_GENDER = {:male => '2', :female => '1'}
27
+ PATTERN = {0x02 => 'LLa*',
28
+ 0x0f => 'LCLSCCCa*',
29
+ 0x0c => 'LCLSLSa*',
30
+ 0x11 => 'LCLSCCCCa*',
31
+ 0x0e => 'CLa*',
32
+ 0x0a => 'LLLLa*',
33
+ 0x23 => 'La8',
34
+ 0x21 => 'La8LL',
35
+ 0x20 => 'a8LLLa226a29LLa20',
36
+ 0x1f => 'LLa8a21a43'}
37
+
38
+ =begin rdoc
39
+ Tworzy nowy obiekt GG i loguje sie na serwerze. Opcjonalnie uruchamia serwer DCC na porcie *dccport*.
40
+ =end
41
+ def initialize(uin, password, dccport=0, server='217.17.45.146', port=8074)
42
+ @uin=uin
43
+ @dccport=dccport
44
+ @socket=TCPSocket.new(server, port)
45
+ type, length = read_header
46
+ if type!=0x01
47
+ raise "Packet not recognized"
48
+ end
49
+ seed=read_body(length, 'L')[0]
50
+ hash=gg_login_hash(password, seed)
51
+ if dccport>0
52
+ @dcc = DCCServer.new(dccport, uin)
53
+ localip=@socket.addr[3].split('.').collect {|x| x.to_i }.pack('C4').unpack('L')[0]
54
+ else
55
+ localip=0
56
+ end
57
+ write(0x15, 'LLLLCLSLSCC', uin, hash, 0x02, 0x20, 0x00, localip, dccport, localip, dccport, 0x00, 0xbe)
58
+ type, length = read_header
59
+ read_body(length)
60
+ unless type==0x03 or type==0x14
61
+ raise "Authorization failed"
62
+ end
63
+ write(0x12, '')
64
+ @action, @status, @search_reply, @dcc_code, @dcc_action, @dcc_client_action = {}, {}, {}, [], {}, {}
65
+ @action_thread, @dcc_client, @dcc_client_thread = [], [], []
66
+ @action[0x0a] = lambda {|sender, seq, time, cl, message| msg_received(sender, seq, time, cl, message) }
67
+ @action[0x02] = lambda {|uin, status, description| status_changed(uin, status, description) }
68
+ @action[0x0f] = lambda {|uin, status, ip, port, version, x, y, description| status_changed(uin, status, description, ip, port, version) }
69
+ @action[0x0c] = lambda {|uin, status, ip, port, version, x, description| status_changed(uin, status, description, ip, port, version) }
70
+ @action[0x11] = lambda {|uin, status, ip, port, version, imgsize, x, descsize, description| status_changed(uin, status, description, ip, port, version) }
71
+ @action[0x0e] = lambda {|type, seq, response| @search_reply[seq] = response }
72
+ @action[0x23] = lambda {|type, code| @dcc_code << code }
73
+ @action[0x21] = lambda {|uin, code, offset, x| @dcc_action[code].call(offset) if @dcc_action[code] }
74
+ @action[0x1f] = lambda do |uin, x, code, ip_port, y|
75
+ if action = @dcc_client_action[code]
76
+ @dcc_client_action.delete(code)
77
+ action.call(*(ip_port.chomp(0.chr).split(' ')))
78
+ end
79
+ end
80
+ @read_loop = Thread.new do
81
+ loop do
82
+ sleep 0.1
83
+ read or break
84
+ end
85
+ end
86
+ @ping_loop = Thread.new do
87
+ loop do
88
+ sleep 180
89
+ ping
90
+ end
91
+ end
92
+ end
93
+
94
+ =begin rdoc
95
+ Wysyla wiadomosc *message* do *uin*.
96
+ =end
97
+ def msg(uin, message)
98
+ write(0x0b, 'LLLa*C', uin, rand(2**32), 0x04, message, 0)
99
+ end
100
+
101
+ =begin rdoc
102
+ Zmienia status i opcjonalnie ustawia opis. Status powinien byc jednym z symboli: :avail, :busy, :invisible, :notavail
103
+ =end
104
+ def status(status, description=nil)
105
+ if description
106
+ status_id = TO_STATUS_DESCRIPTION[status] or raise "Wrong status"
107
+ write(0x02, 'La*C', status_id, description, 0) if status_id
108
+ else
109
+ status_id = TO_STATUS[status] or raise "Wrong status"
110
+ write(0x02, 'L', status_id) if status_id
111
+ end
112
+ end
113
+
114
+ =begin rdoc
115
+ Dodaje numery GG do listy kontaktow. *uins* moze byc liczba lub tablica liczb.
116
+ =end
117
+ def add(*uins)
118
+ uins.each do |uin|
119
+ @status[uin] = {:status => :notavail}
120
+ write(0x0d, 'LC', uin, 0x03)
121
+ end
122
+ end
123
+
124
+ =begin rdoc
125
+ Usuwa numery GG z listy kontaktow. *uins* moze byc liczba lub tablica liczb.
126
+ =end
127
+ def remove(*uins)
128
+ uins.each do |uin|
129
+ @status[uin] = nil
130
+ write(0x0e, 'LC', uin, 0x03)
131
+ end
132
+ end
133
+
134
+ =begin rdoc
135
+ Wyszukiwanie w katalogu publicznym. *params* moze byc numerem GG lub tablica asocjacyjna z kluczami: :uin, :firstname, :lastname, :nickname, :birthyear, :city, :gender, :active. Kluczowi :gender moze odpowiadac wartosc :male lub :female. Zwraca tablice asocjacyjna z wynikami wyszukiwania, mozliwe sa klucze: :uin, :firstname, :nickname, :birthyear, :city, :status.
136
+ =end
137
+ def find(params)
138
+ if params.class == Hash
139
+ request=[]
140
+ request.push('FmNumber', params[:uin].to_s) if params[:uin]
141
+ request.push('firstname', params[:firstname]) if params[:firstname]
142
+ request.push('lastname', params[:lastname]) if params[:lastname]
143
+ request.push('nickname', params[:nickname]) if params[:nickname]
144
+ request.push('birthyear', params[:birthyear].to_s) if params[:birthyear]
145
+ request.push('city', params[:city]) if params[:city]
146
+ request.push('gender', TO_GENDER[params[:gender]]) if params[:gender]
147
+ request.push('ActiveOnly', '1') if params[:active]
148
+ else
149
+ request=['FmNumber', params.to_s]
150
+ end
151
+ write(0x14, 'CLa*C', 0x03, seq=rand(2**32), request.join("\x00"), 0)
152
+ sleep 0.1 until reply = @search_reply[seq]
153
+ @search_reply.delete(seq)
154
+ search_reply_array = reply.chomp(0.chr).split("\x00\x00")
155
+ search_reply_hash=[]
156
+ search_reply_array.each do |x|
157
+ row={}
158
+ y=x.split("\x00")
159
+ y.each_index do |i|
160
+ if i%2==0
161
+ key=y[i]
162
+ value=y[i+1]
163
+ case key
164
+ when 'FmNumber': row[:uin] = value.to_i
165
+ when 'firstname': row[:firstname] = value
166
+ when 'nickname': row[:nickname] = value
167
+ when 'birthyear': row[:birthyear] = value.to_i
168
+ when 'city': row[:city] = value
169
+ when 'FmStatus': row[:status] = STATUS[value.to_i]
170
+ when 'nextstart': break
171
+ end
172
+ end
173
+ end
174
+ search_reply_hash << row if row.length>0
175
+ end
176
+ search_reply_hash
177
+ end
178
+
179
+ =begin rdoc
180
+ Wysyla plik *filename* do *uin*. Jesli dolaczono blok kodu, blok ten bedzie wywolywany podczas wysylania pliku. Blok przyjmuje jeden parametr - aktualna pozycje pliku w bajtach. Funkcja w zaleznosci od wersji protokolu DCC zwraca kod transferu lub numer GG odbiorcy.
181
+ =end
182
+ def dcc_request(uin, filename, &callback)
183
+ raise "No DCC server" unless @dcc
184
+ if File.exist?(filename)
185
+ if @status[uin] and version=@status[uin][:version] and version.to_f >= 7.7
186
+ write(0x23, 'L', 0x04)
187
+ sleep 0.1 until code = @dcc_code.shift
188
+ filesize=File.size(filename)
189
+ hash=Digest::SHA1.digest(File.read(filename))
190
+ write(0x20, 'a8LLLa226a29LLa20', code, @uin, uin, 0x04, File.basename(filename), '', filesize, 0, hash)
191
+ @dcc_action[code] = lambda do |offset|
192
+ if callback
193
+ @dcc.add_send_code(code, filename, filesize, offset) {|pos| callback.call(pos) }
194
+ else
195
+ @dcc.add_send_code(code, filename, filesize, offset)
196
+ end
197
+ write(0x1f, 'LLa8a21a43', uin, 0x01, code, @socket.addr[3]+' '+@dccport.to_s, '')
198
+ if @socket.addr[3] =~ /^(10\.|172\.16\.|192\.168\.)/
199
+ @dcc_client_action[code] = lambda do |ip, port|
200
+ @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|
201
+ begin
202
+ client = DCCClient.new(ip, port, client_uin, server_uin, code)
203
+ @dcc_client << client
204
+ if callback
205
+ client.client_send7(filename, filesize, offset) {|pos| callback.call(pos) }
206
+ else
207
+ client.client_send7(filename, filesize, offset)
208
+ end
209
+ rescue
210
+ warn "Error: #{$!}"
211
+ end
212
+ end
213
+ end
214
+ end
215
+
216
+ end
217
+ code
218
+ else
219
+ if @socket.addr[3] =~ /^(10\.|172\.16\.|192\.168\.)/ and ip = get_ip(uin) and port = get_port(uin) and port > 1
220
+ @dcc_client_thread << Thread.new(ip, port, @uin, uin, filename, callback) do |ip, port, client_uin, server_uin, filename, callback|
221
+ begin
222
+ client = DCCClient.new(ip, port, client_uin, server_uin)
223
+ @dcc_client << client
224
+ if callback
225
+ client.client_send(filename) {|pos| callback.call(pos) }
226
+ else
227
+ client.client_send(filename)
228
+ end
229
+ rescue
230
+ warn "Error: #{$!}"
231
+ end
232
+ end
233
+ else
234
+ if callback
235
+ @dcc.add_send(uin, filename) {|pos| callback.call(pos) }
236
+ else
237
+ @dcc.add_send(uin, filename)
238
+ end
239
+ write(0x0b, 'LLLa*C', uin, rand(2**32), 0x10, "\x02", 0)
240
+ end
241
+ uin
242
+ end
243
+ end
244
+ end
245
+
246
+ =begin rdoc
247
+ Ustawia blok kodu jako zdarzenie wywolywane, gdy otrzymamy wiadomosc. Blok przyjmuje 3 parametry: numer GG nadawcy, czas wysylania wiadomosci i wiadomosc. Czas wyslania jest obiektem Time.
248
+ =end
249
+ def on_msg(&action)
250
+ @msg_action = action
251
+ end
252
+
253
+ =begin rdoc
254
+ Ustawia blok kodu jako zdarzenie wywolywane, gdy ktos z listy kontaktow zmieni status. Blok przyjmuje 6 parametrow: numer GG, status, opis, IP, port i wersje klienta. 2 pierwsze parametry wystepuja zawsze, pozostale nie sa obowiazkowe. Status moze byc jednym z symboli: :avail, :busy, :notavail. IP to lancuch znakow w postaci 4 liczb oddzielonych kropkami. Wersja klientu to liczba typu Float lub nil jesli wersji nie rozpoznano.
255
+ =end
256
+ def on_status(&action)
257
+ @status_action = action
258
+ end
259
+
260
+ =begin rdoc
261
+ Ustawia blok kodu jako zdarzenie wywolywane, gdy ktos zechce wyslac do nas plik. Blok przyjmuje 3 parametry: numer GG, nazwe pliku i rozmiar. Blok powinien zwrocic lokalna sciezke, gdzie plik ma zostac pobrany, nil, jesli nalezy odrzucic pobieranie pliku, lub tablice skladajaca sie z lokalnej sciezki i bloku kodu, ktory bedzie wywolywany podczas pobierania pliku. Blok ten przyjmuje jeden parametr - aktualna pozycje w bajtach.
262
+ =end
263
+ def on_dcc_recv(&action)
264
+ raise "No DCC server" unless @dcc
265
+ @dcc_recv_action = action
266
+ @dcc.on_recv {|uin, filename, filesize| action.call(uin, filename, filesize) }
267
+ @action[0x20] = lambda do |code, sender_uin, recv_uin, type, filename, x, filesize, y, hash|
268
+ if type==0x04
269
+ filename.sub!(/\x00.*/m, '')
270
+ if local_filename=action.call(sender_uin, filename, filesize)
271
+ if local_filename.class == Array
272
+ local_filename, callback = local_filename
273
+ end
274
+ if File.exist?(local_filename)
275
+ offset = File.size(local_filename)
276
+ else
277
+ offset = 0
278
+ end
279
+ if callback
280
+ @dcc.add_recv_code(code, local_filename, filesize, offset) {|pos| callback.call(pos) }
281
+ else
282
+ @dcc.add_recv_code(code, local_filename, filesize, offset)
283
+ end
284
+ write(0x21, 'La8LL', sender_uin, code, offset, 0)
285
+ write(0x1f, 'LLa8a21a43', sender_uin, 0x01, code, @socket.addr[3]+' '+@dccport.to_s, '')
286
+ @dcc_client_action[code] = lambda do |ip, port|
287
+ @dcc_client_thread << Thread.new(ip, port, @uin, sender_uin, code, local_filename, filesize, offset, callback) do |ip, port, client_uin, server_uin, code, filename, filesize, offset, callback|
288
+ begin
289
+ client = DCCClient.new(ip, port, client_uin, server_uin, code)
290
+ @dcc_client << client
291
+ if callback
292
+ client.client_recv7(filename, filesize, offset) {|pos| callback.call(pos) }
293
+ else
294
+ client.client_recv7(filename, filesize, offset)
295
+ end
296
+ rescue
297
+ warn "Error: #{$!}"
298
+ end
299
+ end
300
+ end
301
+ else
302
+ write(0x22, 'La8L', sender_uin, code, 0x02)
303
+ end
304
+ end
305
+ end
306
+ end
307
+
308
+ =begin rdoc
309
+ Zwraca status *uin* jesli jest on na liscie kontaktow. Status moze byc jednym z symboli: :avail, :busy, :notavail.
310
+ =end
311
+ def get_status(uin)
312
+ @status[uin][:status]
313
+ end
314
+
315
+ =begin rdoc
316
+ Zwraca opis *uin* jesli jest on na liscie kontaktow.
317
+ =end
318
+ def get_description(uin)
319
+ @status[uin][:description]
320
+ end
321
+
322
+ =begin rdoc
323
+ Zwraca adres IP *uin* jako lancuch znakow w postaci 4 liczb oddzielonych kropkami.
324
+ =end
325
+ def get_ip(uin)
326
+ @status[uin][:ip]
327
+ end
328
+
329
+ =begin rdoc
330
+ Zwraca port, na ktorym *uin* ma serwer DCC.
331
+ =end
332
+ def get_port(uin)
333
+ @status[uin][:port]
334
+ end
335
+
336
+ =begin rdoc
337
+ Zwraca wersje klienta *uin* jako liczbe Float lub nil jesli nie rozpoznano wersji.
338
+ =end
339
+ def get_version(uin)
340
+ @status[uin][:version]
341
+ end
342
+
343
+ =begin rdoc
344
+ Sprawdza aktualna pozycje transferu DCC. *code* to numer GG lub kod transferu.
345
+ =end
346
+ def dcc_progress(code)
347
+ @dcc && @dcc.progress(code) or @dcc_client.find {|c| c.progress(code) != nil }
348
+ end
349
+
350
+ =begin rdoc
351
+ Pozwala dzialac klientowi GG, blokujac watek do momentu, gdy skonczy dzialanie. Funkcja powinna zostac wywolana po ustawieniu zdarzen.
352
+ =end
353
+ def wait
354
+ @read_loop.join
355
+ end
356
+
357
+ =begin rdoc
358
+ Rozlacza sie z serwerem GG, wylacza serwer DCC i zamyka wszystkie polaczenia DCC.
359
+ =end
360
+ def close
361
+ @action_thread.each {|t| t.exit }
362
+ @ping_loop.exit
363
+ @read_loop.exit
364
+ @socket.close
365
+ @dcc.close
366
+ @dcc_client_thread.each {|t| t.exit }
367
+ end
368
+
369
+ def write(type, pattern, *params)
370
+ rawbody=params.pack(pattern)
371
+ rawpacket = [type, rawbody.length].pack('LL') + rawbody
372
+ @socket.write(rawpacket)
373
+ end
374
+
375
+ def read
376
+ type, length = read_header
377
+ return nil unless type
378
+ if PATTERN[type]
379
+ body = read_body(length, PATTERN[type])
380
+ else
381
+ body = read_body(length)
382
+ end
383
+ if @action[type]
384
+ @action_thread << Thread.new(body) {|bd| @action[type].call(*bd) }
385
+ end
386
+ 1
387
+ end
388
+
389
+ def read_header
390
+ type=@socket.read(4).unpack('L')[0]
391
+ length=@socket.read(4).unpack('L')[0]
392
+ return type, length
393
+ end
394
+
395
+ def read_body(length, pattern=nil)
396
+ if length>0
397
+ packet=@socket.read(length)
398
+ if pattern
399
+ packet.unpack(pattern)
400
+ else
401
+ packet
402
+ end
403
+ end
404
+ end
405
+
406
+ def ping
407
+ write(0x08, '')
408
+ end
409
+
410
+ def gg_login_hash(password, seed)
411
+ x, y, z = 0, seed, 0
412
+ password.each_byte do |b|
413
+ x = (x & 0xffffff00) | b
414
+ y = y ^ x
415
+ y = (y + x) % 2**32
416
+ x = (x << 8) % 2**32
417
+ y = y ^ x
418
+ x = (x << 8) % 2**32
419
+ y = (y - x) % 2**32
420
+ x = (x << 8) % 2**32
421
+ y = y ^ x
422
+ z = y & 0x1f
423
+ y = ((y << z) % 2**32) | ((y >> (32 - z)) % 2**32)
424
+ end
425
+ y
426
+ end
427
+
428
+ def msg_received(sender, seq, time, cl, message)
429
+ if cl == 0x10 and message.chomp(0.chr) == 2.chr
430
+ if @dcc_recv_action and ip = get_ip(sender) and port = get_port(sender) and port > 1
431
+ @dcc_client_thread << Thread.new(ip, port, @uin, sender, @dcc_recv_action) do |ip, port, uin1, uin2, dcc_recv_action|
432
+ begin
433
+ client = DCCClient.new(ip, port, uin1, uin2)
434
+ @dcc_client << client
435
+ client.client_recv {|uin, filename, filesize| dcc_recv_action.call(uin, filename, filesize) }
436
+ rescue
437
+ warn "Error: #{$!}"
438
+ end
439
+ end
440
+ end
441
+ else
442
+ @msg_action.call(sender, Time.at(time), message.chomp(0.chr)) if @msg_action
443
+ end
444
+ end
445
+
446
+ def status_changed(uin, status, description, ip=nil, port=nil, version=nil)
447
+ uin = uin % 2**24
448
+ if description
449
+ description.chomp!(0.chr)
450
+ description=nil if description.empty?
451
+ end
452
+ status=STATUS[status]
453
+ version=VERSION[version] || version if version
454
+ ip = (ip>0 ? [ip].pack('L').unpack('C4').join('.') : nil) if ip
455
+ @status[uin] = {:status => status}
456
+ @status[uin][:description] = description if description
457
+ @status[uin][:ip] = ip if ip
458
+ @status[uin][:port] = port if port
459
+ @status[uin][:version] = version if version
460
+ @status_action.call(uin, status, description, ip, port, version) if @status_action
461
+ end
462
+ end
metadata ADDED
@@ -0,0 +1,47 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: rgadu
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2007-08-04 00:00:00 +02:00
8
+ summary: A simple pure-Ruby implementation of the Gadu-Gadu protocol.
9
+ require_paths:
10
+ - lib
11
+ email: bl4@playker.info
12
+ homepage:
13
+ rubyforge_project:
14
+ description:
15
+ autorequire: gg
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ authors:
29
+ - bl4
30
+ files:
31
+ - lib/gg.rb
32
+ - lib/gg/dccserver.rb
33
+ - lib/gg/dccclient.rb
34
+ test_files: []
35
+
36
+ rdoc_options: []
37
+
38
+ extra_rdoc_files: []
39
+
40
+ executables: []
41
+
42
+ extensions: []
43
+
44
+ requirements: []
45
+
46
+ dependencies: []
47
+