jekyll-twitter-plugin 1.4.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eb81bd5f03311d7ea54536c4ae9b66a4958a0581
4
- data.tar.gz: 8718758139ec729d224592a725e406e3fd18e795
3
+ metadata.gz: 4c1b655f7a4220868b0d2cd098cba068f4956e68
4
+ data.tar.gz: a2c010f3a7d65bd5c9fe5e2af53971fa25ca7105
5
5
  SHA512:
6
- metadata.gz: 32e14f43bd34316445f6ac38eeb685b4fc5fa554a765022df00febfd45c8d153f4864144e95163e6f03e0fd6c4c46abf99a4bbd999715df4bf04c1c90fc683c0
7
- data.tar.gz: a7a19a137770446e06eda37ee35ce540f880962d6e1f380b71ee8405d2602fdb2b49b7f803aed58e657eea5cda83acca6fa09bd64fd21f428dd1fe527243a708
6
+ metadata.gz: 4f83e487aac6acb23ff716b6384914d9f881abdbcddc249418bfa471702b918d5efc39700a943beae3eb1cf37513da84ef7fa2fa41f5a437df150b4e1b8be2f3
7
+ data.tar.gz: 969e54b10b302987d5a8f40d64a0e545216ff587e68f517bd3b510e2f610453f0cd7afa2109842b98e53dd3825b5efab2b0747c983b2e08759c58db807779914
@@ -14,3 +14,7 @@ Style/FileName:
14
14
 
15
15
  Lint/AssignmentInCondition:
16
16
  Enabled: false
17
+
18
+ AllCops:
19
+ Exclude:
20
+ - 'spec/integration_tests.rb'
@@ -2,7 +2,6 @@ language: ruby
2
2
  cache: bundler
3
3
  sudo: false
4
4
  rvm:
5
- - 1.9.3
6
5
  - 2.0.0
7
6
  - 2.1.0
8
7
  - 2.2.0
data/README.md CHANGED
@@ -1,36 +1,61 @@
1
- ## jekyll-twitter-plugin
1
+ <img alt="Screenshot embedded tweet" src="https://raw.githubusercontent.com/rob-murray/jekyll-twitter-plugin/master/media/embedded-tweet.png" align="right" />
2
2
 
3
- A Liquid tag plugin for Jekyll that renders Tweets from Twitter API.
3
+ # jekyll-twitter-plugin
4
+
5
+ A Liquid tag plugin for Jekyll blogging engine that embeds Tweets, Timelines and more from Twitter API.
4
6
 
5
7
  [![Build Status](https://travis-ci.org/rob-murray/jekyll-twitter-plugin.svg?branch=master)](https://travis-ci.org/rob-murray/jekyll-twitter-plugin)
6
8
  [![Gem Version](https://badge.fury.io/rb/jekyll-twitter-plugin.svg)](http://badge.fury.io/rb/jekyll-twitter-plugin)
7
9
 
10
+ ---
8
11
 
9
- ### Description
10
12
 
11
- A Liquid tag plugin for [Jekyll](http://jekyllrb.com/) that enables Twitter content to be used in any content served by Jekyll, content is fetched from the [Twitter API](https://dev.twitter.com/home).
13
+ ## Description
12
14
 
13
- It is based on the original [Jekyll Tweet Tag](https://github.com/scottwb/jekyll-tweet-tag) from [scottwb](https://github.com/scottwb/) which has not been updated since Twitter changed their API to require certain preconditions. This version uses the excellent [Twitter gem](https://github.com/sferik/twitter) to make requests and handle authentication.
15
+ **jekyll-twitter-plugin** is a Liquid tag plugin for [Jekyll](http://jekyllrb.com/) that enables Twitter content to be used in any pages generated by Jekyll, content is fetched from the [Twitter Publish platform](https://publish.twitter.com).
14
16
 
15
- This plugin replaces the broken [Jekyll Tweet Tag](https://github.com/scottwb/jekyll-tweet-tag) plugin mentioned above and uses a different tag name and API - this is by design so that the two plugins can be separated and you can be certain which plugin is being used. You can also install this plugin via Rubygems and require it in your Jekyll `_config.yml` file. You can use *ENV* variables or `_config.yml` file for your Twitter API secret keys.
17
+ The [Publish platform](https://publish.twitter.com) allows Twitter users to curate content for display outside of Twitter, you can display many different types of content with the familiar Twitter styling. We use this API and allow any customisation of the content that is accepted by the Twitter publish platform.
16
18
 
19
+ > You can now embed any Twitter content in your Jekyll powered blog!
17
20
 
18
- ### Features
21
+ ### Here are a few examples
19
22
 
20
- The plugin supports the following features:
23
+ #### Tweet
21
24
 
22
- * Installed via Rubygems.
23
- * Twitter API secrets read from *ENV* vars or `_config.yml` file.
24
- * [Oembed](#oembed) - Embed a Tweet in familiar Twitter styling.
25
- * [Caching](#caching) - Twitter API responses can be cached to avoid hitting request limits.
25
+ An example of a Tweet - `{% twitter https://twitter.com/rubygems/status/518821243320287232 %}`
26
+
27
+ ![Embedded tweet](https://raw.githubusercontent.com/rob-murray/jekyll-twitter-plugin/master/media/embedded-tweet.png "Screenshot of embedded tweet")
28
+
29
+ #### Timeline
30
+
31
+ An example of a Timeline - `{% twitter https://twitter.com/jekyllrb maxwidth=500 limit=5 %}`
32
+
33
+ ![Embedded timeline](https://raw.githubusercontent.com/rob-murray/jekyll-twitter-plugin/master/media/embedded-timeline.png "Screenshot of embedded timeline")
34
+
35
+ #### Grid Timeline
36
+
37
+ An example of a Grid Timeline - `{% twitter https://twitter.com/TwitterDev/timelines/539487832448843776 limit=5 widget_type=grid maxwidth=500 %}`
38
+
39
+ ![Embedded Grid Timeline](https://raw.githubusercontent.com/rob-murray/jekyll-twitter-plugin/master/media/embedded-grid.png "Screenshot of embedded Grid Timeline")
26
40
 
41
+ #### Moment
27
42
 
28
- ### Requirements
43
+ An example of a Moment - `{% twitter https://twitter.com/i/moments/650667182356082688 maxwidth=500 %}`
29
44
 
30
- * Twitter oauth credentials - Most Twitter api functions now require authentication. Set up your [application](https://dev.twitter.com/apps/new) and get the credentials.
45
+ ![Embedded moment](https://raw.githubusercontent.com/rob-murray/jekyll-twitter-plugin/master/media/embedded-moment.png "Screenshot of embedded moment")
31
46
 
32
47
 
33
- ### Usage
48
+ ### Features
49
+
50
+ The plugin supports the following features:
51
+
52
+ * Installed via Rubygems.
53
+ * [Customisation](#customisation) - All customisation options passed to Twitter API.
54
+ * [Authentication](#authentication) - No authentication required!
55
+ * [Caching](#caching) - Twitter API responses can be cached to speed up builds.
56
+
57
+
58
+ ## Getting Started
34
59
 
35
60
  As mentioned by [Jekyll's documentation](http://jekyllrb.com/docs/plugins/#installing-a-plugin) you have two options; manually import the source file or require the plugin as a `gem`.
36
61
 
@@ -49,7 +74,6 @@ Add the `jekyll-twitter-plugin` to your site `_config.yml` file for Jekyll to im
49
74
  gems: ['jekyll-twitter-plugin']
50
75
  ```
51
76
 
52
-
53
77
  #### Manual import
54
78
 
55
79
  > Note: this is deprecated and support will be removed in a later version.
@@ -63,86 +87,65 @@ $ wget https://raw.githubusercontent.com/rob-murray/jekyll-twitter-plugin/master
63
87
  ```
64
88
 
65
89
 
66
- #### Credentials
90
+ #### Plugin tag usage
67
91
 
68
- Your Twitter application authentication credentials are private - do not distribute these!
92
+ To use the plugin, in your source content use the tag `twitter` and then pass additional options to the plugin, these are passed to the API.
69
93
 
70
- You can set the authentication variables by adding them to `_config.yml`.
94
+ ```liquid
95
+ {% plugin_type twitter_url *options %}
71
96
 
72
- ```yaml
73
- # _config.yml
74
- twitter:
75
- consumer_key: asdf
76
- consumer_secret: asdf
77
- access_token: asdf
78
- access_token_secret: asdf
97
+ # Example for timeline of the **jekyllrb** user with a maximum of 5 Tweets and with a width of 500px
98
+ {% twitter https://twitter.com/jekyllrb maxwidth=500 limit=5 %}
79
99
  ```
80
100
 
81
- If the authentication variables are not present in `_config.yml` they can be gathered from
82
- environment variables.
101
+ | Argument | Required? | Description |
102
+ |---|---|---|
103
+ | `plugin_type` | Yes | Either `twitter` or `twitternocache` (same as `twitter` but does not cache api responses) |
104
+ | `twitter_url` | Yes | The Twitter URL to use, check below for supported URLs. |
105
+ | `*options` | No | Parameters for the API separated by spaces. Refer below and to respective Twitter API documentation for available parameters. |
83
106
 
84
- * TWITTER_CONSUMER_KEY
85
- * TWITTER_CONSUMER_SECRET
86
- * TWITTER_ACCESS_TOKEN
87
- * TWITTER_ACCESS_TOKEN_SECRET
88
107
 
89
- ```bash
90
- $ export TWITTER_CONSUMER_KEY=foo etc.
91
- # Or given a file .env with the keys and secrets
92
- $ export $(cat .env | xargs)
93
- ```
108
+ ### Supported Twitter URLs
94
109
 
110
+ The Twitter URLs that are supported depends on Twitter, we pass the url and all parameters to the API - check [Twitter Publish platform](https://publish.twitter.com) for availability. Here is documentation for some common types:
95
111
 
96
- #### Plugin tag usage
112
+ ##### Tweet:
97
113
 
98
- To use the plugin, in your source content use the tag `twitter` and then pass additional parameters to the plugin.
114
+ * [https://dev.twitter.com/web/overview](https://dev.twitter.com/web/overview)
115
+ * [https://dev.twitter.com/web/embedded-tweets/parameters](https://dev.twitter.com/web/embedded-tweets/parameters)
99
116
 
100
- ```liquid
101
- {% plugin_type api_type *params %}
102
- ```
117
+ ##### Timeline:
103
118
 
104
- | Argument | Required? | Description |
105
- |---|---|---|
106
- | `plugin_type` | Yes | Either `twitter` or `twitternocache` (same as `twitter` but does not cache api responses) |
107
- | `api_type` | No - defaults to **oembed** | The Twitter API to use, check below for supported APIs. |
108
- | `*params` | Yes* | Parameters for the API separated by spaces. Refer below and to respective Twitter API documentation for available parameters. |
119
+ * [https://dev.twitter.com/web/embedded-timelines/oembed](https://dev.twitter.com/web/embedded-timelines/oembed)
109
120
 
110
- * Required arguments depend on the API used.
121
+ ##### Moments:
111
122
 
123
+ * [https://dev.twitter.com/web/embedded-moments/oembed](https://dev.twitter.com/web/embedded-moments/oembed)
112
124
 
113
- ### Supported Twitter APIs
125
+ ### Customisation
114
126
 
115
- The following Twitter APIs are supported.
127
+ All pairs of options and values after the URL are passed to the API, the parameters must be in pairs with the option as the key: `option=value`.
116
128
 
129
+ For exampled if you wanted to limit the width of the embedded content then the API supports a `maxwidth` option so you could construct the tag as below to limit it to a value of 500 (pixels).
117
130
 
118
- #### Oembed - Default
131
+ ```liquid
132
+ {% twitter https://twitter.com/jekyllrb maxwidth=500 %}
133
+ ```
119
134
 
120
- The [oembed](https://dev.twitter.com/rest/reference/get/statuses/oembed) API returns html snippet to embed in your app, this will be rendered in the familiar Twitter style.
135
+ ### Authentication
121
136
 
122
- This API requires the `status_url` parameter, all others are optional.
137
+ The API does not require any authentication.
123
138
 
124
- ```liquid
125
- # With api_type specified
126
- {% twitter oembed status_url *options %}
127
- # 'oembed' autoselected by default
128
- {% twitter status_url *options %}
129
-
130
- # Basic example
131
- {% twitter oembed https://twitter.com/rubygems/status/518821243320287232 %}
132
- # Oembed default example
133
- {% twitter https://twitter.com/rubygems/status/518821243320287232 %}
134
- # With options
135
- {% twitter oembed https://twitter.com/rubygems/status/518821243320287232 align='right' width='350' %}
136
- ```
139
+ > Previous version of this library used an API that required API keys (TWITTER_CONSUMER_KEY TWITTER_CONSUMER_SECRET TWITTER_ACCESS_TOKEN TWITTER_ACCESS_TOKEN_SECRET). We now print a warning if these are detected as a reminder that you can safely remove them.
137
140
 
138
141
 
139
142
  ### Output
140
143
 
141
- As with the original plugin, all content will be rendered inside a div with the classes 'embed' and 'twitter'
144
+ All content will be rendered inside a div with the class `jekyll-twitter-plugin`.
142
145
 
143
146
  ```html
144
- <div class='embed twitter'>
145
- -- content --
147
+ <div class='jekyll-twitter-plugin'>
148
+ -- content from API --
146
149
  </div>
147
150
  ```
148
151
 
@@ -150,42 +153,49 @@ If something goes wrong then a basic error message will be displayed;
150
153
 
151
154
  > Tweet could not be processed
152
155
 
153
- If the Twitter client receives one of `Twitter::Error::NotFound, Twitter::Error::Forbidden` errors, this suggests the Tweet is protected or deleted and the following error will be displayed and cached so that it is not fetched again, and again. If the Tweet is restored then simply delete the cached response from `.tweet-cache` directory and build again.
154
-
155
- > There was a '{error name}' error fetching Tweet '{Tweet status url}'
156
+ If we receive an error from the API then a message will be cached and rendered something like this below. If its a 404 then this suggests the Tweet is protected or deleted and it is not fetched again, and again. If the Tweet is restored then simply delete the cached response from `.tweet-cache` directory and build again.
156
157
 
158
+ ```html
159
+ <div class='jekyll-twitter-plugin'>
160
+ <p>There was a '{error name}' error fetching Tweet '{Tweet status url}'</p>
161
+ </div>
162
+ ```
157
163
 
158
164
  ### Caching
159
165
 
160
- Twitter API responses can be cached to speed up Jekyll site builds and avoid going over API limits. The reponses will be cached in a directory within your Jekyll project called `.tweet-cache`, ensure that this is not commit to source control.
166
+ Twitter API responses can be cached to speed up Jekyll site builds. The reponses will be cached in a directory within your Jekyll project called `.tweet-cache`, this should not be committed to source control.
161
167
 
162
- Caching is enabled by default.
168
+ Caching is enabled by using the `twitter` tag.
163
169
 
164
170
  It is possible to disable caching by using the specific `twitternocache` tag.
165
171
 
166
172
  ```liquid
167
- {% twitternocache oembed status_url *options %}
173
+ {% twitternocache twitter_url *options %}
168
174
 
169
175
  # Example
170
- {% twitternocache oembed https://twitter.com/rubygems/status/518821243320287232 %}
176
+ {% twitternocache https://twitter.com/rubygems/status/518821243320287232 %}
171
177
 
172
178
  ```
173
179
 
174
180
 
175
181
  ### Contributions
176
182
 
177
- I've tried hard to keep all classes and code in the one `lib/jekyll-twitter-plugin.rb` file so that people can just grab this file and include in their Jekyll `_plugins` directory if they do not want to use the Gem. This will be dropped in a later version.
183
+ I've tried hard to keep all code in the one `lib/jekyll-twitter-plugin.rb` file so that people can just grab this file and include in their Jekyll `_plugins` directory if they do not want to install with Rubygems. This will be dropped in a later version.
178
184
 
179
185
  Please use the GitHub pull-request mechanism to submit contributions.
180
186
 
181
- There is a quick integration test in `spec/integration_tests.rb` that needs API keys and will use the public api and output a file `output_test.html`. Run this with the following command:
187
+ There is a quick integration test in `spec/integration_tests.rb` that will use the **jekyll-twitter-plugin** public api and output a file `output_test.html`. Run this with the following command:
182
188
 
183
189
  ```ruby
184
- $ ruby spec/integration_tests.rb
190
+ $ ruby spec/integration_tests.rb && open output_test.html
185
191
  ```
186
192
 
187
193
 
188
- ### License
194
+ ## Versioning
195
+
196
+ We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/rob-murray/jekyll-twitter-plugin/tags).
197
+
198
+
199
+ ## License
189
200
 
190
- This project is available for use under the MIT software license.
191
- See LICENSE
201
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details
@@ -5,13 +5,13 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "jekyll-twitter-plugin"
8
- spec.version = "1.4.0"
8
+ spec.version = "2.0.0"
9
9
  spec.authors = ["Rob Murray"]
10
10
  spec.email = ["robmurray17@gmail.com"]
11
- spec.summary = "A Liquid tag plugin for Jekyll that renders Tweets from Twitter API"
11
+ spec.summary = "A Liquid tag plugin for Jekyll blogging engine that embeds Tweets, Timelines and more from Twitter API."
12
12
  spec.homepage = "https://github.com/rob-murray/jekyll-twitter-plugin"
13
13
  spec.license = "MIT"
14
- spec.required_ruby_version = ">= 1.9.3"
14
+ spec.required_ruby_version = ">= 2.0.0"
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0")
17
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
@@ -19,8 +19,7 @@ Gem::Specification.new do |spec|
19
19
 
20
20
  spec.add_development_dependency "bundler", "~> 1.6"
21
21
  spec.add_development_dependency "rake"
22
- spec.add_development_dependency "rspec", "~> 3.0"
22
+ spec.add_development_dependency "rspec"
23
+ spec.add_development_dependency "webmock"
23
24
  spec.add_development_dependency "byebug" if RUBY_VERSION >= "2.0"
24
-
25
- spec.add_dependency "twitter", "~> 5.16"
26
25
  end
@@ -1,23 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
  require "fileutils"
3
- require "twitter"
3
+ require "net/http"
4
+ require "uri"
5
+ require "ostruct"
6
+ require "json"
7
+ require "digest"
4
8
 
5
9
  ##
6
10
  # A Liquid tag plugin for Jekyll that renders Tweets from Twitter API.
7
11
  # https://github.com/rob-murray/jekyll-twitter-plugin
8
12
  #
9
13
  module TwitterJekyll
10
- MissingApiKeyError = Class.new(StandardError)
11
- TwitterSecrets = Struct.new(:consumer_key, :consumer_secret, :access_token, :access_token_secret) do
12
- def self.build(source, keys)
13
- new(*source.values_at(*keys))
14
- end
15
- end
16
- CONTEXT_API_KEYS = %w(consumer_key consumer_secret access_token access_token_secret).freeze
17
- ENV_API_KEYS = %w(TWITTER_CONSUMER_KEY TWITTER_CONSUMER_SECRET TWITTER_ACCESS_TOKEN TWITTER_ACCESS_TOKEN_SECRET).freeze
18
- TWITTER_STATUS_URL = %r{\Ahttps?://twitter\.com/(:#!\/)?\w+/status(es)?/\d+}i
19
- REFER_TO_README = "Please see 'https://github.com/rob-murray/jekyll-twitter-plugin' for usage."
20
-
14
+ # TODO: remove after deprecation cycle
15
+ CONTEXT_API_KEYS = %w(consumer_key consumer_secret access_token access_token_secret).freeze
16
+ ENV_API_KEYS = %w(TWITTER_CONSUMER_KEY TWITTER_CONSUMER_SECRET TWITTER_ACCESS_TOKEN TWITTER_ACCESS_TOKEN_SECRET).freeze
17
+ REFER_TO_README = "Please see 'https://github.com/rob-murray/jekyll-twitter-plugin' for usage.".freeze
18
+ LIBRARY_VERSION = "jekyll-twitter-plugin-v2.0.0".freeze
19
+ REQUEST_HEADERS = { "User-Agent" => LIBRARY_VERSION }.freeze
20
+
21
+ # Cache class that writes to filesystem
22
+ # TODO: Do i really need to cache?
23
+ # @api private
21
24
  class FileCache
22
25
  def initialize(path)
23
26
  @cache_folder = File.expand_path path
@@ -49,6 +52,8 @@ module TwitterJekyll
49
52
  end
50
53
  end
51
54
 
55
+ # Cache class that does nothing
56
+ # @api private
52
57
  class NullCache
53
58
  def initialize(*_args); end
54
59
 
@@ -57,117 +62,104 @@ module TwitterJekyll
57
62
  def write(_key, _data); end
58
63
  end
59
64
 
60
- module Cacheable
61
- def cache_key
62
- Digest::MD5.hexdigest("#{self.class.name}-#{key}")
63
- end
64
-
65
- def key; end
66
- end
67
-
68
- class TwitterApi
69
- ERRORS_TO_IGNORE = [Twitter::Error::NotFound, Twitter::Error::Forbidden].freeze
65
+ # Wrapper around an API
66
+ # @api private
67
+ class ApiClient
68
+ # Perform API request; return hash with html content
69
+ def fetch(api_request)
70
+ uri = api_request.to_uri
71
+ response = Net::HTTP.start(uri.host, use_ssl: api_request.ssl?) do |http|
72
+ http.read_timeout = 5
73
+ http.open_timeout = 5
74
+ http.get uri.request_uri, REQUEST_HEADERS
75
+ end
70
76
 
71
- attr_reader :error
77
+ handle_response(api_request, response)
72
78
 
73
- def initialize(client, params)
74
- @client = client
75
- @status_url = params.shift
76
- parse_args(params)
79
+ rescue Timeout::Error => e
80
+ ErrorResponse.new(api_request, e.class.name).to_h
77
81
  end
78
82
 
79
- def fetch; end
80
-
81
83
  private
82
84
 
83
- def id_from_status_url(url)
84
- Regexp.last_match[1] if url.to_s =~ %r{([^\/]+$)}
85
- end
86
-
87
- def find_tweet(id)
88
- return unless id
89
-
90
- @client.status(id.to_i)
91
- rescue *ERRORS_TO_IGNORE => e
92
- @error = create_error(e)
93
- return nil
85
+ def handle_response(api_request, response)
86
+ case response
87
+ when Net::HTTPSuccess
88
+ JSON.parse(response.body)
89
+ else
90
+ ErrorResponse.new(api_request, response.message).to_h
91
+ end
94
92
  end
93
+ end
95
94
 
96
- def parse_args(args)
97
- @params ||= begin
98
- args.each_with_object({}) do |arg, params|
99
- k, v = arg.split("=").map(&:strip)
100
- if k && v
101
- v = Regexp.last_match[1] if v =~ /^'(.*)'$/
102
- params[k] = v
103
- end
104
- end
105
- end
95
+ # @api private
96
+ ErrorResponse = Struct.new(:request, :message) do
97
+ def html
98
+ "<p>There was a '#{message}' error fetching URL: '#{request.entity_url}'</p>"
106
99
  end
107
100
 
108
- def create_error(exception)
109
- ErrorResponse.new("There was a '#{exception.class.name}' error fetching Tweet '#{@status_url}'")
101
+ def to_h
102
+ { html: html }
110
103
  end
111
104
  end
112
105
 
113
- class Oembed < TwitterApi
114
- include TwitterJekyll::Cacheable
106
+ # Holds the URI were going to request with any parameters
107
+ # @api private
108
+ ApiRequest = Struct.new(:entity_url, :params) do
109
+ TWITTER_API_URL = "https://publish.twitter.com/oembed".freeze
115
110
 
116
- def fetch
117
- tweet_id = id_from_status_url(@status_url)
111
+ # Always;
112
+ def ssl?
113
+ true
114
+ end
118
115
 
119
- if tweet = find_tweet(tweet_id)
120
- # To work around a 'bug' in the Twitter gem modifying our hash we pass in
121
- # a copy otherwise our cache key is altered.
122
- @client.oembed tweet, @params.dup
123
- else
124
- error
116
+ # Return a URI for Twitter API with query params
117
+ def to_uri
118
+ URI.parse(TWITTER_API_URL).tap do |uri|
119
+ uri.query = URI.encode_www_form url_params
125
120
  end
126
121
  end
127
122
 
128
- private
129
-
130
- def key
131
- format("%s-%s", @status_url, @params.to_s)
123
+ # A cache key applicable to the current request with params
124
+ def cache_key
125
+ Digest::MD5.hexdigest("#{self.class.name}-#{unique_request_key}")
132
126
  end
133
- end
134
-
135
- class ErrorResponse
136
- attr_reader :error
137
127
 
138
- def initialize(error)
139
- @error = error
140
- end
128
+ private
141
129
 
142
- def html
143
- "<p>#{@error}</p>"
130
+ def url_params
131
+ params.merge(url: entity_url)
144
132
  end
145
133
 
146
- def to_h
147
- { html: html }
134
+ def unique_request_key
135
+ format("%s-%s", entity_url, params.to_s)
148
136
  end
149
137
  end
150
138
 
139
+ # Class to respond to Jekyll tag; entry point to library
140
+ # @api public
151
141
  class TwitterTag < Liquid::Tag
152
- ERROR_BODY_TEXT = "<p>Tweet could not be processed</p>"
153
- DEFAULT_API_TYPE = "oembed"
142
+ ERROR_BODY_TEXT = "<p>Tweet could not be processed</p>".freeze
143
+ OEMBED_ARG = "oembed".freeze
154
144
 
155
145
  attr_writer :cache # for testing
156
146
 
157
147
  def initialize(_name, params, _tokens)
158
148
  super
159
- @api_type, @params = parse_params(params)
149
+ @api_request = parse_params(params)
160
150
  end
161
151
 
152
+ # Class that implements caching strategy
153
+ # @api private
162
154
  def self.cache_klass
163
155
  FileCache
164
156
  end
165
157
 
158
+ # Return html string for Jekyll engine
159
+ # @api public
166
160
  def render(context)
167
- secrets = find_secrets!(context)
168
- create_twitter_rest_client(secrets)
169
- api_client = create_api_client(@api_type, @params)
170
- response = cached_response(api_client) || live_response(api_client)
161
+ api_secrets_deprecation_warning(context) # TODO: remove after deprecation cycle
162
+ response = cached_response || live_response
171
163
  html_output_for(response)
172
164
  end
173
165
 
@@ -177,84 +169,112 @@ module TwitterJekyll
177
169
  @cache ||= self.class.cache_klass.new("./.tweet-cache")
178
170
  end
179
171
 
172
+ def api_client
173
+ @api_client ||= ApiClient.new
174
+ end
175
+
176
+ # Return Twitter response or error html
177
+ # @api private
180
178
  def html_output_for(response)
181
179
  body = (response.html if response) || ERROR_BODY_TEXT
182
180
 
183
- "<div class='embed twitter'>#{body}</div>"
181
+ "<div class='jekyll-twitter-plugin'>#{body}</div>"
184
182
  end
185
183
 
186
- def live_response(api_client)
187
- if response = api_client.fetch
188
- cache.write(api_client.cache_key, response)
189
- response
184
+ # Return response from API and write to cache
185
+ # @api private
186
+ def live_response
187
+ if response = api_client.fetch(@api_request)
188
+ cache.write(@api_request.cache_key, response)
189
+ build_response(response)
190
190
  end
191
191
  end
192
192
 
193
- def cached_response(api_client)
194
- response = cache.read(api_client.cache_key)
195
- OpenStruct.new(response) unless response.nil?
193
+ # Return response cache if present, otherwise nil
194
+ # @api private
195
+ def cached_response
196
+ response = cache.read(@api_request.cache_key)
197
+ build_response(response) unless response.nil?
196
198
  end
197
199
 
200
+ # Return an `ApiRequest` with the url and arguments
201
+ # @api private
198
202
  def parse_params(params)
199
203
  args = params.split(/\s+/).map(&:strip)
204
+ invalid_args!(args) unless args.any?
200
205
 
201
- case args[0]
202
- when DEFAULT_API_TYPE
203
- api_type, *api_args = args
204
- [api_type, api_args]
205
- when TWITTER_STATUS_URL
206
- [DEFAULT_API_TYPE, args]
207
- else
208
- invalid_args!(args)
206
+ if args[0].to_s == OEMBED_ARG # TODO: remove after deprecation cycle
207
+ arguments_deprecation_warning(args)
208
+ args.shift
209
209
  end
210
- end
211
210
 
212
- def create_api_client(api_type, params)
213
- klass_name = api_type.capitalize
214
- api_client_klass = TwitterJekyll.const_get(klass_name)
215
- api_client_klass.new(@twitter_client, params)
211
+ url, *api_args = args
212
+ ApiRequest.new(url, parse_args(api_args))
216
213
  end
217
214
 
218
- def create_twitter_rest_client(secrets)
219
- @twitter_client = Twitter::REST::Client.new do |config|
220
- config.consumer_key = secrets.consumer_key
221
- config.consumer_secret = secrets.consumer_secret
222
- config.access_token = secrets.access_token
223
- config.access_token_secret = secrets.access_token_secret
215
+ # Transform 'a=b x=y' tag arguments into { "a" => "b", "x" => "y" }
216
+ # @api private
217
+ def parse_args(args)
218
+ args.each_with_object({}) do |arg, params|
219
+ k, v = arg.split("=").map(&:strip)
220
+ if k && v
221
+ v = Regexp.last_match[1] if v =~ /^'(.*)'$/
222
+ params[k] = v
223
+ end
224
224
  end
225
225
  end
226
226
 
227
- def find_secrets!(context)
228
- extract_twitter_secrets_from_context(context) || extract_twitter_secrets_from_env || missing_keys!
227
+ # Format a response hash
228
+ # @api private
229
+ def build_response(h)
230
+ OpenStruct.new(h)
231
+ end
232
+
233
+ # TODO: remove after deprecation cycle
234
+ def arguments_deprecation_warning(args)
235
+ warn "#{LIBRARY_VERSION}: Passing '#{OEMBED_ARG}' as the first argument is not required anymore. This will result in an error in future versions.\nCalled with #{args.inspect}"
229
236
  end
230
237
 
231
- def extract_twitter_secrets_from_context(context)
238
+ # TODO: remove after deprecation cycle
239
+ def api_secrets_deprecation_warning(context)
240
+ warn_if_twitter_secrets_in_context(context) || warn_if_twitter_secrets_in_env
241
+ end
242
+
243
+ # TODO: remove after deprecation cycle
244
+ def warn_if_twitter_secrets_in_context(context)
232
245
  twitter_secrets = context.registers[:site].config.fetch("twitter", {})
233
246
  return unless store_has_keys?(twitter_secrets, CONTEXT_API_KEYS)
234
247
 
235
- TwitterSecrets.build(twitter_secrets, CONTEXT_API_KEYS)
248
+ warn_secrets_in_project("Jekyll _config.yml")
236
249
  end
237
250
 
238
- def extract_twitter_secrets_from_env
251
+ # TODO: remove after deprecation cycle
252
+ def warn_if_twitter_secrets_in_env
239
253
  return unless store_has_keys?(ENV, ENV_API_KEYS)
240
254
 
241
- TwitterSecrets.build(ENV, ENV_API_KEYS)
255
+ warn_secrets_in_project("ENV")
242
256
  end
243
257
 
244
- def store_has_keys?(store, keys)
245
- keys.all? { |required_key| store.key?(required_key) }
258
+ # TODO: remove after deprecation cycle
259
+ def warn_secrets_in_project(source)
260
+ warn "#{LIBRARY_VERSION}: Found Twitter API keys in #{source}, this library does not require these keys anymore. You can remove these keys, if used for another library then ignore this message."
246
261
  end
247
262
 
248
- def missing_keys!
249
- raise MissingApiKeyError, "Twitter API keys not found. You can specify these in Jekyll config or ENV. #{REFER_TO_README}"
263
+ # TODO: remove after deprecation cycle
264
+ def store_has_keys?(store, keys)
265
+ keys.all? { |required_key| store.key?(required_key) }
250
266
  end
251
267
 
268
+ # Raise error for invalid arguments
269
+ # @api private
252
270
  def invalid_args!(arguments)
253
271
  formatted_args = Array(arguments).join(" ")
254
272
  raise ArgumentError, "Invalid arguments '#{formatted_args}' passed to 'jekyll-twitter-plugin'. #{REFER_TO_README}"
255
273
  end
256
274
  end
257
275
 
276
+ # Specialization of TwitterTag without any caching
277
+ # @api public
258
278
  class TwitterTagNoCache < TwitterTag
259
279
  def self.cache_klass
260
280
  NullCache