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.
Files changed (40) hide show
  1. data/CHANGELOG.rdoc +2 -0
  2. data/Gemfile +2 -0
  3. data/MIT-LICENSE +20 -0
  4. data/Manifest +38 -0
  5. data/README.md +187 -0
  6. data/Rakefile +12 -0
  7. data/init.rb +1 -0
  8. data/lib/ruggby.rb +72 -0
  9. data/lib/ruggby/action/base.rb +16 -0
  10. data/lib/ruggby/action/change_status.rb +30 -0
  11. data/lib/ruggby/action/create_message.rb +27 -0
  12. data/lib/ruggby/action/login.rb +40 -0
  13. data/lib/ruggby/action/mark.rb +27 -0
  14. data/lib/ruggby/action/new_message.rb +28 -0
  15. data/lib/ruggby/action/ping.rb +40 -0
  16. data/lib/ruggby/action/read.rb +49 -0
  17. data/lib/ruggby/callback.rb +22 -0
  18. data/lib/ruggby/client.rb +95 -0
  19. data/lib/ruggby/converter.rb +57 -0
  20. data/lib/ruggby/logger.rb +67 -0
  21. data/lib/ruggby/packet/factory.rb +39 -0
  22. data/lib/ruggby/packet/incoming/base.rb +31 -0
  23. data/lib/ruggby/packet/incoming/login_status.rb +28 -0
  24. data/lib/ruggby/packet/incoming/message.rb +35 -0
  25. data/lib/ruggby/packet/incoming/welcome.rb +33 -0
  26. data/lib/ruggby/packet/outgoing/base.rb +67 -0
  27. data/lib/ruggby/packet/outgoing/change_status.rb +57 -0
  28. data/lib/ruggby/packet/outgoing/login.rb +75 -0
  29. data/lib/ruggby/packet/outgoing/mark.rb +28 -0
  30. data/lib/ruggby/packet/outgoing/message.rb +47 -0
  31. data/lib/ruggby/packet/outgoing/ping.rb +27 -0
  32. data/lib/ruggby/password.rb +19 -0
  33. data/lib/ruggby/socket.rb +67 -0
  34. data/lib/ruggby/string_encoder.rb +42 -0
  35. data/lib/ruggby/threader.rb +21 -0
  36. data/lib/ruggby/version.rb +5 -0
  37. data/ruggby.gemspec +35 -0
  38. data/spec/callback_spec.rb +40 -0
  39. data/spec/spec_helper.rb +7 -0
  40. 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