eventmachine-irc-server 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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