blur 1.5.3 → 1.6

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.
@@ -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'