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.
Files changed (55) hide show
  1. data/.gitignore +5 -0
  2. data/Gemfile +3 -0
  3. data/LICENSE +20 -0
  4. data/README.md +182 -0
  5. data/Rakefile +13 -0
  6. data/bin/wesabot +111 -0
  7. data/config/wesabot.yml.sample +4 -0
  8. data/lib/campfire/bot.rb +53 -0
  9. data/lib/campfire/configuration.rb +65 -0
  10. data/lib/campfire/message.rb +102 -0
  11. data/lib/campfire/polling_bot/plugin.rb +162 -0
  12. data/lib/campfire/polling_bot/plugins/airbrake/.gitignore +1 -0
  13. data/lib/campfire/polling_bot/plugins/airbrake/airbrake/api.rb +48 -0
  14. data/lib/campfire/polling_bot/plugins/airbrake/airbrake/error.rb +51 -0
  15. data/lib/campfire/polling_bot/plugins/airbrake/airbrake_plugin.rb +63 -0
  16. data/lib/campfire/polling_bot/plugins/airbrake/airbrake_plugin.yml.sample +3 -0
  17. data/lib/campfire/polling_bot/plugins/bookmark/bookmark.rb +15 -0
  18. data/lib/campfire/polling_bot/plugins/bookmark/bookmark_plugin.rb +89 -0
  19. data/lib/campfire/polling_bot/plugins/debug/debug_plugin.rb +23 -0
  20. data/lib/campfire/polling_bot/plugins/deploy/deploy_plugin.rb +155 -0
  21. data/lib/campfire/polling_bot/plugins/deploy/deploy_plugin.yml.sample +9 -0
  22. data/lib/campfire/polling_bot/plugins/deploy/spec/deploy_plugin_spec.rb +153 -0
  23. data/lib/campfire/polling_bot/plugins/greeting/greeting_plugin.rb +146 -0
  24. data/lib/campfire/polling_bot/plugins/greeting/greeting_setting.rb +19 -0
  25. data/lib/campfire/polling_bot/plugins/greeting/spec/greeting_plugin_spec.rb +51 -0
  26. data/lib/campfire/polling_bot/plugins/greeting/spec/greeting_setting_spec.rb +61 -0
  27. data/lib/campfire/polling_bot/plugins/help/help_plugin.rb +53 -0
  28. data/lib/campfire/polling_bot/plugins/history/history_plugin.rb +34 -0
  29. data/lib/campfire/polling_bot/plugins/image_search/displayed_image.rb +8 -0
  30. data/lib/campfire/polling_bot/plugins/image_search/image_search_plugin.rb +106 -0
  31. data/lib/campfire/polling_bot/plugins/interjector/interjector_plugin.rb +58 -0
  32. data/lib/campfire/polling_bot/plugins/kibitz/kibitz_plugin.rb +90 -0
  33. data/lib/campfire/polling_bot/plugins/kibitz/spec/kibitz_plugin_spec.rb +56 -0
  34. data/lib/campfire/polling_bot/plugins/plugin.db +0 -0
  35. data/lib/campfire/polling_bot/plugins/reload/reload_plugin.rb +24 -0
  36. data/lib/campfire/polling_bot/plugins/remind_me/remind_me_plugin.rb +149 -0
  37. data/lib/campfire/polling_bot/plugins/remind_me/reminder.rb +8 -0
  38. data/lib/campfire/polling_bot/plugins/shared/message.rb +37 -0
  39. data/lib/campfire/polling_bot/plugins/shared/user.rb +32 -0
  40. data/lib/campfire/polling_bot/plugins/sms/sms_plugin.rb +88 -0
  41. data/lib/campfire/polling_bot/plugins/sms/sms_setting.rb +7 -0
  42. data/lib/campfire/polling_bot/plugins/time/time_plugin.rb +17 -0
  43. data/lib/campfire/polling_bot/plugins/tweet/tweet.rb +52 -0
  44. data/lib/campfire/polling_bot/plugins/tweet/tweet_plugin.rb +117 -0
  45. data/lib/campfire/polling_bot/plugins/tweet/tweet_plugin.yml.sample +4 -0
  46. data/lib/campfire/polling_bot/plugins/twitter_search/twitter_search_plugin.rb +58 -0
  47. data/lib/campfire/polling_bot.rb +125 -0
  48. data/lib/campfire/sample_plugin.rb +56 -0
  49. data/lib/campfire/version.rb +3 -0
  50. data/lib/wesabot.rb +3 -0
  51. data/spec/.gitignore +1 -0
  52. data/spec/polling_bot_spec.rb +23 -0
  53. data/spec/spec_helper.rb +190 -0
  54. data/wesabot.gemspec +55 -0
  55. metadata +336 -0
@@ -0,0 +1,90 @@
1
+ # Plugin to make Wes chatty (or annoying)
2
+ class KibitzPlugin < Campfire::PollingBot::Plugin
3
+ accepts :text_message, :addressed_to_me => true
4
+ priority -1
5
+
6
+ def process(message)
7
+ person = message.person
8
+ case message.command
9
+ when /^\s*$/
10
+ # question questioners, exclaim at exclaimers, dot dotters
11
+ message.body.match(/(\?|\!|\.)$/)
12
+ bot.say(person + $1.to_s)
13
+ when /say\s+(.*)/
14
+ bot.say($1)
15
+ when /^(hey|hi|hello|sup|howdy)/i
16
+ bot.say("#{$1} #{person}")
17
+ when /(^later|(?:good\s*)?bye)/i
18
+ bot.say("#{$1} #{person}")
19
+ when /you rock|awesome|cool|nice/i
20
+ sayings = ["Thanks, #{person}, you're pretty cool yourself.",
21
+ "I try.",
22
+ "Aw, shucks. Thanks, #{person}."]
23
+ bot.say_random(sayings)
24
+ when /(^|you|still)\s*there/i, /\byt\b/i
25
+ bot.say_random(%w{Yup y})
26
+ when /(wake up|you awake)/i
27
+ bot.say("Yo.")
28
+ when /zod/i
29
+ bot.say_random [
30
+ "Zod's a wanker.",
31
+ "I'd tell you about Zod, but you wouldn't listen. No one ever does.",
32
+ "somebody send Zod back to the Phantom Zone",
33
+ "Zod and I were friends, once. It all ended one awful night in El Paso over a bottle of shitty scotch and a ten-dollar whore. Christ, those were the days."
34
+ ]
35
+ when /thanks|thank you/i
36
+ bot.say_random ["No problem.", "np", "any time", "that's what I'm here for", "You're welcome."]
37
+ when /^(good\s?night|(?:g')?night)$/i
38
+ bot.say_random [
39
+ "see you later, #{person}",
40
+ "later, #{person}",
41
+ "night",
42
+ "goodnight",
43
+ "bye",
44
+ "have a good night"
45
+ ]
46
+ when /^(see you(?: later)?)$/i
47
+ bot.say_random [
48
+ "see you later, #{person}",
49
+ "later, #{person}",
50
+ "bye",
51
+ "later",
52
+ "see ya",
53
+ ]
54
+ when /^(?:get|grab|fetch|bring)(?: (.*?))?(?: (?:a|some))? coffee$/i
55
+ person = $1
56
+ if person =~ /(me|us)/
57
+ person, do_they = "you", "do you"
58
+ else
59
+ do_they = "does #{person}"
60
+ end
61
+
62
+ bot.say_random [
63
+ "would #{person} like cream or sugar?",
64
+ "how #{do_they} take it?",
65
+ "coming right up",
66
+ "It is by caffeine alone I set my mind in motion",
67
+ "It is by the beans of Java that thoughts acquire speed",
68
+ "The hands acquire shakes, the shakes become a warning",
69
+ "It is by caffeine alone I set my mind in motion"
70
+ ]
71
+ else
72
+ bot.say_random [
73
+ "I have no idea what you're talking about, #{person}.",
74
+ "eh?",
75
+ "oh, interesting",
76
+ "say more, #{person}",
77
+ "#{person}, you do realize that you're talking to a bot with a very limited vocabulary, don't you?",
78
+ "Whatever, #{person}.",
79
+ "#{bot.other_person(person) || 'Marc'}, tell #{person} to leave me alone.",
80
+ "Not now, #{person}.",
81
+ "brb crying",
82
+ "what do you think, #{person}?",
83
+ "That's really something.",
84
+ "but what can I do? I'm just a lowly bot",
85
+ "I'll get some electrons on that right away",
86
+ "How do you feel when someone says '#{message.command}' to you, #{person}?"
87
+ ]
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe KibitzPlugin do
4
+ before do
5
+ @plugin = KibitzPlugin.new
6
+ end
7
+
8
+ describe "when not addressing wes" do
9
+ it "does not say anything" do
10
+ saying("should we deploy?").
11
+ should_not make_wes_say_anything
12
+ end
13
+ end
14
+
15
+ describe "when addressing wes" do
16
+ it "responds with the name of the sender when the message has no command" do
17
+ saying("wes?").
18
+ should make_wes_say("John?")
19
+ end
20
+
21
+ it "says whatever the sender tells wes to when using the 'say' command" do
22
+ saying("wes, say Hi There").
23
+ should make_wes_say("Hi There")
24
+ end
25
+
26
+ it "responds with the same greeting the sender uses" do
27
+ saying("hey wes").
28
+ should make_wes_say("hey John")
29
+ end
30
+
31
+ it "responds with the same goodbye the sender uses" do
32
+ saying("bye, wes").
33
+ should make_wes_say("bye John")
34
+ end
35
+
36
+ it "responds politely to praise" do
37
+ saying("yay! you rock, wes").
38
+ should make_wes_say("Thanks, John, you're pretty cool yourself.")
39
+ end
40
+
41
+ it "says yes when the sender asks if wes is there" do
42
+ asking("wes, yt?").
43
+ should make_wes_say("Yup")
44
+ end
45
+
46
+ it "says hi informally when told to wake up" do
47
+ asking("you awake wes?").
48
+ should make_wes_say("Yo.")
49
+ end
50
+
51
+ it "responds politely to thanks" do
52
+ asking("thanks, wes!").
53
+ should make_wes_say("No problem.")
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,24 @@
1
+ # Plugin to allow Wes to update and reload himself
2
+ class ReloadPlugin < Campfire::PollingBot::Plugin
3
+ accepts :text_message, :addressed_to_me => true
4
+
5
+ def process(message)
6
+ case message.command
7
+ when /^reload/i
8
+ bot.say("k")
9
+ system("git pull origin master && bundle install")
10
+ if bot.debug
11
+ head = `git rev-parse HEAD`
12
+ bot.say("updated to: https://github.com/hackarts/wesabot/commit/#{head}")
13
+ end
14
+ bot.say("restarting...")
15
+ exec *INVOCATION
16
+ return HALT
17
+ end
18
+ end
19
+
20
+ # return array of available commands and descriptions
21
+ def help
22
+ [['reload', "update and reload #{bot.name}"]]
23
+ end
24
+ end
@@ -0,0 +1,149 @@
1
+ # Reminders plugin
2
+ require 'chronic'
3
+ class RemindMePlugin < Campfire::PollingBot::Plugin
4
+ accepts :text_message, :addressed_to_me => true
5
+ priority 10
6
+
7
+ NUMBER_WORDS = %w{zero one two three four five six seven eight nine ten}
8
+ PRONOUN_SUBS = [
9
+ ["I'm", "you're"],
10
+ ['my', 'your'],
11
+ ['I', 'you'],
12
+ ]
13
+
14
+ def process(message)
15
+ case message.command
16
+ when /remind\s+(.*?)\s+(in\s+(\S+)\s+(?:seconds|minutes|hours|days|weeks|years))\s+to\s+(.*)/i
17
+ reminder_person = $1
18
+ time_string = $2
19
+ time_value = $3
20
+ action = $4
21
+ if i = NUMBER_WORDS.index(time_value.downcase)
22
+ time_string.sub!(time_value, i.to_s)
23
+ end
24
+ when /remind\s+(.*?)\s+to\s+(.*?)\s+((?:in|on|at|next|this).*)/i
25
+ reminder_person = $1
26
+ action = $2
27
+ time_string = $3
28
+ when /remind\s+(.*?)\s+(.*?)\s+to\s+(.*)/i
29
+ reminder_person = $1
30
+ time_string = $2
31
+ action = $3
32
+ when /(?:list|show) (\S+) reminders/i, /(?:list|show) reminders for (\S+)/i
33
+ list_reminders(message.person, $1)
34
+ return HALT
35
+ when /(list|show) reminders$/
36
+ list_reminders(message.person, message.person)
37
+ return HALT
38
+ when /delete reminder (?:#\s*)?(\d+)/
39
+ delete_reminder(message.person, $1.to_i)
40
+ return HALT
41
+ else
42
+ return
43
+ end
44
+
45
+ PRONOUN_SUBS.each do |sub|
46
+ action.gsub!(/\b#{sub[0]}\b/i, sub[1])
47
+ end
48
+
49
+ begin
50
+ time = Chronic.parse(time_string.sub(/^at/, ''))
51
+ rescue RangeError
52
+ bot.say("Whatever, #{message.person}.")
53
+ return HALT
54
+ end
55
+
56
+ if !time || time.kind_of?(Chronic::Span)
57
+ bot.say("sorry, #{message.person}, but I don't know what '#{time_string}' means.")
58
+ return HALT
59
+ end
60
+
61
+ if reminder_person.downcase == 'me'
62
+ reminder_person = message.person
63
+ else
64
+ reminder_person.gsub!(/^(\w)/) {$1.upcase}
65
+ end
66
+
67
+ Reminder.create(:person => reminder_person, :action => action, :reminder_time => time)
68
+ time_now = Time.now
69
+ if [time.year,time.month,time.day] == [time_now.year,time_now.month,time_now.day]
70
+ converted_time_string = time.strftime('%I:%M %p').gsub(/^0/,'')
71
+ else
72
+ converted_time_string = time.strftime('%c')
73
+ end
74
+
75
+ bot.say("ok, #{message.person}, I will remind #{reminder_person == message.person ? 'you' : reminder_person} #{time_string} (#{converted_time_string}) to #{action}")
76
+ return HALT
77
+ end
78
+
79
+ def heartbeat
80
+ # check reminders every 9 seconds (heartbeat is called every 3 sec)
81
+ @heartbeat_counter ||= 0
82
+ @heartbeat_counter += 1
83
+ return unless (@heartbeat_counter % 3) == 1
84
+ due_reminders.each do |reminder|
85
+ bot.say("#{reminder.person}, I'm reminding you to #{reminder.action}")
86
+ reminder.destroy!
87
+ end
88
+ end
89
+
90
+ # return array of available commands and descriptions
91
+ def help
92
+ [['remind (me|<person>) [in] <time string> to <message>', "set up a reminder"],
93
+ ['remind (me|<person>) to <message> (in|on|at|next|this) <time string>', "set up a reminder"],
94
+ ["[list|show] [person]['s] reminders", "display current reminders for yourself or person"],
95
+ ["delete reminder <n>", "delete your reminder #n"],
96
+ ]
97
+ end
98
+
99
+ private
100
+
101
+ def due_reminders
102
+ Reminder.all(:reminder_time.lte => Time.now)
103
+ end
104
+
105
+ def list_reminders(current_person, request_person)
106
+ case request_person.downcase
107
+ when 'my', 'me'
108
+ request_person = current_person
109
+ else
110
+ request_person.gsub!(/'s$/i,'')
111
+ end
112
+ request_person.capitalize!
113
+
114
+ case request_person
115
+ when /everyone/i, /all/i
116
+ if (reminders = Reminder.all).any?
117
+ bot.say("Here are all the reminders I have:")
118
+ reminders.each do |reminder|
119
+ bot.say("#{reminder.id} - [#{reminder.reminder_time}] - #{reminder.person} - #{reminder.action}")
120
+ end
121
+ end
122
+ return
123
+ else
124
+ reminders = Reminder.all(:conditions => {:person => request_person}, :order => [:reminder_time])
125
+ if reminders.any?
126
+ if request_person == current_person
127
+ bot.say("Here are the reminders I have for you, #{current_person}:")
128
+ else
129
+ bot.say("Here are the reminders for #{request_person}:")
130
+ end
131
+ reminders.each do |reminder|
132
+ bot.say("#{reminder.id} - [#{reminder.reminder_time}] - #{reminder.action}")
133
+ end
134
+ return
135
+ end
136
+ end
137
+ bot.say("I couldn't find any reminders for #{request_person}")
138
+ end
139
+
140
+ def delete_reminder(current_person, id)
141
+ reminder = Reminder.get(id)
142
+ if reminder.person == current_person
143
+ reminder.destroy
144
+ bot.say("Ok, I've deleted reminder ##{id}.")
145
+ else
146
+ bot.say("Sorry, #{current_person}, but I couldn't find a reminder with that id that belongs to you.")
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,8 @@
1
+ # used by RemindMe plugin
2
+ class Reminder
3
+ include DataMapper::Resource
4
+ property :id, Serial
5
+ property :person, String, :index => true
6
+ property :action, Text, :lazy => false
7
+ property :reminder_time, Time, :required => true, :index => true
8
+ end
@@ -0,0 +1,37 @@
1
+ # used by HistoryPlugin, among others
2
+ class Message
3
+ include DataMapper::Resource
4
+ property :id, Serial
5
+ property :room, Integer, :required => true, :index => true
6
+ property :message_id, Integer, :required => true
7
+ property :message_type, String, :length => 20, :required => true, :index => true
8
+ property :person, String, :index => true
9
+ property :user_id, Integer, :required => false, :index => true
10
+ property :link, Text, :lazy => false
11
+ property :body, Text, :lazy => false
12
+ property :timestamp, Integer, :required => true, :index => true
13
+
14
+ belongs_to :user, :required => false
15
+
16
+ def self.last_message(name, time = nil)
17
+ first(:conditions => {
18
+ :person => name,
19
+ :message_type.not => 'Enter',
20
+ :timestamp.lt => (time || Time.now)
21
+ }, :order => [:timestamp.desc])
22
+ end
23
+
24
+ def self.last_left(name, time = nil)
25
+ first(:conditions => {
26
+ :person => name,
27
+ :message_type => ['Leave','Kick'],
28
+ :timestamp.lt => (time || Time.now)
29
+ }, :order => [:timestamp.desc])
30
+ end
31
+
32
+ # guess whether the message is still on the screen by counting
33
+ # how many messages there are between it and now
34
+ def visible?
35
+ self.class.count(:id.gt => id) < 20
36
+ end
37
+ end
@@ -0,0 +1,32 @@
1
+ class User
2
+ include DataMapper::Resource
3
+ property :id, Serial
4
+ property :name, String, :index => true
5
+ property :campfire_id, Integer, :index => true
6
+
7
+ has n, :messages
8
+
9
+ def short_name
10
+ name && name.split(' ').first
11
+ end
12
+
13
+ def wants_greeting?
14
+ greeting_setting = GreetingSetting.for_user(self)
15
+
16
+ if greeting_setting
17
+ greeting_setting.wants_greeting
18
+ else
19
+ true
20
+ end
21
+ end
22
+
23
+ def wants_greeting=(yesno)
24
+ greeting_setting = GreetingSetting.for_user(self)
25
+
26
+ if greeting_setting
27
+ greeting_setting.update(:wants_greeting => yesno)
28
+ else
29
+ GreetingSetting.create(:user => self, :wants_greeting => yesno)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,88 @@
1
+ # Plugin to send SMS (well, email) messages
2
+ require 'net/smtp'
3
+ class SMSPlugin < Campfire::PollingBot::Plugin
4
+ accepts :text_message, :addressed_to_me => true
5
+
6
+ def process(message)
7
+ case message.command
8
+ when /(?:set)?\s+my\s+sms\s+address\s*(?:is|to|:)\s*(.+)/i
9
+ address = $1
10
+ if address =~ /"mailto:(.*?)"/
11
+ address = $1
12
+ end
13
+ update_sms_address(message.person, address)
14
+ bot.say("OK, #{message.person}, I've set your SMS address to #{address}")
15
+ return HALT
16
+ when /(?:set)?\s+(\w+?)'s\s+sms\s+address\s*(?:is|to|:)\s*(.+)/i
17
+ person = $1
18
+ address = $2
19
+ if address =~ /"mailto:(.*?)"/
20
+ address = $1
21
+ end
22
+ update_sms_address(person, address)
23
+ bot.say("OK, #{message.person}, I've set #{$1}'s SMS address to #{$2}")
24
+ return HALT
25
+ when /list sms addresses/i
26
+ if (settings = SMSSetting.all(:order => [:person])).any?
27
+ bot.say("Here are the SMS addresses I have:")
28
+ settings.each { |setting| bot.say(" #{setting.person} - #{setting.address}") }
29
+ else
30
+ bot.say("Sorry, I don't have any SMS addresses yet.")
31
+ end
32
+ return HALT
33
+ when /(?:sms|text|txt)\s+([^\s:]+):?\s*(.*)/i
34
+ if address = send_sms(message.person, $1, $2)
35
+ bot.say("Sent an SMS to #{$1} at #{address}")
36
+ end
37
+ return HALT
38
+ end
39
+ end
40
+
41
+ # return array of available commands and descriptions
42
+ def help
43
+ [['set my sms address to: <address>', "set your sms address"],
44
+ ["set <person>'s sms address to", "set someone else's sms address"],
45
+ ['(sms|text|txt) <person>: <message>', "send an sms message"],
46
+ ["list sms addresses", "list all sms addresses"]
47
+ ]
48
+ end
49
+
50
+ private
51
+
52
+ def update_sms_address(person, address)
53
+ person.downcase!
54
+ if setting = find_sms_setting(person)
55
+ setting.update_attributes(:address => address)
56
+ else
57
+ SMSSetting.create(:person => person, :address => address)
58
+ end
59
+ return address
60
+ end
61
+
62
+ def send_sms(sender, recipient, message)
63
+ if setting = find_sms_setting(recipient)
64
+ send_email(sender, setting.address, message)
65
+ return setting.address
66
+ else
67
+ bot.say("Sorry, I don't have an SMS address for #{recipient}.")
68
+ return nil
69
+ end
70
+ end
71
+
72
+ def find_sms_setting(person)
73
+ SMSSetting.first(:person => person.downcase)
74
+ end
75
+
76
+ def send_email(from, to, message)
77
+ from += "@wesabe.com"
78
+ msg = "From: #{from}\nTo: #{to}\n\n#{message}"
79
+ begin
80
+ Net::SMTP.start('localhost', 25, 'wesabe.com') { |smtp| smtp.send_message(msg, from, to) }
81
+ return true
82
+ rescue Exception => e
83
+ bot.say("Hmm...couldn't send mail. Here's what happened when I tried:")
84
+ bot.paste(e.message)
85
+ return false
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,7 @@
1
+ # used by SMSPlugin
2
+ class SMSSetting
3
+ include DataMapper::Resource
4
+ property :id, Serial
5
+ property :person, String, :index => true
6
+ property :address, String
7
+ end
@@ -0,0 +1,17 @@
1
+ # Simple sample plugin to just give the current time
2
+ class TimePlugin < Campfire::PollingBot::Plugin
3
+ accepts :text_message, :addressed_to_me => true
4
+
5
+ def process(message)
6
+ case message.command
7
+ when /time/i
8
+ bot.say("#{message.person}, the time is #{Time.now}")
9
+ return HALT
10
+ end
11
+ end
12
+
13
+ # return array of available commands and descriptions
14
+ def help
15
+ [['time', "say the current time"]]
16
+ end
17
+ end
@@ -0,0 +1,52 @@
1
+ # used by TweetPlugin
2
+ class Tweet
3
+ class TwitterError < RuntimeError
4
+ attr_reader :http_result
5
+
6
+ def initialize(http_result)
7
+ @http_result = http_result
8
+ end
9
+ end
10
+
11
+ include DataMapper::Resource
12
+ property :id, Serial
13
+ property :message, String, :required => true
14
+ property :person, String
15
+ property :timestamp, Integer, :required => true
16
+
17
+ def tweet(user, pass, proxy=nil)
18
+ self.class.tweet(message, user, pass, proxy)
19
+ end
20
+
21
+ def self.tweet(message, user, pass, proxy=nil)
22
+ result = post(message, user, pass, proxy)
23
+ if result.code == 200
24
+ return true
25
+ else
26
+ raise TwitterError.new(result.body)
27
+ end
28
+ end
29
+
30
+ def self.list
31
+ all(:order => [:timestamp.asc])
32
+ end
33
+
34
+ def to_s
35
+ message
36
+ end
37
+
38
+ private
39
+
40
+ # post a message to twitter
41
+ def self.post(message, user, pass, proxy=nil)
42
+ options = {
43
+ :query => {:status => message},
44
+ :basic_auth => {:username => user, :password => pass}
45
+ }
46
+ if proxy ||= ENV['HTTP_PROXY']
47
+ proxy_uri = URI.parse(proxy)
48
+ options.update(:http_proxyaddr => proxy_uri.host, :http_proxyport => proxy_uri.port)
49
+ end
50
+ HTTParty.post("http://twitter.com/statuses/update.xml", options)
51
+ end
52
+ end
@@ -0,0 +1,117 @@
1
+ # plugin to send a tweet to a Twitter account
2
+ class TweetPlugin < Campfire::PollingBot::Plugin
3
+ accepts :text_message, :addressed_to_me => true
4
+ priority 20
5
+
6
+ def process(message)
7
+ case message.command
8
+ when /^(?:tweet|twitter):?\s*("?)(.*?)\1$/i
9
+ begin
10
+ text = strip_links($2)
11
+ post_tweet(text)
12
+ rescue TwitterError => ex
13
+ bot.say("Hmm...didn't work. Got this response:")
14
+ bot.paste(ex.http_result.content)
15
+ end
16
+ return HALT
17
+ when /^(?:save|queue)\s+(?:tweet|twitter):?\s*("?)(.*?)\1$/i
18
+ tweet = Tweet.create(:person => message.person, :message => strip_links($2), :timestamp => message.timestamp.to_i)
19
+ bot.say("Ok, saved for later: #{tweet}")
20
+ return HALT
21
+ when /^(?:show|list)\s+(?:all\s+)?(?:queued\s+)?(?:tweets|twitters)$/i
22
+ act_on_list do |list|
23
+ bot.say("Here are the tweets in the queue:")
24
+ i = 0
25
+ bot.paste(list.map{|tweet| "#{i+=1}. #{tweet}"}.join("\n"))
26
+ end
27
+ return HALT
28
+ when /^show\s+next\s+(?:tweet|twitter)$/i
29
+ act_on_list do |list|
30
+ bot.say("Next tweet to post: #{list.first}")
31
+ end
32
+ return HALT
33
+ when /^(?:post|send)\s+next\s+(?:tweet|twitter)$/i
34
+ act_on_list do |list|
35
+ tweet = list.first
36
+ post_tweet(tweet)
37
+ tweet.destroy
38
+ end
39
+ return HALT
40
+ when /^(?:post|send)\s+tweet\s+(\d+)$/i
41
+ act_on_tweet($1.to_i-1) do |tweet|
42
+ post_tweet(tweet)
43
+ tweet.destroy
44
+ end
45
+ return HALT
46
+ when /^delete\s+tweet\s+(\d+)$/i
47
+ act_on_tweet($1.to_i-1) do |tweet|
48
+ tweet.destroy
49
+ bot.say("Okay, deleted tweet #{$1}: #{tweet}")
50
+ end
51
+ return HALT
52
+ end
53
+ end
54
+
55
+ # return array of available commands and descriptions
56
+ def help
57
+ user = username || 'the configured user'
58
+ [['tweet: <message>', "post <message> to #{user}'s twitter account"],
59
+ ['save tweet: <message>', "save <message> for later"],
60
+ ['show tweets', "shows the queued tweets for #{user}'s twitter account"],
61
+ ['show next tweet', "shows the oldest queued twitter message"],
62
+ ['post next tweet', "sends the oldest queued twitter message"],
63
+ ['post tweet <n>', "sends the <n>th tweet from the list"],
64
+ ['delete tweet <n>', "deletes the <n>th tweet from the list"]]
65
+ end
66
+
67
+ private
68
+
69
+ def username
70
+ config && config['username']
71
+ end
72
+
73
+ def password
74
+ config && config['password']
75
+ end
76
+
77
+ def proxy
78
+ config && config['proxy']
79
+ end
80
+
81
+ def post_tweet(tweet)
82
+ if username.nil? || password.nil?
83
+ bot.say("The twitter plugin is not properly configured!")
84
+ return
85
+ end
86
+
87
+ begin
88
+ Tweet.tweet(tweet.to_s, username, password, proxy)
89
+ bot.say("Ok, tweeted: #{tweet}")
90
+ rescue Tweet::TwitterError => ex
91
+ bot.say("Hmm...didn't work. Got this response:")
92
+ bot.paste(ex.http_result.content)
93
+ end
94
+ end
95
+
96
+ def act_on_list
97
+ list = Tweet.list
98
+ if list.empty?
99
+ bot.say("There are no queued tweets.")
100
+ else
101
+ yield list
102
+ end
103
+ end
104
+
105
+ def act_on_tweet(n)
106
+ if tweet = Tweet.list[n]
107
+ yield tweet
108
+ else
109
+ bot.say("Could not find tweet #{n}")
110
+ end
111
+ end
112
+
113
+ # strip links from CF messages
114
+ def strip_links(msg)
115
+ msg.gsub(/<a href="([^"]*)".*?>.*?<\/a>/, '\1')
116
+ end
117
+ end
@@ -0,0 +1,4 @@
1
+ # Configuration file for TweetPlugin
2
+ username: your_twitter_username
3
+ password: your_twitter_password
4
+ proxy: http://your.proxy.com:8080 # optional