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 ADDED
@@ -0,0 +1,3 @@
1
+ Boredom strikes on Sunday mornings.
2
+
3
+ Released under the MIT license. Google it.
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
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 1
4
+ :patch: 0
@@ -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,13 @@
1
+ module Botbckt #:nodoc:
2
+
3
+ # Returns 'PONG!' in-channel
4
+ #
5
+ class Ping
6
+ extend Commands
7
+
8
+ on :ping do |*args|
9
+ say 'PONG!'
10
+ end
11
+ end
12
+
13
+ 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,16 @@
1
+ module Botbckt #:nodoc:
2
+
3
+ # Feeds the bot a tasty treat.
4
+ #
5
+ # Inspired by Clojurebot: http://github.com/hiredman/clojurebot
6
+ #
7
+ class Snack
8
+ extend Commands
9
+
10
+ on :botsnack do |*args|
11
+ say 'nom nom nom'
12
+ end
13
+
14
+ end
15
+
16
+ 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 }
@@ -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
@@ -0,0 +1,2 @@
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 }" }
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
+