Make sure only distributable statuses are marked curated; move bangtags processing into own helper lib.

staging
multiple creatures 2019-04-17 13:43:29 -05:00
parent 500b485b77
commit 036f422877
5 changed files with 367 additions and 360 deletions

View File

@ -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)

360
app/lib/bangtags.rb Normal file
View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)