]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - data/rbot/plugins/twitter.rb
Ruby 1.9 cleanup: variables warnings
[user/henk/code/ruby/rbot.git] / data / rbot / plugins / twitter.rb
1 #-- vim:sw=2:et
2 #++
3 #
4 # :title: Twitter Status Update for rbot
5 #
6 # Author:: Carter Parks (carterparks) <carter@carterparks.com>
7 # Author:: Giuseppe "Oblomov" Bilotta <giuseppe.bilotta@gmail.com>
8 # Author:: NeoLobster <neolobster@snugglenets.com>
9 #
10 # Copyright:: (C) 2007 Carter Parks
11 # Copyright:: (C) 2007 Giuseppe Bilotta
12 #
13 # Users can setup their twitter username and password and then begin updating
14 # twitter whenever
15
16 begin
17   require 'oauth'
18 rescue LoadError
19   error "OAuth module could not be loaded, twits will not be submitted and protected twits will not be accessible"
20 end
21
22 require 'yaml'
23 require 'rexml/rexml'
24
25 class TwitterPlugin < Plugin
26    Config.register Config::StringValue.new('twitter.key',
27       :default => "",
28       :desc => "Twitter OAuth Consumer Key")
29
30    Config.register Config::StringValue.new('twitter.secret',
31       :default => "",
32       :desc => "Twitter OAuth Consumer Secret")
33
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'")
37
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'")
41
42   def initialize
43     super
44
45     @has_oauth = defined? OAuth
46
47     class << @registry
48       def store(val)
49         val
50       end
51       def restore(val)
52         val
53       end
54     end
55   end
56
57   def report_oauth_missing(m, failed_action)
58     m.reply [failed_action, "I cannot authenticate to Twitter (OAuth not available)"].join(' because ')
59   end
60
61   def report_key_missing(m, failed_action)
62     m.reply [failed_action, "no Twitter Consumer Key/Secret is defined"].join(' because ')
63   end
64
65   def help(plugin, topic="")
66     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 ...)"
67   end
68
69   # update the status on twitter
70   def get_status(m, params)
71     friends = params[:friends]
72
73     if @registry.has_key?(m.sourcenick + "_access_token")
74       @access_token = YAML::load(@registry[m.sourcenick + "_access_token"])
75       nick = params[:nick] || @access_token.params[:screen_name]
76     else
77       if friends
78         if @has_oauth
79           m.reply "You are not authorized with Twitter. Please use 'twitter authorize' first to use this feature."
80         else
81           report_oauth_missing(m, "I cannot retrieve your friends status")
82         end
83         return false
84       end
85       nick = params[:nick]
86     end
87
88     if not nick
89       m.reply "you should specify the username of the twitter to use, or identify using 'twitter authorize'"
90       return false
91     end
92
93     count = friends ? @bot.config['twitter.friends_status_count'] : @bot.config['twitter.status_count']
94     user = URI.escape(nick)
95     if @has_oauth and @registry.has_key?(m.sourcenick + "_access_token")
96         if friends
97           #no change to count variable
98           uri = "https://api.twitter.com/1/statuses/friends_timeline.xml?count=#{count}"
99           response = @access_token.get(uri).body
100         else
101           uri = "https://api.twitter.com/1/statuses/user_timeline.xml?screen_name=#{user}&count=#{count}"
102           response = @access_token.get(uri).body
103         end
104     else
105        #unauthorized user, will try to get from public timeline the old way
106        uri = "http://twitter.com/statuses/user_timeline/#{user}.xml?count=#{count}"
107        response = @bot.httputil.get(uri, :cache => false)
108     end
109     debug response
110
111     texts = []
112
113     if response
114       begin
115         rex = REXML::Document.new(response)
116         rex.root.elements.each("status") { |st|
117           # month, day, hour, min, sec, year = st.elements['created_at'].text.match(/\w+ (\w+) (\d+) (\d+):(\d+):(\d+) \S+ (\d+)/)[1..6]
118           # debug [year, month, day, hour, min, sec].inspect
119           # time = Time.local(year.to_i, month, day.to_i, hour.to_i, min.to_i, sec.to_i)
120           time = Time.parse(st.elements['created_at'].text)
121           now = Time.now
122           # Sometimes, time can be in the future; invert the relation in this case
123           delta = ((time > now) ? time - now : now - time)
124           msg = st.elements['text'].to_s + " (#{Utils.secs_to_string(delta.to_i)} ago via #{st.elements['source'].to_s})"
125           author = ""
126           if friends
127             author = Utils.decode_html_entities(st.elements['user'].elements['name'].text) + ": " rescue ""
128           end
129           texts << author+Utils.decode_html_entities(msg).ircify_html
130         }
131         if friends
132           # friends always return the latest 20 updates, so we clip the count
133           texts[count..-1]=nil
134         end
135       rescue
136         error $!
137         if friends
138           m.reply "could not parse status for #{nick}'s friends"
139         else
140           m.reply "could not parse status for #{nick}"
141         end
142         return false
143       end
144       if texts.empty?
145         m.reply "No status updates!"
146       else
147         m.reply texts.reverse.join("\n")
148       end
149       return true
150     else
151       if friends
152         rep = "could not get status for #{nick}'s friends"
153         rep << ", try asking in private" unless m.private?
154       else
155         rep = "could not get status for #{nick}"
156       end
157       m.reply rep
158       return false
159     end
160   end
161
162   def deauthorize(m, params)
163     if @registry.has_key?(m.sourcenick + "_request_token")
164       @registry.delete(m.sourcenick + "_request_token")
165     end
166     if @registry.has_key?(m.sourcenick + "_access_token")
167       @registry.delete(m.sourcenick + "_access_token")
168     end
169     m.reply "Done! You can reauthorize this account in the future by using 'twitter authorize'"
170   end
171
172   def authorize(m, params)
173     failed_action = "we can't complete the authorization process"
174     unless @has_oauth
175       report_oauth_missing(m, failed_action)
176       return false
177     end
178
179     #remove all old authorization data
180     if @registry.has_key?(m.sourcenick + "_request_token")
181       @registry.delete(m.sourcenick + "_request_token")
182     end
183     if @registry.has_key?(m.sourcenick + "_access_token")
184       @registry.delete(m.sourcenick + "_access_token")
185     end
186
187     key = @bot.config['twitter.key']
188     secret = @bot.config['twitter.secret']
189     if key.empty? or secret.empty?
190       report_key_missing(m, failed_action)
191       return false
192     end
193
194     @consumer = OAuth::Consumer.new(key, secret, {
195       :site => "https://api.twitter.com",
196       :request_token_path => "/oauth/request_token",
197       :access_token_path => "/oauth/access_token",
198       :authorize_path => "/oauth/authorize"
199     } )
200     begin
201       @request_token = @consumer.get_request_token
202     rescue OAuth::Unauthorized
203       m.reply _("My authorization failed! Did you block me? Or is my Twitter Consumer Key/Secret pair incorrect?")
204       return false
205     end
206     @registry[m.sourcenick + "_request_token"] = YAML::dump(@request_token)
207     m.reply "Go to this URL to get your authorization PIN, then use 'twitter pin <pin>' to finish authorization: " + @request_token.authorize_url
208   end
209
210   def pin(m, params)
211      unless @registry.has_key?(m.sourcenick + "_request_token")
212        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"
213        return false
214      end
215      @request_token = YAML::load(@registry[m.sourcenick + "_request_token"])
216      begin
217        @access_token = @request_token.get_access_token( { :oauth_verifier => params[:pin] } )
218      rescue
219        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"
220        return false
221      end
222      @registry[m.sourcenick + "_access_token"] = YAML::dump(@access_token)
223      m.reply "Okay, you're all set"
224   end
225
226   # update the status on twitter
227   def update_status(m, params)
228     unless @has_oauth
229       report_oauth_missing(m, "I cannot update your status")
230       return false
231     end
232
233     unless @registry.has_key?(m.sourcenick + "_access_token")
234        m.reply "You must first authorize your Twitter account before tweeting."
235        return false;
236     end
237     @access_token = YAML::load(@registry[m.sourcenick + "_access_token"])
238
239     uri = "https://api.twitter.com/statuses/update.json"
240     msg = params[:status].to_s
241
242     if msg.length > 140
243       m.reply "your status message update is too long, please keep it under 140 characters"
244       return
245     end
246
247     response = @access_token.post(uri, { :status => msg })
248     debug response
249
250     reply_method = params[:notify] ? :notify : :reply
251     if response.class == Net::HTTPOK
252       m.__send__(reply_method, "status updated")
253     else
254       m.__send__(reply_method, "could not update status")
255     end
256   end
257
258   # ties a nickname to a twitter username and password
259   def identify(m, params)
260     @registry[m.sourcenick + "_username"] = params[:username].to_s
261     @registry[m.sourcenick + "_password"] = params[:password].to_s
262     m.reply "you're all set up!"
263   end
264
265   # update on ACTION if the user has enabled the option
266   # Possible TODO: move the has_oauth check further down and alert
267   # the user the first time we do not update because of the missing oauth
268   def ctcp_listen(m)
269     return unless @has_oauth
270     return unless m.action?
271     return unless @registry[m.sourcenick + "_actions"]
272     update_status(m, :status => m.message, :notify => true)
273   end
274
275   # show or toggle action twitting
276   def actions(m, params)
277     case params[:toggle]
278     when 'on'
279       @registry[m.sourcenick + "_actions"] = true
280       m.okay
281     when 'off'
282       @registry.delete(m.sourcenick + "_actions")
283       m.okay
284     else
285       if @registry[m.sourcenick + "_actions"]
286         m.reply _("actions will be twitted")
287       else
288         m.reply _("actions will not be twitted")
289       end
290     end
291   end
292 end
293
294 # create an instance of our plugin class and register for the "length" command
295 plugin = TwitterPlugin.new
296 plugin.map 'twitter update *status', :action => "update_status", :threaded => true
297 plugin.map 'twitter authorize', :action => "authorize", :public => false
298 plugin.map 'twitter deauthorize', :action => "deauthorize", :public => false
299 plugin.map 'twitter pin :pin', :action => "pin", :public => false
300 plugin.map 'twitter actions [:toggle]', :action => "actions", :requirements => { :toggle => /^on|off$/ }
301 plugin.map 'twitter status [:nick]', :action => "get_status", :threaded => true
302 plugin.map 'twitter :friends [status] [:nick]', :action => "get_status", :requirements => { :friends => /^friends?$/ }, :threaded => true