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,27 @@
|
|
1
|
+
module RuGGby
|
2
|
+
|
3
|
+
module Action
|
4
|
+
|
5
|
+
# Send a "mark" message
|
6
|
+
# If we don't download GG friends list, we need to send a mark anyway
|
7
|
+
class Mark < Base
|
8
|
+
|
9
|
+
def initialize(client)
|
10
|
+
@client = client
|
11
|
+
@block = client.actions[:mark]
|
12
|
+
end
|
13
|
+
|
14
|
+
# Send a mark packet
|
15
|
+
def run!
|
16
|
+
@client.logger.debug('RuGGby::Action::Mark')
|
17
|
+
|
18
|
+
mark = Packet::Outgoing::Mark.new
|
19
|
+
@client.socket.write(mark)
|
20
|
+
@block.call if @block
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module RuGGby
|
2
|
+
|
3
|
+
module Action
|
4
|
+
|
5
|
+
# Action invoken when new message occures
|
6
|
+
class NewMessage < Base
|
7
|
+
|
8
|
+
attr_reader :uin, :message
|
9
|
+
|
10
|
+
def initialize(client, data)
|
11
|
+
@client = client
|
12
|
+
@block = client.actions[:new_message]
|
13
|
+
@uin = data[0]
|
14
|
+
@created_at = Time.at(data[2])
|
15
|
+
@message = StringEncoder.complex(data[4])
|
16
|
+
end
|
17
|
+
|
18
|
+
def run!
|
19
|
+
@client.logger.debug('RuGGby::Action::NewMessage')
|
20
|
+
|
21
|
+
@block.call(@uin, @created_at, @message) if @block
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module RuGGby
|
2
|
+
|
3
|
+
module Action
|
4
|
+
|
5
|
+
# Ping action
|
6
|
+
# In order to maintaine a TCP connection with GG server, we need to send
|
7
|
+
# a ping from time to time.
|
8
|
+
# This is done in a background thread (don't need to worry about it
|
9
|
+
# just ignite it and forget ;) )
|
10
|
+
class Ping < Base
|
11
|
+
|
12
|
+
SLEEP_TIME = 180
|
13
|
+
|
14
|
+
def initialize(client)
|
15
|
+
@client = client
|
16
|
+
@block = client.actions[:ping]
|
17
|
+
end
|
18
|
+
|
19
|
+
def run!
|
20
|
+
@client.loops[:ping] = Thread.new do
|
21
|
+
begin
|
22
|
+
loop do
|
23
|
+
@client.logger.info('RuGGby::Action::Ping')
|
24
|
+
ping = Packet::Outgoing::Ping.new
|
25
|
+
@client.socket.write(ping)
|
26
|
+
sleep SLEEP_TIME
|
27
|
+
@block.call if @block
|
28
|
+
end
|
29
|
+
rescue => e
|
30
|
+
@client.logger.fatal("RuGGby::Action::Ping #{e}")
|
31
|
+
raise e
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module RuGGby
|
2
|
+
|
3
|
+
module Action
|
4
|
+
|
5
|
+
# Action reading from socket - will check every SLEEP_TIME if there is a
|
6
|
+
# data in TCPSocket
|
7
|
+
class Read < Base
|
8
|
+
|
9
|
+
SLEEP_TIME = 0.2
|
10
|
+
|
11
|
+
def initialize(client)
|
12
|
+
@client = client
|
13
|
+
@block = client.actions[:read]
|
14
|
+
end
|
15
|
+
|
16
|
+
# Start a thread that reads data and if there is any data - try to create
|
17
|
+
# an appropriate packet and if successful will create a thread that
|
18
|
+
# handles an callback
|
19
|
+
def run!
|
20
|
+
@client.loops[:read] = Thread.new do
|
21
|
+
@client.logger.debug('RuGGby::Action::Read')
|
22
|
+
begin
|
23
|
+
loop do
|
24
|
+
sleep SLEEP_TIME
|
25
|
+
|
26
|
+
if el = RuGGby::Packet::Factory.new(@client.socket).build
|
27
|
+
@client.logger.info("RuGGby::Action::Read - type: #{el.class::TYPE}")
|
28
|
+
|
29
|
+
case RuGGby::Converter.action(el.class::TYPE)
|
30
|
+
when :new_message then
|
31
|
+
action = RuGGby::Action::NewMessage.new(@client, el.data)
|
32
|
+
Threader.run(@client, action)
|
33
|
+
end
|
34
|
+
|
35
|
+
@block.call(el) if @block
|
36
|
+
end
|
37
|
+
end
|
38
|
+
rescue => e
|
39
|
+
@client.logger.fatal("RuGGby::Action::Read #{e}")
|
40
|
+
raise e
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module RuGGby
|
2
|
+
|
3
|
+
# Defines methods that we can use to define an actions to events
|
4
|
+
module Callback
|
5
|
+
|
6
|
+
# What should we do when we receive a new message
|
7
|
+
# You'll get passed 3 parameters to a block:
|
8
|
+
# uin => user GG number
|
9
|
+
# created_at => Time.at when message was sent
|
10
|
+
# message => String containing a received message
|
11
|
+
def on_new_message(&block)
|
12
|
+
self.actions[:new_message] = block
|
13
|
+
end
|
14
|
+
|
15
|
+
# Method invoked when we change our status or description on GG
|
16
|
+
def on_change_status(&block)
|
17
|
+
self.actions[:change_status] = block
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module RuGGby
|
2
|
+
|
3
|
+
# The main GG bot class used to control our GG bot
|
4
|
+
class Client
|
5
|
+
|
6
|
+
include Callback
|
7
|
+
|
8
|
+
class << self
|
9
|
+
attr_accessor :logger
|
10
|
+
attr_accessor :log_level
|
11
|
+
end
|
12
|
+
|
13
|
+
@logger ||= RuGGby::Logger
|
14
|
+
@log_level ||= :debug
|
15
|
+
|
16
|
+
attr_reader :socket, :password
|
17
|
+
attr_writer :logged
|
18
|
+
attr_accessor :loops, :actions, :threads, :reconnect, :status,
|
19
|
+
:description, :login, :logger
|
20
|
+
|
21
|
+
def initialize()
|
22
|
+
@loops = {}
|
23
|
+
@actions = {}
|
24
|
+
@threads = []
|
25
|
+
@logged = false
|
26
|
+
@logger = self.class.logger.new(self.class.log_level)
|
27
|
+
|
28
|
+
@logger.debug('RuGGby::Client initialized')
|
29
|
+
end
|
30
|
+
|
31
|
+
def login!(gg_nr, password, params = {})
|
32
|
+
@logger.debug('RuGGby::Client login in')
|
33
|
+
|
34
|
+
@login = gg_nr
|
35
|
+
@password = password
|
36
|
+
@status = params[:status] || :available
|
37
|
+
@description = params[:description]
|
38
|
+
|
39
|
+
if logged?
|
40
|
+
@logger.info('RuGGby::Client already logged')
|
41
|
+
return true
|
42
|
+
end
|
43
|
+
|
44
|
+
@logger.debug('RuGGby::Client creating socket')
|
45
|
+
|
46
|
+
@socket = Socket.new(@login)
|
47
|
+
|
48
|
+
begin
|
49
|
+
RuGGby::Action::Login.new(self).run!
|
50
|
+
rescue => e
|
51
|
+
@logger.info("RuGGby::Client login failed")
|
52
|
+
@logger.error("RuGGby::Client #{e}")
|
53
|
+
|
54
|
+
@logged = false
|
55
|
+
end
|
56
|
+
|
57
|
+
if logged?
|
58
|
+
RuGGby::Action::Mark.new(self).run!
|
59
|
+
RuGGby::Action::Ping.new(self).run!
|
60
|
+
RuGGby::Action::Read.new(self).run!
|
61
|
+
end
|
62
|
+
|
63
|
+
@logged
|
64
|
+
end
|
65
|
+
|
66
|
+
def logged?
|
67
|
+
@logged
|
68
|
+
end
|
69
|
+
|
70
|
+
def logout!
|
71
|
+
@logger.debug('RuGGby::Client logout')
|
72
|
+
|
73
|
+
@threads.each { |t| t.exit }
|
74
|
+
@loops.each { |k, v| v.exit }
|
75
|
+
@socket.close
|
76
|
+
@logged = false
|
77
|
+
end
|
78
|
+
|
79
|
+
def change_status(status, description = '')
|
80
|
+
RuGGby::Action::ChangeStatus.new(self, status, description).run!
|
81
|
+
end
|
82
|
+
|
83
|
+
def message(uin, message)
|
84
|
+
@logger.debug('RuGGby::Client message')
|
85
|
+
|
86
|
+
RuGGby::Action::CreateMessage.new(self, uin, message).run!
|
87
|
+
end
|
88
|
+
|
89
|
+
def wait
|
90
|
+
@loops[:ping].join
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module RuGGby
|
2
|
+
|
3
|
+
# Module used to convert messages from GG format (INT, Chars) to a ruby symbols
|
4
|
+
module Converter
|
5
|
+
|
6
|
+
# Available actions
|
7
|
+
ACTION = {
|
8
|
+
Packet::Incoming::Message::TYPE => :new_message
|
9
|
+
}
|
10
|
+
|
11
|
+
# Available statuses (sent by GG server)
|
12
|
+
STATUS = {
|
13
|
+
0x02 => :available,
|
14
|
+
0x04 => :available,
|
15
|
+
0x03 => :busy,
|
16
|
+
0x05 => :busy,
|
17
|
+
0x14 => :invisible,
|
18
|
+
0x16 => :invisible,
|
19
|
+
0x01 => :not_available,
|
20
|
+
0x15 => :not_available
|
21
|
+
}
|
22
|
+
|
23
|
+
# Statuses that we can use
|
24
|
+
TO_STATUS = {
|
25
|
+
:available => 0x02,
|
26
|
+
:busy => 0x03,
|
27
|
+
:invisible => 0x14,
|
28
|
+
:not_available => 0x01
|
29
|
+
}
|
30
|
+
|
31
|
+
# Statuses that are available but we also need to provide a description
|
32
|
+
TO_STATUS_DESCRIPTION = {
|
33
|
+
:available => 0x04,
|
34
|
+
:busy => 0x05,
|
35
|
+
:invisible => 0x16,
|
36
|
+
:not_available => 0x15
|
37
|
+
}
|
38
|
+
|
39
|
+
def self.status(status)
|
40
|
+
if status.is_a?(Symbol) || status.is_a?(String)
|
41
|
+
TO_STATUS[status.to_sym]
|
42
|
+
else
|
43
|
+
STATUS[status]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.status_description(status)
|
48
|
+
TO_STATUS_DESCRIPTION[status]
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.action(action)
|
52
|
+
ACTION[action]
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module RuGGby
|
2
|
+
|
3
|
+
class Logger
|
4
|
+
|
5
|
+
DEBUG = 0
|
6
|
+
INFO = 1
|
7
|
+
ERROR = 2
|
8
|
+
FATAL = 3
|
9
|
+
|
10
|
+
LEVEL_MAP = {
|
11
|
+
:debug => DEBUG,
|
12
|
+
:info => INFO,
|
13
|
+
:error => ERROR,
|
14
|
+
:fatal => FATAL
|
15
|
+
}
|
16
|
+
|
17
|
+
# Available log levels
|
18
|
+
# => debug
|
19
|
+
# => error
|
20
|
+
# => fatal
|
21
|
+
def initialize(level)
|
22
|
+
@level = LEVEL_MAP[level]
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns +true+ iff the current level allows for the printing of
|
26
|
+
# +DEBUG+ messages.
|
27
|
+
def debug?; @level <= DEBUG; end
|
28
|
+
|
29
|
+
# Returns +true+ iff the current level allows for the printing of
|
30
|
+
# +INFO+ messages.
|
31
|
+
def info?; @level <= INFO; end
|
32
|
+
|
33
|
+
# Returns +true+ iff the current level allows for the printing of
|
34
|
+
# +ERROR+ messages.
|
35
|
+
def error?; @level <= ERROR; end
|
36
|
+
|
37
|
+
# Returns +true+ iff the current level allows for the printing of
|
38
|
+
# +FATAL+ messages.
|
39
|
+
def fatal?; @level <= FATAL; end
|
40
|
+
|
41
|
+
def debug(msg)
|
42
|
+
handle_message(msg, :debug)
|
43
|
+
end
|
44
|
+
|
45
|
+
def info(msg)
|
46
|
+
handle_message(msg, :info)
|
47
|
+
end
|
48
|
+
|
49
|
+
def error(msg)
|
50
|
+
handle_message(msg, :error)
|
51
|
+
end
|
52
|
+
|
53
|
+
def fatal(msg)
|
54
|
+
handle_message(msg, :fatal)
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def handle_message(msg, level)
|
60
|
+
if self.send(:"#{level}?")
|
61
|
+
printf "%-6s %s\n", level.upcase.to_s+':', msg
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module RuGGby
|
2
|
+
|
3
|
+
module Packet
|
4
|
+
|
5
|
+
# Will build a certain incoming packet based on a message header
|
6
|
+
class Factory
|
7
|
+
|
8
|
+
# I => unsigned int, native endian
|
9
|
+
HEADER_PACK_PATTERN = 'II'
|
10
|
+
HEADER_SIZE = 8
|
11
|
+
|
12
|
+
attr_reader :type, :length, :data
|
13
|
+
|
14
|
+
def initialize(socket)
|
15
|
+
unpacked = socket.read(HEADER_SIZE).unpack HEADER_PACK_PATTERN
|
16
|
+
@type, @length = unpacked
|
17
|
+
@data = socket.read(@length)
|
18
|
+
end
|
19
|
+
|
20
|
+
def build
|
21
|
+
case @type
|
22
|
+
when Packet::Incoming::Welcome::TYPE then
|
23
|
+
Incoming::Welcome.new(@data)
|
24
|
+
when Packet::Incoming::LoginStatus::TYPE then
|
25
|
+
Packet::Incoming::LoginStatus.new(true)
|
26
|
+
when Packet::Incoming::Message::TYPE then
|
27
|
+
Packet::Incoming::Message.new(@data)
|
28
|
+
when nil
|
29
|
+
Packet::Incoming::LoginStatus.new(false)
|
30
|
+
else
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module RuGGby
|
2
|
+
|
3
|
+
module Packet
|
4
|
+
|
5
|
+
module Incoming
|
6
|
+
|
7
|
+
# Base class for all the incoming packets
|
8
|
+
# Unpacks the data accorging to the pattern method
|
9
|
+
class Base
|
10
|
+
|
11
|
+
class NotImplemented < Exception; end
|
12
|
+
|
13
|
+
attr_reader :data
|
14
|
+
|
15
|
+
def initialize(data)
|
16
|
+
@data = data.unpack(pattern)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def pattern
|
22
|
+
raise NotImplemented
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|