commit
8a05b85d78
20 changed files with 267 additions and 21 deletions
|
@ -18,7 +18,7 @@ class Settings::PrivacyController < Settings::BaseController
|
|||
private
|
||||
|
||||
def account_params
|
||||
params.require(:account).permit(:discoverable, :unlocked, :indexable, :show_collections, :dissubscribable, settings: UserSettings.keys)
|
||||
params.require(:account).permit(:discoverable, :unlocked, :indexable, :show_collections, settings: UserSettings.keys)
|
||||
end
|
||||
|
||||
def set_account
|
||||
|
|
|
@ -18,7 +18,7 @@ class Settings::PrivacyExtraController < Settings::BaseController
|
|||
private
|
||||
|
||||
def account_params
|
||||
params.require(:account).permit(settings: UserSettings.keys)
|
||||
params.require(:account).permit(:dissubscribable, settings: UserSettings.keys)
|
||||
end
|
||||
|
||||
def set_account
|
||||
|
|
|
@ -30,4 +30,29 @@ module KmyblueCapabilitiesHelper
|
|||
|
||||
capabilities
|
||||
end
|
||||
|
||||
def capabilities_for_nodeinfo
|
||||
capabilities = %i(
|
||||
wide_emoji
|
||||
status_reference
|
||||
quote
|
||||
kmyblue_quote
|
||||
searchability
|
||||
kmyblue_searchability
|
||||
visibility_mutual
|
||||
visibility_limited
|
||||
kmyblue_antenna
|
||||
kmyblue_bookmark_category
|
||||
kmyblue_searchability_limited
|
||||
kmyblue_circle_history
|
||||
)
|
||||
|
||||
capabilities << :full_text_search if Chewy.enabled?
|
||||
if Setting.enable_emoji_reaction
|
||||
capabilities << :emoji_reaction
|
||||
capabilities << :enable_wide_emoji_reaction
|
||||
end
|
||||
|
||||
capabilities
|
||||
end
|
||||
end
|
||||
|
|
|
@ -53,7 +53,7 @@ class ActivityPub::Activity::Follow < ActivityPub::Activity
|
|||
friend.update!(passive_state: :pending, active_state: :idle, passive_follow_activity_id: @json['id'])
|
||||
else
|
||||
@friend = FriendDomain.new(domain: @account.domain, passive_state: :pending, passive_follow_activity_id: @json['id'])
|
||||
@friend.initialize_inbox_url!
|
||||
@friend.inbox_url = @json['inboxUrl'].presence || @friend.default_inbox_url
|
||||
@friend.save!
|
||||
end
|
||||
|
||||
|
|
|
@ -207,6 +207,7 @@ class FeedManager
|
|||
# also tagged with another followed hashtag or from a followed user
|
||||
scope = from_tag.statuses
|
||||
.where(id: timeline_status_ids)
|
||||
.where.not(account: into_account)
|
||||
.where.not(account: into_account.following)
|
||||
.tagged_with_none(TagFollow.where(account: into_account).pluck(:tag_id))
|
||||
|
||||
|
|
|
@ -147,7 +147,7 @@ class StatusReachFinder
|
|||
|
||||
def friend_inboxes
|
||||
if @status.public_visibility? || @status.public_unlisted_visibility? || (@status.unlisted_visibility? && (@status.public_searchability? || @status.public_unlisted_searchability?))
|
||||
DeliveryFailureTracker.without_unavailable(FriendDomain.distributables.where(delivery_local: true).pluck(:inbox_url))
|
||||
DeliveryFailureTracker.without_unavailable(FriendDomain.distributables.where(delivery_local: true).where.not(domain: AccountDomainBlock.where(account: @status.account).select(:domain)).pluck(:inbox_url))
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
@ -155,7 +155,7 @@ class StatusReachFinder
|
|||
|
||||
def nolocal_friend_inboxes
|
||||
if @status.public_visibility?
|
||||
DeliveryFailureTracker.without_unavailable(FriendDomain.distributables.where(delivery_local: false).pluck(:inbox_url))
|
||||
DeliveryFailureTracker.without_unavailable(FriendDomain.distributables.where(delivery_local: false).where.not(domain: AccountDomainBlock.where(account: @status.account).select(:domain)).pluck(:inbox_url))
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
|
|
@ -39,12 +39,12 @@ class EmojiReaction < ApplicationRecord
|
|||
custom_emoji.present?
|
||||
end
|
||||
|
||||
def remote_custom_emoji?
|
||||
custom_emoji? && !custom_emoji.local?
|
||||
def sign?
|
||||
true
|
||||
end
|
||||
|
||||
def sign?
|
||||
status&.distributable_friend?
|
||||
def object_type
|
||||
:emoji_reaction
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -93,16 +93,12 @@ class FriendDomain < ApplicationRecord
|
|||
destroy!
|
||||
end
|
||||
|
||||
def initialize_inbox_url!
|
||||
self.inbox_url = default_inbox_url
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def default_inbox_url
|
||||
"https://#{domain}/inbox"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def delete_for_friend!
|
||||
activity_id = ActivityPub::TagManager.instance.generate_uri_for(nil)
|
||||
payload = Oj.dump(delete_follow_activity(activity_id))
|
||||
|
@ -118,6 +114,9 @@ class FriendDomain < ApplicationRecord
|
|||
type: 'Follow',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(some_local_account),
|
||||
object: ActivityPub::TagManager::COLLECTIONS[:public],
|
||||
|
||||
# Cannot use inbox_url method because this model also has inbox_url column
|
||||
inboxUrl: "https://#{Rails.configuration.x.web_domain}/inbox",
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ class NodeInfo::Serializer < ActiveModel::Serializer
|
|||
|
||||
def metadata
|
||||
{
|
||||
features: fedibird_capabilities,
|
||||
features: capabilities_for_nodeinfo,
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -23,7 +23,10 @@ class EmojiReactService < BaseService
|
|||
raise Mastodon::ValidationError, I18n.t('reactions.errors.duplication') unless emoji_reaction.nil?
|
||||
|
||||
shortcode, domain = name.split('@')
|
||||
domain = nil if TagManager.instance.local_domain?(domain)
|
||||
custom_emoji = CustomEmoji.find_by(shortcode: shortcode, domain: domain)
|
||||
return if domain.present? && !EmojiReaction.exists?(status: status, custom_emoji: custom_emoji)
|
||||
|
||||
emoji_reaction = EmojiReaction.create!(account: account, status: status, name: shortcode, custom_emoji: custom_emoji)
|
||||
|
||||
status.touch # rubocop:disable Rails/SkipsModelValidations
|
||||
|
@ -62,7 +65,6 @@ class EmojiReactService < BaseService
|
|||
status = emoji_reaction.status
|
||||
|
||||
return unless status.account.local?
|
||||
return if emoji_reaction.remote_custom_emoji?
|
||||
|
||||
ActivityPub::RawDistributionWorker.perform_async(build_json(emoji_reaction), status.account_id)
|
||||
end
|
||||
|
|
|
@ -24,9 +24,6 @@
|
|||
.fields-group
|
||||
= ff.input :noai, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_noai'), hint: I18n.t('simple_form.hints.defaults.setting_noai')
|
||||
|
||||
.fields-group
|
||||
= f.input :dissubscribable, as: :boolean, wrapper: :with_label, kmyblue: true, hint: t('simple_form.hints.defaults.dissubscribable')
|
||||
|
||||
.fields-group
|
||||
= f.input :unlocked, as: :boolean, wrapper: :with_label
|
||||
|
||||
|
|
|
@ -21,6 +21,9 @@
|
|||
.fields-group
|
||||
= 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')
|
||||
|
||||
.fields-group
|
||||
= ff.input :allow_quote, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_allow_quote'), hint: false
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ module Mastodon
|
|||
end
|
||||
|
||||
def kmyblue_minor
|
||||
0
|
||||
1
|
||||
end
|
||||
|
||||
def kmyblue_flag
|
||||
|
|
|
@ -310,6 +310,7 @@ RSpec.describe ActivityPub::Activity::Follow do
|
|||
let!(:friend) { Fabricate(:friend_domain, domain: 'abc.com', passive_state: :idle) }
|
||||
let!(:owner_user) { Fabricate(:user, role: UserRole.find_by(name: 'Owner')) }
|
||||
let!(:patch_user) { Fabricate(:user, role: Fabricate(:user_role, name: 'OhagiOps', permissions: UserRole::FLAGS[:manage_federation])) }
|
||||
let(:inbox_url) { nil }
|
||||
|
||||
let(:json) do
|
||||
{
|
||||
|
@ -318,6 +319,7 @@ RSpec.describe ActivityPub::Activity::Follow do
|
|||
type: 'Follow',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
object: 'https://www.w3.org/ns/activitystreams#Public',
|
||||
inboxUrl: inbox_url,
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
|
@ -343,6 +345,24 @@ RSpec.describe ActivityPub::Activity::Follow do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when no record and inbox_url is specified' do
|
||||
let(:inbox_url) { 'https://ohagi.com/inbox' }
|
||||
|
||||
before do
|
||||
friend.update(domain: 'def.com')
|
||||
end
|
||||
|
||||
it 'marks the friend as pending' do
|
||||
subject.perform
|
||||
|
||||
friend = FriendDomain.find_by(domain: 'abc.com')
|
||||
expect(friend).to_not be_nil
|
||||
expect(friend.they_are_pending?).to be true
|
||||
expect(friend.passive_follow_activity_id).to eq 'foo'
|
||||
expect(friend.inbox_url).to eq 'https://ohagi.com/inbox'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when my server is pending' do
|
||||
before do
|
||||
friend.update(active_state: :pending)
|
||||
|
|
|
@ -562,6 +562,44 @@ RSpec.describe FeedManager do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#unmerge_tag_from_home' do
|
||||
let(:receiver) { Fabricate(:account) }
|
||||
let(:tag) { Fabricate(:tag) }
|
||||
|
||||
it 'leaves a tagged status' do
|
||||
status = Fabricate(:status)
|
||||
status.tags << tag
|
||||
described_class.instance.push_to_home(receiver, status)
|
||||
|
||||
described_class.instance.unmerge_tag_from_home(tag, receiver)
|
||||
|
||||
expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to_not include(status.id.to_s)
|
||||
end
|
||||
|
||||
it 'remains a tagged status written by receiver\'s followee' do
|
||||
followee = Fabricate(:account)
|
||||
receiver.follow!(followee)
|
||||
|
||||
status = Fabricate(:status, account: followee)
|
||||
status.tags << tag
|
||||
described_class.instance.push_to_home(receiver, status)
|
||||
|
||||
described_class.instance.unmerge_tag_from_home(tag, receiver)
|
||||
|
||||
expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to include(status.id.to_s)
|
||||
end
|
||||
|
||||
it 'remains a tagged status written by receiver' do
|
||||
status = Fabricate(:status, account: receiver)
|
||||
status.tags << tag
|
||||
described_class.instance.push_to_home(receiver, status)
|
||||
|
||||
described_class.instance.unmerge_tag_from_home(tag, receiver)
|
||||
|
||||
expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to include(status.id.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#clear_from_home' do
|
||||
let(:account) { Fabricate(:account) }
|
||||
let(:followed_account) { Fabricate(:account) }
|
||||
|
|
|
@ -239,6 +239,18 @@ describe StatusReachFinder do
|
|||
expect(subject.inboxes_for_friend).to_not include 'https://foo.bar/inbox'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when distributable but domain blocked by account' do
|
||||
before do
|
||||
Fabricate(:account_domain_block, account: alice, domain: 'foo.bar')
|
||||
Fabricate(:friend_domain, domain: 'foo.bar', inbox_url: 'https://foo.bar/inbox', passive_state: :accepted, pseudo_relay: true)
|
||||
end
|
||||
|
||||
it 'send status' do
|
||||
expect(subject.inboxes).to_not include 'https://foo.bar/inbox'
|
||||
expect(subject.inboxes_for_friend).to_not include 'https://foo.bar/inbox'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it contains distributable friend server' do
|
||||
|
|
|
@ -21,6 +21,7 @@ describe FriendDomain do
|
|||
type: 'Follow',
|
||||
actor: 'https://cb6e6126.ngrok.io/actor',
|
||||
object: 'https://www.w3.org/ns/activitystreams#Public',
|
||||
inboxUrl: 'https://cb6e6126.ngrok.io/inbox',
|
||||
}))).to have_been_made.once
|
||||
end
|
||||
end
|
||||
|
|
|
@ -28,5 +28,10 @@ describe NodeInfo::Serializer do # rubocop:disable RSpec/FilePath
|
|||
it 'returns features' do
|
||||
expect(serialization['metadata']['features']).to include 'emoji_reaction'
|
||||
end
|
||||
|
||||
it 'returns nodeinfo own features' do
|
||||
expect(serialization['metadata']['features']).to include 'quote'
|
||||
expect(serialization['metadata']['features']).to_not include 'kmyblue_markdown'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,5 +22,9 @@ describe REST::InstanceSerializer do
|
|||
it 'returns fedibird_capabilities' do
|
||||
expect(serialization['fedibird_capabilities']).to include 'emoji_reaction'
|
||||
end
|
||||
|
||||
it 'returns api own fedibird_capabilities' do
|
||||
expect(serialization['fedibird_capabilities']).to include 'kmyblue_markdown'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
139
spec/services/emoji_react_service_spec.rb
Normal file
139
spec/services/emoji_react_service_spec.rb
Normal file
|
@ -0,0 +1,139 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe EmojiReactService, type: :service do
|
||||
subject do
|
||||
described_class.new.call(sender, status, name)
|
||||
EmojiReaction.where(status: status, account: sender)
|
||||
end
|
||||
|
||||
let(:name) { '😀' }
|
||||
let(:sender) { Fabricate(:user).account }
|
||||
let(:author) { Fabricate(:user).account }
|
||||
let(:status) { Fabricate(:status, account: author) }
|
||||
|
||||
it 'with a simple case' do
|
||||
expect(subject.count).to eq 1
|
||||
expect(subject.first.name).to eq '😀'
|
||||
expect(subject.first.custom_emoji_id).to be_nil
|
||||
end
|
||||
|
||||
context 'with name duplication on same account' do
|
||||
before { Fabricate(:emoji_reaction, status: status, name: '😀') }
|
||||
|
||||
it 'react with emoji' do
|
||||
expect(subject.count).to eq 1
|
||||
expect(subject.first.name).to eq '😀'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when multiple reactions by same account' do
|
||||
let(:name) { '😂' }
|
||||
|
||||
before { Fabricate(:emoji_reaction, account: sender, status: status, name: '😀') }
|
||||
|
||||
it 'react with emoji' do
|
||||
expect(subject.count).to eq 2
|
||||
expect(subject.pluck(:name)).to contain_exactly('😀', '😂')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when already reacted by other account' do
|
||||
let(:name) { '😂' }
|
||||
|
||||
before { Fabricate(:emoji_reaction, status: status, name: '😀') }
|
||||
|
||||
it 'react with emoji' do
|
||||
expect(subject.count).to eq 1
|
||||
expect(subject.pluck(:name)).to contain_exactly('😂')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when already reacted same emoji by other account', :tag do
|
||||
before { Fabricate(:emoji_reaction, status: status, name: '😀') }
|
||||
|
||||
it 'react with emoji' do
|
||||
expect(subject.count).to eq 1
|
||||
expect(subject.first.name).to eq '😀'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when over limit' do
|
||||
let(:name) { '🚗' }
|
||||
|
||||
before do
|
||||
Fabricate(:emoji_reaction, status: status, account: sender, name: '😀')
|
||||
Fabricate(:emoji_reaction, status: status, account: sender, name: '😎')
|
||||
Fabricate(:emoji_reaction, status: status, account: sender, name: '🐟')
|
||||
end
|
||||
|
||||
it 'react with emoji' do
|
||||
expect { subject.count }.to raise_error Mastodon::ValidationError
|
||||
|
||||
reactions = EmojiReaction.where(status: status, account: sender).pluck(:name)
|
||||
expect(reactions.size).to eq 3
|
||||
expect(reactions).to contain_exactly('😀', '😎', '🐟')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with custom emoji of local' do
|
||||
let(:name) { 'ohagi' }
|
||||
let!(:custom_emoji) { Fabricate(:custom_emoji, shortcode: 'ohagi') }
|
||||
|
||||
it 'react with emoji' do
|
||||
expect(subject.count).to eq 1
|
||||
expect(subject.first.name).to eq 'ohagi'
|
||||
expect(subject.first.custom_emoji.id).to eq custom_emoji.id
|
||||
end
|
||||
end
|
||||
|
||||
context 'with custom emoji but not existing' do
|
||||
let(:name) { 'ohagi' }
|
||||
|
||||
it 'react with emoji' do
|
||||
expect { subject.count }.to raise_error ActiveRecord::RecordInvalid
|
||||
expect(EmojiReaction.exists?(status: status, account: sender, name: 'ohagi')).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'with custom emoji of remote' do
|
||||
let(:name) { 'ohagi@foo.bar' }
|
||||
let!(:custom_emoji) { Fabricate(:custom_emoji, shortcode: 'ohagi', domain: 'foo.bar', uri: 'https://foo.bar/emoji/ohagi') }
|
||||
|
||||
before { Fabricate(:emoji_reaction, status: status, name: 'ohagi', custom_emoji: custom_emoji) }
|
||||
|
||||
it 'react with emoji' do
|
||||
expect(subject.count).to eq 1
|
||||
expect(subject.first.name).to eq 'ohagi'
|
||||
expect(subject.first.custom_emoji.id).to eq custom_emoji.id
|
||||
end
|
||||
end
|
||||
|
||||
context 'with custom emoji of remote without existing one' do
|
||||
let(:name) { 'ohagi@foo.bar' }
|
||||
|
||||
before { Fabricate(:custom_emoji, shortcode: 'ohagi', domain: 'foo.bar', uri: 'https://foo.bar/emoji/ohagi') }
|
||||
|
||||
it 'react with emoji' do
|
||||
expect(subject.count).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
context 'with custom emoji of remote but local has same name emoji' do
|
||||
let(:name) { 'ohagi@foo.bar' }
|
||||
let!(:custom_emoji) { Fabricate(:custom_emoji, shortcode: 'ohagi', domain: 'foo.bar', uri: 'https://foo.bar/emoji/ohagi') }
|
||||
|
||||
before do
|
||||
Fabricate(:custom_emoji, shortcode: 'ohagi', domain: nil)
|
||||
Fabricate(:emoji_reaction, status: status, name: 'ohagi', custom_emoji: custom_emoji)
|
||||
end
|
||||
|
||||
it 'react with emoji' do
|
||||
expect(subject.count).to eq 1
|
||||
expect(subject.first.name).to eq 'ohagi'
|
||||
expect(subject.first.custom_emoji.id).to eq custom_emoji.id
|
||||
expect(subject.first.custom_emoji.domain).to eq 'foo.bar'
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue