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" %>