discourse/plugins/discourse-narrative-bot/lib/discourse_narrative_bot/base.rb
2023-01-07 11:11:37 +00:00

211 lines
5.3 KiB
Ruby

# frozen_string_literal: true
module DiscourseNarrativeBot
class Base
include Actions
class InvalidTransitionError < StandardError
end
def input(input, user, post: nil, topic_id: nil, skip: false)
new_post = nil
@post = post
@topic_id = topic_id
@skip = skip
synchronize(user) do
@user = user
@data = get_data(user) || {}
@state = (@data[:state] && @data[:state].to_sym) || :begin
@input = input
opts = {}
begin
opts = transition
loop do
next_state = opts[:next_state]
break if next_state == :end
next_opts = self.class::TRANSITION_TABLE.fetch(next_state)
prerequisite = next_opts[:prerequisite]
if (!prerequisite || instance_eval(&prerequisite)) &&
!(
SiteSetting.discourse_narrative_bot_skip_tutorials.present? &&
SiteSetting
.discourse_narrative_bot_skip_tutorials
.split("|")
.include?(next_state.to_s)
)
break
end
%i[next_state next_instructions].each { |key| opts[key] = next_opts[key] }
end
rescue InvalidTransitionError
# For given input, no transition for current state
return
end
next_state = opts[:next_state]
action = opts[:action]
if next_instructions = opts[:next_instructions]
@next_instructions = next_instructions
end
begin
old_data = @data.dup
new_post =
if (@skip && @state != :end)
skip_tutorial(next_state)
else
self.send(action)
end
if new_post
old_state = old_data[:state]
state_changed = (old_state.to_s != next_state.to_s)
clean_up_state(old_state) if state_changed
@state = @data[:state] = next_state
@data[:last_post_id] = new_post.id
set_data(@user, @data)
init_state(next_state) if state_changed
if next_state == :end
end_reply
cancel_timeout_job(user)
BadgeGranter.grant(Badge.find_by(name: self.class.badge_name), user)
set_data(@user, topic_id: new_post.topic_id, state: :end, track: self.class.to_s)
end
end
rescue => e
@data = old_data
set_data(@user, @data)
raise e
end
end
new_post
end
def reset_bot
not_implemented
end
def set_data(user, value)
DiscourseNarrativeBot::Store.set(user.id, value)
end
def get_data(user)
DiscourseNarrativeBot::Store.get(user.id)
end
def notify_timeout(user)
@data = get_data(user) || {}
if post = Post.find_by(id: @data[:last_post_id])
reply_to(
post,
I18n.t(
"discourse_narrative_bot.timeout.message",
i18n_post_args(
username: user.username,
skip_trigger: TrackSelector.skip_trigger,
reset_trigger: "#{TrackSelector.reset_trigger} #{self.class.reset_trigger}",
),
),
{},
skip_send_email: false,
)
end
end
def certificate(type = nil)
options = { user_id: @user.id, date: Time.zone.now.strftime("%b %d %Y"), format: :svg }
options.merge!(type: type) if type
src =
Discourse.base_url +
DiscourseNarrativeBot::Engine.routes.url_helpers.certificate_path(options)
alt = CGI.escapeHTML(I18n.t("#{self.class::I18N_KEY}.certificate.alt"))
"<iframe class='discobot-certificate' src='#{src}' width='650' height='464' alt='#{alt}'></iframe>"
end
protected
def set_state_data(key, value)
@data[@state] ||= {}
@data[@state][key] = value
set_data(@user, @data)
end
def get_state_data(key)
@data[@state] ||= {}
@data[@state][key]
end
def reset_data(user, additional_data = {})
old_data = get_data(user)
new_data = additional_data
set_data(user, new_data)
new_data
end
def transition
options = self.class::TRANSITION_TABLE.fetch(@state).dup
input_options = options.fetch(@input)
options.merge!(input_options) unless @skip
options
rescue KeyError
raise InvalidTransitionError.new
end
def skip_tutorial(next_state)
return unless valid_topic?(@post.topic_id)
fake_delay
if next_state != :end
reply = reply_to(@post, instance_eval(&@next_instructions))
enqueue_timeout_job(@user)
reply
else
@post
end
end
def i18n_post_args(extra = {})
{ base_uri: Discourse.base_path }.merge(extra)
end
def valid_topic?(topic_id)
topic_id == @data[:topic_id]
end
def not_implemented
raise "Not implemented."
end
private
def clean_up_state(state)
clean_up_method = "clean_up_#{state}"
self.send(clean_up_method) if self.class.private_method_defined?(clean_up_method)
end
def init_state(state)
init_method = "init_#{state}"
self.send(init_method) if self.class.private_method_defined?(init_method)
end
end
end