wesabot 1.0.1
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/.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
|