rgadu 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+