masover-net-irc 0.9.3.1
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.
- data/MIT-LICENSE +20 -0
- data/README.markdown +25 -0
- data/Rakefile +10 -0
- data/bin/nicl +234 -0
- data/lib/net/hybrid.yml +14 -0
- data/lib/net/hyperion.yml +3 -0
- data/lib/net/irc.rb +825 -0
- data/lib/net/ircu.yml +54 -0
- data/lib/net/isupport.yml +2 -0
- data/lib/net/rfc1459.yml +134 -0
- data/lib/net/rfc2812.yml +29 -0
- data/net-irc.gemspec +33 -0
- metadata +64 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 unwwwired.net
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# Net::IRC
|
2
|
+
|
3
|
+
A ruby implementation of the IRC client protocol.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Run the following if you haven't already:
|
8
|
+
|
9
|
+
$ gem sources -a http://gems.github.com
|
10
|
+
|
11
|
+
Install the gem(s):
|
12
|
+
|
13
|
+
$ sudo gem install -r sbfaulkner-net-irc
|
14
|
+
|
15
|
+
## Example
|
16
|
+
|
17
|
+
The gem installs the nicl command-line client (pronounced "nickle", as in worth
|
18
|
+
about a...). This client is strictly intended as a sample usage of and testbed
|
19
|
+
for Net::IRC, and is not meant as an example of how to write a command-line
|
20
|
+
client.
|
21
|
+
|
22
|
+
## Legal
|
23
|
+
|
24
|
+
**Author:** S. Brent Faulkner <brentf@unwwwired.net>
|
25
|
+
**License:** Copyright © 2008 unwwwired.net, released under the MIT license
|
data/Rakefile
ADDED
data/bin/nicl
ADDED
@@ -0,0 +1,234 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
#
|
3
|
+
# nicl Copyright © 2007-2008 unwwwired.net
|
4
|
+
# Created by: S. Brent Faulkner (brentf@unwwwired.net) 2007-08-29
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
require 'net/irc'
|
9
|
+
|
10
|
+
module Ansi
|
11
|
+
RESET = 0
|
12
|
+
BOLD = 1
|
13
|
+
DIM = 2
|
14
|
+
UNDERSCORE = 4
|
15
|
+
BLINK = 5
|
16
|
+
REVERSE = 7
|
17
|
+
HIDDEN = 8
|
18
|
+
|
19
|
+
BLACK = 0
|
20
|
+
RED = 1
|
21
|
+
GREEN = 2
|
22
|
+
YELLOW = 3
|
23
|
+
BLUE = 4
|
24
|
+
MAGENTA = 5
|
25
|
+
CYAN = 6
|
26
|
+
WHITE = 7
|
27
|
+
|
28
|
+
def esc(*attrs)
|
29
|
+
"\033[#{attrs.join(';')}m"
|
30
|
+
end
|
31
|
+
|
32
|
+
def fg(colour)
|
33
|
+
30 + colour
|
34
|
+
end
|
35
|
+
|
36
|
+
def bg(colour)
|
37
|
+
40 + colour
|
38
|
+
end
|
39
|
+
|
40
|
+
def highlight(text, *attrs)
|
41
|
+
"#{esc(*attrs)}#{text}#{esc(RESET)}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
include Ansi
|
46
|
+
|
47
|
+
begin
|
48
|
+
require 'termios'
|
49
|
+
# real implementation for toggling echo
|
50
|
+
def echo(on = true)
|
51
|
+
oldt = Termios.tcgetattr(STDIN)
|
52
|
+
newt = oldt.dup
|
53
|
+
newt.lflag &= ~Termios::ECHO
|
54
|
+
Termios.tcsetattr(STDIN, Termios::TCSANOW, newt)
|
55
|
+
|
56
|
+
# if no block is provided, return the original echo setting
|
57
|
+
return (oldt.lflag & Termios::ECHO) == Termios::ECHO unless block_given?
|
58
|
+
|
59
|
+
# otherwise yield to the block and restore the original echo setting
|
60
|
+
ret = yield
|
61
|
+
Termios.tcsetattr(STDIN, Termios::TCSANOW, oldt)
|
62
|
+
ret
|
63
|
+
end
|
64
|
+
rescue LoadError
|
65
|
+
# minimal stub in case Termios is not installed
|
66
|
+
def echo(on = true)
|
67
|
+
return true unless block_given?
|
68
|
+
yield
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def prompt(text, hidden = false)
|
73
|
+
echo(false) do
|
74
|
+
print text
|
75
|
+
line = STDIN.readline.chomp
|
76
|
+
print "\n"
|
77
|
+
line
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# TODO: a bit backwards... should probably be "Net::IRC.logger = logger"
|
82
|
+
logger = Net::IRC.logger
|
83
|
+
|
84
|
+
Net::IRC.logger.level = Logger::DEBUG
|
85
|
+
Net::IRC.logger.datetime_format = "%Y/%m/%d %H:%M:%S"
|
86
|
+
|
87
|
+
Thread.abort_on_exception = true
|
88
|
+
|
89
|
+
server = ARGV[0] || "irc.freenode.net"
|
90
|
+
port = ARGV[1] && ARGV[1].to_i || 6667
|
91
|
+
user = ARGV[2] || prompt('User: ')
|
92
|
+
full_name = ARGV[3] || ARGV[2] || prompt('Full name: ')
|
93
|
+
password = ARGV[4] || prompt('Password: ', true)
|
94
|
+
|
95
|
+
Net::IRC.start user, password, full_name, server, port do |irc|
|
96
|
+
Thread.new do
|
97
|
+
irc.each do |message|
|
98
|
+
case message
|
99
|
+
# TODO: required = VERSION, PING, CLIENTINFO, ACTION
|
100
|
+
# TODO: handle internally... probably true for most CTCP requests
|
101
|
+
when Net::IRC::CTCPVersion
|
102
|
+
irc.ctcp_version(message.source, "net-irc simple-client", Net::IRC::VERSION, PLATFORM, "http://www.github.com/sbfaulkner/net-irc")
|
103
|
+
|
104
|
+
when Net::IRC::CTCPAction
|
105
|
+
puts "#{highlight(message.source, BOLD, fg(YELLOW))} #{highlight(message.target, BOLD, fg(GREEN))}: #{highlight(message.text, BOLD)}"
|
106
|
+
|
107
|
+
when Net::IRC::CTCPPing
|
108
|
+
irc.ctcp_ping(message.source, message.arg)
|
109
|
+
|
110
|
+
when Net::IRC::CTCPTime
|
111
|
+
irc.ctcp_time(message.source)
|
112
|
+
|
113
|
+
when Net::IRC::CTCP
|
114
|
+
puts highlight("Unhandled CTCP REQUEST: #{message.class} (#{message.keyword})", BOLD, fg(RED))
|
115
|
+
|
116
|
+
when Net::IRC::Join
|
117
|
+
puts "#{highlight(message.prefix.nickname, BOLD, fg(YELLOW))} joined #{highlight(message.channels.first, BOLD, fg(GREEN))}."
|
118
|
+
|
119
|
+
when Net::IRC::Part
|
120
|
+
if message.text && ! message.text.empty?
|
121
|
+
puts "#{highlight(message.prefix.nickname, BOLD, fg(YELLOW))} has left #{highlight(message.channels.first, BOLD, fg(GREEN))} (#{message.text})."
|
122
|
+
else
|
123
|
+
puts "#{highlight(message.prefix.nickname, BOLD, fg(YELLOW))} has left #{highlight(message.channels.first, BOLD, fg(GREEN))}."
|
124
|
+
end
|
125
|
+
|
126
|
+
when Net::IRC::Mode
|
127
|
+
# TODO: handle internally
|
128
|
+
puts highlight("#{message.channel} mode changed #{message.modes}", fg(BLUE))
|
129
|
+
|
130
|
+
when Net::IRC::Quit
|
131
|
+
if message.text && ! message.text.empty?
|
132
|
+
puts "#{highlight(message.prefix.nickname, BOLD, fg(YELLOW))} has quit (#{message.text})."
|
133
|
+
else
|
134
|
+
puts "#{highlight(message.prefix.nickname, BOLD, fg(YELLOW))} has quit."
|
135
|
+
end
|
136
|
+
|
137
|
+
when Net::IRC::Notice
|
138
|
+
puts highlight(message.text, fg(CYAN))
|
139
|
+
|
140
|
+
when Net::IRC::Privmsg
|
141
|
+
puts "#{highlight(message.prefix.nickname, BOLD, fg(YELLOW))} #{highlight(message.target, BOLD, fg(GREEN))}: #{highlight(message.text, BOLD)}"
|
142
|
+
|
143
|
+
when Net::IRC::Nick
|
144
|
+
puts "#{highlight(message.prefix.nickname, BOLD)} is now #{highlight(message.nickname, BOLD, fg(YELLOW))}"
|
145
|
+
|
146
|
+
when Net::IRC::ErrNicknameinuse
|
147
|
+
irc.nick message.nickname.sub(/\d*$/) { |n| n.to_i + 1 }
|
148
|
+
|
149
|
+
when Net::IRC::ErrNeedreggednick
|
150
|
+
irc.privmsg('nickserv', 'help')
|
151
|
+
|
152
|
+
when Net::IRC::RplLoggedin, Net::IRC::RplLoggedout
|
153
|
+
puts highlight(message.text, fg(GREEN))
|
154
|
+
|
155
|
+
when Net::IRC::Error
|
156
|
+
puts highlight("Unhandled ERROR: #{message.class} (#{message.command})", BOLD, fg(RED))
|
157
|
+
|
158
|
+
when Net::IRC::RplWelcome, Net::IRC::RplYourhost, Net::IRC::RplCreated
|
159
|
+
puts message.text
|
160
|
+
|
161
|
+
when Net::IRC::RplLuserclient, Net::IRC::RplLuserme, Net::IRC::RplLocalusers, Net::IRC::RplGlobalusers, Net::IRC::RplStatsconn
|
162
|
+
puts highlight(message.text, fg(BLUE))
|
163
|
+
|
164
|
+
when Net::IRC::RplLuserop, Net::IRC::RplLuserchannels
|
165
|
+
puts highlight("#{message.count} #{message.text}", fg(BLUE))
|
166
|
+
|
167
|
+
when Net::IRC::RplIsupport
|
168
|
+
# TODO: handle internally... parse into capabilities collection
|
169
|
+
|
170
|
+
when Net::IRC::RplMyinfo
|
171
|
+
when Net::IRC::RplMotdstart
|
172
|
+
|
173
|
+
when Net::IRC::RplTopic
|
174
|
+
# TODO: handle internally
|
175
|
+
puts "#{highlight(message.channel, BOLD, fg(GREEN))}: #{message.text}"
|
176
|
+
|
177
|
+
when Net::IRC::RplTopicwhotime
|
178
|
+
# TODO: handle internally
|
179
|
+
puts "#{highlight(message.channel, BOLD, fg(GREEN))}: #{message.nickname} #{message.time.strftime("%Y/%m/%d %H:%M:%S")}"
|
180
|
+
|
181
|
+
when Net::IRC::RplNamreply
|
182
|
+
# TODO: handle internally
|
183
|
+
puts "#{highlight(message.channel, BOLD, fg(GREEN))}: #{message.names.join(', ')}"
|
184
|
+
|
185
|
+
when Net::IRC::RplEndofnames
|
186
|
+
# TODO: handle internally
|
187
|
+
|
188
|
+
when Net::IRC::RplMotd
|
189
|
+
puts message.text.sub(/^- /,'')
|
190
|
+
|
191
|
+
when Net::IRC::RplEndofmotd
|
192
|
+
puts ""
|
193
|
+
|
194
|
+
when Net::IRC::Reply
|
195
|
+
puts highlight("Unhandled REPLY: #{message.class} (#{message.command})", BOLD, fg(RED))
|
196
|
+
|
197
|
+
when Net::IRC::Message
|
198
|
+
puts highlight("Unhandled MESSAGE: #{message.class} (#{message.command})", BOLD, fg(RED))
|
199
|
+
|
200
|
+
else
|
201
|
+
raise IOError, "unknown class #{message.class}"
|
202
|
+
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
while line = STDIN.readline
|
208
|
+
scanner = StringScanner.new(line.chomp)
|
209
|
+
if command = scanner.scan(/\/(\S+)\s*/) && scanner[1]
|
210
|
+
case command.upcase
|
211
|
+
when 'JOIN'
|
212
|
+
# TODO: validate arguments... support for password... etc.
|
213
|
+
irc.join scanner.rest
|
214
|
+
|
215
|
+
when 'MSG'
|
216
|
+
# TODO: validate arguments... support for password... etc.
|
217
|
+
scanner.scan(/(\S+)\s+(.*)/)
|
218
|
+
irc.privmsg(scanner[1], scanner[2])
|
219
|
+
when 'PART'
|
220
|
+
# TODO: validate arguments... support for password... etc.
|
221
|
+
irc.part scanner.rest
|
222
|
+
|
223
|
+
when 'QUIT'
|
224
|
+
break
|
225
|
+
else
|
226
|
+
puts highlight("Unknown COMMAND: #{command}", BOLD, fg(RED))
|
227
|
+
end
|
228
|
+
elsif scanner.scan(/(\S+)\s+(.*)/)
|
229
|
+
irc.privmsg(scanner[1], scanner[2])
|
230
|
+
else
|
231
|
+
# TODO: error? need a concept of a current room
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
data/lib/net/hybrid.yml
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
---
|
2
|
+
14: RPL_YOURCOOKIE
|
3
|
+
220: RPL_STATSPLINE
|
4
|
+
224: RPL_STATSFLINE
|
5
|
+
225: RPL_STATSDLINE
|
6
|
+
246: RPL_STATSULINE
|
7
|
+
247: RPL_STATSXLINE
|
8
|
+
249: RPL_STATSDEBUG
|
9
|
+
265: RPL_LOCALUSERS
|
10
|
+
266: RPL_GLOBALUSERS
|
11
|
+
385: RPL_NOTOPERANYMORE
|
12
|
+
479: ERR_BADCHANNAME
|
13
|
+
484: ERR_DESYNC
|
14
|
+
503: ERR_GHOSTEDCLIENT
|
data/lib/net/irc.rb
ADDED
@@ -0,0 +1,825 @@
|
|
1
|
+
require 'net/protocol'
|
2
|
+
require 'strscan'
|
3
|
+
require 'yaml'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
module Net
|
7
|
+
class IRC < Protocol
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def logger
|
12
|
+
@logger ||= Logger.new('net-irc.log')
|
13
|
+
end
|
14
|
+
|
15
|
+
def logger=(logger)
|
16
|
+
@logger = logger
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
USER_MODE_DEFAULT = 0
|
21
|
+
USER_MODE_RECEIVE_WALLOPS = 4
|
22
|
+
USER_MODE_INVISIBLE = 8
|
23
|
+
|
24
|
+
PORT_DEFAULT = 6667
|
25
|
+
|
26
|
+
VERSION = "0.9.3"
|
27
|
+
|
28
|
+
class CTCP
|
29
|
+
attr_accessor :source, :target, :keyword, :parameters
|
30
|
+
|
31
|
+
CTCP_REGEX = /\001(.*?)\001/
|
32
|
+
|
33
|
+
def initialize(keyword, *parameters)
|
34
|
+
@source = nil
|
35
|
+
@keyword = keyword
|
36
|
+
@parameters = parameters
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
str = "\001#{keyword}"
|
41
|
+
str << parameters.collect { |p| " #{p}"}.join
|
42
|
+
str << "\001"
|
43
|
+
end
|
44
|
+
|
45
|
+
class << self
|
46
|
+
def parse(text)
|
47
|
+
[
|
48
|
+
text.gsub(CTCP_REGEX, ''),
|
49
|
+
text.scan(CTCP_REGEX).flatten.collect do |message|
|
50
|
+
parameters = message.split(' ')
|
51
|
+
case keyword = parameters.shift
|
52
|
+
when 'VERSION'
|
53
|
+
CTCPVersion.new(*parameters)
|
54
|
+
when 'PING'
|
55
|
+
CTCPPing.new(*parameters)
|
56
|
+
when 'CLIENTINFO'
|
57
|
+
CTCPClientinfo.new(*parameters)
|
58
|
+
when 'ACTION'
|
59
|
+
CTCPAction.new(*parameters)
|
60
|
+
when 'FINGER'
|
61
|
+
CTCPFinger.new(*parameters)
|
62
|
+
when 'TIME'
|
63
|
+
CTCPTime.new(*parameters)
|
64
|
+
when 'DCC'
|
65
|
+
CTCPDcc.new(*parameters)
|
66
|
+
when 'ERRMSG'
|
67
|
+
CTCPErrmsg.new(*parameters)
|
68
|
+
when 'PLAY'
|
69
|
+
CTCPPlay.new(*parameters)
|
70
|
+
else
|
71
|
+
CTCP.new(keyword, *parameters)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class CTCPVersion < CTCP
|
80
|
+
def initialize(*parameters)
|
81
|
+
super('VERSION', parameters)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class CTCPPing < CTCP
|
86
|
+
attr_accessor :arg
|
87
|
+
|
88
|
+
def initialize(arg = nil)
|
89
|
+
if @arg = arg
|
90
|
+
super('PING', arg)
|
91
|
+
else
|
92
|
+
super('PING')
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class CTCPClientinfo < CTCP
|
98
|
+
def initialize(*keywords)
|
99
|
+
super('CLIENTINFO', *keywords)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class CTCPAction < CTCP
|
104
|
+
attr_accessor :text
|
105
|
+
|
106
|
+
def initialize(*parameters)
|
107
|
+
@text = parameters.join(' ')
|
108
|
+
|
109
|
+
super('ACTION', *parameters)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class CTCPFinger < CTCP
|
114
|
+
def initialize(text = nil)
|
115
|
+
super('FINGER', text)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class CTCPTime < CTCP
|
120
|
+
attr_accessor :time
|
121
|
+
def initialize(*parameters)
|
122
|
+
@time = parameters.join(' ')
|
123
|
+
|
124
|
+
super('TIME', *parameters)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class CTCPDcc < CTCP
|
129
|
+
def initialize(type, protocol, ip, port, *args)
|
130
|
+
super('DCC', type, protocol, ip, port, *args)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
class CTCPErrmsg < CTCP
|
135
|
+
def initialize(keyword, text = nil)
|
136
|
+
super('ERRMSG', keyword, text)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class CTCPPlay < CTCP
|
141
|
+
def initialize(filename, mime_type)
|
142
|
+
super('PLAY', filename, mime_type)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
class Message
|
147
|
+
attr_reader :prefix
|
148
|
+
attr_accessor :command, :parameters
|
149
|
+
|
150
|
+
COMMAND_MAPS = %w(rfc1459 rfc2812 isupport hybrid ircu hyperion)
|
151
|
+
|
152
|
+
def initialize(*args)
|
153
|
+
raise ArgumentError, "wrong number of arguments (#{args.size} for 2)" if args.size < 2
|
154
|
+
|
155
|
+
# puts ">>>>> args=#{args.inspect}"
|
156
|
+
|
157
|
+
@prefix, @command, *parameters = args
|
158
|
+
# puts ">>>>> @prefix=#{@prefix.inspect}, command=#{@command.inspect}, parameters=#{parameters.inspect}"
|
159
|
+
@parameters = Array(parameters)
|
160
|
+
end
|
161
|
+
|
162
|
+
class Prefix
|
163
|
+
attr_accessor :prefix
|
164
|
+
|
165
|
+
PREFIX_REGEX = /^([^!@]+)(?:(?:!([^@]+))?@(.+))?/
|
166
|
+
|
167
|
+
def initialize(prefix)
|
168
|
+
@prefix = prefix
|
169
|
+
@matches = prefix.match(PREFIX_REGEX)
|
170
|
+
end
|
171
|
+
|
172
|
+
def server
|
173
|
+
@prefix
|
174
|
+
end
|
175
|
+
|
176
|
+
def nickname
|
177
|
+
@matches[1]
|
178
|
+
end
|
179
|
+
|
180
|
+
def user
|
181
|
+
@matches[2]
|
182
|
+
end
|
183
|
+
|
184
|
+
def host
|
185
|
+
@matches[3]
|
186
|
+
end
|
187
|
+
|
188
|
+
def to_s
|
189
|
+
@prefix
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def prefix=(value)
|
194
|
+
@prefix = value && Prefix.new(value)
|
195
|
+
end
|
196
|
+
|
197
|
+
def prefix?
|
198
|
+
@prefix
|
199
|
+
end
|
200
|
+
|
201
|
+
def to_s
|
202
|
+
# puts ">>>>> prefix=#{prefix.inspect}, command=#{command.inspect}, parameters=#{parameters.inspect}"
|
203
|
+
str = prefix ? ":#{prefix} " : ""
|
204
|
+
str << command
|
205
|
+
if ! parameters.empty?
|
206
|
+
parameters[0..-2].each do |param|
|
207
|
+
str << " #{param}"
|
208
|
+
end
|
209
|
+
if parameters.last =~ /^:| /
|
210
|
+
str << " :#{parameters.last}"
|
211
|
+
else
|
212
|
+
str << " #{parameters.last}"
|
213
|
+
end
|
214
|
+
end
|
215
|
+
str
|
216
|
+
end
|
217
|
+
|
218
|
+
def write(socket)
|
219
|
+
line = to_s
|
220
|
+
IRC.logger.debug ">>>>> #{line.inspect}"
|
221
|
+
socket.writeline(line)
|
222
|
+
end
|
223
|
+
|
224
|
+
class << self
|
225
|
+
def parse(line)
|
226
|
+
scanner = StringScanner.new(line)
|
227
|
+
|
228
|
+
prefix = scanner.scan(/:([^ ]+) /) && scanner[1]
|
229
|
+
command = scanner.scan(/[[:alpha:]]+|\d{3}/)
|
230
|
+
params = []
|
231
|
+
14.times do
|
232
|
+
break if ! scanner.scan(/ ([^ :][^ ]*)/)
|
233
|
+
params << scanner[1]
|
234
|
+
end
|
235
|
+
params << scanner[1] if scanner.scan(/ :(.+)/)
|
236
|
+
|
237
|
+
message = nil
|
238
|
+
command_name = command.to_i > 0 ? command_for_number(command.to_i) : command
|
239
|
+
|
240
|
+
if command_name
|
241
|
+
message_type = "#{command_name.downcase.split('_').collect { |w| w.capitalize }.join}"
|
242
|
+
if Net::IRC.const_defined?(message_type)
|
243
|
+
message_type = Net::IRC.const_get(message_type)
|
244
|
+
message = message_type.new(*params)
|
245
|
+
message.prefix = prefix
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
message ||= Message.new(prefix, command_name || command, *params)
|
250
|
+
end
|
251
|
+
|
252
|
+
def command_for_number(number)
|
253
|
+
@command_map ||= COMMAND_MAPS.inject({}) { |merged,map| merged.merge!(YAML.load_file("#{File.dirname(__FILE__)}/#{map}.yml")) }
|
254
|
+
@command_map[number]
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
class Reply < Message
|
260
|
+
attr_accessor :text
|
261
|
+
|
262
|
+
def initialize(prefix, command, *args)
|
263
|
+
args.pop unless @text = args.last
|
264
|
+
super(nil, command, *args)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
class ReplyWithTarget < Reply
|
269
|
+
attr_accessor :target
|
270
|
+
|
271
|
+
def initialize(prefix, command, target, *args)
|
272
|
+
@target = target
|
273
|
+
super(prefix, command, @target, *args)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
# 001 <target> :Welcome to the Internet Relay Network <nick>!<user>@<host>
|
278
|
+
class RplWelcome < ReplyWithTarget
|
279
|
+
def initialize(target, text)
|
280
|
+
super(nil, 'RPL_WELCOME', target, text)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
# 002 <target> :Your host is <servername>, running version <ver>
|
285
|
+
class RplYourhost < Reply
|
286
|
+
def initialize(target, text)
|
287
|
+
super(nil, 'RPL_YOURHOST', target, text)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
# 003 <target> :This server was created <date>
|
292
|
+
class RplCreated < ReplyWithTarget
|
293
|
+
def initialize(target, text)
|
294
|
+
super(nil, 'RPL_CREATED', target, text)
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
# 004 <target> <servername> <version> <available user modes> <available channel modes>
|
299
|
+
class RplMyinfo < ReplyWithTarget
|
300
|
+
attr_accessor :servername, :version, :available_user_modes, :available_channel_modes
|
301
|
+
|
302
|
+
def initialize(target, servername, version, available_user_modes, available_channel_modes)
|
303
|
+
@servername = servername
|
304
|
+
@version = version
|
305
|
+
@available_user_modes = available_user_modes
|
306
|
+
@available_channel_modes = available_channel_modes
|
307
|
+
|
308
|
+
super(nil, 'RPL_MYINFO', target, servername, version, available_user_modes, available_channel_modes, nil)
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
# 005 <target> ( [ "-" ] <parameter> ) | ( <parameter> "=" [ <value> ] ) *( ( [ "-" ] <parameter> ) | ( <parameter> "=" [ <value> ] ) ) :are supported by this server
|
313
|
+
class RplIsupport < ReplyWithTarget
|
314
|
+
class Parameter
|
315
|
+
PARAMETER_REGEX = /^(-)?([[:alnum:]]{1,20})(?:=(.*))?/
|
316
|
+
|
317
|
+
def initialize(param)
|
318
|
+
@param = param
|
319
|
+
@matches = param.match(PARAMETER_REGEX)
|
320
|
+
end
|
321
|
+
|
322
|
+
def name
|
323
|
+
@matches[2]
|
324
|
+
end
|
325
|
+
|
326
|
+
def value
|
327
|
+
@matches[3] || @matches[1].nil?
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def initialize(target, *args)
|
332
|
+
raise ArgumentError, "wrong number of arguments (#{1 + args.size} for 3)" if args.size < 2
|
333
|
+
|
334
|
+
@parameters = args[0..-2].collect { |p| Parameter.new(p) }
|
335
|
+
|
336
|
+
super(nil, 'RPL_ISUPPORT', target, *args)
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
# 250 <target> :<text>
|
341
|
+
class RplStatsconn < ReplyWithTarget
|
342
|
+
def initialize(target, text)
|
343
|
+
super(nil, 'RPL_LSTATSCONN', target, text)
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
# 251 <target> :<text>
|
348
|
+
class RplLuserclient < ReplyWithTarget
|
349
|
+
def initialize(target, text)
|
350
|
+
super(nil, 'RPL_LUSERCLIENT', target, text)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
class ReplyWithCount < ReplyWithTarget
|
355
|
+
attr_accessor :count
|
356
|
+
|
357
|
+
def initialize(prefix, command, target, count, text)
|
358
|
+
@count = count
|
359
|
+
super(prefix, command, target, count, text)
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
# 252 <target> <count> :<text>
|
364
|
+
class RplLuserop < ReplyWithCount
|
365
|
+
def initialize(target, count, text)
|
366
|
+
super(nil, 'RPL_LUSEROP', target, count, text)
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
# 254 <target> <count> :<text>
|
371
|
+
class RplLuserchannels < ReplyWithCount
|
372
|
+
def initialize(target, count, text)
|
373
|
+
super(nil, 'RPL_LUSERCHANNELS', target, count, text)
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
# 255 <target> :<text>
|
378
|
+
class RplLuserme < ReplyWithTarget
|
379
|
+
def initialize(target, text)
|
380
|
+
super(nil, 'RPL_LUSERME', target, text)
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
# 265 <target> :<text>
|
385
|
+
class RplLocalusers < ReplyWithTarget
|
386
|
+
def initialize(target, text)
|
387
|
+
super(nil, 'RPL_LOCALUSERS', target, text)
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
# 266 <target> :<text>
|
392
|
+
class RplGlobalusers < ReplyWithTarget
|
393
|
+
def initialize(target, text)
|
394
|
+
super(nil, 'RPL_GLOBALUSERS', target, text)
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
class ReplyWithChannel < ReplyWithTarget
|
399
|
+
attr_accessor :channel
|
400
|
+
|
401
|
+
def initialize(prefix, command, target, channel, *args)
|
402
|
+
@channel = channel
|
403
|
+
super(prefix, command, target, @channel, *args)
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
# 332 <target> <target> <channel> :<text>
|
408
|
+
class RplTopic < ReplyWithChannel
|
409
|
+
def initialize(target, channel, text)
|
410
|
+
super(nil, 'RPL_TOPIC', target, channel, text)
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
# 333 <target> <channel> <nickname> <time>
|
415
|
+
class RplTopicwhotime < ReplyWithChannel
|
416
|
+
attr_accessor :nickname, :time
|
417
|
+
|
418
|
+
def initialize(target, channel, nickname, time)
|
419
|
+
@nickname = nickname
|
420
|
+
@time = Time.at(time.to_i)
|
421
|
+
super(nil, 'RPL_TOPICWHOTIME', target, channel, nickname, time, nil)
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
# 353 <target> ( "=" | "*" | "@" ) <channel> :[ "@" | "+" ] <nick> *( " " [ "@" / "+" ] <nick> )
|
426
|
+
class RplNamreply < Reply
|
427
|
+
attr_accessor :channel_type, :channel, :names
|
428
|
+
|
429
|
+
def initialize(target, channel_type, channel, names)
|
430
|
+
@channel_type = channel_type
|
431
|
+
@channel = channel
|
432
|
+
@names = names.split(' ')
|
433
|
+
super(nil, 'RPL_NAMREPLY', target, @channel_type, @channel, names, nil)
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
# 366 <target> <channel> :End of /NAMES list
|
438
|
+
class RplEndofnames < ReplyWithChannel
|
439
|
+
def initialize(target, channel, text)
|
440
|
+
super(nil, 'RPL_ENDOFNAMES', target, channel, text)
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
# 372 <target> :- <text>
|
445
|
+
class RplMotd < Reply
|
446
|
+
def initialize(target, text)
|
447
|
+
super(nil, 'RPL_MOTD', target, text)
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
# 375 <target> :- <server> Message of the day -
|
452
|
+
class RplMotdstart < Reply
|
453
|
+
def initialize(target, text)
|
454
|
+
super(nil, 'RPL_MOTDSTART', target, text)
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
# 376 <target> :End of MOTD command.
|
459
|
+
class RplEndofmotd < Reply
|
460
|
+
def initialize(target, text)
|
461
|
+
super(nil, 'RPL_ENDOFMOTD', target, text)
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
class Error < ReplyWithTarget
|
466
|
+
end
|
467
|
+
|
468
|
+
# 422 <target> <nickname> :Nickname is already in use.
|
469
|
+
class ErrNicknameinuse < Error
|
470
|
+
attr_accessor :nickname
|
471
|
+
|
472
|
+
def initialize(target, nickname, text)
|
473
|
+
@nickname = nickname
|
474
|
+
|
475
|
+
super(nil, 'ERR_NICKNAMEINUSE', target, @nickname, text)
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
# 477 <target> <channel> :<text>
|
480
|
+
class ErrNeedreggednick < Error
|
481
|
+
attr_accessor :channel
|
482
|
+
|
483
|
+
def initialize(target, channel, text)
|
484
|
+
@channel = channel
|
485
|
+
|
486
|
+
super(nil, 'ERR_NEEDREGGEDNICK', target, channel, text)
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
# 901 <target> <id> <username> <hostname> :You are now logged in. (id <id>, username <username>, hostname <hostname>)
|
491
|
+
class ReplyWithRegistryParameters < ReplyWithTarget
|
492
|
+
attr_accessor :id, :username, :hostname
|
493
|
+
|
494
|
+
def initialize(prefix, command, target, id, username, hostname, text)
|
495
|
+
@id = id
|
496
|
+
@username = username
|
497
|
+
@hostname = hostname
|
498
|
+
|
499
|
+
super(prefix, command, target, id, username, hostname, text)
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
# 901 <target> <id> <username> <hostname> :You are now logged in. (id <id>, username <username>, hostname <hostname>)
|
504
|
+
class RplLoggedin < ReplyWithRegistryParameters
|
505
|
+
def initialize(target, id, username, hostname, text)
|
506
|
+
super(nil, 'RPL_LOGGED_IN', target, id, username, hostname, text)
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
510
|
+
# 902 <target> <id> <username> <hostname> :You are now logged out. (id <id>, username <username>, hostname <hostname>)
|
511
|
+
class RplLoggedout < ReplyWithRegistryParameters
|
512
|
+
def initialize(target, id, username, hostname, text)
|
513
|
+
super(nil, 'RPL_LOGGED_OUT', target, id, username, hostname, text)
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
|
518
|
+
# JOIN ( <channel> *( "," <channel> ) [ <key> *( "," <key> ) ] )
|
519
|
+
# / "0"
|
520
|
+
class Join < Message
|
521
|
+
attr_accessor :channels, :keys
|
522
|
+
|
523
|
+
def initialize(channels, keys = nil)
|
524
|
+
@channels = channels.split(',')
|
525
|
+
@keys = keys && keys.split(',')
|
526
|
+
|
527
|
+
if keys
|
528
|
+
super(nil, 'JOIN', channels, keys)
|
529
|
+
else
|
530
|
+
super(nil, 'JOIN', channels)
|
531
|
+
end
|
532
|
+
end
|
533
|
+
end
|
534
|
+
|
535
|
+
# MODE <channel> *( ( "-" / "+" ) *<modes> *<modeparams> )
|
536
|
+
class Mode < Message
|
537
|
+
attr_accessor :channel, :modes
|
538
|
+
|
539
|
+
def initialize(channel, *parameters)
|
540
|
+
@channel = channel
|
541
|
+
@modes = parameters.join(' ')
|
542
|
+
|
543
|
+
super(nil, 'MODE', channel, *parameters)
|
544
|
+
end
|
545
|
+
end
|
546
|
+
|
547
|
+
# NICK <nickname>
|
548
|
+
class Nick < Message
|
549
|
+
attr_accessor :nickname
|
550
|
+
|
551
|
+
def initialize(nickname)
|
552
|
+
@nickname = nickname
|
553
|
+
|
554
|
+
super(nil, 'NICK', @nickname)
|
555
|
+
end
|
556
|
+
end
|
557
|
+
|
558
|
+
# NOTICE <target> <text>
|
559
|
+
class Notice < Message
|
560
|
+
attr_accessor :target, :text, :ctcp
|
561
|
+
|
562
|
+
def initialize(target, text)
|
563
|
+
@target = target
|
564
|
+
@text, @ctcp = CTCP.parse(text)
|
565
|
+
|
566
|
+
super(nil, 'NOTICE', @target, text)
|
567
|
+
end
|
568
|
+
end
|
569
|
+
|
570
|
+
# PART <channel> *( "," <channel> ) [ <text> ]
|
571
|
+
class Part < Message
|
572
|
+
attr_accessor :channels, :text
|
573
|
+
|
574
|
+
def initialize(channels, message = nil)
|
575
|
+
@channels = channels.split(',')
|
576
|
+
|
577
|
+
if message
|
578
|
+
super(nil, 'PART', channels, message)
|
579
|
+
else
|
580
|
+
super(nil, 'PART', channels)
|
581
|
+
end
|
582
|
+
end
|
583
|
+
end
|
584
|
+
|
585
|
+
# PASS <password>
|
586
|
+
class Pass < Message
|
587
|
+
attr_accessor :password
|
588
|
+
|
589
|
+
def initialize(password)
|
590
|
+
@password = password
|
591
|
+
|
592
|
+
super(nil, 'PASS', @password)
|
593
|
+
end
|
594
|
+
end
|
595
|
+
|
596
|
+
# PING <server> [ <target> ]
|
597
|
+
class Ping < Message
|
598
|
+
attr_accessor :server, :target
|
599
|
+
|
600
|
+
def initialize(server, target = nil)
|
601
|
+
@server = server
|
602
|
+
|
603
|
+
if @target = target
|
604
|
+
super(nil, 'PING', @server, @target)
|
605
|
+
else
|
606
|
+
super(nil, 'PING', @server)
|
607
|
+
end
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
611
|
+
# PONG <server> [ <target> ]
|
612
|
+
class Pong < Message
|
613
|
+
attr_accessor :server, :target
|
614
|
+
|
615
|
+
def initialize(server, target = nil)
|
616
|
+
@server = server
|
617
|
+
|
618
|
+
if @target = target
|
619
|
+
super(nil, 'PONG', @server, @target)
|
620
|
+
else
|
621
|
+
super(nil, 'PONG', @server)
|
622
|
+
end
|
623
|
+
end
|
624
|
+
end
|
625
|
+
|
626
|
+
# PRIVMSG <target> <text>
|
627
|
+
class Privmsg < Message
|
628
|
+
attr_accessor :target, :text, :ctcp
|
629
|
+
|
630
|
+
def initialize(target, text)
|
631
|
+
@target = target
|
632
|
+
@text, @ctcp = CTCP.parse(text)
|
633
|
+
|
634
|
+
super(nil, 'PRIVMSG', @target, text)
|
635
|
+
end
|
636
|
+
end
|
637
|
+
|
638
|
+
# QUIT [ <text> ]
|
639
|
+
class Quit < Message
|
640
|
+
attr_accessor :text
|
641
|
+
|
642
|
+
def initialize(text = nil)
|
643
|
+
if @text = text
|
644
|
+
super(nil, 'QUIT', @text)
|
645
|
+
else
|
646
|
+
super(nil, 'QUIT')
|
647
|
+
end
|
648
|
+
end
|
649
|
+
end
|
650
|
+
|
651
|
+
# USER <user> <mode> <unused> <realname>
|
652
|
+
class User < Message
|
653
|
+
attr_accessor :user, :realname, :mode
|
654
|
+
|
655
|
+
def initialize(*args)
|
656
|
+
# puts ">>>>> User#initialize(#{args.inspect})"
|
657
|
+
raise ArgumentError, "wrong number of arguments (#{args.size} for 2)" if args.size < 2
|
658
|
+
raise ArgumentError, "wrong number of arguments (#{args.size} for 4)" if args.size > 4
|
659
|
+
|
660
|
+
@user = args.shift
|
661
|
+
|
662
|
+
# treat mode and "unused" as optional for convenience
|
663
|
+
@mode = args.size > 1 && args.shift || USER_MODE_DEFAULT
|
664
|
+
|
665
|
+
args.shift if args.size > 1
|
666
|
+
|
667
|
+
@realname = args.shift
|
668
|
+
|
669
|
+
# puts ">>>>> @user=#{@user.inspect}, @mode=#{@mode.inspect}, unused=#{unused.inspect}, @realname=#{@realname.inspect}"
|
670
|
+
super(nil, 'USER', @user, @mode, '*', @realname)
|
671
|
+
# puts ">>>>> prefix=#{prefix.inspect}, command=#{command.inspect}, parameters=#{parameters.inspect}"
|
672
|
+
end
|
673
|
+
end
|
674
|
+
|
675
|
+
class << self
|
676
|
+
def start(user, password, realname, address, port = nil, &block)
|
677
|
+
new(address, port).start(user, password, realname, &block)
|
678
|
+
end
|
679
|
+
end
|
680
|
+
|
681
|
+
def initialize(address, port = nil)
|
682
|
+
@address = address
|
683
|
+
@port = port || PORT_DEFAULT
|
684
|
+
@started = false
|
685
|
+
@socket = nil
|
686
|
+
end
|
687
|
+
|
688
|
+
def started?
|
689
|
+
@started
|
690
|
+
end
|
691
|
+
|
692
|
+
def start(user, password, realname, nickname = nil)
|
693
|
+
raise IOError, 'IRC session already started' if started?
|
694
|
+
|
695
|
+
if block_given?
|
696
|
+
begin
|
697
|
+
do_start(user, password, realname, nickname)
|
698
|
+
return yield(self)
|
699
|
+
ensure
|
700
|
+
do_finish
|
701
|
+
end
|
702
|
+
else
|
703
|
+
do_start(user, password, realname, nickname)
|
704
|
+
return self
|
705
|
+
end
|
706
|
+
end
|
707
|
+
|
708
|
+
def finish
|
709
|
+
raise IOError, 'IRC session not yet started' if ! started?
|
710
|
+
end
|
711
|
+
|
712
|
+
def each
|
713
|
+
while line = @socket.readline
|
714
|
+
IRC.logger.debug "<<<<< #{line.inspect}"
|
715
|
+
|
716
|
+
message = Message.parse(line.chomp)
|
717
|
+
|
718
|
+
if message.respond_to? :ctcp
|
719
|
+
message.ctcp.each do |ctcp|
|
720
|
+
ctcp.source = message.prefix.nickname
|
721
|
+
ctcp.target = message.target
|
722
|
+
|
723
|
+
yield ctcp
|
724
|
+
end
|
725
|
+
next if message.text.empty?
|
726
|
+
end
|
727
|
+
|
728
|
+
case message
|
729
|
+
when Net::IRC::Ping
|
730
|
+
pong message.server
|
731
|
+
else
|
732
|
+
yield message
|
733
|
+
end
|
734
|
+
end
|
735
|
+
rescue IOError
|
736
|
+
raise if started?
|
737
|
+
end
|
738
|
+
|
739
|
+
def ctcp(target, text)
|
740
|
+
privmsg(target, "\001#{text}\001")
|
741
|
+
end
|
742
|
+
|
743
|
+
def ctcp_version(target, *parameters)
|
744
|
+
notice(target, CTCPVersion.new(*parameters).to_s)
|
745
|
+
end
|
746
|
+
|
747
|
+
def ctcp_ping(target, arg = nil)
|
748
|
+
notice(target, CTCPPing.new(arg).to_s)
|
749
|
+
end
|
750
|
+
|
751
|
+
def ctcp_time(target, time = nil)
|
752
|
+
time ||= Time.now
|
753
|
+
differential = '%.2d%.2d' % (time.utc_offset / 60).divmod(60)
|
754
|
+
notice(target, CTCPTime.new(time.strftime("%a, %d %b %Y %H:%M #{differential}")).to_s)
|
755
|
+
end
|
756
|
+
|
757
|
+
def join(channels = nil)
|
758
|
+
case channels
|
759
|
+
when NilClass
|
760
|
+
Join.new('0')
|
761
|
+
when Hash
|
762
|
+
Join.new(channels.keys.join(','), channels.values.join(','))
|
763
|
+
when Array
|
764
|
+
Join.new(channels.join(','))
|
765
|
+
else
|
766
|
+
Join.new(channels.to_s)
|
767
|
+
end.write(@socket)
|
768
|
+
end
|
769
|
+
|
770
|
+
def nick(nickname)
|
771
|
+
Nick.new(nickname).write(@socket)
|
772
|
+
end
|
773
|
+
|
774
|
+
def notice(target, text)
|
775
|
+
Notice.new(target, text).write(@socket)
|
776
|
+
end
|
777
|
+
|
778
|
+
def part(channels, message = nil)
|
779
|
+
if message
|
780
|
+
Part.new(Array(channels).join(','), message)
|
781
|
+
else
|
782
|
+
Part.new(Array(channels).join(','))
|
783
|
+
end.write(@socket)
|
784
|
+
end
|
785
|
+
|
786
|
+
def pass(password)
|
787
|
+
Pass.new(password).write(@socket)
|
788
|
+
end
|
789
|
+
|
790
|
+
def pong(server, target = nil)
|
791
|
+
Pong.new(server, target).write(@socket)
|
792
|
+
end
|
793
|
+
|
794
|
+
def privmsg(target, text)
|
795
|
+
Privmsg.new(target, text).write(@socket)
|
796
|
+
end
|
797
|
+
|
798
|
+
def quit(text = nil)
|
799
|
+
Quit.new(text).write(@socket)
|
800
|
+
end
|
801
|
+
|
802
|
+
def user(user, realname, mode = nil)
|
803
|
+
User.new(user, mode || USER_MODE_DEFAULT, realname).write(@socket)
|
804
|
+
end
|
805
|
+
|
806
|
+
private
|
807
|
+
def do_start(user, password, realname, nickname = nil)
|
808
|
+
@socket = InternetMessageIO.old_open(@address, @port)
|
809
|
+
pass(password) unless password.nil? || password.empty?
|
810
|
+
nick(user)
|
811
|
+
user(user, realname)
|
812
|
+
@started = true
|
813
|
+
ensure
|
814
|
+
do_finish if ! started?
|
815
|
+
end
|
816
|
+
|
817
|
+
def do_finish
|
818
|
+
quit if started?
|
819
|
+
ensure
|
820
|
+
@started = false
|
821
|
+
@socket.close if @socket && ! @socket.closed?
|
822
|
+
@socket = nil
|
823
|
+
end
|
824
|
+
end
|
825
|
+
end
|