ircguerilla-irc 1.2.0 → 1.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 (53) hide show
  1. data/lib/irc/client/command_handler.rb +2 -2
  2. data/lib/irc/client/connected_state.rb +53 -8
  3. data/lib/irc/client/connection.rb +41 -6
  4. data/lib/irc/client/connection_listener.rb +26 -11
  5. data/lib/irc/client/connection_state.rb +15 -2
  6. data/lib/irc/client/context.rb +49 -11
  7. data/lib/irc/client/disconnected_state.rb +28 -16
  8. data/lib/irc/client/message_handler.rb +40 -16
  9. data/lib/irc/client/registered_state.rb +18 -0
  10. data/lib/irc/client/unregistered_state.rb +51 -16
  11. data/lib/irc/commands/nick_command.rb +14 -0
  12. data/lib/irc/messages/error_join_message.rb +1 -1
  13. data/lib/irc/messages/error_message.rb +53 -24
  14. data/lib/irc/messages/error_nick_name_in_use_message.rb +70 -0
  15. data/lib/irc/messages/factory.rb +16 -0
  16. data/lib/irc/messages/isupport_message.rb +86 -0
  17. data/lib/irc/messages/kick_message.rb +114 -0
  18. data/lib/irc/messages/message.rb +21 -1
  19. data/lib/irc/messages/nick_message.rb +17 -2
  20. data/lib/irc/messages/pong_message.rb +1 -1
  21. data/lib/irc/messages/welcome_message.rb +72 -0
  22. data/lib/irc/util/color.rb +77 -0
  23. data/lib/irc/util/nick_generator.rb +58 -0
  24. data/test/functional/irc/client/connection_test.rb +34 -1
  25. data/test/functional/irc/client/context_test.rb +5 -2
  26. data/test/mocks/test/irc/client/connection.rb +128 -0
  27. data/test/mocks/test/irc/client/disconnected_state.rb +44 -0
  28. data/test/test_helper.rb +1 -1
  29. data/test/unit/irc/client/command_handler_test.rb +12 -12
  30. data/test/unit/irc/client/message_handler_test.rb +136 -0
  31. data/test/unit/irc/commands/command_test.rb +87 -84
  32. data/test/unit/irc/commands/join_command_test.rb +3 -1
  33. data/test/unit/irc/commands/nick_command_test.rb +7 -1
  34. data/test/unit/irc/commands/ping_command_test.rb +12 -16
  35. data/test/unit/irc/commands/pong_command_test.rb +2 -1
  36. data/test/unit/irc/messages/error_join_message_test.rb +23 -2
  37. data/test/unit/irc/messages/error_message_test.rb +56 -0
  38. data/test/unit/irc/messages/error_nick_name_in_use_message_test.rb +56 -0
  39. data/test/unit/irc/messages/factory_test.rb +25 -0
  40. data/test/unit/irc/messages/isupport_message_test.rb +138 -0
  41. data/test/unit/irc/messages/join_message_test.rb +22 -1
  42. data/test/unit/irc/messages/kick_message_test.rb +76 -0
  43. data/test/unit/irc/messages/message_test.rb +33 -0
  44. data/test/unit/irc/messages/nick_message_test.rb +22 -1
  45. data/test/unit/irc/messages/notice_message_test.rb +22 -1
  46. data/test/unit/irc/messages/part_message_test.rb +22 -1
  47. data/test/unit/irc/messages/ping_message_test.rb +21 -1
  48. data/test/unit/irc/messages/pong_message_test.rb +21 -1
  49. data/test/unit/irc/messages/private_message_test.rb +22 -1
  50. data/test/unit/irc/messages/welcome_message_test.rb +59 -0
  51. data/test/unit/irc/util/color_test.rb +97 -0
  52. data/test/unit/irc/util/nick_generator_test.rb +52 -0
  53. metadata +69 -52
@@ -23,8 +23,10 @@
23
23
  #
24
24
  # $Id: message_handler.rb 85 2006-08-13 11:42:07Z roman $
25
25
  #
26
+ require 'irc/client/disconnected_state'
26
27
  require 'irc/messages/factory'
27
28
  require 'log4r'
29
+ require 'timeout'
28
30
 
29
31
  module IRC
30
32
 
@@ -38,6 +40,7 @@ module IRC
38
40
  def initialize(context)
39
41
  @log = Log4r::Logger.new('IRC::Client::MessageHandler')
40
42
  @context = context
43
+ @terminate = false
41
44
  end
42
45
 
43
46
  def start
@@ -45,21 +48,38 @@ module IRC
45
48
  # Don't start the command handler if already started.
46
49
  raise MessageHandlerError.new("Can't start message handler. Already started.") if @thread != nil
47
50
 
51
+ # Terminate the message reading loop if true.
52
+ @terminate = false
53
+
48
54
  # Start the message handler thread.
49
55
  @thread = Thread.new do
50
56
 
51
- loop do
52
-
53
- # Read the raw message from the socket.
54
- raw_message = socket.gets("\r\n")
55
-
56
- # Log the raw message.
57
- @log.debug("[#{@context.network.to_s.upcase}] <<< #{raw_message.chomp}")
58
-
59
- # Create the message object & ask the message object to handle it.
60
- message = message = IRC::Messages::Factory.create(raw_message)
61
- message.handle(@context)
62
-
57
+ while @terminate == false do
58
+
59
+ begin
60
+
61
+ raw_message = nil
62
+
63
+ # Read the raw message from the socket.
64
+ Timeout::timeout(0.5) do
65
+ raw_message = socket.gets("\r\n")
66
+ end
67
+
68
+ # Log the raw message.
69
+ @log.debug("[#{@context.network.to_s.upcase}] <<< #{raw_message.chomp}")
70
+
71
+ # Create the message object & ask the message object to handle it.
72
+ message = message = IRC::Messages::Factory.create(raw_message)
73
+ message.handle(@context)
74
+
75
+ rescue IOError => e
76
+ @terminate = true
77
+ @thread = nil
78
+
79
+ rescue Timeout::Error => e
80
+ # Ok
81
+ end
82
+
63
83
  end
64
84
 
65
85
  end
@@ -71,11 +91,15 @@ module IRC
71
91
  def stop
72
92
 
73
93
  # Don't stop the command handler if already stopped.
74
- raise CommandHandlerError.new("Can't stop message handler. Already stopped.") if @thread == nil
94
+ raise MessageHandlerError.new("Can't stop message handler. Already stopped.") if @thread == nil
75
95
 
76
- # Kill the thread.
77
- @thread.kill
96
+ # Terminate the message reading loop.
97
+ @terminate = true
78
98
 
99
+ # Kill the thread.
100
+ # Thread.kill(@thread)
101
+
102
+ @thread = nil
79
103
  @log.debug("[#{@context.network.to_s.upcase}] Message handler successfully stopped.")
80
104
 
81
105
  end
@@ -83,7 +107,7 @@ module IRC
83
107
  private
84
108
 
85
109
  def socket
86
- return @context.socket
110
+ return @context.input_socket
87
111
  end
88
112
 
89
113
  end
@@ -26,6 +26,7 @@
26
26
  require 'irc/client/client_error'
27
27
  require 'irc/client/connected_state'
28
28
  require 'irc/commands/join_command'
29
+ require 'irc/commands/nick_command'
29
30
  require 'irc/commands/part_command'
30
31
  require 'log4r'
31
32
 
@@ -39,6 +40,11 @@ module IRC
39
40
  @log = Log4r::Logger.new('IRC::Client::RegisteredState')
40
41
  end
41
42
 
43
+ # Changes the nick name.
44
+ def change_nick(context, nick)
45
+ IRC::Commands::NickCommand.new(nick).execute(context)
46
+ end
47
+
42
48
  # Connects to the server.
43
49
  def connect(context, server)
44
50
  raise ClientError.new("Can't connect to server. Already connected to a server.")
@@ -54,6 +60,11 @@ module IRC
54
60
  IRC::Commands::PartCommand.new(channels).execute(context)
55
61
  end
56
62
 
63
+ # Register the connection by sending the password, nick and user commands.
64
+ def register(context, nick, login, realname)
65
+ raise ClientError.new("Can't register connection again. Already registered.")
66
+ end
67
+
57
68
  # ConnectionListener
58
69
 
59
70
  # This method gets called when a user (possibly us) joins a channel.
@@ -72,6 +83,13 @@ module IRC
72
83
  def on_kick(context, channel, kick_user, kicked_user, reason)
73
84
  end
74
85
 
86
+ # This method gets called when the chosen nick name is already in use.
87
+ def on_nick_already_in_use(context, nick_name)
88
+ return unless context.auto_nick_change
89
+ raise ClientError("Maximum number of nick name changes reached. Giving up!") if context.nick_generator.names.empty?
90
+ context.change_nick(context.nick_generator.pop)
91
+ end
92
+
75
93
  # This method gets called when a user (possibly us) leaves a channel.
76
94
  def on_part(context, channel, user)
77
95
  return unless context.nick.strip.downcase == user.nick.strip.downcase
@@ -29,6 +29,8 @@ require 'irc/client/registered_state'
29
29
  require 'irc/messages/error_message'
30
30
  require 'irc/commands/nick_command'
31
31
  require 'irc/messages/notice_message'
32
+ require 'irc/commands/password_command'
33
+ require 'irc/messages/ping_message'
32
34
  require 'irc/commands/user_command'
33
35
  require 'log4r'
34
36
 
@@ -42,6 +44,11 @@ module IRC
42
44
  @log = Log4r::Logger.new('IRC::Client::UnregisteredState')
43
45
  end
44
46
 
47
+ # Changes the nick name.
48
+ def change_nick(context, nick)
49
+ raise ClientError.new("Can't change nick name. The connection is not yet registered.")
50
+ end
51
+
45
52
  # Joins the given channels.
46
53
  def join(context, channels)
47
54
  raise ClientError.new("Can't join any channel. The connection is not yet registered.")
@@ -56,7 +63,21 @@ module IRC
56
63
  def private_message(context, target, message)
57
64
  raise ClientError.new("Can't send a private message. The connection is not yet registered.")
58
65
  end
59
-
66
+
67
+ # Register the connection by sending the password, nick and user commands.
68
+ def register(context, nick, login, realname)
69
+
70
+ if context.server.password
71
+ IRC::Commands::PasswordCommand.new(context.server.password).execute(context)
72
+ end
73
+
74
+ IRC::Commands::NickCommand.new(nick).execute(context)
75
+ IRC::Commands::UserCommand.new(nick, Socket.gethostname, context.server.hostname, realname).execute(context)
76
+
77
+ context.nick, context.login, context.realname = nick, login, realname
78
+
79
+ end
80
+
60
81
  # ConnectionListener
61
82
 
62
83
  # This method gets called when a message from the server is received.
@@ -67,36 +88,45 @@ module IRC
67
88
  # Ignore notice messages.
68
89
  when IRC::Messages::NoticeMessage::CODE
69
90
  return
91
+
92
+ # Ignore ping messages.
93
+ when IRC::Messages::PingMessage::CODE
94
+ return
70
95
 
71
96
  # The given nick name is not valid.
72
97
  when IRC::Messages::ErrorErroneusNickNameMessage::CODE
73
- registration_failure(context, message)
98
+ registration_failure(context, message)
99
+ return
74
100
 
75
101
  # We didn't send a valid command. There are some parameters missing.
76
102
  when IRC::Messages::ErrorNeedMoreParametersMessage::CODE
77
103
  registration_failure(context, message)
78
-
104
+ return
105
+
79
106
  # Nick name collision.
80
107
  when IRC::Messages::ErrorNickCollisionMessage::CODE
81
108
  registration_failure(context, message)
82
-
109
+ return
110
+
83
111
  # The chosen nick name is already in use.
84
112
  when IRC::Messages::ErrorNickNameInUseMessage::CODE
85
113
  registration_failure(context, message)
86
-
114
+ return
115
+
87
116
  # We didn't send a valid nick command. The nick name was missing.
88
117
  when IRC::Messages::ErrorNoNickNameGivenMessage::CODE
89
118
  registration_failure(context, message)
90
-
91
- # We are already registered. Did we miss something?
92
- when IRC::Messages::ErrorAlreadyRegisteredMessage::CODE
93
- change_state(context, RegisteredState.instance)
94
-
95
- # Everything ok. Change to the registered state.
96
- else
97
- change_state(context, RegisteredState.instance)
119
+ return
120
+
121
+ end
122
+
123
+ # Everything ok. Change to the registered state.
124
+ change_state(context, RegisteredState.instance)
98
125
 
99
- end
126
+ # Notify all connection listeners that we are successfully connected now.
127
+ context.connection_listeners.each do |connection_listener|
128
+ connection_listener.on_registration(context)
129
+ end
100
130
 
101
131
  end
102
132
 
@@ -109,8 +139,13 @@ module IRC
109
139
  connection_listener.on_registration_failure(context, message)
110
140
  end
111
141
 
112
- IRC::Commands::NickCommand.new("au_lasa").execute(context)
113
- IRC::Commands::UserCommand.new("au_lasa", Socket.gethostname, server.hostname, context.realname).execute(context)
142
+ return unless context.auto_nick_change
143
+
144
+ raise ClientError("Maximum number of nick name changes reached. Giving up!") if context.nick_generator.names.empty?
145
+ nick = context.nick_generator.pop
146
+
147
+ IRC::Commands::NickCommand.new(nick).execute(context)
148
+ IRC::Commands::UserCommand.new(nick, Socket.gethostname, context.server.hostname, context.realname).execute(context)
114
149
 
115
150
  end
116
151
 
@@ -25,6 +25,7 @@
25
25
  #
26
26
  require 'irc/commands/command'
27
27
  require 'irc/commands/invalid_command'
28
+ require 'irc/messages/notice_message'
28
29
 
29
30
  module IRC
30
31
 
@@ -80,6 +81,19 @@ module IRC
80
81
  return "NICK #{nick} #{hopcount ? hopcount.to_s : ''}"
81
82
  end
82
83
 
84
+ # This method gets called when a message from the server is received.
85
+ def on_server_response(connection, message)
86
+
87
+ # Ignore notice messages
88
+ return if message.code == IRC::Messages::NoticeMessage::CODE
89
+
90
+ if !connection.registered? || (connection.registered? && message.code == IRC::Messages::NickMessage::CODE)
91
+ connection.remove_connection_listener(self)
92
+ @response = message
93
+ end
94
+
95
+ end
96
+
83
97
  end
84
98
 
85
99
  end
@@ -57,7 +57,7 @@ module IRC
57
57
  super(raw_message)
58
58
 
59
59
  match_data = Regexp.new('(\S+)\s+(\S+)\s+:?(.+)').match(message)
60
- raise InvalidMessage.new("Can't parse join error message. Invalid message format.") unless match_data
60
+ raise InvalidMessage.new("Can't parse 'nick already in use' message. Invalid message format.") unless match_data
61
61
 
62
62
  # Extract nick & channel name and the error reason.
63
63
  @nick, @channel, @reason = match_data[1], match_data[2], match_data[3]
@@ -32,59 +32,88 @@ module IRC
32
32
  module Messages
33
33
 
34
34
  class ErrorMessage < Message
35
+
36
+ CODE = "ERROR"
37
+
38
+ attr_reader :reason
39
+
40
+ # Notify all connection listeners, that a user changed it's nick name.
41
+ def handle(context)
42
+
43
+ # Notify all connection listeners by calling their on_server_response method.
44
+ super(context)
45
+
46
+ # Notify all connection listeners by calling their on_nick method.
47
+ notify(context) do |connection_listener|
48
+ connection_listener.on_error(context, reason)
49
+ end
50
+
51
+ end
52
+
53
+ protected
54
+
55
+ def parse(raw_message)
56
+
57
+ # Initialize the base message fields.
58
+ super(raw_message)
59
+
60
+ # Match the message against the message format.
61
+ match_data = Regexp.new('\s*ERROR\s+:?(.+)').match(raw_message)
62
+ raise InvalidMessage.new("Can't parse error message. Invalid message format.") unless match_data
63
+
64
+ @reason = match_data[1]
65
+
66
+ end
67
+
35
68
  end
36
69
 
37
- class JoinFailureMessage < ErrorMessage
38
- end
70
+ # class JoinFailureMessage < Message
71
+ # end
72
+ #
73
+ # class PartFailureMessage < Message
74
+ # end
39
75
 
40
- class PartFailureMessage < ErrorMessage
41
- end
42
-
43
- class ErrorAlreadyRegisteredMessage < ErrorMessage
76
+ class ErrorAlreadyRegisteredMessage < Message
44
77
  CODE = ERR_ALREADYREGISTRED
45
78
  end
46
79
 
47
80
  class ErrorBadChannelKeyMessage < ErrorJoinMessage
48
81
  CODE = ERR_BADCHANNELKEY
49
82
  end
50
-
83
+
51
84
  class ErrorBadChannelMaskMessage < ErrorJoinMessage
52
85
  CODE = ERR_BADCHANMASK
53
86
  end
54
-
87
+
55
88
  class ErrorBannedFromChannelMessage < ErrorJoinMessage
56
89
  CODE = ERR_BANNEDFROMCHAN
57
90
  end
58
-
91
+
59
92
  class ErrorChannelIsFulMessage < ErrorJoinMessage
60
93
  CODE = ERR_CHANNELISFULL
61
94
  end
62
-
63
- class ErrorErroneusNickNameMessage < ErrorMessage
95
+
96
+ class ErrorErroneusNickNameMessage < Message
64
97
  CODE = ERR_ERRONEUSNICKNAME
65
98
  end
66
-
99
+
67
100
  class ErrorInviteOnlyChannelMessage < ErrorJoinMessage
68
101
  CODE = ERR_INVITEONLYCHAN
69
102
  end
70
103
 
71
- class ErrorNeedMoreParametersMessage < ErrorMessage
104
+ class ErrorNeedMoreParametersMessage < Message
72
105
  CODE = ERR_NEEDMOREPARAMS
73
106
  end
74
-
75
- class ErrorNickCollisionMessage < ErrorMessage
107
+
108
+ class ErrorNickCollisionMessage < Message
76
109
  CODE = ERR_NICKCOLLISION
77
110
  end
78
-
79
- class ErrorNickNameInUseMessage < ErrorMessage
80
- CODE = ERR_NICKNAMEINUSE
81
- end
82
111
 
83
- class ErrorNoNickNameGivenMessage < ErrorMessage
112
+ class ErrorNoNickNameGivenMessage < Message
84
113
  CODE = ERR_NONICKNAMEGIVEN
85
114
  end
86
115
 
87
- class ErrorNoOriginMessage < ErrorMessage
116
+ class ErrorNoOriginMessage < Message
88
117
  CODE = ERR_NOORIGIN
89
118
  end
90
119
 
@@ -92,14 +121,14 @@ module IRC
92
121
  CODE = ERR_NOSUCHCHANNEL
93
122
  end
94
123
 
95
- class ErrorNoSuchServerMessage < ErrorMessage
124
+ class ErrorNoSuchServerMessage < Message
96
125
  CODE = ERR_NOSUCHSERVER
97
126
  end
98
127
 
99
- class ErrorNotOnChannelMessage < ErrorMessage
128
+ class ErrorNotOnChannelMessage < Message
100
129
  CODE = ERR_NOTONCHANNEL
101
130
  end
102
-
131
+
103
132
  class ErrorTooManyChannelsMessage < ErrorJoinMessage
104
133
  CODE = ERR_TOOMANYCHANNELS
105
134
  end
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (c) 2006 Roman Scherer | IRC Guerilla | Rapid Packet Movement
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ #
24
+ # $Id: nick_message.rb 89 2006-08-13 14:03:35Z roman $
25
+ #
26
+ require 'irc/messages/message'
27
+ require 'irc/messages/invalid_message'
28
+
29
+ module IRC
30
+
31
+ module Messages
32
+
33
+ class ErrorNickNameInUseMessage < Message
34
+
35
+ CODE = ERR_NICKNAMEINUSE
36
+
37
+ attr_reader :nick_name_in_use
38
+
39
+ # Notify all connection listeners, that a user changed it's nick name.
40
+ def handle(context)
41
+
42
+ # Notify all connection listeners by calling their on_server_response method.
43
+ super(context)
44
+
45
+ # Notify all connection listeners by calling their on_nick method.
46
+ notify(context) do |connection_listener|
47
+ connection_listener.on_nick_already_in_use(context, nick_name_in_use)
48
+ end
49
+
50
+ end
51
+
52
+ protected
53
+
54
+ def parse(raw_message)
55
+
56
+ # Initialize the base message fields.
57
+ super(raw_message)
58
+
59
+ # Match the message against the message format.
60
+ match_data = Regexp.new('\\*\s+(\S+)\s+:?(\S+)\s*').match(raw_message)
61
+ raise InvalidMessage.new("Can't parse nick message. Invalid message format.") unless match_data
62
+
63
+ @nick_name_in_use = match_data[1]
64
+ end
65
+
66
+ end
67
+
68
+ end
69
+
70
+ end