Add mutual visibility support
This commit is contained in:
parent
9662e6ae32
commit
79062bfc2f
14 changed files with 85 additions and 9 deletions
|
@ -62,7 +62,12 @@ class StatusesController < ApplicationController
|
||||||
|
|
||||||
def set_status
|
def set_status
|
||||||
@status = @account.statuses.find(params[:id])
|
@status = @account.statuses.find(params[:id])
|
||||||
authorize @status, :show?
|
|
||||||
|
if request.authorization.present? && request.authorization.match(/^Bearer /i)
|
||||||
|
raise Mastodon::NotPermittedError unless @status.capability_tokens.find_by(token: request.authorization.gsub(/^Bearer /i, ''))
|
||||||
|
else
|
||||||
|
authorize @status, :show?
|
||||||
|
end
|
||||||
rescue Mastodon::NotPermittedError
|
rescue Mastodon::NotPermittedError
|
||||||
not_found
|
not_found
|
||||||
end
|
end
|
||||||
|
|
|
@ -24,6 +24,8 @@ const messages = defineMessages({
|
||||||
login_long: { id: 'privacy.login.long', defaultMessage: 'Login user only' },
|
login_long: { id: 'privacy.login.long', defaultMessage: 'Login user only' },
|
||||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
||||||
private_long: { id: 'privacy.private.long', defaultMessage: 'Visible for followers only' },
|
private_long: { id: 'privacy.private.long', defaultMessage: 'Visible for followers only' },
|
||||||
|
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual' },
|
||||||
|
mutual_long: { id: 'privacy.mutual.long', defaultMessage: 'Mutual follows only' },
|
||||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
||||||
direct_long: { id: 'privacy.direct.long', defaultMessage: 'Visible for mentioned users only' },
|
direct_long: { id: 'privacy.direct.long', defaultMessage: 'Visible for mentioned users only' },
|
||||||
change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' },
|
change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' },
|
||||||
|
@ -231,6 +233,7 @@ class PrivacyDropdown extends PureComponent {
|
||||||
{ icon: 'cloud', value: 'public_unlisted', text: formatMessage(messages.public_unlisted_short), meta: formatMessage(messages.public_unlisted_long) },
|
{ icon: 'cloud', value: 'public_unlisted', text: formatMessage(messages.public_unlisted_short), meta: formatMessage(messages.public_unlisted_long) },
|
||||||
{ icon: 'key', value: 'login', text: formatMessage(messages.login_short), meta: formatMessage(messages.login_long) },
|
{ icon: 'key', value: 'login', text: formatMessage(messages.login_short), meta: formatMessage(messages.login_long) },
|
||||||
{ icon: 'unlock', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) },
|
{ icon: 'unlock', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) },
|
||||||
|
{ icon: 'exchange', value: 'mutual', text: formatMessage(messages.mutual_short), meta: formatMessage(messages.mutual_long) },
|
||||||
{ icon: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) },
|
{ icon: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) },
|
||||||
{ icon: 'at', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
|
{ icon: 'at', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
|
||||||
];
|
];
|
||||||
|
|
|
@ -29,7 +29,7 @@ class StatusReachFinder
|
||||||
|
|
||||||
if @status.reblog?
|
if @status.reblog?
|
||||||
[]
|
[]
|
||||||
else
|
elsif !@status.limited_visibility?
|
||||||
Account.where(id: reached_account_ids).where.not(domain: banned_domains).inboxes
|
Account.where(id: reached_account_ids).where.not(domain: banned_domains).inboxes
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -37,7 +37,7 @@ class StatusReachFinder
|
||||||
def reached_account_inboxes_for_misskey
|
def reached_account_inboxes_for_misskey
|
||||||
if @status.reblog?
|
if @status.reblog?
|
||||||
[]
|
[]
|
||||||
else
|
elsif !@status.limited_visibility?
|
||||||
Account.where(id: reached_account_ids).where(domain: banned_domains_for_misskey).inboxes
|
Account.where(id: reached_account_ids).where(domain: banned_domains_for_misskey).inboxes
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -303,6 +303,10 @@ module AccountInteractions
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def mutuals
|
||||||
|
followers.merge(Account.where(id: following))
|
||||||
|
end
|
||||||
|
|
||||||
def relations_map(account_ids, domains = nil, **options)
|
def relations_map(account_ids, domains = nil, **options)
|
||||||
relations = {
|
relations = {
|
||||||
blocked_by: Account.blocked_by_map(account_ids, id),
|
blocked_by: Account.blocked_by_map(account_ids, id),
|
||||||
|
|
|
@ -79,6 +79,7 @@ class Status < ApplicationRecord
|
||||||
has_many :references, through: :reference_objects, class_name: 'Status', source: :target_status
|
has_many :references, through: :reference_objects, class_name: 'Status', source: :target_status
|
||||||
has_many :referenced_by_status_objects, foreign_key: 'target_status_id', class_name: 'StatusReference', inverse_of: :target_status, dependent: :destroy
|
has_many :referenced_by_status_objects, foreign_key: 'target_status_id', class_name: 'StatusReference', inverse_of: :target_status, dependent: :destroy
|
||||||
has_many :referenced_by_statuses, through: :referenced_by_status_objects, class_name: 'Status', source: :status
|
has_many :referenced_by_statuses, through: :referenced_by_status_objects, class_name: 'Status', source: :status
|
||||||
|
has_many :capability_tokens, class_name: 'StatusCapabilityToken', inverse_of: :status, dependent: :destroy
|
||||||
|
|
||||||
has_and_belongs_to_many :tags
|
has_and_belongs_to_many :tags
|
||||||
has_and_belongs_to_many :preview_cards
|
has_and_belongs_to_many :preview_cards
|
||||||
|
|
25
app/models/status_capability_token.rb
Normal file
25
app/models/status_capability_token.rb
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: status_capability_tokens
|
||||||
|
#
|
||||||
|
# id :bigint(8) not null, primary key
|
||||||
|
# status_id :bigint(8) not null
|
||||||
|
# token :string
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
#
|
||||||
|
class StatusCapabilityToken < ApplicationRecord
|
||||||
|
belongs_to :status
|
||||||
|
|
||||||
|
validates :token, presence: true
|
||||||
|
|
||||||
|
before_validation :generate_token, on: :create
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def generate_token
|
||||||
|
self.token = Doorkeeper::OAuth::Helpers::UniqueToken.generate
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,7 +4,7 @@ class ActivityPub::ActivityPresenter < ActiveModelSerializers::Model
|
||||||
attributes :id, :type, :actor, :published, :to, :cc, :virtual_object
|
attributes :id, :type, :actor, :published, :to, :cc, :virtual_object
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def from_status(status, allow_inlining: true, for_misskey: false)
|
def from_status(status, use_bearcap: true, allow_inlining: true, for_misskey: false)
|
||||||
new.tap do |presenter|
|
new.tap do |presenter|
|
||||||
presenter.id = ActivityPub::TagManager.instance.activity_uri_for(status)
|
presenter.id = ActivityPub::TagManager.instance.activity_uri_for(status)
|
||||||
presenter.type = status.reblog? ? 'Announce' : 'Create'
|
presenter.type = status.reblog? ? 'Announce' : 'Create'
|
||||||
|
@ -20,6 +20,8 @@ class ActivityPub::ActivityPresenter < ActiveModelSerializers::Model
|
||||||
else
|
else
|
||||||
ActivityPub::TagManager.instance.uri_for(status.proper)
|
ActivityPub::TagManager.instance.uri_for(status.proper)
|
||||||
end
|
end
|
||||||
|
elsif status.limited_visibility? && use_bearcap
|
||||||
|
"bear:?#{{ u: ActivityPub::TagManager.instance.uri_for(status.proper), t: status.capability_tokens.first.token }.to_query}"
|
||||||
else
|
else
|
||||||
status.proper
|
status.proper
|
||||||
end
|
end
|
||||||
|
|
|
@ -118,6 +118,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer
|
||||||
:kmyblue_reaction_deck,
|
:kmyblue_reaction_deck,
|
||||||
:kmyblue_visibility_login,
|
:kmyblue_visibility_login,
|
||||||
:status_reference,
|
:status_reference,
|
||||||
|
:visibility_mutual,
|
||||||
]
|
]
|
||||||
|
|
||||||
capabilities << :profile_search unless Chewy.enabled?
|
capabilities << :profile_search unless Chewy.enabled?
|
||||||
|
|
|
@ -127,6 +127,7 @@ class REST::V1::InstanceSerializer < ActiveModel::Serializer
|
||||||
:kmyblue_reaction_deck,
|
:kmyblue_reaction_deck,
|
||||||
:kmyblue_visibility_login,
|
:kmyblue_visibility_login,
|
||||||
:status_reference,
|
:status_reference,
|
||||||
|
:visibility_mutual,
|
||||||
]
|
]
|
||||||
|
|
||||||
capabilities << :profile_search unless Chewy.enabled?
|
capabilities << :profile_search unless Chewy.enabled?
|
||||||
|
|
|
@ -32,7 +32,7 @@ class BackupService < BaseService
|
||||||
add_comma = true
|
add_comma = true
|
||||||
|
|
||||||
file.write(statuses.map do |status|
|
file.write(statuses.map do |status|
|
||||||
item = serialize_payload(ActivityPub::ActivityPresenter.from_status(status), ActivityPub::ActivitySerializer)
|
item = serialize_payload(ActivityPub::ActivityPresenter.from_status(status, use_bearcap: false), ActivityPub::ActivitySerializer)
|
||||||
item.delete('@context')
|
item.delete('@context')
|
||||||
|
|
||||||
unless item[:type] == 'Announce' || item[:object][:attachment].blank?
|
unless item[:type] == 'Announce' || item[:object][:attachment].blank?
|
||||||
|
|
|
@ -74,6 +74,7 @@ class PostStatusService < BaseService
|
||||||
end) || @options[:spoiler_text].present?
|
end) || @options[:spoiler_text].present?
|
||||||
@text = @options.delete(:spoiler_text) if @text.blank? && @options[:spoiler_text].present?
|
@text = @options.delete(:spoiler_text) if @text.blank? && @options[:spoiler_text].present?
|
||||||
@visibility = @options[:visibility] || @account.user&.setting_default_privacy
|
@visibility = @options[:visibility] || @account.user&.setting_default_privacy
|
||||||
|
@visibility = :limited if @options[:visibility] == 'mutual'
|
||||||
@visibility = :unlisted if (@visibility&.to_sym == :public || @visibility&.to_sym == :public_unlisted || @visibility&.to_sym == :login) && @account.silenced?
|
@visibility = :unlisted if (@visibility&.to_sym == :public || @visibility&.to_sym == :public_unlisted || @visibility&.to_sym == :login) && @account.silenced?
|
||||||
@visibility = :public_unlisted if @visibility&.to_sym == :public && !@options[:force_visibility] && !@options[:application]&.superapp && @account.user&.setting_public_post_to_unlisted
|
@visibility = :public_unlisted if @visibility&.to_sym == :public && !@options[:force_visibility] && !@options[:application]&.superapp && @account.user&.setting_public_post_to_unlisted
|
||||||
@searchability = searchability
|
@searchability = searchability
|
||||||
|
@ -113,7 +114,7 @@ class PostStatusService < BaseService
|
||||||
|
|
||||||
def process_status!
|
def process_status!
|
||||||
@status = @account.statuses.new(status_attributes)
|
@status = @account.statuses.new(status_attributes)
|
||||||
process_mentions_service.call(@status, save_records: false)
|
process_mentions_service.call(@status, limited_type: @status.limited_visibility? ? 'mutual' : '', save_records: false)
|
||||||
safeguard_mentions!(@status)
|
safeguard_mentions!(@status)
|
||||||
|
|
||||||
UpdateStatusExpirationService.new.call(@status)
|
UpdateStatusExpirationService.new.call(@status)
|
||||||
|
@ -122,6 +123,7 @@ class PostStatusService < BaseService
|
||||||
# the media attachments when the status is created
|
# the media attachments when the status is created
|
||||||
ApplicationRecord.transaction do
|
ApplicationRecord.transaction do
|
||||||
@status.save!
|
@status.save!
|
||||||
|
@status.capability_tokens.create! if @status.limited_visibility?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,9 @@ class ProcessMentionsService < BaseService
|
||||||
# and create local mention pointers
|
# and create local mention pointers
|
||||||
# @param [Status] status
|
# @param [Status] status
|
||||||
# @param [Boolean] save_records Whether to save records in database
|
# @param [Boolean] save_records Whether to save records in database
|
||||||
def call(status, save_records: true)
|
def call(status, limited_type: '', save_records: true)
|
||||||
@status = status
|
@status = status
|
||||||
|
@limited_type = limited_type
|
||||||
@save_records = save_records
|
@save_records = save_records
|
||||||
|
|
||||||
return unless @status.local?
|
return unless @status.local?
|
||||||
|
@ -62,6 +63,8 @@ class ProcessMentionsService < BaseService
|
||||||
"@#{mentioned_account.acct}"
|
"@#{mentioned_account.acct}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
process_mutual! if @limited_type == 'mutual'
|
||||||
|
|
||||||
@status.save! if @save_records
|
@status.save! if @save_records
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -92,4 +95,12 @@ class ProcessMentionsService < BaseService
|
||||||
def mention_undeliverable?(mentioned_account)
|
def mention_undeliverable?(mentioned_account)
|
||||||
mentioned_account.nil? || (!mentioned_account.local? && !mentioned_account.activitypub?)
|
mentioned_account.nil? || (!mentioned_account.local? && !mentioned_account.activitypub?)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def process_mutual!
|
||||||
|
mentioned_account_ids = @current_mentions.map(&:account_id)
|
||||||
|
|
||||||
|
@status.account.mutuals.find_each do |target_account|
|
||||||
|
@current_mentions << @status.mentions.new(silent: true, account: target_account) unless mentioned_account_ids.include?(target_account.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
12
db/migrate/20230812083752_create_status_capability_token.rb
Normal file
12
db/migrate/20230812083752_create_status_capability_token.rb
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CreateStatusCapabilityToken < ActiveRecord::Migration[7.0]
|
||||||
|
def change
|
||||||
|
create_table :status_capability_tokens do |t|
|
||||||
|
t.belongs_to :status, null: false, foreign_key: { on_delete: :cascade }
|
||||||
|
t.string :token
|
||||||
|
t.datetime :created_at, null: false
|
||||||
|
t.datetime :updated_at, null: false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
11
db/schema.rb
11
db/schema.rb
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.0].define(version: 2023_08_04_222017) do
|
ActiveRecord::Schema[7.0].define(version: 2023_08_12_083752) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
|
||||||
|
@ -1029,6 +1029,14 @@ ActiveRecord::Schema[7.0].define(version: 2023_08_04_222017) do
|
||||||
t.index ["var"], name: "index_site_uploads_on_var", unique: true
|
t.index ["var"], name: "index_site_uploads_on_var", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "status_capability_tokens", force: :cascade do |t|
|
||||||
|
t.bigint "status_id", null: false
|
||||||
|
t.string "token"
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["status_id"], name: "index_status_capability_tokens_on_status_id"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "status_edits", force: :cascade do |t|
|
create_table "status_edits", force: :cascade do |t|
|
||||||
t.bigint "status_id", null: false
|
t.bigint "status_id", null: false
|
||||||
t.bigint "account_id"
|
t.bigint "account_id"
|
||||||
|
@ -1395,6 +1403,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_08_04_222017) do
|
||||||
add_foreign_key "scheduled_statuses", "accounts", on_delete: :cascade
|
add_foreign_key "scheduled_statuses", "accounts", on_delete: :cascade
|
||||||
add_foreign_key "session_activations", "oauth_access_tokens", column: "access_token_id", name: "fk_957e5bda89", on_delete: :cascade
|
add_foreign_key "session_activations", "oauth_access_tokens", column: "access_token_id", name: "fk_957e5bda89", on_delete: :cascade
|
||||||
add_foreign_key "session_activations", "users", name: "fk_e5fda67334", on_delete: :cascade
|
add_foreign_key "session_activations", "users", name: "fk_e5fda67334", on_delete: :cascade
|
||||||
|
add_foreign_key "status_capability_tokens", "statuses", on_delete: :cascade
|
||||||
add_foreign_key "status_edits", "accounts", on_delete: :nullify
|
add_foreign_key "status_edits", "accounts", on_delete: :nullify
|
||||||
add_foreign_key "status_edits", "statuses", on_delete: :cascade
|
add_foreign_key "status_edits", "statuses", on_delete: :cascade
|
||||||
add_foreign_key "status_pins", "accounts", name: "fk_d4cb435b62", on_delete: :cascade
|
add_foreign_key "status_pins", "accounts", name: "fk_d4cb435b62", on_delete: :cascade
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue