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'")
42 loc = Utils.check_location(s, Regexp.new('twitter\.com/#!/.*/status/\d+'))
44 id = loc.first.match(/\/status\/(\d+)/)[1]
46 response = @app_access_token.get('/1.1/statuses/show/'+id).body
48 tweet = JSON.parse(response).first
50 :date => (Time.parse(tweet["created_at"]) rescue "<unknown>"),
51 :id => (tweet["id"].text rescue "<unknown>"),
52 :text => (tweet["text"].ircify_html rescue "<error>"),
53 :source => (tweet["source"].text rescue "<unknown>"),
54 :user => (tweet["user"]["name"] rescue "<unknown>"),
55 :user_nick => (tweet["user"]["screen_name"] rescue "<unknown>")
58 status[:nicedate] = String === status[:date] ? status[:date] : Utils.timeago(status[:date])
60 :title => "#{status[:user]}/#{status[:id]}",
61 :content => "#{status[:text]} (#{status[:nicedate]} via #{status[:source]})"
79 # setup the application authentication
81 key = @bot.config['twitter.key']
82 secret = @bot.config['twitter.secret']
83 @client = OAuth2::Client.new(key, secret,
84 :token_url => '/oauth2/token',
86 @app_access_token = @client.client_credentials.get_token
88 debug "app access-token generated: #{@app_access_token.inspect}"
90 @bot.register_filter(:twitter, :htmlinfo) { |s| twitter_filter(s) }
93 def report_key_missing(m, failed_action)
94 m.reply [failed_action, "no Twitter Consumer Key/Secret is defined"].join(' because ')
97 def help(plugin, topic="")
98 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 ...)"
101 # show latest status of a twitter user or the users timeline/mentions/retweets
102 def get_status(m, params)
103 nick = params[:nick] # (optional)
104 type = params[:type] # (optional) home, mentions, retweets
106 if @registry.has_key?(m.sourcenick + "_access_token")
107 @access_token = YAML::load(@registry[m.sourcenick + "_access_token"])
110 nick = @access_token.params[:screen_name]
113 m.reply "You are not authorized with Twitter. Please use 'twitter authorize' first to use this feature."
117 if not nick and not type
118 m.reply "you should specify the username of the twitter to use, or identify using 'twitter authorize'"
122 # use the application-only authentication
124 @access_token = @app_access_token
127 count = type ? @bot.config['twitter.timeline_status_count'] : @bot.config['twitter.status_count']
128 user = URI.escape(nick)
130 url = "/1.1/statuses/user_timeline.json?screen_name=#{nick}&count=#{count}&include_rts=true"
131 elsif type == 'retweets'
132 url = "/1.1/statuses/retweets_of_me.json?count=#{count}&include_rts=true"
134 url = "/1.1/statuses/#{type || 'user'}_timeline.json?count=#{count}&include_rts=true"
136 response = @access_token.get(url).body
142 tweets = JSON.parse(response)
143 if tweets.class == Array
144 tweets.each do |tweet|
145 time = Time.parse(tweet['created_at'])
147 # Sometimes, time can be in the future; invert the relation in this case
148 delta = ((time > now) ? time - now : now - time)
149 msg = tweet['text'] + " (#{Utils.secs_to_string(delta.to_i)} ago via #{tweet['source'].to_s})"
152 author = tweet['user']['name'] + ": " rescue ""
154 texts << author+Utils.decode_html_entities(msg).ircify_html
157 raise 'timeline response: ' + response
160 # friends always return the latest 20 updates, so we clip the count
166 m.reply "could not parse status for #{nick}'s timeline"
168 m.reply "could not parse status for #{nick}"
173 m.reply "No status updates!"
175 m.reply texts.reverse.join("\n")
180 rep = "could not get status for #{nick}'s #{type} timeline"
181 rep << ", try asking in private" unless m.private?
183 rep = "could not get status for #{nick}"
190 def deauthorize(m, params)
191 if @registry.has_key?(m.sourcenick + "_request_token")
192 @registry.delete(m.sourcenick + "_request_token")
194 if @registry.has_key?(m.sourcenick + "_access_token")
195 @registry.delete(m.sourcenick + "_access_token")
197 m.reply "Done! You can reauthorize this account in the future by using 'twitter authorize'"
200 def authorize(m, params)
201 failed_action = "we can't complete the authorization process"
203 #remove all old authorization data
204 if @registry.has_key?(m.sourcenick + "_request_token")
205 @registry.delete(m.sourcenick + "_request_token")
207 if @registry.has_key?(m.sourcenick + "_access_token")
208 @registry.delete(m.sourcenick + "_access_token")
211 key = @bot.config['twitter.key']
212 secret = @bot.config['twitter.secret']
213 if key.empty? or secret.empty?
214 report_key_missing(m, failed_action)
218 @consumer = OAuth::Consumer.new(key, secret, {
220 :request_token_path => "/oauth/request_token",
221 :access_token_path => "/oauth/access_token",
222 :authorize_path => "/oauth/authorize"
225 @request_token = @consumer.get_request_token
226 rescue OAuth::Unauthorized
227 m.reply _("My authorization failed! Did you block me? Or is my Twitter Consumer Key/Secret pair incorrect?")
230 @registry[m.sourcenick + "_request_token"] = YAML::dump(@request_token)
231 m.reply "Go to this URL to get your authorization PIN, then use 'twitter pin <pin>' to finish authorization: " + @request_token.authorize_url
235 unless @registry.has_key?(m.sourcenick + "_request_token")
236 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"
239 @request_token = YAML::load(@registry[m.sourcenick + "_request_token"])
241 @access_token = @request_token.get_access_token( { :oauth_verifier => params[:pin] } )
243 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"
246 @registry[m.sourcenick + "_access_token"] = YAML::dump(@access_token)
247 m.reply "Okay, you're all set"
250 # update the status on twitter
251 def update_status(m, params)
252 unless @registry.has_key?(m.sourcenick + "_access_token")
253 m.reply "You must first authorize your Twitter account before tweeting."
256 @access_token = YAML::load(@registry[m.sourcenick + "_access_token"])
258 #uri = URL + '/statuses/update.json'
259 status = params[:status].to_s
261 if status.length > 140
262 m.reply "your status message update is too long, please keep it under 140 characters"
266 response = @access_token.post('/1.1/statuses/update.json', { :status => status })
269 reply_method = params[:notify] ? :notify : :reply
270 if response.class == Net::HTTPOK
271 m.__send__(reply_method, "status updated")
273 debug 'twitter update response: ' + response.body
276 json = JSON.parse(response.body)
277 error = json['errors'].first['message'] || '?'
280 m.__send__(reply_method, "could not update status: #{error}")
284 # ties a nickname to a twitter username and password
285 def identify(m, params)
286 @registry[m.sourcenick + "_username"] = params[:username].to_s
287 @registry[m.sourcenick + "_password"] = params[:password].to_s
288 m.reply "you're all set up!"
291 # update on ACTION if the user has enabled the option
293 return unless m.action?
294 return unless @registry[m.sourcenick + "_actions"]
295 update_status(m, :status => m.message, :notify => true)
298 # show or toggle action twitting
299 def actions(m, params)
302 @registry[m.sourcenick + "_actions"] = true
305 @registry.delete(m.sourcenick + "_actions")
308 if @registry[m.sourcenick + "_actions"]
309 m.reply _("actions will be twitted")
311 m.reply _("actions will not be twitted")
317 # create an instance of our plugin class and register for the "length" command
318 plugin = TwitterPlugin.new
319 plugin.map 'twitter update *status', :action => "update_status", :threaded => true
320 plugin.map 'twitter authorize', :action => "authorize", :public => false
321 plugin.map 'twitter deauthorize', :action => "deauthorize", :public => false
322 plugin.map 'twitter pin :pin', :action => "pin", :public => false
323 plugin.map 'twitter actions [:toggle]', :action => "actions", :requirements => { :toggle => /^on|off$/ }
324 plugin.map 'twitter status [:nick]', :action => "get_status", :threaded => true
325 plugin.map 'twitter :type [status] [:nick]', :action => "get_status", :requirements => { :type => /^(home|mentions|retweets)?$/ }, :threaded => true