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>
9 # Author:: Matthias Hecker <apoc@sixserv.org>
11 # Copyright:: (C) 2007 Carter Parks
12 # Copyright:: (C) 2007 Giuseppe Bilotta
14 # Users can setup their twitter username and password and then begin updating
22 class TwitterPlugin < Plugin
23 URL = 'https://api.twitter.com'
25 Config.register Config::StringValue.new('twitter.key',
26 :default => "BdCN4FCokm9hkf8sIDmIJA",
27 :desc => "Twitter OAuth Consumer Key")
29 Config.register Config::StringValue.new('twitter.secret',
30 :default => "R4V00wUdEXlMr38SKOQR9UFQLqAmc3P7cpft7ohuqo",
31 :desc => "Twitter OAuth Consumer Secret")
33 Config.register Config::IntegerValue.new('twitter.status_count',
34 :default => 1, :validate => Proc.new { |v| v > 0 && v <= 10},
35 :desc => "Maximum number of status updates shown by 'twitter status'")
37 Config.register Config::IntegerValue.new('twitter.timeline_status_count',
38 :default => 3, :validate => Proc.new { |v| v > 0 && v <= 10},
39 :desc => "Maximum number of status updates shown by 'twitter [home|mentions|retweets] status'")
41 URL_PATTERN = %r{twitter\.com/([^/]+)(?:/status/(\d+))?}
44 loc = Utils.check_location(s, URL_PATTERN)
46 matches = loc.first.match URL_PATTERN
47 if matches[2] # status id matched
49 url = '/1.1/statuses/show/%s.json' % id
50 else # no status id, get the latest status of that user
52 url = '/1.1/statuses/user_timeline.json?screen_name=%s&count=1&include_rts=true' % user
54 response = @app_access_token.get(url).body
56 tweet = JSON.parse(response)
57 tweet = tweet.first if tweet.instance_of? Array
59 :date => (Time.parse(tweet["created_at"]) rescue "<unknown>"),
60 :id => (tweet["id_str"] rescue "<unknown>"),
61 :text => (tweet["text"].ircify_html rescue "<error>"),
62 :source => (tweet["source"].ircify_html rescue "<unknown>"),
63 :user => (tweet["user"]["name"] rescue "<unknown>"),
64 :user_nick => (tweet["user"]["screen_name"] rescue "<unknown>")
67 status[:nicedate] = String === status[:date] ? status[:date] : Utils.timeago(status[:date])
69 :title => "@#{status[:user_nick]}",
70 :content => "#{status[:text]} (#{status[:nicedate]} via #{status[:source]})"
88 # setup the application authentication
90 key = @bot.config['twitter.key']
91 secret = @bot.config['twitter.secret']
92 @client = OAuth2::Client.new(key, secret,
93 :token_url => '/oauth2/token',
95 @app_access_token = @client.client_credentials.get_token
97 debug "app access-token generated: #{@app_access_token.inspect}"
99 @bot.register_filter(:twitter, :htmlinfo) { |s| twitter_filter(s) }
102 def report_key_missing(m, failed_action)
103 m.reply [failed_action, "no Twitter Consumer Key/Secret is defined"].join(' because ')
106 def help(plugin, topic="")
107 return "twitter status [nick] => show nick's (or your) status, use 'twitter [home/mentions/retweets] status' to show your 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 ...)"
110 # show latest status of a twitter user or the users timeline/mentions/retweets
111 def get_status(m, params)
112 nick = params[:nick] # (optional)
113 type = params[:type] # (optional) home, mentions, retweets
115 if @registry.has_key?(m.sourcenick + "_access_token")
116 @access_token = YAML::load(@registry[m.sourcenick + "_access_token"])
119 nick = @access_token.params[:screen_name]
122 m.reply "You are not authorized with Twitter. Please use 'twitter authorize' first to use this feature."
126 if not nick and not type
127 m.reply "you should specify the username of the twitter to use, or identify using 'twitter authorize'"
131 # use the application-only authentication
133 @access_token = @app_access_token
136 count = type ? @bot.config['twitter.timeline_status_count'] : @bot.config['twitter.status_count']
137 user = URI.escape(nick)
139 url = "/1.1/statuses/user_timeline.json?screen_name=#{nick}&count=#{count}&include_rts=true"
140 elsif type == 'retweets'
141 url = "/1.1/statuses/retweets_of_me.json?count=#{count}&include_rts=true"
143 url = "/1.1/statuses/#{type || 'user'}_timeline.json?count=#{count}&include_rts=true"
145 response = @access_token.get(url).body
151 tweets = JSON.parse(response)
152 if tweets.class == Array
153 tweets.each do |tweet|
154 time = Time.parse(tweet['created_at'])
156 # Sometimes, time can be in the future; invert the relation in this case
157 delta = ((time > now) ? time - now : now - time)
158 msg = tweet['text'] + " (#{Utils.secs_to_string(delta.to_i)} ago via #{tweet['source'].to_s})"
161 author = tweet['user']['name'] + ": " rescue ""
163 texts << author+Utils.decode_html_entities(msg).ircify_html
166 raise 'timeline response: ' + response
169 # friends always return the latest 20 updates, so we clip the count
175 m.reply "could not parse status for #{nick}'s timeline"
177 m.reply "could not parse status for #{nick}"
182 m.reply "No status updates!"
184 m.reply texts.reverse.join("\n")
189 rep = "could not get status for #{nick}'s #{type} timeline"
190 rep << ", try asking in private" unless m.private?
192 rep = "could not get status for #{nick}"
199 def deauthorize(m, params)
200 if @registry.has_key?(m.sourcenick + "_request_token")
201 @registry.delete(m.sourcenick + "_request_token")
203 if @registry.has_key?(m.sourcenick + "_access_token")
204 @registry.delete(m.sourcenick + "_access_token")
206 m.reply "Done! You can reauthorize this account in the future by using 'twitter authorize'"
209 def authorize(m, params)
210 failed_action = "we can't complete the authorization process"
212 #remove all old authorization data
213 if @registry.has_key?(m.sourcenick + "_request_token")
214 @registry.delete(m.sourcenick + "_request_token")
216 if @registry.has_key?(m.sourcenick + "_access_token")
217 @registry.delete(m.sourcenick + "_access_token")
220 key = @bot.config['twitter.key']
221 secret = @bot.config['twitter.secret']
222 if key.empty? or secret.empty?
223 report_key_missing(m, failed_action)
227 @consumer = OAuth::Consumer.new(key, secret, {
229 :request_token_path => "/oauth/request_token",
230 :access_token_path => "/oauth/access_token",
231 :authorize_path => "/oauth/authorize"
234 @request_token = @consumer.get_request_token
235 rescue OAuth::Unauthorized
236 m.reply _("My authorization failed! Did you block me? Or is my Twitter Consumer Key/Secret pair incorrect?")
239 @registry[m.sourcenick + "_request_token"] = YAML::dump(@request_token)
240 m.reply "Go to this URL to get your authorization PIN, then use 'twitter pin <pin>' to finish authorization: " + @request_token.authorize_url
244 unless @registry.has_key?(m.sourcenick + "_request_token")
245 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"
248 @request_token = YAML::load(@registry[m.sourcenick + "_request_token"])
250 @access_token = @request_token.get_access_token( { :oauth_verifier => params[:pin] } )
252 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"
255 @registry[m.sourcenick + "_access_token"] = YAML::dump(@access_token)
256 m.reply "Okay, you're all set"
259 # update the status on twitter
260 def update_status(m, params)
261 unless @registry.has_key?(m.sourcenick + "_access_token")
262 m.reply "You must first authorize your Twitter account before tweeting."
265 @access_token = YAML::load(@registry[m.sourcenick + "_access_token"])
267 #uri = URL + '/statuses/update.json'
268 status = params[:status].to_s
270 if status.length > 140
271 m.reply "your status message update is too long, please keep it under 140 characters"
275 response = @access_token.post('/1.1/statuses/update.json', { :status => status })
278 reply_method = params[:notify] ? :notify : :reply
279 if response.class == Net::HTTPOK
280 m.__send__(reply_method, "status updated")
282 debug 'twitter update response: ' + response.body
285 json = JSON.parse(response.body)
286 error = json['errors'].first['message'] || '?'
289 m.__send__(reply_method, "could not update status: #{error}")
293 # ties a nickname to a twitter username and password
294 def identify(m, params)
295 @registry[m.sourcenick + "_username"] = params[:username].to_s
296 @registry[m.sourcenick + "_password"] = params[:password].to_s
297 m.reply "you're all set up!"
300 # update on ACTION if the user has enabled the option
302 return unless m.action?
303 return unless @registry[m.sourcenick + "_actions"]
304 update_status(m, :status => m.message, :notify => true)
307 # show or toggle action twitting
308 def actions(m, params)
311 @registry[m.sourcenick + "_actions"] = true
314 @registry.delete(m.sourcenick + "_actions")
317 if @registry[m.sourcenick + "_actions"]
318 m.reply _("actions will be twitted")
320 m.reply _("actions will not be twitted")
326 # create an instance of our plugin class and register for the "length" command
327 plugin = TwitterPlugin.new
328 plugin.map 'twitter update *status', :action => "update_status", :threaded => true
329 plugin.map 'twitter authorize', :action => "authorize", :public => false
330 plugin.map 'twitter deauthorize', :action => "deauthorize", :public => false
331 plugin.map 'twitter pin :pin', :action => "pin", :public => false
332 plugin.map 'twitter actions [:toggle]', :action => "actions", :requirements => { :toggle => /^on|off$/ }
333 plugin.map 'twitter status [:nick]', :action => "get_status", :threaded => true
334 plugin.map 'twitter :type [status] [:nick]', :action => "get_status", :requirements => { :type => /^(home|mentions|retweets)?$/ }, :threaded => true