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.
@@ -0,0 +1,358 @@
1
+ module EventMachine
2
+ module IRC
3
+ # Client commands
4
+ # @see http://tools.ietf.org/html/rfc2812 RFC 2812
5
+ module Commands
6
+ # Set connection password
7
+ # @see http://tools.ietf.org/html/rfc2812#section-3.1.1 3.1.1 Password message
8
+ def pass(password)
9
+ send_data("PASS #{password}")
10
+ end
11
+
12
+ # Set/get user nick
13
+ # @return [String] nick if no param
14
+ # @return nil otherwise
15
+ # @see http://tools.ietf.org/html/rfc2812#section-3.1.2 3.1.2 Nick Message
16
+ def nick(nick = nil)
17
+ if nick
18
+ send_data("NICK #{nick}")
19
+ else
20
+ @nick
21
+ end
22
+ end
23
+
24
+ # Set username, hostname, and realname
25
+ # @see http://tools.ietf.org/html/rfc2812#section-3.1.3 3.1.3 User Message
26
+ def user(username, mode, realname)
27
+ send_data("USER #{username} #{mode} * :#{realname}")
28
+ end
29
+
30
+ # Gain operator privledges
31
+ # @see http://tools.ietf.org/html/rfc2812#section-3.1.4 3.1.4 Oper Message
32
+ def oper(name, password)
33
+ send_data("OPER #{name} #{password}")
34
+ end
35
+
36
+ # Set user mode
37
+ # @see http://tools.ietf.org/html/rfc2812#section-3.1.5 3.1.5 Mode Message
38
+ def mode(nickname, setting)
39
+ raise NotImplementedError.new
40
+ end
41
+
42
+ # Register a new service
43
+ # @see http://tools.ietf.org/html/rfc2812#section-3.1.6 3.1.6 Service Message
44
+ def service(nickname, reserved, distribution, type)
45
+ end
46
+
47
+ # Terminate connection
48
+ # @see http://tools.ietf.org/html/rfc2812#section-3.1.7 3.1.7 Quit
49
+ def quit(message = 'leaving')
50
+ send_data("QUIT :#{message}")
51
+ end
52
+
53
+ # Disconnect server links
54
+ # @see http://tools.ietf.org/html/rfc2812#section-3.1.8 3.1.8 Squit
55
+ def squit(server, message = "quiting")
56
+ raise NotImplementedError.new
57
+ end
58
+
59
+ # Join a channel
60
+ # @see http://tools.ietf.org/html/rfc2812#section-3.2.1 3.2.1 Join message
61
+ # @example
62
+ # client.join("#general")
63
+ # client.join("#general", "fubar") # join #general with fubar key
64
+ # client.join(['#general', 'fubar'], "#foo") # join multiple channels
65
+ def join(*args)
66
+ raise ArgumentError.new("Not enough arguments") unless args.size > 0
67
+ channels, keys = [], []
68
+ args.map! {|arg| arg.is_a?(Array) ? arg : [arg, '']}
69
+ args.sort! {|a,b| b[1].length <=> a[1].length} # key channels first
70
+ args.each {|arg|
71
+ channels << arg[0]
72
+ keys << arg[1] if arg[1].length > 0
73
+ }
74
+ send_data("JOIN #{channels.join(',')} #{keys.join(',')}".strip)
75
+ end
76
+
77
+ # Part all channels
78
+ def part_all
79
+ join('0')
80
+ end
81
+
82
+ # Leave a channel
83
+ # @see http://tools.ietf.org/html/rfc2812#section-3.2.2 3.2.2 Part message
84
+ # @example
85
+ # client.part('#general')
86
+ # client.part('#general', '#foo')
87
+ # client.part('#general', 'Bye all!')
88
+ # client.part('#general', '#foo', 'Bye all!')
89
+ def part(*args)
90
+ raise ArgumentError.new("Not enough arguments") unless args.size > 0
91
+ message = channel?(args.last) ? "Leaving..." : args.pop
92
+ send_data("PART #{args.join(',')} :#{message}")
93
+ end
94
+
95
+ # Set channel mode
96
+ # @todo name conflict with user MODE message
97
+ def channel_mode
98
+ raise NotImplementedError.new
99
+ end
100
+
101
+ # Set/get topic
102
+ # @param topic [Mixed] String, nil
103
+ # non-blank string sets the topic
104
+ # blank string unsets the topic
105
+ # nil returns the current topic (default)
106
+ # @see http://tools.ietf.org/html/rfc2812#section-3.2.4 3.2.4 Topic message
107
+ def topic(channel, message = nil)
108
+ message = message.nil? ? "" : ":#{message}"
109
+ send_data("TOPIC #{channel} #{message}".strip)
110
+ end
111
+
112
+ # List all nicknames visible to user
113
+ # @param args [Multiple] list of channels to list nicks
114
+ # if last argument is a hash, then :target can request
115
+ # which server generates response
116
+ # @see http://tools.ietf.org/html/rfc2812#section-3.2.5 3.2.5 Names message
117
+ def names(*args)
118
+ options = args.extract_options!
119
+ send_data("NAMES #{args.join(',')} #{options[:target]}".strip)
120
+ end
121
+
122
+ # List channels and topics
123
+ # @param args [Multiple] list of channels
124
+ # if last argument is a hash, then :target can request
125
+ # which server generates response
126
+ # @see http://tools.ietf.org/html/rfc2812#section-3.2.6 3.2.6 List message
127
+ def list(*args)
128
+ options = args.extract_options!
129
+ send_data("LIST #{args.join(',')} #{options[:target]}".strip)
130
+ end
131
+
132
+
133
+ # Invite a user to a channel
134
+ # @param nickname [String] to invite
135
+ # @param channel [String] to invite to
136
+ # @see http://tools.ietf.org/html/rfc2812#section-3.2.7 3.2.7 Invite message
137
+ def invite(nickname, channel)
138
+ send_data("INVITE #{nickname} #{channel}")
139
+ end
140
+
141
+ # Kick a user from a channel
142
+ # @param args [Multiple]
143
+ # @example
144
+ # client.kick('#general', 'jch')
145
+ # client.kick('#general', '&bar', 'jch', 'wcc')
146
+ # @see http://tools.ietf.org/html/rfc2812#section-3.2.8 3.2.8 Kick message
147
+ def kick(*args)
148
+ channels = args.select {|arg| channel?(arg)}
149
+ nicks = args.select {|arg| !channel?(arg)}
150
+ raise ArgumentError.new("Missing channels") if channels.empty?
151
+ send_data("KICK #{channels.join(',')} #{nicks.join(',')}".strip)
152
+ end
153
+
154
+ # Send message to user or channel
155
+ # @param target [String] nick or channel name
156
+ # @param message [String]
157
+ # @see http://tools.ietf.org/html/rfc2812#section-3.3.1 3.3.1 Private message
158
+ def privmsg(target, message)
159
+ send_data("PRIVMSG #{target} :#{message}")
160
+ end
161
+ alias_method :message, :privmsg
162
+
163
+ # Send message to user or channel
164
+ # @param target [String] nick or channel name
165
+ # @param message [String]
166
+ # @see http://tools.ietf.org/html/rfc2812#section-3.3.2 3.3.2 Notice message
167
+ def notice(target, message)
168
+ send_data("NOTICE #{target} :#{message}")
169
+ end
170
+
171
+ # Get message of the day for a server or current server
172
+ # @param target [String] server name or current server if nil
173
+ # @see http://tools.ietf.org/html/rfc2812#section-3.4.1 3.4.1 Motd message
174
+ def motd(target = nil)
175
+ send_data("MOTD #{target}".strip)
176
+ end
177
+
178
+ # List users
179
+ # @see http://tools.ietf.org/html/rfc2812#section-3.4.2 3.4.2 Lusers message
180
+ def lusers(mask = nil, target = nil)
181
+ send_data("LUSERS #{mask} #{target}".strip)
182
+ end
183
+
184
+ # Get server version
185
+ # @param target [String] server or current server if nil
186
+ # @see http://tools.ietf.org/html/rfc2812#section-3.4.3 3.4.3 Version message
187
+ def version(target = nil)
188
+ send_data("VERSION #{target}".strip)
189
+ end
190
+
191
+ # Get stats for a server
192
+ # @see http://tools.ietf.org/html/rfc2812#section-3.4.4 3.4.4 Stats message
193
+ def stats(query = nil, target = nil)
194
+ send_data("STATS #{query} #{target}".strip)
195
+ end
196
+
197
+ # List all servernames
198
+ # @see http://tools.ietf.org/html/rfc2812#section-3.4.5 3.4.5 Links message
199
+ def links(remote_server = nil, server_mask = nil)
200
+ send_data("LINKS #{remote_server} #{server_mask}".strip)
201
+ end
202
+
203
+ # Get server local time
204
+ # @see http://tools.ietf.org/html/rfc2812#section-3.4.6 3.4.6 Time message
205
+ def time(target = nil)
206
+ send_data("TIME #{target}".strip)
207
+ end
208
+
209
+ # Connect to another server
210
+ # @see http://tools.ietf.org/html/rfc2812#section-3.4.7 3.4.7 Connect message
211
+ def server_connect(target, port, remote = nil)
212
+ send_data("CONNECT #{target} #{port} #{remote}".strip)
213
+ end
214
+
215
+ # Find the route to a specific server
216
+ # @see http://tools.ietf.org/html/rfc2812#section-3.4.8 3.4.8 Trace message
217
+ def trace(target = nil)
218
+ send_data("TRACE #{target}".strip)
219
+ end
220
+
221
+ # Find info about admin of a given server
222
+ # @see http://tools.ietf.org/html/rfc2812#section-3.4.9 3.4.9 Admin message
223
+ def admin(target = nil)
224
+ send_data("ADMIN #{target}".strip)
225
+ end
226
+
227
+ # Describe server information
228
+ # @see http://tools.ietf.org/html/rfc2812#section-3.4.10 3.4.10 Info message
229
+ def info(target = nil)
230
+ send_data("INFO #{target}".strip)
231
+ end
232
+
233
+ # List services connected to network
234
+ # @see http://tools.ietf.org/html/rfc2812#section-3.5.1 3.5.1 Servlist message
235
+ def servlist(mask = nil, type = nil)
236
+ send_data("SERVLIST #{mask} #{type}".strip)
237
+ end
238
+ alias_method :server_list, :servlist
239
+
240
+ # Send a message to a service
241
+ # @see http://tools.ietf.org/html/rfc2812#section-3.5.2 3.5.2 Squery message
242
+ def squery(service, text)
243
+ send_data("SQUERY #{service} :#{text}")
244
+ end
245
+
246
+ # Get info about a user
247
+ # @see http://tools.ietf.org/html/rfc2812#section-3.6.1 3.6.1 Who message
248
+ def who(mask, mode = 'o')
249
+ send_data("WHO #{mask} #{mode}")
250
+ end
251
+
252
+ # Get user information about a list of users
253
+ # @param args [Multiple]
254
+ # if last arg is a hash, :target specifies which server to query
255
+ # @see http://tools.ietf.org/html/rfc2812#section-3.6.2 3.6.2 Whois message
256
+ def whois(*args)
257
+ options = args.extract_options!
258
+ target = options[:target] ? "#{options[:target]} " : ''
259
+ send_data("WHOIS #{target}#{args.join(',')}")
260
+ end
261
+
262
+ # Get user information that no longer exists (nick changed, etc)
263
+ # @param args [Multiple] list of nicknames
264
+ # @param options [Hash]
265
+ # @option options [Integer] :count number of history entries to list
266
+ # @option options [Integer] :target
267
+ # @example
268
+ # client.whowas('jch')
269
+ # client.whowas('jch', 'foo')
270
+ # client.whowas('jch', 'foo', :count => 5, :target => 'irc.net')
271
+ # @see http://tools.ietf.org/html/rfc2812#section-3.6.3 3.6.3 Whowas message
272
+ def whowas(*args)
273
+ options = args.extract_options!
274
+ send_data("WHOWAS #{args.join(',')} #{options[:count]} #{options[:target]}".strip)
275
+ end
276
+
277
+ # Terminate a connection by nickname
278
+ # @see http://tools.ietf.org/html/rfc2812#section-3.7.1 3.7.1 Kill message
279
+ def kill(nickname, comment = "Connection killed")
280
+ send_data("KILL #{nickname} :#{comment}".strip)
281
+ end
282
+
283
+ # Test connection is alive
284
+ # @see http://tools.ietf.org/html/rfc2812#section-3.7.2 3.7.2 Ping message
285
+ def ping(server, target = '')
286
+ send_data("PING #{server} #{target}".strip)
287
+ end
288
+
289
+ # Respond to a server ping
290
+ # @see http://tools.ietf.org/html/rfc2812#section-3.7.3 3.7.3 Pong message
291
+ def pong(*servers)
292
+ send_data("PONG #{servers.join(' ')}")
293
+ end
294
+
295
+ # Server serious or fatal error, or to terminate a connection on quit
296
+ # @see http://tools.ietf.org/html/rfc2812#section-3.7.4 3.7.4 Error message
297
+ def error(message)
298
+ send_data("ERROR :#{message}")
299
+ end
300
+
301
+ # Set user as away with optional message
302
+ # @see http://tools.ietf.org/html/rfc2812#section-4.1 4.1 Away
303
+ def away(message = nil)
304
+ send_data("AWAY" + (message ? ":#{message}" : ""))
305
+ end
306
+
307
+ # Force user to re-read config
308
+ # @see http://tools.ietf.org/html/rfc2812#section-4.2 4.2 Rehash message
309
+ def rehash
310
+ send_data("REHASH")
311
+ end
312
+
313
+ # Shutdown server
314
+ # @see http://tools.ietf.org/html/rfc2812#section-4.3 4.3 Die message
315
+ def die
316
+ send_data("DIE")
317
+ end
318
+
319
+ # Restart server
320
+ # @see http://tools.ietf.org/html/rfc2812#section-4.4 4.4 Restart
321
+ def restart
322
+ send_data("RESTART")
323
+ end
324
+
325
+ # Ask user to join IRC
326
+ # @see http://tools.ietf.org/html/rfc2812#section-4.5 4.5 Summon
327
+ def summon(user, target = nil, channel = nil)
328
+ send_data("SUMMON #{user} #{target} #{channel}".strip)
329
+ end
330
+
331
+ # List logged in users
332
+ # @see http://tools.ietf.org/html/rfc2812#section-4.6 4.6 Users
333
+ def users(target = nil)
334
+ send_data("USERS #{target}".strip)
335
+ end
336
+
337
+ # Broadcast to all logged in users
338
+ # @see http://tools.ietf.org/html/rfc2812#section-4.7 4.7 Operwall
339
+ def wallops(message)
340
+ send_data("WALLOPS :#{message}")
341
+ end
342
+ alias_method :broadcast, :wallops
343
+
344
+ # Returns information about up to 5 nicknames
345
+ # @see http://tools.ietf.org/html/rfc2812#section-4.9 4.9 Userhost
346
+ def userhost(*nicks)
347
+ raise ArgumentError.new("Wrong number of arguments") unless nicks.size > 0 && nicks.size <= 5
348
+ send_data("USERHOST #{nicks.join(' ')}")
349
+ end
350
+
351
+ # Efficient way to check if nicks are currently on
352
+ # @see http://tools.ietf.org/html/rfc2812#section-4.9 4.9 Ison
353
+ def ison(*nicks)
354
+ send_data("ISON #{nicks.join(' ')}")
355
+ end
356
+ end
357
+ end
358
+ end
@@ -9,20 +9,16 @@ module EventMachine
9
9
  raise ArgumentError.new(":parent parameter is required for EM#connect") unless options[:parent]
10
10
  # TODO: if parent doesn't respond to a above methods, do a no-op
11
11
  @parent = options[:parent]
12
+ @ssl = options[:ssl] || false
12
13
  end
13
14
 
14
15
  # @parent.conn is set back to nil when this is created
15
16
  def post_init
16
17
  @parent.conn = self
17
- @parent.ready unless @parent.ssl
18
18
  end
19
19
 
20
20
  def connection_completed
21
- if @parent.ssl
22
- start_tls
23
- else
24
- @parent.ready
25
- end
21
+ @ssl ? start_tls : @parent.ready
26
22
  end
27
23
 
28
24
  def ssl_handshake_completed
@@ -0,0 +1,88 @@
1
+ module EventMachine
2
+ module IRC
3
+ # This module defines callbacks for IRC server responses
4
+ module Responses
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ class_attribute :server_callbacks
9
+
10
+ server_reply 'PRIVMSG' do |m|
11
+ who = sender_nick(m[:prefix])
12
+ channel = m[:params].first
13
+ message = m[:params].slice(1..-1).join(' ').gsub(/^:/, '')
14
+ trigger(:message, who, channel, message)
15
+ end
16
+
17
+ server_reply '001', 'RPL_WELCOME' do |m|
18
+ @nick = m[:params].first
19
+ trigger(:nick, @nick)
20
+ end
21
+
22
+ server_reply 'PING' do |m|
23
+ pong(m[:params].first)
24
+ trigger(:ping, *m[:params])
25
+ end
26
+
27
+ server_reply 'JOIN' do |m|
28
+ trigger(:join, sender_nick(m[:prefix]), m[:params].first)
29
+ end
30
+
31
+ server_reply '433', 'ERR_NICKNAMEINUSE' do |m|
32
+ @nick = nil
33
+ end
34
+ end
35
+
36
+ module ClassMethods
37
+ def server_reply(*cmds, &blk)
38
+ cmds << cmds.first if cmds.size == 1
39
+ self.server_callbacks ||= {}
40
+ self.server_callbacks[cmds.first] = {
41
+ :name => cmds.last,
42
+ :callback => block_given? ? blk : lambda {|m|
43
+ trigger(cmd.last.downcase.to_sym, *m[:params])
44
+ }
45
+ }
46
+ end
47
+ end
48
+
49
+ # @return [Hash] h
50
+ # @option h [String] :prefix
51
+ # @option h [String] :command
52
+ # @option h [Array] :params
53
+ # @private
54
+ def parse_message(message)
55
+ # TODO: error handling
56
+ result = {}
57
+
58
+ parts = message.split(' ')
59
+ result[:prefix] = parts.shift.gsub(/^:/, '') if parts[0] =~ /^:/
60
+ result[:command] = parts.shift
61
+ result[:params] = parts.take_while {|e| e[0] != ':'}
62
+ if result[:params].size < parts.size
63
+ full_string = parts.slice(result[:params].size..-1).join(" ")
64
+ full_string.gsub!(/^:/, '')
65
+ result[:params] << full_string
66
+ end
67
+ result
68
+ end
69
+
70
+ # @private
71
+ def handle_parsed_message(m)
72
+ if handler = self.class.server_callbacks[m[:command]]
73
+ instance_exec(m, &handler[:callback])
74
+ # error codes 400 to 599
75
+ trigger(:error, handler[:name]) if (m[:command].to_i / 100) > 3
76
+ else
77
+ log Logger::ERROR, "Unimplemented command: #{m[:prefix]} #{m[:command]} #{m[:params].join(' ')}"
78
+ end
79
+ end
80
+
81
+ protected
82
+ # @private
83
+ def sender_nick(prefix)
84
+ prefix.split('!').first
85
+ end
86
+ end
87
+ end
88
+ end