Skip to content
Browse files

Imported matzbot with a git plugin

  • Loading branch information...
0 parents commit 70844cb21a61b21ea871488725a65da613b970cd @evanphx evanphx committed Aug 14, 2007
Showing with 2,155 additions and 0 deletions.
  1. +61 −0 README
  2. +25 −0 chat.txt
  3. +7 −0 cron.rb
  4. +66 −0 launch.rb
  5. +156 −0 lib/client.rb
  6. +140 −0 lib/commands.rb
  7. +76 −0 lib/daemon.rb
  8. +64 −0 lib/matzbot.rb
  9. +57 −0 lib/session.rb
  10. +41 −0 plugins/count.rb
  11. +38 −0 plugins/drink.rb
  12. +34 −0 plugins/eightball.rb
  13. +44 −0 plugins/git.rb
  14. +6 −0 plugins/hax.rb
  15. +115 −0 plugins/language.rb
  16. +6 −0 plugins/lispify.rb
  17. +125 −0 plugins/lj.rb
  18. +23 −0 plugins/matzdev.rb
  19. +22 −0 plugins/moo.rb
  20. +27 −0 plugins/old_quote.rb
  21. +58 −0 plugins/pastie.rb
  22. +24 −0 plugins/people.rb
  23. +12 −0 plugins/roll.rb
  24. +17 −0 plugins/seen.rb
  25. +48 −0 plugins/sing.rb
  26. +43 −0 plugins/svn.rb
  27. +179 −0 plugins/tld.rb
  28. +323 −0 plugins/tld.yml
  29. +53 −0 plugins/trybot.rb
  30. +136 −0 plugins/web.rb
  31. +129 −0 plugins/what.rb
61 README
@@ -0,0 +1,61 @@
+**********************
+ Plugin API Howto
+ (so we don't forget)
+**********************
+
+Typical plugin:
+
+ module MatzBot::Commands
+ needs_gem 'hpricot' => [ :tinyurl, :get_tinyurl ]
+
+ def tinyurl(data)
+ say "Here it is! #{get_tinyurl(data.first)}"
+ rescue
+ say "No, no that's not gonna work."
+ end
+
+ private
+ def get_tinyurl(url)
+ return if url.empty?
+
+ res = Net::HTTP.post_form(URI.parse('http://tinyurl.com/create.php'), { 'url' => url })
+ doc = Hpricot(res.body)
+ ((doc/:blockquote).last/:b).innerHTML
+ end
+ end
+
+- New methods added to MatzBot::Commands show up as commands
+
+- If you want to add methods which aren't bot commands, make them protected or private.
+
+- Methods must accept one argument, an array of words said after the command.
+
+- If you need to require a gem, use needs_gem like above. Pass it the gem name and all
+ the methods that depend on it.
+
+- If you need to keep around data, use the `session' hash.
+ session[:my_plugin] = true
+ session[:my_plugin]
+
+- You can use `reply' like say, except it will prefix your command with the user's name.
+ <defunkt> hey bot
+ <matzbot> defunkt: hey # => using reply 'hey'
+
+- You can use `pm' to send a private message to whoever made a request of you. Use it like `say.'
+
+- The above plugin would be triggered like this:
+ <irc_dude> tinyurl http://myurl.com
+ <matzbot> Here it is! http://tinyurl.com/fz3
+
+- You get these methods in the Commands API:
+ - puts(string to say)
+ - reply(string to say)
+ - pm(sting to pm) [your bot must be ident'd on most networks for this to work]
+ - action(string to emote)
+ - config -- hash of runtime configuration options
+ - socket -- direct access to the socket, if you need it
+
+- Put plugins in ~/.matzbot and name them whatever.rb.
+
+=> Evan Weaver && Chris Wanstrath
+>> %w[ http://blog.evanweaver.com http://errtheblog.com ]
25 chat.txt
@@ -0,0 +1,25 @@
+[08/14/2007 04:33 PM PST] Connecting to irc.us.freenode.net:6667...
+[08/14/2007 04:33 PM PST] Error from server: ERROR :Closing Link: 127.0.0.1 (Connection Timed Out)
+
+[08/14/2007 04:34 PM PST] Connecting to irc.us.freenode.net:6667...
+[08/14/2007 04:34 PM PST] Connecting to irc.us.freenode.net:6667...
+[08/14/2007 04:34 PM PST] <freenode-connect> : VERSION
+[08/14/2007 04:35 PM PST] <evan> : crazybot1234: blah
+[08/14/2007 04:38 PM PST] Connecting to irc.us.freenode.net:6667...
+[08/14/2007 04:38 PM PST] <freenode-connect> : VERSION
+[08/14/2007 04:38 PM PST] <evan> : sweet.
+[08/14/2007 04:38 PM PST] <evan> : crazybot1234: botsnack
+[08/14/2007 04:38 PM PST] <evan> : hm.
+[08/14/2007 04:39 PM PST] <evan> : crazybot1234: defunkt
+[08/14/2007 04:39 PM PST] <evan> : hehe
+[08/14/2007 04:42 PM PST] <Defiler> : Hah
+[08/14/2007 04:42 PM PST] <evan> : i'm getting a git bot working
+[08/14/2007 04:42 PM PST] <evan> : obviously
+[08/14/2007 04:43 PM PST] <evan> : u
+[08/14/2007 04:43 PM PST] <Defiler> : We need it working soon so that sassy commit messages will have an audience
+[08/14/2007 04:44 PM PST] <evan> : t
+[08/14/2007 04:44 PM PST] <evan> : GR
+[08/14/2007 04:44 PM PST] <evan> : u
+[08/14/2007 04:44 PM PST] <evan> : there we go.
+[08/14/2007 04:49 PM PST] <evan> : crazybot1234: hax 3 + 4
+[08/14/2007 04:49 PM PST] <evan> : crazybot1234: hax 3 + 4
7 cron.rb
@@ -0,0 +1,7 @@
+#!/usr/bin/env ruby
+
+matzdir = "~/matzbot/"
+
+if `ps -A | grep \`cat #{matzdir}matzbot.pid\` | wc`.to_i.zero?
+ system("cd #{matzdir}; rm matzbot.pid; ./launch.rb -d -w `cat the_password`")
+end
66 launch.rb
@@ -0,0 +1,66 @@
+#!/usr/bin/env ruby
+
+$:.unshift "lib"
+require 'rubygems'
+require 'choice'
+require_gem 'choice', '>= 0.1.2'
+require 'matzbot'
+
+Choice.options do
+ header ""
+ header "Specific options:"
+
+ options :server => {
+ :short => '-s', :long => '--server SERVER',
+ :desc => ["The irc server to connect to.", "(default irc.freenode.org)"], :default => 'irc.freenode.org'
+ },
+
+ :port => {
+ :short => '-p', :long => '--port PORT', :desc => ["The irc server's port.", "(default 6667)"],
+ :default => 6667, :cast => Integer
+ },
+
+ :nick => {
+ :short => '-n', :long => '--nick NICK', :desc => ["The irc bot's nick.", "(default matz)"],
+ :default => 'matz'
+ },
+
+ :password => {
+ :short => '-w', :long => '--password PASSWORD', :desc => "Your nick's password, if registered."
+ },
+
+ :channel => {
+ :short => '-c', :long => '--channel CHANNEL', :desc => ["The channel to join on connect -- without a hash.", "(default polymorphs)"],
+ :default => 'polymorphs'
+ },
+
+ :user => {
+ :short => '-u', :long => '--user USER', :desc => ["The irc bot's username.", "(default matz)"], :default => 'matz'
+ },
+
+ :name => {
+ :short => '-m', :long => '--name NAME', :desc => ["The irc bot's name.", "(default Matz)"], :default => 'Matz'
+ },
+
+ :daemonize => {
+ :short => '-d', :long => '--daemon',
+ :desc => ["Run as a daemon - pass it nothing, start, stop, or restart.", "If you pass nothing, `start' is assumed."]
+ },
+
+ :only_when_addressed => {
+ :short => '-a', :long => '--addressed',
+ :desc => ["Only speak if being addressed.", "(default false)"]
+ }
+end
+
+MatzBot::Session.load unless Choice.choices[:daemonize] == 'stop'
+if Choice.choices[:daemonize]
+ require 'daemon'
+ MatzBot::Daemonize.daemonize(Choice.choices.merge('daemonize' => Choice.choices[:daemonize] == 'stop' ? 'stop' : 'start'))
+else
+ begin
+ MatzBot::Client.start(Choice.choices)
+ ensure
+ MatzBot::Session.save
+ end
+end
156 lib/client.rb
@@ -0,0 +1,156 @@
+require 'socket'
+
+module MatzBot
+ module Client
+ extend self
+
+ attr_accessor :config, :socket, :last_nick, :authorized
+
+ def start(options)
+ self.config ||= {}
+ self.config.merge! Hash[*options.map { |k,v| [k.intern, v] }.flatten]
+
+ connect!
+ main_loop
+ end
+
+ def connect!
+ log "Connecting to #{config[:server]}:#{config[:port]}..."
+ self.socket = TCPSocket.new(config[:server], config[:port])
+
+ socket.puts "USER #{config[:user]} #{config[:nick]} #{config[:name]} :#{config[:name]} \r\n"
+ socket.puts "NICK #{config[:nick]} \r\n"
+
+ socket.puts "PRIVMSG NickServ :IDENTIFY #{config[:password]}" if config[:password]
+
+ # channel might not have a # in front of it, so add it
+ config[:channel] = config[:channel][/^#/] ? config[:channel] : '#' + config[:channel]
+ join config[:channel]
+ end
+
+ def reconnect!
+ socket.close
+ self.socket = nil
+ start
+ end
+
+ def main_loop
+ while true
+ if IO.select([socket])
+ react_to socket.gets
+ end
+ end
+ #socket.each do |line|
+ # react_to line # its own method so we can change it on the fly, without hurting the loop
+ #end
+ end
+
+ def react_to(line)
+ begin
+ MatzBot.reload!
+ rescue Exception => bang
+ say "Class load error #{bang.class} (#{caller[1]}), #{bang}."
+ end
+
+ self.authorized = false # not authorized
+
+ info = grab_info(line) # grabs the info from an PRIVMSG
+ puts line # puts to the console
+
+ pong(line) if line[0..3] == "PING" # keep-alive
+
+ if info && info.last # only called if grabbing the info was successful
+ log_message info # logs in a friendly format, in chat.txt
+ execute(info.last, info.first) if info
+ elsif has_error?(line)
+ reconnect!
+ end
+ end
+
+ def has_error?(line)
+ log "Error from server: #{line}" and return true if line[/^ERROR/]
+ end
+
+ def execute(cmd, nick)
+ data = cmd.split
+ return false unless data && data.first && authorize?(data)
+
+ self.last_nick = nick
+
+ data.join(' ').split(' then ').each do |command|
+ # callbacks
+ filters(:listen).each do |filter|
+ filter.call(command) if command
+ end if filters(:listen).size.nonzero?
+
+ command = command.split(' ')
+ command.shift if command.first =~ /^#{config[:nick]}/i
+
+ if Commands.methods.include? command.first and !(EmptyModule.methods.include? command.first)
+ Commands.send(command.first, command[1..-1])
+ #else
+ # say "no command #{command}"
+ end
+ end
+ rescue Exception => bang
+ say "Command error #{bang.class}, #{bang}."
+ say " #{bang.backtrace.first}"
+ end
+
+ def say(message)
+ Commands.say message
+ end
+
+ def filters(type)
+ Commands.send(:filters, type)
+ end
+
+ def pong(line)
+ line[0..3] = "PONG"
+ socket.puts "#{line}"
+ puts "#{line}"
+ Commands.poll(line) if Commands.methods.include? 'poll'
+ end
+
+ def grab_info(text)
+ # The following is the format of what the bot recieves:
+ # :kyle!~kyle@X-24735511.lmdaca.adelphia.net PRIVMSG #youngcoders :for the most part
+ # :nick!~ident@host PRIVMSG #channel :message
+ text =~ /^\:(.+)\!\~?(.+)\@(.+) PRIVMSG \#?(\w+) \:(.+)/ ? [$1, $2, $3, $4, $5] : false
+ end
+
+ def authorize?(data)
+ if self.config[:only_when_addressed] and data.first != "#{self.config[:nick]}:"
+ return false
+ end
+
+ command, password = data.first, data[1]
+ if Commands.protected_instance_methods(false).include? command
+ self.authorized = config[:password] && password == config[:password]
+ data.delete_at(1)
+ authorized
+ else true
+ end
+ end
+
+ def join(channel, quit_prev = true)
+ socket.puts "PART #{config[:channel]}" if quit_prev
+ socket.puts "JOIN #{channel} \r\n"
+ config[:channel] = channel
+ end
+
+ def log_message(array)
+ log "<#{array[0]}> : #{array[4]}"
+ end
+
+ def log(string)
+ File.open("chat.txt", "a") do |f|
+ f.puts "[#{Time.new.strftime "%m/%d/%Y %I:%M %p PST"}] #{string} \n"
+ end
+ end
+ end
+end
+
+module EmptyModule
+end
+
140 lib/commands.rb
@@ -0,0 +1,140 @@
+require 'set'
+require 'yaml'
+
+module MatzBot
+ module Commands
+ extend self
+
+ def help(data)
+ config[:hidden_methods] ||= []
+ hidden = config[:hidden_methods].map { |m| m.to_s }
+ if is_admin?
+ commands = protected_instance_methods(false) - hidden
+ command = :pm
+ else
+ commands = public_instance_methods(false) - hidden
+ command = :say
+ end
+
+ send(command, "Commands I know: \0037 #{commands.sort * ', '}")
+ end
+
+ def action(data)
+ data = [data].flatten
+ socket.puts "PRIVMSG #{config[:channel]} :\01ACTION #{data * ' '}\01"
+ sleep 1
+ end
+
+ def say(data, channel = config[:channel])
+ data = data.join(" ") if data.is_a?(Array)
+ data.split("\n").each do |message|
+ message = filters(:say).inject(message) do |string, filter|
+ filter.call(string)
+ end if filters(:say).size.nonzero?
+ while message
+ fragment, message = message[0..450], message[450..-1]
+ socket.puts "PRIVMSG #{channel} :#{fragment}"
+ end
+ sleep 1
+ end
+ nil
+ end
+ alias :puts :say
+ private :puts
+
+ def save!(data)
+ Session.save
+ puts "Saved session data. ;)"
+ end
+
+ protected
+ def quit(data)
+ socket.puts "QUIT :#{data.shift}"
+ exit
+ end
+
+ def update_nick(data)
+ socket.puts "NICK :#{nick = data.shift}"
+ config[:nick] = nick
+ end
+
+ private
+ def session
+ Session.session
+ end
+
+ def reply(string)
+ say "#{Client.last_nick}: #{string}" if Client.last_nick
+ end
+
+ def pm(data, nick = Client.last_nick)
+ say(data, nick)
+ end
+
+ # filter :say => :drunk_say
+ # filter :listen => :google_translate
+ def filter(type_and_methods)
+ type = type_and_methods.keys.first
+ Array(type_and_methods[type]).each do |method|
+ filters(type) <<
+ case method
+ when Symbol, String then proc { |message| send(method, message) }
+ when Proc then method
+ end
+ end
+ end
+
+ def filters(type)
+ $filters ||= {}
+ $filters[type.to_sym] ||= Set.new
+ end
+
+ def reload_filters!
+ $filters.clear if $filters
+ end
+
+ def help_method(options = {})
+ # help_method :svn => [ :add_repo, :clear_repos ]
+ options.each do |method, wraps|
+ define_method(method) do |*data|
+ wraps.map! { |m| m.to_s }
+ command = :pm
+ unless is_admin?
+ wraps -= protected_instance_methods(false)
+ command = :say
+ end
+ send(command, "Commands for `#{method}': #{wraps.sort * ', '}")
+ end
+ config[:hidden_methods] ||= Set.new
+ config[:hidden_methods] += wraps.map { |m| m.to_s }
+ end
+ end
+
+ def socket
+ Client.socket
+ end
+
+ def config
+ Client.config ||= {}
+ end
+
+ def is_admin?
+ Client.authorized
+ end
+
+ def needs_gem(hash)
+ # needs_gem 'hpricot' => [ :method1, :method2 ]
+ config[:failed_methods] ||= Set.new
+ gem = ''
+ hash.keys.each { |gem| require gem }
+ rescue LoadError
+ config[:failed_methods] += Array(hash[gem])
+ end
+
+ def method_added(method)
+ config[:failed_methods] ||= Set.new
+ remove_method method if config[:failed_methods].include?(method)
+ end
+ end
+end
+
76 lib/daemon.rb
@@ -0,0 +1,76 @@
+# http://www.bigbold.com/snippets/posts/show/2265
+# (but hacked)
+require 'fileutils'
+
+module MatzBot
+ module Daemonize
+ extend self
+
+ WorkingDirectory = File.expand_path(FileUtils.pwd)
+
+ def pid_fn
+ File.join(WorkingDirectory, "matzbot.pid")
+ end
+
+ def daemonize(config)
+ case config[:daemonize]
+ when 'start'
+ start(config)
+ when 'stop'
+ stop
+ when 'restart'
+ stop
+ start(config)
+ else
+ puts "Invalid command. Please specify start, stop or restart."
+ exit
+ end
+ end
+
+ def start(config)
+ fork do
+ Process.setsid
+ exit if fork
+ store(Process.pid)
+ Dir.chdir WorkingDirectory
+ File.umask 0000
+ STDIN.reopen "/dev/null"
+ STDOUT.reopen "/dev/null", "a"
+ STDERR.reopen STDOUT
+ trap("TERM") do
+ MatzBot::Session.save
+ exit
+ end
+ begin
+ Client.start(config)
+ ensure
+ stop
+ end
+ end
+ end
+
+ def stop
+ unless File.file? pid_fn
+ puts "Pid file not found. Is the daemon started?"
+ exit
+ end
+ pid = recall
+ FileUtils.rm pid_fn
+ puts "Process killed."
+ Process.kill("TERM", pid) rescue nil
+ end
+
+ def store(pid)
+ if File.file? pid_fn
+ puts "** Already started with PID #{File.read(pid_fn)}"
+ exit!
+ else
+ File.open(pid_fn, 'w') { |f| f << pid }
+ end
+ end
+
+ def recall
+ IO.read(pid_fn).to_i rescue nil
+ end
+ end
+end
64 lib/matzbot.rb
@@ -0,0 +1,64 @@
+$:.unshift File.dirname(__FILE__)
+require 'session'
+require 'client'
+require 'commands'
+
+module MatzBot
+ extend self
+
+ @loaded_plugins ||= {}
+
+ def reload!
+ load File.join(File.expand_path(File.dirname(__FILE__)), 'session.rb')
+ load File.join(File.expand_path(File.dirname(__FILE__)), 'client.rb')
+ reload_plugins!
+ end
+
+ def reload_plugins!
+ commands_file = File.join(File.expand_path(File.dirname(__FILE__)), 'commands.rb')
+ if [commands_file, plugin_files].flatten.any? { |f| plugin_changed? f }
+ Commands.send(:reload_filters!)
+ MatzBot.send(:remove_const, :Commands)
+ load commands_file
+ load_plugins
+ end
+ end
+
+ def load_plugins
+ plugin_files.each do |file|
+ puts "Loading #{file}..."
+ begin
+ touch_plugin(file)
+ load file
+ rescue Object => e
+ puts "Unable to load #{file}, disabled."
+ p e
+ end
+ end
+ end
+
+ def plugin_files
+ [dot_matz_bot, 'plugins'].map do |directory|
+ next unless File.exists? directory
+ Dir[File.join(directory, '*.rb')]
+ end.flatten.compact
+ end
+
+ def dot_matz_bot
+ @dot_matz_bot ||=
+ if PLATFORM =~ /win32/
+ dot_matz_bot = ENV['HOMEDRIVE'] + ENV['HOMEPATH']
+ dot_matz_bot = File.join(home, 'MatzBot')
+ else
+ dot_matz_bot = File.join(File.expand_path("~/matzbot/"), "configuration")
+ end
+ end
+
+ def plugin_changed?(file)
+ touch_plugin(file) if !@loaded_plugins[file] || @loaded_plugins[file] < File.mtime(file)
+ end
+
+ def touch_plugin(file)
+ @loaded_plugins[file] = File.mtime(file)
+ end
+end
57 lib/session.rb
@@ -0,0 +1,57 @@
+require 'fileutils'
+
+module MatzBot
+ module Session
+ extend self
+
+ def session_file
+ File.join(home_dir, 'session.yml')
+ end
+
+ def home_dir
+ MatzBot.dot_matz_bot
+ end
+
+ def session
+ $session ||= {}
+ end
+
+ def save
+ FileUtils.mkdir(home_dir) unless File.exists? home_dir
+
+ File.open(session_file, 'w') do |f|
+ session_marshalled = session.dup
+ marshalled = {}
+
+ session_marshalled.each do |key, value|
+ callback(:session_before_save, key)
+ begin
+ marshalled[key] = Marshal.dump(value)
+ rescue
+ # don't dump things we cant marshal
+ end
+ end
+ # save
+ f.puts Marshal.dump(marshalled)
+ end
+ rescue
+ puts "Problem saving session file!"
+ end
+
+ def load
+ if File.exist? session_file
+ Marshal.load(open(session_file).read).each do |key, value|
+ session[key] = Marshal.load(value)
+ puts "Loaded session key: #{key}."
+ callback(:session_after_load, key)
+ end
+ end
+ end
+
+ def callback(callback, key)
+ if MatzBot::Commands.methods(false).include?(method = "#{callback}_#{key}")
+ MatzBot::Commands.send(method)
+ end
+ end
+ end
+end
41 plugins/count.rb
@@ -0,0 +1,41 @@
+module MatzBot::Commands
+ needs_gem 'linguistics' => :count
+
+ def count(data)
+ Linguistics.use(:en)
+
+ if data.first == 'slow'
+ data.shift
+ slow = true
+ else
+ slow = false
+ end
+
+ case data.shift
+ when 'to'
+ bot = 0
+ top = data.shift.to_i
+ when 'from'
+ bot = data.shift.to_i
+ top = data[1].to_i
+ end
+
+ raise if (top - bot).abs > 20
+
+ if top < bot
+ range = (top..bot).to_a.reverse
+ else
+ range = (bot..top).to_a
+ end
+
+ words = range.map { |i| i.en.numwords }
+
+ if slow
+ words.each { |word| say word }
+ else
+ say words
+ end
+ rescue
+ say "I don't think so."
+ end
+end
38 plugins/drink.rb
@@ -0,0 +1,38 @@
+module MatzBot::Commands
+ filter :say => :drunk_talk
+
+ def drink(data)
+ drinks = ["has a beer", "thanks!", "has another beer", "thanks man",
+ "has a margarita", "not enough lime :(", "has a beer", "dude this rocks",
+ "throws back a 3 wiseman", "woooooooo",
+ "drinks something", "hey i don't think i should drive",
+ "slips", "hey sexy bot", "falls over", "omg help me people"]
+
+ if (session[:drunkenness] ||=0) >= drinks.length
+ say ["blarrrahghh", "blawauuuagh", "bruagh"][rand(3)]
+ else
+ action drinks[session[:drunkenness]]
+ say drinks[session[:drunkenness] + 1]
+ session[:drunkenness] += 2
+ end
+ end
+
+ def get_sober(data)
+ session[:drunkenness] = 0
+ say "ohhhh, my head..."
+ action "takes some advil"
+ end
+
+ def drunkness(data)
+ say session[:drunkenness].to_s
+ end
+
+private
+ def drunk_talk(message)
+ session[:drunkenness] ||= 0
+ return message unless session[:drunkenness].nonzero?
+ message.split("").map do |c|
+ rand(60) < session[:drunkenness] ? ['l','r','c','z','b','m','s'][rand(7)] : c
+ end.join
+ end
+end
34 plugins/eightball.rb
@@ -0,0 +1,34 @@
+module MatzBot::Commands
+ filter :listen => :eightball
+
+ private
+
+ def eightball(message)
+ answers = [
+ "Yeah probably.",
+ "Yes.",
+ "Totally.",
+ "Without a doubt.",
+ "I think so.",
+ "Seems that way.",
+ "You can take it to the #{config[:nick]}-bank.",
+ "Yep.",
+ "Completely.",
+ "It is decidedly so.",
+ "Not really sure.",
+ "It's a secret.",
+ "I can't say or I will be killed.",
+ "Ceiling cat says no.",
+ "No clue.",
+ "Probably not.",
+ "I doubt it.",
+ "Definitely not.",
+ "No, sorry.",
+ "Of course not."
+ ]
+
+ if message.strip[-1..-1] == '?' and (rand(10).zero? or message =~ /^#{config[:nick]}/i)
+ say answers[message.hash % answers.size].downcase[0..-2]
+ end
+ end
+end
44 plugins/git.rb
@@ -0,0 +1,44 @@
+module MatzBot::Commands
+
+ require 'open-uri'
+ require 'rexml/document'
+
+ GIT_URL = 'http://git.rubini.us/?p=code;a=atom'
+ GIT_MAX = 3
+
+ def update_git
+ STDOUT.puts "updating git..."
+ last_hash = session[:git_last_hash]
+ data = open(GIT_URL).read
+
+ doc = REXML::Document.new(data)
+
+ i = 0
+
+ REXML::XPath.each(doc, "//entry") do |entry|
+ break if i == GIT_MAX
+ i += 1
+
+ title = REXML::XPath.first(entry, "./title")
+ link = REXML::XPath.first(entry, "./link")
+ name = REXML::XPath.first(entry, "./author/name")
+ hash = link.attributes['href'].split("=").last
+
+ last_hash = hash if i == 1
+
+ break if hash == last_hash
+
+ count = 0
+ REXML::XPath.each(entry, "./content/div/ul/li") { |e| count += 1 }
+ say "#{hash[0..7]} by #{name.text}, #{count} files changed"
+ say " #{title.text}"
+ end
+
+ STDOUT.puts "Last hash #{last_hash}"
+ session[:git_last_hash] = last_hash
+ end
+
+ Signal.trap("USR2") do
+ update_git
+ end
+end
6 plugins/hax.rb
@@ -0,0 +1,6 @@
+module MatzBot::Commands
+ protected
+ def hax(data)
+ pm eval(data.join(" ")).inspect
+ end
+end
115 plugins/language.rb
@@ -0,0 +1,115 @@
+require 'open-uri'
+require 'uri'
+require 'net/http'
+
+module MatzBot::Commands
+ filter :listen => :languages
+ filter :say => :language_talk
+
+ def speak(data)
+ session[:language] = data.first.downcase
+ action "becomes a #{session[:language].capitalize} bot."
+ end
+
+ def languages(message, is_say_filter = false)
+ url_google = 'http://www.google.com/translate_t'
+ url_dialect = 'http://www.rinkworks.com/dialect/'
+
+ if message.is_a? Array
+ langs = []
+ open(url_google).read.split("</option><option value=\"").each do |lang|
+ if lang =~ /English to (.*)/
+ langs.push $1.gsub(/\&.*?;/, ' ').gsub(/BETA/, '').strip
+ end
+ end
+ open(url_dialect).read[/<select name="dialect">\n(.*?)<\/select>/m, 1].split("\n").each do |lang|
+ if lang =~ />\s(.*)$/
+ langs.push $1
+ end
+ end
+ langs.push "Snoop"
+
+ say "Available languages: #{langs.uniq.join(", ")}"
+ else
+ lang_pairs = []
+ begin while message.strip =~ /(.*)\s(in|to|from)\s(\w{3,})$/
+ message = $1
+ direction = $2
+ lang = $3
+ if lang =~ /(.*)lish$/
+ real_lang = $1
+ lang_pairs.unshift ['from', real_lang]
+ lang_pairs.unshift ['in', real_lang]
+ else
+ lang_pairs.unshift [direction, lang]
+ end
+ end
+
+# say "#{lang_pairs.length} pairs"
+ translated = false
+
+ lang_pairs.each do |direction, lang|
+# say "#{message} :: #{direction} :: #{lang}"
+
+ # get the google language we're interested in
+ if direction =~ /in|to/
+ phrase = "English to #{lang}"
+ elsif direction =~ /from/
+ phrase = "#{lang} to English"
+ end
+ lang_code = open(url_google).read.split("</option><option value=\"").grep(/#{phrase}/i)
+
+ # translate
+ if !lang_code.empty?
+ message = open(URI.escape(url_google + "?text=#{message}&langpair=#{lang_code.first[0..4]}")).read[/result_box.*?\>(.*?)<\/div/, 1].gsub(/\&\#\d\d;/, '')
+ translated = true
+
+ elsif direction =~ /in|to/
+ # look for dialectizer languages
+ lang_code = open(url_dialect).read[/<select name="dialect">\n(.*?)<\/select>/m, 1].split("\n").grep(/#{lang}/i)
+
+ if !lang_code.empty?
+ url_dialect_post = 'http://www.rinkworks.com/dialect/dialectt.cgi'
+ response, content = Net::HTTP.post_form(URI.parse(url_dialect_post),
+ {'dialect' => lang_code.first[/\"(.*?)\"/, 1], 'text' => message})
+ # this markup is fucked
+ if parsed = content[/Your Text, Dialectized.*?<p>(.*?)<\/td>/m, 1]
+ message = parsed
+ translated = true
+ end
+ elsif lang =~ /snoop/i
+ url_snoop = 'http://www.gizoogle.com/index.php?translate=true&transtext='
+ message = open(url_snoop + URI.escape(message)).read[/.*?Sponsored Links.*?width=\"500\".*?br>(.*?)\s+<\/TD>/m, 1]
+ translated = true
+ end
+ end
+ end
+
+ {'lt' => '<', 'gt' => '>', 'quot' => "'"}.each do |k, v|
+ message.gsub!("&#{k};", v)
+ end
+ message.squeeze!(" ")
+
+ unless lang_pairs.empty? or !translated or is_say_filter
+ say message
+
+ if lang_pairs.last[1] == "spanish" and rand(5) == 0
+ action "puts on a sombrero"
+ say "cha cha cha"
+ end
+ end
+ end
+
+ message if is_say_filter
+
+ end
+ end
+
+ private
+ def language_talk(message)
+ session[:language] ||= "english"
+ return message unless session[:language] != "english"
+ languages(message + " in #{session[:language]}", true)
+ end
+
+end
6 plugins/lispify.rb
@@ -0,0 +1,6 @@
+module MatzBot::Commands
+ def lispify(data)
+# puts data.join(" ").gsub(/(..)/) { |s| s + (rand(2).zero? ? ") " : " (") }
+ puts data.join(" ").gsub(/s+/, "th")
+ end
+end
125 plugins/lj.rb
@@ -0,0 +1,125 @@
+require 'livejournal/login'
+require 'livejournal/entry'
+require 'hpricot'
+require 'open-uri'
+
+module MatzBot::Commands
+ help_method :lj => [:blog, :quote]
+
+ ACTIONS = %w(writes thinks types)
+
+ MOODS = %w(distressed touched pessimistic hungry cheerful high amused disappointed cold sore grumpy mellow irate pleased tired ditzy energetic exanimate full crazy indifferent thirsty apathetic peaceful happy sympathetic nostalgic contemplative pensive loved listless rejuvenated flirty crushed angry horny bitchy sick exhausted irritated refreshed sleepy pissed off numb cynical thankful surprised embarrassed moody weird good frustrated cranky ecstatic morose naughty hopeful bored lazy jubilant complacent crappy satisfied envious dirty chipper giggly uncomfortable okay blah discontent blank recumbent jealous rejected melancholy content nauseated enraged excited hyper curious silly infuriated restless optimistic calm aggravated impressed thoughtful rushed sad intimidated stressed giddy nervous drained lethargic hot bouncy devious relaxed shocked relieved lonely scared quixotic confused worried mischievous annoyed groggy depressed guilty gloomy anxious drunk grateful)
+
+ TALKS = %w(crap stuff things words chortles hawt lines good sweet only forever endlessly crazy)
+
+ def blog(data)
+ subject = data.join(" ")
+ subject = MOODS[rand(MOODS.length)] if data.empty?
+
+ count = 0
+ begin
+ body = open('http://lcamtuf.coredump.cx/b3/blog-re.shtml?said=' + CGI.escape(subject)).read[/<hr.*?>(.*)<hr/im, 1]
+ body = body.gsub('<P>', "\n").gsub(/([^\n])\n([^\n])/, '\1 \2').gsub(/\n{1,3}/, " lnbrk ").downcase.split(" ")
+ body[(i=rand(30))..(i+rand(8))] = [subject]
+ body = body.join(" ")
+
+ user = LiveJournal::User.new('matzbot', open("lj_password").read.chomp)
+ (login = LiveJournal::Request::Login.new(user)).run
+ entry = LiveJournal::Entry.new
+
+ entry.subject = subject
+ entry.subject = body[/lnbrk.*?\s.*?\s.*?\s(.*?)[^\w\s\']/, 1] if data.empty?
+
+ entry.mood = languages(MOODS[rand(MOODS.length)] + " in koreanlish", true).downcase
+ entry.moodid = rand(130)
+
+ # and now a hack of great horribleness
+ body = languages(body + (rand(3).zero? ? " in koreanlish" : ""), true)
+ body = body.sub(/(.*?lnbrk.*?lnbrk.*?lnbrk)/, '\1' + image_for(subject) + 'lnbrk').gsub(/lnbrk/i, "\n\n").gsub(/[\[\]]/, "")
+ entry.event = body
+
+ entry.time = LiveJournal::coerce_gmt Time.now
+ LiveJournal::Request::PostEvent.new(user, entry).run
+ action "writes about #{subject} in his lj (http://matzbot.livejournal.com)"
+ rescue Object => boom
+ count += 1
+ retry if count < 8
+ puts "dont wanna post right now sry"
+# puts boom.inspect
+ end
+
+ end
+
+ def quote data
+
+ author = data.last
+ talk = TALKS[rand(TALKS.size)]
+ pronoun = "#{rand(5).zero? ? "S" : ""}HE"
+ verb = "talks"
+
+ if author =~ /matz/i
+ author = "I"
+ pronoun = "I"
+ verb.chop!
+ end
+
+ message = data[0..-2].join(" ")
+ return if !message or message.empty?
+
+ user = LiveJournal::User.new('matzbot', open("lj_password").read.chomp)
+ (login = LiveJournal::Request::Login.new(user)).run
+ entry = LiveJournal::Entry.new
+
+ entry.subject = "#{author} #{verb} #{talk}".upcase
+ entry.event = "<h2>#{pronoun} SAYZ:<BR><BR>&nbsp;&nbsp;&nbsp;#{message.upcase}</h2>"
+
+ entry.time = LiveJournal::coerce_gmt Time.now
+ LiveJournal::Request::PostEvent.new(user, entry).run
+
+ action "quoteddd it k"
+ end
+
+
+ filter :listen => :blogurl_watcher
+
+ private
+
+ def blogurl_watcher(message)
+ return unless message =~ /(^|\s)(http.*?)(\s|$)/
+
+ url = $2
+ url = url[0..-2] if url[url.length - 1] == 1
+
+ user = LiveJournal::User.new('matzbot', open("lj_password").read.chomp)
+ (login = LiveJournal::Request::Login.new(user)).run
+ entry = LiveJournal::Entry.new
+
+ title = (Hpricot(open(url))/:head/:title).innerHTML rescue nil
+ title = url[/.*\/(.*)/, 1] if !title or title.empty?
+
+ if url =~ /\.(png|gif|jpg|jpeg|bmp)$/i
+ entry.subject = "picz lol"
+ else
+ entry.subject = title.downcase
+ end
+
+ entry.event = %Q(<a href="#{url}">#{title}</a>)
+
+ entry.time = LiveJournal::coerce_gmt Time.now
+ LiveJournal::Request::PostEvent.new(user, entry).run
+ end
+
+ def image_for subject
+ begin
+ url = "http://images.google.com/images?&q=#{CGI.escape(subject)}&nojs=1"
+ src = (Hpricot(open(url))/:img)[4].attributes['src'][/(http.*)/, 1]
+ raise unless src
+ "<img width=\"350px\" style=\"padding-left: 35px; padding-right: 35px;\" src=\"#{src}\">"
+ rescue Object => boom
+ subject = subject.split(" ")[0..-2].join(" ")
+ retry unless subject.empty?
+ ""
+ end
+ end
+
+end
23 plugins/matzdev.rb
@@ -0,0 +1,23 @@
+module MatzBot::Commands
+ def version(data = nil)
+ `svn info | grep Revision` =~ /(\d+)/
+ say ["Version #{$1}" + ((`svn status | grep '^M'`.length > 0) ? ", with local changes" : "")]
+ end
+
+ def be_polite(data=nil)
+ config[:only_when_addressed] = true
+ say "Pardon me sir, I'll only speak when spoken to."
+ end
+
+ def be_noisy(data=nil)
+ config[:only_when_addressed] = false
+ say "WOO! Party!"
+ end
+
+protected
+ def update(data)
+ `svn up`
+ say "Updating..."
+ version
+ end
+end
22 plugins/moo.rb
@@ -0,0 +1,22 @@
+module MatzBot::Commands
+
+ COW_OPTIONS = %[borg dead paranoid stoned tired wired young]
+ help_method :cow => [:cowsay, :cowthink]
+
+ def cowsay data
+ cow :say, data
+ end
+ def cowthink data
+ cow :think, data
+ end
+
+ private
+ def cow o, data
+ flag = ("-#{data.shift[0..0]}" if COW_OPTIONS.include? data.first)
+ IO.popen("cow#{o} #{flag}", "r+") do |p|
+ p.puts data.join(" ")
+ p.close_write
+ say p.read
+ end
+ end
+end
27 plugins/old_quote.rb
@@ -0,0 +1,27 @@
+#module MatzBot::Commands
+# needs_gem 'mechanize' => :quote
+#
+# DB_URI = "http://www.eskimo.com/~hottub/software/programming_quotes.html"
+# DB_CACHE = "/tmp/matzbot_serialized_quote_db"
+#
+# def quote(data)
+# unless File.exist? DB_CACHE
+# page = WWW::Mechanize.new.get(DB_URI).body
+#
+# @db = page.split(/\<img.*?\>/).collect { |s|
+# s.gsub(/\n/, " ").sub("<blockquote>", " - ").gsub(/\<.*?\>/, "").gsub(/\\222/, "'").strip.gsub(" ", " ")
+# }.select { |s|
+# s !~ /^[\s\n]*$/ and s.length > 20
+# }.push("Der der I'm defunkt der der der. - defunkt")
+#
+# File.open(DB_CACHE, 'w') do |f|
+# f.puts YAML.dump(@db)
+# end
+# end
+#
+# @db ||= YAML.load_file(DB_CACHE)
+# puts @db[rand(@db.length)]
+#
+# end
+#
+#end
58 plugins/pastie.rb
@@ -0,0 +1,58 @@
+require 'net/http'
+require 'hpricot'
+require 'open-uri'
+#require 'mechanize'
+
+module MatzBot::Commands
+
+# def pastie(data)
+# pastie = "pastie.caboo.se"
+# url = "http://#{pastie}/paste"
+# search_url = "http://#{pastie}/search?q="
+# agent = WWW::Mechanize.new
+# form = agent.get(url).forms[1]
+# form['paste[body]'] = "#{config[:nick]} says hello"
+# posted = agent.submit(form)
+# edit_url = posted.uri.to_s + "/edit"
+# session_id = agent.cookie_jar.jar[pastie]['_session_id']
+#
+# pm "Here's your pastie url. When you complete your paste, the link will be announced in #{config[:channel]}."
+# # depends on web.rb plugin
+# pm get_tinyurl(search_url + uri_escape(compose_js(session_id, edit_url)))
+#
+# pastie_listen(posted.uri.to_s, pastie)
+# end
+
+ def run_pastie(data)
+ url = data.first
+ url = "http://p.caboo.se/#{url}" if url.to_i.to_s == url
+ puts "Evalin' #{url.split('/').last}..."
+ url = (Hpricot(open(url).read)/:a/".utility").select{|e| e.innerHTML =~ /view/i}.first.attributes['href']
+ open(url).read.split("\n").each { |line| send(:>>, line.split(' ')) }
+ rescue
+ puts "Pastie problem, ho."
+ end
+
+#private
+# def pastie_listen(uri, pastie)
+# r = Net::HTTP.new(pastie)
+# unpasted = r.get(uri).body
+# 21.times do
+# sleep(5)
+# if unpasted != (pasted = r.get(uri).body)
+# puts "#{MatzBot::Client.last_nick} has pasted a paste at: #{uri}" and return
+# end
+# end
+# pm "You didn't paste in time. You had 2 minutes to paste your paste; now I must say goodbye."
+# end
+#
+# def uri_escape(string)
+# string.gsub(/([^ a-zA-Z0-9_.-]+)/n) do
+# '%' + $1.unpack('H2' * $1.size).join('%').upcase
+# end.tr(' ', '+')
+# end
+#
+# def compose_js(session_id = nil, edit_url = nil)
+# %[<script>document.cookie='#{session_id.to_s}; expires=; path=/';window.location='#{edit_url}';</script>]
+# end
+end
24 plugins/people.rb
@@ -0,0 +1,24 @@
+module MatzBot::Commands
+ help_method :people => [ :defunkt, :hank ]
+
+ def defunkt(data)
+ puts "i'm defunkt der der guru der der der"
+ end
+
+ def hank(data)
+ 6.times do
+ rand(2).times { puts "HANK!! THE SCREEN IS SCROLLING!" }
+ sleep(rand(1))
+ rand(4).times { puts "Hank! " * (rand(3) + 1) }
+ sleep(rand(1))
+ end
+ end
+
+ def hammer(data)
+ 10.times do
+ sleep(1)
+ say "bang!"
+ end
+ end
+
+end
12 plugins/roll.rb
@@ -0,0 +1,12 @@
+module MatzBot::Commands
+ def roll(data)
+ if data.first =~ /(\d+)d(\d*)/
+ sides = $2.empty? ? 6 : $2.to_i
+ return puts("No.") if sides > 100 || $1.to_i > 100
+ roll = Range.new(0, $1.to_i).inject { |m, i| m += (rand(sides) + 1) }
+ action "rolls #{$1} #{sides} sided dice and gets a #{roll}."
+ else
+ action "rolls a six sided die and gets a #{rand(6)+1}."
+ end
+ end
+end
17 plugins/seen.rb
@@ -0,0 +1,17 @@
+module MatzBot::Commands
+ def seen(data)
+ nick = data.join(" ")
+ history = `tail -n 4000 chat.txt`.split("\n").reverse
+ if nick == config[:nick]
+ say "I'm right here, jerkface."
+ else
+ result = history.find {|line| line =~ /\<#{nick}\>/ }
+ if result
+ puts "I last saw #{nick} saying:"
+ puts result
+ else
+ puts "I haven't seen #{nick} recently."
+ end
+ end
+ end
+end
48 plugins/sing.rb
@@ -0,0 +1,48 @@
+require 'net/http'
+require 'open-uri'
+require 'hpricot'
+
+module MatzBot::Commands
+
+ def sing(data)
+ query = data.join(" ")
+ # get most popular songs
+ if query.length == 0
+ popular = Hpricot(open('http://chart.lyricsfreak.com/lyrics.html').read)
+ sing_song((links = (popular/"#lyric"/:a))[rand(links.length)].attributes['href'])
+ else
+ begin
+ google = Net::HTTP.new('www.google.com')
+ url = google.get("/search?q=#{query.gsub(" ", "+")}+site%3Awww.lyricsfreak.com&btnI")['Location']
+ search = Hpricot(open(url).read)
+ unless (search/"#lyric").inner_html == ""
+ url = (links = (search/"#lyric"/:a))[rand(links.length)].attributes['href']
+ end
+ sing_song(url)
+ rescue Exception
+ say("couldnt find it " + ":( " * rand(3))
+ end
+ end
+ end
+
+ private
+ def sing_song(song_url)
+ song = (Hpricot(open(song_url).read))
+ lines = (song/"#content").innerHTML.gsub("\n", '').split("<br />").map{|s| s.strip.squeeze(" ")}.select{|s| s.length > 0}[0..rand(3)+5]
+ raise RuntimeError if lines.join(" ") =~ /wikipedia/i
+ title = (song/:title).first.innerHTML.gsub(/lyrics/i, '').sub('|', '-').strip.squeeze(" ") rescue nil
+ if title
+ action ["clears his throat", "sings", "coughs"][rand(3)] and sleep(1) if rand(4) == 0
+ lines.each do |line|
+ next if !line or line.length == 0 or line =~ /\&|\[|\]|\)|\(|chorus|verse/i
+ [',', '.', '!', '?', '-', ';', ':'].map{|p| line.chomp! p}
+ say "#{line}...".downcase.gsub(/\&.{1,4}\;/, '')
+ sleep rand(2)
+ end
+ sleep(2)
+ say " - #{title}".downcase.gsub(/\&.{1,4}\;/, '')
+ else
+ say "no such song, sillyhead"
+ end
+ end
+end
43 plugins/svn.rb
@@ -0,0 +1,43 @@
+module MatzBot::Commands
+ needs_gem 'hpricot' => ['poll']
+
+ session[:repositories] ||= {}
+ session[:last_poll] ||= Time.now
+
+ filter :listen => :poll
+
+ def add_repos(data)
+ session[:repositories][data.first] = 0
+ say "Okay I'll keep an eye on #{data.first}."
+ end
+
+ def show_repos(data)
+ say session[:repositories].inspect
+ end
+
+ def clear_trac_tickets(data)
+ session[:trac_tickets].clear
+ stop_poll
+ say "Clear.d."
+ end
+
+ def reset_repos(*data)
+ session[:repositories].each { |k, v| session[:repositories][k] = 0 }
+ end
+
+ def remove_repos(data)
+ return say("Gone.") if session[:repositories].delete(data.first)
+ say "Which one?"
+ end
+
+ def poll(message)
+ return unless (Time.now - session[:last_poll] > 15 rescue true)
+ session[:last_poll] = Time.now
+ session[:repositories].each do |repo, last|
+ (Hpricot(`svn log #{repo} -rHEAD:#{last} --limit 10 --xml`)/:logentry).reverse[1..-1].each do |ci|
+ session[:repositories][repo] = rev = ci.attributes['revision'].to_i
+ say "Commit #{rev} to #{repo.split("/").last} by #{(ci/:author).text}: #{(ci/:msg).text}"
+ end rescue nil
+ end
+ end
+end
179 plugins/tld.rb
@@ -0,0 +1,179 @@
+require 'yaml'
+require 'pathname'
+
+module MatzBot::Commands
+ TLDS = YAML.load_file(Pathname.new(__FILE__).dirname.to_s + '/tld.yml')
+ STANDARD_TLDS = %w[com org net]
+ WHOIS_SPECIAL_NXDOMAINS = {'st' => "No entries found for",
+ 'sh' => "To purchase please go to"}
+
+ LETTERS = (?a..?z).to_a.map{|c| c.chr}
+ NUMBERS = (0..9).to_a.map{|i| i.to_s}
+
+ def dhax data
+ if data.empty?
+ say ">> dhax prefix"
+ say "thinks up real web 2.0-style domains for your prefix"
+ else
+ data.each do |word|
+ results = []
+ [word, word[0..-2], word[0..-3]].each do |domain|
+ next if word.empty?
+ realwords = (Hpricot(open("http://www.morewords.com/?#{domain}*").read)/:li).map{|n| (n/:a).innerHTML}
+ tlds = TLDS.keys.map{|tld| "#{domain}.#{tld}"}
+ results += tlds.select{|tld| realwords.include? tld.downcase.gsub(".", '')}
+ end
+ if results.empty?
+ say "i couldnt come up with anything sry"
+ else
+ results.map! do |domain|
+ domain.gsub!(".", '')
+ if _check(domain) =~ /available/
+ "*#{domain}*"
+ else
+ domain
+ end
+ end
+ say "sexy domain hax for #{word}: #{results.join(", ")}"
+ end
+ end
+ end
+ end
+
+ def check data
+ data.each do |domain|
+ domain = domain.gsub(/[^\da-zA-Z\-]/, '').downcase
+
+ if STANDARD_TLDS.include? domain[-3..-1]
+ domain = domain[0..-4]
+ word = ""
+ else
+ puts(result = _check(domain))
+ word = ", though"
+ word = ", too" if result =~ /available/
+ end
+
+ STANDARD_TLDS.each do |postfix|
+ result = _check(domain + postfix)
+ if result =~ /available/ or word.empty?
+ puts result.chomp("!") + word
+ word = ", too" unless word.empty?
+ end
+ end
+
+ end
+ end
+
+ if false #config[:nick] != "matz"
+ def search_namespace data
+ power = data[0].to_i
+ choices = []
+ case data[1]
+ when "mixed"
+ choices += LETTERS + NUMBERS + ["-"]
+ when "letters"
+ choices += LETTERS
+ when "numbers"
+ choices += NUMBERS
+ else
+ puts "please say the combination length, then 'letters', 'numbers', or 'mixed', and the optionally the tlds to search" and return
+ end
+
+ postfixes = data[2..-1]
+ postfixes = STANDARD_TLDS if postfixes.empty?
+
+ total, count = 0, 0
+ [false, true].each do |execute|
+ choices.cartesian_power(power) do |c|
+ postfixes.each do |postfix|
+ if execute
+ count += 1
+ result = _check(c.join("") + postfix)
+ (puts result and open("tld.search.#{data[0]}.#{data[1]}", 'a') {|f| f.puts result}) if result =~ /available/
+ puts "ridin dirtay yeah! #{count} checks so far" if (count % 10000).zero?
+ else
+ total += 1
+ end
+ end
+ end
+ puts "im in ur #{postfixes.join(", ")} namespacez"
+ puts "findin ur #{total} #{power}-length combinations from #{choices.size} elementz" unless execute
+ end
+ puts "done finding ur namez"
+ end
+ end
+
+ def tld data
+ data.each do |tld|
+ tld.gsub!('.', '')
+ tld = interpolate(tld) unless TLDS[tld.to_sym]
+ puts ".#{tld}: #{TLDS[tld.to_sym] or TLDS[(TLDS.keys.map{|s|s.to_s}.grep(/#{tld.to_sym}/).first.to_sym rescue nil)] or 'invalid'} (http://en.wikipedia.org/wiki/.#{tld.split(".").last})"
+ end
+ end
+
+ private
+
+ def _check domain
+ if domain =~ /(#{TLDS.keys.join("|").gsub(".", "")})$/
+
+ tld = TLDS[$1.to_sym] ? $1 : interpolate($1)
+ domain = "#{domain[0..-($1.length + 1)]}.#{tld}"
+ #Debugger.start; debugger
+
+ dig = if domain =~ /\.(#{STANDARD_TLDS.join("|")})$/ and domain.length < 7 or domain =~ /^\-|\-\./ or domain.length < 5
+ "SERVFAIL"
+ else
+ whois_norecord = WHOIS_SPECIAL_NXDOMAINS[tld]
+ if whois_norecord
+ if `whois #{domain}` =~ /#{whois_norecord}/
+ "NXDOMAIN"
+ else
+ "NOERROR"
+ end
+ else
+ if ENV['HOME'] =~ /^\/Users/
+ # os x
+ `ssh allison dig @63.78.188.33 ns #{domain}`
+ else
+ # linux
+ `dig ns #{domain}`
+ end
+ end
+ end
+
+ case dig
+ when /NXDOMAIN/
+ "#{domain} is available!"
+ when /NOERROR/
+ "#{domain} has already been registered..."
+ when /SERVFAIL/
+ "#{domain} is invalid or too short"
+ else
+ "#{domain} threw an error"
+ end
+ elsif domain =~ /(#{TLDS.keys.map{|k| k.to_s.split(".").last}.join("|")})$/
+ "#{domain} is disabled (.#{$1} only allows tertiary registrations)"
+ else
+ "#{interpolate(domain)} is invalid"
+ end
+ end
+
+ def interpolate s
+ (s[0..-3] + "." + s[-2..-1]).gsub(/^\./, '') rescue s
+ end
+end
+
+
+class Array
+ def cartesian_power(n)
+ iterations = size ** n
+ iterations.times do |i|
+ array = Array.new(n) do |j|
+ k = i
+ (n-j-1).times { k /= size }
+ slice(k%size)
+ end
+ yield array
+ end
+ end
+end
323 plugins/tld.yml
@@ -0,0 +1,323 @@
+---
+:tl: East Timor Old code .tp is still in use
+:mn: Mongolia
+:coop: cooperatives The .coop TLD is limited to cooperatives as defined by the Rochdale Principles
+:eun.eg: Egyptian Universities Network
+:edu.eg: Egypt Educational sites
+:sci.eg: Egypt Scientific Sites
+:gov.eg: Egypt Governmental Sites
+:com.eg: Egypt Commercial Sites
+:org.eg: Egyptian Organizations
+:net.eg: Egypt Networking
+:mil.eg: Egypt military sites
+:hu: Hungary
+:pk: Pakistan
+:bd: Bangladesh
+:sz: Swaziland
+:dj: Djibouti
+:ly: Libya
+:aero: air-transport industry Must verify eligibility for registration; only those in various categories of air-travel-related entities may register.
+:ws: Samoa Formerly Western Samoa
+:nz: New Zealand
+:as: American Samoa
+:gw: Guinea-Bissau
+:la: Laos
+:lv: Latvia
+:root: unknown This is in root for some unknown purpose, with only one entry (a TXT record).
+:sn: Senegal
+:sl: Sierra Leone
+:co: Colombia
+:vc: Saint Vincent and the Grenadines
+:gh: Ghana
+:ch: "Switzerland (Confoederatio Helvetica) "
+:nf: Norfolk Island
+:ag: Antigua and Barbuda
+:ca: Canada
+:ki: Kiribati
+:sd: Sudan
+:ug: Uganda
+:pro: professions Currently, .pro is reserved for licensed doctors, attorneys, and certified public accountants only. A professional seeking to register a .pro domain must provide their registrar with the appropriate credentials.
+:cn: Peoples Republic of China Mainland China only
+:cl: Chile
+:fo: Faroe Islands
+:mw: Malawi
+:re: "Reunion AFNIC does not allow .re registrations from organisations or individuals outside Ile de Reunion."
+:bo: Bolivia
+:at: Austria
+:id: Indonesia
+:je: Jersey
+:tn: Tunisia
+:mp: Northern Mariana Islands
+:gov: governmental The .gov TLD is limited to US governmental entities and agencies
+:es: Spain
+:il: Israel
+:pm: Saint-Pierre and Miquelon
+:bf: Burkina Faso
+:td: Chad
+:gs: South Georgia and the South Sandwich Islands
+:dm: Dominica
+:mc: Monaco
+:yt: Mayotte
+:pa: Panama
+:aw: Aruba
+:pw: Palau
+:hk: Hong Kong Special administrative region of the Peoples Republic of China
+:li: Liechtenstein
+:so: Somalia
+:cu: Cuba
+:st: "S\xE3o Tom\xE9 and Pr\xEDncipe"
+:vg: British Virgin Islands
+:gl: Greenland
+:ni: Nicaragua
+:am: Armenia
+:sg: Singapore
+:cf: Central African Republic
+:kn: Saint Kitts and Nevis
+:um: United States Minor Outlying Islands
+:my: Malaysia
+:xxx: PornographyPornographic
+:gb: United Kingdom (Great Britain) Seldom used; the primary ccTLD is :uk
+:jo: Jordan
+:com: commercial This is an open TLD; any person or entity is permitted to register.
+:ru: Russia
+:bs: Bahamas
+:md: Moldova
+:tt: Trinidad and Tobago
+:eu: European Union Restricted to companies and individuals in the European Union
+:mr: Mauritania
+:mil: Military of the United StatesUnited States Military The .mil TLD is limited to use by the U.S. military.
+:pr: Puerto Rico
+:bh: Bahrain
+:in: "India"
+:co.in: "India"
+:firm.in: "India"
+:net.in: "India"
+:org.in: "India"
+:gen.in: "India"
+:ind.in: "India"
+:tg: Togo
+:mh: Marshall Islands
+:dz: Algeria Not available for private use
+:za: South Africa
+:hn: Honduras
+:pf: French Polynesia Clipperton Island
+:az: Azerbaijan
+:su: former Soviet Union Still in use
+:cy: Cyprus
+:lr: Liberia
+:no: Norway Must be registered with a company in Norway to register.
+:vn: Vietnam
+:np: Nepal
+:ao: Angola
+:gq: Equatorial Guinea
+:name: individuals, by name This is an open TLD; any person or entity is permitted to register; however, registrations may be challenged later if they are not by individuals (or the owners of fictional characters) in accordance with the domains charter
+:kw: Kuwait
+:lt: Lithuania
+:si: Slovenia
+:ci: "C\xF4te d'Ivoire"
+:uy: Uruguay
+:ge: Georgia (country)Georgia
+:gp: Guadeloupe
+:na: Namibia
+:ad: Andorra
+:sa: Saudi Arabia
+:bw: Botswana
+:ke: Kenya
+:tw: Taiwan, Province of China Used in the area under the effective control of the Government of the Republic of China, namely Taiwan, Penghu, Kinmen, and Matsu IslandsMatsu
+:mt: Malta
+:museum: museums Must be verified as a legitimate museum.
+:fj: Fiji
+:ir: Iran
+:pt: Portugal Only available for Portuguese registered brands and companies
+:bj: Benin
+:tj: Tajikistan
+:ee: Estonia
+:ml: Mali
+:ai: Anguilla
+:zw: Zimbabwe
+:bb: Barbados
+:ht: Haiti
+:ph: Philippines
+:sy: Syria
+:arpa: Address and Routing Parameter Area This is an internet infrastructure tld.
+:de: Germany
+:lu: Luxembourg
+:wf: Wallis and Futuna
+:nu: Niue Commonly used for Swedish and Dutch websites.
+:com.ar: Argentina
+:edu.ar: Argentina
+:gov.ar: Argentina
+:int.ar: Argentina
+:net.ar: Argentina
+:mil.ar: Argentina
+:org.ar: Argentina
+:gu: Guam
+:kz: Kazakhstan
+:cc: Cocos IslandsCocos (Keeling) Islands
+:sk: Slovakia
+:cm: Cameroon
+:va: Vatican CityVatican City State
+:mm: Myanmar
+:gg: Guernsey
+:jobs: companies The .jobs TLD is designed to be added after the names of established companies with jobs to advertise. At this time, owners of a "company.jobs" domain are not permitted to post jobs of third party employers.
+:ne: Niger
+:af: Afghanistan
+:sc: Seychelles
+:bz: Belize
+:kh: Cambodia
+:ua: Ukraine
+:mv: Maldives
+:org: organization This is an open TLD; any person or entity is permitted to register.
+:fm: Federated States of Micronesia Used for some radio related websites outside Micronesia
+:it: Italy Restricted to companies and individuals in the European Union
+:qa: Qatar
+:bn: BruneiBrunei Darussalam
+:tm: Turkmenistan
+:com.er: Eritrea
+:edu.er: Eritrea
+:gov.er: Eritrea
+:edu: educational The .edu TLD is limited to institutions of learning (mostly U.S.), such as 2 and 4-year colleges and universities.
+:pl: Poland
+:be: Belgium
+:int: international organizations The .int TLD is strictly limited to organizations, offices, and programs which are endorsed by a treaty between two or more nations.
+:ie: Republic of Ireland (Local usage only)
+:tc: Turks and Caicos Islands
+:ma: Morocco (Local contact required)
+:co.ma: Morocco (Local contact required)
+:lc: Saint Lucia
+:dk: Denmark
+:ye: Yemen
+:gy: Guyana
+:com.om: Oman
+:edu.om: Oman
+:gov.om: Oman
+:org.om: Oman
+:med.om: Oman
+:net.om: Oman
+:au: Australia Includes Ashmore and Cartier Islands and Coral Sea Islands
+:sm: San Marino
+:cr: Costa Rica
+:lb: Lebanon
+:cv: Cape Verde
+:ve: Venezuela
+:bt: Bhutan
+:ng: Nigeria
+:gov.al: Albania
+:edu.al: Albania
+:org.al: Albania
+:com.al: Albania
+:net.al: Albania
+:gi: Gibraltar
+:km: Comoros
+:se: Sweden
+:cd: Democratic Republic of the Congo Formerly Zaire
+:uk: United Kingdom
+:ga: Gabon
+:mx: Mexico
+:travel: travel and travel-agency related sites Must be verified as a legitimate travel-related entity.
+:tk: Tokelau Also used as a free domain service to the public
+:ro: Romania
+:br: Brazil
+:jm: Jamaica
+:tp: East Timor ISO code has changed to TL; .tl is now assigned but .tp is still in use
+:mq: Martinique
+:info: information This is an open TLD; any person or entity is permitted to register.
+:com.et: Ethiopia
+:org.et: Ethiopia
+:gov.et: Ethiopia
+:edu.et: Ethiopia
+:net.et: Ethiopia
+:biz.et: Ethiopia
+:to: Tonga
+:im: Isle of Man
+:gm: The Gambia
+:pn: Pitcairn Islands
+:bg: Bulgaria
+:tf: French Southern and Antarctic Lands
+:do: Dominican Republic
+:mg: Madagascar
+:yu: Federal Republic of YugoslaviaYugoslavia Now used for Serbia and Montenegro
+:ax: Aland (Registration only available for residents)
+:hm: Heard Island and McDonald Islands
+:pe: Peru
+:sr: Suriname
+:cx: Christmas Island
+:lk: Sri Lanka
+:vi: U.S. Virgin Islands
+:nl: Netherlands
+:an: Netherlands Antilles (Local usage only)
+:gn: Guinea
+:kr: South Korea
+:sh: Saint Helena
+:cg: Republic of the Congo
+:us: United StatesUnited States of America
+:gd: Grenada
+:mz: Mozambique
+:ac: Ascension Island
+:rw: Rwanda
+:bv: Bouvet Island Not in use (Norwegian dependency; see .no)
+:jp: Japan
+:tv: Tuvalu Also sold as advertising domains
+:ms: Montserrat
+:mobi: mobile devices Must be used for mobile-compatible sites in accordance with standards.
+:fi: Finland
+:iq: Iraq
+:ps: Palestinian territories PA-controlled West Bank and Gaza Strip
+:bi: Burundi
+:ac.th: Thailand
+:co.th: Thailand
+:in.th: Thailand
+:go.th: Thailand
+:mi.th: Thailand
+:or.th: Thailand
+:net.th: Thailand
+:ec: Ecuador
+:mk: Republic of Macedonia
+:zm: Zambia
+:pg: Papua New Guinea
+:ba: Bosnia and Herzegovina
+:gr: Greece
+:hr: Croatia
+:sv: El Salvador
+:ls: Lesotho
+:cz: Czech Republic
+:vu: Vanuatu
+:gt: Guatemala
+:nr: Nauru
+:aq: Antarctica Defined as per the Antarctic Treaty SystemAntarctic Treaty as everything south of latitude 60S
+:sj: Svalbard and Jan Mayen Islands Not in use (Norwegian dependencies; see .no)
+:co.ck: Cook Islands (Companies must use name based on their legal name)
+:org.ck: Cook Islands
+:edu.ck: Cook Islands
+:gov.ck: Cook Islands
+:net.ck: Cook Islands
+:ky: Cayman Islands
+:uz: Uzbekistan
+:io: British Indian Ocean Territory
+:nc: New Caledonia
+:ae: United Arab Emirates
+:fr: France Can only be used by organisations or persons with a precence in France.
+:gf: French Guiana
+:kg: Kyrgyzstan
+:sb: Solomon Islands
+:by: Belarus Limited to Belarus residents; third-level subdomains have specific restrictions
+:co.tz: Tanzania
+:ac.tz: Tanzania
+:ne.tz: Tanzania
+:or.tz: Tanzania
+:go.tz: Tanzania
+:fk: Falkland Islands
+:mu: Mauritius
+:net: network This is an open TLD; any person or entity is permitted to register.
+:tr: Turkey
+:py: Paraguay
+:bm: Bermuda
+:is: Iceland
+:biz: business This is an open TLD; any person or entity is permitted to register; however, registrations may be challenged later if they are not by commercial entities in accordance with the domains charter.
+:cat: Catalan This is a TLD for websites in the Catalan language or related to Catalan culture.
+:com.mo: Macau Special administrative region of the Peoples Republic of China
+:edu.mo: Macau Special administrative region of the Peoples Republic of China
+:net.mo: Macau Special administrative region of the Peoples Republic of China
+:org.mo: Macau Special administrative region of the Peoples Republic of China
+:gov.mo: Macau Special administrative region of the Peoples Republic of China
+
53 plugins/trybot.rb
@@ -0,0 +1,53 @@
+require 'cgi'
+
+module MatzBot::Commands
+ TRY_URL = "http://tryruby.hobix.com/irb?cmd=" unless defined?(TRY_URL)
+
+ if `hostname` =~ /darkstar/
+ require 'timeout'
+ def >>(data)
+ begin
+ Timeout.timeout(12) {
+ say "=> " + instance_eval(data*" ").inspect
+ }
+ rescue TimeoutError
+ say "=> [Timed out]"
+ end
+ end
+
+ def reset_irb
+ exec("kill -9 `cat /home/matz/matzbot.pid`")
+ end
+ else
+
+ def >>(data)
+ session[:tryruby] ||= wget("!INIT!IRB!")
+
+ if cmd = data * ' '
+ resp = wget(cmd, {'Cookie' => '_session_id=' + session[:tryruby]})
+ if resp =~ /^Your session has been closed/
+ session[:tryruby] = wget("!INIT!IRB!")
+ resp = wget(cmd, {'Cookie' => '_session_id=' + session[:tryruby]})
+ end
+ resp = resp.split("\n").select{|s| s !~ /^\s+from .+\:\d+(\:|$)/}.join("\n")
+ puts resp.strip
+ else
+ puts "tryruby module: ?eval <code> => object"
+ end
+ end
+
+ def reset_irb(data)
+ session[:tryruby] = nil
+ puts "irb reset!"
+ end
+
+ private
+ def wget(url, hdrs = {})
+ require 'open-uri'
+ open(TRY_URL + CGI.escape(url), hdrs) do |f|
+ f.read
+ end
+ end
+ end
+
+end
136 plugins/web.rb
@@ -0,0 +1,136 @@
+require 'net/http'
+require 'open-uri'
+require 'rfuzz/session'
+require 'cgi'
+
+module MatzBot::Commands
+ needs_gem 'hpricot' => [ :del, :rubyurl, :get_rubyurl, :burner ]
+
+ def google(data, title = true)
+ return if data.empty?
+ google = Net::HTTP.new('www.google.com')
+ lucky = google.get("/search?q=#{data * '+'}&btnI")['Location']
+ puts $1.gsub(/\n|\r/,' ').strip if open(lucky).read =~ /<title>(.*?)<\/title>/mi if title
+ puts lucky
+ blogurl_watcher lucky
+ rescue
+ puts "Uh, nothing."
+ end
+
+ def wiki(data)
+ google(data.unshift("wikipedia+site:en.wikipedia.org"), false)
+ end
+
+ def alexa(data)
+ return if data.empty?
+ alexa = Net::HTTP.new('www.alexa.com')
+
+ if alexa.get("/data/details/traffic_details?url=#{data}").body =~ /<span class="descBold">(.*?)<\/span>/
+ content = $1.dup
+ rank = content.scan(/>(\d+)</)
+ say "Alexa guesses #{data}'s rank to be #{rank}."
+ else
+ say "No dice, dude."
+ end
+ end
+
+ def technorati(data)
+ return if data.empty?
+ technorati = Net::HTTP.new('www.technorati.com')
+
+ if (body = technorati.get("/search/#{data}").body) =~ /Rank: ([0-9,]+).*?\(([0-9,]+) links from ([0-9,]+) blogs\)/
+ string = "Looks like #{data} has a rank of #$1."
+ string << " (That's #$2 links from #$3 blawgs.)" if $2
+ say string
+ elsif body =~ /([0-9,]+) links to this URL/
+ say "No ranking, but #{data} is linked to #$1 times."
+ else
+ say "No ranking."
+ end
+ end
+
+ def del(data)
+ return if data.empty?
+ data = data.to_s
+ delicious = Net::HTTP.new('del.icio.us')
+
+ if data =~ /^http:/
+ require 'digest/md5'
+ url = Digest::MD5.hexdigest(data)
+ if delicious.get("/url/#{url}").body =~ /saved by ([0-9,]+) people/
+ say "#{data} has been saved by #$1 people"
+ else
+ say "No one's saved that url."
+ end
+ else
+ doc = Hpricot(delicious.get("/#{data}").body)
+ link = (doc/:h4).first/:a
+ say link.first['href']
+ say link.innerHTML
+ end
+ rescue
+ say "I... I don't think so."
+ end
+
+ def worth(data)
+ return if data.empty?
+ url = name = data.first
+ url += '.com' unless url['.']
+ res = Net::HTTP.post_form(URI.parse('http://www.business-opportunities.biz/projects/how-much-is-your-blog-worth/'), { 'url' => url })
+ if res.body =~ /is worth ([0-9,.$]+)/
+ price = $1
+ price = price == '$0.00' ? 'less' : " #{price}"
+ say "#{name} is worth#{price}"
+ end
+ rescue
+ say "No go, broke kid."
+ end
+
+ #filter :listen => :rubyurl_watcher
+
+# def rubyurl_watcher(message)
+# return if message =~ /run_pastie/
+# return unless message =~ /(^|\s)(http.*?)(\s|$)/
+#
+# url = $2
+# url = url[0..-2] if url[url.length - 1] == 1
+#
+# if url.length > "http://rubyurl.com/xxxxx".length
+# response = get_rubyurl url
+# action "shortens that to #{response}" if response
+# end
+# end
+#
+# def rubyurl(data)
+# say "Here it is! #{get_rubyurl(data.first)}"
+# rescue
+# say "Rubyurl barfed on that one."
+# action "gets a bucket and some lysol"
+# end
+
+ def burner(data)
+ return if data.empty?
+ url = "http://api.feedburner.com/awareness/1.0/GetFeedData?uri=#{data.first}"
+ doc = Hpricot.parse open(url)
+ entry = (doc/:entry).first
+ circ = entry.attributes['circulation']
+ hits = entry.attributes['hits']
+ puts "Circulation: #{circ} | Hits in the Last 24 hours: #{hits}"
+ rescue
+ puts "Feed error! Make sure that the url is correct and that the API is turned on for it"
+ end
+
+private
+ def get_rubyurl(url)
+ return if url.empty?
+ include RFuzz
+
+ agent = "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.4) Firefox/1.5.0.4"
+ target = HttpClient.new("rubyurl.com", 80)
+ res = target.get("/rubyurl/remote",
+ :head => {"User-Agent" => agent},
+ :query => {"website_url" => url}) rescue nil
+ res['LOCATION'].sub('/rubyurl/show', '') rescue nil
+ end
+
+end
129 plugins/what.rb
@@ -0,0 +1,129 @@
+require 'open-uri'
+require 'hpricot'
+
+module MatzBot::Commands
+
+ help_method :questions => [:who, :what, :where, :when, :why, :how]
+ PREPOSITIONS = %w[so to from within on without with of in for then as who]
+ INVALID_CLOSERS = %w[and the a an]
+
+ def converse(data)
+ begin
+ verb = data.pop
+ last = data
+ current = inner_what([verb] + last).split(" ")
+ while current
+ say current.join(" ") + "..."
+ last = current
+ current = inner_what([verb] + last[-2..-1]).split(" ") rescue nil
+ sleep(2)
+ end
+ rescue
+ say "converse some phrase [are/is] "
+ end
+ end
+
+ def what(data)
+ say inner_what(data) rescue nil
+ end
+
+ private
+ def inner_what(data)
+ return if data.length < 2
+
+ verb = data.first
+ data[-1] = data.last[0..-2] if data.last[-1..-1] == '?'
+
+ return if verb !~ /is|am|are|was|were/ or data.length == 1
+
+ pre_phrase = data[1..-1]
+ post_phrase = []
+ if PREPOSITIONS.include? pre_phrase.last
+ post_phrase.push pre_phrase.last
+ pre_phrase = pre_phrase[0..-2]
+ end
+ pre_phrase = pre_phrase.join(" ")
+ post_phrase = post_phrase.join(" ")
+
+ mirrors = {'i' => ['you', {'am' => 'are', 'was' => 'were'}],
+ 'you' => ['i', {'are' => 'am', 'were' => 'was'}],
+ 'your' => ['my', {}],
+ 'my' => ['your', {}],
+ 'we' => ['you guys', {}],
+ 'our' => ['your', {}],
+ 'me' => ['you', {}]}
+
+ phrase = [pre_phrase, verb, post_phrase].join(" ").strip.squeeze(" ")
+
+ if phrase =~/(^| )(#{mirrors.keys.join("|")})( |$)/
+ who = $2
+ pre_phrase.gsub!(who, mirrors[who].first)
+ post_phrase.gsub!(who, mirrors[who].first)
+ verb = (mirrors[who].last[verb] or verb)
+ phrase = [pre_phrase, verb, post_phrase].join(" ").strip.squeeze(" ")
+ end
+
+ if phrase == 'i am'
+ return "i am in ur base!!\nkillin ur d00ds!!!"
+ end
+
+ result = "NoResult"
+ searches = (Hpricot(open("http://www.google.com/xhtml/search?q=%22#{phrase.gsub(" ", "+")}%22").read)/:div)
+ searches = searches.select{|s| s.inner_html =~ /#{phrase}/im and s.inner_html !~ /Web results|in your extended network|listeners.* at.*last/m}
+ searches.collect! {|s| s.inner_html.gsub("\n", " ")[/<\/a>(.*)<span/, 1] }
+ searches.compact!
+
+ results = []
+ while !searches.empty?
+ result = searches[rand searches.length]
+ searches.delete result
+
+ result.gsub!(/<.*?>/, ' ')
+ result.gsub!(/\&.{1,6}\;/, ' ')
+ result.gsub!(/\s*-\s*/, ' ')
+ result.gsub!(/[^\s\w\d\,-\;\:\.\'\?\!\?\(\)\]\[]/, '')
+ result = result.squeeze(" ").strip.downcase
+ result.gsub!(/.*(and|if|then|that|who|will|in|by|for|was)$/, " ")
+ result = result.squeeze(" ").strip.downcase
+ 20.times do
+ result = result[/(#{phrase}.*)[\,|-|\;|\:|\.|\!|\?]+/, 1] || result
+ result = result.squeeze(" ").strip.downcase
+ result = result[/(#{phrase}.*?)\s(#{(PREPOSITIONS + INVALID_CLOSERS).join("|")})$/, 1] || result
+ result = result.squeeze(" ").strip.downcase
+ result = result[/(.*)\(/, 1] if result =~ /\(/ and result !~ /\)/
+ end
+ result.gsub!(/ ([st]) /, "'" + '\1 ')
+ result += " to love" if result =~ / able$/
+ result = result[/(.*)\s/, 1] if result.split(" ").last !~ /[aeiou]/
+ results.push result unless !result or result !~/^#{phrase}/i or result =~/#{phrase}$/
+ end
+
+ if !results.empty?
+ result = results.sort{|a,b| a.length <=> b.length}.last
+
+# tags = {'heart|love|desire' => '<3', 'flirt|sex|dirty|wink|mom' => ';)', 'happy|glad|wonderful|amazing|peace' => ':)', 'chris' => ':p'}
+# tags.each do |key, value|
+# if result =~ /#{key}/
+# result += "\n#{value}"
+# break
+# end
+# end
+ result
+
+ else
+ if verb =~ /are|were/
+ say "#{phrase} mysteries to man"
+ else
+ say "#{phrase} a mystery to man"
+ end
+ nil
+ end
+ end
+
+ alias :who :what
+ alias :where :what
+ alias :when :what
+ alias :why :what
+