# frozen_string_literal: true class CommandTag::Processor include Redisable include CommandTag::Commands STATEMENT_RE = /^\s*#!\s*([^\n]+ (?:start|begin|do)$.*)\n\s*#!\s*(?:end|stop|done)\s*$|^\s*#!\s*(.*?)\s*$/im.freeze STATEMENT_STRIP_RE = /^\s*#!\s*(?:[^\n]+ (?:start|begin|do)$.*)\n\s*#!\s*(?:end|stop|done)\s*$\n?|^\s*#!\s*(?:.*?)\s*$\n?/im.freeze def initialize(account, status) @account = account @status = status @parent = status.thread @conversation = status.conversation @run_once = Set[] @vars = {} @text = prepare_input(status.text) return unless @account.present? && @account.local? && @status.present? end def process! reset_status_caches @statements = parse_statements @text = @text.gsub(STATEMENT_STRIP_RE, '').split("\n") %w(at_start once_at_start).each { |suffix| execute_statements(suffix) } @text = @text.join("\n").rstrip %w(before_save once_before_save).each { |suffix| execute_statements(suffix) } if @text.blank? || @text.gsub(Account::MENTION_RE, '').strip.blank? %w(when_blank once_when_blank).each { |suffix| execute_statements(suffix) } unless (@status.published? && !@status.edited.zero?) || @text.present? %w(before_destroy once_before_destroy).each { |suffix| execute_statements(suffix) } @status.destroy %w(after_destroy once_after_destroy).each { |suffix| execute_statements(suffix) } end elsif @status.destroyed? %w(after_destroy once_after_destroy).each { |suffix| execute_statements(suffix) } elsif @status.update(text: @text) %w(after_save once_after_save).each { |suffix| execute_statements(suffix) } else %w(after_save_fail once_after_save_fail).each { |suffix| execute_statements(suffix) } end %w(at_end once_at_end).each { |suffix| execute_statements(suffix) } reset_status_caches end private def prepare_input(text) text.gsub(/\r\n|\n\r|\r/, "\n") .gsub(/^\s*((?:(?:#{Account::MENTION_RE}|#{Tag::HASHTAG_RE})\s*)+)#!/, "\\1\n#!") end def parse_statements @text.scan(STATEMENT_RE).flatten.map do |statement| next if statement.blank? || statement[0]&.strip.blank? statement = statement.scan(/^(.*) (?:start|begin|do)$(.*)|'([^']*)'|"([^"]*)"|(\S+)/im).flatten.compact statement[0] = statement[0].strip.tr(':.\- ', '_').gsub(/__+/, '_').downcase statement end.compact end def execute_statements(suffix) @statements.each do |statement| name = "handle_#{statement[0]}_#{suffix}" is_run_once = suffix.start_with?('once_') public_send(name, *statement[1..-1]) if respond_to?(name) && !(is_run_once && @run_once.include?(name)) @run_once << name if is_run_once end end def reset_status_caches [@status, @parent].each do |status| next unless @account.id == status&.account_id Rails.cache.delete_matched("statuses/#{status.id}-*") Rails.cache.delete("statuses/#{status.id}") Rails.cache.delete(status) redis.zremrangebyscore("spam_check:#{status.account.id}", status.id, status.id) end end def author_of_status? @account.id == @status.account_id end def author_of_parent? @account.id == @parent&.account_id end def destroy_status! @status.destroy unless @status.destroyed? end end