From b63d8207a959e1963191a59de5e9a2237e72de10 Mon Sep 17 00:00:00 2001 From: evazion Date: Thu, 18 Feb 2021 07:08:45 -0600 Subject: [PATCH] forum: automatically post new forum posts to Discord. --- .github/workflows/test.yaml | 2 + app/jobs/discord_notification_job.rb | 7 ++++ app/logical/d_text.rb | 23 ++++++++++++ app/logical/discord_api_client.rb | 56 ++++++++++++++++++++++++++++ app/models/forum_post.rb | 10 +++++ config/danbooru_default_config.rb | 8 ++++ 6 files changed, 106 insertions(+) create mode 100644 app/jobs/discord_notification_job.rb create mode 100644 app/logical/discord_api_client.rb diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 0b7557ddc..f2d756acf 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -44,6 +44,8 @@ jobs: DANBOORU_PAWOO_CLIENT_SECRET: ${{ secrets.DANBOORU_PAWOO_CLIENT_SECRET }} DANBOORU_BARAAG_CLIENT_ID: ${{ secrets.DANBOORU_BARAAG_CLIENT_ID }} DANBOORU_BARAAG_CLIENT_SECRET: ${{ secrets.DANBOORU_BARAAG_CLIENT_SECRET }} + DANBOORU_DISCORD_WEBHOOK_ID: ${{ secrets.DANBOORU_DISCORD_WEBHOOK_ID }} + DANBOORU_DISCORD_WEBHOOK_SECRET: ${{ secrets.DANBOORU_DISCORD_WEBHOOK_SECRET }} DANBOORU_RAKISMET_KEY: ${{ secrets.DANBOORU_RAKISMET_KEY }} DANBOORU_RAKISMET_URL: ${{ secrets.DANBOORU_RAKISMET_URL }} DANBOORU_IP_REGISTRY_API_KEY: ${{ secrets.DANBOORU_IP_REGISTRY_API_KEY }} diff --git a/app/jobs/discord_notification_job.rb b/app/jobs/discord_notification_job.rb new file mode 100644 index 000000000..20251c178 --- /dev/null +++ b/app/jobs/discord_notification_job.rb @@ -0,0 +1,7 @@ +class DiscordNotificationJob < ApplicationJob + retry_on Exception, attempts: 0 + + def perform(forum_post:) + forum_post.send_discord_notification + end +end diff --git a/app/logical/d_text.rb b/app/logical/d_text.rb index f665c4923..61c34db6f 100644 --- a/app/logical/d_text.rb +++ b/app/logical/d_text.rb @@ -250,6 +250,29 @@ class DText text.gsub(/\A[[:space:]]+|[[:space:]]+\z/, "") end + def self.to_markdown(dtext) + html_to_markdown(format_text(dtext)) + end + + def self.html_to_markdown(html) + html = Nokogiri::HTML.fragment(html) + + html.children.map do |node| + case node.name + when "div", "blockquote", "table" + "" # strip [expand], [quote], and [table] tags + when "br" + "\n" + when "text" + node.text.gsub(/_/, '\_').gsub(/\*/, '\*') + when "p", "h1", "h2", "h3", "h4", "h5", "h6" + html_to_markdown(node.inner_html) + "\n\n" + else + html_to_markdown(node.inner_html) + end + end.join + end + def self.from_html(text, inline: false, &block) html = Nokogiri::HTML.fragment(text) diff --git a/app/logical/discord_api_client.rb b/app/logical/discord_api_client.rb new file mode 100644 index 000000000..8a1131058 --- /dev/null +++ b/app/logical/discord_api_client.rb @@ -0,0 +1,56 @@ +class DiscordApiClient + attr_reader :webhook_id, :webhook_secret, :http + + def initialize(webhook_id: Danbooru.config.discord_webhook_id, webhook_secret: Danbooru.config.discord_webhook_secret, http: Danbooru::Http.new) + @webhook_id = webhook_id + @webhook_secret = webhook_secret + @http = http + end + + def enabled? + webhook_id.present? && webhook_secret.present? + end + + # https://discord.com/developers/docs/resources/webhook#execute-webhook + def post_message(forum_post) + return unless enabled? + + http.post(webhook_url, params: { wait: true }, json: build_message(forum_post)) + end + + # https://discord.com/developers/docs/resources/channel#embed-object + def build_message(forum_post) + { + embeds: [{ + title: forum_post.topic.title, + description: convert_dtext(forum_post.body), + timestamp: forum_post.created_at.iso8601, + url: Routes.url_for(forum_post), + author: { + name: forum_post.creator.name, + url: Routes.url_for(forum_post.creator) + }, + fields: [ + { + name: "Replies", + value: forum_post.topic.response_count, + inline: true + }, + { + name: "Users", + value: forum_post.topic.forum_posts.distinct.count(:creator_id), + inline: true + } + ] + }] + } + end + + def convert_dtext(dtext) + DText.to_markdown(dtext).truncate(2000) + end + + def webhook_url + "https://discord.com/api/webhooks/#{webhook_id}/#{webhook_secret}" + end +end diff --git a/app/models/forum_post.rb b/app/models/forum_post.rb index 16941f3c9..79ffd795a 100644 --- a/app/models/forum_post.rb +++ b/app/models/forum_post.rb @@ -23,6 +23,7 @@ class ForumPost < ApplicationRecord after_destroy(:if => ->(rec) {rec.updater_id != rec.creator_id}) do |rec| ModAction.log("#{CurrentUser.user.name} deleted forum ##{rec.id}", :forum_post_delete) end + after_create_commit :async_send_discord_notification deletable mentionable( @@ -153,6 +154,15 @@ class ForumPost < ApplicationRecord end end + def async_send_discord_notification + DiscordNotificationJob.perform_later(forum_post: self) + end + + def send_discord_notification + return unless policy(User.anonymous).show? + DiscordApiClient.new.post_message(self) + end + def build_response dup.tap do |x| x.body = x.quoted_response diff --git a/config/danbooru_default_config.rb b/config/danbooru_default_config.rb index 291d79974..7bd5bc404 100644 --- a/config/danbooru_default_config.rb +++ b/config/danbooru_default_config.rb @@ -394,6 +394,14 @@ module Danbooru def twitter_api_secret end + # If defined, Danbooru will automatically post new forum posts to the + # Discord channel belonging to this webhook. + def discord_webhook_id + end + + def discord_webhook_secret + end + # you should override this def email_key "zDMSATq0W3hmA5p3rKTgD"