bitbckt-botbckt 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|