Add limited_scope support
This commit is contained in:
parent
ec16074def
commit
c1f6d22ad2
17 changed files with 107 additions and 10 deletions
|
@ -24,6 +24,7 @@ module ContextHelper
|
|||
emoji_reactions: { 'fedibird' => 'http://fedibird.com/ns#', 'emojiReactions' => { '@id' => 'fedibird:emojiReactions', '@type' => '@id' } },
|
||||
searchable_by: { 'fedibird' => 'http://fedibird.com/ns#', 'searchableBy' => { '@id' => 'fedibird:searchableBy', '@type' => '@id' } },
|
||||
subscribable_by: { 'kmyblue' => 'http://kmy.blue/ns#', 'subscribableBy' => { '@id' => 'kmyblue:subscribableBy', '@type' => '@id' } },
|
||||
limited_scope: { 'kmyblue' => 'http://kmy.blue/ns#', 'limitedScope' => { '@id' => 'kmyblue:limitedScope', '@type' => '@id' } },
|
||||
other_setting: { 'fedibird' => 'http://fedibird.com/ns#', 'otherSetting' => 'fedibird:otherSetting' },
|
||||
references: { 'fedibird' => 'http://fedibird.com/ns#', 'references' => { '@id' => 'fedibird:references', '@type' => '@id' } },
|
||||
olm: {
|
||||
|
|
|
@ -70,6 +70,7 @@ const messages = defineMessages({
|
|||
login_short: { id: 'privacy.login.short', defaultMessage: 'Login only' },
|
||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
||||
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Limited menbers only' },
|
||||
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual followers only' },
|
||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
||||
edited: { id: 'status.edited', defaultMessage: 'Edited {date}' },
|
||||
});
|
||||
|
@ -400,10 +401,11 @@ class Status extends ImmutablePureComponent {
|
|||
'login': { icon: 'key', text: intl.formatMessage(messages.login_short) },
|
||||
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
||||
'limited': { icon: 'get-pocket', text: intl.formatMessage(messages.limited_short) },
|
||||
'mutual': { icon: 'exchange', text: intl.formatMessage(messages.mutual_short) },
|
||||
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
||||
};
|
||||
|
||||
let visibilityIcon = visibilityIconInfo[status.get('visibility_ex')] || visibilityIconInfo[status.get('visibility')];
|
||||
let visibilityIcon = visibilityIconInfo[status.get('limited_scope') || status.get('visibility_ex')] || visibilityIconInfo[status.get('visibility')];
|
||||
|
||||
if (this.state.forceFilter === undefined ? matchedFilters : this.state.forceFilter) {
|
||||
const minHandlers = this.props.muted ? {} : {
|
||||
|
@ -564,7 +566,7 @@ class Status extends ImmutablePureComponent {
|
|||
statusAvatar = <AvatarOverlay account={status.get('account')} friend={account} />;
|
||||
}
|
||||
|
||||
visibilityIcon = visibilityIconInfo[status.get('visibility_ex')] || visibilityIconInfo[status.get('visibility')];
|
||||
visibilityIcon = visibilityIconInfo[status.get('limited_scope') || status.get('visibility_ex')] || visibilityIconInfo[status.get('visibility')];
|
||||
|
||||
let emojiReactionsBar = null;
|
||||
if (!this.props.withoutEmojiReactions && status.get('emoji_reactions')) {
|
||||
|
|
|
@ -21,6 +21,7 @@ const messages = defineMessages({
|
|||
login_short: { id: 'privacy.login.short', defaultMessage: 'Login only' },
|
||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
||||
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Limited menbers only' },
|
||||
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual followers only' },
|
||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
||||
});
|
||||
|
||||
|
@ -53,10 +54,11 @@ class StatusCheckBox extends PureComponent {
|
|||
'login': { icon: 'key', text: intl.formatMessage(messages.login_short) },
|
||||
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
||||
'limited': { icon: 'get-pocket', text: intl.formatMessage(messages.limited_short) },
|
||||
'mutual': { icon: 'exchange', text: intl.formatMessage(messages.mutual_short) },
|
||||
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
||||
};
|
||||
|
||||
const visibilityIcon = visibilityIconInfo[status.get('visibility_ex')];
|
||||
const visibilityIcon = visibilityIconInfo[status.get('limited_scope') || status.get('visibility_ex')];
|
||||
|
||||
const labelComponent = (
|
||||
<div className='status-check-box__status poll__option__text'>
|
||||
|
|
|
@ -31,6 +31,7 @@ const messages = defineMessages({
|
|||
login_short: { id: 'privacy.login.short', defaultMessage: 'Login only' },
|
||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
||||
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Limited menbers only' },
|
||||
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual followers only' },
|
||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
||||
searchability_public_short: { id: 'searchability.public.short', defaultMessage: 'Public' },
|
||||
searchability_private_short: { id: 'searchability.unlisted.short', defaultMessage: 'Followers' },
|
||||
|
@ -251,10 +252,11 @@ class DetailedStatus extends ImmutablePureComponent {
|
|||
'login': { icon: 'key', text: intl.formatMessage(messages.login_short) },
|
||||
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
||||
'limited': { icon: 'get-pocket', text: intl.formatMessage(messages.limited_short) },
|
||||
'mutual': { icon: 'exchange', text: intl.formatMessage(messages.mutual_short) },
|
||||
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
||||
};
|
||||
|
||||
const visibilityIcon = visibilityIconInfo[status.get('visibility_ex')];
|
||||
const visibilityIcon = visibilityIconInfo[status.get('limited_scope') || status.get('visibility_ex')];
|
||||
const visibilityLink = <> · <Icon id={visibilityIcon.icon} title={visibilityIcon.text} /></>;
|
||||
|
||||
const searchabilityIconInfo = {
|
||||
|
|
|
@ -28,6 +28,7 @@ const messages = defineMessages({
|
|||
login_short: { id: 'privacy.login.short', defaultMessage: 'Login only' },
|
||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
||||
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Limited menbers only' },
|
||||
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual followers only' },
|
||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
||||
});
|
||||
|
||||
|
@ -96,10 +97,11 @@ class BoostModal extends ImmutablePureComponent {
|
|||
'login': { icon: 'key', text: intl.formatMessage(messages.login_short) },
|
||||
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
||||
'limited': { icon: 'get-pocket', text: intl.formatMessage(messages.limited_short) },
|
||||
'mutual': { icon: 'exchange', text: intl.formatMessage(messages.mutual_short) },
|
||||
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
||||
};
|
||||
|
||||
const visibilityIcon = visibilityIconInfo[status.get('visibility_ex')];
|
||||
const visibilityIcon = visibilityIconInfo[status.get('limited_scope') || status.get('visibility_ex')];
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal boost-modal'>
|
||||
|
|
|
@ -133,6 +133,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
reply: @status_parser.reply,
|
||||
sensitive: @account.sensitized? || @status_parser.sensitive || false,
|
||||
visibility: @status_parser.visibility,
|
||||
limited_scope: @status_parser.limited_scope,
|
||||
searchability: searchability,
|
||||
thread: replied_to_status,
|
||||
conversation: conversation_from_uri(@object['conversation']),
|
||||
|
|
|
@ -86,6 +86,14 @@ class ActivityPub::Parser::StatusParser
|
|||
end
|
||||
end
|
||||
|
||||
def limited_scope
|
||||
if @object['limitedScope'] == 'Mutual'
|
||||
:mutual
|
||||
else
|
||||
:none
|
||||
end
|
||||
end
|
||||
|
||||
def language
|
||||
if content_language_map?
|
||||
@object['contentMap'].keys.first
|
||||
|
|
|
@ -221,6 +221,10 @@ class ActivityPub::TagManager
|
|||
nil
|
||||
end
|
||||
|
||||
def limited_scope(status)
|
||||
status.mutual_limited? ? 'Mutual' : ''
|
||||
end
|
||||
|
||||
def subscribable_by(account)
|
||||
account.dissubscribable ? [] : [COLLECTIONS[:public]]
|
||||
end
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
# ordered_media_attachment_ids :bigint(8) is an Array
|
||||
# searchability :integer
|
||||
# markdown :boolean default(FALSE)
|
||||
# limited_scope :integer
|
||||
#
|
||||
|
||||
require 'ostruct'
|
||||
|
@ -54,6 +55,7 @@ class Status < ApplicationRecord
|
|||
|
||||
enum visibility: { public: 0, unlisted: 1, private: 2, direct: 3, limited: 4, public_unlisted: 10, login: 11 }, _suffix: :visibility
|
||||
enum searchability: { public: 0, private: 1, direct: 2, limited: 3, unsupported: 4, public_unlisted: 10 }, _suffix: :searchability
|
||||
enum limited_scope: { none: 0, mutual: 1 }, _suffix: :limited
|
||||
|
||||
belongs_to :application, class_name: 'Doorkeeper::Application', optional: true
|
||||
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
class ActivityPub::NoteSerializer < ActivityPub::Serializer
|
||||
include FormattingHelper
|
||||
|
||||
context_extensions :atom_uri, :conversation, :sensitive, :voters_count, :searchable_by, :references
|
||||
context_extensions :atom_uri, :conversation, :sensitive, :voters_count, :searchable_by, :references, :limited_scope
|
||||
|
||||
attributes :id, :type, :summary,
|
||||
:in_reply_to, :published, :url,
|
||||
:attributed_to, :to, :cc, :sensitive,
|
||||
:atom_uri, :in_reply_to_atom_uri,
|
||||
:conversation, :searchable_by
|
||||
:conversation, :searchable_by, :limited_scope
|
||||
|
||||
attribute :references, if: :not_private_post?
|
||||
|
||||
|
@ -148,6 +148,10 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
|
|||
ActivityPub::TagManager.instance.searchable_by(object)
|
||||
end
|
||||
|
||||
def limited_scope
|
||||
ActivityPub::TagManager.instance.limited_scope(object)
|
||||
end
|
||||
|
||||
def local?
|
||||
object.account.local?
|
||||
end
|
||||
|
|
|
@ -4,7 +4,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
|||
include FormattingHelper
|
||||
|
||||
attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id,
|
||||
:sensitive, :spoiler_text, :visibility, :visibility_ex, :language,
|
||||
:sensitive, :spoiler_text, :visibility, :visibility_ex, :limited_scope, :language,
|
||||
:uri, :url, :replies_count, :reblogs_count, :searchability, :markdown,
|
||||
:status_reference_ids, :status_references_count, :status_referred_by_count,
|
||||
:favourites_count, :emoji_reactions, :emoji_reactions_count, :reactions, :edited_at
|
||||
|
@ -69,6 +69,10 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
|||
object.visibility
|
||||
end
|
||||
|
||||
def limited_scope
|
||||
!object.none_limited? && object.limited_visibility? ? object.limited_scope : nil
|
||||
end
|
||||
|
||||
def searchability
|
||||
object.compute_searchability
|
||||
end
|
||||
|
|
|
@ -128,6 +128,7 @@ class REST::V1::InstanceSerializer < ActiveModel::Serializer
|
|||
:kmyblue_visibility_login,
|
||||
:status_reference,
|
||||
:visibility_mutual,
|
||||
:kmyblue_limited_scope,
|
||||
]
|
||||
|
||||
capabilities << :profile_search unless Chewy.enabled?
|
||||
|
|
|
@ -247,6 +247,7 @@ class PostStatusService < BaseService
|
|||
spoiler_text: @options[:spoiler_text] || '',
|
||||
markdown: @markdown,
|
||||
visibility: @visibility,
|
||||
limited_scope: @visibility == :limited ? :mutual : :none,
|
||||
searchability: @searchability,
|
||||
language: valid_locale_cascade(@options[:language], @account.user&.preferred_posting_language, I18n.default_locale),
|
||||
application: @options[:application],
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddLimitedScopeToStatuses < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_column :statuses, :limited_scope, :integer
|
||||
end
|
||||
end
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.0].define(version: 2023_08_12_083752) do
|
||||
ActiveRecord::Schema[7.0].define(version: 2023_08_12_130612) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
|
@ -1122,6 +1122,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_08_12_083752) do
|
|||
t.bigint "ordered_media_attachment_ids", array: true
|
||||
t.integer "searchability"
|
||||
t.boolean "markdown", default: false
|
||||
t.integer "limited_scope"
|
||||
t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20190820", order: { id: :desc }, where: "(deleted_at IS NULL)"
|
||||
t.index ["account_id"], name: "index_statuses_on_account_id"
|
||||
t.index ["deleted_at"], name: "index_statuses_on_deleted_at", where: "(deleted_at IS NOT NULL)"
|
||||
|
|
|
@ -290,6 +290,7 @@ RSpec.describe ActivityPub::Activity::Create do
|
|||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.visibility).to eq 'limited'
|
||||
expect(status.limited_scope).to eq 'none'
|
||||
end
|
||||
|
||||
it 'creates silent mention' do
|
||||
|
@ -298,6 +299,50 @@ RSpec.describe ActivityPub::Activity::Create do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when limited_scope' do
|
||||
let(:recipient) { Fabricate(:account) }
|
||||
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
to: ActivityPub::TagManager.instance.uri_for(recipient),
|
||||
limitedScope: 'Mutual',
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.visibility).to eq 'limited'
|
||||
expect(status.limited_scope).to eq 'mutual'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invalid limited_scope' do
|
||||
let(:recipient) { Fabricate(:account) }
|
||||
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
to: ActivityPub::TagManager.instance.uri_for(recipient),
|
||||
limitedScope: 'IdosdsazsF',
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.visibility).to eq 'limited'
|
||||
expect(status.limited_scope).to eq 'none'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when direct' do
|
||||
let(:recipient) { Fabricate(:account) }
|
||||
|
||||
|
|
|
@ -168,7 +168,17 @@ RSpec.describe PostStatusService, type: :service do
|
|||
status = subject.call(account, text: 'test status update')
|
||||
|
||||
expect(ProcessMentionsService).to have_received(:new)
|
||||
expect(mention_service).to have_received(:call).with(status, save_records: false)
|
||||
expect(mention_service).to have_received(:call).with(status, limited_type: '', save_records: false)
|
||||
end
|
||||
|
||||
it 'mutual visibility' do
|
||||
account = Fabricate(:account)
|
||||
text = 'This is an English text.'
|
||||
|
||||
status = subject.call(account, text: text, visibility: 'mutual')
|
||||
|
||||
expect(status.visibility).to eq 'limited'
|
||||
expect(status.limited_scope).to eq 'mutual'
|
||||
end
|
||||
|
||||
it 'safeguards mentions' do
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue