331 lines
11 KiB
Ruby
331 lines
11 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# name: discourse-narrative-bot
|
|
# about: Introduces staff to Discourse
|
|
# version: 1.0
|
|
# authors: Nick Sahler, Alan Tan
|
|
# url: https://github.com/discourse/discourse/tree/main/plugins/discourse-narrative-bot
|
|
|
|
enabled_site_setting :discourse_narrative_bot_enabled
|
|
hide_plugin
|
|
|
|
if Rails.env == "development"
|
|
# workaround, teach reloader to reload jobs
|
|
# if we do not do this then
|
|
#
|
|
# 1. on reload rails goes and undefines Jobs::Base
|
|
# 2. as a side effect this undefines Jobs::BotInput
|
|
# 3. we have a post_edited hook that queues a job for bot input
|
|
# 4. if you are not running sidekiq in dev every time you save a post it will trigger it
|
|
# 5. but the constant can not be autoloaded
|
|
Rails.configuration.autoload_paths << File.expand_path("../autoload/jobs", __FILE__)
|
|
end
|
|
|
|
require_relative "lib/discourse_narrative_bot/welcome_post_type_site_setting.rb"
|
|
register_asset "stylesheets/discourse-narrative-bot.scss"
|
|
|
|
after_initialize do
|
|
SeedFu.fixture_paths << Rails
|
|
.root
|
|
.join("plugins", "discourse-narrative-bot", "db", "fixtures")
|
|
.to_s
|
|
|
|
Mime::Type.register "image/svg+xml", :svg
|
|
|
|
%w[
|
|
../autoload/jobs/regular/bot_input.rb
|
|
../autoload/jobs/regular/narrative_timeout.rb
|
|
../autoload/jobs/regular/narrative_init.rb
|
|
../autoload/jobs/regular/send_default_welcome_message.rb
|
|
../autoload/jobs/onceoff/discourse_narrative_bot/grant_badges.rb
|
|
../autoload/jobs/onceoff/discourse_narrative_bot/remap_old_bot_images.rb
|
|
../lib/discourse_narrative_bot/actions.rb
|
|
../lib/discourse_narrative_bot/base.rb
|
|
../lib/discourse_narrative_bot/new_user_narrative.rb
|
|
../lib/discourse_narrative_bot/advanced_user_narrative.rb
|
|
../lib/discourse_narrative_bot/track_selector.rb
|
|
../lib/discourse_narrative_bot/certificate_generator.rb
|
|
../lib/discourse_narrative_bot/dice.rb
|
|
../lib/discourse_narrative_bot/quote_generator.rb
|
|
../lib/discourse_narrative_bot/magic_8_ball.rb
|
|
../lib/discourse_narrative_bot/welcome_post_type_site_setting.rb
|
|
].each { |path| load File.expand_path(path, __FILE__) }
|
|
|
|
RailsMultisite::ConnectionManagement.safe_each_connection do
|
|
if SiteSetting.discourse_narrative_bot_enabled
|
|
# Disable welcome message because that is what the bot is supposed to replace.
|
|
SiteSetting.send_welcome_message = false
|
|
|
|
certificate_path = "#{Discourse.base_url}/discobot/certificate.svg"
|
|
if !SiteSetting.allowed_iframes.include?(certificate_path)
|
|
SiteSetting.allowed_iframes =
|
|
SiteSetting.allowed_iframes.split("|").append(certificate_path).join("|")
|
|
end
|
|
end
|
|
end
|
|
|
|
require_dependency "plugin_store"
|
|
|
|
module ::DiscourseNarrativeBot
|
|
PLUGIN_NAME = "discourse-narrative-bot".freeze
|
|
BOT_USER_ID = -2
|
|
|
|
class Engine < ::Rails::Engine
|
|
engine_name PLUGIN_NAME
|
|
isolate_namespace DiscourseNarrativeBot
|
|
end
|
|
|
|
class Store
|
|
def self.set(key, value)
|
|
::PluginStore.set(PLUGIN_NAME, key, value)
|
|
end
|
|
|
|
def self.get(key)
|
|
::PluginStore.get(PLUGIN_NAME, key)
|
|
end
|
|
|
|
def self.remove(key)
|
|
::PluginStore.remove(PLUGIN_NAME, key)
|
|
end
|
|
end
|
|
|
|
class CertificatesController < ::ApplicationController
|
|
layout :false
|
|
skip_before_action :check_xhr
|
|
requires_login
|
|
|
|
def generate
|
|
immutable_for(24.hours)
|
|
|
|
%i[date user_id].each do |key|
|
|
unless params[key]&.present?
|
|
raise Discourse::InvalidParameters.new("#{key} must be present")
|
|
end
|
|
end
|
|
|
|
if params[:user_id].to_i != current_user.id
|
|
rate_limiter = RateLimiter.new(current_user, "svg_certificate", 3, 1.minute)
|
|
else
|
|
rate_limiter = RateLimiter.new(current_user, "svg_certificate_self", 30, 10.minutes)
|
|
end
|
|
rate_limiter.performed! unless current_user.staff?
|
|
|
|
user = User.find_by(id: params[:user_id])
|
|
raise Discourse::NotFound if user.blank?
|
|
|
|
hijack do
|
|
generator = CertificateGenerator.new(user, params[:date], avatar_url(user))
|
|
|
|
svg =
|
|
params[:type] == "advanced" ? generator.advanced_user_track : generator.new_user_track
|
|
|
|
respond_to { |format| format.svg { render inline: svg } }
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def avatar_url(user)
|
|
UrlHelper.absolute(Discourse.base_path + user.avatar_template.gsub("{size}", "250"))
|
|
end
|
|
end
|
|
end
|
|
|
|
DiscourseNarrativeBot::Engine.routes.draw do
|
|
get "/certificate" => "certificates#generate", :format => :svg
|
|
end
|
|
|
|
Discourse::Application.routes.append { mount ::DiscourseNarrativeBot::Engine, at: "/discobot" }
|
|
|
|
self.add_model_callback(User, :after_destroy) { DiscourseNarrativeBot::Store.remove(self.id) }
|
|
|
|
self.on(:user_created) do |user|
|
|
if SiteSetting.discourse_narrative_bot_welcome_post_delay == 0 && !user.staged
|
|
user.enqueue_bot_welcome_post
|
|
end
|
|
end
|
|
|
|
self.on(:user_first_logged_in) do |user|
|
|
user.enqueue_bot_welcome_post if SiteSetting.discourse_narrative_bot_welcome_post_delay > 0
|
|
end
|
|
|
|
self.on(:user_unstaged) { |user| user.enqueue_bot_welcome_post }
|
|
|
|
self.add_model_callback(UserOption, :after_save) do
|
|
user.delete_bot_welcome_post if saved_change_to_skip_new_user_tips? && self.skip_new_user_tips
|
|
end
|
|
|
|
self.add_to_class(:user, :enqueue_bot_welcome_post) do
|
|
return if SiteSetting.disable_discourse_narrative_bot_welcome_post
|
|
|
|
delay = SiteSetting.discourse_narrative_bot_welcome_post_delay
|
|
|
|
case SiteSetting.discourse_narrative_bot_welcome_post_type
|
|
when "new_user_track"
|
|
if enqueue_narrative_bot_job? && !manually_disabled_discobot?
|
|
Jobs.enqueue_in(
|
|
delay,
|
|
:narrative_init,
|
|
user_id: self.id,
|
|
klass: DiscourseNarrativeBot::NewUserNarrative.to_s,
|
|
)
|
|
end
|
|
when "welcome_message"
|
|
Jobs.enqueue_in(delay, :send_default_welcome_message, user_id: self.id)
|
|
end
|
|
end
|
|
|
|
self.add_to_class(:user, :manually_disabled_discobot?) { user_option&.skip_new_user_tips }
|
|
|
|
self.add_to_class(:user, :enqueue_narrative_bot_job?) do
|
|
SiteSetting.discourse_narrative_bot_enabled && self.human? && !self.anonymous? &&
|
|
!self.staged &&
|
|
!SiteSetting
|
|
.discourse_narrative_bot_ignored_usernames
|
|
.split("|".freeze)
|
|
.include?(self.username)
|
|
end
|
|
|
|
self.add_to_class(:user, :delete_bot_welcome_post) do
|
|
data = DiscourseNarrativeBot::Store.get(self.id) || {}
|
|
topic_id = data[:topic_id]
|
|
return if topic_id.blank? || data[:track] != DiscourseNarrativeBot::NewUserNarrative.to_s
|
|
|
|
topic_user = topic_users.find_by(topic_id: topic_id)
|
|
return if topic_user.present? && topic_user.last_read_post_number.present?
|
|
|
|
topic = Topic.find_by(id: topic_id)
|
|
return if topic.blank?
|
|
|
|
first_post = topic.ordered_posts.first
|
|
|
|
notification = Notification.where(topic_id: topic.id, post_number: first_post.post_number).first
|
|
if notification.present?
|
|
Notification.read(self, notification.id)
|
|
self.saw_notification_id(notification.id)
|
|
self.reload
|
|
self.publish_notifications_state
|
|
end
|
|
|
|
PostDestroyer.new(Discourse.system_user, first_post, skip_staff_log: true).destroy
|
|
DiscourseNarrativeBot::Store.remove(self.id)
|
|
end
|
|
|
|
self.on(:post_created) do |post, options|
|
|
user = post.user
|
|
|
|
if user&.enqueue_narrative_bot_job? && !options[:skip_bot]
|
|
Jobs.enqueue(:bot_input, user_id: user.id, post_id: post.id, input: "reply")
|
|
end
|
|
end
|
|
|
|
self.on(:post_edited) do |post|
|
|
if post.user&.enqueue_narrative_bot_job?
|
|
Jobs.enqueue(:bot_input, user_id: post.user.id, post_id: post.id, input: "edit")
|
|
end
|
|
end
|
|
|
|
self.on(:post_destroyed) do |post, options, user|
|
|
if user&.enqueue_narrative_bot_job? && !options[:skip_bot]
|
|
Jobs.enqueue(
|
|
:bot_input,
|
|
user_id: user.id,
|
|
post_id: post.id,
|
|
topic_id: post.topic_id,
|
|
input: "delete",
|
|
)
|
|
end
|
|
end
|
|
|
|
self.on(:post_recovered) do |post, _, user|
|
|
if user&.enqueue_narrative_bot_job?
|
|
Jobs.enqueue(:bot_input, user_id: user.id, post_id: post.id, input: "recover")
|
|
end
|
|
end
|
|
|
|
self.add_model_callback(PostAction, :after_commit, on: :create) do
|
|
if self.post && self.user.enqueue_narrative_bot_job?
|
|
input =
|
|
case self.post_action_type_id
|
|
when *PostActionType.flag_types.values
|
|
self.post_action_type_id == PostActionType.types[:inappropriate] ? "flag" : "reply"
|
|
when PostActionType.types[:like]
|
|
"like"
|
|
end
|
|
|
|
Jobs.enqueue(:bot_input, user_id: self.user.id, post_id: self.post.id, input: input) if input
|
|
end
|
|
end
|
|
|
|
self.add_model_callback(Bookmark, :after_commit, on: :create) do
|
|
if self.user.enqueue_narrative_bot_job?
|
|
if self.bookmarkable_type == "Post"
|
|
Jobs.enqueue(
|
|
:bot_input,
|
|
user_id: self.user_id,
|
|
post_id: self.bookmarkable_id,
|
|
input: "bookmark",
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
self.on(:topic_notification_level_changed) do |_, user_id, topic_id|
|
|
user = User.find_by(id: user_id)
|
|
|
|
if user && user.enqueue_narrative_bot_job?
|
|
Jobs.enqueue(
|
|
:bot_input,
|
|
user_id: user_id,
|
|
topic_id: topic_id,
|
|
input: "topic_notification_level_changed",
|
|
)
|
|
end
|
|
end
|
|
|
|
UserAvatar.register_custom_user_gravatar_email_hash(
|
|
DiscourseNarrativeBot::BOT_USER_ID,
|
|
"discobot@discourse.org",
|
|
)
|
|
|
|
self.on(:system_message_sent) do |args|
|
|
next if !SiteSetting.discourse_narrative_bot_enabled
|
|
next if args[:message_type] != "tl2_promotion_message"
|
|
|
|
recipient = args[:recipient]
|
|
next if recipient.nil?
|
|
|
|
I18n.with_locale(recipient.effective_locale) do
|
|
raw =
|
|
I18n.t(
|
|
"discourse_narrative_bot.tl2_promotion_message.text_body_template",
|
|
discobot_username: ::DiscourseNarrativeBot::Base.new.discobot_username,
|
|
reset_trigger:
|
|
"#{::DiscourseNarrativeBot::TrackSelector.reset_trigger} #{::DiscourseNarrativeBot::AdvancedUserNarrative.reset_trigger}",
|
|
)
|
|
|
|
PostCreator.create!(
|
|
::DiscourseNarrativeBot::Base.new.discobot_user,
|
|
title: I18n.t("discourse_narrative_bot.tl2_promotion_message.subject_template"),
|
|
raw: raw,
|
|
skip_validations: true,
|
|
archetype: Archetype.private_message,
|
|
target_usernames: recipient.username,
|
|
)
|
|
end
|
|
end
|
|
|
|
PostGuardian.class_eval do
|
|
alias_method :existing_can_create_post?, :can_create_post?
|
|
|
|
def can_create_post?(parent)
|
|
if SiteSetting.discourse_narrative_bot_enabled && parent.try(:subtype) == "system_message" &&
|
|
parent.try(:user) == ::DiscourseNarrativeBot::Base.new.discobot_user
|
|
return true
|
|
end
|
|
|
|
existing_can_create_post?(parent)
|
|
end
|
|
end
|
|
end
|