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.
@@ -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