wesabot 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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