discourse/plugins/discourse-narrative-bot/lib/discourse_narrative_bot/base.rb
Rafael dos Santos Silva bf5611f7eb
FIX: Make discobot certificate faster/non blocking (#11344)
This moves the way we add the user avatar and site logo
to the discobot certificates from embeded base64 png to
just using the files urls in the href to the image tag.

This will make generation faster and the certificate
smaller overall, but it can't be used in a  `img` tag
anymore, since SVGs in `img` tags don't load the external images

In order to work around that we will move the certificate
in posts to an iframe, which works fine without any user
visible changes. For this to be possible the plugin automatically
adds the site current domain to the list of allowed iframe origins.
2021-02-01 20:49:32 -03:00

211 lines
5.2 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
[:next_state, :next_instructions].each do |key|
opts[key] = next_opts[key]
end
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}'>"
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