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.
- data/lib/gg/dccclient.rb +48 -0
- data/lib/gg/dccserver.rb +214 -0
- data/lib/gg.rb +462 -0
- metadata +47 -0
data/lib/gg/dccclient.rb
ADDED
@@ -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
|
data/lib/gg/dccserver.rb
ADDED
@@ -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
|
+
|