ircguerilla-irc 1.2.0 → 1.3.0

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