wesabot 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +3 -0
- data/LICENSE +20 -0
- data/README.md +182 -0
- data/Rakefile +13 -0
- data/bin/wesabot +111 -0
- data/config/wesabot.yml.sample +4 -0
- data/lib/campfire/bot.rb +53 -0
- data/lib/campfire/configuration.rb +65 -0
- data/lib/campfire/message.rb +102 -0
- data/lib/campfire/polling_bot/plugin.rb +162 -0
- data/lib/campfire/polling_bot/plugins/airbrake/.gitignore +1 -0
- data/lib/campfire/polling_bot/plugins/airbrake/airbrake/api.rb +48 -0
- data/lib/campfire/polling_bot/plugins/airbrake/airbrake/error.rb +51 -0
- data/lib/campfire/polling_bot/plugins/airbrake/airbrake_plugin.rb +63 -0
- data/lib/campfire/polling_bot/plugins/airbrake/airbrake_plugin.yml.sample +3 -0
- data/lib/campfire/polling_bot/plugins/bookmark/bookmark.rb +15 -0
- data/lib/campfire/polling_bot/plugins/bookmark/bookmark_plugin.rb +89 -0
- data/lib/campfire/polling_bot/plugins/debug/debug_plugin.rb +23 -0
- data/lib/campfire/polling_bot/plugins/deploy/deploy_plugin.rb +155 -0
- data/lib/campfire/polling_bot/plugins/deploy/deploy_plugin.yml.sample +9 -0
- data/lib/campfire/polling_bot/plugins/deploy/spec/deploy_plugin_spec.rb +153 -0
- data/lib/campfire/polling_bot/plugins/greeting/greeting_plugin.rb +146 -0
- data/lib/campfire/polling_bot/plugins/greeting/greeting_setting.rb +19 -0
- data/lib/campfire/polling_bot/plugins/greeting/spec/greeting_plugin_spec.rb +51 -0
- data/lib/campfire/polling_bot/plugins/greeting/spec/greeting_setting_spec.rb +61 -0
- data/lib/campfire/polling_bot/plugins/help/help_plugin.rb +53 -0
- data/lib/campfire/polling_bot/plugins/history/history_plugin.rb +34 -0
- data/lib/campfire/polling_bot/plugins/image_search/displayed_image.rb +8 -0
- data/lib/campfire/polling_bot/plugins/image_search/image_search_plugin.rb +106 -0
- data/lib/campfire/polling_bot/plugins/interjector/interjector_plugin.rb +58 -0
- data/lib/campfire/polling_bot/plugins/kibitz/kibitz_plugin.rb +90 -0
- data/lib/campfire/polling_bot/plugins/kibitz/spec/kibitz_plugin_spec.rb +56 -0
- data/lib/campfire/polling_bot/plugins/plugin.db +0 -0
- data/lib/campfire/polling_bot/plugins/reload/reload_plugin.rb +24 -0
- data/lib/campfire/polling_bot/plugins/remind_me/remind_me_plugin.rb +149 -0
- data/lib/campfire/polling_bot/plugins/remind_me/reminder.rb +8 -0
- data/lib/campfire/polling_bot/plugins/shared/message.rb +37 -0
- data/lib/campfire/polling_bot/plugins/shared/user.rb +32 -0
- data/lib/campfire/polling_bot/plugins/sms/sms_plugin.rb +88 -0
- data/lib/campfire/polling_bot/plugins/sms/sms_setting.rb +7 -0
- data/lib/campfire/polling_bot/plugins/time/time_plugin.rb +17 -0
- data/lib/campfire/polling_bot/plugins/tweet/tweet.rb +52 -0
- data/lib/campfire/polling_bot/plugins/tweet/tweet_plugin.rb +117 -0
- data/lib/campfire/polling_bot/plugins/tweet/tweet_plugin.yml.sample +4 -0
- data/lib/campfire/polling_bot/plugins/twitter_search/twitter_search_plugin.rb +58 -0
- data/lib/campfire/polling_bot.rb +125 -0
- data/lib/campfire/sample_plugin.rb +56 -0
- data/lib/campfire/version.rb +3 -0
- data/lib/wesabot.rb +3 -0
- data/spec/.gitignore +1 -0
- data/spec/polling_bot_spec.rb +23 -0
- data/spec/spec_helper.rb +190 -0
- data/wesabot.gemspec +55 -0
- metadata +336 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
|
3
|
+
# plugin to search twitter
|
4
|
+
class TwitterSearchPlugin < Campfire::PollingBot::Plugin
|
5
|
+
priority 1
|
6
|
+
accepts :text_message, :addressed_to_me => true
|
7
|
+
|
8
|
+
def process(message)
|
9
|
+
searched = false
|
10
|
+
|
11
|
+
case message.command
|
12
|
+
when /(?:(?:what(?:'s)? (?:(?:are )?people|(?:is |does )?every(?:one|body)) (?:saying|tweeting|think) about)|what's the word on)\s+(?:the )?(.*?)[.?!]?\s*$/i
|
13
|
+
subject = $1
|
14
|
+
tweets = search_twitter(subject)
|
15
|
+
if tweets.any?
|
16
|
+
tweets.each {|t| bot.tweet(t) }
|
17
|
+
else
|
18
|
+
bot.say("Couldn't find anything for \"#{subject}\"")
|
19
|
+
end
|
20
|
+
return HALT
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# return array of available commands and descriptions
|
25
|
+
def help
|
26
|
+
[
|
27
|
+
["what are (people|is everyone) saying about <subject>", "search twitter for tweets on <subject>"],
|
28
|
+
["what's the word on <subject>", "search twitter for tweets on <subject>"],
|
29
|
+
]
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# construct a twitter url from the given response json
|
35
|
+
def twitter_url(json)
|
36
|
+
"http://twitter.com/#{json['from_user']}/status/#{json['id']}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def search_twitter(subject)
|
40
|
+
tweets = []
|
41
|
+
res = HTTParty.get(
|
42
|
+
"http://search.twitter.com/search.json",
|
43
|
+
:query => { :q => subject, :result_type => "mixed" },
|
44
|
+
# other possible result types include "popular" and "recent"
|
45
|
+
:headers => {'User-Agent' => 'wesabot/1.0 github-hackarts-wesabot'})
|
46
|
+
case res.code
|
47
|
+
when 200
|
48
|
+
tweets = res["results"].first(5).map {|r| twitter_url(r) }
|
49
|
+
when 400, 420
|
50
|
+
bot.say("Sorry, we've hit the Twitter rate limit for searches.")
|
51
|
+
else
|
52
|
+
bot.say("Hmm...didn't work. Got this response:")
|
53
|
+
bot.paste("#{res.code} (#{res.message})\n#{res.body}")
|
54
|
+
end
|
55
|
+
|
56
|
+
return tweets
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# PollingBot - a bot that polls the room for messages
|
2
|
+
require 'campfire/bot'
|
3
|
+
require 'campfire/message'
|
4
|
+
|
5
|
+
require 'firering'
|
6
|
+
|
7
|
+
module Campfire
|
8
|
+
class PollingBot < Bot
|
9
|
+
require 'campfire/polling_bot/plugin'
|
10
|
+
attr_accessor :plugins
|
11
|
+
HEARTBEAT_INTERVAL = 3 # seconds
|
12
|
+
|
13
|
+
def initialize(config)
|
14
|
+
# load plugin queue, sorting by priority
|
15
|
+
super
|
16
|
+
self.plugins = Plugin.load_all(self)
|
17
|
+
end
|
18
|
+
|
19
|
+
# main event loop
|
20
|
+
def run
|
21
|
+
# set up a heartbeat thread for plugins that want them
|
22
|
+
Thread.new do
|
23
|
+
loop do
|
24
|
+
plugins.each {|p| p.heartbeat if p.respond_to?(:heartbeat)}
|
25
|
+
sleep HEARTBEAT_INTERVAL
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
host = "https://#{config.subdomain}.campfirenow.com"
|
30
|
+
conn = Firering::Connection.new(host) do |c|
|
31
|
+
c.token = config.api_token
|
32
|
+
c.logger = logger
|
33
|
+
c.retry_delay = 2
|
34
|
+
end
|
35
|
+
|
36
|
+
EM.run do
|
37
|
+
conn.room(room.id) do |room|
|
38
|
+
room.stream do |data|
|
39
|
+
|
40
|
+
begin
|
41
|
+
klass = Campfire.const_get(data.type)
|
42
|
+
message = klass.new(data)
|
43
|
+
|
44
|
+
if data.from_user?
|
45
|
+
data.user do |user|
|
46
|
+
dbuser = User.first(:campfire_id => user.id)
|
47
|
+
|
48
|
+
if dbuser.nil?
|
49
|
+
dbuser = User.create(
|
50
|
+
:campfire_id => user.id,
|
51
|
+
:name => user.name
|
52
|
+
)
|
53
|
+
else
|
54
|
+
dbuser.update(:name => user.name)
|
55
|
+
end
|
56
|
+
|
57
|
+
message.user = dbuser
|
58
|
+
process(message)
|
59
|
+
end
|
60
|
+
else
|
61
|
+
process(message)
|
62
|
+
end
|
63
|
+
|
64
|
+
rescue => e
|
65
|
+
log_error(e)
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
trap("INT") { EM.stop; raise SystemExit }
|
72
|
+
end
|
73
|
+
|
74
|
+
rescue Exception => e # leave the room if we crash
|
75
|
+
if e.kind_of?(SystemExit)
|
76
|
+
room.leave
|
77
|
+
exit 0
|
78
|
+
else
|
79
|
+
log_error(e)
|
80
|
+
room.leave
|
81
|
+
exit 1
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def process(message)
|
86
|
+
logger.debug "processing #{message} (#{message.person} - #{message.body})"
|
87
|
+
|
88
|
+
# ignore messages from ourself
|
89
|
+
return if [message.person, message.person_full_name].include? self.name
|
90
|
+
|
91
|
+
plugins.each do |plugin|
|
92
|
+
begin
|
93
|
+
if plugin.accepts?(message)
|
94
|
+
logger.debug "sending to plugin #{plugin} (priority #{plugin.priority})"
|
95
|
+
status = plugin.process(message)
|
96
|
+
if status == Plugin::HALT
|
97
|
+
logger.debug "plugin chain halted"
|
98
|
+
break
|
99
|
+
end
|
100
|
+
end
|
101
|
+
rescue Exception => e
|
102
|
+
say("Oops, #{plugin.class} threw an exception. Enable debugging to see it.") unless debug
|
103
|
+
log_error(e)
|
104
|
+
break
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
logger.debug "done processing #{message}"
|
109
|
+
end
|
110
|
+
|
111
|
+
# determine if a message is addressed to the bot. if so, store the command in the message
|
112
|
+
def addressed_to_me?(message)
|
113
|
+
m = message.body.match(/^#{name}(?:[,:]\s*|\s+)(.*)/i)
|
114
|
+
m ||= message.body.match(/^\s*(.*?)(?:,\s+)?\b#{name}[.!?\s]*$/i)
|
115
|
+
message.command = m[1] if m
|
116
|
+
end
|
117
|
+
|
118
|
+
def log_error(e)
|
119
|
+
msg = "Exception: #{e.class}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
|
120
|
+
logger.error(msg)
|
121
|
+
paste(msg) if debug
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# Sample Campfire plugin. Plugins must be placed in the plugins directory in order to be loaded.
|
2
|
+
class SamplePlugin < Campfire::PollingBot::Plugin
|
3
|
+
# accept - specify which kinds of messages this plugin accepts. Put each type on its own line.
|
4
|
+
# You may optionally set :addressed_to_me => true to get only messages addressed to the bot.
|
5
|
+
# For example,
|
6
|
+
# accept :text_message, :addressed_to_me => true
|
7
|
+
# Will only accept text messages that are in the form "<bot name>, ..." or "... <bot name>".
|
8
|
+
# The body of the message minus the bot name will be returned by the 'command' method of the
|
9
|
+
# message.
|
10
|
+
#
|
11
|
+
# Message types are:
|
12
|
+
# - :all - all messages
|
13
|
+
# - :text_message - normal user text message
|
14
|
+
# - :paste_message - a paste
|
15
|
+
# - :enter_message - sent when a user enters the room
|
16
|
+
# - :leave_message - sent when a user leaves the room
|
17
|
+
# - :kick_message - sent when a user times out from inactivity
|
18
|
+
# - :lock_message - sent when the room is locked
|
19
|
+
# - :unlock_message - sent when the room is unlocked
|
20
|
+
# - :allow_guests_message - sent when guest access is turned on
|
21
|
+
# - :disallow_guests_message - sent when guest access is turned off
|
22
|
+
# - :topic_change_message - sent when the room's topic is changed
|
23
|
+
|
24
|
+
accepts :text_message, :addressed_to_me => true
|
25
|
+
accepts :paste_message
|
26
|
+
|
27
|
+
# priority is used to determine the plugin's order in the plugin queue. A higher number represents
|
28
|
+
# a higher priority. There are no upper or lower bounds. If you don't specify a priority, it defaults
|
29
|
+
# to 0.
|
30
|
+
|
31
|
+
priority 10
|
32
|
+
|
33
|
+
# If you need to do any one-time setup when the plugin is initially loaded, do it here. Optional.
|
34
|
+
def initialize
|
35
|
+
end
|
36
|
+
|
37
|
+
# If your plugin implements the heartbeat method, it will be called every time the bot polls the room
|
38
|
+
# for activity (currently every 3 seconds), whether or not there are any new messages. The heartbeat
|
39
|
+
# method is optional. It does not take any parameters.
|
40
|
+
def heartbeat
|
41
|
+
end
|
42
|
+
|
43
|
+
# process is the only method your plugin needs to implement. This is called by the bot whenever it
|
44
|
+
# has a new message that matches one of the message types accepted by the plugin. See Campfire::Message
|
45
|
+
# for message documentation.
|
46
|
+
# If no other plugins should receive the message after this plugin, return HALT.
|
47
|
+
def process(message)
|
48
|
+
end
|
49
|
+
|
50
|
+
# help is actually functionality provided by another plugin, HelpPlugin. Just return an array of
|
51
|
+
# ['command', 'description'] tuples
|
52
|
+
def help
|
53
|
+
[['some command', 'description of some command'],
|
54
|
+
['some other command', 'description of some other command']]
|
55
|
+
end
|
56
|
+
end
|
data/lib/wesabot.rb
ADDED
data/spec/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
test.sqlite
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Campfire::PollingBot do
|
4
|
+
before do
|
5
|
+
@bot = FakeBot.new
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'recognizes commands addressed to itself' do
|
9
|
+
messages = ["wes", "wes?", "wes hi", "wes, hi", "hi, wes", "hi wes",
|
10
|
+
"wes: hi"]
|
11
|
+
messages.each do |m|
|
12
|
+
@bot.addressed_to_me?(Campfire::TextMessage.new(:body => m)).should be_true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'does not recognize commands not addressed to itself' do
|
17
|
+
messages = ["hi", "western", "weswes?"]
|
18
|
+
messages.each do |m|
|
19
|
+
@bot.addressed_to_me?(Campfire::TextMessage.new(:body => m)).should be_false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.setup
|
4
|
+
|
5
|
+
$:.push File.expand_path("../lib", __FILE__)
|
6
|
+
require 'wesabot'
|
7
|
+
require 'rspec'
|
8
|
+
|
9
|
+
Campfire::PollingBot::Plugin.load_plugin_classes
|
10
|
+
|
11
|
+
class FakeBot < Campfire::PollingBot
|
12
|
+
def initialize
|
13
|
+
self.name = 'Wes'
|
14
|
+
self.config = Campfire::Configuration.new(:datauri => "sqlite3://#{File.expand_path('../test.sqlite', __FILE__)}")
|
15
|
+
Campfire::PollingBot::Plugin.load_all(self)
|
16
|
+
end
|
17
|
+
|
18
|
+
def say(message)
|
19
|
+
transcript << [:say, message]
|
20
|
+
end
|
21
|
+
|
22
|
+
def paste(message)
|
23
|
+
transcript << [:paste, message]
|
24
|
+
end
|
25
|
+
|
26
|
+
def say_random(messages)
|
27
|
+
transcript << [:say, messages.first]
|
28
|
+
end
|
29
|
+
|
30
|
+
def transcript
|
31
|
+
@transcript ||= []
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
RSpec.configure do |config|
|
36
|
+
def saying(what)
|
37
|
+
message Campfire::TextMessage, :body => what
|
38
|
+
end
|
39
|
+
|
40
|
+
alias :asking :saying
|
41
|
+
alias :say :saying
|
42
|
+
|
43
|
+
def entering
|
44
|
+
message Campfire::EnterMessage
|
45
|
+
end
|
46
|
+
|
47
|
+
alias :enter :entering
|
48
|
+
|
49
|
+
def leaving
|
50
|
+
message Campfire::LeaveMessage
|
51
|
+
end
|
52
|
+
|
53
|
+
alias :leave :leaving
|
54
|
+
|
55
|
+
def message(type, params={})
|
56
|
+
bot = FakeBot.new
|
57
|
+
@plugin.class.bot = bot
|
58
|
+
message = type.new(params)
|
59
|
+
@user ||= User.create(:name => "John Tester")
|
60
|
+
message.user = @user
|
61
|
+
@plugin.process(message) if @plugin.accepts?(message)
|
62
|
+
return bot.transcript
|
63
|
+
end
|
64
|
+
|
65
|
+
def make_wes_say(what)
|
66
|
+
MakeWesSend.new.and_say(what)
|
67
|
+
end
|
68
|
+
|
69
|
+
def make_wes_paste(what)
|
70
|
+
MakeWesSend.new.and_paste(what)
|
71
|
+
end
|
72
|
+
|
73
|
+
def make_wes_say_something
|
74
|
+
MakeWesSaySomething.new
|
75
|
+
end
|
76
|
+
|
77
|
+
alias :make_wes_say_anything :make_wes_say_something
|
78
|
+
|
79
|
+
module MessagePrinter
|
80
|
+
def print_messages(messages)
|
81
|
+
messages.map {|e| " #{e.first} #{e.last.inspect}"}.join("\n")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class MakeWesSend
|
86
|
+
include MessagePrinter
|
87
|
+
|
88
|
+
def matches?(actual)
|
89
|
+
@actual = actual
|
90
|
+
|
91
|
+
if actual.size != expected.size
|
92
|
+
@failure = [:size, actual.size, expected.size]
|
93
|
+
return false
|
94
|
+
end
|
95
|
+
|
96
|
+
actual.zip(expected).each do |a, e|
|
97
|
+
if a.first != e.first
|
98
|
+
@failure = [:type, a, e]
|
99
|
+
return false
|
100
|
+
end
|
101
|
+
|
102
|
+
case e.last
|
103
|
+
when Regexp
|
104
|
+
if a.last !~ e.last
|
105
|
+
@failure = [:match, a, e]
|
106
|
+
return false
|
107
|
+
end
|
108
|
+
else
|
109
|
+
if e.last != a.last
|
110
|
+
@failure = [:equal, a, e]
|
111
|
+
return false
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
return true
|
117
|
+
end
|
118
|
+
|
119
|
+
def failure_message
|
120
|
+
failure_type, actual, expected = *@failure
|
121
|
+
|
122
|
+
case failure_type
|
123
|
+
when :size
|
124
|
+
"expected #{expected} message(s):\n\n" +
|
125
|
+
print_messages(@expected) +
|
126
|
+
"\n\ngot #{actual} message(s):\n\n" +
|
127
|
+
print_messages(@actual)
|
128
|
+
when :type
|
129
|
+
"expected Wes to #{expected.first} #{expected.last.inspect}, " +
|
130
|
+
"but got #{actual.first} #{actual.last.inspect}"
|
131
|
+
when :match
|
132
|
+
"expected Wes to #{expected.first} something matching #{expected.last.inspect}, " +
|
133
|
+
"but got #{actual.last.inspect}"
|
134
|
+
when :equal
|
135
|
+
"expected Wes to #{expected.first} #{expected.last.inspect}, " +
|
136
|
+
"but got #{actual.last.inspect}"
|
137
|
+
else
|
138
|
+
@failure.inspect
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def negative_failure_message
|
143
|
+
"expected not to get the following message(s), but did:\n\n" +
|
144
|
+
print_messages(@expected)
|
145
|
+
end
|
146
|
+
|
147
|
+
def and(expected)
|
148
|
+
expect nil, expected
|
149
|
+
end
|
150
|
+
|
151
|
+
def and_say(expected)
|
152
|
+
expect :say, expected
|
153
|
+
end
|
154
|
+
|
155
|
+
def and_paste(expected)
|
156
|
+
expect :paste, expected
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
def expect(type, message)
|
162
|
+
type ||= @last_type
|
163
|
+
expected << [type, message]
|
164
|
+
@last_type = type
|
165
|
+
return self
|
166
|
+
end
|
167
|
+
|
168
|
+
def expected
|
169
|
+
@expected ||= []
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
class MakeWesSaySomething
|
174
|
+
include MessagePrinter
|
175
|
+
|
176
|
+
def matches?(actual)
|
177
|
+
@actual = actual
|
178
|
+
actual.any?
|
179
|
+
end
|
180
|
+
|
181
|
+
def failure_message
|
182
|
+
"expected Wes to say something, but he didn't"
|
183
|
+
end
|
184
|
+
|
185
|
+
def negative_failure_message
|
186
|
+
"expected Wes not to say anything, but he did:\n\n" +
|
187
|
+
print_messages(@actual)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
data/wesabot.gemspec
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "campfire/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "wesabot"
|
7
|
+
s.version = Campfire::VERSION
|
8
|
+
s.authors = ["Brad Greenlee", "André Arko", "Brian Donovan"]
|
9
|
+
s.email = ["brad@footle.org", "andre@arko.net", "me@brian-donovan.com"]
|
10
|
+
s.homepage = "https://github.com/hackarts/wesabot"
|
11
|
+
s.summary = %q{Wesabe's Campfire bot framework}
|
12
|
+
s.description = %q{Wesabot is a Campfire bot framework we've been using and
|
13
|
+
developing at Wesabe since not long after our inception. It started as a
|
14
|
+
way to avoid parking tickets near our office ("Wes, remind me in 2 hours
|
15
|
+
to move my car"), and has evolved into an essential work aid. When you
|
16
|
+
enter the room, Wes greets you with a link to the point in the transcript
|
17
|
+
where you last left. You can also ask him to bookmark points in the
|
18
|
+
transcript, send an sms message (well, an email) to someone, or even post
|
19
|
+
a tweet, among other things. His functionality is easily extendable via
|
20
|
+
plugins.}
|
21
|
+
|
22
|
+
s.rubyforge_project = "wesabot"
|
23
|
+
|
24
|
+
s.files = `git ls-files`.split("\n")
|
25
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
26
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
27
|
+
s.require_paths = ["lib"]
|
28
|
+
|
29
|
+
### core dependencies
|
30
|
+
s.add_dependency "tinder", "~> 1.7.0"
|
31
|
+
s.add_dependency "data_mapper", "~> 1.1"
|
32
|
+
s.add_dependency "dm-sqlite-adapter", "~> 1.1"
|
33
|
+
s.add_dependency "daemons"
|
34
|
+
s.add_dependency "i18n"
|
35
|
+
s.add_dependency "firering", "~> 1.2.0"
|
36
|
+
|
37
|
+
### plugin dependencies
|
38
|
+
|
39
|
+
# airbrake
|
40
|
+
s.add_dependency "nokogiri"
|
41
|
+
s.add_dependency "rest-client"
|
42
|
+
|
43
|
+
# image_search
|
44
|
+
s.add_dependency "httparty" # also used by twitter_search
|
45
|
+
s.add_dependency "google-search"
|
46
|
+
|
47
|
+
# remind_me
|
48
|
+
s.add_dependency "chronic"
|
49
|
+
|
50
|
+
### development dependencies
|
51
|
+
s.add_development_dependency "rspec", "~> 2.6.0"
|
52
|
+
s.add_development_dependency "rake"
|
53
|
+
s.add_development_dependency "ruby-debug"
|
54
|
+
|
55
|
+
end
|