mirror of
https://github.com/discourse/discourse.git
synced 2024-11-23 09:26:54 -06:00
87b5d16c10
Discobot will reply to users they need to enable onboarding tips to interact with them when issued a command.
267 lines
8.0 KiB
Ruby
267 lines
8.0 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module DiscourseNarrativeBot
|
|
class TrackSelector
|
|
include Actions
|
|
|
|
GENERIC_REPLIES_COUNT_PREFIX = 'discourse-narrative-bot:track-selector-count:'.freeze
|
|
PUBLIC_DISPLAY_BOT_HELP_KEY = 'discourse-narrative-bot:track-selector:display-bot-help'.freeze
|
|
|
|
TRACKS = [
|
|
AdvancedUserNarrative,
|
|
NewUserNarrative
|
|
]
|
|
|
|
TOPIC_ACTIONS = [
|
|
:delete,
|
|
:topic_notification_level_changed
|
|
].each(&:freeze)
|
|
|
|
RESET_TRIGGER_EXACT_MATCH_LENGTH = 200
|
|
|
|
def initialize(input, user, post_id:, topic_id: nil)
|
|
@input = input
|
|
@user = user
|
|
@post_id = post_id
|
|
@topic_id = topic_id
|
|
@post = Post.find_by(id: post_id)
|
|
end
|
|
|
|
def select
|
|
data = Store.get(@user.id)
|
|
|
|
if @post && @post.post_type == Post.types[:regular] && !is_topic_action?
|
|
is_reply = @input == :reply
|
|
@is_pm_to_bot = pm_to_bot?(@post)
|
|
|
|
return if is_reply && reset_track
|
|
|
|
if data && (data[:topic_id] == @post.topic_id) && @is_pm_to_bot
|
|
state = data[:state]
|
|
klass = (data[:track] || NewUserNarrative.to_s).constantize
|
|
|
|
if is_reply && like_user_post
|
|
terminate_track(data)
|
|
elsif state&.to_sym == :end && is_reply
|
|
bot_commands(bot_mentioned?) || generic_replies(klass.reset_trigger)
|
|
elsif is_reply
|
|
previous_status = data[:attempted]
|
|
current_status = klass.new.input(@input, @user, post: @post, skip: skip_track?)
|
|
data = Store.get(@user.id)
|
|
data[:attempted] = !current_status
|
|
|
|
if previous_status && data[:attempted] == previous_status && !data[:skip_attempted]
|
|
generic_replies(klass.reset_trigger, state)
|
|
else
|
|
Discourse.redis.del(generic_replies_key(@user))
|
|
end
|
|
|
|
Store.set(@user.id, data)
|
|
else
|
|
klass.new.input(@input, @user, post: @post, skip: skip_track?)
|
|
end
|
|
elsif is_reply && (@is_pm_to_bot || public_reply?)
|
|
like_user_post if @is_pm_to_bot
|
|
bot_commands
|
|
end
|
|
elsif data && data.dig(:state)&.to_sym != :end && is_topic_action?
|
|
klass = (data[:track] || NewUserNarrative.to_s).constantize
|
|
klass.new.input(@input, @user, post: @post, topic_id: @topic_id)
|
|
end
|
|
end
|
|
|
|
def self.reset_trigger
|
|
I18n.t(i18n_key("reset_trigger"))
|
|
end
|
|
|
|
def self.skip_trigger
|
|
I18n.t(i18n_key("skip_trigger"))
|
|
end
|
|
|
|
def self.help_trigger
|
|
I18n.t(i18n_key("help_trigger"))
|
|
end
|
|
|
|
def self.quote_trigger
|
|
I18n.t("discourse_narrative_bot.quote.trigger")
|
|
end
|
|
|
|
def self.dice_trigger
|
|
I18n.t("discourse_narrative_bot.dice.trigger")
|
|
end
|
|
|
|
def self.magic_8_ball_trigger
|
|
I18n.t("discourse_narrative_bot.magic_8_ball.trigger")
|
|
end
|
|
|
|
private
|
|
|
|
def is_topic_action?
|
|
@is_topic_action ||= TOPIC_ACTIONS.include?(@input)
|
|
end
|
|
|
|
def reset_track
|
|
reset = false
|
|
|
|
TRACKS.each do |klass|
|
|
if selected_track(klass)
|
|
klass.new.reset_bot(@user, @post)
|
|
reset = true
|
|
break
|
|
end
|
|
end
|
|
|
|
reset
|
|
end
|
|
|
|
def selected_track(klass)
|
|
trigger = "#{self.class.reset_trigger} #{klass.reset_trigger}"
|
|
|
|
if @post.raw.length < RESET_TRIGGER_EXACT_MATCH_LENGTH && @is_pm_to_bot
|
|
@post.raw.match(Regexp.new("\\b\\W\?#{trigger}\\W\?\\b", 'i'))
|
|
else
|
|
match_trigger?(trigger)
|
|
end
|
|
end
|
|
|
|
def bot_commands(hint = true)
|
|
raw =
|
|
if @user.manually_disabled_discobot?
|
|
I18n.t(self.class.i18n_key('random_mention.discobot_disabled'))
|
|
elsif match_data = match_trigger?("#{self.class.dice_trigger} (\\d+)d(\\d+)")
|
|
DiscourseNarrativeBot::Dice.roll(match_data[1].to_i, match_data[2].to_i)
|
|
elsif match_trigger?(self.class.quote_trigger)
|
|
DiscourseNarrativeBot::QuoteGenerator.generate(@user)
|
|
elsif match_trigger?(self.class.magic_8_ball_trigger)
|
|
DiscourseNarrativeBot::Magic8Ball.generate_answer
|
|
elsif match_trigger?(self.class.help_trigger)
|
|
help_message
|
|
elsif hint
|
|
message = I18n.t(self.class.i18n_key('random_mention.reply'),
|
|
discobot_username: self.discobot_username,
|
|
help_trigger: self.class.help_trigger
|
|
)
|
|
|
|
if public_reply?
|
|
key = "#{PUBLIC_DISPLAY_BOT_HELP_KEY}:#{@post.topic_id}"
|
|
last_bot_help_post_number = Discourse.redis.get(key)
|
|
|
|
if !last_bot_help_post_number ||
|
|
(last_bot_help_post_number &&
|
|
@post.post_number - 10 > last_bot_help_post_number.to_i &&
|
|
(1.day.to_i - Discourse.redis.ttl(key)) > 6.hours.to_i)
|
|
|
|
Discourse.redis.setex(key, 1.day.to_i, @post.post_number)
|
|
message
|
|
end
|
|
else
|
|
message
|
|
end
|
|
end
|
|
|
|
if raw
|
|
fake_delay
|
|
reply_to(@post, raw, skip_validations: true)
|
|
end
|
|
end
|
|
|
|
def help_message
|
|
message = I18n.t(
|
|
self.class.i18n_key('random_mention.tracks'),
|
|
discobot_username: self.discobot_username,
|
|
reset_trigger: self.class.reset_trigger,
|
|
tracks: [NewUserNarrative.reset_trigger, AdvancedUserNarrative.reset_trigger].join(', ')
|
|
)
|
|
|
|
message << "\n\n#{I18n.t(self.class.i18n_key('random_mention.bot_actions'),
|
|
discobot_username: self.discobot_username,
|
|
dice_trigger: self.class.dice_trigger,
|
|
quote_trigger: self.class.quote_trigger,
|
|
quote_sample: DiscourseNarrativeBot::QuoteGenerator.generate(@user),
|
|
magic_8_ball_trigger: self.class.magic_8_ball_trigger
|
|
)}"
|
|
end
|
|
|
|
def generic_replies_key(user)
|
|
"#{GENERIC_REPLIES_COUNT_PREFIX}#{user.id}"
|
|
end
|
|
|
|
def generic_replies(track_reset_trigger, state = nil)
|
|
reset_trigger = "#{self.class.reset_trigger} #{track_reset_trigger}"
|
|
key = generic_replies_key(@user)
|
|
count = (Discourse.redis.get(key) || Discourse.redis.setex(key, 900, 0)).to_i
|
|
|
|
case count
|
|
when 0
|
|
raw = I18n.t(self.class.i18n_key('do_not_understand.first_response'))
|
|
|
|
if state && state.to_sym != :end
|
|
raw = "#{raw}\n\n#{I18n.t(self.class.i18n_key('do_not_understand.track_response'), reset_trigger: reset_trigger, skip_trigger: self.class.skip_trigger)}"
|
|
end
|
|
|
|
reply_to(@post, raw)
|
|
when 1
|
|
reply_to(@post, I18n.t(self.class.i18n_key('do_not_understand.second_response'),
|
|
base_path: Discourse.base_path,
|
|
reset_trigger: self.class.reset_trigger
|
|
))
|
|
else
|
|
# Stay out of the user's way
|
|
end
|
|
|
|
Discourse.redis.incr(key)
|
|
end
|
|
|
|
def self.i18n_key(key)
|
|
"discourse_narrative_bot.track_selector.#{key}"
|
|
end
|
|
|
|
def skip_track?
|
|
if @is_pm_to_bot
|
|
@post.raw.match(/((^@#{self.discobot_username} #{self.class.skip_trigger})|(^#{self.class.skip_trigger}$))/i)
|
|
else
|
|
false
|
|
end
|
|
end
|
|
|
|
@@cooked_triggers = {}
|
|
|
|
def cook(trigger)
|
|
@@cooked_triggers[trigger] ||= PrettyText.cook("@#{self.discobot_username} #{trigger}")
|
|
end
|
|
|
|
def match_trigger?(trigger)
|
|
# we remove the leading <p> to allow for trigger to be at the end of a paragraph
|
|
cooked_trigger = cook(trigger)[3..-1]
|
|
regexp = Regexp.new(cooked_trigger, 'i')
|
|
match = @post.cooked.match(regexp)
|
|
|
|
if @is_pm_to_bot
|
|
match || @post.raw.strip.match(Regexp.new("^#{trigger}$", 'i'))
|
|
else
|
|
match
|
|
end
|
|
end
|
|
|
|
def like_user_post
|
|
if @post.raw.match(/thank/i)
|
|
PostActionCreator.like(self.discobot_user, @post)
|
|
end
|
|
end
|
|
|
|
def bot_mentioned?
|
|
@bot_mentioned ||= PostAnalyzer.new(@post.raw, @post.topic_id).raw_mentions.include?(self.discobot_username)
|
|
end
|
|
|
|
def public_reply?
|
|
!SiteSetting.discourse_narrative_bot_disable_public_replies &&
|
|
(reply_to_bot_post?(@post) || bot_mentioned?)
|
|
end
|
|
|
|
def terminate_track(data)
|
|
Store.set(@user.id, data.merge!(track: nil, state: nil, topic_id: nil))
|
|
cancel_timeout_job(@user)
|
|
end
|
|
end
|
|
end
|