mirror of
https://github.com/discourse/discourse.git
synced 2024-12-02 03:43:40 +08:00
bf5611f7eb
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.
211 lines
5.2 KiB
Ruby
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
|