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