207 lines
6.9 KiB
Ruby
207 lines
6.9 KiB
Ruby
# -*- coding: utf-8 -*-
|
||
#
|
||
# = Cinch help plugin
|
||
# This plugin parses the +help+ option of all plugins registered with
|
||
# the bot when connecting to the server and provides a nice interface
|
||
# for querying these help strings via the 'help' command.
|
||
#
|
||
# == Commands
|
||
# [cinch help]
|
||
# Print the intro message and list all plugins.
|
||
# [cinch help <plugin>]
|
||
# List all commands with explanations for a plugin.
|
||
# [cinch help search <query>]
|
||
# List all commands with explanations that contain <query>.
|
||
#
|
||
# == Dependencies
|
||
# None.
|
||
#
|
||
# == Configuration
|
||
# Add the following to your bot’s configure.do stanza:
|
||
#
|
||
# config.plugins.options[Cinch::Help] = {
|
||
# :intro => "%s at your service."
|
||
# }
|
||
#
|
||
# [intro]
|
||
# First message posted when the user issues 'help' to the bot
|
||
# without any parameters. %s is replaced with Cinch’s current
|
||
# nickname.
|
||
#
|
||
# == Writing help messages
|
||
# In order to add help strings, update your plugins to set +help+
|
||
# properly like this:
|
||
#
|
||
# class YourPlugin
|
||
# include Cinch::Plugin
|
||
#
|
||
# set :help, <<-HELP
|
||
# command <para1> <para2>
|
||
# This command does this and that. The description may
|
||
# as well be multiline.
|
||
# command2 <para1> [optpara]
|
||
# Another sample command.
|
||
# HELP
|
||
# end
|
||
#
|
||
# Cinch recognises a command in the help string as one if it starts at
|
||
# the beginning of the line, all subsequent indented lines are considered
|
||
# part of the command explanation, up until the next line that is not
|
||
# indented or the end of the string. Multiline explanations are therefore
|
||
# handled fine, but you should still try to keep the descriptions as short
|
||
# as possible, because many IRC servers will block too much text as flooding,
|
||
# resulting in the help messages being cut. Instead, provide a web link or
|
||
# something similar for full-blown descriptions.
|
||
#
|
||
# The command itself may be in any form you want (as long as it’s a single
|
||
# line), but I recommend the following conventions so users know how to
|
||
# talk to the bot:
|
||
#
|
||
# [cinch ...]
|
||
# A command Cinch understands when posted publicely into the channel,
|
||
# prefixed with its name (or whatever prefix you want, replace "cinch"
|
||
# accordingly).
|
||
# [/msg cinch ...]
|
||
# A command Cinch understands when send directly to him via PM and
|
||
# without a prefix.
|
||
# [[/msg] cinch ...]
|
||
# A command Cinch understands when posted publicely with his nick as
|
||
# a prefix, or privately to him without any prefix.
|
||
# [...]
|
||
# That is, a bare command without a prefix. Cinch watches the conversion
|
||
# on the channel, and whenever he encounters this string/pattern he will
|
||
# take action, without any prefix at all.
|
||
#
|
||
# The word "cinch" in the command string will automatically be replaced with
|
||
# the actual nickname of your bot.
|
||
#
|
||
# == Author
|
||
# Marvin Gülker (Quintus)
|
||
#
|
||
# == License
|
||
# A help plugin for Cinch.
|
||
# Copyright © 2012 Marvin Gülker
|
||
#
|
||
# This program is free software: you can redistribute it and/or modify
|
||
# it under the terms of the GNU Lesser General Public License as published by
|
||
# the Free Software Foundation, either version 3 of the License, or
|
||
# (at your option) any later version.
|
||
#
|
||
# This program is distributed in the hope that it will be useful,
|
||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
# GNU Lesser General Public License for more details.
|
||
#
|
||
# You should have received a copy of the GNU Lesser General Public License
|
||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
||
# Help plugin for Cinch.
|
||
class Cinch::Help
|
||
include Cinch::Plugin
|
||
|
||
listen_to :connect, :method => :on_connect
|
||
match /help(.*)/i, :prefix => lambda{|msg| Regexp.compile("^#{Regexp.escape(msg.bot.nick)}:?\s*")}, :react_on => :channel
|
||
match /help(.*)/i, :use_prefix => false, :react_on => :private
|
||
|
||
set :help, <<-EOF
|
||
[/msg] cinch help
|
||
Post a short introduction and list available plugins.
|
||
[/msg] cinch help <plugin>
|
||
List all commands available in a plugin.
|
||
[/msg] cinch help search <query>
|
||
Search all plugin’s commands and list all commands containing
|
||
<query>.
|
||
EOF
|
||
|
||
def execute(msg, query)
|
||
query = query.strip.downcase
|
||
response = ""
|
||
|
||
# Act depending on the subcommand.
|
||
if query.empty?
|
||
response << @intro_message.strip << "\n"
|
||
response << "Available plugins:\n"
|
||
response << bot.config.plugins.plugins.map{|plugin| format_plugin_name(plugin)}.join(", ")
|
||
response << "\n'help <plugin>' for help on a specific plugin."
|
||
|
||
# Help for a specific plugin
|
||
elsif plugin = @help.keys.find{|plugin| format_plugin_name(plugin) == query}
|
||
@help[plugin].keys.sort.each do |command|
|
||
response << format_command(command, @help[plugin][command], plugin)
|
||
end
|
||
|
||
# help search <...>
|
||
elsif query =~ /^search (.*)$/i
|
||
query2 = $1.strip
|
||
@help.each_pair do |plugin, hsh|
|
||
hsh.each_pair do |command, explanation|
|
||
response << format_command(command, explanation, plugin) if command.include?(query2)
|
||
end
|
||
end
|
||
|
||
# For plugins without help
|
||
response << "Sorry, no help available for the #{format_plugin_name(plugin)} plugin." if response.empty?
|
||
|
||
# Something we don't know what do do with
|
||
else
|
||
response << "Sorry, I cannot find '#{query}'."
|
||
end
|
||
|
||
response << "Sorry, nothing found." if response.empty?
|
||
msg.reply(response)
|
||
end
|
||
|
||
# Called on startup. This method iterates the list of registered plugins
|
||
# and parses all their help messages, collecting them in the @help hash,
|
||
# which has a structure like this:
|
||
#
|
||
# {Plugin => {"command" => "explanation"}}
|
||
#
|
||
# where +Plugin+ is the plugin’s class object. It also parses configuration
|
||
# options.
|
||
def on_connect(msg)
|
||
@help = {}
|
||
|
||
if config[:intro]
|
||
@intro_message = config[:intro] % bot.nick
|
||
else
|
||
@intro_message = "#{bot.nick} at your service."
|
||
end
|
||
|
||
bot.config.plugins.plugins.each do |plugin|
|
||
@help[plugin] = Hash.new{|h, k| h[k] = ""}
|
||
next unless plugin.help # Some plugins don't provide help
|
||
current_command = "<unparsable content>" # For not properly formatted help strings
|
||
|
||
plugin.help.lines.each do |line|
|
||
if line =~ /^\s+/
|
||
@help[plugin][current_command] << line.strip
|
||
else
|
||
current_command = line.strip.gsub(/cinch/i, bot.name)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
private
|
||
|
||
# Format the help for a single command in a nice, unicode mannor.
|
||
def format_command(command, explanation, plugin)
|
||
result = ""
|
||
|
||
result << "┌─ " << command << " ─── Plugin: " << format_plugin_name(plugin) << " ─" << "\n"
|
||
result << explanation.lines.map(&:strip).join(" ").chars.each_slice(80).map(&:join).join("\n")
|
||
result << "\n" << "└ ─ ─ ─ ─ ─ ─ ─ ─\n"
|
||
|
||
result
|
||
end
|
||
|
||
# Downcase the plugin name and clip it to the last component
|
||
# of the namespace, so it can be displayed in a user-friendly
|
||
# way.
|
||
def format_plugin_name(plugin)
|
||
plugin.to_s.split("::").last.downcase
|
||
end
|
||
|
||
end
|