Добавил больше плагинов

This commit is contained in:
Alexander Yakovlev 2017-01-18 16:43:58 +07:00
parent 8339fb95c9
commit 08896e9598
6 changed files with 602 additions and 3 deletions

View file

@ -2,3 +2,4 @@ source 'https://rubygems.org'
gem 'cinch'
gem 'cinch-dicebag'
gem 'translit'
gem 'nokogiri'

27
bot.rb
View file

@ -1,19 +1,40 @@
require 'cinch'
require 'cinch-dicebag'
require 'translit'
require_relative 'plugins/history'
require_relative 'plugins/link_info'
require_relative 'plugins/help'
require_relative 'plugins/seen'
bot = Cinch::Bot.new do
configure do |c|
c.server = "irc.forestnet.org"
c.port = 6667
c.channels = ["#urq"]
c.channels = ["#urq", "#instead", "#qsp", "#ifrus"]
c.nick = 'Дроид'
c.plugins.plugins = [Cinch::Plugins::Dicebag]
c.plugins.plugins = [
Cinch::Plugins::Dicebag,
Cinch::History,
Cinch::Help,
Cinch::LinkInfo,
Cinch::Seen
]
c.plugins.options[Cinch::History] = {
:mode => :max_messages,
:max_messages => 20,
# :max_age => 5,
:time_format => "%H:%M",
:file => "/home/znc/seenlog.dat"
}
c.plugins.options[Cinch::LinkInfo] = {
:blacklist => [/\.xz$/i, /\.zip$/i, /\.rar$/i],
:no_description => true,
}
end
on :message do |m|
if m.user.nick == 'MAlischka' or m.user.nick == 'MA' then
Channel('#urq').send(Translit.convert(m.message, :russian))
m.reply( Translit.convert(m.message, :russian) )
end
end
end

206
plugins/help.rb Normal file
View file

@ -0,0 +1,206 @@
# -*- 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 bots 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 Cinchs 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 its 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 plugins 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 plugins 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

184
plugins/history.rb Normal file
View file

@ -0,0 +1,184 @@
# -*- coding: utf-8 -*-
#
# = Cinch history plugin
# This plugin adds a non-persistant, short-time memory to Cinch that allows
# users to replay part of the session Cinch currently observers. This plugin
# can act in two different modes: The default mode is :max_messags, in which
# Cinch will only remember a fixed number of messages for replay; however, in
# a busy channel this may not provide enough information for finding ones way
# into a discussion without having to raise the limit of remembered messages
# to some extraordinarily high value which this plugin is not meant for (if
# you want real, persistant logging, use the Logging plugin). Instead, you
# can switch to :max_age mode, in which Cinch will remember all messages
# whose age does not exceed a certain number of minutes.
#
# == Usage
# As a user, issue the following command privately to Cinch:
#
# /msg cinch history
#
# Cinch will respond to you with a private message containing the history
# in the configured format.
#
# == Configuration
# Add the following to your bots configure.do stanza:
#
# config.plugins.options[Cinch::History] = {
# :mode => :max_messages,
# :max_messages => 10,
# # :max_age => 5,
# :time_format => "%H:%M"
# }
#
# [mode (:max_messages)]
# Either :max_messages or :max_age, see explanations above.
# [max_messages (10)]
# If you chose :max_messages mode, this is the number of messages
# Cinch will remember.
# [max_age (5)]
# If you chose :max_age mode, this is the maximum age in minutes
# that a message may have before Cinch drops it from his memory.
# The smallest useful value is 1, floating point values are not
# allowed.
# [time_format ("%H:%M")]
# When replaying history, Cinch prints the time stamp for each
# message next to it, in the format specified via this configuration
# option. See date(1) for possible directives.
#
# == Author
# Marvin Gülker (Quintus)
#
# == License
# A history 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/>.
class Cinch::History
include Cinch::Plugin
Entry = Struct.new(:time, :prefix, :message)
listen_to :connect, :method => :on_connect
listen_to :channel, :method => :on_channel
listen_to :topic, :method => :on_topic
listen_to :away, :method => :on_away
listen_to :unaway, :method => :on_unaway
listen_to :action, :method => :on_action
listen_to :leaving, :method => :on_leaving
listen_to :join, :method => :on_join
timer 60, :method => :check_message_age
match /history/, :method => :replay, :react_on => :private, :use_prefix => false
set :help, <<-HELP
/msg cinch history
Sends the most recent messages of the channel to you via PM.
HELP
def on_connect(*)
@mode = config[:mode] || :max_messages
@max_messages = config[:max_messages] || 10
@max_age = (config[:max_age] || 5 ) * 60
@timeformat = config[:time_format] || "%H:%M"
@history_mutex = Mutex.new
@history = []
end
def on_channel(msg)
return if msg.message.start_with?("\u0001")
@history_mutex.synchronize do
@history << Entry.new(msg.time, msg.user.nick, msg.message)
if @mode == :max_messages
# In :max_messages mode, let messages over the limit just
# fall out of the history.
@history.shift if @history.length > @max_messages
end
end
end
def on_topic(msg)
@history_mutex.synchronize do
@history << Entry.new(msg.time, "##", "#{msg.user.nick} changed the topic to “#{msg.channel.topic}")
end
end
def on_away(msg)
@history_mutex.synchronize do
@history << Entry.new(msg.time, "##", "#{msg.user.nick} is away (“#{msg.message}”)")
end
end
def on_unaway(msg)
@history_mutex.synchronize do
@history << Entry.new(msg.time, "##" "#{msg.user.nick} is back")
end
end
def on_action(msg)
return unless msg.message =~ /^\u0001ACTION(.*?)\u0001/
@history_mutex.synchronize do
@history << Entry.new(msg.time, "**", "#{msg.user.nick} #{$1.strip}")
end
end
def on_leaving(msg, user)
@history_mutex.synchronize do
@history << Entry.new(msg.time, "<=", "#{user.nick} left the channel")
end
end
def on_join(msg)
@history_mutex.synchronize do
@history << Entry.new(msg.time, "=>", "#{msg.user.nick} entered the channel")
end
end
# In :max_age mode, remove messages from the history older than
# the threshold.
def check_message_age
return unless @mode == :max_age
@history_mutex.synchronize do
@history.delete_if{|entry| Time.now - entry.time > @max_age}
end
end
def replay(msg)
# Informative preamble
if @mode == :max_age
msg.reply("Here are the messages of the last #@max_age seconds:")
else
msg.reply("Here are the last #{@history.count} messages:")
end
# Actual historic(al) response
@history_mutex.synchronize do
r = @history.reduce("") do |answer, entry|
answer + format_entry(entry)
end
msg.reply(r.chomp)
end
end
private
def format_entry(entry)
sprintf("[%s] %15s | %s\n", entry.time.strftime(@timeformat), entry.prefix, entry.message)
end
end

93
plugins/link_info.rb Normal file
View file

@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
#
# = Cinch Link Info plugin
# Inspects any links that are posted into a channel Cinch
# is currently in and prints out the value of the title
# and description meta tags, if any.
#
# == Dependencies
# * Gem: nokogiri
#
# == Configuration
# Add the following to your bots configure.do stanza:
#
# config.plugins.options[Cinch::LinkInfo] = {
# :blacklist => [/\.xz$/],
# :no_description => false,
# }
#
# [blacklist]
# If a URL matches any of the regular expressions defined
# in this array, it will not be inspected. This plugin
# alraedy ignores URLs ending in common image file
# extensions, so you dont have to specify .png, .jpeg,
# etc.
# [no_description]
# Set this to true if you want Cinch to not print the
# content of the Meta description tag.
#
# == Author
# Marvin Gülker (Quintus)
#
# == License
# A link info 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/>.
require "open-uri"
require "nokogiri"
# Plugin for inspecting links pasted into channels.
class Cinch::LinkInfo
include Cinch::Plugin
# Default list of URL regexps to ignore.
DEFAULT_BLACKLIST = [/\.png$/i, /\.jpe?g$/i, /\.bmp$/i, /\.gif$/i, /\.pdf$/i].freeze
set :help, <<-HELP
http[s]://...
Ill fire a GET request at any link I encounter, parse the HTML
meta tags, and paste the result back into the channel.
HELP
match %r{(https?://.*?)(?:\s|$|,|\.\s|\.$)}, :use_prefix => false
def execute(msg, url)
blacklist = DEFAULT_BLACKLIST.dup
blacklist.concat(config[:blacklist]) if config[:blacklist]
return if blacklist.any?{|entry| url =~ entry}
debug "URL matched: #{url}"
html = Nokogiri::HTML(open(url))
if node = html.at_xpath("html/head/title")
msg.reply("Title: #{node.text}")
end
if !config[:no_description]
node = html.at_xpath('html/head/meta[@name="description"]') || html.at_xpath('html/head/meta[@name="Description"]')
if node
if node[:content].chars.count > 255
msg.reply("Description: #{node[:content].lines.first(3).join("").gsub("\n", " ")[0..255] + "..."}")
else
msg.reply("Description: #{node[:content]}")
end
end
end
rescue => e
error "#{e.class.name}: #{e.message}"
end
end

94
plugins/seen.rb Normal file
View file

@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
#
# = Cinch Seen plugin
#
# == Configuration
# Add the following to your bots configure.do stanza:
#
# config.plugins.options[Cinch::History] = {
# :file => "/var/cache/seenlog.dat"
# }
#
# [file]
# Where to store the message log. This is a required
# argument.
#
# == Author
# Marvin Gülker (Quintus)
#
# == License
# An seen info plugin for Cinch.
# Copyright © 2014 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/>.
class Cinch::Seen
include Cinch::Plugin
SeenInfo = Struct.new(:time, :nick, :message)
match /seen (.*)/
listen_to :connect, :method => :on_connect
listen_to :channel, :method => :on_channel
def on_connect(*)
@filepath = config[:file] || raise("Missing required argument: :file")
@file = File.open(@filepath, "a+")
@file.seek(0, File::SEEK_END) # Work around Ruby bug https://bugs.ruby-lang.org/issues/10039
@filemutex = Mutex.new
at_exit{@file.close}
end
def on_channel(msg)
return if msg.message.start_with?("\u0001") # ACTION
@filemutex.synchronize do
@file.puts("#{msg.time.to_i}\0#{msg.user.nick}\0#{msg.message.strip}")
end
end
def execute(msg, nick)
if nick == bot.nick
msg.reply "Self-reference err err tilt BOOOOM"
return
end
if info = find_last_message(nick)
msg.reply("I have last seen #{nick} on #{info.time} saying: #{info.message}")
else
msg.reply("I have not yet seen #{nick} saying something.")
end
end
private
def find_last_message(nick)
@filemutex.synchronize do
@file.rewind
@file.lines.reverse_each do |line|
parts = line.split("\0")
if parts[1] == nick
return SeenInfo.new(Time.at(parts[0].to_i), parts[1], parts[2])
end
end
@file.seek(0, File::SEEK_END)
end
nil
end
end