ruggby 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +2 -0
- data/Gemfile +2 -0
- data/MIT-LICENSE +20 -0
- data/Manifest +38 -0
- data/README.md +187 -0
- data/Rakefile +12 -0
- data/init.rb +1 -0
- data/lib/ruggby.rb +72 -0
- data/lib/ruggby/action/base.rb +16 -0
- data/lib/ruggby/action/change_status.rb +30 -0
- data/lib/ruggby/action/create_message.rb +27 -0
- data/lib/ruggby/action/login.rb +40 -0
- data/lib/ruggby/action/mark.rb +27 -0
- data/lib/ruggby/action/new_message.rb +28 -0
- data/lib/ruggby/action/ping.rb +40 -0
- data/lib/ruggby/action/read.rb +49 -0
- data/lib/ruggby/callback.rb +22 -0
- data/lib/ruggby/client.rb +95 -0
- data/lib/ruggby/converter.rb +57 -0
- data/lib/ruggby/logger.rb +67 -0
- data/lib/ruggby/packet/factory.rb +39 -0
- data/lib/ruggby/packet/incoming/base.rb +31 -0
- data/lib/ruggby/packet/incoming/login_status.rb +28 -0
- data/lib/ruggby/packet/incoming/message.rb +35 -0
- data/lib/ruggby/packet/incoming/welcome.rb +33 -0
- data/lib/ruggby/packet/outgoing/base.rb +67 -0
- data/lib/ruggby/packet/outgoing/change_status.rb +57 -0
- data/lib/ruggby/packet/outgoing/login.rb +75 -0
- data/lib/ruggby/packet/outgoing/mark.rb +28 -0
- data/lib/ruggby/packet/outgoing/message.rb +47 -0
- data/lib/ruggby/packet/outgoing/ping.rb +27 -0
- data/lib/ruggby/password.rb +19 -0
- data/lib/ruggby/socket.rb +67 -0
- data/lib/ruggby/string_encoder.rb +42 -0
- data/lib/ruggby/threader.rb +21 -0
- data/lib/ruggby/version.rb +5 -0
- data/ruggby.gemspec +35 -0
- data/spec/callback_spec.rb +40 -0
- data/spec/spec_helper.rb +7 -0
- metadata +142 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
module RuGGby
|
2
|
+
|
3
|
+
module Packet
|
4
|
+
|
5
|
+
module Incoming
|
6
|
+
|
7
|
+
# Message telling as whether or not the loginj process was successfull
|
8
|
+
# It is a bit different from others because it gets one parameter on which
|
9
|
+
# we assume that the login was successful or unsuccessful
|
10
|
+
class LoginStatus
|
11
|
+
|
12
|
+
TYPE = 0x00035
|
13
|
+
|
14
|
+
def initialize(valid)
|
15
|
+
@valid = valid
|
16
|
+
end
|
17
|
+
|
18
|
+
def successful?
|
19
|
+
@valid
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module RuGGby
|
2
|
+
|
3
|
+
module Packet
|
4
|
+
|
5
|
+
module Incoming
|
6
|
+
|
7
|
+
# Packet containing and incoming message
|
8
|
+
# Message packet contains not formatted data (not parsed, etc) so
|
9
|
+
class Message < RuGGby::Packet::Incoming::Base
|
10
|
+
|
11
|
+
TYPE = 0x002e
|
12
|
+
PATTERN = 'LLLLa*'
|
13
|
+
|
14
|
+
attr_reader :uin, :message, :data, :created_at
|
15
|
+
|
16
|
+
def initialize(data)
|
17
|
+
super
|
18
|
+
@uin = @data[0]
|
19
|
+
@created_at = @data[2]
|
20
|
+
@message = @data[4]
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def pattern
|
26
|
+
PATTERN
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module RuGGby
|
2
|
+
|
3
|
+
module Packet
|
4
|
+
|
5
|
+
module Incoming
|
6
|
+
|
7
|
+
# Welcome packet - sent by GG when we connect to their socket
|
8
|
+
# Contains a seed used to mix with password before authorization
|
9
|
+
class Welcome < RuGGby::Packet::Incoming::Base
|
10
|
+
|
11
|
+
TYPE = 0x0001
|
12
|
+
PATTERN = 'I'
|
13
|
+
|
14
|
+
attr_reader :seed
|
15
|
+
|
16
|
+
def initialize(data)
|
17
|
+
super
|
18
|
+
@seed = @data[0]
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def pattern
|
24
|
+
PATTERN
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module RuGGby
|
2
|
+
|
3
|
+
module Packet
|
4
|
+
|
5
|
+
module Outgoing
|
6
|
+
|
7
|
+
# Base class for all the outgoing messages
|
8
|
+
# Contains all the methods needed to prepare the data to be send via
|
9
|
+
# TCPSocket
|
10
|
+
class Base
|
11
|
+
|
12
|
+
class << self
|
13
|
+
|
14
|
+
attr_reader :send_only_header
|
15
|
+
|
16
|
+
@send_only_header = false
|
17
|
+
|
18
|
+
# Tell us that, the package will be send without body
|
19
|
+
def only_header
|
20
|
+
@send_only_header = true
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
class NotImplemented < Exception; end
|
26
|
+
|
27
|
+
def initialize(params = {})
|
28
|
+
params.each do |key, value|
|
29
|
+
self.send(:"#{key}=", value)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Pack data set according to given pattern
|
34
|
+
def pack
|
35
|
+
if self.class.send_only_header
|
36
|
+
rawbody=[].pack('')
|
37
|
+
else
|
38
|
+
rawbody = body.pack(pattern)
|
39
|
+
end
|
40
|
+
|
41
|
+
[type, rawbody.length].pack('LL') + rawbody
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# Body array
|
47
|
+
def body
|
48
|
+
raise NotImplemented
|
49
|
+
end
|
50
|
+
|
51
|
+
# Packet type
|
52
|
+
def type
|
53
|
+
raise NotImplemented
|
54
|
+
end
|
55
|
+
|
56
|
+
# Pack pattern
|
57
|
+
def pattern
|
58
|
+
raise NotImplemented
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module RuGGby
|
2
|
+
|
3
|
+
module Packet
|
4
|
+
|
5
|
+
module Outgoing
|
6
|
+
|
7
|
+
# Packet send when we want to change status
|
8
|
+
# Params:
|
9
|
+
# => new_status
|
10
|
+
# => description
|
11
|
+
class ChangeStatus < RuGGby::Packet::Outgoing::Base
|
12
|
+
|
13
|
+
TYPE = 0x0038
|
14
|
+
PATTERN = 'LLL'
|
15
|
+
|
16
|
+
attr_accessor :status, :description
|
17
|
+
|
18
|
+
def initialize(status, description)
|
19
|
+
@description = description || ''
|
20
|
+
|
21
|
+
if @description.length > 0
|
22
|
+
@status = RuGGby::Converter.status_description(status)
|
23
|
+
else
|
24
|
+
@status = RuGGby::Converter.status(status)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def body
|
31
|
+
[
|
32
|
+
@status, # L
|
33
|
+
0, # L
|
34
|
+
@description.length, # L
|
35
|
+
@description # a
|
36
|
+
]
|
37
|
+
end
|
38
|
+
|
39
|
+
def type
|
40
|
+
TYPE
|
41
|
+
end
|
42
|
+
|
43
|
+
def pattern
|
44
|
+
if @description.length > 0
|
45
|
+
PATTERN+"a#{@description.length}"
|
46
|
+
else
|
47
|
+
PATTERN
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module RuGGby
|
2
|
+
|
3
|
+
module Packet
|
4
|
+
|
5
|
+
module Outgoing
|
6
|
+
|
7
|
+
# Packet send when we want to login into our GG account
|
8
|
+
# There is a lot of data that needs to be send here, but basically
|
9
|
+
# We need to provide:
|
10
|
+
# => :uin - gg number
|
11
|
+
# => :hash - hashed password
|
12
|
+
# We can also provide a gg status:
|
13
|
+
# => :status
|
14
|
+
class Login < RuGGby::Packet::Outgoing::Base
|
15
|
+
|
16
|
+
TYPE = 0x0031
|
17
|
+
PATTERN = 'La2Ca64LLLLSLSCCLa35L'
|
18
|
+
GG_VERSION = 'Gadu-Gadu Client build 10.0.0.10450'
|
19
|
+
|
20
|
+
attr_accessor :uin, :hash, :status, :description
|
21
|
+
|
22
|
+
def initialize(params = {})
|
23
|
+
super
|
24
|
+
@description = params[:description] || ''
|
25
|
+
|
26
|
+
if @description.length > 0
|
27
|
+
@status = RuGGby::Converter.status_description(@status)
|
28
|
+
else
|
29
|
+
@status = RuGGby::Converter.status(@status)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def body
|
36
|
+
[
|
37
|
+
@uin.to_i, # L
|
38
|
+
'pl', # a2
|
39
|
+
0x02, # C
|
40
|
+
@hash, # a64
|
41
|
+
@status, # L
|
42
|
+
0, # L
|
43
|
+
0x00000007, # L
|
44
|
+
0, # L
|
45
|
+
0, # S
|
46
|
+
0, # L
|
47
|
+
0, # S
|
48
|
+
64, # C
|
49
|
+
0x64, # C
|
50
|
+
GG_VERSION.length, # L
|
51
|
+
GG_VERSION, #a35
|
52
|
+
@description.length, #L,
|
53
|
+
@description # Nothing?
|
54
|
+
]
|
55
|
+
end
|
56
|
+
|
57
|
+
def type
|
58
|
+
TYPE
|
59
|
+
end
|
60
|
+
|
61
|
+
def pattern
|
62
|
+
if @description.length > 0
|
63
|
+
PATTERN+"a#{@description.length}"
|
64
|
+
else
|
65
|
+
PATTERN
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module RuGGby
|
2
|
+
|
3
|
+
module Packet
|
4
|
+
|
5
|
+
module Outgoing
|
6
|
+
|
7
|
+
# Mark instance needs to be send to GG if we don't have (and don't want)
|
8
|
+
# any user list - if we don't send this after valid loggin process
|
9
|
+
# the GG will not communicate with us
|
10
|
+
class Mark < RuGGby::Packet::Outgoing::Base
|
11
|
+
|
12
|
+
TYPE = 0x12
|
13
|
+
|
14
|
+
only_header
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def type
|
19
|
+
TYPE
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module RuGGby
|
2
|
+
|
3
|
+
module Packet
|
4
|
+
|
5
|
+
module Outgoing
|
6
|
+
|
7
|
+
# Out going message class
|
8
|
+
# When we want to send a message to a GG user, we need to provide
|
9
|
+
# a GG number an a message
|
10
|
+
class Message < RuGGby::Packet::Outgoing::Base
|
11
|
+
|
12
|
+
TYPE = 0x0b
|
13
|
+
PATTERN = 'LLLa*C'
|
14
|
+
|
15
|
+
attr_accessor :message, :uin
|
16
|
+
|
17
|
+
# uin => GG nr
|
18
|
+
# msg => message we want to send
|
19
|
+
def initialize(uin, msg)
|
20
|
+
# Type uin (gg number) to integer, just to be sure
|
21
|
+
@uin = uin.to_i
|
22
|
+
# Convert incoming message into ISO-8859-2 just to be sure that
|
23
|
+
# polish letters will be sent properly
|
24
|
+
@message = StringEncoder.to_output(msg)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def body
|
30
|
+
[@uin, rand(2**32), 0x08, @message, 0]
|
31
|
+
end
|
32
|
+
|
33
|
+
def pattern
|
34
|
+
PATTERN
|
35
|
+
end
|
36
|
+
|
37
|
+
def type
|
38
|
+
TYPE
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module RuGGby
|
2
|
+
|
3
|
+
module Packet
|
4
|
+
|
5
|
+
module Outgoing
|
6
|
+
|
7
|
+
# Ping packet is used to upkeep the TCP connection with GG
|
8
|
+
# Needs to be send every 3-5 minutes to tell GG that we are alive
|
9
|
+
class Ping < RuGGby::Packet::Outgoing::Base
|
10
|
+
|
11
|
+
TYPE = 0x08
|
12
|
+
|
13
|
+
only_header
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def type
|
18
|
+
TYPE
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
|
4
|
+
module RuGGby
|
5
|
+
|
6
|
+
# Password encryption method
|
7
|
+
class Password
|
8
|
+
|
9
|
+
# SHA1
|
10
|
+
HASH_TYPE = 0x02
|
11
|
+
|
12
|
+
# Hash password with seed to a format accepted by GG server
|
13
|
+
def self.hash(password, seed)
|
14
|
+
Digest::SHA1.digest(password+[seed].pack('L'))
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'open-uri'
|
3
|
+
|
4
|
+
module RuGGby
|
5
|
+
|
6
|
+
# TCP socket proxy
|
7
|
+
class Socket
|
8
|
+
# GG version that we send to the URL
|
9
|
+
VERSION = '10.0.0.7669'
|
10
|
+
# URL with 'real' server address
|
11
|
+
URL = 'http://appmsg.gadu-gadu.pl'
|
12
|
+
|
13
|
+
class NotOperating < Exception; end
|
14
|
+
|
15
|
+
attr_reader :host, :port
|
16
|
+
|
17
|
+
# We need to provide a GG number because different GG numbers might get
|
18
|
+
# different 'real' servers
|
19
|
+
def initialize(gg_nr)
|
20
|
+
conn_params = self.class.connection_params(gg_nr)
|
21
|
+
@host = conn_params[:host]
|
22
|
+
@port = conn_params[:port]
|
23
|
+
ensure_operating
|
24
|
+
|
25
|
+
socket
|
26
|
+
end
|
27
|
+
|
28
|
+
# Before writing to socket, pack the incoming data
|
29
|
+
# We use pack because we assume that only objects from
|
30
|
+
# ruGGby::Packet::Outgoing might be send and all of them implemenent this
|
31
|
+
# method
|
32
|
+
def write(data)
|
33
|
+
socket.write(data.pack)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# Send all the unknown methods invokations into TCPSocket
|
39
|
+
def method_missing(name, *args, &block)
|
40
|
+
socket.public_send(name, *args, &block)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Create a TCPSocket
|
44
|
+
def socket
|
45
|
+
@socket ||= ::TCPSocket.new(@host , @port)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Get connection params
|
49
|
+
def self.connection_params(gg_nr)
|
50
|
+
gg_info = open(build_url(gg_nr)).readlines[0].split(/\s/)[2].split(':')
|
51
|
+
{:host => gg_info[0], :port => gg_info[1]}
|
52
|
+
end
|
53
|
+
|
54
|
+
# Buduje caly adres ktory bedziemy odpytywac o IP i port serwera do laczenia
|
55
|
+
# sie z GG
|
56
|
+
def self.build_url(gg_nr)
|
57
|
+
@build_url ||= "#{URL}/appsvc/appmsg_ver8.asp?fmnumber=#{gg_nr}&version=#{VERSION}&lastmsg=0"
|
58
|
+
end
|
59
|
+
|
60
|
+
# Raise exception if we will get a not operating 'real' server
|
61
|
+
def ensure_operating
|
62
|
+
raise NotOperating if @host == 'notoperating'
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|