ruggby 0.3.0

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