436 lines
12 KiB
Ruby
436 lines
12 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module DiscourseNarrativeBot
|
|
class AdvancedUserNarrative < Base
|
|
I18N_KEY = "discourse_narrative_bot.advanced_user_narrative".freeze
|
|
BADGE_NAME = "Licensed".freeze
|
|
|
|
TRANSITION_TABLE = {
|
|
begin: {
|
|
next_state: :tutorial_edit,
|
|
next_instructions: Proc.new { I18n.t("#{I18N_KEY}.edit.instructions", i18n_post_args) },
|
|
init: {
|
|
action: :start_advanced_track,
|
|
},
|
|
},
|
|
tutorial_edit: {
|
|
next_state: :tutorial_delete,
|
|
next_instructions: Proc.new { I18n.t("#{I18N_KEY}.delete.instructions", i18n_post_args) },
|
|
edit: {
|
|
action: :reply_to_edit,
|
|
},
|
|
reply: {
|
|
next_state: :tutorial_edit,
|
|
action: :missing_edit,
|
|
},
|
|
},
|
|
tutorial_delete: {
|
|
next_state: :tutorial_recover,
|
|
next_instructions: Proc.new { I18n.t("#{I18N_KEY}.recover.instructions", i18n_post_args) },
|
|
delete: {
|
|
action: :reply_to_delete,
|
|
},
|
|
reply: {
|
|
next_state: :tutorial_delete,
|
|
action: :missing_delete,
|
|
},
|
|
},
|
|
tutorial_recover: {
|
|
next_state: :tutorial_category_hashtag,
|
|
next_instructions:
|
|
Proc.new do
|
|
category = Category.secured(Guardian.new(@user)).last
|
|
slug = category.slug
|
|
|
|
if parent_category = category.parent_category
|
|
slug = "#{parent_category.slug}#{CategoryHashtag::SEPARATOR}#{slug}"
|
|
end
|
|
|
|
# TODO (martin) When enable_experimental_hashtag_autocomplete is the only option
|
|
# update the instructions and remove instructions_experimental, as well as the
|
|
# not_found translation
|
|
if SiteSetting.enable_experimental_hashtag_autocomplete
|
|
I18n.t(
|
|
"#{I18N_KEY}.category_hashtag.instructions_experimental",
|
|
i18n_post_args(category: "##{slug}"),
|
|
)
|
|
else
|
|
I18n.t(
|
|
"#{I18N_KEY}.category_hashtag.instructions",
|
|
i18n_post_args(category: "##{slug}"),
|
|
)
|
|
end
|
|
end,
|
|
recover: {
|
|
action: :reply_to_recover,
|
|
},
|
|
reply: {
|
|
next_state: :tutorial_recover,
|
|
action: :missing_recover,
|
|
},
|
|
},
|
|
tutorial_category_hashtag: {
|
|
next_state: :tutorial_change_topic_notification_level,
|
|
next_instructions:
|
|
Proc.new do
|
|
I18n.t("#{I18N_KEY}.change_topic_notification_level.instructions", i18n_post_args)
|
|
end,
|
|
reply: {
|
|
action: :reply_to_category_hashtag,
|
|
},
|
|
},
|
|
tutorial_change_topic_notification_level: {
|
|
next_state: :tutorial_poll,
|
|
next_instructions: Proc.new { I18n.t("#{I18N_KEY}.poll.instructions", i18n_post_args) },
|
|
topic_notification_level_changed: {
|
|
action: :reply_to_topic_notification_level_changed,
|
|
},
|
|
reply: {
|
|
next_state: :tutorial_change_topic_notification_level,
|
|
action: :missing_topic_notification_level_change,
|
|
},
|
|
},
|
|
tutorial_poll: {
|
|
prerequisite:
|
|
Proc.new do
|
|
SiteSetting.poll_enabled &&
|
|
@user.has_trust_level?(SiteSetting.poll_minimum_trust_level_to_create)
|
|
end,
|
|
next_state: :tutorial_details,
|
|
next_instructions: Proc.new { I18n.t("#{I18N_KEY}.details.instructions", i18n_post_args) },
|
|
reply: {
|
|
action: :reply_to_poll,
|
|
},
|
|
},
|
|
tutorial_details: {
|
|
next_state: :end,
|
|
reply: {
|
|
action: :reply_to_details,
|
|
},
|
|
},
|
|
}
|
|
|
|
def self.badge_name
|
|
BADGE_NAME
|
|
end
|
|
|
|
def self.reset_trigger
|
|
I18n.t("discourse_narrative_bot.advanced_user_narrative.reset_trigger")
|
|
end
|
|
|
|
def reset_bot(user, post)
|
|
if pm_to_bot?(post)
|
|
reset_data(user, topic_id: post.topic_id)
|
|
else
|
|
reset_data(user)
|
|
end
|
|
|
|
Jobs.enqueue_in(2.seconds, :narrative_init, user_id: user.id, klass: self.class.to_s)
|
|
end
|
|
|
|
private
|
|
|
|
def init_tutorial_edit
|
|
data = get_data(@user)
|
|
|
|
fake_delay
|
|
|
|
post =
|
|
PostCreator.create!(
|
|
@user,
|
|
raw:
|
|
I18n.t(
|
|
"#{I18N_KEY}.edit.bot_created_post_raw",
|
|
i18n_post_args(discobot_username: self.discobot_username),
|
|
),
|
|
topic_id: data[:topic_id],
|
|
skip_bot: true,
|
|
skip_validations: true,
|
|
)
|
|
|
|
set_state_data(:post_id, post.id)
|
|
post
|
|
end
|
|
|
|
def init_tutorial_recover
|
|
data = get_data(@user)
|
|
|
|
post =
|
|
PostCreator.create!(
|
|
@user,
|
|
raw:
|
|
I18n.t(
|
|
"#{I18N_KEY}.recover.deleted_post_raw",
|
|
i18n_post_args(discobot_username: self.discobot_username),
|
|
),
|
|
topic_id: data[:topic_id],
|
|
skip_bot: true,
|
|
skip_validations: true,
|
|
)
|
|
|
|
set_state_data(:post_id, post.id)
|
|
|
|
opts = { skip_bot: true }
|
|
|
|
if SiteSetting.delete_removed_posts_after < 1
|
|
opts[:delete_removed_posts_after] = 1
|
|
|
|
result = PostActionCreator.notify_moderators(self.discobot_user, post)
|
|
result.reviewable.perform(self.discobot_user, :ignore_and_do_nothing)
|
|
end
|
|
|
|
PostDestroyer.new(@user, post, opts).destroy
|
|
end
|
|
|
|
def start_advanced_track
|
|
raw = I18n.t("#{I18N_KEY}.start_message", i18n_post_args(username: @user.username))
|
|
|
|
raw = <<~MD
|
|
#{raw}
|
|
|
|
#{instance_eval(&@next_instructions)}
|
|
MD
|
|
|
|
opts = {
|
|
title: I18n.t("#{I18N_KEY}.title"),
|
|
target_usernames: @user.username,
|
|
archetype: Archetype.private_message,
|
|
}
|
|
|
|
if @post && @post.topic.private_message? &&
|
|
@post.topic.topic_allowed_users.pluck(:user_id).include?(@user.id)
|
|
end
|
|
|
|
if @data[:topic_id]
|
|
opts = opts.merge(topic_id: @data[:topic_id]).except(:title, :target_usernames, :archetype)
|
|
end
|
|
post = reply_to(@post, raw, opts)
|
|
|
|
@data[:topic_id] = post.topic_id
|
|
@data[:track] = self.class.to_s
|
|
post
|
|
end
|
|
|
|
def reply_to_edit
|
|
return unless valid_topic?(@post.topic_id)
|
|
|
|
fake_delay
|
|
|
|
raw = <<~MD
|
|
#{I18n.t("#{I18N_KEY}.edit.reply", i18n_post_args)}
|
|
|
|
#{instance_eval(&@next_instructions)}
|
|
MD
|
|
|
|
reply_to(@post, raw)
|
|
end
|
|
|
|
def missing_edit
|
|
post_id = get_state_data(:post_id)
|
|
return unless valid_topic?(@post.topic_id) && post_id != @post.id
|
|
|
|
fake_delay
|
|
|
|
unless @data[:attempted]
|
|
reply_to(
|
|
@post,
|
|
I18n.t("#{I18N_KEY}.edit.not_found", i18n_post_args(url: Post.find_by(id: post_id).url)),
|
|
)
|
|
end
|
|
|
|
enqueue_timeout_job(@user)
|
|
false
|
|
end
|
|
|
|
def reply_to_delete
|
|
return unless valid_topic?(@topic_id)
|
|
|
|
fake_delay
|
|
|
|
raw = <<~MD
|
|
#{I18n.t("#{I18N_KEY}.delete.reply", i18n_post_args)}
|
|
|
|
#{instance_eval(&@next_instructions)}
|
|
MD
|
|
|
|
PostCreator.create!(self.discobot_user, raw: raw, topic_id: @topic_id)
|
|
end
|
|
|
|
def missing_delete
|
|
return unless valid_topic?(@post.topic_id)
|
|
fake_delay
|
|
unless @data[:attempted]
|
|
reply_to(@post, I18n.t("#{I18N_KEY}.delete.not_found", i18n_post_args))
|
|
end
|
|
enqueue_timeout_job(@user)
|
|
false
|
|
end
|
|
|
|
def reply_to_recover
|
|
return unless valid_topic?(@post.topic_id)
|
|
|
|
fake_delay
|
|
|
|
raw = <<~MD
|
|
#{I18n.t("#{I18N_KEY}.recover.reply", i18n_post_args(deletion_after: SiteSetting.delete_removed_posts_after))}
|
|
|
|
#{instance_eval(&@next_instructions)}
|
|
MD
|
|
|
|
PostCreator.create!(self.discobot_user, raw: raw, topic_id: @post.topic_id)
|
|
end
|
|
|
|
def missing_recover
|
|
unless valid_topic?(@post.topic_id) &&
|
|
post_id = get_state_data(:post_id) && @post.id != post_id
|
|
return
|
|
end
|
|
|
|
fake_delay
|
|
unless @data[:attempted]
|
|
reply_to(@post, I18n.t("#{I18N_KEY}.recover.not_found", i18n_post_args))
|
|
end
|
|
enqueue_timeout_job(@user)
|
|
false
|
|
end
|
|
|
|
def reply_to_category_hashtag
|
|
topic_id = @post.topic_id
|
|
return unless valid_topic?(topic_id)
|
|
|
|
hashtag_css_class =
|
|
SiteSetting.enable_experimental_hashtag_autocomplete ? ".hashtag-cooked" : ".hashtag"
|
|
if Nokogiri::HTML5.fragment(@post.cooked).css(hashtag_css_class).size > 0
|
|
raw = <<~MD
|
|
#{I18n.t("#{I18N_KEY}.category_hashtag.reply", i18n_post_args)}
|
|
|
|
#{instance_eval(&@next_instructions)}
|
|
MD
|
|
|
|
fake_delay
|
|
reply_to(@post, raw)
|
|
else
|
|
fake_delay
|
|
unless @data[:attempted]
|
|
if SiteSetting.enable_experimental_hashtag_autocomplete
|
|
reply_to(
|
|
@post,
|
|
I18n.t("#{I18N_KEY}.category_hashtag.not_found_experimental", i18n_post_args),
|
|
)
|
|
else
|
|
reply_to(@post, I18n.t("#{I18N_KEY}.category_hashtag.not_found", i18n_post_args))
|
|
end
|
|
end
|
|
enqueue_timeout_job(@user)
|
|
false
|
|
end
|
|
end
|
|
|
|
def missing_topic_notification_level_change
|
|
return unless valid_topic?(@post.topic_id)
|
|
|
|
fake_delay
|
|
unless @data[:attempted]
|
|
reply_to(
|
|
@post,
|
|
I18n.t("#{I18N_KEY}.change_topic_notification_level.not_found", i18n_post_args),
|
|
)
|
|
end
|
|
enqueue_timeout_job(@user)
|
|
false
|
|
end
|
|
|
|
def reply_to_topic_notification_level_changed
|
|
return unless valid_topic?(@topic_id)
|
|
|
|
fake_delay
|
|
raw = <<~MD
|
|
#{I18n.t("#{I18N_KEY}.change_topic_notification_level.reply", i18n_post_args)}
|
|
|
|
#{instance_eval(&@next_instructions)}
|
|
MD
|
|
|
|
fake_delay
|
|
|
|
post = PostCreator.create!(self.discobot_user, raw: raw, topic_id: @topic_id)
|
|
|
|
enqueue_timeout_job(@user)
|
|
post
|
|
end
|
|
|
|
def reply_to_poll
|
|
topic_id = @post.topic_id
|
|
return unless valid_topic?(topic_id)
|
|
|
|
if Nokogiri::HTML5.fragment(@post.cooked).css(".poll").size > 0
|
|
raw = <<~MD
|
|
#{I18n.t("#{I18N_KEY}.poll.reply", i18n_post_args)}
|
|
|
|
#{instance_eval(&@next_instructions)}
|
|
MD
|
|
|
|
fake_delay
|
|
reply_to(@post, raw)
|
|
else
|
|
fake_delay
|
|
unless @data[:attempted]
|
|
reply_to(@post, I18n.t("#{I18N_KEY}.poll.not_found", i18n_post_args))
|
|
end
|
|
enqueue_timeout_job(@user)
|
|
false
|
|
end
|
|
end
|
|
|
|
def reply_to_details
|
|
topic_id = @post.topic_id
|
|
return unless valid_topic?(topic_id)
|
|
|
|
fake_delay
|
|
|
|
if Nokogiri::HTML5.fragment(@post.cooked).css("details").size > 0
|
|
reply_to(@post, I18n.t("#{I18N_KEY}.details.reply", i18n_post_args))
|
|
else
|
|
unless @data[:attempted]
|
|
reply_to(@post, I18n.t("#{I18N_KEY}.details.not_found", i18n_post_args))
|
|
end
|
|
enqueue_timeout_job(@user)
|
|
false
|
|
end
|
|
end
|
|
|
|
def reply_to_wiki
|
|
topic_id = @post.topic_id
|
|
return unless valid_topic?(topic_id)
|
|
|
|
fake_delay
|
|
|
|
if @post.wiki
|
|
reply_to(@post, I18n.t("#{I18N_KEY}.wiki.reply", i18n_post_args))
|
|
else
|
|
unless @data[:attempted]
|
|
reply_to(@post, I18n.t("#{I18N_KEY}.wiki.not_found", i18n_post_args))
|
|
end
|
|
enqueue_timeout_job(@user)
|
|
false
|
|
end
|
|
end
|
|
|
|
def end_reply
|
|
fake_delay
|
|
|
|
reply_to(
|
|
@post,
|
|
I18n.t("#{I18N_KEY}.end.message", i18n_post_args(certificate: certificate("advanced"))),
|
|
)
|
|
end
|
|
|
|
def synchronize(user)
|
|
if Rails.env.test?
|
|
yield
|
|
else
|
|
DistributedMutex.synchronize("advanced_user_narrative_#{user.id}") { yield }
|
|
end
|
|
end
|
|
end
|
|
end
|