Sam Saffron 30990006a9 DEV: enable frozen string literal on all files
This reduces chances of errors where consumers of strings mutate inputs
and reduces memory usage of the app.

Test suite passes now, but there may be some stuff left, so we will run
a few sites on a branch prior to merging
2019-05-13 09:31:32 +08:00

394 lines
10 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.last
slug = category.slug
if parent_category = category.parent_category
slug = "#{parent_category.slug}#{CategoryHashtag::SEPARATOR}#{slug}"
end
I18n.t("#{I18N_KEY}.category_hashtag.instructions",
i18n_post_args(category: "##{slug}")
)
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 { I18n.t("#{I18N_KEY}.change_topic_notification_level.instructions", i18n_post_args) },
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 { SiteSetting.poll_enabled },
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.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_user.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_user.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)
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 = <<~RAW
#{raw}
#{instance_eval(&@next_instructions)}
RAW
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)
opts = opts.merge(topic_id: @post.topic_id)
end
if @data[:topic_id]
opts = opts.merge(topic_id: @data[:topic_id])
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 = <<~RAW
#{I18n.t("#{I18N_KEY}.edit.reply", i18n_post_args)}
#{instance_eval(&@next_instructions)}
RAW
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 = <<~RAW
#{I18n.t("#{I18N_KEY}.delete.reply", i18n_post_args)}
#{instance_eval(&@next_instructions)}
RAW
PostCreator.create!(self.discobot_user,
raw: raw,
topic_id: @topic_id
)
end
def missing_delete
return unless valid_topic?(@post.topic_id)
fake_delay
reply_to(@post, I18n.t("#{I18N_KEY}.delete.not_found", i18n_post_args)) unless @data[:attempted]
enqueue_timeout_job(@user)
false
end
def reply_to_recover
return unless valid_topic?(@post.topic_id)
fake_delay
raw = <<~RAW
#{I18n.t("#{I18N_KEY}.recover.reply", i18n_post_args(deletion_after: SiteSetting.delete_removed_posts_after))}
#{instance_eval(&@next_instructions)}
RAW
PostCreator.create!(self.discobot_user,
raw: raw,
topic_id: @post.topic_id
)
end
def missing_recover
return unless valid_topic?(@post.topic_id) &&
post_id = get_state_data(:post_id) && @post.id != post_id
fake_delay
reply_to(@post, I18n.t("#{I18N_KEY}.recover.not_found", i18n_post_args)) unless @data[:attempted]
enqueue_timeout_job(@user)
false
end
def reply_to_category_hashtag
topic_id = @post.topic_id
return unless valid_topic?(topic_id)
if Nokogiri::HTML.fragment(@post.cooked).css('.hashtag').size > 0
raw = <<~RAW
#{I18n.t("#{I18N_KEY}.category_hashtag.reply", i18n_post_args)}
#{instance_eval(&@next_instructions)}
RAW
fake_delay
reply_to(@post, raw)
else
fake_delay
reply_to(@post, I18n.t("#{I18N_KEY}.category_hashtag.not_found", i18n_post_args)) unless @data[:attempted]
enqueue_timeout_job(@user)
false
end
end
def missing_topic_notification_level_change
return unless valid_topic?(@post.topic_id)
fake_delay
reply_to(@post, I18n.t("#{I18N_KEY}.change_topic_notification_level.not_found", i18n_post_args)) unless @data[:attempted]
enqueue_timeout_job(@user)
false
end
def reply_to_topic_notification_level_changed
return unless valid_topic?(@topic_id)
fake_delay
raw = <<~RAW
#{I18n.t("#{I18N_KEY}.change_topic_notification_level.reply", i18n_post_args)}
#{instance_eval(&@next_instructions)}
RAW
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::HTML.fragment(@post.cooked).css(".poll").size > 0
raw = <<~RAW
#{I18n.t("#{I18N_KEY}.poll.reply", i18n_post_args)}
#{instance_eval(&@next_instructions)}
RAW
fake_delay
reply_to(@post, raw)
else
fake_delay
reply_to(@post, I18n.t("#{I18N_KEY}.poll.not_found", i18n_post_args)) unless @data[:attempted]
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::HTML.fragment(@post.cooked).css("details").size > 0
reply_to(@post, I18n.t("#{I18N_KEY}.details.reply", i18n_post_args))
else
reply_to(@post, I18n.t("#{I18N_KEY}.details.not_found", i18n_post_args)) unless @data[:attempted]
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
reply_to(@post, I18n.t("#{I18N_KEY}.wiki.not_found", i18n_post_args)) unless @data[:attempted]
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