Fix #5360: Use OpenGraph's og:image metadata for posts.
* Add og:image:width, og:image:height, and og:image:type tags. * Use og:video tags for videos. * Use 720x720 instead of 150x150 preview images for videos. * Add duration tag to JSON-LD data for videos. * Add OpenGraph tags to media assets show page. * Respect Twitter max image size limits. * Don't include OpenGraph image tags when someone shares a plain https://danbooru.donmai.us link with no tag search. This caused random potentially NSFW images to be shown when someone shared a https://danbooru.donmai.us link on social media, which could be cached for long periods of time.
This commit is contained in:
68
app/components/open_graph_component.rb
Normal file
68
app/components/open_graph_component.rb
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Render Open Graph metatags for an image or video.
|
||||||
|
#
|
||||||
|
# @see https://ogp.me/
|
||||||
|
# @see https://www.opengraph.xyz/
|
||||||
|
# @see https://developers.facebook.com/docs/sharing/webmasters/
|
||||||
|
# @see https://developers.facebook.com/tools/debug/
|
||||||
|
# @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/guides/getting-started
|
||||||
|
# @see https://developers.google.com/search/docs/appearance/structured-data
|
||||||
|
# @see https://search.google.com/test/rich-results
|
||||||
|
# @see https://validator.schema.org/
|
||||||
|
class OpenGraphComponent < ApplicationComponent
|
||||||
|
extend Memoist
|
||||||
|
|
||||||
|
attr_reader :media_asset, :current_user
|
||||||
|
|
||||||
|
delegate :json_ld_tag, :page_title, :meta_description, to: :helpers
|
||||||
|
delegate :is_image?, :is_video?, :is_ugoira?, :image_width, :image_height, :file_size, :mime_type, :variant, :has_variant?, to: :media_asset
|
||||||
|
|
||||||
|
def initialize(media_asset:, current_user:)
|
||||||
|
@media_asset = media_asset
|
||||||
|
@current_user = current_user
|
||||||
|
end
|
||||||
|
|
||||||
|
memoize def video_url
|
||||||
|
if is_video?
|
||||||
|
variant("original").file_url
|
||||||
|
elsif is_ugoira?
|
||||||
|
variant("sample").file_url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
memoize def image_url
|
||||||
|
if is_image?
|
||||||
|
variant("original").file_url
|
||||||
|
elsif is_video? || is_ugoira?
|
||||||
|
variant("720x720").file_url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/summary-card-with-large-image
|
||||||
|
#
|
||||||
|
# Images for this Card support an aspect ratio of 2:1 with minimum dimensions of 300x157 or maximum of 4096x4096
|
||||||
|
# pixels. Images must be less than 5MB in size. JPG, PNG, WEBP and GIF formats are supported. Only the first frame of
|
||||||
|
# an animated GIF will be used. SVG is not supported.
|
||||||
|
memoize def twitter_image_url
|
||||||
|
if is_image? && file_size < 5.megabytes && image_width <= 4096 && image_height <= 4096
|
||||||
|
variant("original").file_url
|
||||||
|
elsif has_variant?("720x720")
|
||||||
|
variant("720x720").file_url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# https://developers.google.com/search/docs/data-types/video#video-object
|
||||||
|
def json_ld_video_data
|
||||||
|
json_ld_tag({
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "VideoObject",
|
||||||
|
name: page_title,
|
||||||
|
description: meta_description,
|
||||||
|
uploadDate: (media_asset.post || media_asset).created_at.iso8601,
|
||||||
|
duration: media_asset.duration.seconds.iso8601,
|
||||||
|
thumbnailUrl: image_url,
|
||||||
|
contentUrl: video_url,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<% if policy(media_asset).can_see_image? %>
|
||||||
|
<% if is_image? %>
|
||||||
|
<%= tag.meta property: "og:image", content: image_url %>
|
||||||
|
<%= tag.meta property: "og:image:secure_url", content: image_url %>
|
||||||
|
<%= tag.meta property: "og:image:width", content: image_width %>
|
||||||
|
<%= tag.meta property: "og:image:height", content: image_height %>
|
||||||
|
<%= tag.meta property: "og:image:type", content: mime_type %>
|
||||||
|
<% elsif is_video? || is_ugoira? %>
|
||||||
|
<%= tag.meta property: "og:image", content: image_url %>
|
||||||
|
<%= tag.meta property: "og:video", content: video_url %>
|
||||||
|
<%= tag.meta property: "og:video:secure_url", content: video_url %>
|
||||||
|
<%= tag.meta property: "og:video:width", content: image_width %>
|
||||||
|
<%= tag.meta property: "og:video:height", content: image_height %>
|
||||||
|
<%= tag.meta property: "og:video:type", content: mime_type %>
|
||||||
|
|
||||||
|
<%= json_ld_video_data %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% if twitter_image_url.present? %>
|
||||||
|
<%= tag.meta name: "twitter:card", content: "summary_large_image" %>
|
||||||
|
<%= tag.meta name: "twitter:image", content: twitter_image_url %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
@@ -14,19 +14,6 @@ module SeoHelper
|
|||||||
"#{Danbooru.config.canonical_app_name} is the original anime image booru. Search millions of anime pictures categorized by thousands of tags."
|
"#{Danbooru.config.canonical_app_name} is the original anime image booru. Search millions of anime pictures categorized by thousands of tags."
|
||||||
end
|
end
|
||||||
|
|
||||||
# https://developers.google.com/search/docs/data-types/video#video-object
|
|
||||||
def json_ld_video_data(post)
|
|
||||||
json_ld_tag({
|
|
||||||
"@context": "https://schema.org",
|
|
||||||
"@type": "VideoObject",
|
|
||||||
name: page_title,
|
|
||||||
description: meta_description,
|
|
||||||
uploadDate: post.created_at.iso8601,
|
|
||||||
thumbnailUrl: post.media_asset.variant("360x360").file_url,
|
|
||||||
contentUrl: post.file_url,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
def json_ld_website_data
|
def json_ld_website_data
|
||||||
urls = [
|
urls = [
|
||||||
Danbooru.config.twitter_url,
|
Danbooru.config.twitter_url,
|
||||||
|
|||||||
@@ -167,18 +167,6 @@ class Post < ApplicationRecord
|
|||||||
media_asset.variant(:preview).file_url
|
media_asset.variant(:preview).file_url
|
||||||
end
|
end
|
||||||
|
|
||||||
def open_graph_image_url
|
|
||||||
if is_image?
|
|
||||||
if has_large?
|
|
||||||
large_file_url
|
|
||||||
else
|
|
||||||
file_url
|
|
||||||
end
|
|
||||||
else
|
|
||||||
preview_file_url
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def file_url_for(user)
|
def file_url_for(user)
|
||||||
if user.default_image_size == "large" && image_width > Danbooru.config.large_image_width
|
if user.default_image_size == "large" && image_width > Danbooru.config.large_image_width
|
||||||
tagged_large_file_url
|
tagged_large_file_url
|
||||||
@@ -209,10 +197,6 @@ class Post < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
concerning :ImageMethods do
|
concerning :ImageMethods do
|
||||||
def twitter_card_supported?
|
|
||||||
image_width.to_i >= 280 && image_height.to_i >= 150
|
|
||||||
end
|
|
||||||
|
|
||||||
def has_large?
|
def has_large?
|
||||||
return false if has_tag?("animated_gif") || has_tag?("animated_png")
|
return false if has_tag?("animated_gif") || has_tag?("animated_png")
|
||||||
return true if is_ugoira?
|
return true if is_ugoira?
|
||||||
|
|||||||
@@ -193,4 +193,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<% content_for(:html_header) do %>
|
||||||
|
<%= render OpenGraphComponent.new(media_asset: @media_asset, current_user: CurrentUser.user) %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<%= render "uploads/secondary_links" %>
|
<%= render "uploads/secondary_links" %>
|
||||||
|
|||||||
@@ -237,6 +237,10 @@
|
|||||||
<% page_title(@post_set.page_title) %>
|
<% page_title(@post_set.page_title) %>
|
||||||
<% meta_description @post_set.meta_description %>
|
<% meta_description @post_set.meta_description %>
|
||||||
<% atom_feed_tag "Posts: #{@post_set.page_title}", posts_url(tags: @post_set.tag_string, format: :atom) %>
|
<% atom_feed_tag "Posts: #{@post_set.page_title}", posts_url(tags: @post_set.tag_string, format: :atom) %>
|
||||||
|
|
||||||
|
<% if @post_set.best_post.present? %>
|
||||||
|
<%= render OpenGraphComponent.new(media_asset: @post_set.best_post.media_asset, current_user: CurrentUser.user) %>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if params[:tags].blank? && @post_set.current_page == 1 %>
|
<% if params[:tags].blank? && @post_set.current_page == 1 %>
|
||||||
@@ -250,10 +254,4 @@
|
|||||||
<% if @post_set.has_explicit? %>
|
<% if @post_set.has_explicit? %>
|
||||||
<meta name="rating" content="adult">
|
<meta name="rating" content="adult">
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if @post_set.best_post.present? %>
|
|
||||||
<%= tag.meta property: "og:image", content: @post_set.best_post.open_graph_image_url %>
|
|
||||||
<%= tag.meta name: "twitter:image", content: @post_set.best_post.open_graph_image_url %>
|
|
||||||
<%= tag.meta name: "twitter:card", content: "summary_large_image" %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -393,21 +393,7 @@
|
|||||||
<meta name="post-id" content="<%= @post.id %>">
|
<meta name="post-id" content="<%= @post.id %>">
|
||||||
<meta name="post-has-embedded-notes" content="<%= @post.has_embedded_notes? %>">
|
<meta name="post-has-embedded-notes" content="<%= @post.has_embedded_notes? %>">
|
||||||
|
|
||||||
<% if policy(@post).visible? %>
|
<%= render OpenGraphComponent.new(media_asset: @post.media_asset, current_user: CurrentUser.user) %>
|
||||||
<%= tag.meta property: "og:image", content: @post.open_graph_image_url %>
|
|
||||||
|
|
||||||
<% if @post.is_video? %>
|
|
||||||
<%= json_ld_video_data(@post) %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<% if @post.twitter_card_supported? %>
|
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
|
||||||
|
|
||||||
<% if policy(@post).visible? %>
|
|
||||||
<%= tag.meta name: "twitter:image", content: @post.open_graph_image_url %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<% if @post.rating == "e" %>
|
<% if @post.rating == "e" %>
|
||||||
<meta name="rating" content="adult">
|
<meta name="rating" content="adult">
|
||||||
|
|||||||
Reference in New Issue
Block a user