ruggby 0.3.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/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
|