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
@@ -0,0 +1,80 @@
1
+ require "cinch/logger/logger"
2
+ module Cinch
3
+ module Logger
4
+ # A formatted logger that will colorize individual parts of IRC
5
+ # messages.
6
+ class FormattedLogger < Cinch::Logger::Logger
7
+ COLORS = {
8
+ :reset => "\e[0m",
9
+ :bold => "\e[1m",
10
+ :red => "\e[31m",
11
+ :green => "\e[32m",
12
+ :yellow => "\e[33m",
13
+ :blue => "\e[34m",
14
+ }
15
+
16
+ # @param [IO] output An IO to log to.
17
+ def initialize(output = STDERR)
18
+ @output = output
19
+ @mutex = Mutex.new
20
+ end
21
+
22
+ # (see Logger::Logger#debug)
23
+ def debug(messages)
24
+ log(messages, :debug)
25
+ end
26
+
27
+ # (see Logger::Logger#log)
28
+ def log(messages, kind = :generic)
29
+ @mutex.synchronize do
30
+ messages = [messages].flatten.map {|s| s.chomp}
31
+ # message = message.to_s.chomp # don't want to tinker with the original string
32
+
33
+ messages.each do |message|
34
+ if kind == :debug
35
+ prefix = colorize("!! ", :yellow)
36
+ message = prefix + message
37
+ else
38
+ pre, msg = message.split(" :", 2)
39
+ pre_parts = pre.split(" ")
40
+
41
+ if kind == :incoming
42
+ prefix = colorize(">> ", :green)
43
+
44
+ if pre_parts.size == 1
45
+ pre_parts[0] = colorize(pre_parts[0], :bold)
46
+ else
47
+ pre_parts[0] = colorize(pre_parts[0], :blue)
48
+ pre_parts[1] = colorize(pre_parts[1], :bold)
49
+ end
50
+
51
+ elsif kind == :outgoing
52
+ prefix = colorize("<< ", :red)
53
+ pre_parts[0] = colorize(pre_parts[0], :bold)
54
+ end
55
+
56
+ message = prefix + pre_parts.join(" ")
57
+ message << colorize(" :#{msg}", :yellow) if msg
58
+ end
59
+ @output.puts message.encode
60
+ end
61
+ end
62
+ end
63
+
64
+ # @api private
65
+ # @param [String] text text to colorize
66
+ # @param [Array<Symbol>] codes array of colors to apply
67
+ # @return [String] colorized string
68
+ def colorize(text, *codes)
69
+ COLORS.values_at(*codes).join + text + COLORS[:reset]
70
+ end
71
+
72
+ # (see Logger::Logger#log_exception)
73
+ def log_exception(e)
74
+ lines = ["#{e.backtrace.first}: #{e.message} (#{e.class})"]
75
+ lines.concat e.backtrace[1..-1].map {|s| "\t" + s}
76
+ debug(lines)
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,44 @@
1
+ module Cinch
2
+ module Logger
3
+ # This is an abstract class describing the logger interface. All
4
+ # loggers should inherit from this class and provide all necessary
5
+ # methods.
6
+ #
7
+ # Note: You cannot initialize this class directly.
8
+ #
9
+ # @abstract
10
+ class Logger
11
+ def initialize(output)
12
+ raise
13
+ end
14
+
15
+ # This method can be used by plugins to log custom messages.
16
+ #
17
+ # @param [String] message The message to log
18
+ # @return [void]
19
+ def debug(message)
20
+ raise
21
+ end
22
+
23
+ # This method is used by {#debug} and {#log_exception} to log
24
+ # messages, and also by the IRC parser to log incoming and
25
+ # outgoing messages. You should not have to call this.
26
+ #
27
+ # @param [String] message The message to log
28
+ # @param [Symbol<:debug, :generic, :incoming, :outgoing>] kind
29
+ # The kind of message to log
30
+ # @return [void]
31
+ def log(message, kind = :generic)
32
+ raise
33
+ end
34
+
35
+ # This method is used for logging messages.
36
+ #
37
+ # @param [Exception] e The exception to log
38
+ # @return [void]
39
+ def log_exception(e)
40
+ raise
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,18 @@
1
+ require "cinch/logger/logger"
2
+ module Cinch
3
+ module Logger
4
+ class NullLogger < Cinch::Logger::Logger
5
+ def initialize(output = nil)
6
+ end
7
+
8
+ def debug(message)
9
+ end
10
+
11
+ def log(message, kind = :generic)
12
+ end
13
+
14
+ def log_exception(e)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,46 @@
1
+ module Cinch
2
+ class Mask
3
+ # @return [String]
4
+ attr_reader :nick
5
+ # @return [String]
6
+ attr_reader :user
7
+ # @return [String]
8
+ attr_reader :host
9
+ # @return [String]
10
+ attr_reader :mask
11
+ def initialize(mask)
12
+ @mask = mask
13
+ @nick, @user, @host = mask.match(/(.+)!(.+)@(.+)/)[1..-1]
14
+ @regexp = Regexp.new(Regexp.escape(mask).gsub("\\*", ".*"))
15
+ end
16
+
17
+ # @return [Boolean]
18
+ def match(user)
19
+ mask = "%s!%s@%s" % [nick, user, host]
20
+ return mask =~ @regexp
21
+
22
+ # TODO support CIDR (freenode)
23
+ end
24
+ alias_method :=~, :match
25
+
26
+ # @return [String]
27
+ def to_s
28
+ @mask.dup
29
+ end
30
+
31
+ # @param [Ban, Mask, User, String]
32
+ # @return [Mask]
33
+ def self.from(target)
34
+ case target
35
+ when User, Ban
36
+ target.mask
37
+ when String
38
+ Mask.new(target)
39
+ when Mask
40
+ target
41
+ else
42
+ raise ArgumentError
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,183 @@
1
+ # -*- coding: utf-8 -*-
2
+ module Cinch
3
+ class Message
4
+ # @return [String]
5
+ attr_accessor :raw
6
+ # @return [String]
7
+ attr_accessor :prefix
8
+ # @return [String]
9
+ attr_accessor :command
10
+ # @return [Array<String>]
11
+ attr_accessor :params
12
+ attr_reader :events
13
+
14
+ def initialize(msg, bot)
15
+ @raw = msg
16
+ @bot = bot
17
+ @matches = {:ctcp => {}, :other => {}}
18
+ @events = []
19
+ parse if msg
20
+ end
21
+
22
+
23
+
24
+ # @return [Boolean] true if the message is an numeric reply (as
25
+ # opposed to a command)
26
+ def numeric_reply?
27
+ !!(@numeric_reply ||= @command.match(/^\d{3}$/))
28
+ end
29
+
30
+ # @api private
31
+ # @return [void]
32
+ def parse
33
+ match = @raw.match(/(^:(\S+) )?(\S+)(.*)/)
34
+ _, @prefix, @command, raw_params = match.captures
35
+
36
+ raw_params.strip!
37
+ if match = raw_params.match(/(?:^:| :)(.*)$/)
38
+ @params = match.pre_match.split(" ")
39
+ @params << match[1]
40
+ else
41
+ @params = raw_params.split(" ")
42
+ end
43
+ end
44
+
45
+ # @return [User] The user who sent this message
46
+ def user
47
+ return unless @prefix
48
+ nick = @prefix[/^(\S+)!/, 1]
49
+ user = @prefix[/^\S+!(\S+)@/, 1]
50
+ host = @prefix[/@(\S+)$/, 1]
51
+
52
+ return nil if nick.nil?
53
+ @user ||= User.find_ensured(user, nick, host, @bot)
54
+ end
55
+
56
+ # @return [String, nil]
57
+ def server
58
+ return unless @prefix
59
+ return if @prefix.match(/[@!]/)
60
+ @server ||= @prefix[/^(\S+)/, 1]
61
+ end
62
+
63
+ # @return [Boolean] true if the message describes an error
64
+ def error?
65
+ !!error
66
+ end
67
+
68
+ # @return [Number, nil] the numeric error code, if any
69
+ def error
70
+ @error ||= (command.to_i if numeric_reply? && command[/[45]\d\d/])
71
+ end
72
+
73
+ # @return [Boolean] true if this message was sent in a channel
74
+ def channel?
75
+ !!channel
76
+ end
77
+
78
+ # @return [Boolean] true if the message is an CTCP message
79
+ def ctcp?
80
+ params.last =~ /\001.+\001/
81
+ end
82
+
83
+ # @return [String, nil] the command part of an CTCP message
84
+ def ctcp_command
85
+ return unless ctcp?
86
+ ctcp_message.split(" ").first
87
+ end
88
+
89
+ # @return [Channel] The channel in which this message was sent
90
+ def channel
91
+ @channel ||= begin
92
+ case command
93
+ when "INVITE", RPL_CHANNELMODEIS.to_s, RPL_BANLIST.to_s
94
+ Channel.find_ensured(params[1], @bot)
95
+ when RPL_NAMEREPLY.to_s
96
+ Channel.find_ensured(params[2], @bot)
97
+ else
98
+ if params.first.start_with?("#")
99
+ Channel.find_ensured(params.first, @bot)
100
+ elsif numeric_reply? and params[1].start_with?("#")
101
+ Channel.find_ensured(params[1], @bot)
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ # @api private
108
+ # @return [MatchData]
109
+ def match(regexp, type)
110
+ if type == :ctcp
111
+ @matches[:ctcp][regexp] ||= ctcp_message.match(regexp)
112
+ else
113
+ @matches[:other][regexp] ||= message.to_s.match(regexp)
114
+ end
115
+ end
116
+
117
+ # @return [String, nil] the CTCP message, without \001 control characters
118
+ def ctcp_message
119
+ return unless ctcp?
120
+ params.last =~ /\001(.+)\001/
121
+ $1
122
+ end
123
+
124
+ def ctcp_args
125
+ return unless ctcp?
126
+ ctcp_message.split(" ")[1..-1]
127
+ end
128
+
129
+ # @return [String, nil]
130
+ def message
131
+ @message ||= begin
132
+ if error?
133
+ error.to_s
134
+ elsif regular_command?
135
+ params.last
136
+ end
137
+ end
138
+ end
139
+
140
+ # Replies to a message, automatically determining if it was a
141
+ # channel or a private message.
142
+ #
143
+ # @param [String] text the message
144
+ # @param [Boolean] prefix if prefix is true and the message was in
145
+ # a channel, the reply will be prefixed by the nickname of whoever
146
+ # send the mesage
147
+ # @return [void]
148
+ def reply(text, prefix = false)
149
+ text = text.to_s
150
+ if channel && prefix
151
+ text = text.split("\n").map {|l| "#{user.nick}: #{l}"}.join("\n")
152
+ end
153
+
154
+ (channel || user).send(text)
155
+ end
156
+
157
+ # Like #reply, but using {Channel#safe_send}/{User#safe_send}
158
+ # instead
159
+ #
160
+ # @param (see #reply)
161
+ # @return (see #reply)
162
+ def safe_reply(text, prefix = false)
163
+ text = text.to_s
164
+ if channel && prefix
165
+ text = "#{user.nick}: #{text}"
166
+ end
167
+ (channel || user).safe_send(text)
168
+ end
169
+
170
+ # Reply to a CTCP message
171
+ #
172
+ # @return [void]
173
+ def ctcp_reply(answer)
174
+ return unless ctcp?
175
+ @bot.raw "NOTICE #{user.nick} :\001#{ctcp_command} #{answer}\001"
176
+ end
177
+
178
+ private
179
+ def regular_command?
180
+ !numeric_reply? # a command can only be numeric or "regular"…
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,62 @@
1
+ # -*- coding: utf-8 -*-
2
+ require "thread"
3
+
4
+ module Cinch
5
+ # @api private
6
+ class MessageQueue
7
+ def initialize(socket, bot)
8
+ @socket = socket
9
+ @queue = Queue.new
10
+ @time_since_last_send = nil
11
+ @bot = bot
12
+
13
+ @log = []
14
+ end
15
+
16
+ # @return [void]
17
+ def queue(message)
18
+ command = message.split(" ").first
19
+
20
+ if command == "PONG"
21
+ @queue.unshift(message)
22
+ else
23
+ @queue << message
24
+ end
25
+ end
26
+
27
+ # @return [void]
28
+ def process!
29
+ while true
30
+ mps = @bot.config.messages_per_second
31
+ max_queue_size = @bot.config.server_queue_size
32
+
33
+ if @log.size > 1
34
+ time_passed = 0
35
+
36
+ @log.each_with_index do |one, index|
37
+ second = @log[index+1]
38
+ time_passed += second - one
39
+ break if index == @log.size - 2
40
+ end
41
+
42
+ messages_processed = (time_passed * mps).floor
43
+ effective_size = @log.size - messages_processed
44
+
45
+ if effective_size <= 0
46
+ @log.clear
47
+ elsif effective_size >= max_queue_size
48
+ sleep 1.0/mps
49
+ end
50
+ end
51
+
52
+ message = @queue.pop.to_s.chomp
53
+
54
+ @log << Time.now
55
+ @bot.logger.log(message, :outgoing) if @bot.config.verbose
56
+
57
+ @time_since_last_send = Time.now
58
+ @socket.print message + "\r\n"
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,205 @@
1
+ module Cinch
2
+ module Plugin
3
+ include Helpers
4
+
5
+ module ClassMethods
6
+ Pattern = Struct.new(:pattern, :use_prefix, :method)
7
+ Listener = Struct.new(:event, :method)
8
+
9
+ # Set a match pattern.
10
+ #
11
+ # @param [Regexp, String] pattern A pattern
12
+ # @option options [Symbol] :method (:execute) The method to execute
13
+ # @option options [Boolean] :use_prefix (true) If true, the
14
+ # plugin prefix will automatically be prepended to the
15
+ # pattern.
16
+ # @return [void]
17
+ def match(pattern, options = {})
18
+ options = {:use_prefix => true, :method => :execute}.merge(options)
19
+ @__cinch_patterns ||= []
20
+ @__cinch_patterns << Pattern.new(pattern, options[:use_prefix], options[:method])
21
+ end
22
+
23
+ # Events to listen to.
24
+ # @overload listen_to(*types, options = {})
25
+ # @param [String, Symbol, Integer] *types Events to listen to. Available
26
+ # events are all IRC commands in lowercase as symbols, all numeric
27
+ # replies, and the following:
28
+ #
29
+ # - :channel (a channel message)
30
+ # - :private (a private message)
31
+ # - :message (both channel and private messages)
32
+ # - :error (IRC errors)
33
+ # - :ctcp (ctcp requests)
34
+ #
35
+ # @param [Hash] options
36
+ # @option options [Symbol] :method (:listen) The method to
37
+ # execute
38
+ # @return [void]
39
+ def listen_to(*types)
40
+ options = {:method => :listen}
41
+ if types.last.is_a?(Hash)
42
+ options.merge!(types.pop)
43
+ end
44
+
45
+ @__cinch_listeners ||= []
46
+
47
+ types.each do |type|
48
+ @__cinch_listeners << Listener.new(type, options[:method])
49
+ end
50
+ end
51
+
52
+ def ctcp(command)
53
+ (@__cinch_ctcps ||= []) << command.to_s.upcase
54
+ end
55
+
56
+ # Define a help message which will be returned on "<prefix>help
57
+ # <pluginname>".
58
+ #
59
+ # @param [String] message
60
+ # @return [void]
61
+ def help(message)
62
+ @__cinch_help_message = message
63
+ end
64
+
65
+ # Set the plugin prefix.
66
+ #
67
+ # @param [String] prefix
68
+ # @return [void]
69
+ def prefix(prefix)
70
+ @__cinch_prefix = prefix
71
+ end
72
+
73
+ # Set which kind of messages to react on (i.e. call {#execute})
74
+ #
75
+ # @param [Symbol<:message, :channel, :private>] target React to all,
76
+ # only public or only private messages?
77
+ # @return [void]
78
+ def react_on(target)
79
+ @__cinch_react_on = target
80
+ end
81
+
82
+ # Define the plugin name.
83
+ #
84
+ # @param [String] name
85
+ # @return [void]
86
+ def plugin(name)
87
+ @__cinch_name = name
88
+ end
89
+
90
+ # @return [String]
91
+ # @api private
92
+ def __plugin_name
93
+ @__cinch_name || self.name.split("::").last.downcase
94
+ end
95
+
96
+ # @return [void]
97
+ # @api private
98
+ def __register_with_bot(bot, instance)
99
+ plugin_name = __plugin_name
100
+
101
+ (@__cinch_listeners || []).each do |listener|
102
+ bot.debug "[plugin] #{plugin_name}: Registering listener for type `#{listener.event}`"
103
+ bot.on(listener.event, [], instance) do |message, plugin|
104
+ plugin.__send__(listener.method, message) if plugin.respond_to?(listener.method)
105
+ end
106
+ end
107
+
108
+ if (@__cinch_patterns ||= []).empty?
109
+ @__cinch_patterns << Pattern.new(plugin_name, true, nil)
110
+ end
111
+
112
+ prefix = @__cinch_prefix || bot.config.plugins.prefix
113
+ if prefix.is_a?(String)
114
+ prefix = Regexp.escape(prefix)
115
+ end
116
+ @__cinch_patterns.each do |pattern|
117
+ if pattern.use_prefix && prefix
118
+ case pattern.pattern
119
+ when Regexp
120
+ pattern.pattern = /^#{prefix}#{pattern.pattern}/
121
+ when String
122
+ pattern.pattern = prefix + pattern.pattern
123
+ end
124
+ end
125
+
126
+ react_on = @__cinch_react_on || :message
127
+
128
+ bot.debug "[plugin] #{plugin_name}: Registering executor with pattern `#{pattern.pattern}`, reacting on `#{react_on}`"
129
+
130
+ bot.on(react_on, pattern.pattern, instance, pattern) do |message, plugin, pattern, *args|
131
+ if plugin.respond_to?(pattern.method)
132
+ method = plugin.method(pattern.method)
133
+ arity = method.arity - 1
134
+ if arity > 0
135
+ args = args[0..arity - 1]
136
+ elsif arity == 0
137
+ args = []
138
+ end
139
+ method.call(message, *args)
140
+ end
141
+ end
142
+ end
143
+
144
+ (@__cinch_ctcps || []).each do |ctcp|
145
+ bot.debug "[plugin] #{plugin_name}: Registering CTCP `#{ctcp}`"
146
+ bot.on(:ctcp, ctcp, instance, ctcp) do |message, plugin, ctcp, *args|
147
+ plugin.__send__("ctcp_#{ctcp.downcase}", message, *args)
148
+ end
149
+ end
150
+
151
+ if @__cinch_help_message
152
+ bot.debug "[plugin] #{plugin_name}: Registering help message"
153
+ bot.on(:message, /#{prefix}help #{Regexp.escape(plugin_name)}/, @__cinch_help_message) do |message, help_message|
154
+ message.reply(help_message)
155
+ end
156
+ end
157
+ end
158
+ end
159
+
160
+ # @return [Bot]
161
+ attr_reader :bot
162
+ # @api private
163
+ def initialize(bot)
164
+ @bot = bot
165
+ self.class.__register_with_bot(bot, self)
166
+ end
167
+
168
+ # @param (see Bot#synchronize)
169
+ # @yield
170
+ # @return (see Bot#synchronize)
171
+ # @see Bot#synchronize
172
+ def synchronize(*args, &block)
173
+ @bot.synchronize(*args, &block)
174
+ end
175
+
176
+ # This method will be executed whenever an event the plugin
177
+ # {Plugin::ClassMethods#listen_to listens to} occurs.
178
+ #
179
+ # @abstract
180
+ # @return [void]
181
+ # @see Plugin::ClassMethods#listen_to
182
+ def listen(*args)
183
+ end
184
+
185
+ # This method will be executed whenever a message matches the
186
+ # {Plugin::ClassMethods#match match pattern} of the plugin.
187
+ #
188
+ # @abstract
189
+ # @return [void]
190
+ # @see Plugin::ClassMethods#match
191
+ def execute(*args)
192
+ end
193
+
194
+ # Provides access to plugin-specific options.
195
+ #
196
+ # @return [Hash] A hash of options
197
+ def config
198
+ @bot.config.plugins.options[self.class]
199
+ end
200
+
201
+ def self.included(by)
202
+ by.extend ClassMethods
203
+ end
204
+ end
205
+ end