irc-socket 0.9
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/README.rdoc +39 -0
- data/Rakefile +58 -0
- data/lib/irc-socket.rb +290 -0
- data/spec/irc-socket_spec.rb +81 -0
- metadata +95 -0
data/README.rdoc
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
== Description
|
2
|
+
|
3
|
+
IRCSocket is an IRC wrapper around a TCPSocket. It implements all of the major
|
4
|
+
commands laid out in {RFC 2812}[http://irchelp.org/irchelp/rfc/rfc2812.txt].
|
5
|
+
All these commands are available as instance methods of an IRCSocket Object.
|
6
|
+
|
7
|
+
API documentation is available {here}[http://rdoc.injekt.net/irc-socket]
|
8
|
+
|
9
|
+
== Example
|
10
|
+
irc = IRCSocket.new('irc.freenode.org')
|
11
|
+
irc.connect
|
12
|
+
|
13
|
+
if irc.connected?
|
14
|
+
irc.nick "HulkHogan"
|
15
|
+
irc.user "Hulk", 0, "*", "I am Hulk Hogan"
|
16
|
+
|
17
|
+
while line = irc.read
|
18
|
+
|
19
|
+
# Join a channel after MOTD
|
20
|
+
if line.split[1] == '376'
|
21
|
+
irc.join "#mychannel"
|
22
|
+
end
|
23
|
+
|
24
|
+
puts "Received: #{line}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
== Installation
|
29
|
+
gem install irc-socket
|
30
|
+
|
31
|
+
Or alternatively you can clone the Github repository
|
32
|
+
git clone https://github.com/injekt/irc-socket
|
33
|
+
|
34
|
+
|
35
|
+
== Author
|
36
|
+
* Lee Jarvis - ljjarvis@gmail.com
|
37
|
+
|
38
|
+
== Notes
|
39
|
+
I may have missed something in the RFC. Patches welcome.
|
data/Rakefile
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require "rake"
|
2
|
+
require "rake/clean"
|
3
|
+
require "rake/gempackagetask"
|
4
|
+
require "spec/rake/spectask"
|
5
|
+
require "rake/rdoctask"
|
6
|
+
|
7
|
+
NAME = 'irc-socket'
|
8
|
+
VERSION = '0.9'
|
9
|
+
CLEAN.include ["*.gem", "rdoc"]
|
10
|
+
|
11
|
+
RDOC_OPTS = [
|
12
|
+
"-U", "--main", "README.rdoc",
|
13
|
+
"--op", "rdoc",
|
14
|
+
]
|
15
|
+
|
16
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
17
|
+
rdoc.rdoc_dir = "rdoc"
|
18
|
+
rdoc.options += RDOC_OPTS
|
19
|
+
rdoc.rdoc_files.add %w(README.rdoc lib/irc-socket.rb)
|
20
|
+
end
|
21
|
+
|
22
|
+
desc "Run specs"
|
23
|
+
Spec::Rake::SpecTask.new('spec') do |t|
|
24
|
+
t.spec_files = Dir['spec/*.rb']
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "Package"
|
28
|
+
task :package => [:clean] do |p|
|
29
|
+
sh "gem build #{NAME}.gemspec"
|
30
|
+
end
|
31
|
+
|
32
|
+
desc "Install gem"
|
33
|
+
task :install => [:package] do
|
34
|
+
sh "sudo gem install ./#{NAME}-#{VERSION} --local"
|
35
|
+
end
|
36
|
+
|
37
|
+
desc "Uninstall gem"
|
38
|
+
task :uninstall => [:clean] do
|
39
|
+
sh "sudo gem uninstall #{NAME}"
|
40
|
+
end
|
41
|
+
|
42
|
+
desc "Upload gem to gemcutter"
|
43
|
+
task :release => [:package] do
|
44
|
+
sh "gem push ./#{NAME}-#{VERSION}.gem"
|
45
|
+
end
|
46
|
+
|
47
|
+
desc "Print #{NAME} version"
|
48
|
+
task :version do
|
49
|
+
puts VERSION
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "Upload rdoc to injekt.net"
|
53
|
+
task :upload => [:clean, :rdoc] do
|
54
|
+
sh("scp -r rdoc/* injekt@injekt.net:/var/www/injekt.net/rdoc/irc-socket")
|
55
|
+
end
|
56
|
+
|
57
|
+
task :default => [:spec]
|
58
|
+
|
data/lib/irc-socket.rb
ADDED
@@ -0,0 +1,290 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
# == Author
|
4
|
+
# * Lee Jarvis - ljjarvis@gmail.com
|
5
|
+
#
|
6
|
+
#
|
7
|
+
# == Description
|
8
|
+
#
|
9
|
+
# IRCSocket is an IRC wrapper around a TCPSocket. It implements all of the major
|
10
|
+
# commands laid out in {RFC 2812}[http://irchelp.org/irchelp/rfc/rfc2812.txt].
|
11
|
+
# All these commands are available as instance methods of an IRCSocket Object.
|
12
|
+
#
|
13
|
+
# == Example
|
14
|
+
# irc = IRCSocket.new('irc.freenode.org')
|
15
|
+
# irc.connect
|
16
|
+
#
|
17
|
+
# if irc.connected?
|
18
|
+
# irc.nick "HulkHogan"
|
19
|
+
# irc.user "Hulk", 0, "*", "I am Hulk Hogan"
|
20
|
+
#
|
21
|
+
# while line = irc.read
|
22
|
+
#
|
23
|
+
# # Join a channel after MOTD
|
24
|
+
# if line.split[1] == '376'
|
25
|
+
# irc.join "#mychannel"
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# puts "Received: #{line}"
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# === Block Form
|
33
|
+
# IRCSocket.new('irc.freenode.org') do |irc|
|
34
|
+
# irc.nick "SpongeBob"
|
35
|
+
# irc.user "Spongey", 0, "*", "Square Pants"
|
36
|
+
#
|
37
|
+
# puts irc.read
|
38
|
+
# end
|
39
|
+
class IRCSocket
|
40
|
+
|
41
|
+
# The server our socket is connected to
|
42
|
+
attr_reader :server
|
43
|
+
|
44
|
+
# The port our socket is connected on
|
45
|
+
attr_reader :port
|
46
|
+
|
47
|
+
# The TCPSocket instance
|
48
|
+
attr_reader :socket
|
49
|
+
|
50
|
+
# Creates a new IRCSocket and automatically connects
|
51
|
+
#
|
52
|
+
# === Example
|
53
|
+
# irc = IRCSocket.open('irc.freenode.org')
|
54
|
+
#
|
55
|
+
# while data = irc.read
|
56
|
+
# puts data
|
57
|
+
# end
|
58
|
+
def self.open(server, port=6667)
|
59
|
+
irc = new(server, port)
|
60
|
+
irc.connect
|
61
|
+
irc
|
62
|
+
end
|
63
|
+
|
64
|
+
# Create a new IRCSocket to connect to +server+ on +port+. Defaults to port 6667.
|
65
|
+
# If an optional code block is given, it will be passed an instance of the IRCSocket.
|
66
|
+
# NOTE: Using the block form does not mean the socket will send the applicable QUIT
|
67
|
+
# command to leave the IRC server. You must send this yourself.
|
68
|
+
def initialize(server, port=6667)
|
69
|
+
@server = server
|
70
|
+
@port = port
|
71
|
+
|
72
|
+
@socket = nil
|
73
|
+
@connected = false
|
74
|
+
|
75
|
+
if block_given?
|
76
|
+
connect
|
77
|
+
yield self
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Check if our socket is alive and connected to an IRC server
|
82
|
+
def connected?
|
83
|
+
@connected
|
84
|
+
end
|
85
|
+
alias connected connected?
|
86
|
+
|
87
|
+
# Connect to an IRC server, returns true on a successful connection, or
|
88
|
+
# raises otherwise
|
89
|
+
def connect
|
90
|
+
@socket = TCPSocket.new(server, port)
|
91
|
+
rescue Interrupt
|
92
|
+
raise
|
93
|
+
rescue Exception
|
94
|
+
raise
|
95
|
+
else
|
96
|
+
@connected = true
|
97
|
+
end
|
98
|
+
|
99
|
+
# Read the next line in from the server. If no arguments are passed
|
100
|
+
# the line will have the CRLF chomp'ed. Returns nil if no data could be read
|
101
|
+
def read(chompstr="\r\n")
|
102
|
+
if data = @socket.gets("\r\n")
|
103
|
+
data.chomp(chompstr) if chompstr
|
104
|
+
data
|
105
|
+
else
|
106
|
+
nil
|
107
|
+
end
|
108
|
+
rescue IOError
|
109
|
+
nil
|
110
|
+
end
|
111
|
+
|
112
|
+
# Write to our Socket and append CRLF
|
113
|
+
def write(data)
|
114
|
+
@socket.write(data + "\r\n")
|
115
|
+
rescue IOError
|
116
|
+
raise
|
117
|
+
end
|
118
|
+
|
119
|
+
# Sugar for #write
|
120
|
+
def raw(*args) # :nodoc:
|
121
|
+
args.last.insert(0, ':') unless args.last.nil?
|
122
|
+
args.join(' ').strip
|
123
|
+
end
|
124
|
+
|
125
|
+
# More sugar
|
126
|
+
def write_optional(command, *optional) # :nodoc:
|
127
|
+
command = "#{command} #{optional.join(' ')}" if optional
|
128
|
+
write(command.strip)
|
129
|
+
end
|
130
|
+
private :raw, :write_optional
|
131
|
+
|
132
|
+
# Send PASS command
|
133
|
+
def pass(password)
|
134
|
+
write("PASS #{password}")
|
135
|
+
end
|
136
|
+
|
137
|
+
# Send NICK command
|
138
|
+
def nick(nickname)
|
139
|
+
write("NICK #{nickname}")
|
140
|
+
end
|
141
|
+
|
142
|
+
# Send USER command -
|
143
|
+
def user(user, mode, unused, realname)
|
144
|
+
write("USER #{user} #{mode} #{unused} :#{realname}")
|
145
|
+
end
|
146
|
+
|
147
|
+
# Send OPER command
|
148
|
+
def oper(name, password)
|
149
|
+
write("OPER #{name} #{password}")
|
150
|
+
end
|
151
|
+
|
152
|
+
# Send the MODE command.
|
153
|
+
# Should probably implement a better way of doing this
|
154
|
+
def mode(channel, *modes)
|
155
|
+
write("MODE #{channel} #{modes.join(' ')}")
|
156
|
+
end
|
157
|
+
|
158
|
+
# Send QUIT command
|
159
|
+
def quit(message=nil)
|
160
|
+
raw("QUIT", message)
|
161
|
+
end
|
162
|
+
|
163
|
+
# Send JOIN command - Join a channel with given password
|
164
|
+
def join(channel, password=nil)
|
165
|
+
write("JOIN #{channel}")
|
166
|
+
end
|
167
|
+
|
168
|
+
# Send PART command -
|
169
|
+
def part(channel, message=nil)
|
170
|
+
raw("PART", channel, message)
|
171
|
+
end
|
172
|
+
|
173
|
+
# Send TOPIC command
|
174
|
+
def topic(channel, topic=nil)
|
175
|
+
raw("TOPIC", channel, topic)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Send NAMES command
|
179
|
+
def names(*channels)
|
180
|
+
write("NAMES #{channels.join(',') unless channels.empty?}")
|
181
|
+
end
|
182
|
+
|
183
|
+
# Send LIST command
|
184
|
+
def list(*channels)
|
185
|
+
write("LIST #{channels.join(',') unless channels.empty?}")
|
186
|
+
end
|
187
|
+
|
188
|
+
# Send INVITE
|
189
|
+
def invite(nickname, channel)
|
190
|
+
write("INVITE #{nickname} #{channel}")
|
191
|
+
end
|
192
|
+
|
193
|
+
# Send KICK
|
194
|
+
def kick(channel, user, comment=nil)
|
195
|
+
raw("KICK", channel, user, comment)
|
196
|
+
end
|
197
|
+
|
198
|
+
# Send PRIVMSG
|
199
|
+
def privmsg(target, message)
|
200
|
+
write("PRIVMSG #{target} :#{message}")
|
201
|
+
end
|
202
|
+
|
203
|
+
# Send NOTICE
|
204
|
+
def notice(target, message)
|
205
|
+
write("NOTICE #{target} :#{message}")
|
206
|
+
end
|
207
|
+
|
208
|
+
# Send MOTD
|
209
|
+
def motd(target=nil)
|
210
|
+
write_optional("MOTD", target)
|
211
|
+
end
|
212
|
+
|
213
|
+
# Send VERSION
|
214
|
+
def version(target=nil)
|
215
|
+
write_optional("VERSION", target)
|
216
|
+
end
|
217
|
+
|
218
|
+
# Send STATS
|
219
|
+
def stats(*params)
|
220
|
+
write_optional("STATS", params)
|
221
|
+
end
|
222
|
+
|
223
|
+
# Send TIME
|
224
|
+
def time(target=nil)
|
225
|
+
write_optional("TIME", target)
|
226
|
+
end
|
227
|
+
|
228
|
+
# Send INFO
|
229
|
+
def info(target=nil)
|
230
|
+
write_optional("INFO", target)
|
231
|
+
end
|
232
|
+
|
233
|
+
# Send SQUERY
|
234
|
+
def squery(target, message)
|
235
|
+
write("SQUERY #{target} :#{message}")
|
236
|
+
end
|
237
|
+
|
238
|
+
# Send WHO
|
239
|
+
def who(*params)
|
240
|
+
write_optional("WHO", params)
|
241
|
+
end
|
242
|
+
|
243
|
+
# Send WHOIS
|
244
|
+
def whois(*params)
|
245
|
+
write_optional("WHOIS", params)
|
246
|
+
end
|
247
|
+
|
248
|
+
# Send WHOWAS
|
249
|
+
def whowas(*params)
|
250
|
+
write_optional("WHOWAS", params)
|
251
|
+
end
|
252
|
+
|
253
|
+
# Send KILL
|
254
|
+
def kill(user, message)
|
255
|
+
write("KILL #{user} :#{message}")
|
256
|
+
end
|
257
|
+
|
258
|
+
# Send PING
|
259
|
+
def ping(server)
|
260
|
+
write("PING #{server}")
|
261
|
+
end
|
262
|
+
|
263
|
+
# Send PONG
|
264
|
+
def pong(server)
|
265
|
+
write("PONG #{server}")
|
266
|
+
end
|
267
|
+
|
268
|
+
# Send AWAY
|
269
|
+
def away(message=nil)
|
270
|
+
raw("AWAY", message)
|
271
|
+
end
|
272
|
+
|
273
|
+
# Send USERS
|
274
|
+
def users(target=nil)
|
275
|
+
write_optional("USERS", target)
|
276
|
+
end
|
277
|
+
|
278
|
+
# Send USERHOST
|
279
|
+
def userhost(*users)
|
280
|
+
write("USERHOST #{users.join(' ')}")
|
281
|
+
end
|
282
|
+
|
283
|
+
# Close our socket instance
|
284
|
+
def close
|
285
|
+
@socket.close if connected?
|
286
|
+
end
|
287
|
+
|
288
|
+
end
|
289
|
+
|
290
|
+
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require File.expand_path('../../lib/irc-socket', __FILE__)
|
2
|
+
|
3
|
+
class IRCSocket
|
4
|
+
def write(data)
|
5
|
+
return data
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
commands = [
|
10
|
+
[:pass, %w(foobar), "PASS foobar"],
|
11
|
+
[:nick, %(ipsum), "NICK ipsum"],
|
12
|
+
[:user, ["guest", 0, '*', "real name"], "USER guest 0 * :real name"],
|
13
|
+
[:oper, %w(foo bar), "OPER foo bar"],
|
14
|
+
[:mode, %w(#foo +v bar), "MODE #foo +v bar"],
|
15
|
+
[:quit, %w(goodbye), "QUIT :goodbye"],
|
16
|
+
[:join, %w(#mychan), "JOIN #mychan"],
|
17
|
+
[:part, %w(#mychan), "PART #mychan"],
|
18
|
+
[:part, %w(#mychan cya!), "PART #mychan :cya!", "with part message"],
|
19
|
+
[:topic, %w(#mychan newtopic), "TOPIC #mychan :newtopic"],
|
20
|
+
[:names, %w(#foo #bar), "NAMES #foo,#bar"],
|
21
|
+
[:list, %w(#foo #bar), "LIST #foo,#bar"],
|
22
|
+
[:invite, %w(foo #mychan), "INVITE foo #mychan"],
|
23
|
+
[:kick, %w(#chan villian), "KICK #chan villian"],
|
24
|
+
[:kick, %w(#chan villian gtfo!), "KICK #chan villian :gtfo!", "with kick reason"],
|
25
|
+
[:privmsg, ['#chan', 'foo bar baz'], "PRIVMSG #chan :foo bar baz"],
|
26
|
+
[:notice, ['#chan', 'foo bar baz'], "NOTICE #chan :foo bar baz"],
|
27
|
+
[:motd, %w(someserver), "MOTD someserver"],
|
28
|
+
[:version, %w(anotherserver), "VERSION anotherserver"],
|
29
|
+
[:stats, %w(m server), "STATS m server"],
|
30
|
+
[:time, %w(irc.someserver.net), "TIME irc.someserver.net"],
|
31
|
+
[:info, %w(foobar), "INFO foobar"],
|
32
|
+
[:squery, %w(irchelp HELPME), "SQUERY irchelp :HELPME"],
|
33
|
+
[:who, %w(*.com o), "WHO *.com o"],
|
34
|
+
[:whois, %w(foo.org user), "WHOIS foo.org user"],
|
35
|
+
[:whowas, %w(foo.org user), "WHOWAS foo.org user"],
|
36
|
+
[:kill, ['badperson', 'get out!'], "KILL badperson :get out!"],
|
37
|
+
[:ping, %w(010123444), "PING 010123444"],
|
38
|
+
[:pong, %w(irc.foobar.org), "PONG irc.foobar.org"],
|
39
|
+
[:away, [], "AWAY"],
|
40
|
+
[:away, ['gone for lunch'], "AWAY :gone for lunch"],
|
41
|
+
[:users, %w(irc.foobar.org), "USERS irc.foobar.org"],
|
42
|
+
[:userhost, %w(foo bar baz), "USERHOST foo bar baz"],
|
43
|
+
]
|
44
|
+
|
45
|
+
describe "IRCSocket" do
|
46
|
+
before do
|
47
|
+
@irc = IRCSocket.new('irc.myserver.net')
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "::new" do
|
51
|
+
it "should return an IRCSocket" do
|
52
|
+
@irc.class.should == IRCSocket
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should default to port 6667" do
|
56
|
+
@irc.port.should == 6667
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should not automatically connect" do
|
60
|
+
@irc.connected.should == false
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should set socket instance as nil" do
|
64
|
+
@irc.socket.should == nil
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "IRC commands as per rfc2812" do
|
70
|
+
commands.each do |requirements|
|
71
|
+
meth, params, pass, extra = *requirements
|
72
|
+
describe "##{meth}" do
|
73
|
+
it "should send a #{meth.to_s.upcase} command #{extra if extra}" do
|
74
|
+
@irc.send(meth, *params).should == pass
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: irc-socket
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 9
|
8
|
+
version: "0.9"
|
9
|
+
platform: ruby
|
10
|
+
authors:
|
11
|
+
- Lee 'injekt' Jarvis
|
12
|
+
autorequire:
|
13
|
+
bindir: bin
|
14
|
+
cert_chain: []
|
15
|
+
|
16
|
+
date: 2010-04-22 00:00:00 +01:00
|
17
|
+
default_executable:
|
18
|
+
dependencies:
|
19
|
+
- !ruby/object:Gem::Dependency
|
20
|
+
name: bacon
|
21
|
+
prerelease: false
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
segments:
|
27
|
+
- 1
|
28
|
+
- 1
|
29
|
+
- 0
|
30
|
+
version: 1.1.0
|
31
|
+
type: :development
|
32
|
+
version_requirements: *id001
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: rspec
|
35
|
+
prerelease: false
|
36
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
segments:
|
41
|
+
- 1
|
42
|
+
- 3
|
43
|
+
- 0
|
44
|
+
version: 1.3.0
|
45
|
+
type: :development
|
46
|
+
version_requirements: *id002
|
47
|
+
description: An IRC wrapper around TCPSocket
|
48
|
+
email: ljjarvis@gmail.com
|
49
|
+
executables: []
|
50
|
+
|
51
|
+
extensions: []
|
52
|
+
|
53
|
+
extra_rdoc_files:
|
54
|
+
- README.rdoc
|
55
|
+
files:
|
56
|
+
- README.rdoc
|
57
|
+
- Rakefile
|
58
|
+
- spec/irc-socket_spec.rb
|
59
|
+
- lib/irc-socket.rb
|
60
|
+
has_rdoc: true
|
61
|
+
homepage: http://wiki.github.com/injekt/irc-socket
|
62
|
+
licenses: []
|
63
|
+
|
64
|
+
post_install_message:
|
65
|
+
rdoc_options:
|
66
|
+
- --quiet
|
67
|
+
- --main
|
68
|
+
- README.rdoc
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
segments:
|
76
|
+
- 1
|
77
|
+
- 8
|
78
|
+
- 4
|
79
|
+
version: 1.8.4
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
segments:
|
85
|
+
- 0
|
86
|
+
version: "0"
|
87
|
+
requirements: []
|
88
|
+
|
89
|
+
rubyforge_project:
|
90
|
+
rubygems_version: 1.3.6
|
91
|
+
signing_key:
|
92
|
+
specification_version: 3
|
93
|
+
summary: An IRC wrapper around TCPSocket
|
94
|
+
test_files: []
|
95
|
+
|