blur 1.5.3 → 1.6
Sign up to get free protection for your applications and to get access to all the features.
- data/library/blur.rb +27 -5
- data/library/blur/client.rb +56 -38
- data/library/blur/encryption.rb +17 -0
- data/library/blur/encryption/base64.rb +71 -0
- data/library/blur/encryption/fish.rb +80 -0
- data/library/blur/enhancements.rb +22 -0
- data/library/blur/handling.rb +108 -14
- data/library/blur/network.rb +78 -24
- data/library/blur/network/channel.rb +39 -1
- data/library/blur/network/command.rb +34 -1
- data/library/blur/network/connection.rb +53 -57
- data/library/blur/network/user.rb +44 -10
- data/library/blur/script.rb +35 -1
- data/library/blur/script/cache.rb +26 -1
- data/library/blur/script/messageparsing.rb +32 -1
- metadata +64 -14
data/library/blur/network.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
23
|
+
# Check whether or not connection is established.
|
24
|
+
def connected?; @connection and @connection.established? end
|
10
25
|
|
11
|
-
|
12
|
-
|
13
|
-
|
26
|
+
# Get the remote hostname.
|
27
|
+
#
|
28
|
+
# @return [String] the remote hostname.
|
29
|
+
def host; @options[:hostname] end
|
14
30
|
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
20
|
-
|
21
|
-
end
|
38
|
+
# Check to see if it's a secure connection.
|
39
|
+
def secure?; @options[:secure] == true end
|
22
40
|
|
23
|
-
|
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
|
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.
|
56
|
-
|
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.
|
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.
|
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
|
-
|
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
|
-
|
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
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
end
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
22
|
-
|
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
|
-
|
25
|
-
|
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
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
33
|
-
|
34
|
-
@
|
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
|
-
|
39
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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'
|