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,153 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DeployPlugin do
|
4
|
+
def use_example_config
|
5
|
+
@plugin.config = {
|
6
|
+
'projects' => {
|
7
|
+
'my_super_site' => {
|
8
|
+
'deployed_revision_url' => 'http://www.example.com/REVISION'
|
9
|
+
},
|
10
|
+
|
11
|
+
'other' => {
|
12
|
+
'deployed_revision_url' => 'http://www.other.com/REVISION'
|
13
|
+
}}}
|
14
|
+
end
|
15
|
+
|
16
|
+
def use_no_config
|
17
|
+
@plugin.config = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def use_blank_config
|
21
|
+
@plugin.config = {'project' => {}}
|
22
|
+
end
|
23
|
+
|
24
|
+
before do
|
25
|
+
@plugin = DeployPlugin.new
|
26
|
+
end
|
27
|
+
|
28
|
+
context "with no configuration" do
|
29
|
+
before do
|
30
|
+
use_no_config
|
31
|
+
end
|
32
|
+
|
33
|
+
it "has no default project" do
|
34
|
+
@plugin.default_project.should be_nil
|
35
|
+
end
|
36
|
+
|
37
|
+
it "has an empty hash of projects" do
|
38
|
+
@plugin.projects.should == {}
|
39
|
+
end
|
40
|
+
|
41
|
+
it "omits the short form of the 'on deck' command from the help" do
|
42
|
+
@plugin.help.should have(2).items
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "the 'on deck' command" do
|
47
|
+
context "with no projects" do
|
48
|
+
before do
|
49
|
+
use_blank_config
|
50
|
+
end
|
51
|
+
|
52
|
+
it "tells the sender that it doesn't know about any projects" do
|
53
|
+
asking("wes, what's on deck?").
|
54
|
+
should make_wes_say( %r{I don't know about any projects} )
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context "with projects" do
|
59
|
+
before do
|
60
|
+
use_example_config
|
61
|
+
@plugin.stub!(:deployed_revision).and_return('abcde')
|
62
|
+
end
|
63
|
+
|
64
|
+
it "tells the sender when the project asked about does not exist" do
|
65
|
+
asking("wes, what's on deck for foobar?").
|
66
|
+
should make_wes_say( %r{I don't know anything about foobar} ).
|
67
|
+
and_paste("my_super_site\nother")
|
68
|
+
end
|
69
|
+
|
70
|
+
context "with no default project" do
|
71
|
+
before do
|
72
|
+
allow_message_expectations_on_nil
|
73
|
+
@project.stub!(:default_project).and_return(nil)
|
74
|
+
end
|
75
|
+
|
76
|
+
context "and more than one registered project" do
|
77
|
+
it "lists available projects when the project is omitted" do
|
78
|
+
asking("wes, what's on deck?").
|
79
|
+
should make_wes_say( %r{I don't have a default project} ).
|
80
|
+
and_paste("my_super_site\nother")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "and only one registered project" do
|
85
|
+
before do
|
86
|
+
@plugin.projects.delete(@plugin.projects.keys.first) while @plugin.projects.size > 1
|
87
|
+
end
|
88
|
+
|
89
|
+
it "uses the only listed project as the default project" do
|
90
|
+
@plugin.default_project.should == @plugin.projects.keys.first
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "with a default project" do
|
96
|
+
before do
|
97
|
+
@plugin.stub!(:default_project).and_return('my_super_site')
|
98
|
+
@plugin.stub!(:project_shortlog).
|
99
|
+
and_return("John Tester (1):\n Fix homepage links.\n")
|
100
|
+
end
|
101
|
+
|
102
|
+
it "uses the default project when the project is omitted" do
|
103
|
+
asking("wes, what's on deck?").
|
104
|
+
should make_wes_say( %r{Here's what's on deck for my_super_site} ).
|
105
|
+
and_paste(anything)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context "when there is nothing on deck" do
|
110
|
+
before do
|
111
|
+
@plugin.stub!(:project_shortlog).
|
112
|
+
and_return('')
|
113
|
+
end
|
114
|
+
|
115
|
+
it "responds that nothing is on deck for the project" do
|
116
|
+
asking("wes, what's on deck for my_super_site?").
|
117
|
+
should make_wes_say("There's nothing on deck for my_super_site right now.")
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context "when there is something on deck" do
|
122
|
+
before do
|
123
|
+
@plugin.stub!(:project_shortlog).
|
124
|
+
and_return("John Tester (1):\n Fix homepage links.\n\n")
|
125
|
+
end
|
126
|
+
|
127
|
+
it "responds with the shortlog for the project" do
|
128
|
+
asking("wes, what's on deck for my_super_site?").
|
129
|
+
should make_wes_say("Here's what's on deck for my_super_site:").
|
130
|
+
and_paste(<<-EOS)
|
131
|
+
$ git shortlog abcde..HEAD
|
132
|
+
|
133
|
+
John Tester (1):
|
134
|
+
Fix homepage links.
|
135
|
+
|
136
|
+
EOS
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
context "when getting the shortlog fails" do
|
141
|
+
before do
|
142
|
+
@plugin.stub!(:project_shortlog).
|
143
|
+
and_raise("testing")
|
144
|
+
end
|
145
|
+
|
146
|
+
it "reports the error to the user" do
|
147
|
+
asking("wes, what's on deck for my_super_site?").
|
148
|
+
should make_wes_say("Sorry John, I couldn't get what's on deck for my_super_site.")
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# Plugin to greet people when they enter, provide a catch-up url, and notify them of any "future" messages
|
2
|
+
# requires the HistoryPlugin
|
3
|
+
class GreetingPlugin < Campfire::PollingBot::Plugin
|
4
|
+
accepts :text_message, :addressed_to_me => true
|
5
|
+
accepts :enter_message
|
6
|
+
|
7
|
+
def process(message)
|
8
|
+
user = message.user
|
9
|
+
wants_greeting = wants_greeting?(user)
|
10
|
+
if message.kind_of?(Campfire::EnterMessage)
|
11
|
+
link = catch_up_link(message.person_full_name)
|
12
|
+
futures = future_messages(message.person_full_name, message.person)
|
13
|
+
if wants_greeting && link
|
14
|
+
bot.say("Hey #{message.person.downcase}. Catch up: #{link}")
|
15
|
+
end
|
16
|
+
futures.each{|m| bot.say(m) }
|
17
|
+
elsif message.kind_of?(Campfire::TextMessage)
|
18
|
+
case message.command
|
19
|
+
when /(disable|turn off) greetings/i
|
20
|
+
wants_greeting(user, false)
|
21
|
+
bot.say("OK, I've disabled greetings for you, #{message.person}")
|
22
|
+
return HALT
|
23
|
+
when /(enable|turn on) greetings/i
|
24
|
+
wants_greeting(user, true)
|
25
|
+
bot.say("OK, I've enabled greetings for you, #{message.person}")
|
26
|
+
return HALT
|
27
|
+
when /toggle greetings/i
|
28
|
+
old_setting = wants_greeting?(user)
|
29
|
+
wants_greeting(user, !old_setting)
|
30
|
+
bot.say("OK, I've #{old_setting ? 'disabled' : 'enabled'} greetings for you, #{message.person}")
|
31
|
+
return HALT
|
32
|
+
when /catch me up|ketchup/i
|
33
|
+
if link = catch_up_link(message.person_full_name, true)
|
34
|
+
bot.say("Here you go, #{message.person}: #{link}")
|
35
|
+
else
|
36
|
+
bot.say("Hmm...couldn't find when you last logged out, #{message.person}")
|
37
|
+
end
|
38
|
+
return HALT
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
# return array of available commands and descriptions
|
43
|
+
def help
|
44
|
+
[['(disable|turn off) greetings', "don't say hi when you log in (you grump)"],
|
45
|
+
['(enable|turn on) greetings', "say hi when you log in"],
|
46
|
+
['toggle greetings', "disable greetings if enabled, enable if disabled. You know--toggle."],
|
47
|
+
['catch me up|ketchup', "gives you a link to the point in the transcript where you last logged out"]
|
48
|
+
]
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# return the last message we saw from the user
|
54
|
+
def last_message(person_full_name)
|
55
|
+
# look for a leave message
|
56
|
+
last_left = Message.last_left(person_full_name)
|
57
|
+
# look for any message more than a couple minutes ago (this allows us
|
58
|
+
# to say hi in CF and then ask for a catch-up link without getting a link
|
59
|
+
# to the message we just sent)
|
60
|
+
last_message = Message.last_message(person_full_name, Time.now - 120)
|
61
|
+
|
62
|
+
if last_left && last_left.message_type == 'Kick'
|
63
|
+
# if person timed out, look for their last entry before the timeout
|
64
|
+
last_seen = last_message
|
65
|
+
else
|
66
|
+
# if the person said things after their last leave message, they are
|
67
|
+
# probably still in the room, so we use their last message instead
|
68
|
+
last_seen = [last_left, last_message].compact.sort_by{|m| m.timestamp }.last
|
69
|
+
end
|
70
|
+
|
71
|
+
return last_seen
|
72
|
+
end
|
73
|
+
|
74
|
+
# get link to when the user last left the room so they can catch up
|
75
|
+
# only return a link if the user has been gone long enough, unless forced
|
76
|
+
def catch_up_link(person_full_name, force=false)
|
77
|
+
message = last_message(person_full_name)
|
78
|
+
# only give a link if the last message is likely no longer on the first page
|
79
|
+
if message && (force || !message.visible?)
|
80
|
+
return message_link(message.message_id)
|
81
|
+
else
|
82
|
+
return nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Tell the person who's just entered about what people were asking them to
|
87
|
+
# read about while they were gone.
|
88
|
+
def future_messages(person_full_name, person)
|
89
|
+
future_messages = []
|
90
|
+
verbs = ["invoked", "called to", "cried out for", "made a sacrifice to", "let slip",
|
91
|
+
"doorbell ditched", "whispered sweetly to", "walked over broken glass to get to",
|
92
|
+
"prayed to the god of", "ran headlong at", "checked in a timebomb for",
|
93
|
+
"interpolated some strings TWO TIMES for", "wished upon a", "was like, oh my god",
|
94
|
+
"went all", "tested the concept of"]
|
95
|
+
|
96
|
+
# future Brian/future Brian Donovan
|
97
|
+
names = [person_full_name, person]
|
98
|
+
|
99
|
+
# future BD
|
100
|
+
name_words = person_full_name.split(/\s+/)
|
101
|
+
names << (name_words.first[0,1]+name_words.last[0,1]) if name_words.size > 1
|
102
|
+
|
103
|
+
future_person = Regexp.new("(?:future |@)(#{names.join('|')})\\b", Regexp::IGNORECASE)
|
104
|
+
future_everybody = Regexp.new("(?:future |@)everybody", Regexp::IGNORECASE)
|
105
|
+
|
106
|
+
if message = last_message(person_full_name)
|
107
|
+
candidates = Message.all(
|
108
|
+
:message_id.gt => message.message_id,
|
109
|
+
:person.not => ['Fogbugz','Subversion','Capistrano',bot.name],
|
110
|
+
:message_type => 'Text')
|
111
|
+
candidates.each do |row|
|
112
|
+
if row.body.match(future_person)
|
113
|
+
verbed = verbs[rand(verbs.size)]
|
114
|
+
future_messages << "#{row.person} #{verbed} future #{person} at: #{message_link(row.message_id)}"
|
115
|
+
elsif row.body.match(future_everybody)
|
116
|
+
verbed = verbs[rand(verbs.size)]
|
117
|
+
future_messages << "#{row.person} #{verbed} future everybody: \"#{row.body}\""
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
return future_messages
|
122
|
+
end
|
123
|
+
|
124
|
+
def wants_greeting?(user)
|
125
|
+
@wants_greeting_cache ||= User.all.inject({}) do |memo, u|
|
126
|
+
memo.merge(u => u.wants_greeting?)
|
127
|
+
end
|
128
|
+
|
129
|
+
if @wants_greeting_cache[user].nil?
|
130
|
+
@wants_greeting_cache[user] = user.wants_greeting = true
|
131
|
+
end
|
132
|
+
|
133
|
+
return @wants_greeting_cache[user]
|
134
|
+
end
|
135
|
+
|
136
|
+
def wants_greeting(user, wants_greeting)
|
137
|
+
user.wants_greeting = wants_greeting
|
138
|
+
@wants_greeting_cache[user] = wants_greeting
|
139
|
+
end
|
140
|
+
|
141
|
+
def message_link(id)
|
142
|
+
"#{bot.base_uri}/room/#{bot.room.id}/transcript/message/#{id}#message_#{id}"
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# used by GreetingPlugin
|
2
|
+
class GreetingSetting
|
3
|
+
include DataMapper::Resource
|
4
|
+
property :id, Serial
|
5
|
+
property :person, String, :index => true
|
6
|
+
property :wants_greeting, Boolean
|
7
|
+
|
8
|
+
belongs_to :user, :required => false
|
9
|
+
|
10
|
+
def self.for_user(user)
|
11
|
+
result = first(:user => user) || first(:person => user.name, :user => nil)
|
12
|
+
|
13
|
+
if result && result.user.nil?
|
14
|
+
result.update(:user => user)
|
15
|
+
end
|
16
|
+
|
17
|
+
return result
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GreetingPlugin do
|
4
|
+
before do
|
5
|
+
GreetingSetting.all.destroy
|
6
|
+
User.all.destroy
|
7
|
+
@plugin = described_class.new
|
8
|
+
@plugin.stub!(:catch_up_link).and_return("http://example.com")
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'greets users as they enter the room by default' do
|
12
|
+
entering.should make_wes_say(/^Hey John/i)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'allows disabling greetings per user' do
|
16
|
+
say('wes, disable greetings')
|
17
|
+
leave
|
18
|
+
entering.should_not make_wes_say_anything
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'confirms disabling greetings per user' do
|
22
|
+
saying('wes, disable greetings').
|
23
|
+
should make_wes_say("OK, I've disabled greetings for you, John")
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'allows re-enabling greetings per user' do
|
27
|
+
say('wes, disable greetings')
|
28
|
+
say('wes, enable greetings')
|
29
|
+
leave
|
30
|
+
entering.should make_wes_say(/^Hey John/i)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'confirms re-enabling greetings per user' do
|
34
|
+
say('wes, disable greetings')
|
35
|
+
saying('wes, enable greetings').
|
36
|
+
should make_wes_say("OK, I've enabled greetings for you, John")
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'allows toggling greetings per user' do
|
40
|
+
say('wes, enable greetings')
|
41
|
+
say('wes, toggle greetings')
|
42
|
+
leave
|
43
|
+
entering.should_not make_wes_say_anything
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'confirms toggling greetings per user' do
|
47
|
+
say('wes, enable greetings')
|
48
|
+
saying('wes, toggle greetings').
|
49
|
+
should make_wes_say("OK, I've disabled greetings for you, John")
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GreetingSetting do
|
4
|
+
before do
|
5
|
+
@bot = FakeBot.new
|
6
|
+
User.all.destroy
|
7
|
+
GreetingSetting.all.destroy
|
8
|
+
end
|
9
|
+
|
10
|
+
describe ".for_user" do
|
11
|
+
context "when the user has no setting" do
|
12
|
+
before do
|
13
|
+
@user = User.create(:name => "Marc")
|
14
|
+
end
|
15
|
+
|
16
|
+
it "returns nil" do
|
17
|
+
described_class.for_user(@user).should be_nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "when the user has a setting by name only" do
|
22
|
+
before do
|
23
|
+
@user = User.create(:name => "Bob")
|
24
|
+
@gs = described_class.create(:person => "Bob")
|
25
|
+
end
|
26
|
+
|
27
|
+
it "returns the greeting setting" do
|
28
|
+
described_class.for_user(@user).should == @gs
|
29
|
+
end
|
30
|
+
|
31
|
+
it "associates the greeting setting with the user" do
|
32
|
+
lambda { described_class.for_user(@user) }.
|
33
|
+
should change { @gs.reload.user }.
|
34
|
+
from(nil).to(@user)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "when the user has the same name as another with an associated setting" do
|
39
|
+
before do
|
40
|
+
@user = User.create(:name => "Sam")
|
41
|
+
@other = User.create(:name => "Sam")
|
42
|
+
@gs = described_class.create(:person => "Sam", :user => @other)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "returns nil" do
|
46
|
+
described_class.for_user(@user).should be_nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "when the user has an associated greeting setting" do
|
51
|
+
before do
|
52
|
+
@user = User.create(:name => "Coda")
|
53
|
+
@gs = described_class.create(:person => "Coda", :user => @user)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "returns the associated greeting setting" do
|
57
|
+
described_class.for_user(@user).should == @gs
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# Plugin to display available commands for each plugin
|
2
|
+
class HelpPlugin < Campfire::PollingBot::Plugin
|
3
|
+
accepts :text_message, :addressed_to_me => true
|
4
|
+
priority 0
|
5
|
+
|
6
|
+
def process(message)
|
7
|
+
person = message.person
|
8
|
+
case message.command
|
9
|
+
when /help(?:\s(.*))?/i
|
10
|
+
if $1
|
11
|
+
name = $1
|
12
|
+
plugin = plugin_helps.keys.find do |k|
|
13
|
+
k =~ /(#{name}|#{name.gsub(/s$/i, '')})(plugin)?/i
|
14
|
+
end
|
15
|
+
matched_helps = { plugin => plugin_helps[plugin] } if plugin
|
16
|
+
end
|
17
|
+
matched_helps ||= plugin_helps
|
18
|
+
|
19
|
+
bot.paste msg_for(matched_helps)
|
20
|
+
return HALT
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# return array of available commands and descriptions
|
25
|
+
def help
|
26
|
+
[['help', "this message"]]
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def plugin_helps
|
32
|
+
@plugin_helps ||= begin
|
33
|
+
help = bot.plugins.map do |plugin|
|
34
|
+
begin
|
35
|
+
[plugin.to_s, plugin.help] if plugin.respond_to?(:help)
|
36
|
+
rescue Exception => e
|
37
|
+
bot.log_error(e)
|
38
|
+
end
|
39
|
+
end.compact
|
40
|
+
Hash[help]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def msg_for(help)
|
45
|
+
help.keys.sort.map do |plugin|
|
46
|
+
cmds = help[plugin].map do |command, description|
|
47
|
+
" - #{command}\n #{description}\n"
|
48
|
+
end.join
|
49
|
+
"#{plugin}:\n" + cmds
|
50
|
+
end.join("\n")
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# records every message the bot receives
|
2
|
+
class HistoryPlugin < Campfire::PollingBot::Plugin
|
3
|
+
accepts :all
|
4
|
+
priority 100
|
5
|
+
|
6
|
+
def process(message)
|
7
|
+
@room_locked ||= false
|
8
|
+
if message.kind_of?(Campfire::LockMessage)
|
9
|
+
@room_locked = true
|
10
|
+
return
|
11
|
+
elsif message.kind_of?(Campfire::UnlockMessage)
|
12
|
+
@room_locked = false
|
13
|
+
return
|
14
|
+
end
|
15
|
+
|
16
|
+
save_message(message) unless @room_locked
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def save_message(message)
|
22
|
+
params = {
|
23
|
+
:room => bot.room.id,
|
24
|
+
:message_id => message.message_id,
|
25
|
+
:message_type => message.type,
|
26
|
+
:person => message.respond_to?(:person_full_name) ? message.person_full_name : nil,
|
27
|
+
:user_id => message.user && message.user.id,
|
28
|
+
:link => message.respond_to?(:link) ? message.link : nil,
|
29
|
+
:body => message.respond_to?(:body) ? message.body : nil,
|
30
|
+
:timestamp => message.timestamp.to_i
|
31
|
+
}
|
32
|
+
Message.create(params)
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'google-search'
|
3
|
+
|
4
|
+
# plugin to send a tweet to a Twitter account
|
5
|
+
class ImageSearchPlugin < Campfire::PollingBot::Plugin
|
6
|
+
priority 1
|
7
|
+
accepts :text_message, :addressed_to_me => true
|
8
|
+
|
9
|
+
def process(message)
|
10
|
+
searched = false
|
11
|
+
|
12
|
+
case message.command
|
13
|
+
when /(google|flickr)\sfor\sa\s(?:photo|image|picture)\s+of:?\s+(?:a:?\s+)?\s*("?)(.*?)\1$/i
|
14
|
+
subject = $3
|
15
|
+
photo = next_photo(subject, $1)
|
16
|
+
searched = true
|
17
|
+
when /(?:photo|image|picture)\s+of:?\s+(?:a:?\s+)?\s*("?)(.*?)\1$/i
|
18
|
+
subject = $2
|
19
|
+
photo = next_photo(subject)
|
20
|
+
searched = true
|
21
|
+
end
|
22
|
+
|
23
|
+
if searched
|
24
|
+
if photo
|
25
|
+
DisplayedImage.create(:uri => photo)
|
26
|
+
bot.say(photo)
|
27
|
+
else
|
28
|
+
bot.say("Couldn't find anything for \"#{subject}\"")
|
29
|
+
end
|
30
|
+
return HALT
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# return array of available commands and descriptions
|
35
|
+
def help
|
36
|
+
[
|
37
|
+
['(photo|image|picture) of <subject>', "find a new picture of <subject>"],
|
38
|
+
['search (google|flickr) for a (photo|image|picture) of <subject>', "search the stated service for a new picture of <subject>"]
|
39
|
+
]
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def next_photo(subject, sources = %w(google flickr))
|
45
|
+
next_photo = nil
|
46
|
+
|
47
|
+
if sources == ["google"] || (sources.include?("google") && bot.config.google_api_key)
|
48
|
+
# google search lazy-fetches pages of results, so we only search as far as we have to
|
49
|
+
if image = query_google(subject).find{|i| !DisplayedImage.first(:uri => i.uri) }
|
50
|
+
next_photo ||= image.uri
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
if sources.include?("flickr")
|
55
|
+
next_photo ||= query_flickr(subject).find{|u| !DisplayedImage.first(:uri => u) }
|
56
|
+
end
|
57
|
+
|
58
|
+
return next_photo
|
59
|
+
end
|
60
|
+
|
61
|
+
def query_google(subject)
|
62
|
+
unless bot.config.google_api_key
|
63
|
+
bot.say("I don't have a Google Search API key. " +
|
64
|
+
"Please add it to my config with the key 'google_api_key'.")
|
65
|
+
return []
|
66
|
+
end
|
67
|
+
|
68
|
+
logger.debug("Searching Google Images for #{subject}")
|
69
|
+
results = Google::Search::Image.new(
|
70
|
+
:query => subject,
|
71
|
+
:api_key => bot.config.google_api_key,
|
72
|
+
:image_type => :photo,
|
73
|
+
:safety_level => :off
|
74
|
+
)
|
75
|
+
|
76
|
+
logger.debug("Got response #{results.inspect}")
|
77
|
+
return results || []
|
78
|
+
end
|
79
|
+
|
80
|
+
def query_flickr(subject)
|
81
|
+
options = {:query => {:q => "select * from flickr.photos.search where text=\"#{subject}\""} }
|
82
|
+
logger.debug("YQL query #{options[:query][:q]}")
|
83
|
+
|
84
|
+
if proxy = (config && config['proxy']) || ENV['HTTP_PROXY']
|
85
|
+
proxy_uri = URI.parse(proxy)
|
86
|
+
options.update(:http_proxyaddr => proxy_uri.host, :http_proxyport => proxy_uri.port)
|
87
|
+
end
|
88
|
+
|
89
|
+
response = HTTParty.get("http://query.yahooapis.com/v1/public/yql", options)
|
90
|
+
logger.debug("Got response #{response.parsed_response.inspect}")
|
91
|
+
case response.code
|
92
|
+
when 200
|
93
|
+
return [] if response["query"]["yahoo:count"] == "0" || response["query"]["results"].nil?
|
94
|
+
photos = response["query"]["results"]["photo"]
|
95
|
+
photos = [photos] if photos.is_a?(Hash)
|
96
|
+
return photos.map {|p| "http://farm%s.static.flickr.com/%s/%s_%s.jpg?v=0" % [p['farm'], p['server'], p['id'], p['secret']]}
|
97
|
+
when 403
|
98
|
+
bot.say("Sorry, we seem to have hit our query limit for the day.")
|
99
|
+
return []
|
100
|
+
else
|
101
|
+
bot.say("Hmm...didn't work. Got this response:")
|
102
|
+
bot.paste(response.body)
|
103
|
+
return nil
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Plugin to make Wes actively comment (and maybe super annoying)
|
2
|
+
class InterjectorPlugin < Campfire::PollingBot::Plugin
|
3
|
+
accepts :text_message
|
4
|
+
priority -1
|
5
|
+
|
6
|
+
def process(message)
|
7
|
+
person = message.person
|
8
|
+
case message.command || message.body
|
9
|
+
when /jessica alba/i
|
10
|
+
bot.say_random [
|
11
|
+
"Jessica Alba is my dream girl",
|
12
|
+
"no question, the hottest girl ever",
|
13
|
+
"yeah... er, what was I saying?",
|
14
|
+
"she really is incredibly hot, you know",
|
15
|
+
]
|
16
|
+
return HALT
|
17
|
+
when /^(goodnight|night)(,?\s(all|every(body|one)))$/i
|
18
|
+
bot.say "goodnight, #{person}"
|
19
|
+
return HALT
|
20
|
+
when /^(facepalm|EFACEPALM|:fp|:facepalm:|\*facepalm\*|m\()$/i
|
21
|
+
bot.say_random [
|
22
|
+
# picard facepalm
|
23
|
+
"https://img.skitch.com/20110224-875h7w1654tgdhgrxm9bikhkwq.jpg",
|
24
|
+
# polar bear facepalm
|
25
|
+
"https://img.skitch.com/20110224-bsd2ds251eit8d3t1y2mkjtfx8.jpg"
|
26
|
+
]
|
27
|
+
when /^(double facepalm|EDOUBLEFACEPALM|:fpfp|:doublefacepalm:|m\( m\()$/i
|
28
|
+
# picard + riker facepalm
|
29
|
+
bot.say "https://img.skitch.com/20110224-ncacgpudhfr2s4te6nswenxaqt.jpg"
|
30
|
+
when /^i see your problem/i
|
31
|
+
# pony mechanic
|
32
|
+
bot.say "https://img.skitch.com/20110224-8fmfwdmg6kkrcpijhamhqu7tm6.jpg"
|
33
|
+
when /^(wfm|womm|works on my machine)$/i
|
34
|
+
# works on my machine
|
35
|
+
bot.say "https://img.skitch.com/20110224-jrcf6e4gc936a2mxc3mueah2in.png"
|
36
|
+
when /^(stacktrace or gtfo|stacktrace or it didn't happen|stacktrace!)$/i
|
37
|
+
# stacktrace or gtfo
|
38
|
+
bot.say "https://img.skitch.com/20110224-pqtmiici9wp9nygqi4nw8gs6hg.png"
|
39
|
+
when /^this is sparta\!*?$/i
|
40
|
+
# this is sparta
|
41
|
+
bot.say "https://img.skitch.com/20110225-k9xpadr2hk37pe5ed4crcqria1.png"
|
42
|
+
when /^i have no idea what i'm doing$/i
|
43
|
+
# I have no idea what I'm doing
|
44
|
+
bot.say "https://img.skitch.com/20110304-1tcmatkhapiq6t51wqqq9igat5.jpg"
|
45
|
+
when /^party[?!.]*$/i
|
46
|
+
# party party party party party cat
|
47
|
+
bot.say "https://img.skitch.com/20110309-qtd33sy8k5yrdaeqa9e119bwd1.jpg"
|
48
|
+
when /^(bomb|system error)$/i
|
49
|
+
# sorry, a system error has occurred
|
50
|
+
bot.say "https://img.skitch.com/20110312-8g31a37spacdjgatr82g3g98j1.jpg"
|
51
|
+
when /^stop hitting yourself$/i
|
52
|
+
# and the angel said to him, Stop hitting yourself!
|
53
|
+
bot.say "https://img.skitch.com/20110316-q7h49p69pjhhyy8756rha2a1jf.jpg"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
end
|