mirror of
https://github.com/discourse/discourse.git
synced 2024-12-04 19:33:41 +08:00
80a80ef2bd
This change refactors the code a bit so that a plugin could easily replace which badge is awarded when completing the discobot new user tutorial and advanced tutorial. By adding a static method and putting the BADGE_NAME constant inside of that method we can simply call that method now instead of the constant. A plugin could then `class_eval` that method and replace it with whatever badge name they choose. This is way cleaner than having the plugin change the frozen constant! eeek.
206 lines
5.0 KiB
Ruby
206 lines
5.0 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]
|
|
|
|
break if !prerequisite || instance_eval(&prerequisite)
|
|
|
|
[: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"))
|
|
|
|
"<img 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_uri }.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
|