dickburt 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +63 -0
- data/Guardfile +7 -0
- data/README.md +50 -0
- data/Rakefile +10 -0
- data/bin/dickburt +26 -0
- data/dickburt.gemspec +28 -0
- data/lib/dickburt.rb +29 -0
- data/lib/dickburt/bot.rb +137 -0
- data/lib/dickburt/campfire.rb +38 -0
- data/lib/dickburt/config.rb +63 -0
- data/lib/dickburt/logger.rb +7 -0
- data/lib/dickburt/message.rb +25 -0
- data/lib/dickburt/response.rb +17 -0
- data/lib/dickburt/retryable.rb +30 -0
- data/lib/dickburt/room.rb +64 -0
- data/lib/dickburt/server.rb +46 -0
- data/lib/dickburt/user.rb +10 -0
- data/lib/dickburt/version.rb +3 -0
- data/spec/dickburt/bot_spec.rb +41 -0
- data/spec/dickburt/campfire_spec.rb +8 -0
- data/spec/dickburt/config_spec.rb +24 -0
- data/spec/dickburt/response_spec.rb +15 -0
- data/spec/dickburt/room_spec.rb +15 -0
- data/spec/dickburt/server_spec.rb +8 -0
- data/spec/spec_helper.rb +23 -0
- metadata +182 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm 1.9.2@dickburt --create
|
data/Gemfile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in camper_van.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
# specified here rather than in gemspec because they're
|
7
|
+
# for local mac development only
|
8
|
+
group :development do
|
9
|
+
gem "rb-fsevent"
|
10
|
+
gem "growl"
|
11
|
+
gem "guard"
|
12
|
+
# 0.4.0.rc versions are still git-only
|
13
|
+
gem "guard-minitest", :git => "https://github.com/guard/guard-minitest.git"
|
14
|
+
gem "fabrication"
|
15
|
+
gem "ffaker", :require => "ffaker"
|
16
|
+
gem 'vcr'
|
17
|
+
gem "fakeweb"
|
18
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
GIT
|
2
|
+
remote: https://github.com/guard/guard-minitest.git
|
3
|
+
revision: a5479b18222ba4f95e65d558484d6c32456ca401
|
4
|
+
specs:
|
5
|
+
guard-minitest (0.4.0)
|
6
|
+
guard (~> 0.4)
|
7
|
+
|
8
|
+
PATH
|
9
|
+
remote: .
|
10
|
+
specs:
|
11
|
+
dickburt (0.0.5)
|
12
|
+
addressable (~> 2.2.6)
|
13
|
+
eventmachine (~> 0.12.10)
|
14
|
+
json (~> 1.5.1)
|
15
|
+
logging (~> 1.5.1)
|
16
|
+
map (~> 4.3.0)
|
17
|
+
patron (~> 0.4.11)
|
18
|
+
trollop (~> 1.16.2)
|
19
|
+
twitter-stream (~> 0.1.14)
|
20
|
+
|
21
|
+
GEM
|
22
|
+
remote: http://rubygems.org/
|
23
|
+
specs:
|
24
|
+
addressable (2.2.6)
|
25
|
+
eventmachine (0.12.10)
|
26
|
+
fabrication (1.0.1)
|
27
|
+
fakeweb (1.3.0)
|
28
|
+
ffaker (1.8.0)
|
29
|
+
growl (1.0.3)
|
30
|
+
guard (0.6.2)
|
31
|
+
thor (~> 0.14.6)
|
32
|
+
http_parser.rb (0.5.1)
|
33
|
+
json (1.5.3)
|
34
|
+
little-plugger (1.1.2)
|
35
|
+
logging (1.5.2)
|
36
|
+
little-plugger (>= 1.1.2)
|
37
|
+
map (4.3.0)
|
38
|
+
minitest (2.2.2)
|
39
|
+
patron (0.4.14)
|
40
|
+
rb-fsevent (0.4.3.1)
|
41
|
+
simple_oauth (0.1.5)
|
42
|
+
thor (0.14.6)
|
43
|
+
trollop (1.16.2)
|
44
|
+
twitter-stream (0.1.14)
|
45
|
+
eventmachine (>= 0.12.8)
|
46
|
+
http_parser.rb (~> 0.5.1)
|
47
|
+
simple_oauth (~> 0.1.4)
|
48
|
+
vcr (1.11.1)
|
49
|
+
|
50
|
+
PLATFORMS
|
51
|
+
ruby
|
52
|
+
|
53
|
+
DEPENDENCIES
|
54
|
+
dickburt!
|
55
|
+
fabrication
|
56
|
+
fakeweb
|
57
|
+
ffaker
|
58
|
+
growl
|
59
|
+
guard
|
60
|
+
guard-minitest!
|
61
|
+
minitest (~> 2.2.2)
|
62
|
+
rb-fsevent
|
63
|
+
vcr
|
data/Guardfile
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
guard 'minitest' do
|
2
|
+
# with Minitest::Spec
|
3
|
+
watch(%r|^spec/(.*)_spec\.rb|)
|
4
|
+
watch(%r|^spec/fabricators/(.*)_fabricator\.rb|) { |m| "spec/dickburt/#{m[1]}_spec.rb" }
|
5
|
+
watch(%r|^lib/(.*)\.rb|) { |m| "spec/#{m[1]}_spec.rb" }
|
6
|
+
watch(%r|^spec/spec_helper\.rb|) { "spec" }
|
7
|
+
end
|
data/README.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
Dickburt: A campfire bot
|
2
|
+
========================
|
3
|
+
|
4
|
+
gem install dickburt
|
5
|
+
|
6
|
+
dickburt desertbeaver the_overlook --token the_token_for_your_bot_user
|
7
|
+
|
8
|
+
Connecting to Campfire™
|
9
|
+
-----------------------
|
10
|
+
Dickburt runs from the command line, he gets installed when the gem gets installed. Connecting him to your campfire requires a few things.
|
11
|
+
|
12
|
+
First, you need a user for your bot. Go make a user, you'll need an email for your bot, but I'll assume you know how to make an email and signup for campfire. Once you've signed up and signed in as your bot, you're going to need to find the API token. Click "My info" and the [API token is pretty easy to spot](http://cl.ly/3W0r1H1h3a281z1a1C00 "Spotting the api token").
|
13
|
+
|
14
|
+
Second, the command line args are:
|
15
|
+
|
16
|
+
1. The subdomain of the campfire you want to join, so, in the example above we're trying to join http://desertbeaver.campfirenow.com
|
17
|
+
|
18
|
+
2. The campfire room we want to join, downcased and spaces replaced with underscores. So again in the above example the_overlook is means we'll join The Overlook room. Pretty easy.
|
19
|
+
|
20
|
+
3. The last argument is the API token. Dickburt will store the api token in ~/.dickburt/config.yml after the first time you give him a token for a subdomain. Note! API Tokens are unique by subdomain.
|
21
|
+
|
22
|
+
Commands
|
23
|
+
--------
|
24
|
+
Right now, this sucks, but you have to say "dickburt <somecommand>" or "somecommand blah lbah blah dickburt". You just have to say a command and dickburt in the same message.
|
25
|
+
|
26
|
+
His commands are really limited but it is easy to make new ones.
|
27
|
+
|
28
|
+
- hi
|
29
|
+
- imageme (takes a query)
|
30
|
+
- beerme
|
31
|
+
- whatup
|
32
|
+
- fuckyeah
|
33
|
+
|
34
|
+
Examples
|
35
|
+
--------
|
36
|
+
|
37
|
+
Tyler: dickburt imageme hipster ferrets
|
38
|
+
# => will upload the first image it finds for "hipster ferrets" on google image search
|
39
|
+
|
40
|
+
Tyler: dickburt whatup
|
41
|
+
# => dickburt: whatup Tyler
|
42
|
+
|
43
|
+
TODO
|
44
|
+
----
|
45
|
+
* Use a better http library like faraday. Patron sucks.
|
46
|
+
* Use yajl to parse json stream.
|
47
|
+
* Make dickburt helpful. Like if he doesn't know a command he should tell you and probably also tell you what he does know.
|
48
|
+
* Keep a list of Dickburt::Users around
|
49
|
+
* Lookup everyone in the Room when we connect
|
50
|
+
* Lookup the user that messaged dickburt from our list of Users, or, go get the info for the user and store them in the list.
|
data/Rakefile
ADDED
data/bin/dickburt
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require "trollop"
|
4
|
+
$:.unshift File.expand_path('../../lib/', __FILE__)
|
5
|
+
require "dickburt"
|
6
|
+
|
7
|
+
t = Trollop::Parser.new do
|
8
|
+
opt :token, "The campfire token for dickburt", :type => :string
|
9
|
+
end
|
10
|
+
|
11
|
+
opts = Trollop::with_standard_exception_handling( t ) do
|
12
|
+
opts = t.parse ARGV
|
13
|
+
host = t.leftovers.shift
|
14
|
+
room = t.leftovers.shift
|
15
|
+
unless host && room
|
16
|
+
raise Trollop::CommandlineError, "host and room must be provided as arguments. Ex: dickburt subdomain_of_my_campfire super_awesome_room"
|
17
|
+
end
|
18
|
+
|
19
|
+
begin
|
20
|
+
Dickburt::Config.set_token(host.to_sym, opts[:token])
|
21
|
+
config = Dickburt::Config.config_hash(host, room)
|
22
|
+
Dickburt::Server.run(config)
|
23
|
+
rescue ::StandardError => se
|
24
|
+
raise Trollop::CommandlineError, se.message
|
25
|
+
end
|
26
|
+
end
|
data/dickburt.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "dickburt/version"
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "dickburt"
|
6
|
+
s.version = Dickburt::VERSION
|
7
|
+
s.authors = ["Tyler Montgomery"]
|
8
|
+
s.email = ["tyler@everlater.com"]
|
9
|
+
s.homepage = "http://github.com/ubermajestix/dickburt"
|
10
|
+
s.summary = %q{Campfire bot}
|
11
|
+
s.description = %q{Dickburt finds pugs}
|
12
|
+
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
|
18
|
+
s.add_dependency "eventmachine" , "~> 0.12.10"
|
19
|
+
s.add_dependency "patron" , "~> 0.4.11"
|
20
|
+
s.add_dependency "logging" , "~> 1.5.1"
|
21
|
+
s.add_dependency "trollop" , "~> 1.16.2"
|
22
|
+
s.add_dependency 'twitter-stream' , "~> 0.1.14"
|
23
|
+
s.add_dependency 'json' , "~> 1.5.1"
|
24
|
+
s.add_dependency "addressable" , "~> 2.2.6"
|
25
|
+
s.add_dependency 'map' , "~> 4.3.0"
|
26
|
+
|
27
|
+
s.add_development_dependency "minitest", "~> 2.2.2"
|
28
|
+
end
|
data/lib/dickburt.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'dickburt/version'
|
2
|
+
require 'twitter/json_stream'
|
3
|
+
require 'patron'
|
4
|
+
require 'json'
|
5
|
+
require "addressable/uri"
|
6
|
+
require 'map'
|
7
|
+
require 'logging'
|
8
|
+
require 'fileutils'
|
9
|
+
|
10
|
+
module Dickburt
|
11
|
+
require 'dickburt/bot'
|
12
|
+
require 'dickburt/campfire'
|
13
|
+
require 'dickburt/config'
|
14
|
+
require 'dickburt/logger'
|
15
|
+
require 'dickburt/message'
|
16
|
+
require 'dickburt/response'
|
17
|
+
require 'dickburt/retryable'
|
18
|
+
require 'dickburt/room'
|
19
|
+
require 'dickburt/server'
|
20
|
+
require 'dickburt/user'
|
21
|
+
class Error < StandardError; end;
|
22
|
+
def self.logger
|
23
|
+
@logger = Logging::Logger[self.name]
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
|
data/lib/dickburt/bot.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
class Dickburt::Bot
|
2
|
+
attr_accessor :message
|
3
|
+
attr_accessor :user
|
4
|
+
attr_accessor :room
|
5
|
+
def initialize(message, user, room)
|
6
|
+
@message = message
|
7
|
+
@user = user
|
8
|
+
@room = room
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.used_images
|
12
|
+
@used_images ||= {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.image_page
|
16
|
+
@image_page ||= {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.process(message, room, campfire)
|
20
|
+
if message[:type] == 'TextMessage' && message.body.match(/\bdickburt\b/i)
|
21
|
+
dickburt = Dickburt::Bot.new(message, room.find_user(message.user_id), room)
|
22
|
+
command = dickburt.parse_command
|
23
|
+
args = dickburt.parse_args
|
24
|
+
if command
|
25
|
+
response = args ? dickburt.send(command, args) : dickburt.send(command)
|
26
|
+
room.speak(response)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def commands
|
32
|
+
@commands ||= %w(imageme beerme hi fuckyeah)
|
33
|
+
end
|
34
|
+
|
35
|
+
def commands_regex
|
36
|
+
Regexp.union(commands.map{|c| /\b#{c}\b/})
|
37
|
+
end
|
38
|
+
|
39
|
+
def parse_command
|
40
|
+
command = commands.detect{|c| message.body.downcase.match(/\b#{c}\b/)}
|
41
|
+
command = 'whatup' if message[:type] == 'EnterMessage'
|
42
|
+
command
|
43
|
+
end
|
44
|
+
|
45
|
+
def parse_args
|
46
|
+
reg = Regexp.union(/dickburt:?/, commands_regex)
|
47
|
+
args = message.body.downcase.gsub(reg,'').strip
|
48
|
+
args.empty? ? nil : args
|
49
|
+
end
|
50
|
+
|
51
|
+
def whatup
|
52
|
+
Dickburt::Response.new("whatup #{user.name}", 'Text')
|
53
|
+
end
|
54
|
+
|
55
|
+
def fuckyeah
|
56
|
+
Dickburt::Response.new('FUCKYEAH BUDDIE!', 'Text')
|
57
|
+
end
|
58
|
+
|
59
|
+
def imageme(query='funny pugs')
|
60
|
+
image = google_image(query)
|
61
|
+
if image
|
62
|
+
Dickburt::Response.new(image, 'Upload')
|
63
|
+
else
|
64
|
+
Dickburt::Response.new("couldn't find any pics of #{query}", 'Text')
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def beerme
|
69
|
+
Dickburt::Response.new('http://f.cl.ly/items/333i290f1x2N330u303N/beerme.png', 'Upload')
|
70
|
+
end
|
71
|
+
|
72
|
+
def hi
|
73
|
+
whatup if rand(5) == 4
|
74
|
+
fuckyeah if rand(10) == 4
|
75
|
+
Dickburt::Response.new("Hi #{user.name}", 'Text')
|
76
|
+
end
|
77
|
+
|
78
|
+
def google_image(query='funny pugs', page=0)
|
79
|
+
@search_server ||= Patron::Session.new
|
80
|
+
@search_server.base_url = "https://ajax.googleapis.com/ajax/services/search"
|
81
|
+
Dickburt::Bot.used_images[query] ||= []
|
82
|
+
page = Dickburt::Bot.image_page[query].to_i + 1 if Dickburt::Bot.image_page[query].to_i > page
|
83
|
+
Dickburt::Bot.image_page[query] = page
|
84
|
+
uri = Addressable::URI.new
|
85
|
+
uri.query_values = {:v => "1.0", :q => query, :rsz=>'8', :start=>page.to_s}
|
86
|
+
puts "finding #{query} on page #{page}"
|
87
|
+
response = @search_server.get("/images?#{uri.query}")
|
88
|
+
if response.status < 400
|
89
|
+
body = JSON.parse(response.body)
|
90
|
+
if body["responseData"]
|
91
|
+
results = JSON.parse(response.body)["responseData"]["results"]
|
92
|
+
results.each_with_index do |image, i|
|
93
|
+
image = Map.new(image)
|
94
|
+
# check to see if we've seen this image for this query
|
95
|
+
next if Dickburt::Bot.used_images[query].include?(image.imageId)
|
96
|
+
Dickburt::Bot.used_images[query] << image.imageId
|
97
|
+
# check to make sure the image doesn't 404
|
98
|
+
exists = image_exists?(image.url)
|
99
|
+
next if exists == false
|
100
|
+
# return the image
|
101
|
+
@image_url = image.url
|
102
|
+
break # cause we found an image
|
103
|
+
end
|
104
|
+
else
|
105
|
+
@error= true
|
106
|
+
end
|
107
|
+
else
|
108
|
+
@error = true
|
109
|
+
puts "="*45
|
110
|
+
puts response.status
|
111
|
+
puts response.body.inspect
|
112
|
+
puts "="*45
|
113
|
+
end
|
114
|
+
puts @image_url.inspect
|
115
|
+
puts @error.inspect
|
116
|
+
# Keep going to the next page of results unless we got an image or blowzed up.
|
117
|
+
google_image(query, page+=1) unless @image_url or @error
|
118
|
+
@image_url
|
119
|
+
end
|
120
|
+
|
121
|
+
def image_exists?(url)
|
122
|
+
begin
|
123
|
+
image = Patron::Session.new
|
124
|
+
split_url = url.split("/",4)
|
125
|
+
image_url = split_url.pop
|
126
|
+
image.base_url = split_url.join("/")
|
127
|
+
response = image.head("/#{image_url}")
|
128
|
+
response.status < 400
|
129
|
+
rescue Patron::ConnectionFailed, Patron::ConnectionFailed
|
130
|
+
puts "="*45
|
131
|
+
puts "DNE!"
|
132
|
+
puts "="*45
|
133
|
+
false
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class Dickburt::Campfire
|
2
|
+
attr_accessor :token
|
3
|
+
attr_accessor :host
|
4
|
+
|
5
|
+
class Error < StandardError; end;
|
6
|
+
|
7
|
+
def initialize(args={})
|
8
|
+
@token = args[:token]
|
9
|
+
@host = "https://#{args[:host]}.campfirenow.com"
|
10
|
+
end
|
11
|
+
|
12
|
+
def http
|
13
|
+
@http ||= Patron::Session.new
|
14
|
+
@http.base_url = host
|
15
|
+
@http.headers["Content-Type"] = "application/json"
|
16
|
+
@http.headers['User-Agent'] = "Dickburt #{Dickburt::VERSION}"
|
17
|
+
@http.username = token
|
18
|
+
@http.password = "x"
|
19
|
+
@http.connect_timeout = 6000
|
20
|
+
@http.timeout = 6000
|
21
|
+
@http
|
22
|
+
end
|
23
|
+
|
24
|
+
def rooms
|
25
|
+
return @rooms if @rooms
|
26
|
+
response = http.get("/rooms.json")
|
27
|
+
@rooms = []
|
28
|
+
if response.status < 400
|
29
|
+
@rooms = JSON.parse(response.body)['rooms']
|
30
|
+
@rooms.collect! do |room|
|
31
|
+
Dickburt::Room.new(room, self)
|
32
|
+
end
|
33
|
+
else
|
34
|
+
raise Dickburt::Campfire::Error, response.status.to_s + ": " + response.body
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Store the campfire api token b/c I'm sick of looking that shit up.
|
2
|
+
# I know the host and room I want to access, but not the token, so if the token
|
3
|
+
# is provided, store it - tokens are uniq by host - so store it per host.
|
4
|
+
# Provides a nice config hash that we build up from the command line to hand off to Dickburt::Server.
|
5
|
+
|
6
|
+
module Dickburt
|
7
|
+
module Config
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
# Provides a nice config hash that we build up from the command line to hand off to Dickburt::Server.
|
12
|
+
# This is only ever called bin/dickburt. That script raises errors for missing host and room, so its
|
13
|
+
# a bit redundant redundant to check that here.
|
14
|
+
def config_hash(host, room)
|
15
|
+
token = config[host.to_sym][:token] if config[host.to_sym]
|
16
|
+
unless token
|
17
|
+
raise Dickburt::Error, "You need to provide an api token.\n Just add: --token yourtokenfromcampfire to the command line args.\n The token will get stored in ~/.dickburt/config.yml for next time so you don't have to remember it"
|
18
|
+
end
|
19
|
+
{ host: host,
|
20
|
+
token: token,
|
21
|
+
room: room.downcase.gsub(/\s/,'_') }
|
22
|
+
end
|
23
|
+
|
24
|
+
# Public: Stores the given token for the given host.
|
25
|
+
# Will not raise an error if the token is not given, leaves that up to bin/dickburt.
|
26
|
+
def set_token(host, token=nil)
|
27
|
+
if token
|
28
|
+
config[host.to_sym] = {:token => token}
|
29
|
+
write
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Reads the config, or gives you an empty hash.
|
34
|
+
def config
|
35
|
+
ensure_config_exists
|
36
|
+
@config ||= (YAML.load_file(config_file) || {})
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# Make sure the config dir and config file are on disk.
|
42
|
+
def ensure_config_exists
|
43
|
+
FileUtils.mkdir(config_dir) unless Dir.exists?(config_dir)
|
44
|
+
FileUtils.touch(config_file) unless File.exists?(config_file)
|
45
|
+
end
|
46
|
+
|
47
|
+
def config_file
|
48
|
+
"#{config_dir}/config.yml"
|
49
|
+
end
|
50
|
+
|
51
|
+
def config_dir
|
52
|
+
"#{Dir.home}/.dickburt"
|
53
|
+
end
|
54
|
+
|
55
|
+
# Writes the @config as yaml to disk.
|
56
|
+
def write
|
57
|
+
ensure_config_exists
|
58
|
+
File.open(config_file, "wb"){|f| f.write @config.to_yaml}
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Dickburt::Message < Map
|
2
|
+
|
3
|
+
attr_accessor :command
|
4
|
+
attr_accessor :args
|
5
|
+
|
6
|
+
def initialize(json)
|
7
|
+
super(JSON.parse(json))
|
8
|
+
puts self.inspect
|
9
|
+
puts "="*45
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
def process_command
|
14
|
+
return unless @command
|
15
|
+
# TODO make dickburt aware of users so he can respond to them.
|
16
|
+
# I really don't like this setup of not having instances of the bot.
|
17
|
+
# If i got an instance I could save things to it, like the user and message object
|
18
|
+
# Then it wouldn't be as weird to process the command here on the message object
|
19
|
+
@args ? Dickburt::Bot.send(@command, @args) : Dickburt::Bot.send(@command)
|
20
|
+
end
|
21
|
+
|
22
|
+
def user
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Dickburt::Response
|
2
|
+
attr_accessor :message_type
|
3
|
+
attr_accessor :body
|
4
|
+
|
5
|
+
def initialize(body, message_type)
|
6
|
+
@message_type = message_type
|
7
|
+
@body = body
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_campfire_hash
|
11
|
+
{:message => {:body => @body, :type => @message_type + "Message"}}
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_json
|
15
|
+
self.to_campfire_hash.to_json
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Dickburt
|
2
|
+
module Retryable
|
3
|
+
# Options:
|
4
|
+
# * :tries - Number of retries to perform. Defaults to 1.
|
5
|
+
# * :on - The Exception on which a retry will be performed. Defaults to Exception, which retries on any Exception.
|
6
|
+
#
|
7
|
+
# Example
|
8
|
+
# =======
|
9
|
+
# retryable(:tries => 1, :on => OpenURI::HTTPError) do
|
10
|
+
# # your code here
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
def retryable(options = {}, &block)
|
14
|
+
opts = { :tries => 1, :on => Exception }.merge(options)
|
15
|
+
|
16
|
+
retry_exception, retries = opts[:on], opts[:tries]
|
17
|
+
|
18
|
+
begin
|
19
|
+
return yield
|
20
|
+
rescue retry_exception
|
21
|
+
puts "="*45
|
22
|
+
puts "#{retry_exception} raised. Retrying #{retries - 1} more times"
|
23
|
+
puts "="*45
|
24
|
+
retry if (retries -= 1) > 0
|
25
|
+
end
|
26
|
+
|
27
|
+
yield
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Dickburt
|
2
|
+
class Room < Map
|
3
|
+
attr_accessor :campfire
|
4
|
+
attr_accessor :response
|
5
|
+
include Logger
|
6
|
+
def initialize(room, campfire)
|
7
|
+
super(room)
|
8
|
+
@campfire = campfire
|
9
|
+
end
|
10
|
+
|
11
|
+
def response=(response)
|
12
|
+
puts "="*45
|
13
|
+
puts response.status
|
14
|
+
puts "="*45
|
15
|
+
if response.status >= 400
|
16
|
+
logger.error JSON.parse(speak.body).inspect
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def speak(response)
|
21
|
+
response = @campfire.http.post("/room/#{id}/speak.json", response.to_json)
|
22
|
+
end
|
23
|
+
|
24
|
+
def join
|
25
|
+
response = @campfire.http.post("/room/#{id}/join.json", {})
|
26
|
+
end
|
27
|
+
|
28
|
+
def leave
|
29
|
+
response = @campfire.http.post("/room/#{id}/leave.json", {})
|
30
|
+
end
|
31
|
+
|
32
|
+
def stream
|
33
|
+
Twitter::JSONStream.connect(
|
34
|
+
:path => "/room/#{id}/live.json",
|
35
|
+
:host => 'streaming.campfirenow.com',
|
36
|
+
:auth => "#{campfire.token}:x"
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns an Array of current users for the room.
|
41
|
+
def users
|
42
|
+
response = @campfire.http.get("/room/#{id}.json")
|
43
|
+
room = Map.new(JSON.parse(response.body)["room"])
|
44
|
+
room.users
|
45
|
+
end
|
46
|
+
|
47
|
+
# Looks up the user by user_id for the current users in the room
|
48
|
+
#
|
49
|
+
# user_id - String 37signals user id
|
50
|
+
#
|
51
|
+
# Returns a Dickburt::User if it finds that user in the room
|
52
|
+
# Raises an error if they can't be found.
|
53
|
+
def find_user(user_id)
|
54
|
+
user = users.detect{|u| u.id == user_id}
|
55
|
+
raise Dickburt::Error, "user #{user_id} not found" unless user
|
56
|
+
User.new(user.to_json, campfire)
|
57
|
+
end
|
58
|
+
|
59
|
+
def handle_failure
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Dickburt
|
2
|
+
module Server
|
3
|
+
class << self
|
4
|
+
include Logger
|
5
|
+
include Retryable
|
6
|
+
end
|
7
|
+
def self.run(args={})
|
8
|
+
logger.info "boom!"
|
9
|
+
EventMachine::run do
|
10
|
+
retryable(:tries => 5, :on => Patron::TimeoutError) do
|
11
|
+
@campfire = Dickburt::Campfire.new(args)
|
12
|
+
puts "="*45
|
13
|
+
puts "Connected to #{@campfire.host} with token #{@campfire.token}"
|
14
|
+
puts "="*45
|
15
|
+
@room = @campfire.rooms.detect{|r| r.name.downcase.gsub(/\s/,'_') == args[:room].downcase.gsub(/\s/,'_')}
|
16
|
+
raise Dickburt::Error, "Could not find a campfire room that matches \"#{args[:room]}\"" unless @room
|
17
|
+
@room.join
|
18
|
+
puts "Ready for messages..."
|
19
|
+
puts "="*45
|
20
|
+
end
|
21
|
+
stream = @room.stream
|
22
|
+
|
23
|
+
stream.each_item do |item|
|
24
|
+
message = Dickburt::Message.new(item)
|
25
|
+
puts message.inspect
|
26
|
+
Dickburt::Bot.process(message, @room, @campfire)
|
27
|
+
end
|
28
|
+
|
29
|
+
stream.on_error do |message|
|
30
|
+
puts "ERROR: #{message.inspect}"
|
31
|
+
end
|
32
|
+
|
33
|
+
stream.on_max_reconnects do |timeout, retries|
|
34
|
+
puts "Tried #{retries} times to connect."
|
35
|
+
exit
|
36
|
+
end
|
37
|
+
|
38
|
+
trap("INT") do
|
39
|
+
@room.leave if @room
|
40
|
+
EM.stop
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Dickburt::Bot do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@message = Dickburt::Message.new({type: "TextMessage", body: "dickburt beerme"}.to_json)
|
7
|
+
@user = Dickburt::User.new({name: "Human"}.to_json, campfire )
|
8
|
+
@room = Dickburt::Room.new({name: 'Campfire Room'}, campfire)
|
9
|
+
@dickburt = Dickburt::Bot.new(@message, @user, @room)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "must deliver beer" do
|
13
|
+
@dickburt.must_respond_to :beerme
|
14
|
+
@dickburt.beerme.must_be_kind_of Dickburt::Response
|
15
|
+
end
|
16
|
+
|
17
|
+
it "must deliver images" do
|
18
|
+
skip "use vcr to mock web response"
|
19
|
+
@dickburt.must_respond_to :imageme
|
20
|
+
@dickburt.imageme.must_be_kind_of Dickburt::Response
|
21
|
+
end
|
22
|
+
|
23
|
+
context ".parse_args" do
|
24
|
+
it "must parse arguments from a message" do
|
25
|
+
message = Dickburt::Message.new({type: "TextMessage", body: "dickburt imageme funny hipsters"}.to_json)
|
26
|
+
dickburt = Dickburt::Bot.new(message, @user, @room)
|
27
|
+
dickburt.parse_args.must_equal 'funny hipsters'
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
it "must return nil if no args exist" do
|
32
|
+
message = Dickburt::Message.new({type: "TextMessage", body: "dickburt imageme"}.to_json)
|
33
|
+
dickburt = Dickburt::Bot.new(message, @user, @room)
|
34
|
+
dickburt.parse_args.must_be_nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'imageme' do
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Dickburt::Config do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
FileUtils.rm_rf("#{Dir.home}/.dickburt/config.yml", :noop=>true)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should create the ~/.dickburt dir" do
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should write the config when we add a token" do
|
13
|
+
Dickburt::Config.set_token("shabam", 'boooooom')
|
14
|
+
Dir.exists?("#{Dir.home}/.dickburt").must_equal true
|
15
|
+
File.exists?("#{Dir.home}/.dickburt/config.yml").must_equal true
|
16
|
+
Dickburt::Config.config.keys.must_include :shabam
|
17
|
+
Dickburt::Config.config[:shabam].must_equal ({:token => "boooooom"})
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should read config" do
|
21
|
+
Dickburt::Config.config.must_be_kind_of Hash
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe Dickburt::Response do
|
3
|
+
before :each do
|
4
|
+
@response = Dickburt::Response.new("beer", "Text")
|
5
|
+
end
|
6
|
+
|
7
|
+
it "should give me a hash for campfire" do
|
8
|
+
@response.to_campfire_hash.must_be_kind_of Hash
|
9
|
+
@response.to_campfire_hash.keys.must_include :message
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should respond to to_json with the appropriate hash" do
|
13
|
+
JSON.parse(@response.to_json).keys.must_include "message"
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Dickburt::Room do
|
4
|
+
context "handling failure" do
|
5
|
+
before :each do
|
6
|
+
@response = Map.new(:status => 401, :body => "you fucked up")
|
7
|
+
@room = Dickburt::Room.new({name: 'Campfire Room'}, campfire)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "handles failure if the response is bad" do
|
11
|
+
skip
|
12
|
+
@room.response = @response
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require "bundler"
|
2
|
+
|
3
|
+
Bundler.setup :default, :development
|
4
|
+
|
5
|
+
require "minitest/spec"
|
6
|
+
require "minitest/autorun"
|
7
|
+
require "minitest/mock"
|
8
|
+
|
9
|
+
require 'fabrication'
|
10
|
+
require 'ffaker'
|
11
|
+
|
12
|
+
require "dickburt"
|
13
|
+
|
14
|
+
alias :context :describe
|
15
|
+
|
16
|
+
def user_mock
|
17
|
+
mock = MiniTest::Mock.new
|
18
|
+
mock.expect("name", "Human")
|
19
|
+
end
|
20
|
+
|
21
|
+
def campfire
|
22
|
+
@campfire ||= Dickburt::Campfire.new(:host=>"everlater")
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dickburt
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.5
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tyler Montgomery
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-09-06 00:00:00.000000000 -06:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: eventmachine
|
17
|
+
requirement: &2164638520 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ~>
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.12.10
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *2164638520
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: patron
|
28
|
+
requirement: &2164638020 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.4.11
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *2164638020
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: logging
|
39
|
+
requirement: &2164637560 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ~>
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: 1.5.1
|
45
|
+
type: :runtime
|
46
|
+
prerelease: false
|
47
|
+
version_requirements: *2164637560
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: trollop
|
50
|
+
requirement: &2164637100 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ~>
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 1.16.2
|
56
|
+
type: :runtime
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: *2164637100
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: twitter-stream
|
61
|
+
requirement: &2164636640 !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ~>
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 0.1.14
|
67
|
+
type: :runtime
|
68
|
+
prerelease: false
|
69
|
+
version_requirements: *2164636640
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: json
|
72
|
+
requirement: &2164636180 !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 1.5.1
|
78
|
+
type: :runtime
|
79
|
+
prerelease: false
|
80
|
+
version_requirements: *2164636180
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: addressable
|
83
|
+
requirement: &2164635720 !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ~>
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: 2.2.6
|
89
|
+
type: :runtime
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: *2164635720
|
92
|
+
- !ruby/object:Gem::Dependency
|
93
|
+
name: map
|
94
|
+
requirement: &2164562540 !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ~>
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: 4.3.0
|
100
|
+
type: :runtime
|
101
|
+
prerelease: false
|
102
|
+
version_requirements: *2164562540
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: minitest
|
105
|
+
requirement: &2164562080 !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 2.2.2
|
111
|
+
type: :development
|
112
|
+
prerelease: false
|
113
|
+
version_requirements: *2164562080
|
114
|
+
description: Dickburt finds pugs
|
115
|
+
email:
|
116
|
+
- tyler@everlater.com
|
117
|
+
executables:
|
118
|
+
- dickburt
|
119
|
+
extensions: []
|
120
|
+
extra_rdoc_files: []
|
121
|
+
files:
|
122
|
+
- .gitignore
|
123
|
+
- .rvmrc
|
124
|
+
- Gemfile
|
125
|
+
- Gemfile.lock
|
126
|
+
- Guardfile
|
127
|
+
- README.md
|
128
|
+
- Rakefile
|
129
|
+
- bin/dickburt
|
130
|
+
- dickburt.gemspec
|
131
|
+
- lib/dickburt.rb
|
132
|
+
- lib/dickburt/bot.rb
|
133
|
+
- lib/dickburt/campfire.rb
|
134
|
+
- lib/dickburt/config.rb
|
135
|
+
- lib/dickburt/logger.rb
|
136
|
+
- lib/dickburt/message.rb
|
137
|
+
- lib/dickburt/response.rb
|
138
|
+
- lib/dickburt/retryable.rb
|
139
|
+
- lib/dickburt/room.rb
|
140
|
+
- lib/dickburt/server.rb
|
141
|
+
- lib/dickburt/user.rb
|
142
|
+
- lib/dickburt/version.rb
|
143
|
+
- spec/dickburt/bot_spec.rb
|
144
|
+
- spec/dickburt/campfire_spec.rb
|
145
|
+
- spec/dickburt/config_spec.rb
|
146
|
+
- spec/dickburt/response_spec.rb
|
147
|
+
- spec/dickburt/room_spec.rb
|
148
|
+
- spec/dickburt/server_spec.rb
|
149
|
+
- spec/spec_helper.rb
|
150
|
+
has_rdoc: true
|
151
|
+
homepage: http://github.com/ubermajestix/dickburt
|
152
|
+
licenses: []
|
153
|
+
post_install_message:
|
154
|
+
rdoc_options: []
|
155
|
+
require_paths:
|
156
|
+
- lib
|
157
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
158
|
+
none: false
|
159
|
+
requirements:
|
160
|
+
- - ! '>='
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: '0'
|
163
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
164
|
+
none: false
|
165
|
+
requirements:
|
166
|
+
- - ! '>='
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0'
|
169
|
+
requirements: []
|
170
|
+
rubyforge_project:
|
171
|
+
rubygems_version: 1.6.2
|
172
|
+
signing_key:
|
173
|
+
specification_version: 3
|
174
|
+
summary: Campfire bot
|
175
|
+
test_files:
|
176
|
+
- spec/dickburt/bot_spec.rb
|
177
|
+
- spec/dickburt/campfire_spec.rb
|
178
|
+
- spec/dickburt/config_spec.rb
|
179
|
+
- spec/dickburt/response_spec.rb
|
180
|
+
- spec/dickburt/room_spec.rb
|
181
|
+
- spec/dickburt/server_spec.rb
|
182
|
+
- spec/spec_helper.rb
|