Skip to content

Commit 7a6c4d9

Browse files
committed
Twitter API v1.1 support, refs #47
1 parent bd54d9b commit 7a6c4d9

1 file changed

Lines changed: 102 additions & 48 deletions

File tree

plugin/tweet_quote.rb

Lines changed: 102 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -18,86 +18,140 @@
1818
require 'pstore'
1919
require 'open-uri'
2020
require 'timeout'
21-
require 'rexml/document'
2221
require 'time'
2322
require 'uri'
23+
require 'openssl'
24+
require 'json'
25+
26+
def twitter_quote_option_keys
27+
%w( consumer_key consumer_secret token token_secret).map{|k| "twitter_quote.oauth_#{k}" }
28+
end
2429

2530
def twitter_statuses_show_api( tweet_id )
26-
url = "http://api.twitter.com/1/statuses/show/#{tweet_id}.xml"
31+
url = "https://api.twitter.com/1.1/statuses/show.json"
32+
unsafe = /[^a-zA-Z0-9\-\.\_\~]/
33+
parameters = {
34+
:id => tweet_id
35+
}
36+
oauth_parameters = {
37+
:oauth_consumer_key => @conf["twitter_quote.oauth_consumer_key"],
38+
:oauth_nonce => OpenSSL::Digest.hexdigest( "MD5", "#{Time.now.to_f}#{rand}" ),
39+
:oauth_signature_method => "HMAC-SHA1",
40+
:oauth_timestamp => Time.now.to_i.to_s,
41+
:oauth_token => @conf["twitter_quote.oauth_token"],
42+
:oauth_version => "1.0"
43+
}
44+
data = "GET&#{URI.escape( url, unsafe )}&"
45+
data << URI.escape( oauth_parameters.merge( parameters ).sort.map{|k, v| "#{k}=#{v}" }.join( "&" ), unsafe )
46+
oauth_parameters[:oauth_signature] = [OpenSSL::HMAC.digest(
47+
OpenSSL::Digest::SHA1.new,
48+
URI.escape( "#{@conf["twitter_quote.oauth_consumer_secret"]}&#{@conf["twitter_quote.oauth_token_secret"]}" ),
49+
data
50+
)].pack( "m" ).chomp
2751

2852
proxy = @conf['proxy']
2953
proxy = 'http://' + proxy if proxy
3054

55+
headers = {
56+
"Authorization" => %Q[OAuth #{oauth_parameters.map{|k ,v| "#{URI.escape( k.to_s, unsafe )}=\"#{URI.escape( v, unsafe )}\""}.join( "," )}],
57+
:proxy => proxy
58+
}
3159
timeout( 20 ) do
32-
open( url, :proxy => proxy ) {|f| f.read }
60+
open( "#{url}?#{parameters.map{|k,v| "#{k}=#{v}"}.join( "&" )}", headers ) {|f| f.read }
3361
end
3462
end
3563

64+
def twitter_status_json_to_html( json )
65+
tweet_id = json['id_str']
66+
screen_name = json['user']['screen_name']
67+
name = json['user']['name']
68+
background_url = json['user']['profile_background_image_url']
69+
profile_background_color = "##{json['user']['profile_background_color']}"
70+
avatar = json['user']['profile_image_url']
71+
source = json['source']
72+
timestamp = Time.parse( json['created_at'] )
73+
content = json['text']
74+
content.gsub!( URI.regexp( %w|http https| ) ){ %Q|<a href="#{$&}">#{$&}</a>| }
75+
content = content.split( /(<[^>]*>)/ ).map {|s|
76+
next s if s[/\A</]
77+
s.gsub!( /@(?>([a-zA-Z0-9_]{1,15}))(?![a-zA-Z0-9_])/ ){ %Q|<a href="http://twitter.com/#{$1}">#{$&}</a>| }
78+
s.gsub( /#([a-zA-Z0-9]{1,16})/ ){ %Q|<a href="http://twitter.com/search?q=%23#{$1}">#{$&}</a>| }
79+
}.join
80+
81+
<<-HTML
82+
<!-- http://twitter.com/#{screen_name}/status/#{tweet_id} -->
83+
<div class="bbpBox" style="background:url(#{background_url}) #{profile_background_color};padding:20px;">
84+
<p class="bbpTweet" style=
85+
"background:#fff;padding:10px 12px 10px 12px;margin:0;min-height:48px;color:#000;font-size:16px !important;line-height:22px;-moz-border-radius:5px;-webkit-border-radius:5px;">
86+
<span class="bbpMetadata" style=
87+
"display:block;width:100%;clear:both;margin-bottom:8px;padding-bottom:12px;height:40px;border-bottom:1px solid #fff;border-bottom:1px solid #e6e6e6;">
88+
<span class="bbpAuthor" style="line-height:19px;"><a href=
89+
"http://twitter.com/#{screen_name}"><img alt="#{name}" src=
90+
"#{avatar}" style=
91+
"float:left;margin:0 17px 0 0;width:38px;height:38px;"></a>
92+
<a href="http://twitter.com/#{screen_name}" style="text-decoration:none">
93+
<strong style="text-decoration:underline">#{name}</strong><br>
94+
@#{screen_name}</a></span></span>
95+
#{content} <span class="bbpTimestamp" style=
96+
"font-size:12px;display:block;"><a title="#{timestamp}" href=
97+
"http://twitter.com/#{screen_name}/status/#{tweet_id}">#{timestamp}</a>
98+
<span style="float:right"><a href=
99+
"https://twitter.com/intent/tweet?in_reply_to=#{tweet_id}">Reply</a> <a href=
100+
"https://twitter.com/intent/retweet?tweet_id=#{tweet_id}">Retweet</a> <a href=
101+
"https://twitter.com/intent/favorite?tweet_id=#{tweet_id}"}>Favorite</a>
102+
</span></span></p>
103+
</div>
104+
<!-- end of tweet -->
105+
HTML
106+
end
36107

37108
def tweet_quote( src )
109+
return unless twitter_quote_option_keys.all?{|v| @options.key? v }
110+
38111
if %r|http(?:s)?://twitter.com/(?:#!/)?[^/]{1,15}/status(?:es)?/([0-9]+)| =~ src.to_s.downcase
39112
src = $1
40113
end
41114

42115
return unless /\A[0-9]+\z/ =~ src.to_s
43116

44-
cache = "#{@cache_path}/blackbird.pstore"
45-
xml = nil
117+
cache = "#{@cache_path}/tweet_quote.pstore"
118+
json = nil
46119

47120
db = PStore.new( cache )
48121
db.transaction do
49122
key = src
50123
db[key] ||= {}
51-
if db[key][:xml]
52-
xml = db[key][:xml]
124+
if db[key][:json] && /\A(?:latest|day|month|nyear)\z/ =~ @mode
125+
json = db[key][:json]
53126
else
54127
begin
55-
xml = twitter_statuses_show_api( src )
128+
json = twitter_statuses_show_api( src )
56129
rescue OpenURI::HTTPError
57-
return %Q|<p class="bbpMessage">#$!</p>|
130+
return %Q|<p class="tweet_quote_error">#$!</p>|
58131
end
59-
db[key][:xml] = xml
132+
db[key][:json] = json
60133
end
61134
end
135+
twitter_status_json_to_html( JSON.parse( json ) )
136+
end
62137

63-
doc = REXML::Document::new( REXML::Source.new( xml ) ).root
64-
65-
tweet_id = doc.elements['//id'].text
66-
screen_name = doc.elements['//user/screen_name'].text
67-
name = doc.elements['//user/name'].text
68-
background_url = doc.elements['//user/profile_background_image_url'].text
69-
profile_background_color = '#' + doc.elements['//user/profile_background_color'].text
70-
avatar = doc.elements['//user/profile_image_url'].text
71-
source = doc.elements['//source'].text
72-
timestamp = Time.parse( doc.elements['//created_at'].text ).to_s
73-
content = doc.elements['//text'].text
74-
content.gsub!( URI.regexp( %w|http https| ) ){ %Q|<a href="#{$&}">#{$&}</a>| }
75-
content = content.split( /(<[^>]*>)/ ).map do |s|
76-
next s if s[/\A</]
77-
s.gsub!( /@(?>([a-zA-Z0-9_]{1,15}))(?![a-zA-Z0-9_])/ ){ %Q|<a href="http://twitter.com/#{$1}">#{$&}</a>| }
78-
s.gsub( /#([a-zA-Z0-9]{1,16})/ ){ %Q|<a href="http://twitter.com/search?q=%23#{$1}">#{$&}</a>| }
79-
end.join
80-
81-
r = <<-HTML
82-
<!-- http://twitter.com/#{screen_name}/status/#{tweet_id} -->
83-
<div class="bbpBox" style=
84-
"background:url(#{background_url}) #{profile_background_color};padding:20px;">
85-
<p class="bbpTweet" style=
86-
"background:#fff;padding:10px 12px 10px 12px;margin:0;min-height:48px;color:#000;font-size:16px !important;line-height:22px;-moz-border-radius:5px;-webkit-border-radius:5px;">
87-
#{content} <span class="bbpTimestamp" style=
88-
"font-size:12px;display:block;"><a title="#{timestamp}" href=
89-
"http://twitter.com/#{screen_name}/status/#{tweet_id}">#{timestamp}</a> via #{source}
90-
</span> <span class="bbpMetadata" style=
91-
"display:block;width:100%;clear:both;margin-top:8px;padding-top:12px;height:40px;border-top:1px solid #fff;border-top:1px solid #e6e6e6;">
92-
<span class="bbpAuthor" style="line-height:19px;"><a href=
93-
"http://twitter.com/#{screen_name}"><img alt="#{name}" src=
94-
"#{avatar}" style=
95-
"float:left;margin:0 7px 0 0;width:38px;height:38px;"></a>
96-
<strong><a href=
97-
"http://twitter.com/#{screen_name}">#{screen_name}</a></strong><br>
98-
#{name}</span></span></p>
99-
</div>
100-
<!-- end of tweet -->
138+
add_conf_proc( 'twitter_quote', 'Embedded Tweets' ) do
139+
if @mode == 'saveconf'
140+
twitter_quote_option_keys.each do |k|
141+
@conf[k] = @cgi.params[k][0]
142+
end
143+
end
144+
<<-HTML
145+
<h2>Twitter OAuth settings</h2>
146+
<h3>Consumer key</h3>
147+
<p><input type="text" name="twitter_quote.oauth_consumer_key" value="#{h @conf["twitter_quote.oauth_consumer_key"]}" size="80"></p>
148+
<h3>Consumer secret</h3>
149+
<p><input type="text" name="twitter_quote.oauth_consumer_secret" value="#{h @conf["twitter_quote.oauth_consumer_secret"]}" size="80"></p>
150+
<h2>Your access token</h2>
151+
<h3>Access token</h3>
152+
<p><input type="text" name="twitter_quote.oauth_token" value="#{h @conf["twitter_quote.oauth_token"]}" size="80"></p>
153+
<h3>Access token secret</h3>
154+
<p><input type="text" name="twitter_quote.oauth_token_secret" value="#{h @conf["twitter_quote.oauth_token_secret"]}" size="80"></p>
101155
HTML
102156
end
103157

0 commit comments

Comments
 (0)