bitbckt-botbckt 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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 }