mirror of
https://github.com/discourse/discourse.git
synced 2025-05-22 21:21:19 +08:00
FEATURE: Merge discourse-automation (#26432)
Automation (previously known as discourse-automation) is now a core plugin.
This commit is contained in:
303
plugins/automation/lib/discourse_automation/scriptable.rb
Normal file
303
plugins/automation/lib/discourse_automation/scriptable.rb
Normal file
@ -0,0 +1,303 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAutomation
|
||||
class Scriptable
|
||||
attr_reader :fields,
|
||||
:name,
|
||||
:not_found,
|
||||
:forced_triggerable,
|
||||
:background,
|
||||
:automation,
|
||||
:placeholders
|
||||
|
||||
@@plugin_triggerables ||= {}
|
||||
|
||||
class << self
|
||||
def add_plugin_triggerable(triggerable, scriptable)
|
||||
@@plugin_triggerables[scriptable.to_sym] ||= []
|
||||
@@plugin_triggerables[scriptable.to_sym] << triggerable.to_sym
|
||||
end
|
||||
|
||||
def plugin_triggerables
|
||||
@@plugin_triggerables
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(name, automation = nil)
|
||||
@name = name
|
||||
@version = 0
|
||||
@fields = []
|
||||
@placeholders = []
|
||||
@triggerables = (@@plugin_triggerables[name&.to_sym] || [])
|
||||
@script = proc {}
|
||||
@on_reset = proc {}
|
||||
@not_found = false
|
||||
@forced_triggerable = nil
|
||||
@background = false
|
||||
@automation = automation
|
||||
|
||||
eval! if @name
|
||||
end
|
||||
|
||||
def run_in_background
|
||||
@background = true
|
||||
end
|
||||
|
||||
def id
|
||||
"script"
|
||||
end
|
||||
|
||||
def scriptable?
|
||||
true
|
||||
end
|
||||
|
||||
def triggerable?
|
||||
false
|
||||
end
|
||||
|
||||
def eval!
|
||||
begin
|
||||
public_send("__scriptable_#{name.underscore}")
|
||||
rescue NoMethodError
|
||||
@not_found = true
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
def triggerable!(*args)
|
||||
if args.present?
|
||||
@forced_triggerable = { triggerable: args[0], state: args[1] }
|
||||
else
|
||||
@forced_triggerable
|
||||
end
|
||||
end
|
||||
|
||||
def placeholder(name = nil, triggerable: nil, &block)
|
||||
if block_given?
|
||||
result = yield(@automation.serialized_fields, @automation)
|
||||
Array(result).each do |name|
|
||||
@placeholders << { name: name.to_sym, triggerable: triggerable&.to_sym }
|
||||
end
|
||||
elsif name
|
||||
@placeholders << { name: name.to_sym, triggerable: triggerable&.to_sym }
|
||||
end
|
||||
end
|
||||
|
||||
def version(*args)
|
||||
if args.present?
|
||||
@version, = args
|
||||
else
|
||||
@version
|
||||
end
|
||||
end
|
||||
|
||||
def permits_trigger?(triggerable)
|
||||
Array(triggerables.map(&:to_s)).include?(triggerable.to_s)
|
||||
end
|
||||
|
||||
def triggerables(*args)
|
||||
if args.present?
|
||||
@triggerables.push(*args[0])
|
||||
else
|
||||
forced_triggerable ? [forced_triggerable[:triggerable]] : @triggerables
|
||||
end
|
||||
end
|
||||
|
||||
def script(&block)
|
||||
if block_given?
|
||||
@script = block
|
||||
else
|
||||
@script
|
||||
end
|
||||
end
|
||||
|
||||
def on_reset(&block)
|
||||
if block_given?
|
||||
@on_reset_block = block
|
||||
else
|
||||
@on_reset_block
|
||||
end
|
||||
end
|
||||
|
||||
def field(name, component:, **options)
|
||||
@fields << {
|
||||
name: name,
|
||||
component: component,
|
||||
extra: {
|
||||
},
|
||||
accepts_placeholders: false,
|
||||
accepted_contexts: [],
|
||||
triggerable: nil,
|
||||
required: false,
|
||||
}.merge(options || {})
|
||||
end
|
||||
|
||||
def components
|
||||
fields.map { |f| f[:component] }.uniq
|
||||
end
|
||||
|
||||
def utils
|
||||
Utils
|
||||
end
|
||||
|
||||
module Utils
|
||||
def self.fetch_report(name, args = {})
|
||||
report = Report.find(name, args)
|
||||
|
||||
return if !report
|
||||
|
||||
return if !report.modes.include?(:table)
|
||||
|
||||
ordered_columns = report.labels.map { |l| l[:property] }
|
||||
|
||||
table = +"\n"
|
||||
table << "|" + report.labels.map { |l| l[:title] }.join("|") + "|\n"
|
||||
table << "|" + report.labels.count.times.map { "-" }.join("|") + "|\n"
|
||||
if report.data.count > 0
|
||||
report.data.each do |data|
|
||||
table << "|#{ordered_columns.map { |col| data[col] }.join("|")}|\n"
|
||||
end
|
||||
else
|
||||
table << "|" + report.labels.count.times.map { " " }.join("|") + "|\n"
|
||||
end
|
||||
table
|
||||
end
|
||||
|
||||
def self.apply_placeholders(input, map = {})
|
||||
input = input.dup
|
||||
map[:site_title] = SiteSetting.title
|
||||
|
||||
input = apply_report_placeholder(input)
|
||||
|
||||
map.each { |key, value| input = input.gsub("%%#{key.upcase}%%", value.to_s) }
|
||||
|
||||
input = Mustache.render(input, map).to_s
|
||||
end
|
||||
|
||||
REPORT_REGEX = /%%REPORT=(.*?)%%/
|
||||
def self.apply_report_placeholder(input = "")
|
||||
input.gsub(REPORT_REGEX) do |pattern|
|
||||
match = pattern.match(REPORT_REGEX)
|
||||
if match
|
||||
params = match[1].match(/^(.*?)(?:\s(.*))?$/)
|
||||
|
||||
args = { filters: {} }
|
||||
if params[2]
|
||||
params[2]
|
||||
.split(" ")
|
||||
.each do |param|
|
||||
key, value = param.split("=")
|
||||
if %w[start_date end_date].include?(key)
|
||||
args[key.to_sym] = begin
|
||||
Date.parse(value)
|
||||
rescue StandardError
|
||||
nil
|
||||
end
|
||||
else
|
||||
args[:filters][key.to_sym] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
fetch_report(params[1].downcase, args) || ""
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.send_pm(
|
||||
pm,
|
||||
sender: Discourse.system_user.username,
|
||||
delay: nil,
|
||||
automation_id: nil,
|
||||
prefers_encrypt: true
|
||||
)
|
||||
pm = pm.symbolize_keys
|
||||
prefers_encrypt = prefers_encrypt && !!defined?(EncryptedPostCreator)
|
||||
|
||||
if delay && delay.to_i > 0 && automation_id
|
||||
pm[:execute_at] = delay.to_i.minutes.from_now
|
||||
pm[:sender] = sender
|
||||
pm[:automation_id] = automation_id
|
||||
pm[:prefers_encrypt] = prefers_encrypt
|
||||
DiscourseAutomation::PendingPm.create!(pm)
|
||||
else
|
||||
sender = User.find_by(username: sender)
|
||||
if !sender
|
||||
Rails.logger.warn "[discourse-automation] Did not send PM #{pm[:title]} - sender does not exist: `#{sender}`"
|
||||
return
|
||||
end
|
||||
|
||||
pm[:target_usernames] = Array.wrap(pm[:target_usernames])
|
||||
pm[:target_group_names] = Array.wrap(pm[:target_group_names])
|
||||
pm[:target_emails] = Array.wrap(pm[:target_emails])
|
||||
|
||||
if pm[:target_usernames].empty? && pm[:target_group_names].empty? &&
|
||||
pm[:target_emails].empty?
|
||||
Rails.logger.warn "[discourse-automation] Did not send PM - no target usernames, groups or emails"
|
||||
return
|
||||
end
|
||||
|
||||
non_existing_targets = []
|
||||
|
||||
if pm[:target_usernames].present?
|
||||
existing_target_usernames = User.where(username: pm[:target_usernames]).pluck(:username)
|
||||
if existing_target_usernames.length != pm[:target_usernames].length
|
||||
non_existing_targets += pm[:target_usernames] - existing_target_usernames
|
||||
pm[:target_usernames] = existing_target_usernames
|
||||
end
|
||||
end
|
||||
|
||||
if pm[:target_group_names].present?
|
||||
existing_target_groups = Group.where(name: pm[:target_group_names]).pluck(:name)
|
||||
if existing_target_groups.length != pm[:target_group_names].length
|
||||
non_existing_targets += pm[:target_group_names] - existing_target_groups
|
||||
pm[:target_group_names] = existing_target_groups
|
||||
end
|
||||
end
|
||||
|
||||
if pm[:target_emails].present?
|
||||
valid_emails = pm[:target_emails].select { |email| Email.is_valid?(email) }
|
||||
if valid_emails.length != pm[:target_emails].length
|
||||
non_existing_targets += pm[:target_emails] - valid_emails
|
||||
pm[:target_emails] = valid_emails
|
||||
end
|
||||
end
|
||||
|
||||
post_created = false
|
||||
pm = pm.merge(archetype: Archetype.private_message)
|
||||
pm[:target_usernames] = pm[:target_usernames].join(",")
|
||||
pm[:target_group_names] = pm[:target_group_names].join(",")
|
||||
pm[:target_emails] = pm[:target_emails].join(",")
|
||||
|
||||
if pm[:target_usernames].blank? && pm[:target_group_names].blank? &&
|
||||
pm[:target_emails].blank?
|
||||
Rails.logger.warn "[discourse-automation] Did not send PM #{pm[:title]} - no valid targets exist"
|
||||
return
|
||||
elsif non_existing_targets.any?
|
||||
Rails.logger.warn "[discourse-automation] Did not send PM #{pm[:title]} to all users - some do not exist: `#{non_existing_targets.join(",")}`"
|
||||
end
|
||||
|
||||
post_created = EncryptedPostCreator.new(sender, pm).create if prefers_encrypt
|
||||
|
||||
PostCreator.new(sender, pm).create! if !post_created
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.add(identifier, &block)
|
||||
@all_scriptables = nil
|
||||
define_method("__scriptable_#{identifier}", &block)
|
||||
end
|
||||
|
||||
def self.remove(identifier)
|
||||
@all_scriptables = nil
|
||||
undef_method("__scriptable_#{identifier}")
|
||||
end
|
||||
|
||||
def self.all
|
||||
@all_scriptables ||=
|
||||
DiscourseAutomation::Scriptable.instance_methods(false).grep(/^__scriptable_/)
|
||||
end
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user