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 CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :patch: 0
3
3
  :major: 0
4
- :minor: 2
4
+ :minor: 3
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
- proc = self.commands[command.to_sym]
40
- proc ? proc.call(sender, channel, *args) : say(befuddled, channel)
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
- on :google do |sender, channel, query|
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 self.google(term) #:nodoc:
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
- on :meme do |sender, channel, *args|
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 self.meme #:nodoc:
18
+ def meme #:nodoc:
18
19
  open("http://meme.boxofjunk.ws/moar.txt?lines=1").read.chomp
19
20
  end
20
21
 
@@ -2,10 +2,11 @@ module Botbckt #:nodoc:
2
2
 
3
3
  # Returns 'PONG!' in-channel
4
4
  #
5
- class Ping
6
- extend Commands
5
+ class Ping < Command
7
6
 
8
- on :ping do |sender, channel, *args|
7
+ trigger :ping
8
+
9
+ def call(sender, channel, *args)
9
10
  say 'PONG!', channel
10
11
  end
11
12
  end
@@ -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
- on :remind do |user, channel, reminder_string|
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
- # TODO: Abstraction here, please.
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 self.remind(user, channel, msg, seconds) #:nodoc:
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
- on :botsnack do |sender, channel, *args|
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
- on :ticker do |sender, channel, symbol|
13
- stock_price symbol, channel
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 self.stock_price(symbol, channel) #:nodoc:
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
- say "#{ticker} - $#{price} (#{change})", channel
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 commands }.each { |lib| require File.dirname(__FILE__) + "/botbckt/#{ lib }" }
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.2.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-28 00:00:00 -07:00
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/commands.rb
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
@@ -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 }