cinch 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,121 @@
1
+ module Cinch
2
+ module IRC
3
+
4
+ # == Author
5
+ # * Lee Jarvis - ljjarvis@gmail.com
6
+ #
7
+ # == Description
8
+ # IRC::Message is a nicely encapsulated IRC message object. Used directly by
9
+ # IRC::Parser#parse_servermessage and sent to every plugin defined. It does
10
+ # not do any parsing of itself, that's all down to the parser
11
+ #
12
+ # == See
13
+ # * Cinch::IRC::Parser#parse_servermessage
14
+ #
15
+ # TODO: Add more documentation
16
+ class Message
17
+
18
+ # Message prefix
19
+ attr_reader :prefix
20
+
21
+ # Message command (PRIVMSG, JOIN, KICK, etc)
22
+ attr_reader :command
23
+
24
+ # Message params
25
+ attr_reader :params
26
+
27
+ # Message symbol (lowercase command, ie. :privmsg, :join, :kick)
28
+ attr_reader :symbol
29
+
30
+ # The raw string passed to ::new
31
+ attr_reader :raw
32
+
33
+ # Hash with message attributes
34
+ attr_reader :data
35
+
36
+ # Message text
37
+ attr_reader :text
38
+
39
+ # Arguments parsed from a rule
40
+ attr_accessor :args
41
+
42
+ # The IRC::Socket object (or nil)
43
+ attr_accessor :irc
44
+
45
+ # Invoked directly from IRC::Parser#parse_servermessage
46
+ def initialize(raw, prefix, command, params)
47
+ @raw = raw
48
+ @prefix = prefix
49
+ @command = command
50
+ @params = params
51
+ @text = params.last unless params.empty?
52
+
53
+ @symbol = command.downcase.to_sym
54
+ @data = {}
55
+ @args = {}
56
+ @irc = nil
57
+ end
58
+
59
+ # Access attribute
60
+ def [](var)
61
+ @data[var.to_sym]
62
+ end
63
+
64
+ # Add a new attribute (stored in @data)
65
+ def add(var, val)
66
+ @data[var.to_sym] = val
67
+ end
68
+ alias []= add
69
+
70
+ # Remove an attribute
71
+ def delete(var)
72
+ var = var.to_sym
73
+ return unless @data.key?(var)
74
+ @data.delete(var)
75
+ end
76
+
77
+ # Check if our message was sent privately
78
+ def private?
79
+ !@data[:channel]
80
+ end
81
+
82
+ # Add the nick/user/host attributes
83
+ def apply_user(nick, user, host)
84
+ @data[:nick] = nick
85
+ @data[:user] = user
86
+ @data[:host] = host
87
+ end
88
+
89
+ def reply(text)
90
+ recipient = data[:channel] || data[:nick]
91
+ @irc.privmsg(recipient, text)
92
+ end
93
+
94
+ def answer(text)
95
+ return unless data[:channel]
96
+ @irc.privmsg(data[:channel], "#{data[:nick]}: #{text}")
97
+ end
98
+
99
+ def action(text)
100
+ reply("\001ACTION #{text}\001")
101
+ end
102
+
103
+ # The raw IRC message
104
+ def to_s
105
+ raw
106
+ end
107
+
108
+ # Catch methods and check if they exist as keys in
109
+ # the attribute hash
110
+ def method_missing(meth, *args, &blk) # :nodoc:
111
+ if @data.key?(meth)
112
+ @data[meth]
113
+ else
114
+ nil
115
+ end
116
+ end
117
+
118
+ end
119
+ end
120
+ end
121
+
@@ -0,0 +1,125 @@
1
+ module Cinch
2
+ module IRC
3
+
4
+ # == Author
5
+ # * Lee Jarvis - ljjarvis@gmail.com
6
+ #
7
+ # == Description
8
+ # Parse incoming IRC lines and extract data, returning a nicely
9
+ # encapsulated Cinch::IRC::Message
10
+ #
11
+ # == Example
12
+ # require 'cinch/irc/parser'
13
+ # include Cinch::IRC::Parser
14
+ #
15
+ # message = parse(":foo!bar@myhost.com PRIVMSG #mychan :ding dong!")
16
+ #
17
+ # message.class #=> Cinch::IRC::Message
18
+ # message.command #=> PRIVMSG
19
+ # message.nick #=> foo
20
+ # message.channel #=> #mychan
21
+ # message.text #=> ding dong!
22
+ class Parser
23
+
24
+ # A hash holding all of our patterns
25
+ attr_reader :patterns
26
+
27
+ def initialize
28
+ @patterns = {}
29
+ setup_patterns
30
+ end
31
+
32
+ # Add a new pattern
33
+ def add_pattern(key, pattern)
34
+ raise ArgumentError, "Pattern is not a regular expression" unless pattern.is_a?(Regexp)
35
+ @patterns[key.to_sym] = pattern
36
+ end
37
+
38
+ # Remove a pattern
39
+ def remove_pattern(key)
40
+ key = key.to_sym
41
+ @patterns.delete(key) if @patterns.key?(key)
42
+ end
43
+
44
+ # Helper for our patterns Hash
45
+ def pattern(key)
46
+ @patterns[key]
47
+ end
48
+
49
+ # Set up some default patterns used directly by this class
50
+ def setup_patterns
51
+ add_pattern :letter, /[a-zA-Z]/
52
+ add_pattern :hex, /[\dA-Fa-f]/
53
+
54
+ add_pattern :ip4addr, /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
55
+ add_pattern :ip6addr, /[\dA-Fa-f](?::[\dA-Fa-f]){7}|0:0:0:0:0:(?:0|[Ff]{4}):#{pattern(:ip4addr)}/
56
+ add_pattern :hostaddr, /#{pattern(:ip4addr)}|#{pattern(:ip6addr)}/
57
+ add_pattern :shortname, /[A-Za-z0-9][A-Za-z0-9-]*/
58
+ add_pattern :hostname, /#{pattern(:shortname)}(?:\.#{pattern(:shortname)})*/
59
+ add_pattern :host, /#{pattern(:hostname)}|#{pattern(:hostaddr)}/
60
+
61
+ add_pattern :user, /[^\x00\x10\x0D\x20@]+/
62
+ add_pattern :nick, /[A-Za-z\[\]\\`_^{|}][A-Za-z\d\[\]\\`_^{|}-]{0,19}/
63
+
64
+ add_pattern :userhost, /(#{pattern(:nick)})(?:(?:!(#{pattern(:user)}))?@(#{pattern(:host)}))?/
65
+
66
+ add_pattern :channel, /(?:[#+&]|![A-Z\d]{5})[^\x00\x07\x10\x0D\x20,:]/
67
+
68
+ # Server message parsing patterns
69
+ add_pattern :prefix, /(?:(\S+)\x20)?/
70
+ add_pattern :command, /([A-Za-z]+|\d{3})/
71
+ add_pattern :middle, /[^\x00\x20\r\n:][^\x00\x20\r\n]*/
72
+ add_pattern :trailing, /[^\x00\r\n]*/
73
+ add_pattern :params, /(?:((?:#{pattern(:middle)}){0,14}(?::?#{pattern(:trailing)})?))/
74
+ add_pattern :message, /\A#{pattern(:prefix)}#{pattern(:command)}#{pattern(:params)}\Z/
75
+
76
+ add_pattern :params_scan, /(?!:)([^\x00\x20\r\n:]+)|:([^\x00\r\n]*)/
77
+ end
78
+ private :setup_patterns
79
+
80
+ # Parse the incoming raw IRC string and return
81
+ # a nicely formatted IRC::Message
82
+ def parse_servermessage(raw)
83
+ raise ArgumentError, raw unless matches = raw.match(pattern(:message))
84
+
85
+ prefix, command, parameters = matches.captures
86
+
87
+ params = []
88
+ parameters.scan(pattern(:params_scan)) {|a, c| params << (a || c) }
89
+
90
+ m = IRC::Message.new(raw, prefix, command, params)
91
+
92
+ if prefix && userhost = parse_userhost(prefix)
93
+ m.apply_user(*userhost)
94
+
95
+ unless m.params.empty?
96
+ m[:recipient] = m.params.first
97
+ m[:channel] = m[:recipient] if valid_channel?(m[:recipient])
98
+ end
99
+ end
100
+
101
+ m # Return our IRC::Message
102
+ end
103
+ alias :parse :parse_servermessage
104
+
105
+ # Parse the prefix returned from the server
106
+ # and return an Array of [nick, user, host] or
107
+ # nil if no match is found
108
+ def parse_userhost(prefix)
109
+ if matches = prefix.match(pattern(:userhost))
110
+ matches.captures
111
+ else
112
+ nil
113
+ end
114
+ end
115
+ alias :extract_userhost :parse_userhost
116
+
117
+ # Check if a string is a valid channel
118
+ def valid_channel?(str)
119
+ !str.match(pattern(:channel)).nil?
120
+ end
121
+
122
+ end
123
+ end
124
+ end
125
+
@@ -0,0 +1,291 @@
1
+ module Cinch
2
+ module IRC
3
+ # == Author
4
+ # * Lee Jarvis - ljjarvis@gmail.com
5
+ #
6
+ # == Description
7
+ # This class has been directly take from the irc-socket library. Original documentation
8
+ # for this class can be found {here}[http://rdoc.injekt.net/irc-socket].
9
+ #
10
+ # IRCSocket is an IRC wrapper around a TCPSocket. It implements all of the major
11
+ # commands laid out in {RFC 2812}[http://irchelp.org/irchelp/rfc/rfc2812.txt].
12
+ # All these commands are available as instance methods of an IRCSocket Object.
13
+ #
14
+ # == Example
15
+ # irc = IRCSocket.new('irc.freenode.org')
16
+ # irc.connect
17
+ #
18
+ # if irc.connected?
19
+ # irc.nick "HulkHogan"
20
+ # irc.user "Hulk", 0, "*", "I am Hulk Hogan"
21
+ #
22
+ # while line = irc.read
23
+ #
24
+ # # Join a channel after MOTD
25
+ # if line.split[1] == '376'
26
+ # irc.join "#mychannel"
27
+ # end
28
+ #
29
+ # puts "Received: #{line}"
30
+ # end
31
+ # end
32
+ #
33
+ # === Block Form
34
+ # IRCSocket.new('irc.freenode.org') do |irc|
35
+ # irc.nick "SpongeBob"
36
+ # irc.user "Spongey", 0, "*", "Square Pants"
37
+ #
38
+ # puts irc.read
39
+ # end
40
+ class Socket
41
+
42
+ # The server our socket is connected to
43
+ attr_reader :server
44
+
45
+ # The port our socket is connected on
46
+ attr_reader :port
47
+
48
+ # The TCPSocket instance
49
+ attr_reader :socket
50
+
51
+ # Creates a new IRCSocket and automatically connects
52
+ #
53
+ # === Example
54
+ # irc = IRCSocket.open('irc.freenode.org')
55
+ #
56
+ # while data = irc.read
57
+ # puts data
58
+ # end
59
+ def self.open(server, port=6667)
60
+ irc = new(server, port)
61
+ irc.connect
62
+ irc
63
+ end
64
+
65
+ # Create a new IRCSocket to connect to +server+ on +port+. Defaults to port 6667.
66
+ # If an optional code block is given, it will be passed an instance of the IRCSocket.
67
+ # NOTE: Using the block form does not mean the socket will send the applicable QUIT
68
+ # command to leave the IRC server. You must send this yourself.
69
+ def initialize(server, port=6667)
70
+ @server = server
71
+ @port = port
72
+
73
+ @socket = nil
74
+ @connected = false
75
+
76
+ if block_given?
77
+ connect
78
+ yield self
79
+ end
80
+ end
81
+
82
+ # Check if our socket is alive and connected to an IRC server
83
+ def connected?
84
+ @connected
85
+ end
86
+ alias connected connected?
87
+
88
+ # Connect to an IRC server, returns true on a successful connection, or
89
+ # raises otherwise
90
+ def connect
91
+ @socket = TCPSocket.new(server, port)
92
+ rescue Interrupt
93
+ raise
94
+ rescue Exception
95
+ raise
96
+ else
97
+ @connected = true
98
+ end
99
+
100
+ # Read the next line in from the server. If no arguments are passed
101
+ # the line will have the CRLF chomp'ed. Returns nil if no data could be read
102
+ def read(chompstr="\r\n")
103
+ if data = @socket.gets("\r\n")
104
+ data.chomp!(chompstr) if chompstr
105
+ data
106
+ else
107
+ nil
108
+ end
109
+ rescue IOError
110
+ nil
111
+ end
112
+
113
+ # Write to our Socket and append CRLF
114
+ def write(data)
115
+ @socket.write(data + "\r\n")
116
+ rescue IOError
117
+ raise
118
+ end
119
+
120
+ # Sugar for #write
121
+ def raw(*args) # :nodoc:
122
+ args.last.insert(0, ':') unless args.last.nil?
123
+ write args.join(' ').strip
124
+ end
125
+
126
+ # More sugar
127
+ def write_optional(command, *optional) # :nodoc:
128
+ command = "#{command} #{optional.join(' ')}" if optional
129
+ write(command.strip)
130
+ end
131
+ private :raw, :write_optional
132
+
133
+ # Send PASS command
134
+ def pass(password)
135
+ write("PASS #{password}")
136
+ end
137
+
138
+ # Send NICK command
139
+ def nick(nickname)
140
+ write("NICK #{nickname}")
141
+ end
142
+
143
+ # Send USER command
144
+ def user(user, mode, unused, realname)
145
+ write("USER #{user} #{mode} #{unused} :#{realname}")
146
+ end
147
+
148
+ # Send OPER command
149
+ def oper(name, password)
150
+ write("OPER #{name} #{password}")
151
+ end
152
+
153
+ # Send the MODE command.
154
+ # Should probably implement a better way of doing this
155
+ def mode(channel, *modes)
156
+ write("MODE #{channel} #{modes.join(' ')}")
157
+ end
158
+
159
+ # Send QUIT command
160
+ def quit(message=nil)
161
+ raw("QUIT", message)
162
+ end
163
+
164
+ # Send JOIN command - Join a channel with given password
165
+ def join(channel, password=nil)
166
+ write("JOIN #{channel}")
167
+ end
168
+
169
+ # Send PART command
170
+ def part(channel, message=nil)
171
+ raw("PART", channel, message)
172
+ end
173
+
174
+ # Send TOPIC command
175
+ def topic(channel, topic=nil)
176
+ raw("TOPIC", channel, topic)
177
+ end
178
+
179
+ # Send NAMES command
180
+ def names(*channels)
181
+ write("NAMES #{channels.join(',') unless channels.empty?}")
182
+ end
183
+
184
+ # Send LIST command
185
+ def list(*channels)
186
+ write("LIST #{channels.join(',') unless channels.empty?}")
187
+ end
188
+
189
+ # Send INVITE command
190
+ def invite(nickname, channel)
191
+ write("INVITE #{nickname} #{channel}")
192
+ end
193
+
194
+ # Send KICK command
195
+ def kick(channel, user, comment=nil)
196
+ raw("KICK", channel, user, comment)
197
+ end
198
+
199
+ # Send PRIVMSG command
200
+ def privmsg(target, message)
201
+ write("PRIVMSG #{target} :#{message}")
202
+ end
203
+
204
+ # Send NOTICE command
205
+ def notice(target, message)
206
+ write("NOTICE #{target} :#{message}")
207
+ end
208
+
209
+ # Send MOTD command
210
+ def motd(target=nil)
211
+ write_optional("MOTD", target)
212
+ end
213
+
214
+ # Send VERSION command
215
+ def version(target=nil)
216
+ write_optional("VERSION", target)
217
+ end
218
+
219
+ # Send STATS command
220
+ def stats(*params)
221
+ write_optional("STATS", params)
222
+ end
223
+
224
+ # Send TIME command
225
+ def time(target=nil)
226
+ write_optional("TIME", target)
227
+ end
228
+
229
+ # Send INFO command
230
+ def info(target=nil)
231
+ write_optional("INFO", target)
232
+ end
233
+
234
+ # Send SQUERY command
235
+ def squery(target, message)
236
+ write("SQUERY #{target} :#{message}")
237
+ end
238
+
239
+ # Send WHO command
240
+ def who(*params)
241
+ write_optional("WHO", params)
242
+ end
243
+
244
+ # Send WHOIS command
245
+ def whois(*params)
246
+ write_optional("WHOIS", params)
247
+ end
248
+
249
+ # Send WHOWAS command
250
+ def whowas(*params)
251
+ write_optional("WHOWAS", params)
252
+ end
253
+
254
+ # Send KILL command
255
+ def kill(user, message)
256
+ write("KILL #{user} :#{message}")
257
+ end
258
+
259
+ # Send PING command
260
+ def ping(server)
261
+ write("PING #{server}")
262
+ end
263
+
264
+ # Send PONG command
265
+ def pong(server)
266
+ write("PONG #{server}")
267
+ end
268
+
269
+ # Send AWAY command
270
+ def away(message=nil)
271
+ raw("AWAY", message)
272
+ end
273
+
274
+ # Send USERS command
275
+ def users(target=nil)
276
+ write_optional("USERS", target)
277
+ end
278
+
279
+ # Send USERHOST command
280
+ def userhost(*users)
281
+ write("USERHOST #{users.join(' ')}")
282
+ end
283
+
284
+ # Close our socket instance
285
+ def close
286
+ @socket.close if connected?
287
+ end
288
+ end
289
+ end
290
+ end
291
+