Merge branch 'kb_development' into kb_migration
This commit is contained in:
commit
6cfe78d51c
22 changed files with 541 additions and 32 deletions
|
@ -1,5 +1,7 @@
|
|||
# kmyblue
|
||||
|
||||
[](https://github.com/kmycode/mastodon/actions/workflows/test-ruby.yml)
|
||||
|
||||
kmyblue は[Mastodon](https://github.com/mastodon/mastodon)のフォークです。創作作家のための Mastodon を目指して開発しました。
|
||||
|
||||
kmyblue はフォーク名であり、同時に[サーバー名](https://kmy.blue)でもあります。以下は特に記述がない限り、フォークとしての kmyblue をさします。
|
||||
|
|
|
@ -514,7 +514,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
note = @account&.note
|
||||
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
|
||||
|
||||
searchability = searchability_bio[0]
|
||||
|
|
|
@ -40,7 +40,7 @@ class CustomEmoji < ApplicationRecord
|
|||
|
||||
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_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
|
||||
|
||||
|
|
|
@ -19,13 +19,12 @@ class PublicFeed
|
|||
# @param [Integer] min_id
|
||||
# @return [Array<Status>]
|
||||
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_reblogs_scope) unless with_reblogs?
|
||||
scope.merge!(local_only_scope) if local_only?
|
||||
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!(media_only_scope) if media_only?
|
||||
scope.merge!(language_scope) if account&.chosen_languages.present?
|
||||
|
@ -58,10 +57,6 @@ class PublicFeed
|
|||
@account.nil? && Setting.hide_local_users_for_anonymous
|
||||
end
|
||||
|
||||
def global_timeline?
|
||||
!options[:remote] && !options[:local]
|
||||
end
|
||||
|
||||
def account?
|
||||
account.present?
|
||||
end
|
||||
|
@ -74,6 +69,10 @@ class PublicFeed
|
|||
Status.with_public_visibility.joins(:account).merge(Account.without_suspended.without_silenced)
|
||||
end
|
||||
|
||||
def global_timeline_only_scope
|
||||
Status.with_global_timeline_visibility.joins(:account).merge(Account.without_suspended.without_silenced)
|
||||
end
|
||||
|
||||
def public_search_scope
|
||||
Status.with_public_search_visibility.joins(:account).merge(Account.without_suspended.without_silenced)
|
||||
end
|
||||
|
@ -86,10 +85,6 @@ class PublicFeed
|
|||
Status.remote
|
||||
end
|
||||
|
||||
def global_timeline_only_scope
|
||||
Status.with_global_timeline_visibility.joins(:account).merge(Account.without_suspended.without_silenced)
|
||||
end
|
||||
|
||||
def without_replies_scope
|
||||
Status.without_replies
|
||||
end
|
||||
|
@ -107,7 +102,7 @@ class PublicFeed
|
|||
end
|
||||
|
||||
def anonymous_scope
|
||||
Status.where(visibility: [:public, :public_unlisted])
|
||||
local_only? ? Status.where(visibility: [:public, :public_unlisted]) : Status.where(visibility: :public)
|
||||
end
|
||||
|
||||
def account_filters_scope
|
||||
|
|
|
@ -106,7 +106,9 @@ class Trends::Statuses < Trends::Base
|
|||
private
|
||||
|
||||
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
|
||||
|
||||
def calculate_scores(statuses, at_time)
|
||||
|
|
|
@ -269,9 +269,11 @@ class ActivityPub::ProcessAccountService < BaseService
|
|||
if audience_searchable_by.any? { |uri| ActivityPub::TagManager.instance.public_collection?(uri) }
|
||||
:public
|
||||
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
|
||||
:direct # Reaction only in kmyblue (generics: direct)
|
||||
:direct
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -279,7 +281,7 @@ class ActivityPub::ProcessAccountService < BaseService
|
|||
note = @json['summary'] || ''
|
||||
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
|
||||
|
||||
searchability = searchability_bio[0]
|
||||
|
|
|
@ -39,11 +39,13 @@ class UpdateStatusService < BaseService
|
|||
|
||||
queue_poll_notifications!
|
||||
reset_preview_card!
|
||||
update_references!
|
||||
update_metadata!
|
||||
update_references!
|
||||
broadcast_updates!
|
||||
|
||||
# Mentions are not updated (Cause unknown)
|
||||
@status.reload
|
||||
|
||||
@status
|
||||
rescue NoChangesSubmittedError
|
||||
# For calls that result in no changes, swallow the error
|
||||
|
@ -165,7 +167,6 @@ class UpdateStatusService < BaseService
|
|||
def update_metadata!
|
||||
ProcessHashtagsService.new.call(@status)
|
||||
ProcessMentionsService.new.call(@status)
|
||||
ProcessReferencesWorker.perform_async(@status.id, (@options[:status_reference_ids] || []).map(&:to_i).filter(&:positive?), [])
|
||||
end
|
||||
|
||||
def broadcast_updates!
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
|
||||
= 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
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
|
|
|
@ -4,6 +4,8 @@ class DowncaseCustomEmojiDomains < ActiveRecord::Migration[5.2]
|
|||
disable_ddl_transaction!
|
||||
|
||||
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.each do |row|
|
||||
|
@ -11,6 +13,8 @@ class DowncaseCustomEmojiDomains < ActiveRecord::Migration[5.2]
|
|||
end
|
||||
|
||||
CustomEmoji.in_batches.update_all('domain = lower(domain)')
|
||||
|
||||
CustomEmoji.connection.execute('DROP TABLE IF EXISTS emoji_reactions')
|
||||
end
|
||||
|
||||
def down; end
|
||||
|
|
7
spec/fabricators/instance_info_fabricator.rb
Normal file
7
spec/fabricators/instance_info_fabricator.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Fabricator(:instance_info) do
|
||||
domain 'info.example.com'
|
||||
software 'mastodon'
|
||||
version '4.1.0'
|
||||
end
|
|
@ -3,7 +3,8 @@
|
|||
require 'rails_helper'
|
||||
|
||||
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
|
||||
{
|
||||
|
@ -27,7 +28,10 @@ RSpec.describe ActivityPub::Activity::Create do
|
|||
context 'when fetching' do
|
||||
subject { described_class.new(json, sender) }
|
||||
|
||||
let(:sender_software) { 'mastodon' }
|
||||
|
||||
before do
|
||||
Fabricate(:instance_info, domain: 'example.com', software: sender_software)
|
||||
subject.perform
|
||||
end
|
||||
|
||||
|
@ -318,6 +322,219 @@ RSpec.describe ActivityPub::Activity::Create do
|
|||
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
|
||||
let(:original_status) { Fabricate(:status) }
|
||||
|
||||
|
|
|
@ -42,8 +42,11 @@ RSpec.describe AccountStatusesFilter do
|
|||
|
||||
before do
|
||||
status!(:public)
|
||||
status!(:public_unlisted)
|
||||
status!(:unlisted)
|
||||
status!(:login)
|
||||
status!(:private)
|
||||
status!(:direct)
|
||||
status_with_parent!(:public)
|
||||
status_with_reblog!(:public)
|
||||
status_with_tag!(:public, tag)
|
||||
|
@ -90,7 +93,7 @@ RSpec.describe AccountStatusesFilter do
|
|||
let(:direct_status) { nil }
|
||||
|
||||
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
|
||||
|
||||
it 'returns public replies' do
|
||||
|
@ -120,7 +123,7 @@ RSpec.describe AccountStatusesFilter do
|
|||
let(:current_account) { account }
|
||||
|
||||
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
|
||||
|
||||
it 'returns replies' do
|
||||
|
@ -142,7 +145,7 @@ RSpec.describe AccountStatusesFilter do
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
it 'returns replies' do
|
||||
|
@ -168,7 +171,7 @@ RSpec.describe AccountStatusesFilter do
|
|||
let(:current_account) { Fabricate(:account) }
|
||||
|
||||
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
|
||||
|
||||
it 'returns public replies' do
|
||||
|
|
3
spec/models/instance_info_spec.rb
Normal file
3
spec/models/instance_info_spec.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
|
@ -6,14 +6,36 @@ RSpec.describe PublicFeed do
|
|||
let(:account) { Fabricate(:account) }
|
||||
|
||||
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
|
||||
public_status = Fabricate(:status, visibility: :public)
|
||||
private_status = Fabricate(:status, visibility: :private)
|
||||
let(:viewer) { nil }
|
||||
|
||||
expect(subject).to include(public_status.id)
|
||||
expect(subject).to_not include(private_status.id)
|
||||
context 'with only includes statuses with public visibility' do
|
||||
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
|
||||
|
||||
it 'does not include replies' do
|
||||
|
@ -50,6 +72,7 @@ RSpec.describe PublicFeed do
|
|||
let!(:remote_account) { Fabricate(:account, domain: 'test.com') }
|
||||
let!(:local_status) { Fabricate(:status, account: local_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
|
||||
let(:viewer) { nil }
|
||||
|
@ -61,6 +84,10 @@ RSpec.describe PublicFeed do
|
|||
it 'includes local statuses' do
|
||||
expect(subject).to include(local_status.id)
|
||||
end
|
||||
|
||||
it 'excludes public_unlisted statuses' do
|
||||
expect(subject).to_not include(public_unlisted_status.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a viewer' do
|
||||
|
@ -73,6 +100,10 @@ RSpec.describe PublicFeed do
|
|||
it 'includes local statuses' do
|
||||
expect(subject).to include(local_status.id)
|
||||
end
|
||||
|
||||
it 'excludes public_unlisted statuses' do
|
||||
expect(subject).to_not include(public_unlisted_status.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -83,6 +114,7 @@ RSpec.describe PublicFeed do
|
|||
let!(:remote_account) { Fabricate(:account, domain: 'test.com') }
|
||||
let!(:local_status) { Fabricate(:status, account: local_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
|
||||
let(:viewer) { nil }
|
||||
|
@ -91,6 +123,10 @@ RSpec.describe PublicFeed do
|
|||
expect(subject).to include(local_status.id)
|
||||
expect(subject).to_not include(remote_status.id)
|
||||
end
|
||||
|
||||
it 'includes public_unlisted statuses' do
|
||||
expect(subject).to include(public_unlisted_status.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a viewer' do
|
||||
|
@ -106,6 +142,10 @@ RSpec.describe PublicFeed do
|
|||
expect(subject).to include(local_status.id)
|
||||
expect(subject).to_not include(remote_status.id)
|
||||
end
|
||||
|
||||
it 'includes public_unlisted statuses' do
|
||||
expect(subject).to include(public_unlisted_status.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -116,6 +156,7 @@ RSpec.describe PublicFeed do
|
|||
let!(:remote_account) { Fabricate(:account, domain: 'test.com') }
|
||||
let!(:local_status) { Fabricate(:status, account: local_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
|
||||
let(:viewer) { nil }
|
||||
|
@ -124,6 +165,10 @@ RSpec.describe PublicFeed do
|
|||
expect(subject).to_not include(local_status.id)
|
||||
expect(subject).to include(remote_status.id)
|
||||
end
|
||||
|
||||
it 'excludes public_unlisted statuses' do
|
||||
expect(subject).to_not include(public_unlisted_status.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a viewer' do
|
||||
|
@ -133,6 +178,10 @@ RSpec.describe PublicFeed do
|
|||
expect(subject).to_not include(local_status.id)
|
||||
expect(subject).to include(remote_status.id)
|
||||
end
|
||||
|
||||
it 'excludes public_unlisted statuses' do
|
||||
expect(subject).to_not include(public_unlisted_status.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -114,6 +114,72 @@ RSpec.describe Status do
|
|||
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
|
||||
it 'returns the text of the status if it is not a reblog' do
|
||||
expect(subject.content).to eql subject.text
|
||||
|
|
|
@ -47,7 +47,7 @@ RSpec.describe 'Filters' do
|
|||
it_behaves_like 'unauthorized for invalid token'
|
||||
|
||||
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
|
||||
subject
|
||||
|
|
|
@ -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/.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
|
||||
|
||||
it 'fetches resource' do
|
||||
|
@ -69,6 +70,7 @@ RSpec.describe ActivityPub::FetchRemoteAccountService, type: :service do
|
|||
before do
|
||||
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/nodeinfo').to_return(body: '{}')
|
||||
end
|
||||
|
||||
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/.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/nodeinfo').to_return(body: '{}')
|
||||
end
|
||||
|
||||
it 'fetches resource' do
|
||||
|
@ -127,6 +130,7 @@ RSpec.describe ActivityPub::FetchRemoteAccountService, type: :service do
|
|||
before do
|
||||
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/nodeinfo').to_return(body: '{}')
|
||||
end
|
||||
|
||||
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/.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/nodeinfo').to_return(body: '{}')
|
||||
end
|
||||
|
||||
it 'fetches resource' do
|
||||
|
|
|
@ -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/.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
|
||||
|
||||
it 'fetches resource' do
|
||||
|
@ -69,6 +70,7 @@ RSpec.describe ActivityPub::FetchRemoteActorService, type: :service do
|
|||
before do
|
||||
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/nodeinfo').to_return(body: '{}')
|
||||
end
|
||||
|
||||
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/.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/nodeinfo').to_return(body: '{}')
|
||||
end
|
||||
|
||||
it 'fetches resource' do
|
||||
|
@ -127,6 +130,7 @@ RSpec.describe ActivityPub::FetchRemoteActorService, type: :service do
|
|||
before do
|
||||
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/nodeinfo').to_return(body: '{}')
|
||||
end
|
||||
|
||||
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/.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/nodeinfo').to_return(body: '{}')
|
||||
end
|
||||
|
||||
it 'fetches resource' do
|
||||
|
|
|
@ -52,6 +52,7 @@ RSpec.describe ActivityPub::FetchRemoteKeyService, type: :service do
|
|||
before do
|
||||
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/nodeinfo').to_return(body: '{}')
|
||||
end
|
||||
|
||||
describe '#call' do
|
||||
|
|
|
@ -5,6 +5,113 @@ require 'rails_helper'
|
|||
RSpec.describe ActivityPub::ProcessAccountService, type: :service do
|
||||
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
|
||||
let(:payload) do
|
||||
{
|
||||
|
@ -19,6 +126,10 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do
|
|||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
before do
|
||||
stub_request(:get, 'https://example.com/.well-known/nodeinfo').to_return(body: '{}')
|
||||
end
|
||||
|
||||
it 'parses out of attachment' do
|
||||
account = subject.call('alice', 'example.com', payload)
|
||||
expect(account.fields).to be_a Array
|
||||
|
@ -127,6 +238,9 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do
|
|||
|
||||
before do
|
||||
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
|
||||
|
||||
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}/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/nodeinfo').to_return(body: '{}')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -113,6 +113,34 @@ RSpec.describe PostStatusService, type: :service do
|
|||
expect(status.visibility).to eq 'unlisted'
|
||||
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
|
||||
application = Fabricate(:application)
|
||||
|
||||
|
|
|
@ -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://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/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.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, '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
|
||||
|
||||
context 'when using skip_webfinger' do
|
||||
|
@ -135,8 +137,10 @@ RSpec.describe ResolveAccountService, type: :service do
|
|||
before do
|
||||
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/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' }] }
|
||||
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
|
||||
|
||||
it 'does not return a new remote account' do
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue