Implement Unified Embeds for StackExchange and StackOverflow URLs (#16415)
* complete implementation and add specs * improvements * more robust condition * update API version; re-record VCR cassettes * PR review comments
This commit is contained in:
parent
0559a808c4
commit
63c957b12d
5 changed files with 108 additions and 78 deletions
|
|
@ -1,7 +1,11 @@
|
|||
class StackexchangeTag < LiquidTagBase
|
||||
PARTIAL = "liquids/stackexchange".freeze
|
||||
# update API version here and in stackexchange_tag_spec when a new version is out
|
||||
API_URL = "https://api.stackexchange.com/2.2/".freeze
|
||||
REGISTRY_REGEXP = %r{https://(?:(?<subdomain>\w+)\.)?(?:stackexchange\.com|stackoverflow\.com)/(?<post_type>q|a|questions)/(?<id>\d{1,20})}
|
||||
ID_REGEXP = /\A(?<id>\d{1,20})\Z/
|
||||
SITE_REGEXP = /(?<subdomain>\b[a-zA-Z]+\b)/
|
||||
REGEXP_OPTIONS = [REGISTRY_REGEXP, ID_REGEXP, SITE_REGEXP].freeze
|
||||
STACKOVERFLOW_REGEXP = %r{https://stackoverflow\.com/(q|a|questions)/\d{1,20}(?:/[\w\-]+)?}
|
||||
API_URL = "https://api.stackexchange.com/2.3/".freeze
|
||||
# Filter codes come from the example tools in the docs. For example: https://api.stackexchange.com/docs/posts-by-ids
|
||||
FILTERS = {
|
||||
"post" => "!3tz1WbZW5JxrG-f99",
|
||||
|
|
@ -9,16 +13,16 @@ class StackexchangeTag < LiquidTagBase
|
|||
"question" => "!*1SgQGDOL9bPBHULz9sKS.y6qv7V9fYNszvdhDuv5",
|
||||
"site" => "!mWxO_PNa4i"
|
||||
}.freeze
|
||||
ID_REGEXP = /\A\d{1,20}\z/
|
||||
|
||||
attr_reader :site, :post_type
|
||||
|
||||
def initialize(_tag_name, input, _parse_context)
|
||||
super
|
||||
|
||||
@site = parse_site(input.strip)
|
||||
@post_type = "question"
|
||||
@json_content = get_data(input.strip)
|
||||
stripped_input = strip_tags(input)
|
||||
unescaped_input = CGI.unescape_html(stripped_input)
|
||||
@site = parse_site(unescaped_input)
|
||||
@json_content, @post_type = get_data(unescaped_input)
|
||||
end
|
||||
|
||||
def render(_context)
|
||||
|
|
@ -42,43 +46,29 @@ class StackexchangeTag < LiquidTagBase
|
|||
|
||||
private
|
||||
|
||||
def valid_site?(site)
|
||||
(site =~ /[a-z.]+/i)&.zero?
|
||||
end
|
||||
|
||||
def parse_site(input)
|
||||
return "stackoverflow" if tag_name == "stackoverflow"
|
||||
return "stackoverflow" if tag_name == "stackoverflow" || input.match?(STACKOVERFLOW_REGEXP)
|
||||
|
||||
site = input.match(/[a-z.]+/i)[0]
|
||||
unless valid_site?(site)
|
||||
raise StandardError, I18n.t("liquid_tags.stackexchange_tag.invalid_site", tag: tag_name, input: input)
|
||||
end
|
||||
match = pattern_match_for(input, REGEXP_OPTIONS)
|
||||
# rubocop:disable Layout/LineLength
|
||||
raise StandardError, I18n.t("liquid_tags.stackexchange_tag.invalid_site", tag: tag_name, input: input) unless match && match_group_present?(match, "subdomain")
|
||||
# rubocop:enable Layout/LineLength
|
||||
|
||||
site
|
||||
match[:subdomain].downcase
|
||||
end
|
||||
|
||||
def valid_input?(input)
|
||||
return false if input.nil?
|
||||
|
||||
ID_REGEXP.match?(input.split.first)
|
||||
end
|
||||
|
||||
def handle_response_error(response, input)
|
||||
raise StandardError, "Calling StackExchange API failed: #{response&.error_message}" if response.code != 200
|
||||
|
||||
return unless response["items"].length.zero?
|
||||
|
||||
raise StandardError, I18n.t("liquid_tags.stackexchange_tag.post_not_found", tag: tag_name, input: input)
|
||||
def match_group_present?(match, group_name)
|
||||
match.names.include?(group_name)
|
||||
end
|
||||
|
||||
def get_data(input)
|
||||
unless valid_input?(input)
|
||||
id = input.split.first
|
||||
match = pattern_match_for(id, REGEXP_OPTIONS)
|
||||
unless match && match_group_present?(match, "id")
|
||||
raise StandardError, I18n.t("liquid_tags.stackexchange_tag.invalid_id", tag: tag_name, input: input)
|
||||
end
|
||||
|
||||
id = input.split.first
|
||||
|
||||
url = "#{API_URL}posts/#{id}?site=#{@site}&filter=#{FILTERS['post']}" \
|
||||
url = "#{API_URL}posts/#{match[:id]}?site=#{@site}&filter=#{FILTERS['post']}" \
|
||||
"&key=#{ApplicationConfig['STACK_EXCHANGE_APP_KEY']}"
|
||||
post_response = HTTParty.get(url)
|
||||
|
||||
|
|
@ -86,15 +76,24 @@ class StackexchangeTag < LiquidTagBase
|
|||
|
||||
@post_type = post_response["items"][0]["post_type"]
|
||||
|
||||
url = "#{API_URL}#{@post_type.pluralize}/#{id}?site=#{@site}" \
|
||||
url = "#{API_URL}#{@post_type.pluralize}/#{match[:id]}?site=#{@site}" \
|
||||
"&filter=#{FILTERS[@post_type]}&key=#{ApplicationConfig['STACK_EXCHANGE_APP_KEY']}"
|
||||
final_response = HTTParty.get(url)
|
||||
|
||||
handle_response_error(final_response, input)
|
||||
|
||||
final_response["items"][0]
|
||||
[final_response["items"][0], @post_type]
|
||||
end
|
||||
|
||||
def handle_response_error(response, input)
|
||||
raise StandardError, "Calling StackExchange API failed: #{response&.error_message}" unless response.ok?
|
||||
|
||||
return unless response["items"].length.zero?
|
||||
|
||||
raise StandardError, I18n.t("liquid_tags.stackexchange_tag.post_not_found", tag: tag_name, input: input)
|
||||
end
|
||||
end
|
||||
|
||||
Liquid::Template.register_tag("stackoverflow", StackexchangeTag)
|
||||
Liquid::Template.register_tag("stackexchange", StackexchangeTag)
|
||||
UnifiedEmbed.register(StackexchangeTag, regexp: StackexchangeTag::REGISTRY_REGEXP)
|
||||
|
|
|
|||
|
|
@ -68,6 +68,15 @@ RSpec.describe UnifiedEmbed::Registry do
|
|||
"https://stackblitz.com/edit/web-platform-3tqbd4?embed=1&file=index.html&theme=light",
|
||||
]
|
||||
|
||||
valid_stackexchange_stackoverflow_url_formats = [
|
||||
"https://travel.stackexchange.com/questions/172014/is-it-okay-to-mix-in-local-language-when-i-know-it-poorly",
|
||||
"https://diy.stackexchange.com/q/244088",
|
||||
"https://academia.stackexchange.com/a/181893",
|
||||
"https://stackoverflow.com/q/70974409/9091371",
|
||||
"https://stackoverflow.com/questions/70976451/changing-h1-element-based-on-input-element-content-vanilla-js",
|
||||
"https://stackoverflow.com/a/70976251/9091371",
|
||||
]
|
||||
|
||||
valid_twitch_url_formats = [
|
||||
"https://clips.twitch.tv/embed?clip=SpeedyVivaciousDolphinKappaRoss-IQl5YslMAGKbMOGM&parent=www.example.com",
|
||||
"https://player.twitch.tv/?video=1222841752&parent=www.example.com",
|
||||
|
|
@ -226,6 +235,13 @@ RSpec.describe UnifiedEmbed::Registry do
|
|||
.to eq(StackeryTag)
|
||||
end
|
||||
|
||||
it "returns StackexchangeTag for a valid stackexchange or stackoverflow url", :aggregate_failures do
|
||||
valid_stackexchange_stackoverflow_url_formats.each do |url|
|
||||
expect(described_class.find_liquid_tag_for(link: url))
|
||||
.to eq(StackexchangeTag)
|
||||
end
|
||||
end
|
||||
|
||||
it "returns TweetTag for a tweet url" do
|
||||
expect(described_class.find_liquid_tag_for(link: "https://twitter.com/aritdeveloper/status/1483614684884484099"))
|
||||
.to eq(TweetTag)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
http_interactions:
|
||||
- request:
|
||||
method: get
|
||||
uri: https://api.stackexchange.com/2.2/posts/59679915?filter=!3tz1WbZW5JxrG-f99&key=&site=askubuntu
|
||||
uri: https://api.stackexchange.com/2.3/posts/59679915?filter=!3tz1WbZW5JxrG-f99&key=&site=askubuntu
|
||||
body:
|
||||
encoding: US-ASCII
|
||||
string: ''
|
||||
|
|
@ -18,12 +18,14 @@ http_interactions:
|
|||
code: 200
|
||||
message: OK
|
||||
headers:
|
||||
Cache-Control:
|
||||
- private
|
||||
Content-Length:
|
||||
- '87'
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Strict-Transport-Security:
|
||||
- max-age=2592000
|
||||
- max-age=15552000
|
||||
Access-Control-Allow-Origin:
|
||||
- "*"
|
||||
Access-Control-Allow-Methods:
|
||||
|
|
@ -33,12 +35,13 @@ http_interactions:
|
|||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-Request-Guid:
|
||||
- 356f2fd4-3560-40c4-a494-6a4f0f361e62
|
||||
- 39492868-d51e-49b4-9c87-d5d963306c83
|
||||
Content-Security-Policy:
|
||||
- upgrade-insecure-requests; frame-ancestors 'self' https://stackexchange.com
|
||||
Date:
|
||||
- Mon, 13 Jan 2020 12:23:24 GMT
|
||||
- Fri, 04 Feb 2022 14:00:22 GMT
|
||||
body:
|
||||
encoding: ASCII-8BIT
|
||||
string: '{"items":[],"has_more":false,"quota_max":300,"quota_remaining":287}'
|
||||
http_version:
|
||||
recorded_at: Mon, 13 Jan 2020 12:23:25 GMT
|
||||
recorded_with: VCR 5.0.0
|
||||
string: '{"items":[],"has_more":false,"quota_max":300,"quota_remaining":192}'
|
||||
recorded_at: Fri, 04 Feb 2022 14:00:22 GMT
|
||||
recorded_with: VCR 6.0.0
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
http_interactions:
|
||||
- request:
|
||||
method: get
|
||||
uri: https://api.stackexchange.com/2.2/posts/1163633?filter=!3tz1WbZW5JxrG-f99&key=&site=askubuntu
|
||||
uri: https://api.stackexchange.com/2.3/posts/1163633?filter=!3tz1WbZW5JxrG-f99&key=&site=askubuntu
|
||||
body:
|
||||
encoding: US-ASCII
|
||||
string: ''
|
||||
|
|
@ -18,12 +18,14 @@ http_interactions:
|
|||
code: 200
|
||||
message: OK
|
||||
headers:
|
||||
Cache-Control:
|
||||
- private
|
||||
Content-Length:
|
||||
- '138'
|
||||
- '139'
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Strict-Transport-Security:
|
||||
- max-age=2592000
|
||||
- max-age=15552000
|
||||
Access-Control-Allow-Origin:
|
||||
- "*"
|
||||
Access-Control-Allow-Methods:
|
||||
|
|
@ -33,17 +35,18 @@ http_interactions:
|
|||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-Request-Guid:
|
||||
- a8df412f-cc88-496c-ad89-9e40ad387650
|
||||
- 1f73ab59-8040-47d7-9562-c3ffbff9be16
|
||||
Content-Security-Policy:
|
||||
- upgrade-insecure-requests; frame-ancestors 'self' https://stackexchange.com
|
||||
Date:
|
||||
- Mon, 13 Jan 2020 12:22:38 GMT
|
||||
- Fri, 04 Feb 2022 14:00:21 GMT
|
||||
body:
|
||||
encoding: ASCII-8BIT
|
||||
string: '{"items":[{"last_activity_date":1565109531,"post_type":"answer","post_id":1163633}],"has_more":false,"quota_max":300,"quota_remaining":289}'
|
||||
http_version:
|
||||
recorded_at: Mon, 13 Jan 2020 12:22:39 GMT
|
||||
string: '{"items":[{"last_activity_date":1565109531,"post_type":"answer","post_id":1163633}],"has_more":false,"quota_max":300,"quota_remaining":192}'
|
||||
recorded_at: Fri, 04 Feb 2022 14:00:21 GMT
|
||||
- request:
|
||||
method: get
|
||||
uri: https://api.stackexchange.com/2.2/answers/1163633?filter=!.Fjr38AQkcvWfJTF-2exSL50At_pT&key=&site=askubuntu
|
||||
uri: https://api.stackexchange.com/2.3/answers/1163633?filter=!.Fjr38AQkcvWfJTF-2exSL50At_pT&key=&site=askubuntu
|
||||
body:
|
||||
encoding: US-ASCII
|
||||
string: ''
|
||||
|
|
@ -59,12 +62,14 @@ http_interactions:
|
|||
code: 200
|
||||
message: OK
|
||||
headers:
|
||||
Cache-Control:
|
||||
- private
|
||||
Content-Length:
|
||||
- '608'
|
||||
- '607'
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Strict-Transport-Security:
|
||||
- max-age=2592000
|
||||
- max-age=15552000
|
||||
Access-Control-Allow-Origin:
|
||||
- "*"
|
||||
Access-Control-Allow-Methods:
|
||||
|
|
@ -74,12 +79,14 @@ http_interactions:
|
|||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-Request-Guid:
|
||||
- 9e23222f-b1d1-4841-9349-623e806a0321
|
||||
- 5b3286b4-7d9b-44d9-9ed1-a2b0abda5ae4
|
||||
Content-Security-Policy:
|
||||
- upgrade-insecure-requests; frame-ancestors 'self' https://stackexchange.com
|
||||
Date:
|
||||
- Mon, 13 Jan 2020 12:22:39 GMT
|
||||
- Fri, 04 Feb 2022 14:00:21 GMT
|
||||
body:
|
||||
encoding: ASCII-8BIT
|
||||
string: '{"items":[{"tags":[],"comment_count":3,"is_accepted":false,"score":1,"last_activity_date":1565109531,"last_edit_date":1565109531,"creation_date":1565033637,"answer_id":1163633,"question_id":885432,"link":"https://askubuntu.com/questions/885432/help-setting-up-sshfs/1163633#1163633","title":"Help
|
||||
string: '{"items":[{"tags":[],"comment_count":3,"is_accepted":false,"score":3,"last_activity_date":1565109531,"last_edit_date":1565109531,"creation_date":1565033637,"answer_id":1163633,"question_id":885432,"link":"https://askubuntu.com/questions/885432/help-setting-up-sshfs/1163633#1163633","title":"Help
|
||||
setting up sshfs","body":"<p>As you are getting the following error message:</p>\n\n<blockquote>\n <p>gpasswd:
|
||||
group''fuse'' does not exist in /etc/group</p>\n</blockquote>\n\n<p>This indicates
|
||||
that there is no such group, yet!<br>\nPlease test if there is and subsequently
|
||||
|
|
@ -88,7 +95,6 @@ http_interactions:
|
|||
there is it would look like: </p>\n\n<pre><code>fuse:x:1001: \n</code></pre></li>\n<li><p>If
|
||||
there isn''t, add one like this: </p>\n\n<pre><code>sudo groupadd fuse \n</code></pre></li>\n<li><p>At
|
||||
this point you can add yourself to the group with: </p>\n\n<pre><code>sudo
|
||||
usermod -a -G fuse your_username \n</code></pre></li>\n</ol>\n"}],"has_more":false,"quota_max":300,"quota_remaining":289}'
|
||||
http_version:
|
||||
recorded_at: Mon, 13 Jan 2020 12:22:39 GMT
|
||||
recorded_with: VCR 5.0.0
|
||||
usermod -a -G fuse your_username \n</code></pre></li>\n</ol>\n"}],"has_more":false,"quota_max":300,"quota_remaining":192}'
|
||||
recorded_at: Fri, 04 Feb 2022 14:00:21 GMT
|
||||
recorded_with: VCR 6.0.0
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
http_interactions:
|
||||
- request:
|
||||
method: get
|
||||
uri: https://api.stackexchange.com/2.2/posts/57496168?filter=!3tz1WbZW5JxrG-f99&key=&site=stackoverflow
|
||||
uri: https://api.stackexchange.com/2.3/posts/57496168?filter=!3tz1WbZW5JxrG-f99&key=&site=stackoverflow
|
||||
body:
|
||||
encoding: US-ASCII
|
||||
string: ''
|
||||
|
|
@ -18,12 +18,14 @@ http_interactions:
|
|||
code: 200
|
||||
message: OK
|
||||
headers:
|
||||
Cache-Control:
|
||||
- private
|
||||
Content-Length:
|
||||
- '141'
|
||||
- '143'
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Strict-Transport-Security:
|
||||
- max-age=2592000
|
||||
- max-age=15552000
|
||||
Access-Control-Allow-Origin:
|
||||
- "*"
|
||||
Access-Control-Allow-Methods:
|
||||
|
|
@ -33,17 +35,18 @@ http_interactions:
|
|||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-Request-Guid:
|
||||
- d25e24d9-a659-49ff-ab38-aaaef36d5bbf
|
||||
- 11201cf1-0f43-4e4c-b839-6f9b4e8aa3c3
|
||||
Content-Security-Policy:
|
||||
- upgrade-insecure-requests; frame-ancestors 'self' https://stackexchange.com
|
||||
Date:
|
||||
- Mon, 13 Jan 2020 12:20:49 GMT
|
||||
- Fri, 04 Feb 2022 14:00:21 GMT
|
||||
body:
|
||||
encoding: ASCII-8BIT
|
||||
string: '{"items":[{"last_activity_date":1565806685,"post_type":"question","post_id":57496168}],"has_more":false,"quota_max":300,"quota_remaining":291}'
|
||||
http_version:
|
||||
recorded_at: Mon, 13 Jan 2020 12:20:50 GMT
|
||||
string: '{"items":[{"last_activity_date":1565806685,"post_type":"question","post_id":57496168}],"has_more":false,"quota_max":300,"quota_remaining":194}'
|
||||
recorded_at: Fri, 04 Feb 2022 14:00:21 GMT
|
||||
- request:
|
||||
method: get
|
||||
uri: https://api.stackexchange.com/2.2/questions/57496168?filter=!*1SgQGDOL9bPBHULz9sKS.y6qv7V9fYNszvdhDuv5&key=&site=stackoverflow
|
||||
uri: https://api.stackexchange.com/2.3/questions/57496168?filter=!*1SgQGDOL9bPBHULz9sKS.y6qv7V9fYNszvdhDuv5&key=&site=stackoverflow
|
||||
body:
|
||||
encoding: US-ASCII
|
||||
string: ''
|
||||
|
|
@ -59,12 +62,14 @@ http_interactions:
|
|||
code: 200
|
||||
message: OK
|
||||
headers:
|
||||
Cache-Control:
|
||||
- private
|
||||
Content-Length:
|
||||
- '532'
|
||||
- '530'
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Strict-Transport-Security:
|
||||
- max-age=2592000
|
||||
- max-age=15552000
|
||||
Access-Control-Allow-Origin:
|
||||
- "*"
|
||||
Access-Control-Allow-Methods:
|
||||
|
|
@ -74,18 +79,19 @@ http_interactions:
|
|||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-Request-Guid:
|
||||
- 644cb806-8678-4b08-bfbf-6f0cdb72a10d
|
||||
- a9c1799b-3e84-4869-8316-7013814a36b7
|
||||
Content-Security-Policy:
|
||||
- upgrade-insecure-requests; frame-ancestors 'self' https://stackexchange.com
|
||||
Date:
|
||||
- Mon, 13 Jan 2020 12:20:50 GMT
|
||||
- Fri, 04 Feb 2022 14:00:21 GMT
|
||||
body:
|
||||
encoding: ASCII-8BIT
|
||||
string: '{"items":[{"tags":["laravel","foreach","view","relationship"],"comment_count":1,"view_count":61,"answer_count":4,"score":0,"last_activity_date":1565806685,"creation_date":1565790943,"question_id":57496168,"link":"https://stackoverflow.com/questions/57496168/use-where-and-limit-to-child-in-foreach","title":"Use
|
||||
string: '{"items":[{"tags":["laravel","foreach","view","relationship"],"comment_count":1,"view_count":79,"answer_count":4,"score":0,"last_activity_date":1565806685,"creation_date":1565790943,"question_id":57496168,"link":"https://stackoverflow.com/questions/57496168/use-where-and-limit-to-child-in-foreach","title":"Use
|
||||
"where" and "limit" to child in @foreach","body":"<p>I
|
||||
want to display all user''s profile into views and they posts. It''s pretty
|
||||
simple:</p>\n\n<pre><code>@foreach($profiles as $profile)\n{{ $profile->name
|
||||
}}\n @foreach($profile->posts as $post)\n {{$post->title}}\n @endforeach\n@endforeach\n</code></pre>\n\n<p>But
|
||||
I want to display only the latests posts (->orderBy(''created_at'', ''desc'')->limit(4)
|
||||
) and only accepted posts (->where(''accept'', 1)). How can I do that?</p>\n"}],"has_more":false,"quota_max":300,"quota_remaining":290}'
|
||||
http_version:
|
||||
recorded_at: Mon, 13 Jan 2020 12:20:51 GMT
|
||||
recorded_with: VCR 5.0.0
|
||||
) and only accepted posts (->where(''accept'', 1)). How can I do that?</p>\n"}],"has_more":false,"quota_max":300,"quota_remaining":193}'
|
||||
recorded_at: Fri, 04 Feb 2022 14:00:21 GMT
|
||||
recorded_with: VCR 6.0.0
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue