em-irc 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.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
|
+
[](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
|