diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index 1328a52f02a748..00237d4deab099 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -26,6 +26,7 @@ linters: - 'app/views/settings/preferences/other/show.html.haml' - 'app/views/settings/preferences/reaching/show.html.haml' - 'app/views/settings/profiles/show.html.haml' + - 'app/views/settings/privacy_extra/show.html.haml' # Offense count: 9 RuboCop: diff --git a/app/controllers/settings/preferences/other_controller.rb b/app/controllers/settings/preferences/other_controller.rb index 77b4fe10f832a9..02925fa6e57548 100644 --- a/app/controllers/settings/preferences/other_controller.rb +++ b/app/controllers/settings/preferences/other_controller.rb @@ -4,8 +4,8 @@ class Settings::Preferences::OtherController < Settings::Preferences::BaseContro include DtlHelper def show - @dtl_enabled = DTL_ENABLED - @dtl_tag = DTL_TAG + @dtl_enabled = dtl_enabled? + @dtl_tag = dtl_tag_name end private diff --git a/app/controllers/settings/privacy_extra_controller.rb b/app/controllers/settings/privacy_extra_controller.rb index 85364ec35d5632..49c71d50714f9a 100644 --- a/app/controllers/settings/privacy_extra_controller.rb +++ b/app/controllers/settings/privacy_extra_controller.rb @@ -18,7 +18,7 @@ def update private def account_params - params.require(:account).permit(:dissubscribable, settings: UserSettings.keys) + params.require(:account).permit(:subscription_policy, settings: UserSettings.keys) end def set_account diff --git a/app/helpers/dtl_helper.rb b/app/helpers/dtl_helper.rb index d3c3a8c6621d00..aa2c414c5f6167 100644 --- a/app/helpers/dtl_helper.rb +++ b/app/helpers/dtl_helper.rb @@ -1,6 +1,11 @@ # frozen_string_literal: true module DtlHelper - DTL_ENABLED = ENV.fetch('DTL_ENABLED', 'false') == 'true' - DTL_TAG = ENV.fetch('DTL_TAG', 'kmyblue') + def dtl_enabled? + ENV.fetch('DTL_ENABLED', 'false') == 'true' + end + + def dtl_tag_name + ENV.fetch('DTL_TAG', 'kmyblue') + end end diff --git a/app/lib/account_statuses_filter.rb b/app/lib/account_statuses_filter.rb index 4ff59756102896..8989c8fbf51538 100644 --- a/app/lib/account_statuses_filter.rb +++ b/app/lib/account_statuses_filter.rb @@ -44,7 +44,7 @@ def results private def initial_scope - if (suspended? || (domain_block&.reject_send_dissubscribable && @account.dissubscribable)) || domain_block&.reject_send_media || blocked? + if (suspended? || (domain_block&.reject_send_dissubscribable && !@account.all_subscribable?)) || domain_block&.reject_send_media || blocked? Status.none elsif anonymous? account.statuses.where(visibility: %i(public unlisted public_unlisted)) diff --git a/app/lib/activitypub/tag_manager.rb b/app/lib/activitypub/tag_manager.rb index f0322672803488..ef3bb8310d010a 100644 --- a/app/lib/activitypub/tag_manager.rb +++ b/app/lib/activitypub/tag_manager.rb @@ -236,7 +236,14 @@ def limited_scope(status) end def subscribable_by(account) - account.dissubscribable ? [] : [COLLECTIONS[:public]] + case account.subscription_policy + when :allow + [COLLECTIONS[:public]] + when :followers_only + [account_followers_url(account)] + else + [] + end end def searchable_by(status) diff --git a/app/lib/status_reach_finder.rb b/app/lib/status_reach_finder.rb index 08c625bbe769e3..8f20b94675122d 100644 --- a/app/lib/status_reach_finder.rb +++ b/app/lib/status_reach_finder.rb @@ -195,7 +195,7 @@ def banned_domains_of_status(status) blocks = DomainBlock.where(domain: nil) blocks = blocks.or(DomainBlock.where(reject_send_not_public_searchability: true)) if status.compute_searchability != 'public' blocks = blocks.or(DomainBlock.where(reject_send_public_unlisted: true)) if status.public_unlisted_visibility? - blocks = blocks.or(DomainBlock.where(reject_send_dissubscribable: true)) if status.account.dissubscribable + blocks = blocks.or(DomainBlock.where(reject_send_dissubscribable: true)) unless status.account.all_subscribable? blocks = blocks.or(DomainBlock.where(reject_send_media: true)) if status.with_media? blocks = blocks.or(DomainBlock.where(reject_send_sensitive: true)) if (status.with_media? && status.sensitive) || status.spoiler_text? blocks.pluck(:domain).uniq diff --git a/app/models/account.rb b/app/models/account.rb index b05ef3c1ad414d..f75cd0fddf8dd3 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -52,9 +52,9 @@ # requested_review_at :datetime # group_allow_private_message :boolean # searchability :integer default("direct"), not null -# dissubscribable :boolean default(FALSE), not null # settings :jsonb # indexable :boolean default(FALSE), not null +# master_settings :jsonb # class Account < ApplicationRecord @@ -88,6 +88,7 @@ class Account < ApplicationRecord include AccountSearch include AccountStatusesSearch include AccountOtherSettings + include AccountMasterSettings enum protocol: { ostatus: 0, activitypub: 1 } enum suspension_origin: { local: 0, remote: 1 }, _prefix: true diff --git a/app/models/concerns/account_master_settings.rb b/app/models/concerns/account_master_settings.rb new file mode 100644 index 00000000000000..fef64304536eed --- /dev/null +++ b/app/models/concerns/account_master_settings.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module AccountMasterSettings + extend ActiveSupport::Concern + + included do + def subscription_policy + return master_settings['subscription_policy']&.to_sym || :allow if master_settings.present? + + # allow, followers_only, block + :allow + end + + def subscription_policy=(val) + self.master_settings = (master_settings.nil? ? {} : master_settings).merge({ 'subscription_policy' => val }) + end + + def all_subscribable? + subscription_policy == :allow + end + + def public_master_settings + { + 'subscription_policy' => subscription_policy, + } + end + end +end diff --git a/app/models/concerns/account_other_settings.rb b/app/models/concerns/account_other_settings.rb index a351fce1e31ce3..7f9b4857271c1b 100644 --- a/app/models/concerns/account_other_settings.rb +++ b/app/models/concerns/account_other_settings.rb @@ -104,7 +104,7 @@ def public_settings end def public_settings_for_local - public_settings + public_settings.merge(public_master_settings) end end end diff --git a/app/models/status.rb b/app/models/status.rb index 25fcedb15616a9..7f0b86163b166a 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -320,7 +320,7 @@ def reported? end def dtl? - tags.where(name: DTL_TAG).exists? + (%w(public public_unlisted login).include?(visibility) || (unlisted_visibility? && public_searchability?)) && tags.where(name: dtl_tag_name).exists? end def emojis diff --git a/app/policies/status_policy.rb b/app/policies/status_policy.rb index b30d48e37443e0..41d242e0888b70 100644 --- a/app/policies/status_policy.rb +++ b/app/policies/status_policy.rb @@ -131,7 +131,7 @@ def server_blocking_domain_of_status?(status) else (@domain_block.reject_send_not_public_searchability && status.compute_searchability != 'public') || (@domain_block.reject_send_public_unlisted && status.public_unlisted_visibility?) || - (@domain_block.reject_send_dissubscribable && status.account.dissubscribable) || + (@domain_block.reject_send_dissubscribable && !status.account.all_subscribable?) || (@domain_block.detect_invalid_subscription && status.public_unlisted_visibility? && status.account.user&.setting_reject_public_unlisted_subscription) || (@domain_block.detect_invalid_subscription && status.public_visibility? && status.account.user&.setting_reject_unlisted_subscription) || (@domain_block.reject_send_media && status.with_media?) || diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index 8b6e61a151253d..c064ac23ddfbc0 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -36,7 +36,7 @@ def meta trends_as_landing_page: Setting.trends_as_landing_page, status_page_url: Setting.status_page_url, sso_redirect: sso_redirect, - dtl_tag: DTL_ENABLED ? DTL_TAG : nil, + dtl_tag: dtl_enabled? ? dtl_tag_name : nil, enable_local_privacy: Setting.enable_public_unlisted_visibility, enable_local_timeline: Setting.enable_local_timeline, } diff --git a/app/serializers/rest/account_serializer.rb b/app/serializers/rest/account_serializer.rb index e9aa04bb43176e..e5af3727fac4a9 100644 --- a/app/serializers/rest/account_serializer.rb +++ b/app/serializers/rest/account_serializer.rb @@ -112,7 +112,7 @@ def discoverable end def subscribable - !object.dissubscribable + object.all_subscribable? end def moved_to_account diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index 473b2cabd33be5..f3b1c846158f28 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -83,7 +83,6 @@ def create_account @account.suspension_origin = :local if auto_suspend? @account.silenced_at = domain_block.created_at if auto_silence? @account.searchability = :direct # not null - @account.dissubscribable = false # not null set_immediate_protocol_attributes! @@ -125,8 +124,8 @@ def set_immediate_attributes! @account.discoverable = @json['discoverable'] || false @account.indexable = @json['indexable'] || false @account.searchability = searchability_from_audience - @account.dissubscribable = !subscribable(@account.note) @account.settings = other_settings + @account.master_settings = (@account.master_settings || {}).merge(master_settings(@account.note)) @account.memorial = @json['memorial'] || false end @@ -320,14 +319,24 @@ def subscribable_by @subscribable_by = as_array(@json['subscribableBy']).map { |x| value_or_id(x) } end - def subscribable(note) + def subscription_policy(note) if subscribable_by.nil? - note.exclude?('[subscribable:no]') + note.include?('[subscribable:no]') ? :block : :allow + elsif subscribable_by.any? { |uri| ActivityPub::TagManager.instance.public_collection?(uri) } + :allow + elsif subscribable_by.include?(@account.followers_url) + :followers_only else - subscribable_by.any? { |uri| ActivityPub::TagManager.instance.public_collection?(uri) } + :block end end + def master_settings(note) + { + 'subscription_policy' => subscription_policy(note), + } + end + def other_settings return {} unless @json['otherSetting'].is_a?(Array) diff --git a/app/services/delivery_antenna_service.rb b/app/services/delivery_antenna_service.rb index f17b7815984518..d0491b2a36f9e3 100644 --- a/app/services/delivery_antenna_service.rb +++ b/app/services/delivery_antenna_service.rb @@ -23,8 +23,10 @@ def call(status, update, **options) private def delivery! - must_dtl_tag = @account.dissubscribable - return if must_dtl_tag && !DTL_ENABLED + subscription_policy = @account.subscription_policy + + dtl_post = @status.dtl? && dtl_enabled? + return if subscription_policy == :block && (!dtl_post || !@account.user&.setting_dtl_force_subscribable) tag_ids = @status.tags.pluck(:id) domain = @account.domain @@ -38,8 +40,8 @@ def delivery! antennas = antennas.left_joins(:antenna_accounts).where(any_accounts: true).or(Antenna.left_joins(:antenna_accounts).where(antenna_accounts: { account: @account })) antennas = Antenna.where(id: antennas.select(:id)) - if must_dtl_tag - dtl_tag = Tag.find_or_create_by_names(DTL_TAG).first + if subscription_policy == :block + dtl_tag = Tag.find_or_create_by_names(dtl_tag_name).first return if !dtl_tag || tag_ids.exclude?(dtl_tag.id) antennas = antennas.left_joins(:antenna_tags).where(antenna_tags: { tag_id: dtl_tag.id }) @@ -125,9 +127,9 @@ def delivery_ltl! def followers_only? case @status.visibility.to_sym when :public, :public_unlisted, :login, :limited - false + @status.account.subscription_policy == :followers_only when :unlisted - @status.compute_searchability != 'public' + @status.compute_searchability != 'public' || @status.account.subscription_policy == :followers_only else true end diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index 2d4a5d79256a48..99f3fa4bd3cf26 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -53,12 +53,12 @@ def fan_out_to_local_recipients! when :public, :unlisted, :public_unlisted, :login, :private deliver_to_all_followers! deliver_to_lists! - deliver_to_antennas! if !@account.dissubscribable || (@status.dtl? && DTL_ENABLED && @account.user&.setting_dtl_force_subscribable && @status.tags.exists?(name: DTL_TAG)) + deliver_to_antennas! deliver_to_stl_antennas! if Setting.enable_local_timeline deliver_to_ltl_antennas! if Setting.enable_local_timeline when :limited deliver_to_lists_mentioned_accounts_only! - deliver_to_antennas! unless @account.dissubscribable + deliver_to_antennas! deliver_to_stl_antennas! if Setting.enable_local_timeline deliver_to_mentioned_followers! else diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 57ca1b940fc92c..cec0737273e184 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -105,10 +105,10 @@ def load_circle end def overwrite_dtl_post - return unless DTL_ENABLED + return unless dtl_enabled? raw_tags = Extractor.extract_hashtags(@text) - return if raw_tags.exclude?(DTL_TAG) + return if raw_tags.exclude?(dtl_tag_name) return unless %i(public public_unlisted unlisted).include?(@visibility) @visibility = @account.user&.setting_dtl_force_visibility if %i(public public_unlisted unlisted).include?(@account.user&.setting_dtl_force_visibility) diff --git a/app/views/settings/privacy_extra/show.html.haml b/app/views/settings/privacy_extra/show.html.haml index 5e102f6565b988..2d0927eeb24ded 100644 --- a/app/views/settings/privacy_extra/show.html.haml +++ b/app/views/settings/privacy_extra/show.html.haml @@ -22,7 +22,7 @@ = ff.input :link_preview, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_link_preview'), hint: I18n.t('simple_form.hints.defaults.setting_link_preview') .fields-group - = f.input :dissubscribable, as: :boolean, wrapper: :with_label, kmyblue: true, hint: t('simple_form.hints.defaults.dissubscribable') + = f.input :subscription_policy, kmyblue: true, collection: %w(allow followers_only block), label_method: ->(item) { safe_join([t("simple_form.labels.subscription_policy.#{item}")]) }, as: :radio_buttons, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li', wrapper: :with_floating_label, label: t('simple_form.labels.defaults.subscription_policy'), hint: t('simple_form.hints.defaults.subscription_policy') .fields-group = ff.input :allow_quote, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_allow_quote'), hint: false diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 6f7df0bb840b2a..6f0705e816ee31 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -47,7 +47,6 @@ en: digest: Only sent after a long period of inactivity and only if you have received any personal messages in your absence discoverable: Allow your account to be discovered by strangers through recommendations, trends and other features discoverable_local: Disable the setting on federated servers. The setting is available this server only for avoiding full-text search on other servers - dissubscribable: Your post is not picked by antenna email: You will be sent a confirmation e-mail group: Reps sent to this account will be automatically BT'd and distributed to all accounts you follow! group_allow_private_message: Posts are duplicated and cannot be edited or deleted by the post @@ -76,6 +75,7 @@ en: setting_single_ref_to_quote: If this server does not have target post, target server maybe cannot read your quote setting_use_blurhash: Gradients are based on the colors of the hidden visuals but obfuscate any details setting_use_pending_items: Hide timeline updates behind a click instead of automatically scrolling the feed + subscription_policy: Your post is not picked by antenna username: You can use letters, numbers, and underscores whole_word: When the keyword or phrase is alphanumeric only, it will only be applied if it matches the whole word domain_allow: @@ -143,6 +143,10 @@ en: settings: indexable: Your profile page may appear in search results on Google, Bing, and others. show_application: You will always be able to see which app published your post regardless. + subscription_policy: + allow: Allow + block: Block + followers_only: Followers only tag: name: You can only change the casing of the letters, for example, to make it more readable user: @@ -208,7 +212,6 @@ en: discoverable: Suggest account to others discoverable_local: Disallow suggesting account on other servers display_name: Display name - dissubscribable: Reject any subscriptions email: E-mail address expires_in: Expire after fields: Extra fields @@ -249,7 +252,7 @@ en: setting_display_media_expand: Show more medias setting_display_media_hide_all: Hide all setting_display_media_show_all: Show all - setting_dtl_force_subscribable: Ignore your dissubscribable setting when using the DTL tag + setting_dtl_force_subscribable: Ignore your subscribability setting when using the DTL tag setting_dtl_force_searchability: Post searchability setting_dtl_force_visibility: Post visibility setting_emoji_reaction_streaming_notify_impl2: Enable stamp notification compat with Nyastodon, Catstodon, glitch-soc @@ -298,6 +301,7 @@ en: setting_use_public_index: Include permitted accounts post to results of search severity: Severity sign_in_token_attempt: Security code + subscription_policy: Subscribability title: Title type: Import type username: Username diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml index 80b493b3a369f9..3229fb13e5e166 100644 --- a/config/locales/simple_form.ja.yml +++ b/config/locales/simple_form.ja.yml @@ -49,7 +49,6 @@ ja: digest: 長期間使用していない場合と不在時に返信を受けた場合のみ送信されます discoverable: レコメンド、トレンド、その他の機能により、あなたのアカウントを他の人から見つけられるようにします。なおkmyblueのローカルユーザーはこの設定をオンにしても全文検索結果には掲載されません。全文検索結果への掲載には、投稿の検索許可設定を変更する必要があります discoverable_local: 上記設定を当サーバー内でのみ適用するようにします。他のサーバーの全文検索結果への掲載を回避できますが、レコメンド、トレンドなどその他の機能への掲載も回避されます - dissubscribable: あなたの投稿はすべてのアンテナに掲載されなくなります。Misskeyのアンテナを拒否することはできません。Mastodonの一部のサーバーもこの設定に対応しますが、挙動が一部kmyblueと異なる場合があります email: 確認のメールが送信されます group: このアカウントに送られたメンションは自動でBTされ、フォローしている全てのアカウントに配信されます group_allow_private_message: 投稿は複製されるため、投稿者が編集・削除することはできません @@ -90,6 +89,7 @@ ja: setting_unsafe_limited_distribution: Mastodon 3.5、4.0、4.1のサーバーにも限定投稿(相互のみ)が届くようになりますが、安全でない方法で送信します setting_use_blurhash: ぼかしはメディアの色を元に生成されますが、細部は見えにくくなっています setting_use_pending_items: 新着があってもタイムラインを自動的にスクロールしないようにします + subscription_policy: あなたの投稿はこの設定の範囲外にあるアカウントのアンテナに掲載されなくなります。Misskeyのアンテナを拒否することはできません。Mastodonの一部のサーバーもこの設定に対応しますが、挙動が一部kmyblueと異なる場合があります username: アルファベット大文字と小文字、数字、アンダーバー「_」が使えます whole_word: キーワードまたはフレーズが英数字のみの場合、単語全体と一致する場合のみ適用されるようになります domain_allow: @@ -222,7 +222,6 @@ ja: discoverable: ディレクトリに掲載する discoverable_local: 他サーバーのディレクトリに掲載しない display_name: 表示名 - dissubscribable: 購読を拒否する email: メールアドレス expires_in: 有効期限 fields: プロフィール補足情報 @@ -314,6 +313,7 @@ ja: setting_use_public_index: Mastodonの標準設定によって検索が許可されたアカウントの公開投稿を検索結果に含める severity: 重大性 sign_in_token_attempt: セキュリティコード + subscription_policy: 購読許可 title: タイトル type: インポートする項目 username: ユーザー名 @@ -420,6 +420,10 @@ ja: settings: indexable: 検索エンジンからアクセスできるようにする show_application: 投稿に使ったアプリを開示する + subscription_policy: + allow: 全員に許可 + block: 全員拒否 + followers_only: フォロワーにのみ許可 tag: listable: 検索とディレクトリへの使用を許可する name: ハッシュタグ diff --git a/db/migrate/20231105225839_add_master_settings_to_accounts.rb b/db/migrate/20231105225839_add_master_settings_to_accounts.rb new file mode 100644 index 00000000000000..7adbdf5493f12c --- /dev/null +++ b/db/migrate/20231105225839_add_master_settings_to_accounts.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require Rails.root.join('lib', 'mastodon', 'migration_helpers') + +class AddMasterSettingsToAccounts < ActiveRecord::Migration[7.1] + include Mastodon::MigrationHelpers + + disable_ddl_transaction! + + class Account < ApplicationRecord; end + + def up + safety_assured do + add_column :accounts, :master_settings, :jsonb + + Account.transaction do + Account.find_in_batches do |accounts| + accounts.each do |account| + account.update(master_settings: { 'subscription_policy' => account.dissubscribable ? 'block' : 'allow' }) + end + end + end + + remove_column :accounts, :dissubscribable + end + end + + def down + safety_assured do + add_column_with_default :accounts, :dissubscribable, :boolean, default: false, allow_null: false + + Account.transaction do + Account.find_in_batches do |accounts| + accounts.each do |account| + account.update(dissubscribable: account.master_settings.present? && account.master_settings['subscription_policy'] != 'allow') + end + end + end + + remove_column :accounts, :master_settings + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 6bd862ac0233ca..4850882e11557a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2023_10_28_005948) do +ActiveRecord::Schema[7.1].define(version: 2023_11_05_225839) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -200,9 +200,9 @@ t.datetime "requested_review_at", precision: nil t.boolean "group_allow_private_message" t.integer "searchability", default: 2, null: false - t.boolean "dissubscribable", default: false, null: false t.jsonb "settings" t.boolean "indexable", default: false, null: false + t.jsonb "master_settings" t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin t.index "lower((username)::text), COALESCE(lower((domain)::text), ''::text)", name: "index_accounts_on_username_and_domain_lower", unique: true t.index ["domain", "id"], name: "index_accounts_on_domain_and_id" diff --git a/spec/lib/status_reach_finder_spec.rb b/spec/lib/status_reach_finder_spec.rb index 8b333de0c4d614..d176483490b2d6 100644 --- a/spec/lib/status_reach_finder_spec.rb +++ b/spec/lib/status_reach_finder_spec.rb @@ -381,7 +381,7 @@ let(:dissubscribable) { false } let(:spoiler_text) { '' } let(:status) { Fabricate(:status, account: alice, visibility: visibility, searchability: searchability, spoiler_text: spoiler_text) } - let(:alice) { Fabricate(:account, username: 'alice', dissubscribable: dissubscribable) } + let(:alice) { Fabricate(:account, username: 'alice', master_settings: { subscription_policy: dissubscribable ? 'block' : 'allow' }) } let(:bob) { Fabricate(:account, username: 'bob', domain: 'example.com', protocol: :activitypub, uri: 'https://example.com/', inbox_url: 'https://example.com/inbox') } let(:tom) { Fabricate(:account, username: 'tom', domain: 'tom.com', protocol: :activitypub, uri: 'https://tom.com/', inbox_url: 'https://tom.com/inbox') } diff --git a/spec/services/activitypub/process_account_service_spec.rb b/spec/services/activitypub/process_account_service_spec.rb index 8955084879716f..40644db5091ac7 100644 --- a/spec/services/activitypub/process_account_service_spec.rb +++ b/spec/services/activitypub/process_account_service_spec.rb @@ -156,6 +156,71 @@ end end + context 'with subscription policy' do + subject { described_class.new.call('alice', 'example.com', payload) } + + let(:subscribable_by) { 'https://www.w3.org/ns/activitystreams#Public' } + let(:sender_bio) { '' } + let(:payload) do + { + id: 'https://foo.test', + type: 'Actor', + inbox: 'https://foo.test/inbox', + followers: 'https://example.com/followers', + subscribableBy: subscribable_by, + summary: sender_bio, + actor_type: 'Person', + }.with_indifferent_access + end + + before do + stub_request(:get, 'https://example.com/.well-known/nodeinfo').to_return(body: '{}') + stub_request(:get, 'https://example.com/followers').to_return(body: '[]') + end + + context 'when public' do + it 'subscription policy is allow' do + expect(subject.subscription_policy.to_s).to eq 'allow' + end + end + + context 'when private' do + let(:subscribable_by) { 'https://example.com/followers' } + + it 'subscription policy is followers_only' do + expect(subject.subscription_policy.to_s).to eq 'followers_only' + end + end + + context 'when empty' do + let(:subscribable_by) { '' } + + it 'subscription policy is block' do + expect(subject.subscription_policy.to_s).to eq 'block' + end + end + + context 'when default value' do + let(:subscribable_by) { nil } + + it 'subscription policy is allow' do + expect(subject.subscription_policy.to_s).to eq 'allow' + end + end + + context 'with bio' do + let(:subscribable_by) { nil } + + context 'with no-subscribe' do + let(:sender_bio) { '[subscribable:no]' } + + it 'subscription policy is block' do + expect(subject.subscription_policy.to_s).to eq 'block' + end + end + end + end + context 'with property values' do let(:payload) do { diff --git a/spec/services/fan_out_on_write_service_spec.rb b/spec/services/fan_out_on_write_service_spec.rb index e7339e15744027..9bcc67292f4ebc 100644 --- a/spec/services/fan_out_on_write_service_spec.rb +++ b/spec/services/fan_out_on_write_service_spec.rb @@ -10,10 +10,10 @@ let(:last_active_at) { Time.now.utc } let(:visibility) { 'public' } let(:searchability) { 'public' } - let(:dissubscribable) { false } + let(:subscription_policy) { :allow } let(:status) { Fabricate(:status, account: alice, visibility: visibility, searchability: searchability, text: 'Hello @bob #hoge') } - let!(:alice) { Fabricate(:user, current_sign_in_at: last_active_at, account_attributes: { dissubscribable: dissubscribable }).account } + let!(:alice) { Fabricate(:user, current_sign_in_at: last_active_at, account_attributes: { master_settings: { subscription_policy: subscription_policy } }).account } let!(:bob) { Fabricate(:user, current_sign_in_at: last_active_at, account_attributes: { username: 'bob' }).account } let!(:tom) { Fabricate(:user, current_sign_in_at: last_active_at).account } let!(:ohagi) { Fabricate(:user, current_sign_in_at: last_active_at).account } @@ -64,6 +64,13 @@ def antenna_with_account(owner, target_account) antenna end + def antenna_with_tag(owner, target_tag, **options) + antenna = Fabricate(:antenna, account: owner, any_tags: false, **options) + tag = Tag.find_or_create_by_names([target_tag])[0] + Fabricate(:antenna_tag, antenna: antenna, tag: tag) + antenna + end + def antenna_with_options(owner, **options) Fabricate(:antenna, account: owner, **options) end @@ -123,13 +130,69 @@ def antenna_with_options(owner, **options) expect(antenna_feed_of(empty_antenna)).to_not include status.id end - context 'when dissubscribable is true' do - let(:dissubscribable) { true } + context 'when subscription is blocked' do + let(:subscription_policy) { :block } it 'is not added to the antenna feed' do expect(antenna_feed_of(antenna)).to_not include status.id end end + + context 'when subscription is allowed followers only' do + let(:subscription_policy) { :followers_only } + let!(:antenna) { antenna_with_account(ohagi, alice) } + + it 'is not added to the antenna feed' do + expect(antenna_feed_of(antenna)).to_not include status.id + end + + context 'with following' do + let!(:antenna) { antenna_with_account(bob, alice) } + + it 'is added to the antenna feed' do + expect(antenna_feed_of(antenna)).to include status.id + end + end + end + + context 'when dtl post' do + let!(:antenna) { antenna_with_tag(bob, 'hoge') } + + around do |example| + ClimateControl.modify DTL_ENABLED: 'true', DTL_TAG: 'hoge' do + example.run + end + end + + context 'with listening tag' do + it 'is added to the antenna feed' do + expect(antenna_feed_of(antenna)).to include status.id + end + end + + context 'with listening tag but sender is limiting subscription' do + let(:subscription_policy) { :block } + + it 'does not add to the antenna feed' do + expect(antenna_feed_of(antenna)).to_not include status.id + end + end + + context 'with listening tag but sender is limiting subscription but permit dtl only' do + let(:subscription_policy) { :block } + let(:custom_before) { true } + + before do + alice.user.settings['dtl_force_subscribable'] = true + alice.user.save! + subject.call(status) + end + + it 'is added to the antenna feed' do + expect(antenna_feed_of(antenna)).to include status.id + end + end + end end context 'with STL antenna' do @@ -141,8 +204,8 @@ def antenna_with_options(owner, **options) expect(antenna_feed_of(empty_antenna)).to_not include status.id end - context 'when dissubscribable is true' do - let(:dissubscribable) { true } + context 'when subscription is blocked' do + let(:subscription_policy) { :block } it 'is added to the antenna feed' do expect(antenna_feed_of(antenna)).to include status.id @@ -168,8 +231,8 @@ def antenna_with_options(owner, **options) expect(antenna_feed_of(empty_antenna)).to_not include status.id end - context 'when dissubscribable is true' do - let(:dissubscribable) { true } + context 'when subscription is blocked' do + let(:subscription_policy) { :block } it 'is added to the antenna feed' do expect(antenna_feed_of(antenna)).to include status.id @@ -370,8 +433,8 @@ def antenna_with_options(owner, **options) expect(antenna_feed_of(empty_antenna)).to_not include status.id end - context 'when dissubscribable is true' do - let(:dissubscribable) { true } + context 'when subscription is blocked' do + let(:subscription_policy) { :block } it 'is not added to the antenna feed' do expect(antenna_feed_of(antenna)).to_not include status.id @@ -388,8 +451,8 @@ def antenna_with_options(owner, **options) expect(antenna_feed_of(empty_antenna)).to_not include status.id end - context 'when dissubscribable is true' do - let(:dissubscribable) { true } + context 'when subscription is blocked' do + let(:subscription_policy) { :block } it 'is added to the antenna feed' do expect(antenna_feed_of(antenna)).to include status.id @@ -415,8 +478,8 @@ def antenna_with_options(owner, **options) expect(antenna_feed_of(empty_antenna)).to_not include status.id end - context 'when dissubscribable is true' do - let(:dissubscribable) { true } + context 'when subscription is blocked' do + let(:subscription_policy) { :block } it 'is added to the antenna feed' do expect(antenna_feed_of(antenna)).to include status.id