Merge branch 'kb_development' into kb_migration

This commit is contained in:
KMY 2023-08-09 08:53:06 +09:00
commit 6cfe78d51c
22 changed files with 541 additions and 32 deletions

View file

@ -1,5 +1,7 @@
# kmyblue # kmyblue
[![Ruby Testing](https://github.com/kmycode/mastodon/actions/workflows/test-ruby.yml/badge.svg)](https://github.com/kmycode/mastodon/actions/workflows/test-ruby.yml)
kmyblue は[Mastodon](https://github.com/mastodon/mastodon)のフォークです。創作作家のための Mastodon を目指して開発しました。 kmyblue は[Mastodon](https://github.com/mastodon/mastodon)のフォークです。創作作家のための Mastodon を目指して開発しました。
kmyblue はフォーク名であり、同時に[サーバー名](https://kmy.blue)でもあります。以下は特に記述がない限り、フォークとしての kmyblue をさします。 kmyblue はフォーク名であり、同時に[サーバー名](https://kmy.blue)でもあります。以下は特に記述がない限り、フォークとしての kmyblue をさします。

View file

@ -514,7 +514,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
note = @account&.note note = @account&.note
return nil if note.blank? return nil if note.blank?
searchability_bio = note.scan(SCAN_SEARCHABILITY_RE).first || note.scan(SCAN_SEARCHABILITY_FEDIBIRD_RE).first searchability_bio = note.scan(SCAN_SEARCHABILITY_FEDIBIRD_RE).first || note.scan(SCAN_SEARCHABILITY_RE).first
return nil unless searchability_bio return nil unless searchability_bio
searchability = searchability_bio[0] searchability = searchability_bio[0]

View file

@ -40,7 +40,7 @@ class CustomEmoji < ApplicationRecord
belongs_to :category, class_name: 'CustomEmojiCategory', optional: true belongs_to :category, class_name: 'CustomEmojiCategory', optional: true
has_one :local_counterpart, -> { where(domain: nil) }, class_name: 'CustomEmoji', primary_key: :shortcode, foreign_key: :shortcode, inverse_of: false has_one :local_counterpart, -> { where(domain: nil) }, class_name: 'CustomEmoji', primary_key: :shortcode, foreign_key: :shortcode, inverse_of: false
has_many :emoji_reactions, inverse_of: :custom_emoji has_many :emoji_reactions, inverse_of: :custom_emoji, dependent: :destroy
has_attached_file :image, styles: { static: { format: 'png', convert_options: '-coalesce +profile "!icc,*" +set modify-date +set create-date' } }, validate_media_type: false has_attached_file :image, styles: { static: { format: 'png', convert_options: '-coalesce +profile "!icc,*" +set modify-date +set create-date' } }, validate_media_type: false

View file

@ -19,13 +19,12 @@ class PublicFeed
# @param [Integer] min_id # @param [Integer] min_id
# @return [Array<Status>] # @return [Array<Status>]
def get(limit, max_id = nil, since_id = nil, min_id = nil) def get(limit, max_id = nil, since_id = nil, min_id = nil)
scope = public_scope scope = local_only? ? public_scope : global_timeline_only_scope
scope.merge!(without_replies_scope) unless with_replies? scope.merge!(without_replies_scope) unless with_replies?
scope.merge!(without_reblogs_scope) unless with_reblogs? scope.merge!(without_reblogs_scope) unless with_reblogs?
scope.merge!(local_only_scope) if local_only? scope.merge!(local_only_scope) if local_only?
scope.merge!(remote_only_scope) if remote_only? || hide_local_users? scope.merge!(remote_only_scope) if remote_only? || hide_local_users?
scope.merge!(global_timeline_only_scope) if global_timeline?
scope.merge!(account_filters_scope) if account? scope.merge!(account_filters_scope) if account?
scope.merge!(media_only_scope) if media_only? scope.merge!(media_only_scope) if media_only?
scope.merge!(language_scope) if account&.chosen_languages.present? scope.merge!(language_scope) if account&.chosen_languages.present?
@ -58,10 +57,6 @@ class PublicFeed
@account.nil? && Setting.hide_local_users_for_anonymous @account.nil? && Setting.hide_local_users_for_anonymous
end end
def global_timeline?
!options[:remote] && !options[:local]
end
def account? def account?
account.present? account.present?
end end
@ -74,6 +69,10 @@ class PublicFeed
Status.with_public_visibility.joins(:account).merge(Account.without_suspended.without_silenced) Status.with_public_visibility.joins(:account).merge(Account.without_suspended.without_silenced)
end end
def global_timeline_only_scope
Status.with_global_timeline_visibility.joins(:account).merge(Account.without_suspended.without_silenced)
end
def public_search_scope def public_search_scope
Status.with_public_search_visibility.joins(:account).merge(Account.without_suspended.without_silenced) Status.with_public_search_visibility.joins(:account).merge(Account.without_suspended.without_silenced)
end end
@ -86,10 +85,6 @@ class PublicFeed
Status.remote Status.remote
end end
def global_timeline_only_scope
Status.with_global_timeline_visibility.joins(:account).merge(Account.without_suspended.without_silenced)
end
def without_replies_scope def without_replies_scope
Status.without_replies Status.without_replies
end end
@ -107,7 +102,7 @@ class PublicFeed
end end
def anonymous_scope def anonymous_scope
Status.where(visibility: [:public, :public_unlisted]) local_only? ? Status.where(visibility: [:public, :public_unlisted]) : Status.where(visibility: :public)
end end
def account_filters_scope def account_filters_scope

View file

@ -106,7 +106,9 @@ class Trends::Statuses < Trends::Base
private private
def eligible?(status) def eligible?(status)
(status.searchability.nil? || status.public_searchability?) && (status.public_visibility? || status.public_unlisted_visibility?) && status.account.discoverable? && !status.account.silenced? && status.spoiler_text.blank? && (!status.sensitive? || status.media_attachments.none?) && !status.reply? && valid_locale?(status.language) (status.searchability.nil? || status.public_searchability?) && (status.public_visibility? || status.public_unlisted_visibility?) &&
status.account.discoverable? && !status.account.silenced? && status.spoiler_text.blank? && (!status.sensitive? || status.media_attachments.none?) &&
!status.reply? && valid_locale?(status.language)
end end
def calculate_scores(statuses, at_time) def calculate_scores(statuses, at_time)

View file

@ -269,9 +269,11 @@ class ActivityPub::ProcessAccountService < BaseService
if audience_searchable_by.any? { |uri| ActivityPub::TagManager.instance.public_collection?(uri) } if audience_searchable_by.any? { |uri| ActivityPub::TagManager.instance.public_collection?(uri) }
:public :public
elsif audience_searchable_by.include?(@account.followers_url) elsif audience_searchable_by.include?(@account.followers_url)
:private # Followers only in kmyblue (generics: private) :private
elsif audience_searchable_by.include?('as:Limited')
:limited
else else
:direct # Reaction only in kmyblue (generics: direct) :direct
end end
end end
@ -279,7 +281,7 @@ class ActivityPub::ProcessAccountService < BaseService
note = @json['summary'] || '' note = @json['summary'] || ''
return nil if note.blank? return nil if note.blank?
searchability_bio = note.scan(SCAN_SEARCHABILITY_RE).first || note.scan(SCAN_SEARCHABILITY_FEDIBIRD_RE).first searchability_bio = note.scan(SCAN_SEARCHABILITY_FEDIBIRD_RE).first || note.scan(SCAN_SEARCHABILITY_RE).first
return nil unless searchability_bio return nil unless searchability_bio
searchability = searchability_bio[0] searchability = searchability_bio[0]

View file

@ -39,11 +39,13 @@ class UpdateStatusService < BaseService
queue_poll_notifications! queue_poll_notifications!
reset_preview_card! reset_preview_card!
update_references!
update_metadata! update_metadata!
update_references!
broadcast_updates! broadcast_updates!
# Mentions are not updated (Cause unknown)
@status.reload @status.reload
@status @status
rescue NoChangesSubmittedError rescue NoChangesSubmittedError
# For calls that result in no changes, swallow the error # For calls that result in no changes, swallow the error
@ -165,7 +167,6 @@ class UpdateStatusService < BaseService
def update_metadata! def update_metadata!
ProcessHashtagsService.new.call(@status) ProcessHashtagsService.new.call(@status)
ProcessMentionsService.new.call(@status) ProcessMentionsService.new.call(@status)
ProcessReferencesWorker.perform_async(@status.id, (@options[:status_reference_ids] || []).map(&:to_i).filter(&:positive?), [])
end end
def broadcast_updates! def broadcast_updates!

View file

@ -38,7 +38,7 @@
= yield :header_tags = yield :header_tags
%script{ src="https://www.googletagmanager.com/gtag/js?id=AW-11130587137" async } %script{ 'src' => "https://www.googletagmanager.com/gtag/js?id=AW-11130587137", 'async' => true }
:javascript :javascript
window.dataLayer = window.dataLayer || []; window.dataLayer = window.dataLayer || [];

View file

@ -4,6 +4,8 @@ class DowncaseCustomEmojiDomains < ActiveRecord::Migration[5.2]
disable_ddl_transaction! disable_ddl_transaction!
def up def up
CustomEmoji.connection.execute('CREATE TABLE IF NOT EXISTS emoji_reactions (id integer, custom_emoji_id integer, created_at timestamp NOT NULL, updated_at timestamp NOT NULL)')
duplicates = CustomEmoji.connection.select_all('SELECT string_agg(id::text, \',\') AS ids FROM custom_emojis GROUP BY shortcode, lower(domain) HAVING count(*) > 1').to_ary duplicates = CustomEmoji.connection.select_all('SELECT string_agg(id::text, \',\') AS ids FROM custom_emojis GROUP BY shortcode, lower(domain) HAVING count(*) > 1').to_ary
duplicates.each do |row| duplicates.each do |row|
@ -11,6 +13,8 @@ class DowncaseCustomEmojiDomains < ActiveRecord::Migration[5.2]
end end
CustomEmoji.in_batches.update_all('domain = lower(domain)') CustomEmoji.in_batches.update_all('domain = lower(domain)')
CustomEmoji.connection.execute('DROP TABLE IF EXISTS emoji_reactions')
end end
def down; end def down; end

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
Fabricator(:instance_info) do
domain 'info.example.com'
software 'mastodon'
version '4.1.0'
end

View file

@ -3,7 +3,8 @@
require 'rails_helper' require 'rails_helper'
RSpec.describe ActivityPub::Activity::Create do RSpec.describe ActivityPub::Activity::Create do
let(:sender) { Fabricate(:account, followers_url: 'http://example.com/followers', domain: 'example.com', uri: 'https://example.com/actor') } let(:sender_bio) { '' }
let(:sender) { Fabricate(:account, followers_url: 'http://example.com/followers', domain: 'example.com', uri: 'https://example.com/actor', note: sender_bio) }
let(:json) do let(:json) do
{ {
@ -27,7 +28,10 @@ RSpec.describe ActivityPub::Activity::Create do
context 'when fetching' do context 'when fetching' do
subject { described_class.new(json, sender) } subject { described_class.new(json, sender) }
let(:sender_software) { 'mastodon' }
before do before do
Fabricate(:instance_info, domain: 'example.com', software: sender_software)
subject.perform subject.perform
end end
@ -318,6 +322,219 @@ RSpec.describe ActivityPub::Activity::Create do
end end
end end
context 'when searchability' do
let(:searchable_by) { 'https://www.w3.org/ns/activitystreams#Public' }
let(:object_json) do
{
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
to: 'https://www.w3.org/ns/activitystreams#Public',
searchableBy: searchable_by,
}
end
context 'with explicit public address' do
it 'create status' do
status = sender.statuses.first
expect(status).to_not be_nil
expect(status.searchability).to eq 'public'
end
end
context 'with public with as:Public' do
let(:searchable_by) { 'as:Public' }
it 'create status' do
status = sender.statuses.first
expect(status).to_not be_nil
expect(status.searchability).to eq 'public'
end
end
context 'with public with Public' do
let(:searchable_by) { 'Public' }
it 'create status' do
status = sender.statuses.first
expect(status).to_not be_nil
expect(status.searchability).to eq 'public'
end
end
context 'with private' do
let(:searchable_by) { 'http://example.com/followers' }
it 'create status' do
status = sender.statuses.first
expect(status).to_not be_nil
expect(status.searchability).to eq 'private'
end
end
context 'with direct' do
let(:searchable_by) { '' }
it 'create status' do
status = sender.statuses.first
expect(status).to_not be_nil
expect(status.searchability).to eq 'direct'
end
end
context 'with direct when not specify' do
let(:searchable_by) { nil }
it 'create status' do
status = sender.statuses.first
expect(status).to_not be_nil
expect(status.searchability).to be_nil
end
end
context 'with limited' do
let(:searchable_by) { 'as:Limited' }
it 'create status' do
status = sender.statuses.first
expect(status).to_not be_nil
expect(status.searchability).to eq 'limited'
end
end
context 'with bio' do
let(:searchable_by) { nil }
context 'with public' do
let(:sender_bio) { '#searchable_by_all_users' }
it 'create status' do
status = sender.statuses.first
expect(status).to_not be_nil
expect(status.searchability).to eq 'public'
end
end
context 'with private' do
let(:sender_bio) { '#searchable_by_followers_only' }
it 'create status' do
status = sender.statuses.first
expect(status).to_not be_nil
expect(status.searchability).to eq 'private'
end
end
context 'with direct' do
let(:sender_bio) { '#searchable_by_reacted_users_only' }
it 'create status' do
status = sender.statuses.first
expect(status).to_not be_nil
expect(status.searchability).to eq 'direct'
end
end
context 'with limited' do
let(:sender_bio) { '#searchable_by_nobody' }
it 'create status' do
status = sender.statuses.first
expect(status).to_not be_nil
expect(status.searchability).to eq 'limited'
end
end
context 'without hashtags' do
let(:sender_bio) { '' }
it 'create status' do
status = sender.statuses.first
expect(status).to_not be_nil
expect(status.searchability).to be_nil
end
end
end
end
context 'when searchability from misskey server' do
let(:sender_software) { 'misskey' }
let(:to) { 'https://www.w3.org/ns/activitystreams#Public' }
let(:object_json) do
{
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
to: to,
}
end
context 'without specify searchability from misskey' do
it 'create status' do
status = sender.statuses.first
expect(status).to_not be_nil
expect(status.searchability).to eq 'public'
end
end
context 'without specify searchability from misskey which visibility is private' do
let(:to) { 'http://example.com/followers' }
it 'create status' do
status = sender.statuses.first
expect(status).to_not be_nil
expect(status.searchability).to eq 'limited'
end
end
end
context 'with multible searchabilities' do
let(:sender_bio) { '#searchable_by_nobody' }
let(:searchable_by) { 'https://www.w3.org/ns/activitystreams#Public' }
let(:object_json) do
{
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
to: 'https://www.w3.org/ns/activitystreams#Public',
searchableBy: searchable_by,
}
end
it 'create status' do
status = sender.statuses.first
expect(status).to_not be_nil
expect(status.searchability).to eq 'public'
end
context 'with misskey' do
let(:sender_software) { 'misskey' }
let(:searchable_by) { 'as:Limited' }
it 'create status' do
status = sender.statuses.first
expect(status).to_not be_nil
expect(status.searchability).to eq 'limited'
end
end
end
context 'with a reply' do context 'with a reply' do
let(:original_status) { Fabricate(:status) } let(:original_status) { Fabricate(:status) }

View file

@ -42,8 +42,11 @@ RSpec.describe AccountStatusesFilter do
before do before do
status!(:public) status!(:public)
status!(:public_unlisted)
status!(:unlisted) status!(:unlisted)
status!(:login)
status!(:private) status!(:private)
status!(:direct)
status_with_parent!(:public) status_with_parent!(:public)
status_with_reblog!(:public) status_with_reblog!(:public)
status_with_tag!(:public, tag) status_with_tag!(:public, tag)
@ -90,7 +93,7 @@ RSpec.describe AccountStatusesFilter do
let(:direct_status) { nil } let(:direct_status) { nil }
it 'returns only public statuses' do it 'returns only public statuses' do
expect(subject.results.pluck(:visibility).uniq).to match_array %w(unlisted public) expect(subject.results.pluck(:visibility).uniq).to match_array %w(unlisted public_unlisted public)
end end
it 'returns public replies' do it 'returns public replies' do
@ -120,7 +123,7 @@ RSpec.describe AccountStatusesFilter do
let(:current_account) { account } let(:current_account) { account }
it 'returns everything' do it 'returns everything' do
expect(subject.results.pluck(:visibility).uniq).to match_array %w(direct private unlisted public) expect(subject.results.pluck(:visibility).uniq).to match_array %w(direct private login unlisted public_unlisted public)
end end
it 'returns replies' do it 'returns replies' do
@ -142,7 +145,7 @@ RSpec.describe AccountStatusesFilter do
end end
it 'returns private statuses' do it 'returns private statuses' do
expect(subject.results.pluck(:visibility).uniq).to match_array %w(private unlisted public) expect(subject.results.pluck(:visibility).uniq).to match_array %w(private login unlisted public_unlisted public)
end end
it 'returns replies' do it 'returns replies' do
@ -168,7 +171,7 @@ RSpec.describe AccountStatusesFilter do
let(:current_account) { Fabricate(:account) } let(:current_account) { Fabricate(:account) }
it 'returns only public statuses' do it 'returns only public statuses' do
expect(subject.results.pluck(:visibility).uniq).to match_array %w(unlisted public) expect(subject.results.pluck(:visibility).uniq).to match_array %w(login unlisted public_unlisted public)
end end
it 'returns public replies' do it 'returns public replies' do

View file

@ -0,0 +1,3 @@
# frozen_string_literal: true
require 'rails_helper'

View file

@ -6,14 +6,36 @@ RSpec.describe PublicFeed do
let(:account) { Fabricate(:account) } let(:account) { Fabricate(:account) }
describe '#get' do describe '#get' do
subject { described_class.new(nil).get(20).map(&:id) } subject { described_class.new(viewer).get(20).map(&:id) }
it 'only includes statuses with public visibility' do let(:viewer) { nil }
public_status = Fabricate(:status, visibility: :public)
private_status = Fabricate(:status, visibility: :private)
expect(subject).to include(public_status.id) context 'with only includes statuses with public visibility' do
expect(subject).to_not include(private_status.id) let!(:public_status) { Fabricate(:status, visibility: :public) }
let!(:login_status) { Fabricate(:status, visibility: :login) }
let!(:unlisted_status) { Fabricate(:status, visibility: :unlisted) }
let!(:private_status) { Fabricate(:status, visibility: :private) }
let!(:direct_status) { Fabricate(:status, visibility: :direct) }
it 'without user' do
expect(subject).to include(public_status.id)
expect(subject).to_not include(login_status.id)
expect(subject).to_not include(unlisted_status.id)
expect(subject).to_not include(private_status.id)
expect(subject).to_not include(direct_status.id)
end
context 'with user' do
let(:viewer) { account }
it 'on global timeline' do
expect(subject).to include(public_status.id)
expect(subject).to include(login_status.id)
expect(subject).to_not include(unlisted_status.id)
expect(subject).to_not include(private_status.id)
expect(subject).to_not include(direct_status.id)
end
end
end end
it 'does not include replies' do it 'does not include replies' do
@ -50,6 +72,7 @@ RSpec.describe PublicFeed do
let!(:remote_account) { Fabricate(:account, domain: 'test.com') } let!(:remote_account) { Fabricate(:account, domain: 'test.com') }
let!(:local_status) { Fabricate(:status, account: local_account) } let!(:local_status) { Fabricate(:status, account: local_account) }
let!(:remote_status) { Fabricate(:status, account: remote_account) } let!(:remote_status) { Fabricate(:status, account: remote_account) }
let!(:public_unlisted_status) { Fabricate(:status, account: local_account, visibility: :public_unlisted) }
context 'without a viewer' do context 'without a viewer' do
let(:viewer) { nil } let(:viewer) { nil }
@ -61,6 +84,10 @@ RSpec.describe PublicFeed do
it 'includes local statuses' do it 'includes local statuses' do
expect(subject).to include(local_status.id) expect(subject).to include(local_status.id)
end end
it 'excludes public_unlisted statuses' do
expect(subject).to_not include(public_unlisted_status.id)
end
end end
context 'with a viewer' do context 'with a viewer' do
@ -73,6 +100,10 @@ RSpec.describe PublicFeed do
it 'includes local statuses' do it 'includes local statuses' do
expect(subject).to include(local_status.id) expect(subject).to include(local_status.id)
end end
it 'excludes public_unlisted statuses' do
expect(subject).to_not include(public_unlisted_status.id)
end
end end
end end
@ -83,6 +114,7 @@ RSpec.describe PublicFeed do
let!(:remote_account) { Fabricate(:account, domain: 'test.com') } let!(:remote_account) { Fabricate(:account, domain: 'test.com') }
let!(:local_status) { Fabricate(:status, account: local_account) } let!(:local_status) { Fabricate(:status, account: local_account) }
let!(:remote_status) { Fabricate(:status, account: remote_account) } let!(:remote_status) { Fabricate(:status, account: remote_account) }
let!(:public_unlisted_status) { Fabricate(:status, account: local_account, visibility: :public_unlisted) }
context 'without a viewer' do context 'without a viewer' do
let(:viewer) { nil } let(:viewer) { nil }
@ -91,6 +123,10 @@ RSpec.describe PublicFeed do
expect(subject).to include(local_status.id) expect(subject).to include(local_status.id)
expect(subject).to_not include(remote_status.id) expect(subject).to_not include(remote_status.id)
end end
it 'includes public_unlisted statuses' do
expect(subject).to include(public_unlisted_status.id)
end
end end
context 'with a viewer' do context 'with a viewer' do
@ -106,6 +142,10 @@ RSpec.describe PublicFeed do
expect(subject).to include(local_status.id) expect(subject).to include(local_status.id)
expect(subject).to_not include(remote_status.id) expect(subject).to_not include(remote_status.id)
end end
it 'includes public_unlisted statuses' do
expect(subject).to include(public_unlisted_status.id)
end
end end
end end
@ -116,6 +156,7 @@ RSpec.describe PublicFeed do
let!(:remote_account) { Fabricate(:account, domain: 'test.com') } let!(:remote_account) { Fabricate(:account, domain: 'test.com') }
let!(:local_status) { Fabricate(:status, account: local_account) } let!(:local_status) { Fabricate(:status, account: local_account) }
let!(:remote_status) { Fabricate(:status, account: remote_account) } let!(:remote_status) { Fabricate(:status, account: remote_account) }
let!(:public_unlisted_status) { Fabricate(:status, account: local_account, visibility: :public_unlisted) }
context 'without a viewer' do context 'without a viewer' do
let(:viewer) { nil } let(:viewer) { nil }
@ -124,6 +165,10 @@ RSpec.describe PublicFeed do
expect(subject).to_not include(local_status.id) expect(subject).to_not include(local_status.id)
expect(subject).to include(remote_status.id) expect(subject).to include(remote_status.id)
end end
it 'excludes public_unlisted statuses' do
expect(subject).to_not include(public_unlisted_status.id)
end
end end
context 'with a viewer' do context 'with a viewer' do
@ -133,6 +178,10 @@ RSpec.describe PublicFeed do
expect(subject).to_not include(local_status.id) expect(subject).to_not include(local_status.id)
expect(subject).to include(remote_status.id) expect(subject).to include(remote_status.id)
end end
it 'excludes public_unlisted statuses' do
expect(subject).to_not include(public_unlisted_status.id)
end
end end
end end

View file

@ -114,6 +114,72 @@ RSpec.describe Status do
end end
end end
describe '#searchability' do
subject { Fabricate(:status, account: account, searchability: status_searchability) }
let(:account_searchability) { :public }
let(:status_searchability) { :public }
let(:account_domain) { 'example.com' }
let(:account) { Fabricate(:account, domain: account_domain, searchability: account_searchability) }
context 'when public-public' do
it 'returns public' do
expect(subject.compute_searchability).to eq 'public'
end
end
context 'when public-private' do
let(:status_searchability) { :private }
it 'returns private' do
expect(subject.compute_searchability).to eq 'private'
end
end
context 'when public-direct' do
let(:status_searchability) { :direct }
it 'returns direct' do
expect(subject.compute_searchability).to eq 'direct'
end
end
context 'when private-public' do
let(:account_searchability) { :private }
it 'returns private' do
expect(subject.compute_searchability).to eq 'private'
end
end
context 'when direct-public' do
let(:account_searchability) { :direct }
it 'returns direct' do
expect(subject.compute_searchability).to eq 'direct'
end
end
context 'when private-limited' do
let(:account_searchability) { :private }
let(:status_searchability) { :limited }
it 'returns limited' do
expect(subject.compute_searchability).to eq 'limited'
end
end
context 'when private-public of local account' do
let(:account_searchability) { :private }
let(:account_domain) { nil }
let(:status_searchability) { :public }
it 'returns public' do
expect(subject.compute_searchability).to eq 'public'
end
end
end
describe '#content' do describe '#content' do
it 'returns the text of the status if it is not a reblog' do it 'returns the text of the status if it is not a reblog' do
expect(subject.content).to eql subject.text expect(subject.content).to eql subject.text

View file

@ -47,7 +47,7 @@ RSpec.describe 'Filters' do
it_behaves_like 'unauthorized for invalid token' it_behaves_like 'unauthorized for invalid token'
context 'with valid params' do context 'with valid params' do
let(:params) { { title: 'magic', context: %w(home), filter_action: 'hide', keywords_attributes: [keyword: 'magic'] } } let(:params) { { title: 'magic', context: %w(home), filter_action: 'hide', keywords_attributes: [keyword: 'magic', whole_word: true] } }
it 'returns http success' do it 'returns http success' do
subject subject

View file

@ -46,6 +46,7 @@ RSpec.describe ActivityPub::FetchRemoteAccountService, type: :service do
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor)) stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' }) stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
stub_request(:get, 'https://example.com/.well-known/nodeinfo').to_return(body: '{}')
end end
it 'fetches resource' do it 'fetches resource' do
@ -69,6 +70,7 @@ RSpec.describe ActivityPub::FetchRemoteAccountService, type: :service do
before do before do
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor)) stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' }) stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
stub_request(:get, 'https://example.com/.well-known/nodeinfo').to_return(body: '{}')
end end
it 'fetches resource' do it 'fetches resource' do
@ -96,6 +98,7 @@ RSpec.describe ActivityPub::FetchRemoteAccountService, type: :service do
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor)) stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' }) stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
stub_request(:get, 'https://iscool.af/.well-known/webfinger?resource=acct:alice@iscool.af').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' }) stub_request(:get, 'https://iscool.af/.well-known/webfinger?resource=acct:alice@iscool.af').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
stub_request(:get, 'https://iscool.af/.well-known/nodeinfo').to_return(body: '{}')
end end
it 'fetches resource' do it 'fetches resource' do
@ -127,6 +130,7 @@ RSpec.describe ActivityPub::FetchRemoteAccountService, type: :service do
before do before do
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor)) stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' }) stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
stub_request(:get, 'https://example.com/.well-known/nodeinfo').to_return(body: '{}')
end end
it 'fetches resource' do it 'fetches resource' do
@ -151,6 +155,7 @@ RSpec.describe ActivityPub::FetchRemoteAccountService, type: :service do
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor)) stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' }) stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
stub_request(:get, 'https://iscool.af/.well-known/webfinger?resource=acct:alice@iscool.af').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' }) stub_request(:get, 'https://iscool.af/.well-known/webfinger?resource=acct:alice@iscool.af').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
stub_request(:get, 'https://iscool.af/.well-known/nodeinfo').to_return(body: '{}')
end end
it 'fetches resource' do it 'fetches resource' do

View file

@ -46,6 +46,7 @@ RSpec.describe ActivityPub::FetchRemoteActorService, type: :service do
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor)) stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' }) stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
stub_request(:get, 'https://example.com/.well-known/nodeinfo').to_return(body: '{}')
end end
it 'fetches resource' do it 'fetches resource' do
@ -69,6 +70,7 @@ RSpec.describe ActivityPub::FetchRemoteActorService, type: :service do
before do before do
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor)) stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' }) stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
stub_request(:get, 'https://example.com/.well-known/nodeinfo').to_return(body: '{}')
end end
it 'fetches resource' do it 'fetches resource' do
@ -96,6 +98,7 @@ RSpec.describe ActivityPub::FetchRemoteActorService, type: :service do
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor)) stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' }) stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
stub_request(:get, 'https://iscool.af/.well-known/webfinger?resource=acct:alice@iscool.af').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' }) stub_request(:get, 'https://iscool.af/.well-known/webfinger?resource=acct:alice@iscool.af').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
stub_request(:get, 'https://iscool.af/.well-known/nodeinfo').to_return(body: '{}')
end end
it 'fetches resource' do it 'fetches resource' do
@ -127,6 +130,7 @@ RSpec.describe ActivityPub::FetchRemoteActorService, type: :service do
before do before do
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor)) stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' }) stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
stub_request(:get, 'https://example.com/.well-known/nodeinfo').to_return(body: '{}')
end end
it 'fetches resource' do it 'fetches resource' do
@ -151,6 +155,7 @@ RSpec.describe ActivityPub::FetchRemoteActorService, type: :service do
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor)) stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' }) stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
stub_request(:get, 'https://iscool.af/.well-known/webfinger?resource=acct:alice@iscool.af').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' }) stub_request(:get, 'https://iscool.af/.well-known/webfinger?resource=acct:alice@iscool.af').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
stub_request(:get, 'https://iscool.af/.well-known/nodeinfo').to_return(body: '{}')
end end
it 'fetches resource' do it 'fetches resource' do

View file

@ -52,6 +52,7 @@ RSpec.describe ActivityPub::FetchRemoteKeyService, type: :service do
before do before do
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor)) stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' }) stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
stub_request(:get, 'https://example.com/.well-known/nodeinfo').to_return(body: '{}')
end end
describe '#call' do describe '#call' do

View file

@ -5,6 +5,113 @@ require 'rails_helper'
RSpec.describe ActivityPub::ProcessAccountService, type: :service do RSpec.describe ActivityPub::ProcessAccountService, type: :service do
subject { described_class.new } subject { described_class.new }
context 'with searchability' do
subject { described_class.new.call('alice', 'example.com', payload) }
let(:software) { 'mastodon' }
let(:searchable_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',
searchableBy: searchable_by,
summary: sender_bio,
}.with_indifferent_access
end
before do
Fabricate(:instance_info, domain: 'example.com', software: software)
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 'searchability is public' do
expect(subject.searchability).to eq 'public'
end
end
context 'when private' do
let(:searchable_by) { 'https://example.com/followers' }
it 'searchability is private' do
expect(subject.searchability).to eq 'private'
end
end
context 'when direct' do
let(:searchable_by) { '' }
it 'searchability is direct' do
expect(subject.searchability).to eq 'direct'
end
end
context 'when limited' do
let(:searchable_by) { 'as:Limited' }
it 'searchability is limited' do
expect(subject.searchability).to eq 'limited'
end
end
context 'when default value' do
let(:searchable_by) { nil }
it 'searchability is direct' do
expect(subject.searchability).to eq 'direct'
end
end
context 'when misskey user' do
let(:software) { 'misskey' }
let(:searchable_by) { nil }
it 'searchability is public' do
expect(subject.searchability).to eq 'public'
end
end
context 'with bio' do
let(:searchable_by) { nil }
context 'with public' do
let(:sender_bio) { '#searchable_by_all_users' }
it 'searchability is public' do
expect(subject.searchability).to eq 'public'
end
end
context 'with private' do
let(:sender_bio) { '#searchable_by_followers_only' }
it 'searchability is private' do
expect(subject.searchability).to eq 'private'
end
end
context 'with direct' do
let(:sender_bio) { '#searchable_by_reacted_users_only' }
it 'searchability is direct' do
expect(subject.searchability).to eq 'direct'
end
end
context 'with limited' do
let(:sender_bio) { '#searchable_by_nobody' }
it 'searchability is limited' do
expect(subject.searchability).to eq 'limited'
end
end
end
end
context 'with property values' do context 'with property values' do
let(:payload) do let(:payload) do
{ {
@ -19,6 +126,10 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do
}.with_indifferent_access }.with_indifferent_access
end end
before do
stub_request(:get, 'https://example.com/.well-known/nodeinfo').to_return(body: '{}')
end
it 'parses out of attachment' do it 'parses out of attachment' do
account = subject.call('alice', 'example.com', payload) account = subject.call('alice', 'example.com', payload)
expect(account.fields).to be_a Array expect(account.fields).to be_a Array
@ -127,6 +238,9 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do
before do before do
stub_const 'ActivityPub::ProcessAccountService::SUBDOMAINS_RATELIMIT', 5 stub_const 'ActivityPub::ProcessAccountService::SUBDOMAINS_RATELIMIT', 5
8.times do |i|
stub_request(:get, "https://test#{i}.testdomain.com/.well-known/nodeinfo").to_return(body: '{}')
end
end end
it 'creates at least some accounts' do it 'creates at least some accounts' do
@ -192,6 +306,7 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do
stub_request(:get, "https://foo.test/users/#{i}/featured").to_return(status: 200, body: featured_json.to_json, headers: { 'Content-Type': 'application/activity+json' }) stub_request(:get, "https://foo.test/users/#{i}/featured").to_return(status: 200, body: featured_json.to_json, headers: { 'Content-Type': 'application/activity+json' })
stub_request(:get, "https://foo.test/users/#{i}/status").to_return(status: 200, body: status_json.to_json, headers: { 'Content-Type': 'application/activity+json' }) stub_request(:get, "https://foo.test/users/#{i}/status").to_return(status: 200, body: status_json.to_json, headers: { 'Content-Type': 'application/activity+json' })
stub_request(:get, "https://foo.test/.well-known/webfinger?resource=acct:user#{i}@foo.test").to_return(body: webfinger.to_json, headers: { 'Content-Type': 'application/jrd+json' }) stub_request(:get, "https://foo.test/.well-known/webfinger?resource=acct:user#{i}@foo.test").to_return(body: webfinger.to_json, headers: { 'Content-Type': 'application/jrd+json' })
stub_request(:get, 'https://foo.test/.well-known/nodeinfo').to_return(body: '{}')
end end
end end

View file

@ -113,6 +113,34 @@ RSpec.describe PostStatusService, type: :service do
expect(status.visibility).to eq 'unlisted' expect(status.visibility).to eq 'unlisted'
end end
it 'creates a status with the given searchability' do
status = create_status_with_options(searchability: :public, visibility: :public)
expect(status).to be_persisted
expect(status.searchability).to eq 'public'
end
it 'creates a status with limited searchability for silenced users' do
status = subject.call(Fabricate(:account, silenced: true), text: 'test', searchability: :public, visibility: :public)
expect(status).to be_persisted
expect(status.searchability).to eq 'private'
end
it 'creates a status with the given searchability=public / visibility=unlisted' do
status = create_status_with_options(searchability: :public, visibility: :unlisted)
expect(status).to be_persisted
expect(status.searchability).to eq 'public'
end
it 'creates a status with the given searchability=public / visibility=private' do
status = create_status_with_options(searchability: :public, visibility: :private)
expect(status).to be_persisted
expect(status.searchability).to eq 'private'
end
it 'creates a status for the given application' do it 'creates a status for the given application' do
application = Fabricate(:application) application = Fabricate(:application)

View file

@ -9,10 +9,12 @@ RSpec.describe ResolveAccountService, type: :service do
stub_request(:get, 'https://example.com/.well-known/host-meta').to_return(status: 404) stub_request(:get, 'https://example.com/.well-known/host-meta').to_return(status: 404)
stub_request(:get, 'https://quitter.no/avatar/7477-300-20160211190340.png').to_return(request_fixture('avatar.txt')) stub_request(:get, 'https://quitter.no/avatar/7477-300-20160211190340.png').to_return(request_fixture('avatar.txt'))
stub_request(:get, 'https://ap.example.com/.well-known/webfinger?resource=acct:foo@ap.example.com').to_return(request_fixture('activitypub-webfinger.txt')) stub_request(:get, 'https://ap.example.com/.well-known/webfinger?resource=acct:foo@ap.example.com').to_return(request_fixture('activitypub-webfinger.txt'))
stub_request(:get, 'https://ap.example.com/.well-known/nodeinfo').to_return(body: '{}')
stub_request(:get, 'https://ap.example.com/users/foo').to_return(request_fixture('activitypub-actor.txt')) stub_request(:get, 'https://ap.example.com/users/foo').to_return(request_fixture('activitypub-actor.txt'))
stub_request(:get, 'https://ap.example.com/users/foo.atom').to_return(request_fixture('activitypub-feed.txt')) stub_request(:get, 'https://ap.example.com/users/foo.atom').to_return(request_fixture('activitypub-feed.txt'))
stub_request(:get, %r{https://ap\.example\.com/users/foo/\w+}).to_return(status: 404) stub_request(:get, %r{https://ap\.example\.com/users/foo/\w+}).to_return(status: 404)
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:hoge@example.com').to_return(status: 410) stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:hoge@example.com').to_return(status: 410)
stub_request(:get, 'https://example.com/.well-known/nodeinfo').to_return(status: 410)
end end
context 'when using skip_webfinger' do context 'when using skip_webfinger' do
@ -135,8 +137,10 @@ RSpec.describe ResolveAccountService, type: :service do
before do before do
webfinger = { subject: 'acct:foo@evil.example.com', links: [{ rel: 'self', href: 'https://ap.example.com/users/foo', type: 'application/activity+json' }] } webfinger = { subject: 'acct:foo@evil.example.com', links: [{ rel: 'self', href: 'https://ap.example.com/users/foo', type: 'application/activity+json' }] }
stub_request(:get, 'https://redirected.example.com/.well-known/webfinger?resource=acct:Foo@redirected.example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' }) stub_request(:get, 'https://redirected.example.com/.well-known/webfinger?resource=acct:Foo@redirected.example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
stub_request(:get, 'https://redirected.example.com/.well-known/nodeinfo').to_return(body: '{}')
webfinger2 = { subject: 'acct:foo@ap.example.com', links: [{ rel: 'self', href: 'https://ap.example.com/users/foo', type: 'application/activity+json' }] } webfinger2 = { subject: 'acct:foo@ap.example.com', links: [{ rel: 'self', href: 'https://ap.example.com/users/foo', type: 'application/activity+json' }] }
stub_request(:get, 'https://evil.example.com/.well-known/webfinger?resource=acct:foo@evil.example.com').to_return(body: Oj.dump(webfinger2), headers: { 'Content-Type': 'application/jrd+json' }) stub_request(:get, 'https://evil.example.com/.well-known/webfinger?resource=acct:foo@evil.example.com').to_return(body: Oj.dump(webfinger2), headers: { 'Content-Type': 'application/jrd+json' })
stub_request(:get, 'https://evil.example.com/.well-known/nodeinfo').to_return(body: '{}')
end end
it 'does not return a new remote account' do it 'does not return a new remote account' do