bitbckt-botbckt 0.2.0 → 0.3.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/VERSION.yml +1 -1
- data/lib/botbckt/bot.rb +9 -2
- data/lib/botbckt/command.rb +63 -0
- data/lib/botbckt/commands/google.rb +5 -4
- data/lib/botbckt/commands/meme.rb +5 -4
- data/lib/botbckt/commands/ping.rb +4 -3
- data/lib/botbckt/commands/remind.rb +6 -6
- data/lib/botbckt/commands/snack.rb +4 -3
- data/lib/botbckt/commands/star_jar.rb +62 -0
- data/lib/botbckt/commands/ticker.rb +12 -8
- data/lib/botbckt/commands/weather.rb +56 -0
- data/lib/botbckt/utilities.rb +16 -0
- data/lib/botbckt.rb +3 -4
- metadata +6 -3
- data/lib/botbckt/commands.rb +0 -39
data/VERSION.yml
CHANGED
data/lib/botbckt/bot.rb
CHANGED
@@ -36,8 +36,15 @@ module Botbckt #:nodoc:
|
|
36
36
|
# TODO: Before/after callbacks?
|
37
37
|
#++
|
38
38
|
def self.run(command, sender, channel, *args)
|
39
|
-
|
40
|
-
|
39
|
+
callable = self.commands[command.to_sym]
|
40
|
+
|
41
|
+
if callable.is_a?(Class)
|
42
|
+
# Callables are Singletons; we use #create! as a convention to give
|
43
|
+
# possible setup code a place to live.
|
44
|
+
callable = callable.create!(sender, channel, *args)
|
45
|
+
end
|
46
|
+
|
47
|
+
callable.respond_to?(:call) ? callable.call(sender, channel, *args) : say(befuddled, channel)
|
41
48
|
end
|
42
49
|
|
43
50
|
# Returns a random "affirmative" message. Use to acknowledge user input.
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module Botbckt #:nodoc:
|
4
|
+
|
5
|
+
# This acts as a kind of abstract class for Botbckt commands. Extend your
|
6
|
+
# command class with this module to define new bot commands.
|
7
|
+
#
|
8
|
+
# Command subclasses must (re-)define initialize and call.
|
9
|
+
#
|
10
|
+
class Command
|
11
|
+
include Utilities
|
12
|
+
include Singleton
|
13
|
+
|
14
|
+
# By default, returns the singleton instance. Override in a subclass if
|
15
|
+
# a different behavior is expected.
|
16
|
+
#
|
17
|
+
# ==== Parameters (args)
|
18
|
+
# sender<String>:: The user and host of the triggering user. Example: botbckt!n=botbckt@unaffiliated/botbckt
|
19
|
+
# channel<String>:: The channel on which the command was triggered. Example: #ruby-lang
|
20
|
+
# *args:: Any string following the trigger in the message
|
21
|
+
#
|
22
|
+
def self.create!(*args)
|
23
|
+
self.instance
|
24
|
+
end
|
25
|
+
|
26
|
+
# This method is executed by the Bot when a command is triggered. Override
|
27
|
+
# it in a subclass to get the behavior you want.
|
28
|
+
#
|
29
|
+
# ==== Parameters (args)
|
30
|
+
# sender<String>:: The user and host of the triggering user. Example: botbckt!n=botbckt@unaffiliated/botbckt
|
31
|
+
# channel<String>:: The channel on which the command was triggered. Example: #ruby-lang
|
32
|
+
# *args:: Any string following the trigger in the message
|
33
|
+
#
|
34
|
+
def call(*args)
|
35
|
+
raise NoMethodError, "Implement #call in a subclass."
|
36
|
+
end
|
37
|
+
|
38
|
+
# Registers a new command with the bot.
|
39
|
+
#
|
40
|
+
# ==== Parameters
|
41
|
+
# command<Symbol>:: In-channel trigger for the command. Required.
|
42
|
+
#
|
43
|
+
def self.trigger(command, &block)
|
44
|
+
Botbckt::Bot.commands[command.to_sym] = block_given? ? block : self
|
45
|
+
end
|
46
|
+
|
47
|
+
# ==== Parameters
|
48
|
+
# msg<String>:: A message to send to the channel. Required.
|
49
|
+
# channel<String>:: The channel to send the message. Required.
|
50
|
+
#
|
51
|
+
def self.say(msg, channel)
|
52
|
+
Botbckt::Bot.say(msg, channel) if msg
|
53
|
+
end
|
54
|
+
|
55
|
+
# Proxy for Command.say
|
56
|
+
#
|
57
|
+
def say(msg, channel)
|
58
|
+
self.class.say(msg, channel)
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -9,10 +9,11 @@ module Botbckt #:nodoc:
|
|
9
9
|
#
|
10
10
|
# Inspired by Clojurebot: http://github.com/hiredman/clojurebot
|
11
11
|
#
|
12
|
-
class Google
|
13
|
-
extend Commands
|
12
|
+
class Google < Command
|
14
13
|
|
15
|
-
|
14
|
+
trigger :google
|
15
|
+
|
16
|
+
def call(sender, channel, query)
|
16
17
|
result = google(query)
|
17
18
|
say "First out of #{result.first} results:", channel
|
18
19
|
say result.last['titleNoFormatting'], channel
|
@@ -21,7 +22,7 @@ module Botbckt #:nodoc:
|
|
21
22
|
|
22
23
|
private
|
23
24
|
|
24
|
-
def
|
25
|
+
def google(term) #:nodoc:
|
25
26
|
json = open("http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=#{CGI.escape(term)}")
|
26
27
|
response = JSON.parse(json.read)
|
27
28
|
|
@@ -5,16 +5,17 @@ module Botbckt #:nodoc:
|
|
5
5
|
# < user> ~meme
|
6
6
|
# < botbckt> THIS IS LARGE. I CAN TELL BY THE DRINKS, AND FROM HAVING SEEN A LOT OF DRAGONS IN MY DAY.
|
7
7
|
#
|
8
|
-
class Meme
|
9
|
-
extend Commands
|
8
|
+
class Meme < Command
|
10
9
|
|
11
|
-
|
10
|
+
trigger :meme
|
11
|
+
|
12
|
+
def call(sender, channel, *args)
|
12
13
|
say meme, channel
|
13
14
|
end
|
14
15
|
|
15
16
|
private
|
16
17
|
|
17
|
-
def
|
18
|
+
def meme #:nodoc:
|
18
19
|
open("http://meme.boxofjunk.ws/moar.txt?lines=1").read.chomp
|
19
20
|
end
|
20
21
|
|
@@ -7,12 +7,13 @@ module Botbckt #:nodoc:
|
|
7
7
|
# ... five minutes
|
8
8
|
# < botbckt> user: message
|
9
9
|
#
|
10
|
-
class Remind
|
11
|
-
extend Commands
|
10
|
+
class Remind < Command
|
12
11
|
|
13
12
|
SCALES = %w{ minute minutes second seconds hour hours }
|
14
13
|
|
15
|
-
|
14
|
+
trigger :remind
|
15
|
+
|
16
|
+
def call(user, channel, reminder_string)
|
16
17
|
# Somewhat faster than #match...
|
17
18
|
reminder_string =~ /in (\d+) (\w+) with (.*)/i
|
18
19
|
num, scale, msg = $1, $2, $3
|
@@ -20,8 +21,7 @@ module Botbckt #:nodoc:
|
|
20
21
|
if SCALES.include?(scale)
|
21
22
|
time = num.to_i.send(scale.to_sym).seconds
|
22
23
|
|
23
|
-
|
24
|
-
remind(user.gsub(/([^!]+).*/, '\1'), channel, msg, time)
|
24
|
+
remind(freenode_split(user).first, channel, msg, time)
|
25
25
|
else
|
26
26
|
say Botbckt::Bot.befuddled, channel
|
27
27
|
end
|
@@ -29,7 +29,7 @@ module Botbckt #:nodoc:
|
|
29
29
|
|
30
30
|
private
|
31
31
|
|
32
|
-
def
|
32
|
+
def remind(user, channel, msg, seconds) #:nodoc:
|
33
33
|
EventMachine::Timer.new(seconds) do
|
34
34
|
say "#{user}: #{msg}", channel
|
35
35
|
end
|
@@ -4,10 +4,11 @@ module Botbckt #:nodoc:
|
|
4
4
|
#
|
5
5
|
# Inspired by Clojurebot: http://github.com/hiredman/clojurebot
|
6
6
|
#
|
7
|
-
class Snack
|
8
|
-
extend Commands
|
7
|
+
class Snack < Command
|
9
8
|
|
10
|
-
|
9
|
+
trigger :botsnack
|
10
|
+
|
11
|
+
def call(sender, channel, *args)
|
11
12
|
say 'nom nom nom', channel
|
12
13
|
end
|
13
14
|
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Botbckt #:nodoc:
|
2
|
+
|
3
|
+
# Gives a gold star to a space-separated list of users.
|
4
|
+
#
|
5
|
+
# < user> ~star joe
|
6
|
+
# < botbckt> joe: Gold star for you!
|
7
|
+
#
|
8
|
+
# But when a user tries to award himself...
|
9
|
+
#
|
10
|
+
# < user> ~star user
|
11
|
+
# < botbckt> user: No star for you!
|
12
|
+
#
|
13
|
+
class StarJar < Command
|
14
|
+
|
15
|
+
trigger :star
|
16
|
+
|
17
|
+
attr_accessor :jar
|
18
|
+
|
19
|
+
def self.create!(*args) #:nodoc:
|
20
|
+
self.instance.jar ||= { }
|
21
|
+
self.instance
|
22
|
+
end
|
23
|
+
|
24
|
+
# Adds a star to the jar for the user
|
25
|
+
#
|
26
|
+
# ==== Parameters
|
27
|
+
# user<String>:: The user receiving a star. Required.
|
28
|
+
#
|
29
|
+
def push(user)
|
30
|
+
@jar[user] ||= 0
|
31
|
+
@jar[user] += 1
|
32
|
+
end
|
33
|
+
|
34
|
+
# Removes a star from the jar for the user
|
35
|
+
#
|
36
|
+
# ==== Parameters
|
37
|
+
# user<String>:: The user being docked a star. Required.
|
38
|
+
#
|
39
|
+
def pop(user)
|
40
|
+
@jar[user] ||= 0
|
41
|
+
|
42
|
+
if @jar[user] == 0
|
43
|
+
return 0
|
44
|
+
else
|
45
|
+
@jar[user] -= 1
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def call(giver, channel, receiver)
|
50
|
+
receiver.split(' ').each do |rcv|
|
51
|
+
if rcv != freenode_split(giver).first
|
52
|
+
total = push(rcv)
|
53
|
+
say "#{rcv}: Gold star for you! (#{total})", channel
|
54
|
+
else
|
55
|
+
say "#{rcv}: No star for you!", channel
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -6,24 +6,28 @@ module Botbckt #:nodoc:
|
|
6
6
|
# < user> ~ticker GOOG
|
7
7
|
# < botbckt> GOOG - $391.06 (+0.06)
|
8
8
|
#
|
9
|
-
class Ticker
|
10
|
-
extend Commands
|
9
|
+
class Ticker < Command
|
11
10
|
|
12
|
-
|
13
|
-
|
11
|
+
trigger :ticker
|
12
|
+
|
13
|
+
def call(sender, channel, symbol)
|
14
|
+
begin
|
15
|
+
say stock_price(symbol.split(' ').first), channel
|
16
|
+
# TODO: Log me.
|
17
|
+
rescue OpenURI::HTTPError => e
|
18
|
+
say Botbckt::Bot.befuddled, channel
|
19
|
+
end
|
14
20
|
end
|
15
21
|
|
16
22
|
private
|
17
23
|
|
18
|
-
def
|
24
|
+
def stock_price(symbol) #:nodoc:
|
19
25
|
json = open("http://www.google.com/finance/info?q=#{CGI.escape(symbol)}")
|
20
26
|
response = JSON.parse(json.read[4..-1]).first
|
21
27
|
|
22
28
|
ticker, price, change = response['t'], response['l'], response['c']
|
23
29
|
|
24
|
-
|
25
|
-
rescue OpenURI::HTTPError => e
|
26
|
-
say Botbckt::Bot.befuddled, channel
|
30
|
+
"#{ticker} - $#{price} (#{change})"
|
27
31
|
end
|
28
32
|
end
|
29
33
|
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Botbckt #:nodoc:
|
2
|
+
|
3
|
+
# Displays a simple forecast or a prosaic description of today's conditions.
|
4
|
+
#
|
5
|
+
# < user> ~forecast 90210
|
6
|
+
# < botbckt> Today's Forecast: 90F/65F (Sunny)
|
7
|
+
#
|
8
|
+
# < user> ~conditions Los Angeles, CA
|
9
|
+
# < botbckt> Conditions -
|
10
|
+
# < botbckt> Today: Areas of low clouds in the morning then mostly sunny. Highs in the upper 60s to mid 70s. West winds 10 to 20 mph in the afternoon.
|
11
|
+
# < botbckt> Tonight: Mostly clear. Lows in the mid to upper 50s. West winds 10 to 20 mph in the evening.
|
12
|
+
#
|
13
|
+
class Weather < Command
|
14
|
+
|
15
|
+
trigger :forecast do |sender, channel, query|
|
16
|
+
begin
|
17
|
+
say forecast(query), channel
|
18
|
+
# TODO: Log me.
|
19
|
+
rescue OpenURI::HTTPError => e
|
20
|
+
say Botbckt::Bot.befuddled, channel
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
trigger :conditions do |sender, channel, query|
|
25
|
+
begin
|
26
|
+
say conditions(query), channel
|
27
|
+
# TODO: Log me.
|
28
|
+
rescue OpenURI::HTTPError => e
|
29
|
+
say Botbckt::Bot.befuddled, channel
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def self.conditions(query) #:nodoc:
|
36
|
+
xml = (search(query)/'txt_forecast')
|
37
|
+
daytime = (xml/'forecastday[1]')
|
38
|
+
evening = (xml/'forecastday[2]')
|
39
|
+
|
40
|
+
"Conditions -\nToday: #{(daytime/'fcttext').inner_html}\nTonight: #{(evening/'fcttext').inner_html}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.forecast(query) #:nodoc:
|
44
|
+
xml = (search(query)/'simpleforecast/forecastday[1]')
|
45
|
+
|
46
|
+
"Today's Forecast: #{(xml/'high/fahrenheit').inner_html}F/#{(xml/'low/fahrenheit').inner_html}F (#{(xml/'conditions').inner_html})"
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.search(query) #:nodoc:
|
50
|
+
xml = open("http://api.wunderground.com/auto/wui/geo/ForecastXML/index.xml?query=#{CGI.escape(query)}")
|
51
|
+
Hpricot.XML(xml)
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Botbckt #:nodoc:
|
2
|
+
|
3
|
+
module Utilities
|
4
|
+
|
5
|
+
# Splits a Freenode user string into a login, user, hostmask tuple
|
6
|
+
#
|
7
|
+
# ==== Parameters
|
8
|
+
# str<String>:: The user string to split
|
9
|
+
#
|
10
|
+
def freenode_split(str)
|
11
|
+
str.scan(/^([^!]+)!n=([^@]+)@(.*)$/).flatten!
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
data/lib/botbckt.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
%w{ rubygems eventmachine activesupport ostruct json open-uri cgi }.each { |lib| require lib }
|
2
|
-
%w{ irc bot
|
3
|
-
|
4
|
-
Botbckt::Bot.start :user => 'botbckt', :server => 'irc.freenode.net', :port => 6667, :channels => ['reno.rb']
|
1
|
+
%w{ rubygems eventmachine activesupport ostruct json open-uri cgi hpricot }.each { |lib| require lib }
|
2
|
+
%w{ irc bot utilities command }.each { |lib| require File.dirname(__FILE__) + "/botbckt/#{ lib }" }
|
3
|
+
Dir[File.dirname(__FILE__) + '/botbckt/commands/*'].each { |lib| require lib }
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bitbckt-botbckt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brandon Mitchell
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-04
|
12
|
+
date: 2009-05-04 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -26,14 +26,17 @@ files:
|
|
26
26
|
- VERSION.yml
|
27
27
|
- lib/botbckt.rb
|
28
28
|
- lib/botbckt/bot.rb
|
29
|
-
- lib/botbckt/
|
29
|
+
- lib/botbckt/command.rb
|
30
30
|
- lib/botbckt/commands/google.rb
|
31
31
|
- lib/botbckt/commands/meme.rb
|
32
32
|
- lib/botbckt/commands/ping.rb
|
33
33
|
- lib/botbckt/commands/remind.rb
|
34
34
|
- lib/botbckt/commands/snack.rb
|
35
|
+
- lib/botbckt/commands/star_jar.rb
|
35
36
|
- lib/botbckt/commands/ticker.rb
|
37
|
+
- lib/botbckt/commands/weather.rb
|
36
38
|
- lib/botbckt/irc.rb
|
39
|
+
- lib/botbckt/utilities.rb
|
37
40
|
- README
|
38
41
|
has_rdoc: true
|
39
42
|
homepage: http://github.com/bitbckt/botbckt
|
data/lib/botbckt/commands.rb
DELETED
@@ -1,39 +0,0 @@
|
|
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
|
-
# ==== Callable args
|
19
|
-
# sender<String>:: The user and host of the triggering user. Example: botbckt!n=botbckt@unaffiliated/botbckt
|
20
|
-
# channel<String>:: The channel on which the command was triggered. Example: #ruby-lang
|
21
|
-
# *args:: Any string following the trigger in the message
|
22
|
-
#
|
23
|
-
def on(command, proc = nil, &block)
|
24
|
-
Botbckt::Bot.commands[command.to_sym] = proc || block
|
25
|
-
end
|
26
|
-
|
27
|
-
# ==== Parameters
|
28
|
-
# msg<String>:: A message to send to the channel. Required.
|
29
|
-
# channel<String>:: The channel to send the message. Required.
|
30
|
-
#
|
31
|
-
def say(msg, channel)
|
32
|
-
Botbckt::Bot.say(msg, channel) if msg
|
33
|
-
end
|
34
|
-
|
35
|
-
end
|
36
|
-
|
37
|
-
end
|
38
|
-
|
39
|
-
Dir[File.dirname(__FILE__) + '/commands/*'].each { |lib| require lib }
|