From e11cd288b9871a5b85dcd28fad6341fc36d4754a Mon Sep 17 00:00:00 2001 From: evazion Date: Fri, 2 Dec 2022 00:59:23 -0600 Subject: [PATCH] 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. --- app/components/open_graph_component.rb | 68 +++++++++++++++++++ .../open_graph_component.html.erb | 23 +++++++ app/helpers/seo_helper.rb | 13 ---- app/models/post.rb | 16 ----- app/views/media_assets/show.html.erb | 4 ++ app/views/posts/index.html.erb | 10 ++- app/views/posts/show.html.erb | 16 +---- 7 files changed, 100 insertions(+), 50 deletions(-) create mode 100644 app/components/open_graph_component.rb create mode 100644 app/components/open_graph_component/open_graph_component.html.erb diff --git a/app/components/open_graph_component.rb b/app/components/open_graph_component.rb new file mode 100644 index 000000000..168a40916 --- /dev/null +++ b/app/components/open_graph_component.rb @@ -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 diff --git a/app/components/open_graph_component/open_graph_component.html.erb b/app/components/open_graph_component/open_graph_component.html.erb new file mode 100644 index 000000000..b4b422361 --- /dev/null +++ b/app/components/open_graph_component/open_graph_component.html.erb @@ -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 %> diff --git a/app/helpers/seo_helper.rb b/app/helpers/seo_helper.rb index faedbe148..cd7dd1725 100644 --- a/app/helpers/seo_helper.rb +++ b/app/helpers/seo_helper.rb @@ -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." 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 urls = [ Danbooru.config.twitter_url, diff --git a/app/models/post.rb b/app/models/post.rb index b24913b97..beb5d96ec 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -167,18 +167,6 @@ class Post < ApplicationRecord media_asset.variant(:preview).file_url 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) if user.default_image_size == "large" && image_width > Danbooru.config.large_image_width tagged_large_file_url @@ -209,10 +197,6 @@ class Post < ApplicationRecord end concerning :ImageMethods do - def twitter_card_supported? - image_width.to_i >= 280 && image_height.to_i >= 150 - end - def has_large? return false if has_tag?("animated_gif") || has_tag?("animated_png") return true if is_ugoira? diff --git a/app/views/media_assets/show.html.erb b/app/views/media_assets/show.html.erb index 64093aaff..e295cff4d 100644 --- a/app/views/media_assets/show.html.erb +++ b/app/views/media_assets/show.html.erb @@ -193,4 +193,8 @@ +<% content_for(:html_header) do %> + <%= render OpenGraphComponent.new(media_asset: @media_asset, current_user: CurrentUser.user) %> +<% end %> + <%= render "uploads/secondary_links" %> diff --git a/app/views/posts/index.html.erb b/app/views/posts/index.html.erb index 958d1d187..7faa61883 100644 --- a/app/views/posts/index.html.erb +++ b/app/views/posts/index.html.erb @@ -237,6 +237,10 @@ <% page_title(@post_set.page_title) %> <% meta_description @post_set.meta_description %> <% 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 %> <% if params[:tags].blank? && @post_set.current_page == 1 %> @@ -250,10 +254,4 @@ <% if @post_set.has_explicit? %> <% 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 %> diff --git a/app/views/posts/show.html.erb b/app/views/posts/show.html.erb index 3a4d52c41..e55292e5c 100644 --- a/app/views/posts/show.html.erb +++ b/app/views/posts/show.html.erb @@ -393,21 +393,7 @@ - <% if policy(@post).visible? %> - <%= 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? %> - - - <% if policy(@post).visible? %> - <%= tag.meta name: "twitter:image", content: @post.open_graph_image_url %> - <% end %> - <% end %> + <%= render OpenGraphComponent.new(media_asset: @post.media_asset, current_user: CurrentUser.user) %> <% if @post.rating == "e" %>