respect 'don't @ me' requests
parent
cd52f75006
commit
b644f1c505
|
@ -62,6 +62,9 @@ export default class StatusIcons extends React.PureComponent {
|
|||
{status.get('delete_after') ? (
|
||||
<i className='fa fa-clock-o' title={new Date(status.get('delete_after'))} aria-hidden='true' />
|
||||
) : null}
|
||||
{status.get('reject_replies') ? (
|
||||
<i className='fa fa-microphone-slash' title='Rejecting replies' aria-hidden='true' />
|
||||
) : null}
|
||||
{(
|
||||
<VisibilityIcon visibility={status.get('visibility')} />
|
||||
)}
|
||||
|
|
|
@ -129,6 +129,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
|||
let favouriteLink = '';
|
||||
let sharekeyLinks = '';
|
||||
let destructIcon = '';
|
||||
let rejectIcon = '';
|
||||
|
||||
if (this.props.measureHeight) {
|
||||
outerStyle.height = `${this.state.height}px`;
|
||||
|
@ -251,6 +252,14 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
|||
)
|
||||
}
|
||||
|
||||
if (status.get('reject_replies')) {
|
||||
rejectIcon = (
|
||||
<span>
|
||||
<i className='fa fa-microphone-slash' title='Rejecting replies' /> ·
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={outerStyle}>
|
||||
<div ref={this.setRef} className={classNames('detailed-status', { compact })} data-status-by={status.getIn(['account', 'acct'])}>
|
||||
|
@ -272,7 +281,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
|||
/>
|
||||
|
||||
<div className='detailed-status__meta'>
|
||||
{sharekeyLinks} {reblogLink} · {favouriteLink} · {destructIcon} <VisibilityIcon visibility={status.get('visibility')} />
|
||||
{sharekeyLinks} {reblogLink} · {favouriteLink} · {destructIcon} {rejectIcon} <VisibilityIcon visibility={status.get('visibility')} />
|
||||
<a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener'>
|
||||
<FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
|
||||
</a>
|
||||
|
|
|
@ -46,7 +46,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
@params = {}
|
||||
|
||||
process_status_params
|
||||
return reject_payload! if twitter_retweet?
|
||||
return reject_payload! if twitter_retweet? || recipient_rejects_replies?
|
||||
process_tags
|
||||
process_audience
|
||||
|
||||
|
@ -86,7 +86,13 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
end
|
||||
|
||||
def twitter_retweet?
|
||||
@params[:text] =~ /^RT / || '🐦🔗:'.in?(@params[:text])
|
||||
@params[:text] =~ /^(?:<p> *)?RT / || '🐦🔗:'.in?(@params[:text])
|
||||
end
|
||||
|
||||
def recipient_rejects_replies?
|
||||
@params[:thread].present? &&
|
||||
@params[:thread]&.reject_replies &&
|
||||
@params[:thread]&.account_id != @account.id
|
||||
end
|
||||
|
||||
def process_status_params
|
||||
|
@ -105,6 +111,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
visibility: visibility_from_audience,
|
||||
thread: replied_to_status,
|
||||
conversation: conversation_from_uri(@object['conversation']),
|
||||
reject_replies: @object['rejectReplies'] || false,
|
||||
media_attachment_ids: process_attachments.take(6).map(&:id),
|
||||
poll: process_poll,
|
||||
}
|
||||
|
|
|
@ -40,6 +40,10 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
|
|||
'mp' => 'https://monsterpit.net/ns#',
|
||||
'froze' => 'mp:froze'
|
||||
},
|
||||
reject_replies: {
|
||||
'mp' => 'https://monsterpit.net/ns#',
|
||||
'rejectReplies' => 'mp:rejectReplies',
|
||||
}
|
||||
}.freeze
|
||||
|
||||
def self.default_key_transform
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
# imported :boolean
|
||||
# origin :string
|
||||
# boostable :boolean
|
||||
# reject_replies :boolean
|
||||
#
|
||||
|
||||
class Status < ApplicationRecord
|
||||
|
@ -46,6 +47,7 @@ class Status < ApplicationRecord
|
|||
|
||||
# match both with and without U+FE0F (the emoji variation selector)
|
||||
LOCAL_ONLY_TOKENS = /(?:#!|\u{1f441}\ufe0f?)\u200b?\z/
|
||||
REJECT_REPLIES_TOKENS = /\b(?:\:ms_dont_at_me\:|no replies|(?:don't|do not) (?:@|at|reply)(?: (?:me|us))?)\b/i
|
||||
|
||||
# If `override_timestamps` is set at creation time, Snowflake ID creation
|
||||
# will be based on current time instead of `created_at`
|
||||
|
@ -318,6 +320,7 @@ class Status < ApplicationRecord
|
|||
before_validation :set_visibility
|
||||
before_validation :set_conversation
|
||||
before_validation :set_local
|
||||
before_validation :infer_reject_replies
|
||||
|
||||
after_create :set_poll_id
|
||||
after_create :process_bangtags, if: :local?
|
||||
|
@ -544,6 +547,13 @@ class Status < ApplicationRecord
|
|||
LOCAL_ONLY_TOKENS.match?(content)
|
||||
end
|
||||
|
||||
def marked_reject_replies?
|
||||
return true if reject_replies
|
||||
return true if spoiler_text.present? && REJECT_REPLIES_TOKENS.match?(spoiler_text)
|
||||
return true if content.present? && REJECT_REPLIES_TOKENS.match?(content.lines.first)
|
||||
content.present? && REJECT_REPLIES_TOKENS.match?(content.lines.last)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_status_stat!(attrs)
|
||||
|
@ -581,6 +591,10 @@ class Status < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def infer_reject_replies
|
||||
self.reject_replies = marked_reject_replies?
|
||||
end
|
||||
|
||||
def process_bangtags
|
||||
Bangtags.new(self).process
|
||||
end
|
||||
|
|
|
@ -2,12 +2,14 @@
|
|||
|
||||
class ActivityPub::NoteSerializer < ActivityPub::Serializer
|
||||
context_extensions :conversation, :sensitive, :big,
|
||||
:hashtag, :emoji, :focal_point, :blurhash
|
||||
:hashtag, :emoji, :focal_point, :blurhash,
|
||||
:reject_replies
|
||||
|
||||
attributes :id, :type, :summary,
|
||||
:in_reply_to, :published, :url,
|
||||
:attributed_to, :to, :cc, :sensitive,
|
||||
:conversation, :source, :tails_never_fail
|
||||
:conversation, :source, :tails_never_fail,
|
||||
:reject_replies
|
||||
|
||||
attribute :content
|
||||
attribute :content_map, if: :language?
|
||||
|
@ -143,6 +145,10 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
|
|||
object.preloadable_poll&.expired?
|
||||
end
|
||||
|
||||
def reject_replies
|
||||
object.reject_replies == true
|
||||
end
|
||||
|
||||
def tails_never_fail
|
||||
true
|
||||
end
|
||||
|
|
|
@ -4,7 +4,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
|||
attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id,
|
||||
:sensitive, :spoiler_text, :visibility, :language,
|
||||
:uri, :url, :replies_count, :reblogs_count,
|
||||
:favourites_count, :network, :curated
|
||||
:favourites_count, :network, :curated, :reject_replies
|
||||
|
||||
attribute :favourited, if: :current_user?
|
||||
attribute :reblogged, if: :current_user?
|
||||
|
@ -140,6 +140,10 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
|||
object.delete_after
|
||||
end
|
||||
|
||||
def reject_replies
|
||||
object.reject_replies == true
|
||||
end
|
||||
|
||||
class ApplicationSerializer < ActiveModel::Serializer
|
||||
attributes :name, :website
|
||||
end
|
||||
|
|
|
@ -30,6 +30,7 @@ class PostStatusService < BaseService
|
|||
# @option [String] :language
|
||||
# @option [String] :scheduled_at
|
||||
# @option [String] :delete_after
|
||||
# @option [Boolean] :noreplies Author does not accept replies
|
||||
# @option [Boolean] :nocrawl Optional skip link card generation
|
||||
# @option [Boolean] :nomentions Optional skip mention processing
|
||||
# @option [Boolean] :delayed Optional publishing delay of 30 secs
|
||||
|
@ -49,6 +50,8 @@ class PostStatusService < BaseService
|
|||
@sensitive = (@account.force_sensitive? ? true : @options[:sensitive])
|
||||
@preloaded_tags = @options[:preloaded_tags] || []
|
||||
|
||||
raise Mastodon::LengthValidationError, I18n.t('statuses.replies_rejected') if recipient_rejects_replies?
|
||||
|
||||
return idempotency_duplicate if idempotency_given? && idempotency_duplicate?
|
||||
|
||||
validate_media!
|
||||
|
@ -69,6 +72,7 @@ class PostStatusService < BaseService
|
|||
nocrawl: @options[:nocrawl],
|
||||
nomentions: @options[:nomentions],
|
||||
delete_after: @delete_after.nil? ? nil : @delete_after + 1.minute,
|
||||
reject_replies: @options[:noreplies] || false,
|
||||
}.compact
|
||||
|
||||
PostStatusWorker.perform_at(delay_until, @status.id, opts)
|
||||
|
@ -86,6 +90,10 @@ class PostStatusService < BaseService
|
|||
|
||||
private
|
||||
|
||||
def recipient_rejects_replies?
|
||||
@in_reply_to.present? && @in_reply_to.reject_replies && @in_reply_to.account_id != @account.id
|
||||
end
|
||||
|
||||
def set_footer_from_i_am
|
||||
return if @footer.present? || @options[:no_footer]
|
||||
name = @account.user.vars['_they:are']
|
||||
|
@ -102,16 +110,19 @@ class PostStatusService < BaseService
|
|||
end
|
||||
|
||||
def limit_visibility_to_reply
|
||||
return if @in_reply_to.nil?
|
||||
@visibility = @in_reply_to.visibility if @visibility.nil? ||
|
||||
VISIBILITY_RANK[@visibility] < VISIBILITY_RANK[@in_reply_to.visibility]
|
||||
end
|
||||
|
||||
def unfilter_thread_on_reply
|
||||
return if @in_reply_to.nil?
|
||||
Redis.current.srem("filtered_threads:#{@account.id}", @in_reply_to.conversation_id)
|
||||
end
|
||||
|
||||
def inherit_reply_rejection
|
||||
return unless @in_reply_to.reject_replies && @in_reply_to.account_id == @account.id
|
||||
@options[:noreplies] = true
|
||||
end
|
||||
|
||||
def set_local_only
|
||||
@local_only = true if @account.user_always_local_only? || @in_reply_to&.local_only
|
||||
end
|
||||
|
@ -140,8 +151,12 @@ class PostStatusService < BaseService
|
|||
set_local_only
|
||||
set_initial_visibility
|
||||
limit_visibility_if_silenced
|
||||
limit_visibility_to_reply
|
||||
unfilter_thread_on_reply
|
||||
|
||||
unless @in_reply_to.nil?
|
||||
inherit_reply_rejection
|
||||
limit_visibility_to_reply
|
||||
unfilter_thread_on_reply
|
||||
end
|
||||
|
||||
@sensitive = (@account.user_defaults_to_sensitive? || @options[:spoiler_text].present?) if @sensitive.nil?
|
||||
|
||||
|
@ -280,6 +295,7 @@ class PostStatusService < BaseService
|
|||
visibility: @visibility,
|
||||
local_only: @local_only,
|
||||
delete_after: @delete_after,
|
||||
reject_replies: @options[:noreplies] || false,
|
||||
sharekey: @sharekey,
|
||||
language: language_from_option(@options[:language]) || @account.user_default_language&.presence || 'en',
|
||||
application: @options[:application],
|
||||
|
|
|
@ -11,6 +11,7 @@ class PostStatusWorker
|
|||
|
||||
status.visibility = options[:visibility] if options[:visibility]
|
||||
status.local_only = options[:local_only] if options[:local_only]
|
||||
status.reject_replies = options[:reject_replies] if options[:reject_replies]
|
||||
status.save!
|
||||
|
||||
process_mentions_service.call(status) unless options[:nomentions]
|
||||
|
|
|
@ -956,6 +956,7 @@ en:
|
|||
one: "%{count} vote"
|
||||
other: "%{count} votes"
|
||||
vote: Vote
|
||||
replies_rejected: 'The author is not accepting replies to this roar.'
|
||||
show_more: Show more
|
||||
sign_in_to_participate: Sign in to participate in the conversation
|
||||
title: '%{name}: "%{quote}"'
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
class AddRejectRepliesToStatuses < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
add_column :statuses, :reject_replies, :boolean
|
||||
end
|
||||
end
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2019_08_01_222823) do
|
||||
ActiveRecord::Schema.define(version: 2019_08_03_170051) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
@ -669,6 +669,7 @@ ActiveRecord::Schema.define(version: 2019_08_01_222823) do
|
|||
t.string "origin"
|
||||
t.tsvector "tsv"
|
||||
t.boolean "boostable"
|
||||
t.boolean "reject_replies"
|
||||
t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20180106", order: { id: :desc }
|
||||
t.index ["account_id", "id", "visibility"], name: "index_statuses_on_account_id_and_id_and_visibility", order: { id: :desc }, where: "(visibility = ANY (ARRAY[0, 1, 2, 4]))"
|
||||
t.index ["in_reply_to_account_id"], name: "index_statuses_on_in_reply_to_account_id"
|
||||
|
|
Loading…
Reference in New Issue