class StatsPlugin < Plugin
- @@commands = {
+ @@commands = {
"stats" => "handle_stats",
"track" => "handle_track",
- "untrack" => "handle_untrack",
+ "untrack" => "handle_untrack",
"listtokens" => "handle_listtokens",
"rmabuser" => "handle_rmabuser"
}
m.reply "What a crazy fool! Did you mean |help stats?"
return
end
-
+
meth = self.method(@@commands[m.plugin])
meth.call(m)
end
ret = Array.new
Utils.safe_exec("/usr/local/bin/gnuvd", m.params).each{|line| if line.length > 5 then ret << line end}
m.reply ret.delete_at(0)
- while ret[0] =~ /^[[:alpha:]_]*[0-9]/
+ while ret[0] =~ /^[[:alpha:]_]*[0-9]/
m.reply ret.delete_at(0)
end
while ret[0] =~ /^[0-9]/
warning _("Invalid alias entry %{alias} : %{command} in %{filename}: %{reason}") %
{:alias => a, :command => c, :filename => @data_file, :reason => $1}
end
- end
- end
+ end
+ end
- def save
+ def save
FileUtils.mkdir_p(@data_path)
Utils.safe_save(@data_file) {|f| f.write @aliases.to_yaml}
end
command.scan(/<(\w+)>/).flatten.to_set ==
text.split.grep(/\A[:*](\w+)\Z/) {$1}.to_set or
raise AliasDefinitionError.new(_('The arguments in alias must match the substitutions in command, and vice versa'))
-
+
begin
map text, :action => :"alias_handle<#{text}>", :auth_path => 'run'
rescue
when /^(\d+)m$/
timer = $1.to_i * 60
when /^(\d+)h$/
- timer = $1.to_i * 60 * 60
+ timer = $1.to_i * 60 * 60
when /^(\d+)d$/
timer = $1.to_i * 60 * 60 * 24
else
xml = @bot.httputil.get("http://bash.org/xml/?random&num=1", :cache => false)
else
xml = @bot.httputil.get("http://bash.org/xml/?" + id + "&num=1")
- end
+ end
unless xml
m.reply "bash.org rss parse failed"
return
end
doc.elements.each("*/item") {|e|
- if(id != 0)
+ if(id != 0)
reply = e.elements["title"].text.gsub(/QDB: /,"") + " " + e.elements["link"].text.gsub(/QDB: /,"") + "\n"
reply = reply + e.elements["description"].text.gsub(/\<br \/\>/, "\n")
else
def help(plugin, topic="")
"cal [month year] => show current calendar [optionally specify month and year]"
end
-
+
def cal_path
@bot.config["cal.path"]
end
else
raise "Error: Couldn't find chucknorris.yml[.gz]"
end
-
+
debug "+ [chucknorris] Loading #{path}..."
-
+
@@facts = YAML.load(fyml).map{|fact,(score,votes)| votes >= MIN_VOTES ? [score,fact] : nil}.compact
debug "+ [chucknorris] #{@@facts.length} Chuck Norris facts loaded..."
debug " Random fact: #{@@facts[rand(@@facts.size)].inspect}"
-
+
super
end
def name
"chucknorris"
end
-
+
# Just a little helper for the initialize method...
def find_facts_file(name)
full_path = File.join Config::datadir, "plugins", name
found_files[0]
end
end
-
+
# HELP!
def help(plugin, topic="chuck")
"chuck|norris|chucknorris [min_rating] => show a random Chuck Norris fact (optional minimum rating from 1-10, default=6.0)."
- #\"fact [person]\" shows a fact about someone in the channel.
+ #\"fact [person]\" shows a fact about someone in the channel.
end
# The meat.
def help(plugin, topic="")
"deepthought => think a deep thought."
end
-
+
def deepthought(m, params)
m.reply THOUGHTS.pick_one
end
Config.register Config::StringValue.new('dictclient.match_format',
:default => '<matches>––<database>',
:desc => _('Format of match results. <matches> will be replaced with the formatted headwords, <database> with the formatted database name'))
-
+
def initialize
super
end
-
+
# create a DICT object, which is passed to the block. after the block finishes,
# the DICT object is automatically disconnected. the return value of the block
# is returned from this method.
end
ret
end
-
+
def format_headword(w)
@bot.config['dictclient.headword_format'].gsub '<headword>', w
end
-
+
def format_database(d)
@bot.config['dictclient.database_format'].gsub '<database>', d
end
-
+
def cmd_define(m, params)
phrase = params[:phrase].to_s
results = with_dict(m) {|d| d.define(params[:database], params[:phrase])}
# the number of definitions is above dictclient.max_defs_before_collapse
if results.any? {|r| r.database != results[0].database} &&
results.length > @bot.config['dictclient.max_defs_before_collapse']
- _("Many definitions for %{phrase} were found in %{databases}. Use 'define <phrase> from <database> to view a definition.") %
+ _("Many definitions for %{phrase} were found in %{databases}. Use 'define <phrase> from <database> to view a definition.") %
{ :phrase => format_headword(phrase),
:databases => results.collect {|r| r.database}.uniq.
collect {|d| format_database d}.join(', ') }
}.join ' | '
end
else
- _("No definition for %{phrase} found from %{database}.") %
+ _("No definition for %{phrase} found from %{database}.") %
{ :phrase => format_headword(phrase),
:database => format_database(params[:database]) }
end
)
end
-
+
def cmd_match(m, params)
phrase = params[:phrase].to_s
results = with_dict(m) {|d| d.match(params[:database],
)
}.join ' '
else
- _("Nothing matched %{query} from %{database} using %{strategy}") %
+ _("Nothing matched %{query} from %{database} using %{strategy}") %
{ :query => format_headword(phrase),
:database => format_database(params[:database]),
:strategy => params[:strategy] }
end
)
end
-
+
def cmd_databases(m, params)
with_dict(m) do |d|
m.reply _("Databases: %{list}") % {
}
end
end
-
+
def cmd_strategies(m, params)
with_dict(m) do |d|
m.reply _("Strategies: %{list}") % {
}
end
end
-
+
def help(plugin, topic='')
case topic
when 'define'
def help(plugin, topic="")
"digg [<max>=5] => show digg headlines, [<max>=5] => return up to <max> headlines (use a negative number to show all the headlines on one line)"
end
-
+
def digg(m, params)
max = params[:limit].to_i
debug "max is #{max}"
max = 8 if max > 8
matches = Array.new
doc.elements.each("rss/channel/item") {|e|
- matches << [ e.elements["title"].text,
+ matches << [ e.elements["title"].text,
Time.parse(e.elements["pubDate"].text).strftime('%a @ %I:%M%p') ]
done += 1
break if done >= max
- }
+ }
if oneline
m.reply matches.collect{|mat| mat[0]}.join(" | ")
else
trans_from = params[:fromlang] ? params[:fromlang] : @bot.config['translate.default_from']
trans_to = params[:tolang] ? params[:tolang] : @bot.config['translate.default_to']
trans_text = params[:phrase].to_s
-
+
lang_match = langs.join("|")
unless(trans_from =~ /^(#{lang_match})$/ && trans_to =~ /^(#{lang_match})$/)
m.reply "invalid language: valid languagess are: #{langs.join(' ')}"
require 'soap/wsdlDriver'
# TODO why not use HttpUtil instead of open-uri?
-require 'open-uri'
+require 'open-uri'
require 'rexml/document'
require 'erb'
return parse(retrieve),Time.new
end
private
- def retrieve
+ def retrieve
forecast = @forecaster.NDFDgenByDay(
@lat,@long,Time.now.strftime("%Y-%m-%d"),2,"24 hourly")
(REXML::Document.new(forecast)).root
end
end
end
-
+
def get_forecast(m,loc)
begin
@cache_mutex.synchronize do
end
return nil unless fortune
-
+
# Try setting the config entry
config_par = {:key => 'fortune.path', :value => [fortune], :silent => true }
debug "Setting fortune.path to #{fortune}"
## say 'em!
m.reply "Fortune categories: #{categories.join ', '}"
end
-
+
end
plugin = FortunePlugin.new
plugin.map 'fortune categories', :action => "categories"
m.reply reply
}
end
-
+
def freshmeat(m, params)
max = params[:limit].to_i
max = 8 if max > 8
plugin = FreshmeatPlugin.new
plugin.map 'freshmeat search :limit *search', :action => 'search_freshmeat',
:defaults => {:limit => 4}, :requirements => {:limit => /^\d+$/}
-plugin.map 'freshmeat :limit', :defaults => {:limit => 4},
+plugin.map 'freshmeat :limit', :defaults => {:limit => 4},
:requirements => {:limit => /^\d+$/}
if wc[word].key?(:when)
tr = _("%{word} learned from %{user} on %{date}") % {:word => word, :user => wc[word][:who], :date => wc[word][:when]}
else
- tr = _("%{word} learned from %{user}") % {:word => word, :user => wc[word][:who]}
+ tr = _("%{word} learned from %{user}") % {:word => word, :user => wc[word][:who]}
end
m.reply tr
- when :delete
+ when :delete
if pars.empty?
m.reply _("provide a word")
return
# (in quiz/) or web pages.
#
def fetch_data( m )
- # Read the winning messages file
+ # Read the winning messages file
@win_messages = Array.new
winfile = datafile 'win_messages'
if File.exists? winfile
end
end
- # If less than all other players' scores, append to table
+ # If less than all other players' scores, append to table
unless inserted
i += 1 unless q.rank_table.empty?
q.rank_table << [nick, stats]
message = m.message.downcase.strip
- nick = m.sourcenick.to_s
+ nick = m.sourcenick.to_s
# Support multiple alternate answers and cores
answer = q.answers.find { |ans| ans.valid?(message) }
class RoshamboPlugin < Plugin
def initialize
- super
+ super
@scoreboard = {}
@beats = { :rock => :scissors, :paper => :rock, :scissors => :paper}
@plays = @beats.keys
class RoulettePlugin < Plugin
Config.register Config::BooleanValue.new('roulette.autospin',
- :default => true,
+ :default => true,
:desc => "Automatically spins the roulette at the butlast shot")
Config.register Config::BooleanValue.new('roulette.kick',
- :default => false,
+ :default => false,
:desc => "Kicks shot players from the channel")
Config.register Config::BooleanValue.new('roulette.twice_in_a_row',
- :default => false,
+ :default => false,
:desc => "Allow players to go twice in a row")
def initialize
k = match[1]
total_players += 1
-
+
win_rate = v.wins.to_f / v.games * 100
if h_win_percent[0].nil? || win_rate > h_win_percent[1] && v.games > 2
h_win_percent = [[k], win_rate]
#-- vim:sw=2:et
#kate: indent-width 2
#++
-#
+#
# :title: Shiritori Plugin for RBot
#
# Author:: Yaohan Chen <yaohan.chen@gmail.com>
# players can interrupt a turn to join, or a free mode where anyone can speak at any
# time.
#
-# In Japanese mode, if present, the plugin can use normalize-japanese
+# In Japanese mode, if present, the plugin can use normalize-japanese
# <http://neruchan.mine.nu:60880/normalize-japanese.rb> to allow
# katakana words be used like hiragana.
-#
+#
# TODO
# * a system to describe settings, so they can be displayed, changed and saved
# * adjust settings during game
def has_word?(s)
raise NotImplementedError
end
-
+
# whether any word starts with prefix, excluding words in excludes. This can be
# possible with non-enumerable dictionaries since some dictionary engines provide
# prefix searching.
@words = words
# debug "Created dictionary with #{@words.length} words"
end
-
+
# whether string s is a word
def has_word?(s)
@words.include? s
end
-
+
# whether any word starts with prefix, excluding words in excludes
def any_word_starting?(prefix, excludes)
# (@words - except).any? {|w| w =~ /\A#{prefix}.+/}
# whether it's possible to continue a word
class Shiritori
attr_reader :used_words
-
+
# dictionary:: a Dictionary object
# overlap_lengths:: a Range for allowed lengths to overlap when continuing words
# check_continuable:: whether all words are checked whether they're continuable,
@allow_reuse = allow_reuse
@used_words = []
end
-
+
# Prefix of s with length n
def head_of(s, n)
# TODO ruby2 unicode
def range_under(r, n)
r.begin .. [r.end, n-1].min
end
-
+
# TODO allow the ruleset to customize this
def continues?(w2, w1)
# this uses the definition w1[-n,n] == w2[0,n] && n < [w1.length, w2.length].min
range_under(@overlap_lengths, [len(w1), len(w2)].min).any? {|n|
tail_of(w1, n)== head_of(w2, n)}
end
-
+
# Checks whether *any* unused word in the dictionary completes the word
# This has the limitation that it can't detect when a word is continuable, but the
# only continuers aren't continuable
range_under(@overlap_lengths, len(s)).any? {|n|
@dictionary.any_word_starting?(tail_of(s, n), @used_words) }
end
-
- # Given a string, give a verdict based on current shiritori state and dictionary
+
+ # Given a string, give a verdict based on current shiritori state and dictionary
def process(s)
# TODO optionally allow used words
# TODO ruby2 unicode
@timer_handle = nil
@say = say
@when_die = when_die
-
+
# TODO allow other forms of dictionaries
dictionary = WordlistDictionary.new(@ruleset[:words])
@game = Shiritori.new(dictionary, @ruleset[:overlap_lengths],
@ruleset[:check_continuable],
@ruleset[:allow_reuse])
end
-
+
def say(s)
@say.call(s)
end
# * when time_limit > 0, new players can join at any time, but existing players must
# take turns, each of which expires after time_limit
# * when time_imit is 0, anyone can speak in the game at any time
- def take_turns?
+ def take_turns?
@players.length > 1 && @ruleset[:time_limit] > 0
end
-
+
# the player who has the current turn
def current_player
@players.first
def previous_word
@game.used_words[-2]
end
-
+
# announce the current word, and player if take_turns?
def announce
say(if take_turns?
end
announce
end
-
+
# handle when turn time limit goes out
def time_out
if @ruleset[:lose_when_timeout]
say _("%{player} took too long and is out of the game. Try again next game!") %
{ :player => current_player }
- if @players.length == 2
+ if @players.length == 2
# 2 players before, and one should remain now
# since the game is ending, save the trouble of removing and booting the player
say _("%{player} is the last remaining player and the winner! Congratulations!") %
def handle_message(m)
message = m.message
speaker = m.sourcenick.to_s
-
+
return unless @ruleset[:listen] =~ message
# in take_turns mode, only new players are allowed to interrupt a turn
return if @booted_players.include? speaker ||
- (take_turns? &&
+ (take_turns? &&
speaker != current_player &&
(@players.length > 1 && @players.include?(speaker)))
m.reply _("It's impossible to continue the chain from %{word}. Start with another word.") % {:word => message}
end
end
-
+
# end the game
def die
# redefine restart_timer to no-op
_("A game in which each player must continue the previous player's word, by using its last one or few characters/letters of the word to start a new word. 'shiritori <ruleset>' => Play shiritori with a set of rules. Available rulesets: %{rulesets}. 'shiritori stop' => Stop the current shiritori game.") %
{:rulesets => @rulesets.keys.join(', ')}
end
-
+
def initialize()
super
@games = {}
-
+
# TODO make rulesets more easily customizable
# TODO initialize default ruleset from config
# Default values of rulesets
end
return ruleset
end
-
+
# start shiritori in a channel
def cmd_shiritori(m, params)
if @games.has_key?( m.channel )
end
end
end
-
+
# change rules for current game
def cmd_set(m, params)
require 'enumerator'
params[:rules].each_slice(2) {|opt, value| new_rules[opt] = value}
raise NotImplementedError
end
-
+
# stop the current game
def cmd_stop(m, params)
if @games.has_key? m.channel
m.reply _("No game to stop here, because no game is being played.")
end
end
-
+
# remove the game, so channel messages are no longer processed, and timer removed
def remove_game(channel)
@games.delete channel
end
-
+
# all messages from a channel is sent to its shiritori game if any
def message(m)
return unless @games.has_key?(m.channel)
# send the message to the game in the channel to handle it
@games[m.channel].handle_message m
end
-
+
# remove all games
def cleanup
@games.each_key {|g| g.die}
@color = 'Wild'
raise if value and not value == '+4'
if value
- @value = value.dup
+ @value = value.dup
@shortform = 'w'+value
else
@value = nil
# only be possible if the first W+4 was illegal, so it wouldn't
# apply for a W+4 played on a +2 anyway.
#
- if @picker == 0 and Wild === cards.first and cards.first.value
+ if @picker == 0 and Wild === cards.first and cards.first.value
# save the previous discard in case of challenge
@last_discard = @discard.dup
# save the color too, in case it was a Wild
def help(plugin, topic)
"googlefight <keyword 1> <keyword 2> [... <keyword n+1>] => battles given keywords based on amount of google search results and announces the winner!"
end
-
+
def fight(m, params)
keywords = parse_keywords(params)
return if keywords.nil?
-
+
keywords.map! do |k|
[k, google_count(k)]
end
-
+
m.reply output(keywords)
end
-
+
def output(result)
result = result.sort_by { |e| e[1] }.reverse
str = result.map do |kw|
:count => kw[1].to_s.gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1,")
}
end.join(" vs. ")
-
+
unless result[0][1].zero?
str << _(" -- %{keyword} wins!") % {
:keyword => Bold+result[0][0]+Bold
}
else
str << _(" -- no winner here!")
- end
+ end
end
-
+
def parse_keywords(params)
str = params[:keywords].join(" ")
-
+
# foo "foo bar" bar
# no separators so assume they're all separate keywords
if str.match(/(?:"[\w\s]+"|\w+)(?: (?:"[\w\s]+"|\w+))+/)
str.scan(/"[^"]+"|\S+/).flatten
end
end
-
+
def google_count(query)
url = 'http://www.google.com/search?hl=en&safe=off&btnG=Search&q=' << CGI.escape(query)
html = Net::HTTP.get(URI.parse((url)))
end
# Find the movies with a participation of :who in the year :year
- # TODO: allow year to be either a year or a decade ('[in the] 1960s')
+ # TODO: allow year to be either a year or a decade ('[in the] 1960s')
#
def movies(m, params)
who = params[:who].to_s
]
##
-# Amounts
+# Amounts
##
@@amt = [
"accumulation",
"cold sores",
"anal warts",
]
-
+
def help(plugin, topic="")
return "[msg]insult me|<person> => insult you or <person>. msginsult insults in private"
end
@registry[thing] = params[:val].to_i
karma(m, params)
end
-
+
def help(plugin, topic="")
"karma module: Listens to everyone's chat. <thing>++/<thing>-- => increase/decrease karma for <thing>, karma for <thing>? => show karma for <thing>, karmastats => show stats. Karma is a community rating system - only in-channel messages can affect karma and you cannot adjust your own."
end
:desc => "List of regexp which match linkbot messages; each regexp needs to have three captures, which in order are the nickname of the original speaker, network, and original message",
:on_change => proc {|bot, v| bot.plugins['linkbot'].update_patterns})
# TODO use template strings instead of regexp for user friendliness
-
+
# Initialize the plugin
def initialize
super
def chat(m, params)
line = generate_string(params[:seed1], params[:seed2])
if line != "#{params[:seed1]} #{params[:seed2]}"
- m.reply line
+ m.reply line
else
m.reply "I can't :("
end
m.reply "I can't :("
end
end
-
+
def message(m)
return if ignore? m
if m.action?
message = "#{m.sourcenick} #{message}"
end
-
+
@learning_queue.push message
random_markov(m, message) unless m.replied?
end
val = Math.exp($2).to_s
expr.gsub!(/#{Regexp.escape exp}/, "+#{val}")
end
-
+
while expr =~ /^\s*(dec2hex\s*(\d+))\s*\?*/
exp = $1
val = sprintf("%x", $2)
while expr =~ /(log\s*((\d+\.?\d*)|\d*\.?\d+))\s*/
exp = $1
res = $2
-
+
if res == 0
val = "Infinity"
else
val = Math.log(res).to_s
end
-
+
expr.gsub!(/#{Regexp.escape exp}/, "+#{val}")
end
def do_mode(m, channel, user, mode)
unless channel
if m.private?
- target = user.nil? ? "you" : user
+ target = user.nil? ? "you" : user
m.reply "You should tell me where you want me to #{mode} #{target}."
return
else
# in case it couldn't be achieved.
class NickRecoverPlugin < Plugin
-
+
Config.register Config::IntegerValue.new('irc.nick_retry',
:default => 60, :valiedate => Proc.new { |v| v >= 0 },
:on_change => Proc.new do |bot, v|
# previously identified successfully
class NickServPlugin < Plugin
-
+
Config.register Config::StringValue.new('nickserv.name',
:default => "nickserv", :requires_restart => false,
:desc => _("Name of the nick server (all lowercase)"))
return _("nickserv listnicks: lists nicknames and associated password the bot knows about - you will need config level auth access to do this one and it will reply by privmsg only")
end
end
-
+
def genpasswd
return Irc::Bot::Auth.random_password
end
m.reply _("uh ... something went wrong ...")
end
end
-
+
def connect
@identified = false
do_identify
end
-
+
def nicktaken(nick)
if @registry.has_key?(nick)
ns_say "GHOST #{nick} #{@registry[nick]}"
def help(plugin, topic="")
"dns <hostname|ip> => show local resolution results for hostname or ip address"
end
-
+
def name_to_ip(m, params)
begin
a = getaddresses(params[:host])
m.reply "#{params[:host]}: not found"
end
end
-
+
def ip_to_name(m, params)
begin
a = gethostname(params[:ip])
# Automatically auths with Q on QuakeNet servers
class QPlugin < Plugin
-
+
def help(plugin, topic="")
case topic
when ""
return "qauth register <email>: register with Q, an email on how to proceed will be sent to the email address you provide"
end
end
-
+
def initialize
super
# this plugin only wants to store strings!
@registry['quakenet.auth'] = params[:password]
m.okay
end
-
+
def connect
identify(nil, {}) if on_quakenet?
end
if @changed[channel]
debug "Writing new quotefile for channel #{channel} ..."
Utils.safe_save(datafile channel) {|file|
- quotes.compact.each {|q|
+ quotes.compact.each {|q|
file.puts "#{q.num} | #{q.date} | #{q.source} | #{q.quote}"
}
}
def addquote(source, channel, quote)
@lists[channel] = Array.new if(!@lists.has_key?(channel))
- num = @lists[channel].length
+ num = @lists[channel].length
@lists[channel][num] = Quote.new(num, Time.new, source.fullform, quote)
@changed[channel] = true
return num
"between 0 and 1 (i.e. 0% and 100%). A reaction can have multiple replies, each with a different chance; if the total of the chances is less than one, " +
"there is a chance that the trigger will not actually cause a reply. Otherwise, the chances express the relative frequency of the replies."
when :trigger, :triggers
- "reaction triggers can have one of the format: single_word 'multiple words' \"multiple words \" /regular_expression/ !regular_expression!. " +
+ "reaction triggers can have one of the format: single_word 'multiple words' \"multiple words \" /regular_expression/ !regular_expression!. " +
"If prefixed by 'act:' (e.g. act:/(order|command)s/) the bot will only respond if a CTCP ACTION matches the trigger"
when :reply, :replies
"reaction replies are simply messages that the bot will reply when a trigger is matched. " +
end
def no_more(m, params)
who = params.has_key?(:who) ? params[:who] : m.sourcenick
- deleted = params.has_key?(:string) ?
+ deleted = params.has_key?(:string) ?
del_reminder(who, params[:string].to_s) : del_reminder(who)
if deleted
m.okay
- else
+ else
m.reply "but I wasn't going to :/"
end
end
return m.reply("failed to execute ri")
end
ret = ret.gsub(/\t/, " ").split(/\n/).join(" ").gsub(/\s\s+/, ' ')
-
+
if ret.length > @bot.config['ri.max_length']
if !m.private? && tgt.to_s != m.sourcenick
return m.reply('entry is too long to send to the channel or to some other user, use /msg to ask me about it')
require 'rss'
-# Try to load rss/content/2.0 so we can access the data in <content:encoded>
+# Try to load rss/content/2.0 so we can access the data in <content:encoded>
# tags.
begin
require 'rss/content/2.0'
# Make an 'unique' ID for a given item, based on appropriate bot options
# Currently only suppored is bot.config['rss.show_updated']: when false,
# only the guid/link is accounted for.
-
+
def make_uid(item)
uid = [item.guid! || item.link!]
if @bot.config['rss.show_updated']
else
date = item.source.updated.content.to_s
end
- elsif item.respond_to?(:pubDate)
+ elsif item.respond_to?(:pubDate)
if item.pubDate.class <= Time
date = item.pubDate.strftime("%Y/%m/%d %H:%M")
else
desc_opt[:limit] = @bot.config['rss.text_max']
desc_opt[:a_href] = :link_out if @bot.config['rss.show_links']
- # We prefer content_encoded here as it tends to provide more html formatting
+ # We prefer content_encoded here as it tends to provide more html formatting
# for use with ircify_html.
if item.respond_to?(:content_encoded) && item.content_encoded
desc = item.content_encoded.ircify_html(desc_opt)
class SalutPlugin < Plugin
Config.register Config::BooleanValue.new('salut.all_languages',
- :default => true,
+ :default => true,
:desc => "Check for a salutation in all languages and not just in the one defined by core.language",
:on_change => Proc.new {|bot, v| bot.plugins['salut'].reload}
)
Config.register Config::BooleanValue.new('salut.address_only',
- :default => true,
+ :default => true,
:desc => "When set to true, the bot will only reply to salutations directed at him",
:on_change => Proc.new {|bot, v| bot.plugins['salut'].reload}
)
return unless salut
# If the bot wasn't addressed, we continue only if the match was exact
# (apart from space and punctuation) or if @match[:dest] matches too
- return unless to_me or m.message =~ /^#{@punct}#{salut.first}#{@punct}$/ or m.message =~ @match[salut[1]][:dest]
+ return unless to_me or m.message =~ /^#{@punct}#{salut.first}#{@punct}$/ or m.message =~ @match[salut[1]][:dest]
h = Time.new.hour
case h
when 4...12
#
# Scripts are little Ruby programs that run in the context of the script
# plugin. You can create them directly in an IRC channel, and invoke them just
-# like normal rbot plugins.
+# like normal rbot plugins.
define_structure :Command, :code, :nick, :created, :channel
# Convenience variables, can be accessed by scripts:
args = m.message.split
- args.delete_at( 0 )
- user = args.empty? ? m.sourcenick : args.first
+ args.delete_at( 0 )
+ user = args.empty? ? m.sourcenick : args.first
Thread.start {
# TODO allow different safe levels for different botusers
def handle_add_force( m, params )
handle_add( m, params, true )
end
-
+
def handle_del( m, params )
name = params[:name]
page = params[:page].to_i
page = [page, 1].max
page = [page, num_pages].min
- str = cmds[(page-1)*cmds_per_page, cmds_per_page].join(', ')
+ str = cmds[(page-1)*cmds_per_page, cmds_per_page].join(', ')
- m.reply "Available scripts (page #{page}/#{num_pages}): #{str}"
+ m.reply "Available scripts (page #{page}/#{num_pages}): #{str}"
end
def gcalc(m, params)
what = params[:words].to_s
searchfor = CGI.escape(what)
-
+
debug "Getting gcalc thing: #{searchfor.inspect}"
url = GOOGLE_SEARCH + searchfor
end
debug "#{html.size} bytes of html recieved"
-
+
results = html.scan(GOOGLE_CALC_RESULT)
debug "results: #{results.inspect}"
-
+
if results.length != 1
m.reply "couldn't calculate #{what}"
return
end
-
+
result = results[0][0].ircify_html
debug "replying with: #{result.inspect}"
m.reply "#{result}"
end
-
+
def gcount(m, params)
what = params[:words].to_s
searchfor = CGI.escape(what)
-
+
debug "Getting gcount thing: #{searchfor.inspect}"
url = GOOGLE_SEARCH + searchfor
end
debug "#{html.size} bytes of html recieved"
-
+
results = html.scan(GOOGLE_COUNT_RESULT)
debug "results: #{results.inspect}"
-
+
if results.length != 1
m.reply "couldn't count #{what}"
return
end
-
+
result = results[0][0].ircify_html
debug "replying with: #{result.inspect}"
m.reply "total results: #{result}"
def gdef(m, params)
what = params[:words].to_s
searchfor = CGI.escape("define " + what)
-
+
debug "Getting gdef thing: #{searchfor.inspect}"
url = GOOGLE_WAP_SEARCH + searchfor
debug html
results = html.scan(GOOGLE_DEF_RESULT)
debug "results: #{results.inspect}"
-
+
if results.length != 1
m.reply "couldn't find a definition for #{what} on Google"
return
end
-
+
head = results[0][0].ircify_html
text = results[0][1].ircify_html
link = results[0][2]
plugin.map "search *words", :action => 'google', :threaded => true
plugin.map "google *words", :action => 'google', :threaded => true
-plugin.map "gcount *words", :action => 'gcount', :threaded => true
+plugin.map "gcount *words", :action => 'gcount', :threaded => true
plugin.map "gcalc *words", :action => 'gcalc', :threaded => true
plugin.map "gdef *words", :action => 'gdef', :threaded => true
plugin.map "wp :lang *words", :action => 'wikipedia', :requirements => { :lang => /^\w\w\w?$/ }, :threaded => true
def help(plugin, topic="")
"seen <nick> => have you seen, or when did you last see <nick>"
end
-
+
def privmsg(m)
unless(m.params =~ /^(\S)+$/)
m.reply "incorrect usage: " + help(m.plugin)
m.target.to_s, m.message.dup)
when QuitMessage
return if m.address?
- @registry[m.sourcenick] = Saw.new(m.sourcenick.dup, now, "QUIT",
+ @registry[m.sourcenick] = Saw.new(m.sourcenick.dup, now, "QUIT",
nil, m.message.dup)
when NickMessage
return if m.address?
@registry[m.newnick] = saw
when PartMessage
return if m.address?
- @registry[m.sourcenick] = Saw.new(m.sourcenick.dup, Time.new, "PART",
+ @registry[m.sourcenick] = Saw.new(m.sourcenick.dup, Time.new, "PART",
m.target.to_s, m.message.dup)
when JoinMessage
return if m.address?
- @registry[m.sourcenick] = Saw.new(m.sourcenick.dup, Time.new, "JOIN",
+ @registry[m.sourcenick] = Saw.new(m.sourcenick.dup, Time.new, "JOIN",
m.target.to_s, m.message.dup)
when TopicMessage
return if m.address? or m.info_or_set == :info
- @registry[m.sourcenick] = Saw.new(m.sourcenick.dup, Time.new, "TOPIC",
+ @registry[m.sourcenick] = Saw.new(m.sourcenick.dup, Time.new, "TOPIC",
m.target.to_s, m.message.dup)
end
end
-
+
def seen(saw)
ret = "#{saw.nick} was last seen "
ago = Time.new - saw.time
-
+
if (ago.to_i == 0)
ret << "just now, "
else
ret << "changing the topic of #{saw.where} to #{saw.message}"
end
end
-
+
end
plugin = SeenPlugin.new
plugin.register("seen")
require "uri"
class ShortenURLs < Plugin
- # starting from about shorturl 0.8.4, the WWW module is not defined
+ # starting from about shorturl 0.8.4, the WWW module is not defined
include WWW rescue nil
Config.register Config::ArrayValue.new('shortenurls.services_blacklist',
@bot.register_filter(:slashdot, :htmlinfo) { |s| slashdot_filter(s) }
end
end
-
+
def search_slashdot(m, params)
max = params[:limit].to_i
search = params[:search].to_s
m.reply "search for #{search} failed"
end
end
-
+
def slashdot(m, params)
debug params.inspect
max = params[:limit].to_i
max = 8 if max > 8
matches = Array.new
doc.elements.each("*/story") {|e|
- matches << [ e.elements["title"].text,
- e.elements["author"].text,
+ matches << [ e.elements["title"].text,
+ e.elements["author"].text,
e.elements["time"].text.gsub(/\d{4}-(\d{2})-(\d{2})/, "\\2/\\1").gsub(/:\d\d$/, "") ]
done += 1
break if done >= max
- }
+ }
if oneline
m.reply matches.collect{|mat| mat[0]}.join(" | ")
else
HE = [
['a superhumanly strong','an underprivileged','a globe-trotting','an impetuous','a shy','a suave','a notorious','a one-legged','an all-American','a short-sighted','an otherworldly','a hate-fuelled','a scrappy','an unconventional','a jaded','a leather-clad','a fiendish','a Nobel prize-winning','a suicidal','a maverick','a bookish','an old-fashioned','a witless','a lounge-singing','a war-weary','a scarfaced','a gun-slinging','an obese','a time-tossed','a benighted','an uncontrollable','an immortal','an oversexed','a world-famous','an ungodly','a fast talking','a deeply religious','a lonely','a sword-wielding','a genetically engineered'],
-
+
['white trash','zombie','shark-wrestling','playboy','guitar-strumming','Jewish',
'sweet-toothed','bohemian','crooked','chivalrous','moralistic','amnesiac','devious','drug-addicted',
'voodoo','Catholic','overambitious','coffee-fuelled','pirate','misogynist','skateboarding',
'arachnophobic','Amish','small-town','Republican','one-eyed','gay','guerilla','vegetarian',
'dishevelled','alcoholic','flyboy','ninja','albino','hunchbacked','neurotic','umbrella-wielding',
'native American','soccer-playing','day-dreaming'],
-
+
['grifter','stage actor','paramedic','gentleman spy','jungle king','hairdresser',
'photographer','ex-con','vagrant','filmmaker','werewolf','senator','romance novelist','shaman','cop',
'rock star','farmboy','cat burglar','cowboy','cyborg','inventor','assassin','boxer','dog-catcher',
'master criminal','gangster','firefighter','househusband','dwarf','librarian','paranormal investigator',
'Green Beret','waffle chef','vampire hunter','messiah','astronaut','sorceror','card sharp','matador',
'barbarian'],
-
+
['with a robot buddy named Sparky','whom everyone believes is mad','gone bad',
'with a mysterious suitcase handcuffed to his arm','living undercover at Ringling Bros. Circus',
'searching for his wife''s true killer','who dotes on his loving old ma','looking for ''the Big One''',
'a plucky','a sarcastic','a psychotic','a hard-bitten','a manipulative','an orphaned','a cosmopolitan',
'a chain-smoking','a cold-hearted','a warm-hearted','a sharp-shooting','an enchanted','a wealthy','a pregnant',
'a mentally unstable','a virginal','a brilliant','a disco-crazy','a provocative','an artistic'],
-
+
['tempestuous', 'Buddhist', 'foul-mouthed', 'nymphomaniac', 'green-skinned', 'impetuous', 'African-American','punk','hypochondriac','junkie','blonde','goth','insomniac','gypsy','mutant','renegade', 'tomboy','French-Canadian','motormouth','belly-dancing','communist','hip-hop','thirtysomething',
'cigar-chomping','extravagent','out-of-work','Bolivian','mute','cat-loving','snooty','wisecracking',
'red-headed','winged','kleptomaniac','antique-collecting','psychic','gold-digging','bisexual','paranoid',
'streetsmart'],
-
+
['archaeologist','pearl diver','mechanic','detective','hooker','femme fatale',
'former first lady','barmaid','fairy princess','magician''s assistant','schoolgirl','college professor',
'angel','bounty hunter','opera singer','cab driver','soap star','doctor','politician','lawyer','nun',
'snake charmer','journalist','bodyguard','vampire','stripper','Valkyrie','wrestler','mermaid','single mother',
'safe cracker','traffic cop','research scientist','queen of the dead','Hell''s Angel','museum curator',
'advertising executive','widow','mercenary','socialite'],
-
+
['on her way to prison for a murder she didn''t commit','trying to make a difference in a man''s world',
'with the soul of a mighty warrior','looking for love in all the wrong places','with an MBA from Harvard',
'who hides her beauty behind a pair of thick-framed spectacles','with the power to see death',
'BLOOD', 'HARD', 'STEEL', 'TERMINAL', 'HOT', 'COLD', 'TOTAL', 'PROGNOSIS:',
'BURNING', 'FAST', 'PAINFUL', 'MISSION:', 'DEADLY', 'PARTIAL',
'RAGING', 'CORDIAL'],
-
+
['DECISION', 'INCISION', 'CONCLUSION',
'CONCUSSION', 'HEAT', 'FIRE', 'RECOIL', 'INSTINCT', 'DESIRE', 'WEAPON',
'BADGE', 'DEED', 'JUSTICE', 'HEAT', 'VENGEANCE', 'RECESSION', 'COMBUSTION',
'JUDGEMENT', 'WARNING', 'AWAKENING', 'JURISDICTION', 'BASIS', 'FINDINGS',
'INJECTION', 'REJECTION', 'REMISSION', 'DIGESTION', 'IDENTITY',
'DISPERSION'],
-
+
['II','III','IV','V','VI','VII','VIII','IX','X','XI','XII','XIII','XIV','XV','XVI','XVII','XVIII','XIX']
]
def help(plugin, topic="")
"movieplot => generate a random movie scenario. movietitle => generate a random movie title."
end
-
+
def get_random_things(thing_array)
thing_array.map { |things| things.pick_one }
end
-
+
def movieplot(m, params)
he_things = get_random_things(HE)
she_things = get_random_things(SHE)
end
['/', '_'].each { |sp|
arr = Array.new
- zone.split(sp).each{ |s|
+ zone.split(sp).each{ |s|
s[0] = s[0,1].upcase
s[1, s.length] = s[1, s.length].downcase if sp == '/'
arr.push(s) }
zone = arr.join( sp )
}
-
+
tz = TZInfo::Timezone.get( zone )
"#{tz.friendly_identifier} - #{tz.now.strftime( '%a %b %d %H:%M' )} #{tz.current_period.abbreviation}"
end
@cache = cache
end
-
+
# whether the translator supports this direction
def support?(from, to)
from != to && @directions[from].include?(to)
end
def update_default
- @default_translators = bot.config['translator.default_list'] & @translators.keys
+ @default_translators = bot.config['translator.default_list'] & @translators.keys
end
def cmd_translator(m, params)
def help(plugin, topic="")
"tube [district|circle|metropolitan|central|jubilee|bakerloo|waterlooandcity|hammersmithandcity|victoria|eastlondon|northern|piccadilly] => display tube service status for the specified line(Docklands Light Railway is not currently supported)" # , tube stations => list tube stations (not lines) with problems"
end
-
+
def tube(m, params)
line = params[:line]
tube_page = @bot.httputil.get('http://www.tfl.gov.uk/tfl/livetravelnews/realtime/tube/default.html')
stations_array.push $1
end
}
- if stations_array.empty?
+ if stations_array.empty?
m.reply "There are no station-specific announcements"
return
else
Config.register Config::BooleanValue.new('weather.advisory',
:default => true,
:desc => "Should the bot report special weather advisories when any is present?")
-
+
def help(plugin, topic="")
case topic
when "nws"
"weather nws <station> => display the current conditions at the location specified by the NOAA National Weather Service station code <station> ( lookup your station code at http://www.nws.noaa.gov/data/current_obs/ )"
when "station", "wu"
- "weather [<units>] <location> => display the current conditions at the location specified, looking it up on the Weather Underground site; you can use 'station <code>' to look up data by station code ( lookup your station code at http://www.weatherunderground.com/ ); you can optionally set <units> to 'metric' or 'english' if you only want data with the units; use 'both' for units to go back to having both."
+ "weather [<units>] <location> => display the current conditions at the location specified, looking it up on the Weather Underground site; you can use 'station <code>' to look up data by station code ( lookup your station code at http://www.weatherunderground.com/ ); you can optionally set <units> to 'metric' or 'english' if you only want data with the units; use 'both' for units to go back to having both."
else
"weather information lookup. Looks up weather information for the last location you specified. See topics 'nws' and 'wu' for more information"
end
end
-
+
def initialize
super
@wu_url = "http://mobile.wunderground.com/cgi-bin/findweather/getForecast?brand=mobile%s&query=%s"
@wu_station_url = "http://mobile.wunderground.com/auto/mobile%s/global/stations/%s.html"
end
-
+
def weather(m, params)
if params[:where].empty?
if @registry.has_key?(m.sourcenick)
m.reply "cowardly refusing to follow more than 3 redirects"
return
end
-
+
begin
uri = URI.parse(hostname)
rescue URI::InvalidURIError => err
m.reply "#{hostname} is not a valid URI"
return
end
-
+
unless(uri)
m.reply "incorrect usage: " + help(m.plugin)
return
end
-
-
+
+
resp = @bot.httputil.head(uri)
server = resp['Server']
if(server && server.length > 0)
m.reply "couldn't tell what #{uri.host} is running"
end
- if(resp.code == "302" || resp.code == "301")
+ if(resp.code == "302" || resp.code == "301")
newloc = resp['location']
newuri = URI.parse(newloc)
# detect and ignore incorrect redirects (to relative paths etc)
# }
# }
# end
-#
+#
module Irc
class Bot
:action => 'bot_mode',
:auth_path => 'talk::do'
-basics.map "join :chan :pass",
+basics.map "join :chan :pass",
:action => 'bot_join',
:defaults => {:pass => nil},
:auth_path => 'move'
msg = _("no known filters")
end
else
- msg = _("known filters: ") << ar.join(", ")
+ msg = _("known filters: ") << ar.join(", ")
end
m.reply msg
end
if ar.empty?
msg = _("no known filter groups")
else
- msg = _("known filter groups: ") << ar.join(", ")
+ msg = _("known filter groups: ") << ar.join(", ")
end
m.reply msg
end
irclog "@ #{m.source} asked #{who} about #{[m.ctcp, m.message].join(' ')}", logtarget
end
else
- if m.public?
+ if m.public?
irclog "<#{m.source}> #{m.logmessage}", m.target
else
irclog "<#{m.source}(#{m.sourceaddress})> #{m.logmessage}", m.source
#
# Presently, the hash returned on success has only one key, :return, whose
# value is the actual return value of the successfull dispatch.
- #
+ #
# TODO this same kind of mechanism could actually be used in MessageMapper
# itself to be able to handle the case of multiple plugins having the same
# 'first word' ...
#
# If you have to do large-scale editing of the Bot data Hash,
# please use with_botdata.
- #
+ #
def set_botdata(key, value=nil, &block)
Irc::Utils.bot.plugins['userdata'].set_data(self, key, value, &block)
end
end
when 'deflate'
debug "inflating body"
- # From http://www.koders.com/ruby/fid927B4382397E5115AC0ABE21181AB5C1CBDD5C17.aspx?s=thread:
+ # From http://www.koders.com/ruby/fid927B4382397E5115AC0ABE21181AB5C1CBDD5C17.aspx?s=thread:
# -MAX_WBITS stops zlib from looking for a zlib header
inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
begin
_("%{m} minutes") % { :m => secs/SEC_PER_MIN }
when secs > 1
_("%{m} seconds") % { :m => secs }
- else
+ else
_("one second")
end
end
# Execute an external program, returning a String obtained by redirecting
- # the program's standards errors and output
+ # the program's standards errors and output
#
def Utils.safe_exec(command, *args)
IO.popen("-") { |p|
def DBHash.create_db(name)
debug "DBHash: creating empty db #{name}"
- return BDB::Hash.open(name, nil,
+ return BDB::Hash.open(name, nil,
BDB::CREATE | BDB::EXCL, 0600)
end
# We alias the to_s method to __to_s__ to make
# it accessible in all classes
- alias :__to_s__ :to_s
+ alias :__to_s__ :to_s
end
# The Irc module is used to keep all IRC-related classes
GEN_HOST = /#{HOSTNAME}|#{HOSTADDR}/
# # FreeNode network replaces the host of affiliated users with
- # # 'virtual hosts'
+ # # 'virtual hosts'
# # FIXME we need the true syntax to match it properly ...
# PDPC_HOST_PART = /[0-9A-Za-z.-]+/
# PDPC_HOST = /#{PDPC_HOST_PART}(?:\/#{PDPC_HOST_PART})+/
# # NOTE: the final optional and non-greedy dot is needed because some
# # servers (e.g. FreeNode) send the hostname of the services as "services."
# # which is not RFC compliant, but sadly done.
- # GEN_HOST_EXT = /#{PDPC_HOST}|#{GEN_HOST}\.??/
+ # GEN_HOST_EXT = /#{PDPC_HOST}|#{GEN_HOST}\.??/
# Sadly, different networks have different, RFC-breaking ways of cloaking
# the actualy host address: see above for an example to handle FreeNode.
# Adds a user to the channel
#
def add_user(user, opts={})
- silent = opts.fetch(:silent, false)
+ silent = opts.fetch(:silent, false)
if has_user?(user)
warn "Trying to add user #{user} to channel #{self} again" unless silent
else
return 'english'
end
- Config.register Config::EnumValue.new('core.language',
+ Config.register Config::EnumValue.new('core.language',
:default => Irc::Bot::Language.from_locale, :wizard => true,
:values => Proc.new{|bot|
Dir.new(Config::datadir + "/languages").collect {|f|
f =~ /\.lang$/ ? f.gsub(/\.lang$/, "") : nil
}.compact
- },
+ },
:on_change => Proc.new {|bot, v| bot.lang.set_language v},
:desc => "Which language file the bot should use")
-
+
def initialize(bot, language)
@bot = bot
set_language language
data
end
if ColorCode.key?(f)
- ColorCode[f]
+ ColorCode[f]
else
0
end
# threaded::
# a boolean (defaults to false) that determines whether the action should be
# called in a separate thread.
- #
+ #
#
# Further examples:
#
@priority ||= 1
end
- # Returns the symbol :BotModule
+ # Returns the symbol :BotModule
def botmodule_class
:BotModule
end
#
# This command is now superceded by the #map() command, which should be used
# instead whenever possible.
- #
+ #
def register(cmd, opts={})
raise ArgumentError, "Second argument must be a hash!" unless opts.kind_of?(Hash)
who = @manager.who_handles?(cmd)
m.reply(_("incorrect usage, ask for help using '%{command}'") % {:command => "#{@bot.nick}: help #{m.plugin}"})
end
- # Define the priority of the module. During event delegation, lower
+ # Define the priority of the module. During event delegation, lower
# priority modules will be called first. Default priority is 1
def priority=(prio)
if @priority != prio
end
def sort_modules
- @sorted_modules = (core_modules + plugins).sort do |a, b|
+ @sorted_modules = (core_modules + plugins).sort do |a, b|
a.priority <=> b.priority
end || []
@repeat = opts[:repeat] if opts.include? :repeat
if block_given?
- @block = block
+ @block = block
elsif opts[:code]
@block = opts[:code]
end
# The purpose is to provide a workaround for ruby-gettext, which treats empty output
# from msgmerge as error in the po file, where it should mean that no modification
# is needed to the defpo. For updates on the issue follow
-# http://rubyforge.org/pipermail/gettext-users-en/2008-June/000094.html
+# http://rubyforge.org/pipermail/gettext-users-en/2008-June/000094.html
msgmerge = ENV['REAL_MSGMERGE_PATH'] || 'msgmerge'
def ruby(*args)
command config('rubyprog'), *args
end
-
+
def make(task = nil)
command(*[config('makeprog'), task].compact)
end
def srcdirectory?(path)
File.dir?(srcfile(path))
end
-
+
def srcfile?(path)
File.file?(srcfile(path))
end
__send__ "exec_#{task}"
end
end
-
+
def run_metaconfigs
@config.load_script "#{@ardir}/metaconfig"
end
end
# picked up many entries from cvs-1.11.1/src/ignore.c
- JUNK_FILES = %w(
+ JUNK_FILES = %w(
core RCSLOG tags TAGS .make.state
.nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
*~ *.old *.bak *.BAK *.orig *.rej _$* *$
@mock3 = MockModule.new(3)
@mock4 = MockModule.new(4)
@mock5 = MockModule.new(5)
-
+
# This whole thing is a PITA because PluginManagerClass is a singleton
unless @@manager
@@manager = PluginManagerClass.instance
@@manager.instance_eval { @sort_call_count = nil }
@@manager.mark_priorities_dirty
- # We add the modules to the lists in the wrong order
+ # We add the modules to the lists in the wrong order
# on purpose to make sure the sort is working
@@manager.plugins.clear
@@manager.core_modules.clear
dlist << @mock2
dlist << @mock5
end
-
+
def test_default_priority
plugin = TestRealBotModule.new
assert_equal 1, plugin.priority
@mock3 = MockModule.new(3)
@mock4 = MockModule.new(4)
@mock5 = MockModule.new(5)
-
+
# This whole thing is a PITA because PluginManagerClass is a singleton
unless @@manager
@@manager = PluginManagerClass.instance
@@manager.instance_eval { @sort_call_count = nil }
@@manager.mark_priorities_dirty
- # We add the modules to the lists in the wrong order
+ # We add the modules to the lists in the wrong order
# on purpose to make sure the sort is working
@@manager.plugins.clear
@@manager.core_modules.clear
dlist << @mock2
dlist << @mock5
end
-
+
def test_above
@@manager.delegate_event('test', :above => 3)
assert_equal 1, @mock4.test_called_at.size
assert_equal 1, @mock5.test_called_at.size
end
-
+
def test_below
@@manager.delegate_event('test', :below => 3)