blur 1.5.3 → 1.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,83 +1,137 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Blur
4
+ # The +Network+ module is to be percieved as an IRC network.
5
+ #
6
+ # Although the connection is a part of the network module, it is mainly used
7
+ # for network-related structures, such as {User}, {Channel} and {Command}.
4
8
  class Network
9
+ # +ConnectionError+ should only be triggered from within {Connection}.
5
10
  class ConnectionError < StandardError; end
6
11
 
7
- attr_accessor :options, :channels, :dialogues, :delegate, :connection
12
+ # @return [Hash] the network options.
13
+ attr_accessor :options
14
+ # @return [Array] the list of channels the client is in.
15
+ attr_accessor :channels
16
+ # @return [Array] the list of private messages the client remembers.
17
+ attr_accessor :dialogues
18
+ # @return [Client] the client delegate.
19
+ attr_accessor :delegate
20
+ # @return [Network::Connection] the connection instance.
21
+ attr_accessor :connection
8
22
 
9
- def connected?; @connection.established? end
23
+ # Check whether or not connection is established.
24
+ def connected?; @connection and @connection.established? end
10
25
 
11
- def host
12
- @options[:hostname]
13
- end
26
+ # Get the remote hostname.
27
+ #
28
+ # @return [String] the remote hostname.
29
+ def host; @options[:hostname] end
14
30
 
15
- def port
16
- @options[:port] ||= secure? ? 6697 : 6667
17
- end
31
+ # Get the remote port.
32
+ # If no port is specified, it returns 6697 if using a secure connection,
33
+ # returns 6667 otherwise.
34
+ #
35
+ # @return [Fixnum] the remote port
36
+ def port; @options[:port] ||= secure? ? 6697 : 6667 end
18
37
 
19
- def secure?
20
- @options[:secure] == true
21
- end
38
+ # Check to see if it's a secure connection.
39
+ def secure?; @options[:secure] == true end
22
40
 
23
- def initialize options = {}
41
+ # Check to see if FiSH encryption is enabled.
42
+ def fish?; not @options[:fish].nil? end
43
+
44
+ # Instantiates the network.
45
+ #
46
+ # @param [Hash] options the network options.
47
+ # @option options [String] :hostname the remote hostname.
48
+ # @option options [String] :nickname the nickname to represent.
49
+ # @option options [optional, String] :username the username to represent.
50
+ # @option options [optional, String] :realname the “real name” to represent.
51
+ # @option options [optional, String] :password the password for the network.
52
+ # @option options [optional, Fixnum] :port the remote port.
53
+ # @option options [optional, Boolean] :secure use a secure connection.
54
+ def initialize options
24
55
  @options = options
25
56
  @channels = []
26
57
 
27
58
  unless options[:nickname]
28
- raise ArgumentError, "nickname is missing from the network's option block"
59
+ raise ArgumentError, "nickname is missing from the networks option block"
29
60
  end
30
61
 
31
62
  @options[:username] ||= @options[:nickname]
32
63
  @options[:realname] ||= @options[:username]
33
64
  @options[:channels] ||= []
34
-
35
- @connection = Connection.new self, host, port
36
65
  end
37
66
 
67
+ # Send a message to a recipient.
68
+ #
69
+ # @param [String, #to_s] recipient the recipient.
70
+ # @param [String] message the message.
38
71
  def say recipient, message
72
+ if recipient.is_a? Channel and recipient.encrypted?
73
+ message = "+OK #{recipient.encryption.encrypt message}"
74
+ end
75
+
39
76
  transmit :PRIVMSG, recipient, message
40
77
  end
41
78
 
79
+ # Called when the network connection has enough data to form a command.
42
80
  def got_command command
43
81
  @delegate.got_command self, command
44
82
  end
45
83
 
84
+ # Find a channel by its name.
85
+ #
86
+ # @param [String] name the channel name.
87
+ # @return [Network::Channel] the matching channel, or nil.
46
88
  def channel_by_name name
47
89
  @channels.find { |channel| channel.name == name }
48
90
  end
49
91
 
92
+ # Find all instances of channels in which there is a user with the nick
93
+ # +nick+.
94
+ #
95
+ # @param [String] nick the nickname.
96
+ # @return [Array] a list of channels in which the user is located, or nil.
50
97
  def channels_with_user nick
51
98
  @channels.select { |channel| channel.user_by_nick nick }
52
99
  end
53
100
 
101
+ # Attempt to establish a connection and send initial data.
102
+ #
103
+ # @see Connection
54
104
  def connect
55
- @connection.establish
56
- @connection.enable_ssl OpenSSL::SSL::VERIFY_NONE if secure?
57
-
105
+ @connection = EventMachine.connect host, port, Connection, self
106
+ end
107
+
108
+ # Called when the connection was successfully established.
109
+ def connected!
58
110
  transmit :PASS, @options[:password] if @options[:password]
59
111
  transmit :NICK, @options[:nickname]
60
112
  transmit :USER, @options[:username], :void, :void, @options[:realname]
61
113
  end
62
114
 
115
+ # Terminate the connection and clear all channels and users.
63
116
  def disconnect
64
- @connection.terminate
117
+ @connection.close_connection_after_writing
65
118
 
66
119
  @channels.each { |channel| channel.users.clear }
67
120
  @channels.clear
68
121
  end
69
122
 
123
+ # Transmit a command to the server.
124
+ #
125
+ # @param [Symbol, String] name the command name.
126
+ # @param [...] arguments all the prepended parameters.
70
127
  def transmit name, *arguments
71
128
  command = Command.new name, arguments
72
129
  puts "-> #{inspect ^ :bold} | #{command}"
73
130
 
74
- @connection.transmit command
75
- end
76
-
77
- def transcieve
78
- @connection.transcieve
131
+ @connection.send_data "#{command}\r\n"
79
132
  end
80
133
 
134
+ # Convert it to a debug-friendly format.
81
135
  def to_s
82
136
  %{#<#{self.class.name} "#{host}":#{port}>}
83
137
  end
@@ -2,9 +2,34 @@
2
2
 
3
3
  module Blur
4
4
  class Network
5
+ # The +Channel+ class is used for encapsulating a channel and its properties.
6
+ #
7
+ # Users inside the channel is stored in the {#channels} attribute.
8
+ #
9
+ # Modes can be set for a channel, but Blur is not
10
+ # {http://www.irc.org/tech_docs/005.html ISupport}-compliant yet.
11
+ #
12
+ # @todo make so that channels *and* users belongs to the network, and not
13
+ # like now where the user belongs to the channel, resulting in multiple
14
+ # user instances.
5
15
  class Channel
6
- attr_accessor :name, :users, :topic, :modes, :network
16
+ # @return [String] the channels name.
17
+ attr_accessor :name
18
+ # @return [Array] a list of users in the channel.
19
+ attr_accessor :users
20
+ # @return [String] the channels topic.
21
+ attr_accessor :topic
22
+ # @return [String] all the modes set on the channel.
23
+ attr_accessor :modes
24
+ # @return [Network] a reference to the network.
25
+ attr_accessor :network
26
+ # @return [Encryption::Fish] the channel encryption, if any.
27
+ attr_accessor :encryption
7
28
 
29
+ # Check whether or not this is an encrypted channel.
30
+ def encrypted?; not @encryption.nil? end
31
+
32
+ # Instantiate a user with a nickname, a network and a user list.
8
33
  def initialize name, network = nil, users = []
9
34
  @name = name
10
35
  @users = users
@@ -14,6 +39,9 @@ module Blur
14
39
  users.each { |user| user.channel = self }
15
40
  end
16
41
 
42
+ # Merge the channels mode corresponding to the leading character (+ or -).
43
+ #
44
+ # @param [String] modes the modes to merge with.
17
45
  def merge_modes modes
18
46
  addition = true
19
47
 
@@ -29,22 +57,32 @@ module Blur
29
57
  end
30
58
  end
31
59
 
60
+ # Send a message to the channel.
61
+ #
62
+ # @param [String] message the message to send.
32
63
  def say message
33
64
  @network.say self, message
34
65
  end
35
66
 
67
+ # Find a user with +nick+ as its nickname.
68
+ #
69
+ # @param [String] nick the nickname to find the user of.
36
70
  def user_by_nick nick
37
71
  @users.find { |user| user.nick == nick }
38
72
  end
39
73
 
74
+ # Convert it to a debug-friendly format.
40
75
  def inspect
41
76
  %{#<#{self.class.name} @name=#{@name.inspect} @users=#{@users.inspect}}
42
77
  end
43
78
 
79
+ # Called when YAML attempts to save the object, which happens when a
80
+ # scripts cache contains this user and the script is unloaded.
44
81
  def to_yaml options = {}
45
82
  @name.to_yaml options
46
83
  end
47
84
 
85
+ # Get the channels name.
48
86
  def to_s
49
87
  @name
50
88
  end
@@ -2,11 +2,30 @@
2
2
 
3
3
  module Blur
4
4
  class Network
5
+ # The +Command+ class is used for encapsulating the command-lines.
6
+ #
7
+ # Blur is using regular-expression for parsing, this is to be replaced
8
+ # with a more native way of parsing, making it much, much easier on the
9
+ # processor.
5
10
  class Command
6
- attr_accessor :name, :params, :prefix
11
+ # @return [Symbol, Fixnum] the command name.
12
+ # @example
13
+ # 332 or :quit
14
+ attr_accessor :name
15
+ # @return [Array] a list of parameters.
16
+ attr_accessor :params
17
+ # @return [String] a hostname or a hostmask.
18
+ attr_accessor :prefix
7
19
 
20
+ # The arbitrary regex pattern.
8
21
  Pattern = /^(?:[:@]([^\s]+) )?([^\s]+)(?: ((?:[^:\s][^\s]* ?)*))?(?: ?:(.*))?$/
9
22
 
23
+ # Parse a line and encapsulate it as a Command.
24
+ #
25
+ # @return [Command] the parsed command.
26
+ # @example
27
+ # Command.parse "ChanServ!ChanServ@services.uplink.io MODE #uplink +v mk"
28
+ # # => #<Blur::Network::Command … >
10
29
  def self.parse data
11
30
  match = data.strip.match Pattern
12
31
  prefix, name, args, extra = match.captures
@@ -17,12 +36,23 @@ module Blur
17
36
  end
18
37
  end
19
38
 
39
+ # Get a parameter by its +index+.
20
40
  def [] index; @params[index] end
21
41
 
42
+ # Instantiate a command.
43
+ #
44
+ # @see Command.parse
22
45
  def initialize name, params = []
23
46
  @name, @params = name, params
24
47
  end
25
48
 
49
+ # Get the sender of the command.
50
+ #
51
+ # @note the return value is a string if it's a hostname, and an openstruct
52
+ # with the attributes #nickname, #username and #hostname if it's a
53
+ # hostmask.
54
+ #
55
+ # @return [String, OpenStruct] the sender.
26
56
  def sender
27
57
  return @sender if @sender
28
58
 
@@ -33,6 +63,9 @@ module Blur
33
63
  end
34
64
  end
35
65
 
66
+ # Convert it to an IRC-compliant line.
67
+ #
68
+ # @return [String] the command line.
36
69
  def to_s
37
70
  String.new.tap do |line|
38
71
  line << "#{prefix} " if prefix
@@ -1,72 +1,68 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Blur
4
- class Network
5
- class Connection
6
- def established?; @socket and not @socket.closed? end
7
-
8
- def initialize delegate, host = nil, port = nil
9
- @host = host
10
- @port = port
11
- @queue = []
12
- @buffer = ""
13
- @socket = nil
14
- @delegate = delegate
15
- end
16
-
17
- def establish
18
- @socket = TCPSocket.new @host, @port
4
+ class Network
5
+ # The +Connection+ class inherits the LineAndText protocol bundled with
6
+ # the eventmachine library.
7
+ #
8
+ # It merely acts as a receiving handler for all messages eventmachine throws
9
+ # at it through its lifetime.
10
+ #
11
+ # @see EventMachine::Protocols::LineAndTextProtocol
12
+ # @see EventMachine::Connection
13
+ class Connection < EM::Protocols::LineAndTextProtocol
14
+ # Check whether or not connection is established.
15
+ def established?; @connected == true end
16
+
17
+ # EventMachine instantiates this class, and then sends event messages to
18
+ # that instance.
19
+ def initialize network
20
+ @network = network
21
+ @connected = false
22
+
23
+ super
19
24
  end
20
25
 
21
- def enable_ssl verification
22
- @secure = true
26
+ # Called when a new connection is being set up, all we're going to use
27
+ # it for is to enable SSL/TLS on our connection.
28
+ def post_init
29
+ start_tls if @network.secure?
30
+ end
23
31
 
24
- context = OpenSSL::SSL::SSLContext.new
25
- context.set_params verify_mode: verification
32
+ # Called when a line was received, the connection sends it to the network
33
+ # delegate which then sends it to the client.
34
+ def receive_line line
35
+ command = Command.parse line
36
+ @network.got_command command
37
+ end
26
38
 
27
- sslsocket = OpenSSL::SSL::SSLSocket.new @socket, context
28
- sslsocket.sync = true
29
- sslsocket.connect_nonblock
39
+ # Called when the SSL handshake was completed with the remote server,
40
+ # the reason we tell the network that we're connected here is to ensure
41
+ # that the SSL/TLS encryption succeeded before we start talking nonsense
42
+ # to the server.
43
+ def ssl_handshake_completed
44
+ connected!
30
45
  end
31
-
32
- def terminate
33
- @socket = nil if @socket.close
34
- @buffer.clear
35
- @queue.clear
46
+
47
+ # Called once the connection is finally established.
48
+ def connection_completed
49
+ @network.connected! unless @network.secure?
36
50
  end
37
-
38
- def transmit command
39
- @queue.push command
51
+
52
+ # Called just as the connection is being terminated, either by remote or
53
+ # local.
54
+ def unbind
55
+ @connected = false
56
+
57
+ super
40
58
  end
41
-
42
- def transcieve
43
- readable, writable = IO.select [@socket], [@socket]
44
59
 
45
- # If the socket is ready to recieve, do so.
46
- if socket = readable.first
47
- @buffer.<< socket.read_nonblock 512
48
-
49
- while index = @buffer.index(?\n)
50
- command = Command.parse @buffer.slice! 0..index
51
- @delegate.got_command command
52
- end
53
- end
54
-
55
- # If it's ready to write, do that too until the outoing queue is empty.
56
- if socket = writable.first
57
- while command = @queue.shift
58
- socket.write_nonblock "#{command}\n"
59
- end
60
- end
60
+ private
61
+ # Called when connection has been established.
62
+ def connected!
63
+ @connected = true
61
64
 
62
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK
63
- puts "Insecure connection would block"
64
- rescue OpenSSL::SSL::SSLError
65
- if $!.message == "read would block"
66
- puts "Secure connection would block"
67
- else
68
- raise $!
69
- end
65
+ @network.connected!
70
66
  end
71
67
  end
72
68
  end
@@ -2,19 +2,42 @@
2
2
 
3
3
  module Blur
4
4
  class Network
5
+ # The +User+ class is used for encapsulating a user and its properties.
6
+ #
7
+ # The user owns a reference to its parent channel.
8
+ #
9
+ # Modes can be set for a user, but Blur is not
10
+ # {http://www.irc.org/tech_docs/005.html ISupport}-compliant yet.
11
+ #
12
+ # @todo make so that channels *and* users belongs to the network, and not
13
+ # like now where the user belongs to the channel, resulting in multiple
14
+ # user instances.
5
15
  class User
6
- attr_accessor :nick, :name, :host, :modes, :channel, :network
16
+ # @return [String] the users nickname.
17
+ attr_accessor :nick
18
+ # @return [String] the users username.
19
+ attr_accessor :name
20
+ # @return [String] the users hostname.
21
+ attr_accessor :host
22
+ # @return [String] all the modes set on the user.
23
+ attr_accessor :modes
24
+ # @return [Channel] a reference to the users channel.
25
+ attr_accessor :channel
26
+ # @return [Network] a reference to the network.
27
+ attr_accessor :network
7
28
 
8
- def self.map_mode name, character
9
- define_method(:"#{name}?") { @modes.include? character.to_s }
10
- end
11
-
12
- map_mode :admin, :a
13
- map_mode :voice, :v
14
- map_mode :owner, :q
15
- map_mode :operator, :o
16
- map_mode :half_operator, :h
29
+ # Check to see if the user is an admin (+a)
30
+ def admin?; @modes.include? "a" end
31
+ # Check to see if the user has voice (+v)
32
+ def voice?; @modes.include? "v" end
33
+ # Check to see if the user is the owner (+q)
34
+ def owner?; @modes.include? "q" end
35
+ # Check to see if the user is an operator (+o)
36
+ def operator?; @modes.include? "o" end
37
+ # Check to see if the user is an half-operator (+h)
38
+ def half_operator?; @modes.include? "h" end
17
39
 
40
+ # Instantiate a user with a nickname.
18
41
  def initialize nick
19
42
  @nick = nick
20
43
  @modes = ""
@@ -25,6 +48,9 @@ module Blur
25
48
  end
26
49
  end
27
50
 
51
+ # Merge the users mode corresponding to the leading character (+ or -).
52
+ #
53
+ # @param [String] modes the modes to merge with.
28
54
  def merge_modes modes
29
55
  addition = true
30
56
 
@@ -40,24 +66,32 @@ module Blur
40
66
  end
41
67
  end
42
68
 
69
+ # Send a private message to the user.
70
+ #
71
+ # @param [String] message the message to send.
43
72
  def say message
44
73
  @network.say self, message
45
74
  end
46
75
 
76
+ # Convert it to a debug-friendly format.
47
77
  def inspect
48
78
  %{#<#{self.class.name} @nick=#{@nick.inspect} @channel=#{@channel.name.inspect}>}
49
79
  end
50
80
 
81
+ # Called when YAML attempts to save the object, which happens when a
82
+ # scripts cache contains this user and the script is unloaded.
51
83
  def to_yaml options = {}
52
84
  @nick.to_yaml options
53
85
  end
54
86
 
87
+ # Get the users nickname.
55
88
  def to_s
56
89
  @nick
57
90
  end
58
91
 
59
92
  private
60
93
 
94
+ # Translate a nickname-prefix to a mode character.
61
95
  def prefix_to_mode prefix
62
96
  case prefix
63
97
  when '@' then 'o'