eventmachine-irc-server 0.0.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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +38 -0
- data/Rakefile +12 -0
- data/bin/em_irc_server.rb +8 -0
- data/eventmachine-irc-server.gemspec +26 -0
- data/lib/eventmachine/irc/server.rb +770 -0
- data/lib/eventmachine/irc/server/replies.rb +141 -0
- data/lib/eventmachine/irc/server/version.rb +9 -0
- data/test/helper.rb +5 -0
- data/test/test_irc_server.rb +104 -0
- metadata +130 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 138f3644f1d3ebf1a7cd250cfa72a336218e00d6
|
4
|
+
data.tar.gz: ad4e1a6e69ae6b366650ba8d5c4f3b803cce3760
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 767b155e0c9e47f4f7eaeffadd07f6fb7d56b2cd3ae70e4bb17e4c836d983f01c0b0fe5c0bbc18d1c784ab2d60ad9f3af6065ec6dd47ef767ae95b5c5679e450
|
7
|
+
data.tar.gz: ae96a913cbc3aac185fec64764bd88e5b2882104d751859e92865d4c1569e13929f02aefc57899efe7e558e18738bdebd25d7e564eed59139304d818c30dd9da
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 chrislee35
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# EventMachine::IRC::Server
|
2
|
+
|
3
|
+
EventMachine::IRC::Server provides a basic IRC server for Ruby's EventMachine.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'eventmachine-irc-server'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install eventmachine-irc-server
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
require 'eventmachine'
|
24
|
+
require 'eventmachine/irc/server'
|
25
|
+
|
26
|
+
EventMachine.run {
|
27
|
+
Signal.trap("INT") { EventMachine.stop }
|
28
|
+
Signal.trap("TERM") { EventMachine.stop }
|
29
|
+
srvr = EventMachine::start_server "0.0.0.0", 6667, EventMachine::IRC::Server
|
30
|
+
}
|
31
|
+
|
32
|
+
## Contributing
|
33
|
+
|
34
|
+
1. Fork it ( https://github.com/[my-github-username]/eventmachine-irc-server/fork )
|
35
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
36
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
37
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
38
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'eventmachine/irc/server/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "eventmachine-irc-server"
|
8
|
+
spec.version = EventMachine::IRC::Server::VERSION
|
9
|
+
spec.authors = ["chrislee35"]
|
10
|
+
spec.email = ["rubygems@chrislee.dhs.org"]
|
11
|
+
spec.summary = %q{Simple EventMachine-based IRC server. 簡単なイベントマシーンのIRCのサーバーです。}
|
12
|
+
spec.description = %q{For use in the Rubot Emulation Framework, this simple IRC server allows test bots to connect and receive commands.}
|
13
|
+
spec.homepage = "http://github.com/chrislee35/eventmachine-irc-server"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_runtime_dependency "eventmachine", ">= 0.12.10"
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
23
|
+
spec.add_development_dependency "minitest", "~> 5.5"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
spec.add_development_dependency "em-irc", ">= 0.0.2"
|
26
|
+
end
|
@@ -0,0 +1,770 @@
|
|
1
|
+
# Most of the code for this module was taken from blufox's project at
|
2
|
+
# https://code.google.com/p/ruby-ircd/
|
3
|
+
# As I build more functionality/bug-fixes into this, expect major refactoring
|
4
|
+
|
5
|
+
require 'eventmachine'
|
6
|
+
require "eventmachine/irc/server/version"
|
7
|
+
require 'socket'
|
8
|
+
require_relative 'server/replies'
|
9
|
+
|
10
|
+
include EventMachine::IRC::Replies
|
11
|
+
|
12
|
+
def carp(message)
|
13
|
+
puts message
|
14
|
+
end
|
15
|
+
|
16
|
+
module EventMachine
|
17
|
+
module IRC
|
18
|
+
|
19
|
+
CHANNEL = /^[#\$&]+/
|
20
|
+
PREFIX = /^:[^ ]+ +(.+)$/
|
21
|
+
|
22
|
+
class SynchronizedStore
|
23
|
+
def initialize
|
24
|
+
@store = {}
|
25
|
+
#@mutex = Mutex.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def method_missing(name,*args)
|
29
|
+
#@mutex.synchronize {
|
30
|
+
@store.__send__(name,*args)
|
31
|
+
#}
|
32
|
+
end
|
33
|
+
|
34
|
+
def each_value
|
35
|
+
#@mutex.synchronize do
|
36
|
+
@store.each_value {|u|
|
37
|
+
#@mutex.unlock
|
38
|
+
yield u
|
39
|
+
#@mutex.lock
|
40
|
+
}
|
41
|
+
#end
|
42
|
+
end
|
43
|
+
|
44
|
+
def keys
|
45
|
+
#@mutex.synchronize{
|
46
|
+
@store.keys
|
47
|
+
#}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class ConnectedClient
|
52
|
+
attr_reader :nick, :user, :realname, :channels, :state
|
53
|
+
|
54
|
+
def initialize(server)
|
55
|
+
@server = server
|
56
|
+
@channels = Array.new
|
57
|
+
@nick = nil
|
58
|
+
@user = nil
|
59
|
+
@pass = nil
|
60
|
+
@last_ping = Time.now
|
61
|
+
@last_pong = Time.now
|
62
|
+
@state = {}
|
63
|
+
@welcomed = false
|
64
|
+
@nick_tries = 0
|
65
|
+
end
|
66
|
+
|
67
|
+
def host
|
68
|
+
# TODO: figure out how to do this with event machine
|
69
|
+
return @peername
|
70
|
+
end
|
71
|
+
|
72
|
+
def userprefix
|
73
|
+
# Where is this defined?
|
74
|
+
return @usermsg
|
75
|
+
end
|
76
|
+
|
77
|
+
def ready
|
78
|
+
return (!@pass.nil? && !@nick.nil?)
|
79
|
+
end
|
80
|
+
|
81
|
+
def handle_join(channel)
|
82
|
+
@channels << channel
|
83
|
+
end
|
84
|
+
|
85
|
+
def handle_nick(nick)
|
86
|
+
carp "nick => #{nick}"
|
87
|
+
if Server.user_store[nick].nil?
|
88
|
+
userlist = {}
|
89
|
+
if @nick.nil?
|
90
|
+
handle_newconnect(nick)
|
91
|
+
else
|
92
|
+
userlist[nick] = self if self.nick != nick
|
93
|
+
Server.user_store.delete(@nick)
|
94
|
+
@nick = nick
|
95
|
+
end
|
96
|
+
|
97
|
+
Server.user_store << self
|
98
|
+
|
99
|
+
#send the info to the world
|
100
|
+
#get unique users.
|
101
|
+
@channels.each { |c|
|
102
|
+
Server.channel_store[c].each_user { |u|
|
103
|
+
userlist[u.nick] = u
|
104
|
+
}
|
105
|
+
}
|
106
|
+
userlist.values.each {|user|
|
107
|
+
user.reply :nick, nick
|
108
|
+
}
|
109
|
+
@usermsg = ":#{@nick}!~#{@user}@#{@peername}"
|
110
|
+
else
|
111
|
+
#check if we are just nicking ourselves.
|
112
|
+
unless Server.user_store[nick] == self
|
113
|
+
#verify the connectivity of earlier guy
|
114
|
+
reply :numeric, ERR_NICKNAMEINUSE, "* #{nick} ", "Nickname is already in use."
|
115
|
+
@nick_tries += 1
|
116
|
+
if @nick_tries > $config['nick-tries']
|
117
|
+
carp "kicking spurious user #{nick} after #{@nick_tries} tries"
|
118
|
+
handle_abort
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
@nick_tries = 0
|
123
|
+
end
|
124
|
+
|
125
|
+
def handle_user(user, mode, unused, realname)
|
126
|
+
@user = user
|
127
|
+
@mode = mode
|
128
|
+
@realname = realname
|
129
|
+
@usermsg = ":#{@nick}!~#{@user}@#{@peername}"
|
130
|
+
send_welcome if !@nick.nil?
|
131
|
+
end
|
132
|
+
|
133
|
+
def mode
|
134
|
+
return @mode
|
135
|
+
end
|
136
|
+
|
137
|
+
def handle_newconnect(nick)
|
138
|
+
@alive = true
|
139
|
+
@nick = nick
|
140
|
+
@host = Server.config['hostname']
|
141
|
+
@ver = Server.config['version']
|
142
|
+
@starttime = Server.config['starttime']
|
143
|
+
send_welcome if !@user.nil?
|
144
|
+
end
|
145
|
+
|
146
|
+
def handle_pass(pass)
|
147
|
+
@pass = pass
|
148
|
+
end
|
149
|
+
|
150
|
+
def send_welcome
|
151
|
+
if !@welcomed
|
152
|
+
repl_welcome
|
153
|
+
repl_yourhost
|
154
|
+
repl_created
|
155
|
+
repl_myinfo
|
156
|
+
repl_motd
|
157
|
+
repl_mode
|
158
|
+
@welcomed = true
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def repl_welcome
|
163
|
+
client = "#{@nick}!#{@user}@#{@peername}"
|
164
|
+
reply :numeric, WELCOME, @nick, "Welcome to this IRC server #{client}"
|
165
|
+
end
|
166
|
+
|
167
|
+
def repl_yourhost
|
168
|
+
reply :numeric, YOURHOST, @nick, "Your host is #{@host}, running version #{@ver}"
|
169
|
+
end
|
170
|
+
|
171
|
+
def repl_created
|
172
|
+
reply :numeric, CREATED, @nick, "This server was created #{@starttime}"
|
173
|
+
end
|
174
|
+
|
175
|
+
def repl_myinfo
|
176
|
+
reply :numeric, MYINFO, @nick, "#{@host} #{@ver} #{@server.usermodes} #{@server.channelmodes}"
|
177
|
+
end
|
178
|
+
|
179
|
+
def repl_bounce(sever, port)
|
180
|
+
reply :numeric, BOUNCE ,"Try server #{server}, port #{port}"
|
181
|
+
end
|
182
|
+
|
183
|
+
def repl_ison()
|
184
|
+
#XXX TODO
|
185
|
+
reply :numeric, ISON,"notimpl"
|
186
|
+
end
|
187
|
+
|
188
|
+
def repl_away(nick, msg)
|
189
|
+
reply :numeric, AWAY, nick, msg
|
190
|
+
end
|
191
|
+
|
192
|
+
def repl_unaway()
|
193
|
+
reply :numeric, UNAWAY, @nick,"You are no longer marked as being away"
|
194
|
+
end
|
195
|
+
|
196
|
+
def repl_nowaway()
|
197
|
+
reply :numeric, NOWAWAY, @nick,"You have been marked as being away"
|
198
|
+
end
|
199
|
+
|
200
|
+
def repl_motd()
|
201
|
+
reply :numeric, MOTDSTART,'', "- Message of the Day"
|
202
|
+
reply :numeric, MOTD,'', "- Do the dance see the source"
|
203
|
+
reply :numeric, ENDOFMOTD,'', "- End of /MOTD command."
|
204
|
+
end
|
205
|
+
|
206
|
+
def repl_mode()
|
207
|
+
end
|
208
|
+
|
209
|
+
def send_topic(channel)
|
210
|
+
if Server.channel_store[channel]
|
211
|
+
reply :numeric, TOPIC,channel, "#{Server.channel_store[channel].topic}"
|
212
|
+
else
|
213
|
+
send_notonchannel channel
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def send_nonick(nick)
|
218
|
+
reply :numeric, ERR_NOSUCHNICK, nick, "No such nick/channel"
|
219
|
+
end
|
220
|
+
|
221
|
+
def send_nochannel(channel)
|
222
|
+
reply :numeric, ERR_NOSUCHCHANNEL, channel, "That channel doesn't exist"
|
223
|
+
end
|
224
|
+
|
225
|
+
def send_notonchannel(channel)
|
226
|
+
reply :numeric, ERR_NOTONCHANNEL, channel, "Not a member of that channel"
|
227
|
+
end
|
228
|
+
|
229
|
+
def names(channel)
|
230
|
+
return Server.channel_store[channel].nicks
|
231
|
+
end
|
232
|
+
|
233
|
+
def send_nameslist(channel)
|
234
|
+
c = Server.channel_store[channel]
|
235
|
+
if c.nil?
|
236
|
+
carp "names failed :#{c}"
|
237
|
+
return
|
238
|
+
end
|
239
|
+
names = []
|
240
|
+
c.each_user {|user|
|
241
|
+
names << c.mode(user) + user.nick if user.nick
|
242
|
+
}
|
243
|
+
reply :numeric, NAMREPLY,"= #{c.name}","#{names.join(' ')}"
|
244
|
+
reply :numeric, ENDOFNAMES,"#{c.name} ","End of /NAMES list."
|
245
|
+
end
|
246
|
+
|
247
|
+
def send_ping()
|
248
|
+
reply :ping, "#{Server.config['hostname']}"
|
249
|
+
end
|
250
|
+
|
251
|
+
def handle_join(channels)
|
252
|
+
channels.split(/,/).each {|ch|
|
253
|
+
c = ch.strip
|
254
|
+
if c !~ CHANNEL
|
255
|
+
send_nochannel(c)
|
256
|
+
carp "no such channel:#{c}"
|
257
|
+
return
|
258
|
+
end
|
259
|
+
channel = Server.channel_store.add(c)
|
260
|
+
if channel.join(self)
|
261
|
+
send_topic(c)
|
262
|
+
send_nameslist(c)
|
263
|
+
@channels << c
|
264
|
+
else
|
265
|
+
carp "already joined #{c}"
|
266
|
+
end
|
267
|
+
}
|
268
|
+
end
|
269
|
+
|
270
|
+
def handle_ping(pingmsg, rest)
|
271
|
+
reply :pong, pingmsg
|
272
|
+
end
|
273
|
+
|
274
|
+
def handle_pong(srv)
|
275
|
+
carp "got pong: #{srv}"
|
276
|
+
end
|
277
|
+
|
278
|
+
def handle_privmsg(target, msg)
|
279
|
+
case target.strip
|
280
|
+
when CHANNEL
|
281
|
+
channel= Server.channel_store[target]
|
282
|
+
if !channel.nil?
|
283
|
+
channel.privatemsg(msg, self)
|
284
|
+
else
|
285
|
+
send_nonick(target)
|
286
|
+
end
|
287
|
+
else
|
288
|
+
user = Server.user_store[target]
|
289
|
+
if !user.nil?
|
290
|
+
if !user.state[:away].nil?
|
291
|
+
repl_away(user.nick,user.state[:away])
|
292
|
+
end
|
293
|
+
user.reply :privmsg, self.userprefix, user.nick, msg
|
294
|
+
else
|
295
|
+
send_nonick(target)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def handle_notice(target, msg)
|
301
|
+
case target.strip
|
302
|
+
when CHANNEL
|
303
|
+
channel= Server.channel_store[target]
|
304
|
+
if !channel.nil?
|
305
|
+
channel.notice(msg, self)
|
306
|
+
else
|
307
|
+
send_nonick(target)
|
308
|
+
end
|
309
|
+
else
|
310
|
+
user = Server.user_store[target]
|
311
|
+
if !user.nil?
|
312
|
+
user.reply :notice, self.userprefix, user.nick, msg
|
313
|
+
else
|
314
|
+
send_nonick(target)
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def handle_part(channel, msg)
|
320
|
+
if Server.channel_store.channels.include? channel
|
321
|
+
if Server.channel_store[channel].part(self, msg)
|
322
|
+
@channels.delete(channel)
|
323
|
+
else
|
324
|
+
send_notonchannel channel
|
325
|
+
end
|
326
|
+
else
|
327
|
+
send_nochannel channel
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def handle_quit(msg)
|
332
|
+
#do this to avoid double quit due to 2 threads.
|
333
|
+
return if !@alive
|
334
|
+
@alive = false
|
335
|
+
@channels.each do |channel|
|
336
|
+
Server.channel_store[channel].quit(self, msg)
|
337
|
+
end
|
338
|
+
Server.user_store.delete(self.nick)
|
339
|
+
carp "#{self.nick} #{msg}"
|
340
|
+
@server.close_connection
|
341
|
+
end
|
342
|
+
|
343
|
+
def handle_topic(channel, topic)
|
344
|
+
carp "handle topic for #{channel}:#{topic}"
|
345
|
+
if topic.nil? or topic =~ /^ *$/
|
346
|
+
send_topic(channel)
|
347
|
+
else
|
348
|
+
begin
|
349
|
+
Server.channel_store[channel].topic(topic,self)
|
350
|
+
rescue Exception => e
|
351
|
+
carp e
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
def handle_away(msg)
|
357
|
+
carp "handle away :#{msg}"
|
358
|
+
if msg.nil? or msg =~ /^ *$/
|
359
|
+
@state.delete(:away)
|
360
|
+
repl_unaway
|
361
|
+
else
|
362
|
+
@state[:away] = msg
|
363
|
+
repl_nowaway
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
def handle_list(channel)
|
368
|
+
reply :numeric, LISTSTART
|
369
|
+
case channel.strip
|
370
|
+
when /^#/
|
371
|
+
channel.split(/,/).each {|cname|
|
372
|
+
c = Server.channel_store[cname.strip]
|
373
|
+
reply :numeric, LIST, c.name, c.topic if c
|
374
|
+
}
|
375
|
+
else
|
376
|
+
#older opera client sends LIST <1000
|
377
|
+
#we wont obey the boolean after list, but allow the listing
|
378
|
+
#nonetheless
|
379
|
+
Server.channel_store.each_channel {|c|
|
380
|
+
reply :numeric, LIST, c.name, c.topic
|
381
|
+
}
|
382
|
+
end
|
383
|
+
reply :numeric, LISTEND
|
384
|
+
end
|
385
|
+
|
386
|
+
def handle_whois(target,nicks)
|
387
|
+
#ignore target for now.
|
388
|
+
return reply(:numeric, NONICKNAMEGIVEN, "", "No nickname given") if nicks.strip.length == 0
|
389
|
+
nicks.split(/,/).each {|nick|
|
390
|
+
nick.strip!
|
391
|
+
user = Server.user_store[nick]
|
392
|
+
if user
|
393
|
+
reply :numeric, WHOISUSER, "#{user.nick} #{user.user} #{user.host} *", "#{user.realname}"
|
394
|
+
reply :numeric, WHOISCHANNELS, user.nick, "#{user.channels.join(' ')}"
|
395
|
+
repl_away user.nick, user.state[:away] if !user.state[:away].nil?
|
396
|
+
reply :numeric, ENDOFWHOIS, user.nick, "End of /WHOIS list"
|
397
|
+
else
|
398
|
+
return send_nonick(nick)
|
399
|
+
end
|
400
|
+
}
|
401
|
+
end
|
402
|
+
|
403
|
+
def handle_names(channels, server)
|
404
|
+
channels.split(/,/).each {|ch| send_nameslist(ch.strip) }
|
405
|
+
end
|
406
|
+
|
407
|
+
def handle_who(mask, rest)
|
408
|
+
channel = Server.channel_store[mask]
|
409
|
+
hopcount = 0
|
410
|
+
if channel.nil?
|
411
|
+
#match against all users
|
412
|
+
Server.user_store.each_user {|user|
|
413
|
+
reply :numeric, WHOREPLY ,
|
414
|
+
"#{user.channels[0]} #{user.userprefix} #{user.host} #{Server.config['hostname']} #{user.nick} H" ,
|
415
|
+
"#{hopcount} #{user.realname}" if File.fnmatch?(mask, "#{user.host}.#{user.realname}.#{user.nick}")
|
416
|
+
}
|
417
|
+
reply :numeric, ENDOFWHO, mask, "End of /WHO list."
|
418
|
+
else
|
419
|
+
#get all users in the channel
|
420
|
+
channel.each_user {|user|
|
421
|
+
reply :numeric, WHOREPLY ,
|
422
|
+
"#{mask} #{user.userprefix} #{user.host} #{Server.config['hostname']} #{user.nick} H" ,
|
423
|
+
"#{hopcount} #{user.realname}"
|
424
|
+
}
|
425
|
+
reply :numeric, ENDOFWHO, mask, "End of /WHO list."
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
def handle_mode(target, rest)
|
430
|
+
#TODO: dummy
|
431
|
+
reply :mode, target, rest
|
432
|
+
end
|
433
|
+
|
434
|
+
def handle_userhost(nicks)
|
435
|
+
info = []
|
436
|
+
nicks.split(/,/).each {|nick|
|
437
|
+
user = Server.user_store[nick]
|
438
|
+
info << user.nick + '=-' + user.nick + '@' + user.peer
|
439
|
+
}
|
440
|
+
reply :numeric, USERHOST,"", info.join(' ')
|
441
|
+
end
|
442
|
+
|
443
|
+
def handle_reload(password)
|
444
|
+
end
|
445
|
+
|
446
|
+
def handle_abort()
|
447
|
+
handle_quit('aborted..')
|
448
|
+
end
|
449
|
+
|
450
|
+
def handle_version()
|
451
|
+
reply :numeric, VERSION,"#{Server.config['version']} Ruby IRCD", ""
|
452
|
+
end
|
453
|
+
|
454
|
+
def handle_eval(s)
|
455
|
+
reply :raw, eval(s)
|
456
|
+
end
|
457
|
+
|
458
|
+
def handle_unknown(s)
|
459
|
+
carp "unknown:>#{s}<"
|
460
|
+
reply :numeric, ERR_UNKNOWNCOMMAND,s, "Unknown command"
|
461
|
+
end
|
462
|
+
|
463
|
+
def handle_connect
|
464
|
+
reply :raw, "NOTICE AUTH :#{Server.config['version']} initialized, welcome."
|
465
|
+
end
|
466
|
+
|
467
|
+
def reply(method, *args)
|
468
|
+
case method
|
469
|
+
when :raw
|
470
|
+
arg = *args
|
471
|
+
raw arg
|
472
|
+
when :ping
|
473
|
+
host = *args
|
474
|
+
raw "PING :#{host}"
|
475
|
+
when :pong
|
476
|
+
msg = *args
|
477
|
+
# according to rfc 2812 the PONG must be of
|
478
|
+
#PONG csd.bu.edu tolsun.oulu.fi
|
479
|
+
# PONG message from csd.bu.edu to tolsun.oulu.fi
|
480
|
+
# ie no host at the begining
|
481
|
+
raw "PONG #{@host} #{@peername} :#{msg}"
|
482
|
+
when :join
|
483
|
+
user,channel = args
|
484
|
+
raw "#{user} JOIN :#{channel}"
|
485
|
+
when :part
|
486
|
+
user,channel,msg = args
|
487
|
+
raw "#{user} PART #{channel} :#{msg}"
|
488
|
+
when :quit
|
489
|
+
user,msg = args
|
490
|
+
raw "#{user} QUIT :#{msg}"
|
491
|
+
when :privmsg
|
492
|
+
usermsg, channel, msg = args
|
493
|
+
raw "#{usermsg} PRIVMSG #{channel} :#{msg}"
|
494
|
+
when :notice
|
495
|
+
usermsg, channel, msg = args
|
496
|
+
raw "#{usermsg} NOTICE #{channel} :#{msg}"
|
497
|
+
when :topic
|
498
|
+
usermsg, channel, msg = args
|
499
|
+
raw "#{usermsg} TOPIC #{channel} :#{msg}"
|
500
|
+
when :nick
|
501
|
+
nick = *args
|
502
|
+
raw "#{@usermsg} NICK :#{nick}"
|
503
|
+
when :mode
|
504
|
+
nick, rest = args
|
505
|
+
raw "#{@usermsg} MODE #{nick} :#{rest}"
|
506
|
+
when :numeric
|
507
|
+
numeric,msg,detail = args
|
508
|
+
server = Server.config['hostname']
|
509
|
+
raw ":#{server} #{'%03d'%numeric} #{@nick} #{msg} :#{detail}"
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
def raw(arg, abrt=false)
|
514
|
+
begin
|
515
|
+
carp "--> #{arg}"
|
516
|
+
@server.send_data(arg.chomp + "\n") if !arg.nil?
|
517
|
+
rescue Exception => e
|
518
|
+
carp "<#{self.userprefix}>#{e.message}"
|
519
|
+
#carp e.backtrace.join("\n")
|
520
|
+
handle_abort()
|
521
|
+
raise e if abrt
|
522
|
+
end
|
523
|
+
end
|
524
|
+
|
525
|
+
def receive_data(data)
|
526
|
+
carp "<-- '#{data.strip}'"
|
527
|
+
s = if data =~ PREFIX
|
528
|
+
$1
|
529
|
+
else
|
530
|
+
data
|
531
|
+
end
|
532
|
+
case s
|
533
|
+
when /^[ ]*$/
|
534
|
+
return
|
535
|
+
when /^PASS +(.+)$/i
|
536
|
+
handle_pass($1.strip)
|
537
|
+
when /^NICK +(.+)$/i
|
538
|
+
handle_nick($1.strip) #done
|
539
|
+
when /^USER +([^ ]+) +([0-9]+) +([^ ]+) +:(.*)$/i
|
540
|
+
handle_user($1, $2, $3, $4) #done
|
541
|
+
when /^USER +([^ ]+) +([0-9]+) +([^ ]+) +:*(.*)$/i
|
542
|
+
#opera does this.
|
543
|
+
handle_user($1, $2, $3, $4) #done
|
544
|
+
when /^USER ([^ ]+) +[^:]*:(.*)/i
|
545
|
+
#chatzilla does this.
|
546
|
+
handle_user($1, '', '', $3) #done
|
547
|
+
when /^JOIN +(.+)$/i
|
548
|
+
handle_join($1) #done
|
549
|
+
when /^PING +([^ ]+) *(.*)$/i
|
550
|
+
handle_ping($1, $2) #done
|
551
|
+
when /^PONG +:(.+)$/i , /^PONG +(.+)$/i
|
552
|
+
handle_pong($1)
|
553
|
+
when /^PRIVMSG +([^ ]+) +:(.*)$/i
|
554
|
+
handle_privmsg($1, $2) #done
|
555
|
+
when /^NOTICE +([^ ]+) +(.*)$/i
|
556
|
+
handle_notice($1, $2) #done
|
557
|
+
when /^PART :+([^ ]+) *(.*)$/i
|
558
|
+
#some clients require this.
|
559
|
+
handle_part($1, $2) #done
|
560
|
+
when /^PART +([^ ]+) *(.*)$/i
|
561
|
+
handle_part($1, $2) #done
|
562
|
+
when /^QUIT :(.*)$/i
|
563
|
+
handle_quit($1) #done
|
564
|
+
when /^QUIT *(.*)$/i
|
565
|
+
handle_quit($1) #done
|
566
|
+
when /^TOPIC +([^ ]+) *:*(.*)$/i
|
567
|
+
handle_topic($1, $2) #done
|
568
|
+
when /^AWAY +:(.*)$/i
|
569
|
+
handle_away($1)
|
570
|
+
when /^AWAY +(.*)$/i #for opera
|
571
|
+
handle_away($1)
|
572
|
+
when /^:*([^ ])* *AWAY *$/i
|
573
|
+
handle_away(nil)
|
574
|
+
when /^AWAY\s*$/i
|
575
|
+
handle_away(nil)
|
576
|
+
when /^LIST *(.*)$/i
|
577
|
+
handle_list($1)
|
578
|
+
when /^WHOIS +([^ ]+) +(.+)$/i
|
579
|
+
handle_whois($1,$2)
|
580
|
+
when /^WHOIS +([^ ]+)$/i
|
581
|
+
handle_whois(nil,$1)
|
582
|
+
when /^WHO +([^ ]+) *(.*)$/i
|
583
|
+
handle_who($1, $2)
|
584
|
+
when /^NAMES +([^ ]+) *(.*)$/i
|
585
|
+
handle_names($1, $2)
|
586
|
+
when /^MODE +([^ ]+) *(.*)$/i
|
587
|
+
handle_mode($1, $2)
|
588
|
+
when /^USERHOST +:(.+)$/i
|
589
|
+
#besirc does this (not accourding to RFC 2812)
|
590
|
+
handle_userhost($1)
|
591
|
+
when /^USERHOST +(.+)$/i
|
592
|
+
handle_userhost($1)
|
593
|
+
when /^RELOAD +(.+)$/i
|
594
|
+
handle_reload($1)
|
595
|
+
when /^VERSION *$/i
|
596
|
+
handle_version()
|
597
|
+
when /^EVAL (.*)$/i
|
598
|
+
#strictly for debug
|
599
|
+
handle_eval($1)
|
600
|
+
else
|
601
|
+
handle_unknown(s)
|
602
|
+
end
|
603
|
+
end
|
604
|
+
end
|
605
|
+
|
606
|
+
class Channel < SynchronizedStore
|
607
|
+
attr_reader :name, :topic
|
608
|
+
alias each_user each_value
|
609
|
+
|
610
|
+
def initialize(name)
|
611
|
+
super()
|
612
|
+
|
613
|
+
@topic = "There is no topic"
|
614
|
+
@name = name
|
615
|
+
@oper = []
|
616
|
+
carp "create channel:#{@name}"
|
617
|
+
end
|
618
|
+
|
619
|
+
def add(client)
|
620
|
+
@oper << client.nick if @oper.empty? and @store.empty?
|
621
|
+
self[client.nick] = client
|
622
|
+
end
|
623
|
+
|
624
|
+
def remove(client)
|
625
|
+
delete(client.nick)
|
626
|
+
end
|
627
|
+
|
628
|
+
def join(client)
|
629
|
+
return false if is_member? client
|
630
|
+
add client
|
631
|
+
#send join to each user in the channel
|
632
|
+
each_user {|user|
|
633
|
+
user.reply :join, client.userprefix, @name
|
634
|
+
}
|
635
|
+
return true
|
636
|
+
end
|
637
|
+
|
638
|
+
def part(client, msg)
|
639
|
+
return false if !is_member? client
|
640
|
+
each_user {|user|
|
641
|
+
user.reply :part, client.userprefix, @name, msg
|
642
|
+
}
|
643
|
+
remove client
|
644
|
+
Server.channel_store.delete(@name) if self.empty?
|
645
|
+
return true
|
646
|
+
end
|
647
|
+
|
648
|
+
def quit(client, msg)
|
649
|
+
#remove client should happen before sending notification
|
650
|
+
#to others since we dont want a notification to ourselves
|
651
|
+
#after quit.
|
652
|
+
remove client
|
653
|
+
each_user {|user|
|
654
|
+
user.reply :quit, client.userprefix, @name, msg if user!= client
|
655
|
+
}
|
656
|
+
Server.channel_store.delete(@name) if self.empty?
|
657
|
+
end
|
658
|
+
|
659
|
+
def privatemsg(msg, client)
|
660
|
+
each_user {|user|
|
661
|
+
user.reply :privmsg, client.userprefix, @name, msg if user != client
|
662
|
+
}
|
663
|
+
end
|
664
|
+
|
665
|
+
def notice(msg, client)
|
666
|
+
each_user {|user|
|
667
|
+
user.reply :notice, client.userprefix, @name, msg if user != client
|
668
|
+
}
|
669
|
+
end
|
670
|
+
|
671
|
+
def topic(msg=nil,client=nil)
|
672
|
+
return @topic if msg.nil?
|
673
|
+
@topic = msg
|
674
|
+
each_user {|user|
|
675
|
+
user.reply :topic, client.userprefix, @name, msg
|
676
|
+
}
|
677
|
+
return @topic
|
678
|
+
end
|
679
|
+
|
680
|
+
def nicks
|
681
|
+
return keys
|
682
|
+
end
|
683
|
+
|
684
|
+
def mode(u)
|
685
|
+
return @oper.include?(u.nick) ? '@' : ''
|
686
|
+
end
|
687
|
+
|
688
|
+
def is_member?(m)
|
689
|
+
values.include?(m)
|
690
|
+
end
|
691
|
+
|
692
|
+
alias has_nick? is_member?
|
693
|
+
end
|
694
|
+
|
695
|
+
class Server < EventMachine::Connection
|
696
|
+
@@user_store = SynchronizedStore.new
|
697
|
+
class << @@user_store
|
698
|
+
def <<(client)
|
699
|
+
self[client.nick] = client
|
700
|
+
end
|
701
|
+
|
702
|
+
alias nicks keys
|
703
|
+
alias each_user each_value
|
704
|
+
end
|
705
|
+
@@channel_store = SynchronizedStore.new
|
706
|
+
class << @@channel_store
|
707
|
+
def add(c)
|
708
|
+
self[c] ||= Channel.new(c)
|
709
|
+
end
|
710
|
+
|
711
|
+
def remove(c)
|
712
|
+
self.delete[c]
|
713
|
+
end
|
714
|
+
|
715
|
+
alias each_channel each_value
|
716
|
+
alias channels keys
|
717
|
+
end
|
718
|
+
@@config = {
|
719
|
+
'version' => '0.04dev',
|
720
|
+
'timeout' => 10,
|
721
|
+
'port' => 6667,
|
722
|
+
'hostname' => Socket.gethostname.split(/\./).shift,
|
723
|
+
'starttime' => Time.now.to_s,
|
724
|
+
'nick-tries' => 5
|
725
|
+
}
|
726
|
+
|
727
|
+
def self.user_store
|
728
|
+
@@user_store
|
729
|
+
end
|
730
|
+
|
731
|
+
def self.channel_store
|
732
|
+
@@channel_store
|
733
|
+
end
|
734
|
+
|
735
|
+
def self.config
|
736
|
+
@@config
|
737
|
+
end
|
738
|
+
|
739
|
+
attr_reader :user_store, :channel_store, :config
|
740
|
+
|
741
|
+
def initialize
|
742
|
+
@user_store = @@user_store
|
743
|
+
@channel_store = @@channel_store
|
744
|
+
@config = @@config
|
745
|
+
end
|
746
|
+
|
747
|
+
def usermodes
|
748
|
+
return "aAbBcCdDeEfFGhHiIjkKlLmMnNopPQrRsStUvVwWxXyYzZ0123459*@"
|
749
|
+
end
|
750
|
+
|
751
|
+
def channelmodes
|
752
|
+
return "bcdefFhiIklmnoPqstv"
|
753
|
+
end
|
754
|
+
|
755
|
+
def post_init
|
756
|
+
@client = ConnectedClient.new(self)
|
757
|
+
end
|
758
|
+
|
759
|
+
def unbind
|
760
|
+
@client.handle_quit("disconnected...")
|
761
|
+
end
|
762
|
+
|
763
|
+
def receive_data(data)
|
764
|
+
data.split(/\n/).each do |line|
|
765
|
+
@client.receive_data(line)
|
766
|
+
end
|
767
|
+
end
|
768
|
+
end
|
769
|
+
end
|
770
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module IRC
|
3
|
+
module Replies
|
4
|
+
WELCOME = 001
|
5
|
+
YOURHOST = 002
|
6
|
+
CREATED = 003
|
7
|
+
MYINFO = 004
|
8
|
+
BOUNCE = 005
|
9
|
+
TRACELINK = 200
|
10
|
+
TRACECONNECTING = 201
|
11
|
+
TRACEHANDSHAKE = 202
|
12
|
+
TRACEUNKNOWN = 203
|
13
|
+
TRACEOPERATOR = 204
|
14
|
+
TRACEUSER = 205
|
15
|
+
TRACESERVER = 206
|
16
|
+
TRACESERVICE = 207
|
17
|
+
TRACENEWTYPE = 208
|
18
|
+
TRACECLASS = 209
|
19
|
+
TRACERECONNECT = 210
|
20
|
+
TRACELOG = 261
|
21
|
+
TRACEEND = 262
|
22
|
+
STATSLINKINFO = 211
|
23
|
+
STATSCOMMANDS = 212
|
24
|
+
ENDOFSTATS = 219
|
25
|
+
STATSUPTIME = 242
|
26
|
+
STATSOLINE = 243
|
27
|
+
UMODEIS = 221
|
28
|
+
SERVLIST = 234
|
29
|
+
SERVLISTEND = 235
|
30
|
+
LUSERCLIENT = 251
|
31
|
+
LUSEROP = 252
|
32
|
+
LUSERUNKNOWN = 253
|
33
|
+
LUSERCHANNELS = 254
|
34
|
+
LUSERME = 255
|
35
|
+
ADMINME = 256
|
36
|
+
ADMINEMAIL = 259
|
37
|
+
TRYAGAIN = 263
|
38
|
+
USERHOST = 302
|
39
|
+
ISON = 303
|
40
|
+
AWAY = 301
|
41
|
+
UNAWAY = 305
|
42
|
+
NOWAWAY = 306
|
43
|
+
WHOISUSER = 311
|
44
|
+
WHOISSERVER = 312
|
45
|
+
WHOISOPERATOR = 313
|
46
|
+
WHOISIDLE = 317
|
47
|
+
ENDOFWHOIS = 318
|
48
|
+
WHOISCHANNELS = 319
|
49
|
+
WHOWASUSER = 314
|
50
|
+
ENDOFWHOWAS = 369
|
51
|
+
LISTSTART = 321
|
52
|
+
LIST = 322
|
53
|
+
LISTEND = 323
|
54
|
+
UNIQOPIS = 325
|
55
|
+
CHANNELMODEIS = 324
|
56
|
+
NOTOPIC = 331
|
57
|
+
TOPIC = 332
|
58
|
+
INVITING = 341
|
59
|
+
SUMMONING = 342
|
60
|
+
INVITELIST = 346
|
61
|
+
ENDOFINVITELIST = 347
|
62
|
+
EXCEPTLIST = 348
|
63
|
+
ENDOFEXCEPTLIST = 349
|
64
|
+
VERSION = 351
|
65
|
+
WHOREPLY = 352
|
66
|
+
ENDOFWHO = 315
|
67
|
+
NAMREPLY = 353
|
68
|
+
ENDOFNAMES = 366
|
69
|
+
LINKS = 364
|
70
|
+
ENDOFLINKS = 365
|
71
|
+
BANLIST = 367
|
72
|
+
ENDOFBANLIST = 368
|
73
|
+
INFO = 371
|
74
|
+
ENDOFINFO = 374
|
75
|
+
MOTDSTART = 375
|
76
|
+
MOTD = 372
|
77
|
+
ENDOFMOTD = 376
|
78
|
+
YOUREOPER = 381
|
79
|
+
REHASHING = 382
|
80
|
+
YOURESERVICE = 383
|
81
|
+
TIME = 391
|
82
|
+
USERSSTART = 392
|
83
|
+
USERS = 393
|
84
|
+
ENDOFUSERS = 394
|
85
|
+
NOUSERS = 395
|
86
|
+
ERR_NOSUCHNICK = 401
|
87
|
+
ERR_NOSUCHSERVER = 402
|
88
|
+
ERR_NOSUCHCHANNEL = 403
|
89
|
+
ERR_CANNOTSENDTOCHAN = 404
|
90
|
+
ERR_TOOMANYCHANNELS = 405
|
91
|
+
ERR_WASNOSUCHNICK = 406
|
92
|
+
ERR_TOOMANYTARGETS = 407
|
93
|
+
ERR_NOSUCHSERVICE = 408
|
94
|
+
ERR_NOORIGIN = 409
|
95
|
+
ERR_NORECIPIENT = 411
|
96
|
+
ERR_NOTEXTTOSEND = 412
|
97
|
+
ERR_NOTOPLEVEL = 413
|
98
|
+
ERR_WILDTOPLEVEL = 414
|
99
|
+
ERR_BADMASK = 415
|
100
|
+
ERR_UNKNOWNCOMMAND = 421
|
101
|
+
ERR_NOMOTD = 422
|
102
|
+
ERR_NOADMININFO = 423
|
103
|
+
ERR_FILEERROR = 424
|
104
|
+
ERR_NONICKNAMEGIVEN = 431
|
105
|
+
ERR_ERRONEUSNICKNAME = 432
|
106
|
+
ERR_NICKNAMEINUSE = 433
|
107
|
+
ERR_NICKCOLLISION = 436
|
108
|
+
ERR_UNAVAILRESOURCE = 437
|
109
|
+
ERR_USERNOTINCHANNEL = 441
|
110
|
+
ERR_NOTONCHANNEL = 442
|
111
|
+
ERR_USERONCHANNEL = 443
|
112
|
+
ERR_NOLOGIN = 444
|
113
|
+
ERR_SUMMONDISABLED = 445
|
114
|
+
ERR_USERSDISABLED = 446
|
115
|
+
ERR_NOTREGISTERED = 451
|
116
|
+
ERR_NEEDMOREPARAMS = 461
|
117
|
+
ERR_ALREADYREGISTRED = 462
|
118
|
+
ERR_NOPERMFORHOST = 463
|
119
|
+
ERR_PASSWDMISMATCH = 464
|
120
|
+
ERR_YOUREBANNEDCREEP = 465
|
121
|
+
ERR_YOUWILLBEBANNED = 466
|
122
|
+
ERR_KEYSET = 467
|
123
|
+
ERR_CHANNELISFULL = 471
|
124
|
+
ERR_UNKNOWNMODE = 472
|
125
|
+
ERR_INVITEONLYCHAN = 473
|
126
|
+
ERR_BANNEDFROMCHAN = 474
|
127
|
+
ERR_BADCHANNELKEY = 475
|
128
|
+
ERR_BADCHANMASK = 476
|
129
|
+
ERR_NOCHANMODES = 477
|
130
|
+
ERR_BANLISTFULL = 478
|
131
|
+
ERR_NOPRIVILEGES = 481
|
132
|
+
ERR_CHANOPRIVSNEEDED = 482
|
133
|
+
ERR_CANTKILLSERVER = 483
|
134
|
+
ERR_RESTRICTED = 484
|
135
|
+
ERR_UNIQOPPRIVSNEEDED = 485
|
136
|
+
ERR_NOOPERHOST = 491
|
137
|
+
ERR_UMODEUNKNOWNFLAG = 501
|
138
|
+
ERR_USERSDONTMATCH = 502
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
unless Kernel.respond_to?(:require_relative)
|
2
|
+
module Kernel
|
3
|
+
def require_relative(path)
|
4
|
+
require File.join(File.dirname(caller[0]), path.to_str)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
require_relative 'helper'
|
10
|
+
require 'em-irc'
|
11
|
+
require 'logger'
|
12
|
+
|
13
|
+
# monkey patching broken gem
|
14
|
+
module EventMachine
|
15
|
+
module IRC
|
16
|
+
class Client
|
17
|
+
def unbind(reason)
|
18
|
+
log Logger::INFO "Unbind reason: #{reason}" if reason != nil
|
19
|
+
trigger(:disconnect)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class TestIrcServer < Minitest::Test
|
26
|
+
def test_irc_server_start
|
27
|
+
EventMachine.run {
|
28
|
+
Signal.trap("INT") { EventMachine.stop }
|
29
|
+
Signal.trap("TERM") { EventMachine.stop }
|
30
|
+
srvr = EventMachine::start_server "0.0.0.0", 6667, EventMachine::IRC::Server
|
31
|
+
testbot = EventMachine::IRC::Client.new do
|
32
|
+
host '127.0.0.1'
|
33
|
+
port '6667'
|
34
|
+
|
35
|
+
on(:connect) do
|
36
|
+
nick('testbot')
|
37
|
+
end
|
38
|
+
|
39
|
+
on(:nick) do
|
40
|
+
join('#test')
|
41
|
+
end
|
42
|
+
|
43
|
+
on(:join) do |channel| # called after joining a channel
|
44
|
+
message(channel, "howdy all")
|
45
|
+
end
|
46
|
+
|
47
|
+
on(:message) do |source, target, message| # called when being messaged
|
48
|
+
puts "<#{source}> -> <#{target}>: #{message}"
|
49
|
+
if message =~ /quit/
|
50
|
+
testbot.conn.close_connection
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# callback for all messages sent from IRC server
|
55
|
+
on(:parsed) do |hash|
|
56
|
+
puts "#{hash[:prefix]} #{hash[:command]} #{hash[:params].join(' ')}"
|
57
|
+
end
|
58
|
+
|
59
|
+
on(:disconnect) do
|
60
|
+
puts "testbot disconnected"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
botmaster = EventMachine::IRC::Client.new do
|
65
|
+
host '127.0.0.1'
|
66
|
+
port '6667'
|
67
|
+
|
68
|
+
on(:connect) do
|
69
|
+
nick('botmaster')
|
70
|
+
end
|
71
|
+
|
72
|
+
on(:nick) do
|
73
|
+
join('#test')
|
74
|
+
message('#test', 'quit')
|
75
|
+
end
|
76
|
+
|
77
|
+
on(:join) do |channel| # called after joining a channel
|
78
|
+
end
|
79
|
+
|
80
|
+
on(:message) do |source, target, message| # called when being messaged
|
81
|
+
puts "<#{source}> -> <#{target}>: #{message}"
|
82
|
+
end
|
83
|
+
|
84
|
+
# callback for all messages sent from IRC server
|
85
|
+
on(:parsed) do |hash|
|
86
|
+
puts "#{hash[:prefix]} #{hash[:command]} #{hash[:params].join(' ')}"
|
87
|
+
end
|
88
|
+
|
89
|
+
on(:disconnect) do
|
90
|
+
puts "botmaster disconnected"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
testbot.connect
|
94
|
+
|
95
|
+
timer = EventMachine::Timer.new(1) do
|
96
|
+
botmaster.connect
|
97
|
+
end
|
98
|
+
timer2 = EventMachine::Timer.new(5) do
|
99
|
+
EM.stop
|
100
|
+
end
|
101
|
+
|
102
|
+
}
|
103
|
+
end
|
104
|
+
end
|
metadata
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: eventmachine-irc-server
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- chrislee35
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-02-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: eventmachine
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.12.10
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.12.10
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.7'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.7'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.5'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.5'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: em-irc
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.0.2
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.0.2
|
83
|
+
description: For use in the Rubot Emulation Framework, this simple IRC server allows
|
84
|
+
test bots to connect and receive commands.
|
85
|
+
email:
|
86
|
+
- rubygems@chrislee.dhs.org
|
87
|
+
executables:
|
88
|
+
- em_irc_server.rb
|
89
|
+
extensions: []
|
90
|
+
extra_rdoc_files: []
|
91
|
+
files:
|
92
|
+
- ".gitignore"
|
93
|
+
- Gemfile
|
94
|
+
- LICENSE.txt
|
95
|
+
- README.md
|
96
|
+
- Rakefile
|
97
|
+
- bin/em_irc_server.rb
|
98
|
+
- eventmachine-irc-server.gemspec
|
99
|
+
- lib/eventmachine/irc/server.rb
|
100
|
+
- lib/eventmachine/irc/server/replies.rb
|
101
|
+
- lib/eventmachine/irc/server/version.rb
|
102
|
+
- test/helper.rb
|
103
|
+
- test/test_irc_server.rb
|
104
|
+
homepage: http://github.com/chrislee35/eventmachine-irc-server
|
105
|
+
licenses:
|
106
|
+
- MIT
|
107
|
+
metadata: {}
|
108
|
+
post_install_message:
|
109
|
+
rdoc_options: []
|
110
|
+
require_paths:
|
111
|
+
- lib
|
112
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
requirements: []
|
123
|
+
rubyforge_project:
|
124
|
+
rubygems_version: 2.2.2
|
125
|
+
signing_key:
|
126
|
+
specification_version: 4
|
127
|
+
summary: Simple EventMachine-based IRC server. 簡単なイベントマシーンのIRCのサーバーです。
|
128
|
+
test_files:
|
129
|
+
- test/helper.rb
|
130
|
+
- test/test_irc_server.rb
|