]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - data/rbot/plugins/twitter.rb
twitter: bugfix for invalid OAuth PIN Entry
[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 require 'oauth'
17 require 'yaml'
18 require 'rexml/rexml'
19
20 class TwitterPlugin < Plugin
21    Config.register Config::StringValue.new('twitter.key',
22       :default => "",
23       :desc => "Twitter OAuth Consumer Key")
24
25    Config.register Config::StringValue.new('twitter.secret',
26       :default => "",
27       :desc => "Twitter OAuth Consumer Secret")
28
29     Config.register Config::IntegerValue.new('twitter.status_count',
30       :default => 1, :validate => Proc.new { |v| v > 0 && v <= 10},
31       :desc => "Maximum number of status updates shown by 'twitter status'")
32
33     Config.register Config::IntegerValue.new('twitter.friends_status_count',
34       :default => 3, :validate => Proc.new { |v| v > 0 && v <= 10},
35       :desc => "Maximum number of status updates shown by 'twitter friends status'")
36
37   def initialize
38     super
39
40     class << @registry
41       def store(val)
42         val
43       end
44       def restore(val)
45         val
46       end
47     end
48   end
49
50   def help(plugin, topic="")
51     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 ...)"
52   end
53
54   # update the status on twitter
55   def get_status(m, params)
56     friends = params[:friends]
57     if @registry.has_key?(m.sourcenick + "_access_token")
58       @access_token = YAML::load(@registry[m.sourcenick + "_access_token"])
59       nick = params[:nick] || @access_token.params[:screen_name]
60     else
61       if friends
62         m.reply "You are not authorized with Twitter. Please use 'twitter authorize' first to use this feature."
63         return false
64       end
65       nick = params[:nick]
66     end
67
68     if not nick
69       m.reply "you should specify the username of the twitter to use, or identify using 'twitter authorize'"
70       return false
71     end
72
73     count = @bot.config['twitter.friends_status_count']
74     user = URI.escape(nick)
75     if @registry.has_key?(m.sourcenick + "_access_token")
76         if friends
77           #no change to count variable
78           uri = "https://api.twitter.com/1/statuses/friends_timeline.xml?count=#{count}"
79           response = @access_token.get(uri).body
80         else
81           count = @bot.config['twitter.status_count']
82           uri = "https://api.twitter.com/1/statuses/user_timeline.xml?screen_name=#{user}&count=#{count}"
83           response = @access_token.get(uri).body
84         end
85     else
86        #unauthorized user, will try to get from public timeline the old way
87        uri = "http://twitter.com/statuses/user_timeline/#{user}.xml?count=#{count}"
88        response = @bot.httputil.get(uri, :cache => false)
89     end
90     debug response
91
92     texts = []
93
94     if response
95       begin
96         rex = REXML::Document.new(response)
97         rex.root.elements.each("status") { |st|
98           # month, day, hour, min, sec, year = st.elements['created_at'].text.match(/\w+ (\w+) (\d+) (\d+):(\d+):(\d+) \S+ (\d+)/)[1..6]
99           # debug [year, month, day, hour, min, sec].inspect
100           # time = Time.local(year.to_i, month, day.to_i, hour.to_i, min.to_i, sec.to_i)
101           time = Time.parse(st.elements['created_at'].text)
102           now = Time.now
103           # Sometimes, time can be in the future; invert the relation in this case
104           delta = ((time > now) ? time - now : now - time)
105           msg = st.elements['text'].to_s + " (#{Utils.secs_to_string(delta.to_i)} ago via #{st.elements['source'].to_s})"
106           author = ""
107           if friends
108             author = Utils.decode_html_entities(st.elements['user'].elements['name'].text) + ": " rescue ""
109           end
110           texts << author+Utils.decode_html_entities(msg).ircify_html
111         }
112         if friends
113           # friends always return the latest 20 updates, so we clip the count
114           texts[count..-1]=nil
115         end
116       rescue
117         error $!
118         if friends
119           m.reply "could not parse status for #{nick}'s friends"
120         else
121           m.reply "could not parse status for #{nick}"
122         end
123         return false
124       end
125       m.reply texts.reverse.join("\n")
126       return true
127     else
128       if friends
129         rep = "could not get status for #{nick}'s friends"
130         rep << ", try asking in private" unless m.private?
131       else
132         rep = "could not get status for #{nick}"
133       end
134       m.reply rep
135       return false
136     end
137   end
138
139   def deauthorize(m, params)
140     if @registry.has_key?(m.sourcenick + "_request_token")
141       @registry.delete(m.sourcenick + "_request_token")
142     end
143     if @registry.has_key?(m.sourcenick + "_access_token")
144       @registry.delete(m.sourcenick + "_access_token")
145     end
146     m.reply "Done! You can reauthorize this account in the future by using 'twitter authorize'"
147   end
148
149   def authorize(m, params)
150     #remove all old authorization data
151     if @registry.has_key?(m.sourcenick + "_request_token")
152       @registry.delete(m.sourcenick + "_request_token")
153     end
154     if @registry.has_key?(m.sourcenick + "_access_token")
155       @registry.delete(m.sourcenick + "_access_token")
156     end
157
158     key = @bot.config['twitter.key']
159     secret = @bot.config['twitter.secret']
160     @consumer = OAuth::Consumer.new(key, secret, {   
161       :site => "https://api.twitter.com",
162       :request_token_path => "/oauth/request_token",
163       :access_token_path => "/oauth/access_token",
164       :authorize_path => "/oauth/authorize"
165       } )
166     @request_token = @consumer.get_request_token
167     @registry[m.sourcenick + "_request_token"] = YAML::dump(@request_token)        
168     m.reply "Go to this URL to get your authorization PIN, then use 'twitter pin <pin>' to finish authorization: " + @request_token.authorize_url
169   end
170
171   def pin(m, params)
172      unless @registry.has_key?(m.sourcenick + "_request_token")
173        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"
174        return false
175      end
176      @request_token = YAML::load(@registry[m.sourcenick + "_request_token"])
177      begin
178        @access_token = @request_token.get_access_token( { :oauth_verifier => params[:pin] } )
179      rescue
180        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"
181        return false
182      end
183      @registry[m.sourcenick + "_access_token"] = YAML::dump(@access_token)
184      m.reply "Okay, you're all set"
185   end
186
187   # update the status on twitter
188   def update_status(m, params)
189     unless @registry.has_key?(m.sourcenick + "_access_token")
190        m.reply "You must first authorize your Twitter account before tweeting."
191        return false;
192     end
193     @access_token = YAML::load(@registry[m.sourcenick + "_access_token"])
194
195     uri = "https://api.twitter.com/statuses/update.json"
196     msg = params[:status].to_s
197
198     if msg.length > 140
199       m.reply "your status message update is too long, please keep it under 140 characters"
200       return
201     end
202
203     response = @access_token.post(uri, { :status => msg })
204     debug response
205
206     reply_method = params[:notify] ? :notify : :reply
207     if response.class == Net::HTTPOK
208       m.__send__(reply_method, "status updated")
209     else
210       m.__send__(reply_method, "could not update status")
211     end
212   end
213
214   # ties a nickname to a twitter username and password
215   def identify(m, params)
216     @registry[m.sourcenick + "_username"] = params[:username].to_s
217     @registry[m.sourcenick + "_password"] = params[:password].to_s
218     m.reply "you're all set up!"
219   end
220
221   # update on ACTION if the user has enabled the option
222   def ctcp_listen(m)
223     return unless m.action?
224     return unless @registry[m.sourcenick + "_actions"]
225     update_status(m, :status => m.message, :notify => true)
226   end
227
228   # show or toggle action twitting
229   def actions(m, params)
230     case params[:toggle]
231     when 'on'
232       @registry[m.sourcenick + "_actions"] = true
233       m.okay
234     when 'off'
235       @registry.delete(m.sourcenick + "_actions")
236       m.okay
237     else
238       if @registry[m.sourcenick + "_actions"]
239         m.reply _("actions will be twitted")
240       else
241         m.reply _("actions will not be twitted")
242       end
243     end
244   end
245 end
246
247 # create an instance of our plugin class and register for the "length" command
248 plugin = TwitterPlugin.new
249 plugin.map 'twitter update *status', :action => "update_status", :threaded => true
250 plugin.map 'twitter authorize', :action => "authorize", :public => false
251 plugin.map 'twitter deauthorize', :action => "deauthorize", :public => false
252 plugin.map 'twitter pin :pin', :action => "pin", :public => false
253 plugin.map 'twitter actions [:toggle]', :action => "actions", :requirements => { :toggle => /^on|off$/ }
254 plugin.map 'twitter status [:nick]', :action => "get_status", :threaded => true
255 plugin.map 'twitter :friends [status] [:nick]', :action => "get_status", :requirements => { :friends => /^friends?$/ }, :threaded => true