cinch 0.3.5 → 1.0.0

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.
Files changed (82) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +192 -0
  3. data/Rakefile +53 -43
  4. data/examples/basic/autovoice.rb +32 -0
  5. data/examples/basic/google.rb +35 -0
  6. data/examples/basic/hello.rb +15 -0
  7. data/examples/basic/join_part.rb +38 -0
  8. data/examples/basic/memo.rb +39 -0
  9. data/examples/basic/msg.rb +16 -0
  10. data/examples/basic/seen.rb +36 -0
  11. data/examples/basic/urban_dict.rb +35 -0
  12. data/examples/basic/url_shorten.rb +35 -0
  13. data/examples/plugins/autovoice.rb +40 -0
  14. data/examples/plugins/custom_prefix.rb +23 -0
  15. data/examples/plugins/google.rb +37 -0
  16. data/examples/plugins/hello.rb +22 -0
  17. data/examples/plugins/join_part.rb +42 -0
  18. data/examples/plugins/memo.rb +50 -0
  19. data/examples/plugins/msg.rb +22 -0
  20. data/examples/plugins/multiple_matches.rb +41 -0
  21. data/examples/plugins/seen.rb +45 -0
  22. data/examples/plugins/urban_dict.rb +30 -0
  23. data/examples/plugins/url_shorten.rb +32 -0
  24. data/lib/cinch.rb +7 -20
  25. data/lib/cinch/ban.rb +41 -0
  26. data/lib/cinch/bot.rb +479 -0
  27. data/lib/cinch/callback.rb +11 -0
  28. data/lib/cinch/channel.rb +419 -0
  29. data/lib/cinch/constants.rb +369 -0
  30. data/lib/cinch/exceptions.rb +25 -0
  31. data/lib/cinch/helpers.rb +21 -0
  32. data/lib/cinch/irc.rb +344 -38
  33. data/lib/cinch/isupport.rb +96 -0
  34. data/lib/cinch/logger/formatted_logger.rb +80 -0
  35. data/lib/cinch/logger/logger.rb +44 -0
  36. data/lib/cinch/logger/null_logger.rb +18 -0
  37. data/lib/cinch/mask.rb +46 -0
  38. data/lib/cinch/message.rb +183 -0
  39. data/lib/cinch/message_queue.rb +62 -0
  40. data/lib/cinch/plugin.rb +205 -0
  41. data/lib/cinch/rubyext/infinity.rb +1 -0
  42. data/lib/cinch/rubyext/module.rb +18 -0
  43. data/lib/cinch/rubyext/queue.rb +19 -0
  44. data/lib/cinch/rubyext/string.rb +24 -0
  45. data/lib/cinch/syncable.rb +55 -0
  46. data/lib/cinch/user.rb +325 -0
  47. data/spec/bot_spec.rb +5 -0
  48. data/spec/channel_spec.rb +5 -0
  49. data/spec/cinch_spec.rb +5 -0
  50. data/spec/irc_spec.rb +5 -0
  51. data/spec/message_spec.rb +5 -0
  52. data/spec/plugin_spec.rb +5 -0
  53. data/spec/{helper.rb → spec_helper.rb} +0 -0
  54. data/spec/user_spec.rb +5 -0
  55. metadata +69 -51
  56. data/README.rdoc +0 -195
  57. data/examples/autovoice.rb +0 -32
  58. data/examples/custom_patterns.rb +0 -19
  59. data/examples/custom_prefix.rb +0 -25
  60. data/examples/google.rb +0 -31
  61. data/examples/hello.rb +0 -13
  62. data/examples/join_part.rb +0 -26
  63. data/examples/memo.rb +0 -40
  64. data/examples/msg.rb +0 -14
  65. data/examples/named-param-types.rb +0 -19
  66. data/examples/seen.rb +0 -41
  67. data/examples/urban_dict.rb +0 -31
  68. data/examples/url_shorten.rb +0 -34
  69. data/lib/cinch/base.rb +0 -368
  70. data/lib/cinch/irc/message.rb +0 -135
  71. data/lib/cinch/irc/parser.rb +0 -141
  72. data/lib/cinch/irc/socket.rb +0 -329
  73. data/lib/cinch/names.rb +0 -54
  74. data/lib/cinch/rules.rb +0 -171
  75. data/spec/base_spec.rb +0 -94
  76. data/spec/irc/helper.rb +0 -8
  77. data/spec/irc/message_spec.rb +0 -61
  78. data/spec/irc/parser_spec.rb +0 -103
  79. data/spec/irc/socket_spec.rb +0 -90
  80. data/spec/names_spec.rb +0 -393
  81. data/spec/options_spec.rb +0 -45
  82. data/spec/rules_spec.rb +0 -109
@@ -1,135 +0,0 @@
1
- module Cinch
2
- module IRC
3
-
4
- # == Description
5
- # IRC::Message provies a nicely encapsulated IRC message object. Used directly by
6
- # IRC::Parser#parse and sent to any plugin or listener defined.
7
- #
8
- # TODO: Add more documentation
9
- #
10
- # == See Also
11
- # * Cinch::IRC::Parser#parse
12
- #
13
- # == Author
14
- # * Lee Jarvis - ljjarvis@gmail.com
15
- class Message
16
-
17
- # Message prefix
18
- attr_reader :prefix
19
-
20
- # Message command (PRIVMSG, JOIN, KICK, etc)
21
- attr_reader :command
22
-
23
- # Message params
24
- attr_reader :params
25
-
26
- # Message symbol (lowercase command, ie. :privmsg, :join, :kick)
27
- attr_accessor :symbol
28
-
29
- # The raw string passed to ::new
30
- attr_reader :raw
31
-
32
- # Hash with message attributes
33
- attr_reader :data
34
-
35
- # Message text
36
- attr_accessor :text
37
-
38
- # Arguments parsed from a rule
39
- attr_accessor :args
40
-
41
- # The IRC::Socket object (or nil)
42
- attr_accessor :irc
43
-
44
- # Invoked directly from IRC::Parser#parse_servermessage
45
- def initialize(raw, prefix, command, params)
46
- @raw = raw
47
- @prefix = prefix
48
- @command = command
49
- @params = params
50
- @text = params.last unless params.empty?
51
-
52
- @symbol = command.downcase.to_sym
53
- @data = {}
54
- @args = {}
55
- @irc = nil
56
- end
57
-
58
- # Access attribute
59
- def [](var)
60
- @data[var.to_sym]
61
- end
62
-
63
- # Add a new attribute (stored in @data)
64
- def add(var, val)
65
- @data[var.to_sym] = val
66
- end
67
- alias []= add
68
-
69
- # Remove an attribute
70
- def delete(var)
71
- var = var.to_sym
72
- return unless @data.key?(var)
73
- @data.delete(var)
74
- end
75
-
76
- # Alter an attribute
77
- def alter(var, val)
78
- if @data.key?(var)
79
- @data[var] = val
80
- end
81
- end
82
-
83
- # Check if our message was sent privately
84
- def private?
85
- !@data[:channel]
86
- end
87
-
88
- # Check if our message was sent publicly (in a channel)
89
- def public?
90
- !private?
91
- end
92
-
93
- # Reply to a channel or user, probably the most commonly used helper
94
- # method
95
- def reply(text)
96
- recipient = data[:channel] || data[:nick]
97
- @irc.privmsg(recipient, text)
98
- end
99
-
100
- # Same as reply but prefixes the users nickname
101
- def answer(text)
102
- return unless data[:channel]
103
- @irc.privmsg(data[:channel], "#{data[:nick]}: #{text}")
104
- end
105
-
106
- # Send a CTCP reply
107
- def ctcp_reply(text)
108
- recipient = data[:channel] || data[:nick]
109
- @irc.notice(recipient, "\001#{@data[:ctcp_action]} #{text}\001")
110
- end
111
-
112
- # The deadly /me action
113
- def action(text)
114
- reply("\001ACTION #{text}\001")
115
- end
116
-
117
- # The raw IRC message
118
- def to_s
119
- raw
120
- end
121
-
122
- # Catch methods and check if they exist as keys in
123
- # the attribute hash
124
- def method_missing(meth, *args, &blk) # :nodoc:
125
- if @data.key?(meth)
126
- @data[meth]
127
- else
128
- nil
129
- end
130
- end
131
-
132
- end
133
- end
134
- end
135
-
@@ -1,141 +0,0 @@
1
- module Cinch
2
- module IRC
3
-
4
- # == Description
5
- # Parse incoming IRC lines and extract data, returning a nicely
6
- # encapsulated Cinch::IRC::Message
7
- #
8
- # == Example
9
- # require 'cinch/irc/parser'
10
- #
11
- # message = Cinch::IRC::Parser.parse(":foo!bar@myhost.com PRIVMSG #mychan :ding dong!")
12
- #
13
- # message.class #=> Cinch::IRC::Message
14
- # message.command #=> PRIVMSG
15
- # message.nick #=> foo
16
- # message.channel #=> #mychan
17
- # message.text #=> ding dong!
18
- #
19
- # == Author
20
- # * Lee Jarvis - ljjarvis@gmail.com
21
- class Parser
22
-
23
- # A hash holding all of our patterns
24
- attr_reader :patterns
25
-
26
- def initialize
27
- @patterns = {}
28
- setup_patterns
29
- end
30
-
31
- # Add a new pattern
32
- def add_pattern(key, pattern)
33
- @patterns[key.to_sym] = pattern
34
- end
35
-
36
- # Remove a pattern
37
- def remove_pattern(key)
38
- @patterns.delete(key.to_sym)
39
- end
40
-
41
- # Helper for our patterns Hash
42
- def pattern(key)
43
- @patterns[key.to_sym]
44
- end
45
-
46
- # Check if a pattern exists
47
- def has_pattern?(key)
48
- @patterns.key?(key.to_sym)
49
- end
50
-
51
- # Set up some default patterns used directly by this class
52
- def setup_patterns
53
- add_pattern :letter, /[a-zA-Z]/
54
- add_pattern :word, /\w+/
55
- add_pattern :upper, /[A-Z]+/
56
- add_pattern :lower, /[a-z]+/
57
- add_pattern :hex, /[\dA-Fa-f]/
58
-
59
- add_pattern :ip4addr, /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
60
- add_pattern :ip6addr, /[\dA-Fa-f](?::[\dA-Fa-f]){7}|0:0:0:0:0:(?:0|[Ff]{4}):#{pattern(:ip4addr)}/
61
- add_pattern :hostaddr, /#{pattern(:ip4addr)}|#{pattern(:ip6addr)}/
62
- add_pattern :shortname, /[A-Za-z0-9][A-Za-z0-9\-\/]*/
63
- add_pattern :hostname, /#{pattern(:shortname)}(?:\.#{pattern(:shortname)})*/
64
- add_pattern :host, /#{pattern(:hostname)}|#{pattern(:hostaddr)}/
65
-
66
- add_pattern :user, /[^\x00\x10\x0D\x20@]+/
67
- add_pattern :nick, /[A-Za-z\[\]\\`_^{|}][A-Za-z\d\[\]\\`_^{|}-]{0,19}/
68
-
69
- add_pattern :ctcp, /\001(\S+)(?:\s([^\x00\r\n\001]+))?\001$/
70
-
71
- add_pattern :userhost, /(#{pattern(:nick)})(?:(?:!(#{pattern(:user)}))?@(#{pattern(:host)}))?/
72
-
73
- add_pattern :channel, /(?:[#+&]|![A-Z\d]{5})[^\x00\x07\x10\x0D\x20,:]/
74
-
75
- # Server message parsing patterns
76
- add_pattern :prefix, /(?:(\S+)\x20)?/
77
- add_pattern :command, /([A-Za-z]+|\d{3})/
78
- add_pattern :middle, /[^\x00\x20\r\n:][^\x00\x20\r\n]*/
79
- add_pattern :trailing, /[^\x00\r\n]*/
80
- add_pattern :params, /(?:((?:#{pattern(:middle)}){0,14}(?::?#{pattern(:trailing)})?))/
81
- add_pattern :message, /\A#{pattern(:prefix)}#{pattern(:command)}#{pattern(:params)}\Z/
82
-
83
- add_pattern :params_scan, /(?!:)([^\x00\x20\r\n:]+)|:([^\x00\r\n]*)/
84
- end
85
- private :setup_patterns
86
-
87
- # Parse the incoming raw IRC string and return
88
- # a nicely formatted IRC::Message
89
- def parse(raw)
90
- raise ArgumentError, raw unless raw && matches = raw.match(pattern(:message))
91
-
92
- prefix, command, parameters = matches.captures
93
-
94
- params = []
95
- parameters.scan(pattern(:params_scan)) {|a, c| params << (a || c) }
96
-
97
- m = IRC::Message.new(raw, prefix, command, params)
98
-
99
- if prefix && userhost = parse_userhost(prefix)
100
- nick, user, host = userhost
101
- m.add(:nick, nick)
102
- m.add(:user, user)
103
- m.add(:host, host)
104
-
105
- unless m.params.empty?
106
- m.add(:recipient, m.params.first)
107
- m.add(:channel, m.recipient) if valid_channel?(m.recipient)
108
- end
109
- end
110
-
111
- # Parse CTCP response
112
- if m.symbol == :privmsg && data = m.text.match(pattern(:ctcp))
113
- m.symbol = :ctcp
114
- m.text = data[2]
115
- m.add(:ctcp_action, data[1])
116
- end
117
-
118
- m # Return our IRC::Message
119
- end
120
-
121
- # Parse the prefix returned from the server
122
- # and return an Array of [nick, user, host] or
123
- # nil if no match is found
124
- def parse_userhost(prefix)
125
- if matches = prefix.match(pattern(:userhost))
126
- matches.captures
127
- else
128
- nil
129
- end
130
- end
131
- alias :extract_userhost :parse_userhost
132
-
133
- # Check if a string is a valid channel
134
- def valid_channel?(str)
135
- !str.match(pattern(:channel)).nil?
136
- end
137
-
138
- end
139
- end
140
- end
141
-
@@ -1,329 +0,0 @@
1
- module Cinch
2
- module IRC
3
-
4
- # == Description
5
- # This class has been directly taken from the irc-socket library. Original documentation
6
- # for this class can be found {here}[http://rdoc.injekt.net/irc-socket].
7
- #
8
- # IRCSocket is an IRC wrapper around a TCPSocket. It implements all of the major
9
- # commands laid out in {RFC 2812}[http://irchelp.org/irchelp/rfc/rfc2812.txt].
10
- # All these commands are available as instance methods of an IRCSocket Object.
11
- #
12
- # == Example
13
- # irc = IRCSocket.new('irc.freenode.org')
14
- # irc.connect
15
- #
16
- # if irc.connected?
17
- # irc.nick "HulkHogan"
18
- # irc.user "Hulk", 0, "*", "I am Hulk Hogan"
19
- #
20
- # while line = irc.read
21
- #
22
- # # Join a channel after MOTD
23
- # if line.split[1] == '376'
24
- # irc.join "#mychannel"
25
- # end
26
- #
27
- # puts "Received: #{line}"
28
- # end
29
- # end
30
- #
31
- # === Block Form
32
- # IRCSocket.new('irc.freenode.org') do |irc|
33
- # irc.nick "SpongeBob"
34
- # irc.user "Spongey", 0, "*", "Square Pants"
35
- #
36
- # puts irc.read
37
- # end
38
- #
39
- # == Author
40
- # * Lee Jarvis - ljjarvis@gmail.com
41
- class Socket
42
-
43
- # The server our socket is connected to
44
- attr_reader :server
45
-
46
- # The port our socket is connected on
47
- attr_reader :port
48
-
49
- # The TCPSocket instance
50
- attr_reader :socket
51
-
52
- # How long to wait before aborting a connection
53
- attr_accessor :timeout
54
-
55
- # Maximum attempts to retry failed connection
56
- attr_accessor :attempts
57
-
58
- # Creates a new IRCSocket and automatically connects
59
- #
60
- # === Example
61
- # irc = IRCSocket.open('irc.freenode.org')
62
- #
63
- # while data = irc.read
64
- # puts data
65
- # end
66
- def self.open(server, port=6667, ssl=false)
67
- irc = new(server, port)
68
- irc.connect
69
- irc
70
- end
71
-
72
- # Create a new IRCSocket to connect to +server+ on +port+. Defaults to port 6667.
73
- # If an optional code block is given, it will be passed an instance of the IRCSocket.
74
- # NOTE: Using the block form does not mean the socket will send the applicable QUIT
75
- # command to leave the IRC server. You must send this yourself.
76
- def initialize(server, port=6667, ssl=false)
77
- @server = server
78
- @port = port
79
- @ssl = ssl
80
-
81
- @socket = nil
82
- @connected = false
83
- @timeout = 5
84
- @attempts = 5
85
-
86
- if block_given?
87
- connect
88
- yield self
89
- end
90
- end
91
-
92
- # Check if our socket is alive and connected to an IRC server
93
- def connected?
94
- @connected
95
- end
96
- alias connected connected?
97
-
98
- # Connect to an IRC server, returns true on a successful connection, or
99
- # raises otherwise
100
- def connect(server=nil, port=nil, ssl=false)
101
- @server = server if server
102
- @port = port if port
103
- @ssl = ssl if ssl
104
-
105
- Timeout.timeout(@timeout) do
106
- socket = TCPSocket.new(@server, @port)
107
-
108
- if @ssl
109
- require 'openssl'
110
-
111
- ssl = OpenSSL::SSL::SSLContext.new
112
- ssl.verify_mode = OpenSSL::SSL::VERIFY_NONE
113
- @socket = OpenSSL::SSL::SSLSocket.new(socket, ssl)
114
- @socket.sync = true
115
- @socket.connect
116
- else
117
- @socket = socket
118
- end
119
- end
120
- rescue Timeout::Error
121
- if @attempts == 0
122
- puts "Maximum attempts reached. Aborting .."
123
- exit 1
124
- else
125
- puts "Connection timed out. Retrying #{@attempts} more time#{'s' unless @attempts == 1} .."
126
- @attempts -= 1
127
- connect(server, port)
128
- end
129
- rescue Interrupt
130
- puts "\nAborting connection .."
131
- exit
132
- rescue Exception
133
- raise
134
- else
135
- @connected = true
136
- end
137
-
138
- # Read the next line in from the server. If no arguments are passed
139
- # the line will have the CRLF chomp'ed. Returns nil if no data could be read
140
- def read(chompstr="\r\n")
141
- if data = @socket.gets("\r\n")
142
- data.chomp!(chompstr) if chompstr
143
- data
144
- else
145
- nil
146
- end
147
- rescue IOError
148
- nil
149
- end
150
-
151
- # Write to our Socket and append CRLF
152
- def write(data)
153
- @socket.write(data + "\r\n")
154
- rescue IOError
155
- raise
156
- end
157
-
158
- # Sugar for #write
159
- def raw(*args) # :nodoc:
160
- args.last.insert(0, ':') unless args.last.nil?
161
- write args.join(' ').strip
162
- end
163
-
164
- # More sugar
165
- def write_optional(command, *optional) # :nodoc:
166
- command = "#{command} #{optional.join(' ')}" if optional
167
- write(command.strip)
168
- end
169
- private :raw, :write_optional
170
-
171
- # Send PASS command
172
- def pass(password)
173
- write("PASS #{password}")
174
- end
175
-
176
- # Send NICK command
177
- def nick(nickname)
178
- write("NICK #{nickname}")
179
- end
180
-
181
- # Send USER command
182
- def user(user, mode, unused, realname)
183
- write("USER #{user} #{mode} #{unused} :#{realname}")
184
- end
185
-
186
- # Send OPER command
187
- def oper(name, password)
188
- write("OPER #{name} #{password}")
189
- end
190
-
191
- # Send the MODE command.
192
- # Should probably implement a better way of doing this
193
- def mode(channel, *modes)
194
- write("MODE #{channel} #{modes.join(' ')}")
195
- end
196
-
197
- # Send QUIT command
198
- def quit(message=nil)
199
- raw("QUIT", message)
200
- end
201
-
202
- # Send JOIN command - Join a channel with given password
203
- def join(channel, password=nil)
204
- write_optional("JOIN #{channel}", password)
205
- end
206
-
207
- # Send PART command
208
- def part(channel, message=nil)
209
- raw("PART", channel, message)
210
- end
211
-
212
- # Send TOPIC command
213
- def topic(channel, topic=nil)
214
- raw("TOPIC", channel, topic)
215
- end
216
-
217
- # Send NAMES command
218
- def names(*channels)
219
- write("NAMES #{channels.join(',') unless channels.empty?}")
220
- end
221
-
222
- # Send LIST command
223
- def list(*channels)
224
- write("LIST #{channels.join(',') unless channels.empty?}")
225
- end
226
-
227
- # Send INVITE command
228
- def invite(nickname, channel)
229
- write("INVITE #{nickname} #{channel}")
230
- end
231
-
232
- # Send KICK command
233
- def kick(channel, user, comment=nil)
234
- raw("KICK", channel, user, comment)
235
- end
236
-
237
- # Send PRIVMSG command
238
- def privmsg(target, message)
239
- write("PRIVMSG #{target} :#{message}")
240
- end
241
-
242
- # Send NOTICE command
243
- def notice(target, message)
244
- write("NOTICE #{target} :#{message}")
245
- end
246
-
247
- # Send MOTD command
248
- def motd(target=nil)
249
- write_optional("MOTD", target)
250
- end
251
-
252
- # Send VERSION command
253
- def version(target=nil)
254
- write_optional("VERSION", target)
255
- end
256
-
257
- # Send STATS command
258
- def stats(*params)
259
- write_optional("STATS", params)
260
- end
261
-
262
- # Send TIME command
263
- def time(target=nil)
264
- write_optional("TIME", target)
265
- end
266
-
267
- # Send INFO command
268
- def info(target=nil)
269
- write_optional("INFO", target)
270
- end
271
-
272
- # Send SQUERY command
273
- def squery(target, message)
274
- write("SQUERY #{target} :#{message}")
275
- end
276
-
277
- # Send WHO command
278
- def who(*params)
279
- write_optional("WHO", params)
280
- end
281
-
282
- # Send WHOIS command
283
- def whois(*params)
284
- write_optional("WHOIS", params)
285
- end
286
-
287
- # Send WHOWAS command
288
- def whowas(*params)
289
- write_optional("WHOWAS", params)
290
- end
291
-
292
- # Send KILL command
293
- def kill(user, message)
294
- write("KILL #{user} :#{message}")
295
- end
296
-
297
- # Send PING command
298
- def ping(server)
299
- write("PING #{server}")
300
- end
301
-
302
- # Send PONG command
303
- def pong(server)
304
- write("PONG #{server}")
305
- end
306
-
307
- # Send AWAY command
308
- def away(message=nil)
309
- raw("AWAY", message)
310
- end
311
-
312
- # Send USERS command
313
- def users(target=nil)
314
- write_optional("USERS", target)
315
- end
316
-
317
- # Send USERHOST command
318
- def userhost(*users)
319
- write("USERHOST #{users.join(' ')}")
320
- end
321
-
322
- # Close our socket instance
323
- def close
324
- @socket.close if connected?
325
- end
326
- end
327
- end
328
- end
329
-