4 # :title: Twitter Status Update for rbot
6 # Author:: Carter Parks (carterparks) <carter@carterparks.com>
7 # Author:: Giuseppe "Oblomov" Bilotta <giuseppe.bilotta@gmail.com>
8 # Author:: NeoLobster <neolobster@snugglenets.com>
10 # Copyright:: (C) 2007 Carter Parks
11 # Copyright:: (C) 2007 Giuseppe Bilotta
13 # Users can setup their twitter username and password and then begin updating
19 error "OAuth module could not be loaded, twits will not be submitted and protected twits will not be accessible"
25 class TwitterPlugin < Plugin
26 Config.register Config::StringValue.new('twitter.key',
28 :desc => "Twitter OAuth Consumer Key")
30 Config.register Config::StringValue.new('twitter.secret',
32 :desc => "Twitter OAuth Consumer Secret")
34 Config.register Config::IntegerValue.new('twitter.status_count',
35 :default => 1, :validate => Proc.new { |v| v > 0 && v <= 10},
36 :desc => "Maximum number of status updates shown by 'twitter status'")
38 Config.register Config::IntegerValue.new('twitter.friends_status_count',
39 :default => 3, :validate => Proc.new { |v| v > 0 && v <= 10},
40 :desc => "Maximum number of status updates shown by 'twitter friends status'")
45 @has_oauth = defined? OAuth
57 def report_oauth_missing(m, failed_action)
58 m.reply [failed_action, "I cannot authenticate to Twitter (OAuth not available)"].join(' because ')
61 def help(plugin, topic="")
62 return "twitter status [nick] => show nick's (or your) status, use 'twitter friends status [nick]' to also show the friends' timeline | twitter update [status] => updates your status on twitter | twitter authorize => Generates an authorization URL which will give you a PIN to authorize the bot to use your twitter account. | twitter pin [pin] => Finishes bot authorization using the PIN provided by the URL from twitter authorize. | twitter deauthorize => Makes the bot forget your Twitter account. | twitter actions [on|off] => enable/disable twitting of actions (/me does ...)"
65 # update the status on twitter
66 def get_status(m, params)
67 friends = params[:friends]
69 if @registry.has_key?(m.sourcenick + "_access_token")
70 @access_token = YAML::load(@registry[m.sourcenick + "_access_token"])
71 nick = params[:nick] || @access_token.params[:screen_name]
75 m.reply "You are not authorized with Twitter. Please use 'twitter authorize' first to use this feature."
77 report_oauth_missing(m, "I cannot retrieve your friends status")
85 m.reply "you should specify the username of the twitter to use, or identify using 'twitter authorize'"
89 count = @bot.config['twitter.friends_status_count']
90 user = URI.escape(nick)
91 if @has_oauth and @registry.has_key?(m.sourcenick + "_access_token")
93 #no change to count variable
94 uri = "https://api.twitter.com/1/statuses/friends_timeline.xml?count=#{count}"
95 response = @access_token.get(uri).body
97 count = @bot.config['twitter.status_count']
98 uri = "https://api.twitter.com/1/statuses/user_timeline.xml?screen_name=#{user}&count=#{count}"
99 response = @access_token.get(uri).body
102 #unauthorized user, will try to get from public timeline the old way
103 uri = "http://twitter.com/statuses/user_timeline/#{user}.xml?count=#{count}"
104 response = @bot.httputil.get(uri, :cache => false)
112 rex = REXML::Document.new(response)
113 rex.root.elements.each("status") { |st|
114 # month, day, hour, min, sec, year = st.elements['created_at'].text.match(/\w+ (\w+) (\d+) (\d+):(\d+):(\d+) \S+ (\d+)/)[1..6]
115 # debug [year, month, day, hour, min, sec].inspect
116 # time = Time.local(year.to_i, month, day.to_i, hour.to_i, min.to_i, sec.to_i)
117 time = Time.parse(st.elements['created_at'].text)
119 # Sometimes, time can be in the future; invert the relation in this case
120 delta = ((time > now) ? time - now : now - time)
121 msg = st.elements['text'].to_s + " (#{Utils.secs_to_string(delta.to_i)} ago via #{st.elements['source'].to_s})"
124 author = Utils.decode_html_entities(st.elements['user'].elements['name'].text) + ": " rescue ""
126 texts << author+Utils.decode_html_entities(msg).ircify_html
129 # friends always return the latest 20 updates, so we clip the count
135 m.reply "could not parse status for #{nick}'s friends"
137 m.reply "could not parse status for #{nick}"
142 m.reply "No status updates!"
144 m.reply texts.reverse.join("\n")
149 rep = "could not get status for #{nick}'s friends"
150 rep << ", try asking in private" unless m.private?
152 rep = "could not get status for #{nick}"
159 def deauthorize(m, params)
160 if @registry.has_key?(m.sourcenick + "_request_token")
161 @registry.delete(m.sourcenick + "_request_token")
163 if @registry.has_key?(m.sourcenick + "_access_token")
164 @registry.delete(m.sourcenick + "_access_token")
166 m.reply "Done! You can reauthorize this account in the future by using 'twitter authorize'"
169 def authorize(m, params)
171 report_oauth_missing(m, "we can't complete the authorization process")
175 #remove all old authorization data
176 if @registry.has_key?(m.sourcenick + "_request_token")
177 @registry.delete(m.sourcenick + "_request_token")
179 if @registry.has_key?(m.sourcenick + "_access_token")
180 @registry.delete(m.sourcenick + "_access_token")
183 key = @bot.config['twitter.key']
184 secret = @bot.config['twitter.secret']
185 @consumer = OAuth::Consumer.new(key, secret, {
186 :site => "https://api.twitter.com",
187 :request_token_path => "/oauth/request_token",
188 :access_token_path => "/oauth/access_token",
189 :authorize_path => "/oauth/authorize"
191 @request_token = @consumer.get_request_token
192 @registry[m.sourcenick + "_request_token"] = YAML::dump(@request_token)
193 m.reply "Go to this URL to get your authorization PIN, then use 'twitter pin <pin>' to finish authorization: " + @request_token.authorize_url
197 unless @registry.has_key?(m.sourcenick + "_request_token")
198 m.reply "You must first use twitter authorize to get an authorization URL, which you can use to get a PIN for me to use to verify your Twitter account"
201 @request_token = YAML::load(@registry[m.sourcenick + "_request_token"])
203 @access_token = @request_token.get_access_token( { :oauth_verifier => params[:pin] } )
205 m.reply "Error: There was a problem registering your Twitter account. Please make sure you have the right PIN. If the problem persists, use twitter authorize again to get a new PIN"
208 @registry[m.sourcenick + "_access_token"] = YAML::dump(@access_token)
209 m.reply "Okay, you're all set"
212 # update the status on twitter
213 def update_status(m, params)
215 report_oauth_missing(m, "I cannot update your status")
219 unless @registry.has_key?(m.sourcenick + "_access_token")
220 m.reply "You must first authorize your Twitter account before tweeting."
223 @access_token = YAML::load(@registry[m.sourcenick + "_access_token"])
225 uri = "https://api.twitter.com/statuses/update.json"
226 msg = params[:status].to_s
229 m.reply "your status message update is too long, please keep it under 140 characters"
233 response = @access_token.post(uri, { :status => msg })
236 reply_method = params[:notify] ? :notify : :reply
237 if response.class == Net::HTTPOK
238 m.__send__(reply_method, "status updated")
240 m.__send__(reply_method, "could not update status")
244 # ties a nickname to a twitter username and password
245 def identify(m, params)
246 @registry[m.sourcenick + "_username"] = params[:username].to_s
247 @registry[m.sourcenick + "_password"] = params[:password].to_s
248 m.reply "you're all set up!"
251 # update on ACTION if the user has enabled the option
252 # Possible TODO: move the has_oauth check further down and alert
253 # the user the first time we do not update because of the missing oauth
255 return unless @has_oauth
256 return unless m.action?
257 return unless @registry[m.sourcenick + "_actions"]
258 update_status(m, :status => m.message, :notify => true)
261 # show or toggle action twitting
262 def actions(m, params)
265 @registry[m.sourcenick + "_actions"] = true
268 @registry.delete(m.sourcenick + "_actions")
271 if @registry[m.sourcenick + "_actions"]
272 m.reply _("actions will be twitted")
274 m.reply _("actions will not be twitted")
280 # create an instance of our plugin class and register for the "length" command
281 plugin = TwitterPlugin.new
282 plugin.map 'twitter update *status', :action => "update_status", :threaded => true
283 plugin.map 'twitter authorize', :action => "authorize", :public => false
284 plugin.map 'twitter deauthorize', :action => "deauthorize", :public => false
285 plugin.map 'twitter pin :pin', :action => "pin", :public => false
286 plugin.map 'twitter actions [:toggle]', :action => "actions", :requirements => { :toggle => /^on|off$/ }
287 plugin.map 'twitter status [:nick]', :action => "get_status", :threaded => true
288 plugin.map 'twitter :friends [status] [:nick]', :action => "get_status", :requirements => { :friends => /^friends?$/ }, :threaded => true