|
18 | 18 | require 'pstore' |
19 | 19 | require 'open-uri' |
20 | 20 | require 'timeout' |
21 | | -require 'rexml/document' |
22 | 21 | require 'time' |
23 | 22 | 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 |
24 | 29 |
|
25 | 30 | 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 |
27 | 51 |
|
28 | 52 | proxy = @conf['proxy'] |
29 | 53 | proxy = 'http://' + proxy if proxy |
30 | 54 |
|
| 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 | + } |
31 | 59 | 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 } |
33 | 61 | end |
34 | 62 | end |
35 | 63 |
|
| 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 |
36 | 107 |
|
37 | 108 | def tweet_quote( src ) |
| 109 | + return unless twitter_quote_option_keys.all?{|v| @options.key? v } |
| 110 | + |
38 | 111 | if %r|http(?:s)?://twitter.com/(?:#!/)?[^/]{1,15}/status(?:es)?/([0-9]+)| =~ src.to_s.downcase |
39 | 112 | src = $1 |
40 | 113 | end |
41 | 114 |
|
42 | 115 | return unless /\A[0-9]+\z/ =~ src.to_s |
43 | 116 |
|
44 | | - cache = "#{@cache_path}/blackbird.pstore" |
45 | | - xml = nil |
| 117 | + cache = "#{@cache_path}/tweet_quote.pstore" |
| 118 | + json = nil |
46 | 119 |
|
47 | 120 | db = PStore.new( cache ) |
48 | 121 | db.transaction do |
49 | 122 | key = src |
50 | 123 | 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] |
53 | 126 | else |
54 | 127 | begin |
55 | | - xml = twitter_statuses_show_api( src ) |
| 128 | + json = twitter_statuses_show_api( src ) |
56 | 129 | rescue OpenURI::HTTPError |
57 | | - return %Q|<p class="bbpMessage">#$!</p>| |
| 130 | + return %Q|<p class="tweet_quote_error">#$!</p>| |
58 | 131 | end |
59 | | - db[key][:xml] = xml |
| 132 | + db[key][:json] = json |
60 | 133 | end |
61 | 134 | end |
| 135 | + twitter_status_json_to_html( JSON.parse( json ) ) |
| 136 | +end |
62 | 137 |
|
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> |
101 | 155 | HTML |
102 | 156 | end |
103 | 157 |
|
|
0 commit comments