about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
authormultiple creatures <dev@multiple-creature.party>2019-04-17 13:43:29 -0500
committermultiple creatures <dev@multiple-creature.party>2019-05-21 03:16:22 -0500
commit036f422877693ae30079c322bbd3ebf9155a8409 (patch)
treea632a55f6bd7c9bb2b196116efb86c3a42d84f44 /app
parent500b485b771a789e3e25f58cf33728bff3881f7a (diff)
Make sure only distributable statuses are marked curated; move bangtags processing into own helper lib.
Diffstat (limited to 'app')
-rw-r--r--app/controllers/api/v1/statuses/bookmarks_controller.rb2
-rw-r--r--app/lib/bangtags.rb360
-rw-r--r--app/models/status.rb361
-rw-r--r--app/services/favourite_service.rb2
-rw-r--r--app/services/reblog_service.rb2
5 files changed, 367 insertions, 360 deletions
diff --git a/app/controllers/api/v1/statuses/bookmarks_controller.rb b/app/controllers/api/v1/statuses/bookmarks_controller.rb
index 61afd7085..5759ea1f9 100644
--- a/app/controllers/api/v1/statuses/bookmarks_controller.rb
+++ b/app/controllers/api/v1/statuses/bookmarks_controller.rb
@@ -40,7 +40,7 @@ class Api::V1::Statuses::BookmarksController < Api::BaseController
   end
 
   def curate_status(status)
-    return if status.curated || status.direct_visibility? || (status.reply? && status.in_reply_to_account_id != status.account_id)
+    return if status.curated || !status.distributable? || (status.reply? && status.in_reply_to_account_id != status.account_id)
     status.curated = true
     status.save
     FanOutOnWriteService.new.call(status)
diff --git a/app/lib/bangtags.rb b/app/lib/bangtags.rb
new file mode 100644
index 000000000..e8681073d
--- /dev/null
+++ b/app/lib/bangtags.rb
@@ -0,0 +1,360 @@
+# frozen_string_literal: true
+
+class Bangtags
+  attr_reader :status, :account
+
+  def initialize(status)
+    @status        = status
+    @account       = status.account
+    @parent_status = Status.find(status.in_reply_to_id) if status.reply?
+
+    @prefix_ns = {
+      'permalink' => ['link'],
+      'cloudroot' => ['link'],
+      'blogroot' => ['link'],
+    }
+
+    @aliases = {
+      ['media', 'end'] => ['var', 'end'],
+      ['media', 'stop'] => ['var', 'end'],
+      ['media', 'endall'] => ['var', 'endall'],
+      ['media', 'stopall'] => ['var', 'endall'],
+    }
+
+    # sections of the final status text
+    @chunks = []
+    # list of transformation commands
+    @tf_cmds = []
+    # list of post-processing commands
+    @post_cmds = []
+    # hash of bangtag variables
+    @vars = {}
+    # keep track of what variables we're appending the value of between chunks
+    @vore_stack = []
+    # keep track of what type of nested components are active so we can !end them in order
+    @component_stack = []
+  end
+
+  def process
+    status.text.gsub!('#!!', "#\u200c!")
+
+    status.text.split(/(#!(?:.*:!#|{.*?}|[^\s#]+))/).each do |chunk|
+      if chunk.starts_with?("#!")
+        chunk.sub!(/(\\:)?+:+?!#\Z/, '\1')
+        chunk.sub!(/{(.*)}\Z/, '\1')
+
+        if @vore_stack.last != '_comment'
+          cmd = chunk[2..-1].strip
+          next if cmd.blank?
+          cmd = cmd.split(':::')
+          cmd = cmd[0].split('::') + cmd[1..-1]
+          cmd = cmd[0].split(':') + cmd[1..-1]
+
+          cmd.map! {|c| c.gsub(/\\:/, ':').gsub(/\\\\:/, '\:')}
+
+          prefix = @prefix_ns[cmd[0]]
+          cmd = prefix + cmd unless prefix.nil?
+
+          @aliases.each_key do |old_cmd|
+            cmd = aliases[old_cmd] + cmd.drop(old_cmd.length) if cmd.take(old_cmd.length) == old_cmd
+          end
+        elsif chunk.in?(['#!comment:end', '#!comment:stop', '#!comment:endall', '#!comment:stopall'])
+          @vore_stack.pop
+          @component_stack.pop
+          next
+        else
+          next
+        end
+
+        case cmd[0]
+        when 'var'
+          chunk = nil
+          case cmd[1]
+          when 'end', 'stop'
+            @vore_stack.pop
+            @component_stack.pop
+          when 'endall', 'stopall'
+            @vore_stack = []
+            @component_stack.reject! {|c| c == :var}
+          else
+            var = cmd[1]
+            next if var.nil? || var.starts_with?('_')
+            new_value = cmd[2..-1]
+            if new_value.blank?
+              chunk = @vars[var]
+            elsif new_value.length == 1 && new_value[0] == '-'
+              @vore_stack.push(var)
+              @component_stack.push(:var)
+            else
+              @vars[var] = new_value.join(':')
+            end
+          end
+        when 'tf'
+          chunk = nil
+          case cmd[1]
+          when 'end', 'stop'
+            @tf_cmds.pop
+            @component_stack.pop
+          when 'endall', 'stopall'
+            @tf_cmds = []
+            @component_stack.reject! {|c| c == :tf}
+          else
+            @tf_cmds.push(cmd[1..-1])
+            @component_stack.push(:tf)
+          end
+        when 'end', 'stop'
+          chunk = nil
+          case @component_stack.pop
+          when :tf
+            @tf_cmds.pop
+          when :var, :hide
+            @vore_stack.pop
+          end
+        when 'endall', 'stopall'
+          chunk = nil
+          @tf_cmds = []
+          @vore_stack = []
+          @component_stack = []
+        when 'emojify'
+          chunk = nil
+          next if cmd[1].nil?
+          src_img = nil
+          shortcode = cmd[2]
+          case cmd[1]
+          when 'avatar'
+            src_img = status.account.avatar
+          when 'parent'
+            next unless cmd[3].present? && reply?
+            shortcode = cmd[3]
+            next if @parent_status.nil?
+            case cmd[2]
+            when 'avatar'
+              src_img = @parent_status.account.avatar
+            end
+          end
+
+          next if src_img.nil? || shortcode.nil? || !shortcode.match?(/\A\w+\Z/)
+
+          chunk = ":#{shortcode}:"
+          emoji = CustomEmoji.find_or_initialize_by(shortcode: shortcode, domain: nil)
+          if emoji.id.nil?
+            emoji.image = src_img
+            emoji.save
+          end
+        when 'emoji'
+          next if cmd[1].nil?
+          shortcode = cmd[1]
+          domain = (cmd[2].blank? ? nil : cmd[2].downcase)
+          chunk = ":#{shortcode}:"
+          ours = CustomEmoji.find_or_initialize_by(shortcode: shortcode, domain: nil)
+          if ours.id.nil?
+            if domain.nil?
+              theirs = CustomEmoji.find_by(shortcode: shortcode)
+            else
+              theirs = CustomEmoji.find_by(shortcode: shortcode, domain: domain)
+            end
+            unless theirs.nil?
+              ours.image = theirs.image
+              ours.save
+            end
+          end
+        when 'char'
+          chunk = nil
+          charmap = {
+            'zws' => "\u200b",
+            'zwnj' => "\u200c",
+            'zwj' => "\u200d",
+            '\n' => "\n",
+            '\r' => "\r",
+            '\t' => "\t",
+            '\T' => '    '
+          }
+          cmd[1..-1].each do |c|
+            next if c.nil?
+            if c.in?(charmap)
+              @chunks << charmap[cmd[1]]
+            elsif (/^\h{1,5}$/ =~ c) && c.to_i(16) > 0
+              begin
+                @chunks << [c.to_i(16)].pack('U*')
+              rescue
+                @chunks << '?'
+              end
+            end
+          end
+        when 'link'
+          chunk = nil
+          case cmd[1]
+          when 'permalink', 'self'
+            chunk = TagManager.instance.url_for(status)
+          when 'cloudroot'
+            chunk = "https://monsterpit.cloud/~/#{account.username}"
+          when 'blogroot'
+            chunk = "https://monsterpit.blog/~/#{account.username}"
+          end
+        when 'ping'
+          mentions = []
+          case cmd[1]
+          when 'admins'
+            mentions = User.admins.map { |u| "@#{u.account.username}" }
+            mentions.sort!
+          when 'mods'
+            mentions = User.moderators.map { |u| "@#{u.account.username}" }
+            mentions.sort!
+          when 'staff'
+            mentions = User.admins.map { |u| "@#{u.account.username}" }
+            mentions += User.moderators.map { |u| "@#{u.account.username}" }
+            mentions.uniq!
+            mentions.sort!
+          end
+          chunk = mentions.join(' ')
+        when 'tag'
+          chunk = nil
+          records = []
+          valid_name = /^[[:word:]_\-]*[[:alpha:]_·\-][[:word:]_\-]*$/
+          cmd[1..-1].select {|t| t.present? && valid_name.match?(t)}.uniq.each do |name|
+            next if status.tags.where(name: name).exists?
+            tag = Tag.where(name: name).first_or_create(name: name)
+            status.tags << tag
+            records << tag
+            TrendingTags.record_use!(tag, account, status.created_at) if status.distributable?
+          end
+          if status.distributable?
+            account.featured_tags.where(tag_id: records.map(&:id)).each do |featured_tag|
+              featured_tag.increment(status.created_at)
+            end
+          end
+        when 'thread'
+          chunk = nil
+          case cmd[1]
+          when 'reall'
+            if status.conversation_id.present?
+              mention_ids = Status.where(conversation_id: status.conversation_id).flat_map { |s| s.mentions.pluck(:account_id) }
+              mention_ids.uniq!
+              mentions = Account.where(id: mention_ids).map { |a| "@#{a.username}" }
+              chunk = mentions.join(' ')
+            end
+          end
+        when 'parent'
+          chunk = nil
+          next if @parent_status.nil?
+          case cmd[1]
+          when 'permalink'
+            chunk = TagManager.instance.url_for(@parent_status)
+          end
+        when 'media'
+          chunk = nil
+
+          media_idx = cmd[1]
+          media_cmd = cmd[2]
+          media_args = cmd[3..-1]
+
+          next unless media_cmd.present? && media_idx.present? && media_idx.scan(/\D/).empty?
+          media_idx = media_idx.to_i
+          next if status.media_attachments[media_idx-1].nil?
+
+          case media_cmd
+          when 'desc'
+            if media_args.present?
+              @vars["media_#{media_idx}_desc"] = media_args.join(':')
+            else
+              @vore_stack.push("media_#{media_idx}_desc")
+              @component_stack.push(:var)
+            end
+          end
+
+          @post_cmds.push(['media', media_idx, media_cmd])
+        when 'bangtag'
+          chunk = chunk.sub('bangtag:', '').gsub(':', ":\u200c")
+        when 'join'
+          chunk = nil
+          next if cmd[1].nil?
+          charmap = {
+            'zws' => "\u200b",
+            'zwnj' => "\u200c",
+            'zwj' => "\u200d",
+            '\n' => "\n",
+            '\r' => "\r",
+            '\t' => "\t",
+            '\T' => '    '
+          }
+          sep = charmap[cmd[1]]
+          chunk = cmd[2..-1].join(sep.nil? ? cmd[1] : sep)
+        when 'hide'
+          chunk = nil
+          case cmd[1]
+          when 'end', 'stop', 'endall', 'stopall'
+            @vore_stack.reject! {|v| v == '_'}
+            @compontent_stack.reject! {|c| c == :hide}
+          else
+            if cmd[1].nil? && !'_'.in?(@vore_stack)
+              @vore_stack.push('_')
+              @component_stack.push(:hide)
+            end
+          end
+        when 'comment'
+          chunk = nil
+          if cmd[1].nil?
+            @vore_stack.push('_comment')
+            @component_stack.push(:var)
+          end
+        end
+      end
+
+      if chunk.present? && @tf_cmds.present?
+        @tf_cmds.each do |tf_cmd|
+          next if chunk.nil?
+          case tf_cmd[0]
+          when 'replace', 'sub', 's'
+            tf_cmd[1..-1].in_groups_of(2) do |args|
+              chunk.sub!(*args) if args.all?
+            end
+          when 'replaceall', 'gsub', 'gs'
+            tf_cmd[1..-1].in_groups_of(2) do |args|
+              chunk.gsub!(*args) if args.all?
+            end
+          end
+        end
+      end
+
+      unless chunk.blank? || @vore_stack.empty?
+        var = @vore_stack.last
+        next if var == '_'
+        if @vars[var].nil?
+          @vars[var] = chunk.lstrip
+        else
+          @vars[var] += chunk.rstrip
+        end
+        chunk = nil
+      end
+
+      @chunks << chunk unless chunk.nil?
+    end
+
+    @vars.transform_values! {|v| v.rstrip}
+
+    postprocess
+
+    status.text = @chunks.join('')
+    status.save
+  end
+
+  private
+
+  def postprocess
+    @post_cmds.each do |post_cmd|
+      case post_cmd[0]
+      when 'media'
+        media_idx = post_cmd[1]
+        media_cmd = post_cmd[2]
+        media_args = post_cmd[3..-1]
+
+        case media_cmd
+        when 'desc'
+          status.media_attachments[media_idx-1].description = @vars["media_#{media_idx}_desc"]
+          status.media_attachments[media_idx-1].save
+        end
+      end
+    end
+  end
+
+end
diff --git a/app/models/status.rb b/app/models/status.rb
index cd35ca017..022296145 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -368,18 +368,12 @@ class Status < ApplicationRecord
     end
 
     def as_public_timeline(account = nil, local_only = false)
-      if local_only
-        query = Status.network
-          .with_public_visibility
-          .without_replies
-          .without_reblogs
-      elsif account.nil? || account&.user&.setting_rawr_federated
+      if local_only || account.nil? || account&.user&.setting_rawr_federated
         query = timeline_scope(local_only)
-        query = query.without_replies unless Setting.show_replies_in_public_timelines
       else
-        query = Status.curated.public_browsable
-        query = query.without_replies unless Setting.show_replies_in_public_timelines
+        query = Status.curated
       end
+      query = query.without_replies unless Setting.show_replies_in_public_timelines
 
       apply_timeline_filters(query, account, local_only)
     end
@@ -557,354 +551,7 @@ class Status < ApplicationRecord
   end
 
   def process_bangtags
-    return if text&.nil?
-    return unless '#!'.in?(text)
-    text.gsub!('#!!', "#\u200c!")
-
-    prefix_ns = {
-      'permalink' => ['link'],
-      'cloudroot' => ['link'],
-      'blogroot' => ['link'],
-    }
-
-    aliases = {
-      ['media', 'end'] => ['var', 'end'],
-      ['media', 'stop'] => ['var', 'end'],
-      ['media', 'endall'] => ['var', 'endall'],
-      ['media', 'stopall'] => ['var', 'endall'],
-    }
-
-    # sections of the final status text
-    chunks = []
-    # list of transformation commands
-    tf_cmds = []
-    # list of post-processing commands
-    post_cmds = []
-    # hash of bangtag variables
-    vars = {}
-    # keep track of what variables we're appending the value of between chunks
-    vore_stack = []
-    # keep track of what type of nested components are active so we can !end them in order
-    component_stack = []
-
-    text.split(/(#!(?:.*:!#|{.*?}|[^\s#]+))/).each do |chunk|
-      if chunk.starts_with?("#!")
-        chunk.sub!(/(\\:)?+:+?!#\Z/, '\1')
-        chunk.sub!(/{(.*)}\Z/, '\1')
-
-        if vore_stack.last != '_comment'
-          cmd = chunk[2..-1].strip
-          next if cmd.blank?
-          cmd = cmd.split(':::')
-          cmd = cmd[0].split('::') + cmd[1..-1]
-          cmd = cmd[0].split(':') + cmd[1..-1]
-
-          cmd.map! {|c| c.gsub(/\\:/, ':').gsub(/\\\\:/, '\:')}
-
-          prefix = prefix_ns[cmd[0]]
-          cmd = prefix + cmd unless prefix.nil?
-
-          aliases.each_key do |old_cmd|
-            cmd = aliases[old_cmd] + cmd.drop(old_cmd.length) if cmd.take(old_cmd.length) == old_cmd
-          end
-        elsif chunk.in?(['#!comment:end', '#!comment:stop', '#!comment:endall', '#!comment:stopall'])
-          vore_stack.pop
-          component_stack.pop
-          next
-        else
-          next
-        end
-
-        case cmd[0]
-        when 'var'
-          chunk = nil
-          case cmd[1]
-          when 'end', 'stop'
-            vore_stack.pop
-            component_stack.pop
-          when 'endall', 'stopall'
-            vore_stack = []
-            component_stack.reject! {|c| c == :var}
-          else
-            var = cmd[1]
-            next if var.nil? || var.starts_with?('_')
-            new_value = cmd[2..-1]
-            if new_value.blank?
-              chunk = vars[var]
-            elsif new_value.length == 1 && new_value[0] == '-'
-              vore_stack.push(var)
-              component_stack.push(:var)
-            else
-              vars[var] = new_value.join(':')
-            end
-          end
-        when 'tf'
-          chunk = nil
-          case cmd[1]
-          when 'end', 'stop'
-            tf_cmds.pop
-            component_stack.pop
-          when 'endall', 'stopall'
-            tf_cmds = []
-            component_stack.reject! {|c| c == :tf}
-          else
-            tf_cmds.push(cmd[1..-1])
-            component_stack.push(:tf)
-          end
-        when 'end', 'stop'
-          chunk = nil
-          case component_stack.pop
-          when :tf
-            tf_cmds.pop
-          when :var, :hide
-            vore_stack.pop
-          end
-        when 'endall', 'stopall'
-          chunk = nil
-          tf_cmds = []
-          vore_stack = []
-          component_stack = []
-        when 'emojify'
-          chunk = nil
-          next if cmd[1].nil?
-          src_img = nil
-          shortcode = cmd[2]
-          case cmd[1]
-          when 'avatar'
-            src_img = account.avatar
-          when 'parent'
-            next unless cmd[3].present? && reply?
-            shortcode = cmd[3]
-            parent_status = Status.where(id: in_reply_to_id).first
-            next if parent_status.nil?
-            case cmd[2]
-            when 'avatar'
-              src_img = parent_status.account.avatar
-            end
-          end
-
-          next if src_img.nil? || shortcode.nil? || !shortcode.match?(/\A\w+\Z/)
-
-          chunk = ":#{shortcode}:"
-          emoji = CustomEmoji.find_or_initialize_by(shortcode: shortcode, domain: nil)
-          if emoji.id.nil?
-            emoji.image = src_img
-            emoji.save
-          end
-        when 'emoji'
-          next if cmd[1].nil?
-          shortcode = cmd[1]
-          domain = (cmd[2].blank? ? nil : cmd[2].downcase)
-          chunk = ":#{shortcode}:"
-          ours = CustomEmoji.find_or_initialize_by(shortcode: shortcode, domain: nil)
-          if ours.id.nil?
-            if domain.nil?
-              theirs = CustomEmoji.find_by(shortcode: shortcode)
-            else
-              theirs = CustomEmoji.find_by(shortcode: shortcode, domain: domain)
-            end
-            unless theirs.nil?
-              ours.image = theirs.image
-              ours.save
-            end
-          end
-        when 'char'
-          chunk = nil
-          charmap = {
-            'zws' => "\u200b",
-            'zwnj' => "\u200c",
-            'zwj' => "\u200d",
-            '\n' => "\n",
-            '\r' => "\r",
-            '\t' => "\t",
-            '\T' => '    '
-          }
-          cmd[1..-1].each do |c|
-            next if c.nil?
-            if c.in?(charmap)
-              chunks << charmap[cmd[1]]
-            elsif (/^\h{1,5}$/ =~ c) && c.to_i(16) > 0
-              begin
-                chunks << [c.to_i(16)].pack('U*')
-              rescue
-                chunks << '?'
-              end
-            end
-          end
-        when 'link'
-          chunk = nil
-          case cmd[1]
-          when 'permalink', 'self'
-            chunk = TagManager.instance.url_for(self)
-          when 'cloudroot'
-            chunk = "https://monsterpit.cloud/~/#{account.username}"
-          when 'blogroot'
-            chunk = "https://monsterpit.blog/~/#{account.username}"
-          end
-        when 'ping'
-          mentions = []
-          case cmd[1]
-          when 'admins'
-            mentions = User.admins.map { |u| "@#{u.account.username}" }
-            mentions.sort!
-          when 'mods'
-            mentions = User.moderators.map { |u| "@#{u.account.username}" }
-            mentions.sort!
-          when 'staff'
-            mentions = User.admins.map { |u| "@#{u.account.username}" }
-            mentions += User.moderators.map { |u| "@#{u.account.username}" }
-            mentions.uniq!
-            mentions.sort!
-          end
-          chunk = mentions.join(' ')
-        when 'tag'
-          chunk = nil
-          records = []
-          valid_name = /^[[:word:]_\-]*[[:alpha:]_·\-][[:word:]_\-]*$/
-          cmd[1..-1].select {|t| t.present? && valid_name.match?(t)}.uniq.each do |name|
-            next if self.tags.where(name: name).exists?
-            tag = Tag.where(name: name).first_or_create(name: name)
-            self.tags << tag
-            records << tag
-            TrendingTags.record_use!(tag, account, created_at) if distributable?
-          end
-          if public_visibility? || unlisted_visibility?
-            account.featured_tags.where(tag_id: records.map(&:id)).each do |featured_tag|
-              featured_tag.increment(created_at)
-            end
-          end
-        when 'thread'
-          chunk = nil
-          case cmd[1]
-          when 'reall'
-            if conversation_id.present?
-              mention_ids = Status.where(conversation_id: conversation_id).flat_map { |s| s.mentions.pluck(:account_id) }
-              mention_ids.uniq!
-              mentions = Account.where(id: mention_ids).map { |a| "@#{a.username}" }
-              chunk = mentions.join(' ')
-            end
-          end
-        when 'parent'
-          chunk = nil
-          next unless reply?
-          parent_status = Status.where(id: in_reply_to_id).first
-          next if parent_status.nil?
-          case cmd[1]
-          when 'edit'
-            next unless reply? && in_reply_to_account_id == account_id
-          when 'permalink'
-            chunk = TagManager.instance.url_for(parent_status)
-          end
-        when 'media'
-          chunk = nil
-
-          media_idx = cmd[1]
-          media_cmd = cmd[2]
-          media_args = cmd[3..-1]
-
-          next unless media_cmd.present? && media_idx.present? && media_idx.scan(/\D/).empty?
-          media_idx = media_idx.to_i
-          next if media_attachments[media_idx-1].nil?
-
-          case media_cmd
-          when 'desc'
-            if media_args.present?
-              vars["media_#{media_idx}_desc"] = media_args.join(':')
-            else
-              vore_stack.push("media_#{media_idx}_desc")
-              component_stack.push(:var)
-            end
-          end
-
-          post_cmds.push(['media', media_idx, media_cmd])
-        when 'bangtag'
-          chunk = chunk.sub('bangtag:', '').gsub(':', ":\u200c")
-        when 'join'
-          chunk = nil
-          next if cmd[1].nil?
-          charmap = {
-            'zws' => "\u200b",
-            'zwnj' => "\u200c",
-            'zwj' => "\u200d",
-            '\n' => "\n",
-            '\r' => "\r",
-            '\t' => "\t",
-            '\T' => '    '
-          }
-          sep = charmap[cmd[1]]
-          chunk = cmd[2..-1].join(sep.nil? ? cmd[1] : sep)
-        when 'hide'
-          chunk = nil
-          case cmd[1]
-          when 'end', 'stop', 'endall', 'stopall'
-            vore_stack.reject! {|v| v == '_'}
-            compontent_stack.reject! {|c| c == :hide}
-          else
-            if cmd[1].nil? && !'_'.in?(vore_stack)
-              vore_stack.push('_')
-              component_stack.push(:hide)
-            end
-          end
-        when 'comment'
-          chunk = nil
-          if cmd[1].nil?
-            vore_stack.push('_comment')
-            component_stack.push(:var)
-          end
-        end
-      end
-
-      if chunk.present? && tf_cmds.present?
-        tf_cmds.each do |tf_cmd|
-          next if chunk.nil?
-          case tf_cmd[0]
-          when 'replace', 'sub', 's'
-            tf_cmd[1..-1].in_groups_of(2) do |args|
-              chunk.sub!(*args) if args.all?
-            end
-          when 'replaceall', 'gsub', 'gs'
-            tf_cmd[1..-1].in_groups_of(2) do |args|
-              chunk.gsub!(*args) if args.all?
-            end
-          end
-        end
-      end
-
-      unless chunk.blank? || vore_stack.empty?
-        var = vore_stack.last
-        next if var == '_'
-        if vars[var].nil?
-          vars[var] = chunk.lstrip
-        else
-          vars[var] += chunk.rstrip
-        end
-        chunk = nil
-      end
-
-      chunks << chunk unless chunk.nil?
-    end
-
-    vars.transform_values! {|v| v.rstrip}
-
-    if post_cmds.present?
-      post_cmds.each do |post_cmd|
-        case post_cmd[0]
-        when 'media'
-          media_idx = post_cmd[1]
-          media_cmd = post_cmd[2]
-          media_args = post_cmd[3..-1]
-
-          case media_cmd
-          when 'desc'
-            media_attachments[media_idx-1].description = vars["media_#{media_idx}_desc"]
-            media_attachments[media_idx-1].save
-          end
-        end
-      end
-    end
-
-    self.text = chunks.join('')
-    save
+    Bangtags.new(self).process if text&.present? && '#!'.in?(text)
   end
 
   def set_conversation
diff --git a/app/services/favourite_service.rb b/app/services/favourite_service.rb
index 2573bde0c..98275b37e 100644
--- a/app/services/favourite_service.rb
+++ b/app/services/favourite_service.rb
@@ -56,7 +56,7 @@ class FavouriteService < BaseService
   end
 
   def curate_status(status)
-    return if status.curated || status.direct_visibility? || (status.reply? && status.in_reply_to_account_id != status.account_id)
+    return if status.curated || !status.distributable? || (status.reply? && status.in_reply_to_account_id != status.account_id)
     status.curated = true
     status.save
     FanOutOnWriteService.new.call(status)
diff --git a/app/services/reblog_service.rb b/app/services/reblog_service.rb
index a9b662b0c..1a39c6c95 100644
--- a/app/services/reblog_service.rb
+++ b/app/services/reblog_service.rb
@@ -65,7 +65,7 @@ class ReblogService < BaseService
   end
 
   def curate_status(status)
-    return if status.curated || status.direct_visibility? || (status.reply? && status.in_reply_to_account_id != status.account_id)
+    return if status.curated || !status.distributable? || (status.reply? && status.in_reply_to_account_id != status.account_id)
     status.curated = true
     status.save
     FanOutOnWriteService.new.call(status)