bitbckt-botbckt 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +3 -0
- data/Rakefile +21 -0
- data/VERSION.yml +4 -0
- data/lib/botbckt/bot.rb +70 -0
- data/lib/botbckt/commands/google.rb +35 -0
- data/lib/botbckt/commands/meme.rb +23 -0
- data/lib/botbckt/commands/ping.rb +13 -0
- data/lib/botbckt/commands/remind.rb +41 -0
- data/lib/botbckt/commands/snack.rb +16 -0
- data/lib/botbckt/commands/ticker.rb +28 -0
- data/lib/botbckt/commands.rb +33 -0
- data/lib/botbckt/irc.rb +100 -0
- data/lib/botbckt.rb +2 -0
- metadata +65 -0
data/README
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rake/rdoctask'
|
2
|
+
|
3
|
+
Rake::RDocTask.new do |rd|
|
4
|
+
rd.main = 'README'
|
5
|
+
rd.rdoc_dir = 'doc'
|
6
|
+
rd.rdoc_files.include('README', 'lib/**/*.rb')
|
7
|
+
end
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'jeweler'
|
11
|
+
Jeweler::Tasks.new do |g|
|
12
|
+
g.name = 'botbckt'
|
13
|
+
g.summary = 'Boredom strikes on Sunday mornings.'
|
14
|
+
g.email = 'brandon@systemisdown.net'
|
15
|
+
g.homepage = 'http://github.com/bitbckt/botbckt'
|
16
|
+
g.description = 'Boredom strikes on Sunday mornings.'
|
17
|
+
g.authors = ['Brandon Mitchell']
|
18
|
+
end
|
19
|
+
rescue LoadError
|
20
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
21
|
+
end
|
data/VERSION.yml
ADDED
data/lib/botbckt/bot.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
module Botbckt #:nodoc:
|
2
|
+
|
3
|
+
# Create a new IRC bot. See Bot.start to get started.
|
4
|
+
#
|
5
|
+
class Bot
|
6
|
+
|
7
|
+
AFFIRMATIVE = ["'Sea, mhuise.", "In Ordnung", "Ik begrijp", "Alles klar", "Ok.", "Roger.", "You don't have to tell me twice.", "Ack. Ack.", "C'est bon!"]
|
8
|
+
NEGATIVE = ["Titim gan éirí ort.", "Gabh mo leithscéal?", "No entiendo", "excusez-moi", "Excuse me?", "Huh?", "I don't understand.", "Pardon?", "It's greek to me."]
|
9
|
+
|
10
|
+
cattr_accessor :commands
|
11
|
+
@@commands = { }
|
12
|
+
|
13
|
+
# ==== Parameters
|
14
|
+
# options<Hash{Symbol => String,Integer}>
|
15
|
+
#
|
16
|
+
# ==== Options (options)
|
17
|
+
# :user<String>:: The username this instance should use. Required.
|
18
|
+
# :password<String>:: A password to send to the Nickserv. Optional.
|
19
|
+
# :server<String>:: The FQDN of the IRC server. Required.
|
20
|
+
# :port<~to_i>:: The port number of the IRC server. Required.
|
21
|
+
# :channels<Array[String]>:: An array of channels to join. Channel names should *not* include the '#' prefix. Required.
|
22
|
+
# :log<String>:: The name of a log file. Defaults to 'botbckt.log'.
|
23
|
+
#
|
24
|
+
def self.start(options)
|
25
|
+
EventMachine::run do
|
26
|
+
Botbckt::IRC.connect(options)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# ==== Parameters
|
31
|
+
# command<Symbol>:: The name of a registered command to run. Required.
|
32
|
+
# *args:: Arguments to be passed to the command. Optional.
|
33
|
+
#
|
34
|
+
#--
|
35
|
+
# TODO: Before/after callbacks?
|
36
|
+
#++
|
37
|
+
def self.run(command, *args)
|
38
|
+
proc = self.commands[command.to_sym]
|
39
|
+
proc ? proc.call(*args) : say(befuddled)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns a random "affirmative" message. Use to acknowledge user input.
|
43
|
+
#
|
44
|
+
#--
|
45
|
+
# Inspired by Clojurebot: http://github.com/hiredman/clojurebot
|
46
|
+
#++
|
47
|
+
def self.ok
|
48
|
+
AFFIRMATIVE[rand(AFFIRMATIVE.size)]
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns a random "confused" message. Use as a kind of "method missing"
|
52
|
+
# on unknown user input.
|
53
|
+
#
|
54
|
+
#--
|
55
|
+
# Inspired by Clojurebot: http://github.com/hiredman/clojurebot
|
56
|
+
#++
|
57
|
+
def self.befuddled
|
58
|
+
NEGATIVE[rand(NEGATIVE.size)]
|
59
|
+
end
|
60
|
+
|
61
|
+
# ==== Parameters
|
62
|
+
# msg<String>:: A message to send to the channel
|
63
|
+
#
|
64
|
+
def self.say(msg)
|
65
|
+
Botbckt::IRC.connection.say msg
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Botbckt #:nodoc:
|
2
|
+
|
3
|
+
# Sends a query to Google via the JSON API and returns output in-channel:
|
4
|
+
#
|
5
|
+
# < user> ~google ruby
|
6
|
+
# < botbckt> First out of 93900000 results:
|
7
|
+
# < botbckt> Ruby Programming Language
|
8
|
+
# < botbckt> http://www.ruby-lang.org/
|
9
|
+
#
|
10
|
+
# Inspired by Clojurebot: http://github.com/hiredman/clojurebot
|
11
|
+
#
|
12
|
+
class Google
|
13
|
+
extend Commands
|
14
|
+
|
15
|
+
on :google do |query, *args|
|
16
|
+
result = google(query)
|
17
|
+
say "First out of #{result.first} results:"
|
18
|
+
say result.last['titleNoFormatting']
|
19
|
+
say result.last['unescapedUrl']
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def self.google(term) #:nodoc:
|
25
|
+
json = open("http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=#{CGI.escape(term)}")
|
26
|
+
response = JSON.parse(json.read)
|
27
|
+
|
28
|
+
[
|
29
|
+
response['responseData']['cursor']['estimatedResultCount'],
|
30
|
+
response['responseData']['results'].first
|
31
|
+
]
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Botbckt #:nodoc:
|
2
|
+
|
3
|
+
# Grabs the first boxofjunk meme and returns output in-channel:
|
4
|
+
#
|
5
|
+
# < user> ~meme
|
6
|
+
# < botbckt> THIS IS LARGE. I CAN TELL BY THE DRINKS, AND FROM HAVING SEEN A LOT OF DRAGONS IN MY DAY.
|
7
|
+
#
|
8
|
+
class Meme
|
9
|
+
extend Commands
|
10
|
+
|
11
|
+
on :meme do |*args|
|
12
|
+
say meme
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def self.meme #:nodoc:
|
18
|
+
open("http://meme.boxofjunk.ws/moar.txt?lines=1").read.chomp
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Botbckt #:nodoc:
|
2
|
+
|
3
|
+
# Schedules a reminder for a period of seconds, minutes or hours to be
|
4
|
+
# repeated in-channel:
|
5
|
+
#
|
6
|
+
# < user> ~remind in 5 minutes with message
|
7
|
+
# ... five minutes
|
8
|
+
# < botbckt> user: message
|
9
|
+
#
|
10
|
+
class Remind
|
11
|
+
extend Commands
|
12
|
+
|
13
|
+
SCALES = %w{ minute minutes second seconds hour hours }
|
14
|
+
|
15
|
+
on :remind do |reminder_string, user, *args|
|
16
|
+
# Somewhat faster than #match...
|
17
|
+
reminder_string =~ /in (\d+) (\w+) with (.*)/i
|
18
|
+
num, scale, msg = $1, $2, $3
|
19
|
+
|
20
|
+
if SCALES.include?(scale)
|
21
|
+
time = num.to_i.send(scale.to_sym).seconds
|
22
|
+
|
23
|
+
# TODO: Abstraction here, please.
|
24
|
+
remind(user.gsub(/([^!]+).*/, '\1'), msg, time)
|
25
|
+
else
|
26
|
+
say Botbckt::Bot.befuddled
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def self.remind(user, msg, seconds) #:nodoc:
|
33
|
+
EventMachine::Timer.new(seconds) do
|
34
|
+
say "#{user}: #{msg}"
|
35
|
+
end
|
36
|
+
say Botbckt::Bot.ok
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Botbckt #:nodoc:
|
2
|
+
|
3
|
+
# Grabs the current stock price of a symbol from Google Finance and displays
|
4
|
+
# in-channel:
|
5
|
+
#
|
6
|
+
# < user> ~ticker GOOG
|
7
|
+
# < botbckt> GOOG - $391.06 (+0.06)
|
8
|
+
#
|
9
|
+
class Ticker
|
10
|
+
extend Commands
|
11
|
+
|
12
|
+
on :ticker do |symbol, *args|
|
13
|
+
say stock_price(symbol)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def self.stock_price(symbol) #:nodoc:
|
19
|
+
json = open("http://www.google.com/finance/info?q=#{CGI.escape(symbol)}")
|
20
|
+
response = JSON.parse(json.read[4..-1]).first
|
21
|
+
|
22
|
+
ticker, price, change = response['t'], response['l'], response['c']
|
23
|
+
|
24
|
+
"#{ticker} - $#{price} (#{change})"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Botbckt #:nodoc:
|
2
|
+
|
3
|
+
# This acts as a kind of abstract class for Botbckt commands. Extend your
|
4
|
+
# command class with this module to define new bot commands.
|
5
|
+
#
|
6
|
+
module Commands
|
7
|
+
|
8
|
+
# Registers a new command with the bot. Either a proc or a block are
|
9
|
+
# required.
|
10
|
+
#
|
11
|
+
# Inspired by Isaac: http://github.com/ichverstehe/isaac
|
12
|
+
#
|
13
|
+
# ==== Parameters
|
14
|
+
# command<Symbol>:: In-channel trigger for the command. Required.
|
15
|
+
# proc<Proc>:: Proc to execute when the command is triggered.
|
16
|
+
# &block:: Block to execute when the command is triggered.
|
17
|
+
#
|
18
|
+
def on(command, proc = nil, &block)
|
19
|
+
Botbckt::Bot.commands[command.to_sym] = proc || block
|
20
|
+
end
|
21
|
+
|
22
|
+
# ==== Parameters
|
23
|
+
# msg<String>:: A message to send to the channel
|
24
|
+
#
|
25
|
+
def say(msg)
|
26
|
+
Botbckt::Bot.say msg
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
Dir[File.dirname(__FILE__) + '/commands/*'].each { |lib| require lib }
|
data/lib/botbckt/irc.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
module Botbckt #:nodoc:
|
2
|
+
|
3
|
+
# An EventMachine-based IRC client. See IRC.connect to get started.
|
4
|
+
#
|
5
|
+
class IRC < EventMachine::Connection
|
6
|
+
include EventMachine::Protocols::LineText2
|
7
|
+
|
8
|
+
attr_accessor :config
|
9
|
+
cattr_accessor :connection
|
10
|
+
|
11
|
+
# Instantiate a new IRC connection.
|
12
|
+
#
|
13
|
+
# ==== Parameters
|
14
|
+
# options<Hash{Symbol => String,Integer}>
|
15
|
+
#
|
16
|
+
# ==== Options (options)
|
17
|
+
# :user<String>:: The username this instance should use. Required.
|
18
|
+
# :password<String>:: A password to send to the Nickserv. Optional.
|
19
|
+
# :server<String>:: The FQDN of the IRC server. Required.
|
20
|
+
# :port<~to_i>:: The port number of the IRC server. Required.
|
21
|
+
# :channels<Array[String]>:: An array of channels to join. Channel names should *not* include the '#' prefix. Required.
|
22
|
+
# :log<String>:: The name of a log file. Defaults to 'botbckt.log'.
|
23
|
+
#
|
24
|
+
def self.connect(options)
|
25
|
+
self.connection = EM.connect(options[:server], options[:port].to_i, self, options)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Use IRC.connect; this method is not for you.
|
29
|
+
#
|
30
|
+
def initialize(options) #:nodoc:
|
31
|
+
self.config = OpenStruct.new(options)
|
32
|
+
|
33
|
+
log = config[:log] || 'botbckt.log'
|
34
|
+
@logger = ActiveSupport::BufferedLogger.new(log)
|
35
|
+
end
|
36
|
+
|
37
|
+
# ==== Parameters
|
38
|
+
# msg<String>:: A message to send to the channel
|
39
|
+
#
|
40
|
+
#--
|
41
|
+
# TODO: Handle multiple channels
|
42
|
+
#++
|
43
|
+
def say(msg)
|
44
|
+
msg.split("\n").each do |msg|
|
45
|
+
command "PRIVMSG", "##{config.channels.first}", ":#{msg}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def post_init #:nodoc:
|
51
|
+
command "USER", [config.user]*4
|
52
|
+
command "NICK", config.user
|
53
|
+
command("NickServ IDENTIFY", config.user, config.password) if config.password
|
54
|
+
config.channels.each { |channel| command("JOIN", "##{ channel }") } if config.channels
|
55
|
+
end
|
56
|
+
|
57
|
+
#--
|
58
|
+
# FIXME: Re-order commands args such that 1-2 arity commands can still access
|
59
|
+
# both sender and channel
|
60
|
+
#++
|
61
|
+
def receive_line(line) #:nodoc:
|
62
|
+
case line
|
63
|
+
when /^PING (.*)/:
|
64
|
+
command('PONG', $1)
|
65
|
+
when /^:(\S+) PRIVMSG (.*) :(~|#{Regexp.escape config.user}: )(\w+)( .*)?$/:
|
66
|
+
# args are optional - not all commands need/support them
|
67
|
+
args = $5 ? [$5.squish, $1, $2] : [$1, $2]
|
68
|
+
|
69
|
+
# run args: command (with args), sender, channel
|
70
|
+
Botbckt::Bot.run($4, *args)
|
71
|
+
else
|
72
|
+
log line
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def unbind #:nodoc:
|
77
|
+
EM.add_timer(3) do
|
78
|
+
reconnect(config.server, config.port)
|
79
|
+
post_init
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
#--
|
86
|
+
# TODO: Add log levels
|
87
|
+
#++
|
88
|
+
def log(msg) #:nodoc:
|
89
|
+
@logger.add(0, msg)
|
90
|
+
end
|
91
|
+
|
92
|
+
def command(*cmd) #:nodoc:
|
93
|
+
line = "#{ cmd.flatten.join(' ') }\r\n"
|
94
|
+
send_data line
|
95
|
+
log line
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
data/lib/botbckt.rb
ADDED
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bitbckt-botbckt
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brandon Mitchell
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-04-26 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Boredom strikes on Sunday mornings.
|
17
|
+
email: brandon@systemisdown.net
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README
|
24
|
+
files:
|
25
|
+
- Rakefile
|
26
|
+
- VERSION.yml
|
27
|
+
- lib/botbckt.rb
|
28
|
+
- lib/botbckt/bot.rb
|
29
|
+
- lib/botbckt/commands.rb
|
30
|
+
- lib/botbckt/commands/google.rb
|
31
|
+
- lib/botbckt/commands/meme.rb
|
32
|
+
- lib/botbckt/commands/ping.rb
|
33
|
+
- lib/botbckt/commands/remind.rb
|
34
|
+
- lib/botbckt/commands/snack.rb
|
35
|
+
- lib/botbckt/commands/ticker.rb
|
36
|
+
- lib/botbckt/irc.rb
|
37
|
+
- README
|
38
|
+
has_rdoc: true
|
39
|
+
homepage: http://github.com/bitbckt/botbckt
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options:
|
42
|
+
- --charset=UTF-8
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
version:
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
version:
|
57
|
+
requirements: []
|
58
|
+
|
59
|
+
rubyforge_project:
|
60
|
+
rubygems_version: 1.2.0
|
61
|
+
signing_key:
|
62
|
+
specification_version: 2
|
63
|
+
summary: Boredom strikes on Sunday mornings.
|
64
|
+
test_files: []
|
65
|
+
|