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.
@@ -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
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in eventmachine-irc-server.gemspec
4
+ gemspec
@@ -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.
@@ -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
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rake/testtask'
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << 'lib'
8
+ t.test_files = FileList['test/test_*.rb']
9
+ t.verbose = true
10
+ end
11
+
12
+ task :default => :test
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ require 'eventmachine/irc/server'
3
+
4
+ EventMachine.run {
5
+ Signal.trap("INT") { EventMachine.stop }
6
+ Signal.trap("TERM") { EventMachine.stop }
7
+ srvr = EventMachine::start_server "0.0.0.0", 6667, EventMachine::Irc::Server
8
+ }
@@ -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
@@ -0,0 +1,9 @@
1
+ require 'eventmachine'
2
+
3
+ module EventMachine
4
+ module IRC
5
+ class Server < EventMachine::Connection
6
+ VERSION = "0.0.1"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest/test'
3
+ require 'minitest/unit'
4
+ include MiniTest::Assertions
5
+ require File.expand_path('../../lib/eventmachine/irc/server.rb', __FILE__)
@@ -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