em-irc 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -1
- data/.rvmrc +1 -1
- data/.travis.yml +6 -2
- data/Gemfile +5 -1
- data/Guardfile +8 -1
- data/README.md +33 -25
- data/Rakefile +9 -1
- data/lib/em-irc.rb +5 -0
- data/lib/em-irc/client.rb +62 -98
- data/lib/em-irc/commands.rb +358 -0
- data/lib/em-irc/dispatcher.rb +2 -6
- data/lib/em-irc/responses.rb +88 -0
- data/lib/em-irc/version.rb +1 -1
- data/lib/support/dsl_accessor.rb +22 -0
- data/spec/integration/integration_spec.rb +31 -11
- data/spec/lib/em-irc/client_spec.rb +44 -36
- data/spec/lib/em-irc/commands_spec.rb +166 -0
- data/spec/lib/em-irc/dispatcher_spec.rb +16 -16
- data/spec/lib/em-irc/responses_spec.rb +46 -0
- metadata +13 -6
data/.gitignore
CHANGED
data/.rvmrc
CHANGED
@@ -1 +1 @@
|
|
1
|
-
rvm use 1.9.3
|
1
|
+
rvm use 1.9.3@em-irc
|
data/.travis.yml
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
rvm:
|
2
|
-
- 1.9.2
|
3
2
|
- 1.9.3
|
3
|
+
- 1.9.2
|
4
|
+
- 1.8.7
|
5
|
+
- ree
|
6
|
+
- rbx
|
7
|
+
- ruby-head
|
4
8
|
bundler_args: --without darwin --without development
|
5
9
|
gemfile:
|
6
10
|
- Gemfile
|
@@ -9,4 +13,4 @@ before_script:
|
|
9
13
|
- "service ngircd stop"
|
10
14
|
- "ngircd -f spec/config/ngircd-unencrypted.conf"
|
11
15
|
- "ngircd -f spec/config/ngircd-encrypted-gnutls.conf"
|
12
|
-
script: "bundle exec rake"
|
16
|
+
script: "bundle exec rake spec"
|
data/Gemfile
CHANGED
@@ -4,7 +4,6 @@ source 'http://rubygems.org'
|
|
4
4
|
gemspec
|
5
5
|
|
6
6
|
group :development do
|
7
|
-
gem 'pry'
|
8
7
|
gem 'yard'
|
9
8
|
gem 'redcarpet'
|
10
9
|
gem 'guard'
|
@@ -13,6 +12,7 @@ group :development do
|
|
13
12
|
end
|
14
13
|
|
15
14
|
group :development, :test do
|
15
|
+
gem 'pry'
|
16
16
|
gem 'rake'
|
17
17
|
gem 'rspec', "~> 2"
|
18
18
|
end
|
@@ -20,4 +20,8 @@ end
|
|
20
20
|
group :darwin do
|
21
21
|
gem 'rb-fsevent'
|
22
22
|
gem 'growl'
|
23
|
+
end
|
24
|
+
|
25
|
+
platform :ruby_19 do
|
26
|
+
gem 'guard-yard', :group => :development
|
23
27
|
end
|
data/Guardfile
CHANGED
@@ -5,9 +5,16 @@ guard 'bundler' do
|
|
5
5
|
watch('Gemfile')
|
6
6
|
end
|
7
7
|
|
8
|
-
guard 'rspec', :version => 2 do
|
8
|
+
guard 'rspec', :version => 2, :cli => '--tag ~integration' do
|
9
9
|
watch(%r{^spec/.+_spec\.rb$})
|
10
10
|
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
11
11
|
watch('lib/em-irc.rb') { "spec" }
|
12
12
|
watch('spec/spec_helper.rb') { "spec" }
|
13
|
+
end
|
14
|
+
|
15
|
+
begin
|
16
|
+
guard 'yard', :stdout => '/dev/null' do
|
17
|
+
watch(%r{lib/.+\.rb})
|
18
|
+
end
|
19
|
+
rescue => e
|
13
20
|
end
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# EventMachine IRC Client
|
2
2
|
|
3
|
-
[
|
3
|
+
[![Build Status](https://secure.travis-ci.org/jch/em-irc.png?branch=master)](http://travis-ci.org/jch/em-irc)
|
4
4
|
|
5
5
|
em-irc is an IRC client that uses EventMachine to handle connections to servers.
|
6
6
|
|
@@ -9,26 +9,29 @@ em-irc is an IRC client that uses EventMachine to handle connections to servers.
|
|
9
9
|
````ruby
|
10
10
|
require 'em-irc'
|
11
11
|
|
12
|
-
client = EventMachine::IRC::Client.new do
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
client = EventMachine::IRC::Client.new do
|
13
|
+
host 'irc.freenode.net'
|
14
|
+
port '6667'
|
15
|
+
|
16
|
+
on(:connect) do
|
17
|
+
nick('jch')
|
18
|
+
end
|
16
19
|
|
17
|
-
|
20
|
+
on(:nick) do
|
18
21
|
join('#general')
|
19
22
|
join('#private', 'key')
|
20
23
|
end
|
21
24
|
|
22
|
-
|
25
|
+
on(:join) do |channel| # called after joining a channel
|
23
26
|
message(channel, "howdy all")
|
24
27
|
end
|
25
28
|
|
26
|
-
|
29
|
+
on(:message) do |source, target, message| # called when being messaged
|
27
30
|
puts "<#{source}> -> <#{target}>: #{message}"
|
28
31
|
end
|
29
32
|
|
30
33
|
# callback for all messages sent from IRC server
|
31
|
-
|
34
|
+
on(:raw) do |hash|
|
32
35
|
puts "#{hash[:prefix]} #{hash[:command]} #{hash[:params].join(' ')}"
|
33
36
|
end
|
34
37
|
end
|
@@ -36,21 +39,34 @@ end
|
|
36
39
|
client.run! # start EventMachine loop
|
37
40
|
````
|
38
41
|
|
39
|
-
|
40
|
-
|
41
|
-
In the examples folder, there are runnable examples.
|
42
|
+
Alternatively, if local variable access is needed, the first block variable is
|
43
|
+
the client:
|
42
44
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
45
|
+
````ruby
|
46
|
+
client = EventMachine::IRC::Client.new do |c|
|
47
|
+
# c is the client instance
|
48
|
+
end
|
49
|
+
````
|
47
50
|
|
48
51
|
## References
|
49
52
|
|
53
|
+
* [API Documentation](http://rubydoc.info/gems/em-irc/0.0.1/frames)
|
50
54
|
* [RFC 1459 - Internet Relay Chat Protocol](http://tools.ietf.org/html/rfc1459) overview of IRC architecture
|
51
55
|
* [RFC 2812 - Internet Relay Chat: Client Protocol](http://tools.ietf.org/html/rfc2812) specifics of client protocol
|
52
56
|
* [RFC 2813 - Internet Relay Chat: Server Protocol](http://tools.ietf.org/html/rfc2813) specifics of server protocol
|
53
57
|
|
58
|
+
## Platforms
|
59
|
+
|
60
|
+
The following platforms are tentatively supported:
|
61
|
+
|
62
|
+
* 1.8.7
|
63
|
+
* 1.9.2
|
64
|
+
* 1.9.3
|
65
|
+
* ree
|
66
|
+
* Rubinius
|
67
|
+
|
68
|
+
I currently develop ruby 1.9.3, so it'll be the VM that gets the most support.
|
69
|
+
|
54
70
|
## Development
|
55
71
|
|
56
72
|
To run integration specs, you'll need to run a ssl and a non-ssl irc server locally.
|
@@ -89,12 +105,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
89
105
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
90
106
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
91
107
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
92
|
-
THE SOFTWARE.
|
93
|
-
|
94
|
-
## TODO
|
95
|
-
|
96
|
-
* can we skip using Dispatcher connection handler class?
|
97
|
-
* extract :on, :trigger callback gem that works on instances. [hook](https://github.com/apotonick/hooks), but works with instances
|
98
|
-
* would prefer the interface to look synchronous, but work async
|
99
|
-
* ssl dispatcher testing
|
100
|
-
* speed up integration specs
|
108
|
+
THE SOFTWARE.
|
data/Rakefile
CHANGED
@@ -2,5 +2,13 @@
|
|
2
2
|
require "bundler/gem_tasks"
|
3
3
|
Bundler::GemHelper.install_tasks
|
4
4
|
require 'rspec/core/rake_task'
|
5
|
-
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new('spec') do |t|
|
7
|
+
t.rspec_opts = '--tag ~integration'
|
8
|
+
end
|
9
|
+
|
10
|
+
RSpec::Core::RakeTask.new('spec:ci') do |t|
|
11
|
+
t.pattern = 'spec/integration/*_spec.rb'
|
12
|
+
end
|
13
|
+
|
6
14
|
task :default => :spec
|
data/lib/em-irc.rb
CHANGED
@@ -4,6 +4,9 @@ Bundler.setup :default
|
|
4
4
|
require 'active_support/core_ext/hash/keys'
|
5
5
|
require 'active_support/core_ext/object/blank'
|
6
6
|
require 'active_support/callbacks'
|
7
|
+
require 'active_support/deprecation'
|
8
|
+
require 'active_support/concern'
|
9
|
+
require 'active_support/core_ext/array/extract_options'
|
7
10
|
require 'eventmachine'
|
8
11
|
require 'forwardable'
|
9
12
|
require 'set'
|
@@ -14,5 +17,7 @@ module EventMachine
|
|
14
17
|
module IRC
|
15
18
|
autoload :Client, 'em-irc/client'
|
16
19
|
autoload :Dispatcher, 'em-irc/dispatcher'
|
20
|
+
autoload :Commands, 'em-irc/commands'
|
21
|
+
autoload :Responses, 'em-irc/responses'
|
17
22
|
end
|
18
23
|
end
|
data/lib/em-irc/client.rb
CHANGED
@@ -1,22 +1,35 @@
|
|
1
|
+
require 'support/dsl_accessor'
|
2
|
+
|
1
3
|
module EventMachine
|
2
4
|
module IRC
|
3
5
|
class Client
|
6
|
+
include DslAccessor
|
7
|
+
include IRC::Commands
|
8
|
+
include IRC::Responses
|
9
|
+
|
4
10
|
# EventMachine::Connection object to IRC server
|
5
11
|
# @private
|
6
12
|
attr_accessor :conn
|
7
13
|
|
8
|
-
#
|
9
|
-
|
14
|
+
# @macro dsl_accessor
|
15
|
+
# Accessor for `$1`
|
16
|
+
# Defaults to '127.0.0.1'
|
17
|
+
dsl_accessor :host
|
18
|
+
|
19
|
+
# @macro dsl_accessor
|
20
|
+
# Defaults to '6667'
|
21
|
+
dsl_accessor :port
|
22
|
+
|
23
|
+
# @macro dsl_accessor
|
24
|
+
dsl_accessor :realname
|
10
25
|
|
11
|
-
|
12
|
-
|
13
|
-
attr_accessor :ssl
|
26
|
+
# @macro dsl_accessor
|
27
|
+
dsl_accessor :ssl
|
14
28
|
|
15
|
-
#
|
16
|
-
|
29
|
+
# @macro dsl_accessor
|
30
|
+
dsl_accessor :logger
|
17
31
|
|
18
32
|
# Set of channels that this client is connected to
|
19
|
-
# @private
|
20
33
|
attr_reader :channels
|
21
34
|
|
22
35
|
# Hash of callbacks on events. key is symbol event name.
|
@@ -30,29 +43,33 @@ module EventMachine
|
|
30
43
|
# @option options [String] :host
|
31
44
|
# @option options [String] :port
|
32
45
|
# @option options [Boolean] :ssl
|
33
|
-
# @option options [String] :nick
|
34
46
|
# @option options [String] :realname
|
35
47
|
#
|
36
48
|
# @yield [client] new instance for decoration
|
37
49
|
def initialize(options = {}, &blk)
|
38
50
|
options.symbolize_keys!
|
39
51
|
options = {
|
40
|
-
host
|
41
|
-
port
|
42
|
-
ssl
|
43
|
-
realname
|
44
|
-
nick: "guest-#{Time.now.to_i % 1000}"
|
52
|
+
:host => '127.0.0.1',
|
53
|
+
:port => '6667',
|
54
|
+
:ssl => false,
|
55
|
+
:realname => 'Anonymous Annie'
|
45
56
|
}.merge!(options)
|
46
57
|
|
47
58
|
@host = options[:host]
|
48
59
|
@port = options[:port]
|
49
60
|
@ssl = options[:ssl]
|
50
61
|
@realname = options[:realname]
|
51
|
-
@nick = options[:nick]
|
52
62
|
@channels = Set.new
|
53
63
|
@callbacks = Hash.new
|
54
64
|
@connected = false
|
55
|
-
|
65
|
+
|
66
|
+
if block_given?
|
67
|
+
if blk.arity == 1
|
68
|
+
yield self
|
69
|
+
else
|
70
|
+
instance_eval(&blk)
|
71
|
+
end
|
72
|
+
end
|
56
73
|
end
|
57
74
|
|
58
75
|
# Creates a Eventmachine TCP connection with :host and :port. It should be called
|
@@ -60,7 +77,7 @@ module EventMachine
|
|
60
77
|
# @see #on
|
61
78
|
# @return [EventMachine::Connection]
|
62
79
|
def connect
|
63
|
-
self.conn ||= EventMachine::connect(@host, @port, Dispatcher, parent:
|
80
|
+
self.conn ||= EventMachine::connect(@host, @port, Dispatcher, :parent => self, :ssl => @ssl)
|
64
81
|
end
|
65
82
|
|
66
83
|
# @return [Boolean]
|
@@ -68,9 +85,22 @@ module EventMachine
|
|
68
85
|
@connected
|
69
86
|
end
|
70
87
|
|
71
|
-
#
|
88
|
+
# Start running the client
|
89
|
+
def run!
|
90
|
+
EM.epoll
|
91
|
+
EventMachine.run do
|
92
|
+
trap("TERM") { EM::stop }
|
93
|
+
trap("INT") { EM::stop }
|
94
|
+
connect
|
95
|
+
log Logger::INFO, "Starting IRC client..."
|
96
|
+
end
|
97
|
+
log Logger::INFO, "Stopping IRC client"
|
98
|
+
@logger.close if @logger
|
99
|
+
end
|
100
|
+
|
101
|
+
# === Callbacks
|
72
102
|
|
73
|
-
# Register a callback with
|
103
|
+
# Register a callback with `:name` as one of the following, and
|
74
104
|
# a block with the same number of params.
|
75
105
|
#
|
76
106
|
# @example
|
@@ -97,13 +127,20 @@ module EventMachine
|
|
97
127
|
end
|
98
128
|
|
99
129
|
# Trigger a named callback
|
130
|
+
# @private
|
100
131
|
def trigger(name, *args)
|
101
132
|
# TODO: should this be instance_eval(&blk)? prevents it from non-dsl style
|
102
133
|
(@callbacks[name.to_sym] || []).each {|blk| blk.call(*args)}
|
103
134
|
end
|
104
135
|
|
136
|
+
# @private
|
137
|
+
def log(*args)
|
138
|
+
@logger.log(*args) if @logger
|
139
|
+
end
|
140
|
+
|
105
141
|
# Sends raw message to IRC server. Assumes message is correctly formatted
|
106
142
|
# TODO: what if connect fails? or disconnects?
|
143
|
+
# @private
|
107
144
|
def send_data(message)
|
108
145
|
return false unless connected?
|
109
146
|
message = message + "\r\n"
|
@@ -111,82 +148,20 @@ module EventMachine
|
|
111
148
|
self.conn.send_data(message)
|
112
149
|
end
|
113
150
|
|
114
|
-
#
|
115
|
-
# See [RFC 2812](http://tools.ietf.org/html/rfc2812)
|
116
|
-
def renick(nick)
|
117
|
-
send_data("NICK #{nick}")
|
118
|
-
end
|
119
|
-
|
120
|
-
def user(username, mode, realname)
|
121
|
-
send_data("USER #{username} #{mode} * :#{realname}")
|
122
|
-
end
|
123
|
-
|
124
|
-
def join(channel_name, channel_key = nil)
|
125
|
-
send_data("JOIN #{channel_name} #{channel_key}".strip)
|
126
|
-
end
|
127
|
-
|
128
|
-
def pong(servername)
|
129
|
-
send_data("PONG :#{servername}")
|
130
|
-
end
|
131
|
-
|
132
|
-
# @param target [String] nick or channel name
|
133
|
-
# @param message [String]
|
134
|
-
def privmsg(target, message)
|
135
|
-
send_data("PRIVMSG #{target} :#{message}")
|
136
|
-
end
|
137
|
-
alias_method :message, :privmsg
|
138
|
-
|
139
|
-
def quit(message = 'leaving')
|
140
|
-
send_data("QUIT :#{message}")
|
141
|
-
end
|
142
|
-
|
143
|
-
# @return [Hash] h
|
144
|
-
# @option h [String] :prefix
|
145
|
-
# @option h [String] :command
|
146
|
-
# @option h [Array] :params
|
151
|
+
# === EventMachine Callbacks
|
147
152
|
# @private
|
148
|
-
def parse_message(message)
|
149
|
-
# TODO: error handling
|
150
|
-
result = {}
|
151
|
-
parts = message.split(' ')
|
152
|
-
result[:prefix] = parts.shift.gsub(/^:/, '') if parts[0] =~ /^:/
|
153
|
-
result[:command] = parts[0] # cleanup?
|
154
|
-
result[:params] = parts.slice(1..-1).map {|s| s.gsub(/^:/, '')}
|
155
|
-
result
|
156
|
-
end
|
157
|
-
|
158
|
-
def handle_parsed_message(m)
|
159
|
-
case m[:command]
|
160
|
-
when '001' # welcome message
|
161
|
-
when 'PING'
|
162
|
-
pong(m[:params].first)
|
163
|
-
trigger(:ping, *m[:params])
|
164
|
-
when 'PRIVMSG'
|
165
|
-
trigger(:message, m[:prefix], m[:params].first, m[:params].slice(1..-1).join(' '))
|
166
|
-
when 'QUIT'
|
167
|
-
when 'JOIN'
|
168
|
-
trigger(:join, m[:prefix], m[:params].first)
|
169
|
-
else
|
170
|
-
# noop
|
171
|
-
# {:prefix=>"irc.the.net", :command=>"433", :params=>["*", "one", "Nickname", "already", "in", "use", "irc.the.net", "451", "*", "Connection", "not", "registered"]}
|
172
|
-
# {:prefix=>"irc.the.net", :command=>"432", :params=>["*", "one_1328243723", "Erroneous", "nickname"]}
|
173
|
-
end
|
174
|
-
trigger(:raw, m)
|
175
|
-
end
|
176
|
-
|
177
|
-
# EventMachine Callbacks
|
178
153
|
def receive_data(data)
|
179
154
|
data.split("\r\n").each do |message|
|
180
155
|
parsed = parse_message(message)
|
181
156
|
handle_parsed_message(parsed)
|
157
|
+
trigger(:raw, parsed)
|
182
158
|
end
|
183
159
|
end
|
184
160
|
|
185
161
|
# @private
|
186
162
|
def ready
|
187
163
|
@connected = true
|
188
|
-
|
189
|
-
user(@nick, '0', @realname)
|
164
|
+
user('guest', '0', @realname)
|
190
165
|
trigger(:connect)
|
191
166
|
end
|
192
167
|
|
@@ -195,20 +170,9 @@ module EventMachine
|
|
195
170
|
trigger(:disconnect)
|
196
171
|
end
|
197
172
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
def run!
|
203
|
-
EM.epoll
|
204
|
-
EventMachine.run do
|
205
|
-
trap("TERM") { EM::stop }
|
206
|
-
trap("INT") { EM::stop }
|
207
|
-
connect
|
208
|
-
log Logger::INFO, "Starting IRC client..."
|
209
|
-
end
|
210
|
-
log Logger::INFO, "Stopping IRC client"
|
211
|
-
@logger.close if @logger
|
173
|
+
protected
|
174
|
+
def channel?(string)
|
175
|
+
!!(string =~ /^(#|&)/)
|
212
176
|
end
|
213
177
|
end
|
214
178
|
end
|