Merge commit '5a0483ed21
' into kb_migration
This commit is contained in:
commit
7b735921dc
26 changed files with 204 additions and 50 deletions
87
app/controllers/activitypub/references_controller.rb
Normal file
87
app/controllers/activitypub/references_controller.rb
Normal file
|
@ -0,0 +1,87 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::ReferencesController < ActivityPub::BaseController
|
||||
include SignatureVerification
|
||||
include Authorization
|
||||
include AccountOwnedConcern
|
||||
|
||||
REFERENCES_LIMIT = 5
|
||||
|
||||
before_action :require_signature!, if: :authorized_fetch_mode?
|
||||
before_action :set_status
|
||||
|
||||
def index
|
||||
expires_in 0, public: public_fetch_mode?
|
||||
render json: references_collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json', skip_activities: true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def pundit_user
|
||||
signed_request_account
|
||||
end
|
||||
|
||||
def set_status
|
||||
@status = @account.statuses.find(params[:status_id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
||||
def load_statuses
|
||||
cached_references
|
||||
end
|
||||
|
||||
def cached_references
|
||||
cache_collection(Status.where(id: results).reorder(:id), Status)
|
||||
end
|
||||
|
||||
def results
|
||||
@results ||= begin
|
||||
references = @status.reference_objects.order(target_status_id: :asc)
|
||||
references = references.where('target_status_id > ?', page_params[:min_id]) if page_params[:min_id].present?
|
||||
references = references.limit(limit_param(REFERENCES_LIMIT))
|
||||
references.pluck(:target_status_id)
|
||||
end
|
||||
end
|
||||
|
||||
def pagination_min_id
|
||||
results.last
|
||||
end
|
||||
|
||||
def records_continue?
|
||||
results.size == limit_param(REFERENCES_LIMIT)
|
||||
end
|
||||
|
||||
def references_collection_presenter
|
||||
page = ActivityPub::CollectionPresenter.new(
|
||||
id: ActivityPub::TagManager.instance.references_uri_for(@status, page_params),
|
||||
type: :unordered,
|
||||
part_of: ActivityPub::TagManager.instance.references_uri_for(@status),
|
||||
items: load_statuses.map(&:uri),
|
||||
next: next_page
|
||||
)
|
||||
|
||||
return page if page_requested?
|
||||
|
||||
ActivityPub::CollectionPresenter.new(
|
||||
type: :unordered,
|
||||
id: ActivityPub::TagManager.instance.references_uri_for(@status),
|
||||
first: page
|
||||
)
|
||||
end
|
||||
|
||||
def page_requested?
|
||||
truthy_param?(:page)
|
||||
end
|
||||
|
||||
def next_page
|
||||
return unless records_continue?
|
||||
|
||||
ActivityPub::TagManager.instance.references_uri_for(@status, page_params.merge(min_id: pagination_min_id))
|
||||
end
|
||||
|
||||
def page_params
|
||||
params_slice(:min_id, :limit).merge(page: true)
|
||||
end
|
||||
end
|
|
@ -52,11 +52,11 @@ class Api::V1::FiltersController < Api::BaseController
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.permit(:phrase, :expires_in, :irreversible, :whole_word, context: [])
|
||||
params.permit(:phrase, :expires_in, :irreversible, :exclude_follows, :exclude_localusers, :whole_word, context: [])
|
||||
end
|
||||
|
||||
def filter_params
|
||||
resource_params.slice(:phrase, :expires_in, :irreversible, :context)
|
||||
resource_params.slice(:phrase, :expires_in, :irreversible, :exclude_follows, :exclude_localusers, :context)
|
||||
end
|
||||
|
||||
def keyword_params
|
||||
|
|
|
@ -43,6 +43,6 @@ class Api::V2::FiltersController < Api::BaseController
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.permit(:title, :expires_in, :filter_action, context: [], keywords_attributes: [:id, :keyword, :whole_word, :_destroy])
|
||||
params.permit(:title, :expires_in, :filter_action, :exclude_follows, :exclude_localusers, context: [], keywords_attributes: [:id, :keyword, :whole_word, :_destroy])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -49,7 +49,7 @@ class FiltersController < ApplicationController
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:custom_filter).permit(:title, :expires_in, :filter_action, context: [], keywords_attributes: [:id, :keyword, :whole_word, :_destroy])
|
||||
params.require(:custom_filter).permit(:title, :expires_in, :filter_action, :exclude_follows, :exclude_localusers, context: [], keywords_attributes: [:id, :keyword, :whole_word, :_destroy])
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
|
|
|
@ -25,6 +25,7 @@ module ContextHelper
|
|||
searchable_by: { 'fedibird' => 'http://fedibird.com/ns#', 'searchableBy' => { '@id' => 'fedibird:searchableBy', '@type' => '@id' } },
|
||||
subscribable_by: { 'kmyblue' => 'http://kmy.blue/ns#', 'subscribableBy' => { '@id' => 'kmyblue:subscribableBy', '@type' => '@id' } },
|
||||
other_setting: { 'fedibird' => 'http://fedibird.com/ns#', 'otherSetting' => 'fedibird:otherSetting' },
|
||||
references: { 'fedibird' => 'http://fedibird.com/ns#', 'references' => { '@id' => 'fedibird:references', '@type' => '@id' } },
|
||||
olm: { 'toot' => 'http://joinmastodon.org/ns#', 'Device' => 'toot:Device', 'Ed25519Signature' => 'toot:Ed25519Signature', 'Ed25519Key' => 'toot:Ed25519Key', 'Curve25519Key' => 'toot:Curve25519Key', 'EncryptedMessage' => 'toot:EncryptedMessage', 'publicKeyBase64' => 'toot:publicKeyBase64', 'deviceId' => 'toot:deviceId', 'claim' => { '@type' => '@id', '@id' => 'toot:claim' }, 'fingerprintKey' => { '@type' => '@id', '@id' => 'toot:fingerprintKey' }, 'identityKey' => { '@type' => '@id', '@id' => 'toot:identityKey' }, 'devices' => { '@type' => '@id', '@id' => 'toot:devices' }, 'messageFranking' => 'toot:messageFranking', 'messageType' => 'toot:messageType', 'cipherText' => 'toot:cipherText' },
|
||||
suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' },
|
||||
}.freeze
|
||||
|
|
|
@ -473,7 +473,9 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
end
|
||||
|
||||
def process_references!
|
||||
references = @json['references'].nil? ? [] : ActivityPub::FetchReferencesService(@json['references'])
|
||||
references = @object['references'].nil? ? [] : ActivityPub::FetchReferencesService.new.call(@status, @object['references'])
|
||||
quote = @object['quote'] || @object['quoteUrl'] || @object['quoteURL'] || @object['_misskey_quote']
|
||||
references << quote if quote
|
||||
ProcessReferencesWorker.perform_async(@status.id, [], references)
|
||||
end
|
||||
|
||||
|
|
|
@ -78,6 +78,12 @@ class ActivityPub::TagManager
|
|||
account_status_replies_url(target.account, target, page_params)
|
||||
end
|
||||
|
||||
def references_uri_for(target, page_params = nil)
|
||||
raise ArgumentError, 'target must be a local activity' unless %i(note comment activity).include?(target.object_type) && target.local?
|
||||
|
||||
account_status_references_url(target.account, target, page_params)
|
||||
end
|
||||
|
||||
def followers_uri_for(target)
|
||||
target.local? ? account_followers_url(target) : target.followers_url.presence
|
||||
end
|
||||
|
|
|
@ -265,7 +265,7 @@ module AccountInteractions
|
|||
|
||||
def status_matches_filters(status)
|
||||
active_filters = CustomFilter.cached_filters_for(id)
|
||||
CustomFilter.apply_cached_filters(active_filters, status)
|
||||
CustomFilter.apply_cached_filters(active_filters, status, following?(status.account))
|
||||
end
|
||||
|
||||
def followers_for_local_distribution
|
||||
|
|
|
@ -4,14 +4,16 @@
|
|||
#
|
||||
# Table name: custom_filters
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# account_id :bigint(8)
|
||||
# expires_at :datetime
|
||||
# phrase :text default(""), not null
|
||||
# context :string default([]), not null, is an Array
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# action :integer default("warn"), not null
|
||||
# id :bigint(8) not null, primary key
|
||||
# account_id :bigint(8)
|
||||
# expires_at :datetime
|
||||
# phrase :text default(""), not null
|
||||
# context :string default([]), not null, is an Array
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# action :integer default("warn"), not null
|
||||
# exclude_follows :boolean default(FALSE), not null
|
||||
# exclude_localusers :boolean default(FALSE), not null
|
||||
#
|
||||
|
||||
class CustomFilter < ApplicationRecord
|
||||
|
@ -94,8 +96,11 @@ class CustomFilter < ApplicationRecord
|
|||
active_filters.select { |custom_filter, _| !custom_filter.expired? }
|
||||
end
|
||||
|
||||
def self.apply_cached_filters(cached_filters, status)
|
||||
def self.apply_cached_filters(cached_filters, status, following)
|
||||
cached_filters.filter_map do |filter, rules|
|
||||
next if filter.exclude_follows && following
|
||||
next if filter.exclude_localusers && status.account.local?
|
||||
|
||||
match = rules[:keywords].match(status.proper.searchable_text) if rules[:keywords].present?
|
||||
keyword_matches = [match.to_s] unless match.nil?
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# id :bigint(8) not null, primary key
|
||||
# custom_filter_id :bigint(8) not null
|
||||
# keyword :text default(""), not null
|
||||
# whole_word :boolean default(TRUE), not null
|
||||
# whole_word :boolean default(FALSE), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
|
|
|
@ -47,8 +47,8 @@ class UserSettings
|
|||
setting :reduce_motion, default: false
|
||||
setting :expand_content_warnings, default: false
|
||||
setting :display_media, default: 'default', in: %w(default show_all hide_all)
|
||||
setting :display_media_expand, default: false
|
||||
setting :auto_play, default: false
|
||||
setting :display_media_expand, default: true
|
||||
setting :auto_play, default: true
|
||||
end
|
||||
|
||||
namespace :notification_emails do
|
||||
|
|
|
@ -7,6 +7,8 @@ class StatusRelationshipsPresenter
|
|||
:bookmarks_map, :filters_map, :emoji_reactions_map
|
||||
|
||||
def initialize(statuses, current_account_id = nil, **options)
|
||||
@current_account_id = current_account_id
|
||||
|
||||
if current_account_id.nil?
|
||||
@reblogs_map = {}
|
||||
@favourites_map = {}
|
||||
|
@ -37,7 +39,7 @@ class StatusRelationshipsPresenter
|
|||
active_filters = CustomFilter.cached_filters_for(current_account_id)
|
||||
|
||||
@filters_map = statuses.each_with_object({}) do |status, h|
|
||||
filter_matches = CustomFilter.apply_cached_filters(active_filters, status)
|
||||
filter_matches = CustomFilter.apply_cached_filters(active_filters, status, following?(status.account_id))
|
||||
|
||||
unless filter_matches.empty?
|
||||
h[status.id] = filter_matches
|
||||
|
@ -45,4 +47,14 @@ class StatusRelationshipsPresenter
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
def following?(other_account_id)
|
||||
return false if @current_account_id.nil?
|
||||
|
||||
@account ||= Account.find(@current_account_id)
|
||||
return false unless @account
|
||||
|
||||
@following_map ||= @account.following.pluck(:id)
|
||||
@following_map.include?(other_account_id)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
class ActivityPub::NoteSerializer < ActivityPub::Serializer
|
||||
include FormattingHelper
|
||||
|
||||
context_extensions :atom_uri, :conversation, :sensitive, :voters_count, :searchable_by
|
||||
context_extensions :atom_uri, :conversation, :sensitive, :voters_count, :searchable_by, :references
|
||||
|
||||
attributes :id, :type, :summary,
|
||||
:in_reply_to, :published, :url,
|
||||
:attributed_to, :to, :cc, :sensitive,
|
||||
:atom_uri, :in_reply_to_atom_uri,
|
||||
:conversation, :searchable_by
|
||||
:conversation, :searchable_by, :references
|
||||
|
||||
attribute :content
|
||||
attribute :content_map, if: :language?
|
||||
|
@ -64,6 +64,10 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
|
|||
)
|
||||
end
|
||||
|
||||
def references
|
||||
ActivityPub::TagManager.instance.references_uri_for(object)
|
||||
end
|
||||
|
||||
def language?
|
||||
object.language.present?
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class REST::FilterSerializer < ActiveModel::Serializer
|
||||
attributes :id, :title, :context, :expires_at, :filter_action
|
||||
attributes :id, :title, :exclude_follows, :exclude_localusers, :context, :expires_at, :filter_action
|
||||
has_many :keywords, serializer: REST::FilterKeywordSerializer, if: :rules_requested?
|
||||
has_many :statuses, serializer: REST::FilterStatusSerializer, if: :rules_requested?
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
class REST::V1::FilterSerializer < ActiveModel::Serializer
|
||||
attributes :id, :phrase, :context, :whole_word, :expires_at,
|
||||
:irreversible
|
||||
:irreversible, :exclude_follows, :exclude_localusers
|
||||
|
||||
delegate :context, :expires_at, to: :custom_filter
|
||||
|
||||
|
@ -18,6 +18,10 @@ class REST::V1::FilterSerializer < ActiveModel::Serializer
|
|||
custom_filter.irreversible?
|
||||
end
|
||||
|
||||
delegate :exclude_follows, to: :custom_filter
|
||||
|
||||
delegate :exclude_localusers, to: :custom_filter
|
||||
|
||||
private
|
||||
|
||||
def custom_filter
|
||||
|
|
|
@ -13,36 +13,23 @@ class ActivityPub::FetchReferencesService < BaseService
|
|||
|
||||
def collection_items(collection_or_uri)
|
||||
collection = fetch_collection(collection_or_uri)
|
||||
return unless collection.is_a?(Hash) && collection['first'].present?
|
||||
return unless collection.is_a?(Hash)
|
||||
|
||||
all_items = []
|
||||
collection = fetch_collection(collection['first'])
|
||||
collection = fetch_collection(collection['first']) if collection['first'].present?
|
||||
return unless collection.is_a?(Hash)
|
||||
|
||||
while collection.is_a?(Hash)
|
||||
items = begin
|
||||
case collection['type']
|
||||
when 'Collection', 'CollectionPage'
|
||||
collection['items']
|
||||
when 'OrderedCollection', 'OrderedCollectionPage'
|
||||
collection['orderedItems']
|
||||
end
|
||||
end
|
||||
|
||||
break if items.blank?
|
||||
|
||||
all_items.concat(items)
|
||||
|
||||
break if all_items.size >= StatusReferenceValidator::LIMIT
|
||||
|
||||
collection = collection['next'].present? ? fetch_collection(collection['next']) : nil
|
||||
case collection['type']
|
||||
when 'Collection', 'CollectionPage'
|
||||
collection['items']
|
||||
when 'OrderedCollection', 'OrderedCollectionPage'
|
||||
collection['orderedItems']
|
||||
end
|
||||
|
||||
all_items
|
||||
end
|
||||
|
||||
def fetch_collection(collection_or_uri)
|
||||
return collection_or_uri if collection_or_uri.is_a?(Hash)
|
||||
return if invalid_origin?(collection_or_uri)
|
||||
return if unsupported_uri_scheme?(collection_or_uri)
|
||||
return if ActivityPub::TagManager.instance.local_uri?(collection_or_uri)
|
||||
|
||||
fetch_resource_without_id_validation(collection_or_uri, nil, true)
|
||||
end
|
||||
|
|
|
@ -45,6 +45,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
|||
create_edits!
|
||||
end
|
||||
|
||||
update_references!
|
||||
download_media_files!
|
||||
queue_poll_notifications!
|
||||
|
||||
|
@ -240,6 +241,13 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
|||
end
|
||||
end
|
||||
|
||||
def update_references!
|
||||
references = @json['references'].nil? ? [] : ActivityPub::FetchReferencesService.new.call(@status, @json['references'])
|
||||
quote = @json['quote'] || @json['quoteUrl'] || @json['quoteURL'] || @json['_misskey_quote']
|
||||
references << quote if quote
|
||||
ProcessReferencesWorker.perform_async(@status.id, [], references)
|
||||
end
|
||||
|
||||
def expected_type?
|
||||
equals_or_includes_any?(@json['type'], %w(Note Question))
|
||||
end
|
||||
|
|
|
@ -76,6 +76,7 @@ class PostStatusService < BaseService
|
|||
@visibility = :unlisted if (@visibility&.to_sym == :public || @visibility&.to_sym == :public_unlisted || @visibility&.to_sym == :login) && @account.silenced?
|
||||
@visibility = :public_unlisted if @visibility&.to_sym == :public && !@options[:force_visibility] && !@options[:application]&.superapp && @account.user&.setting_public_post_to_unlisted
|
||||
@searchability = searchability
|
||||
@searchability = :private if @account.silenced? && @searchability&.to_sym == :public
|
||||
@markdown = @options[:markdown] || false
|
||||
@scheduled_at = @options[:scheduled_at]&.to_datetime
|
||||
@scheduled_at = nil if scheduled_in_the_past?
|
||||
|
|
|
@ -68,6 +68,8 @@ class ProcessReferencesService < BaseService
|
|||
end
|
||||
|
||||
def create_notifications!
|
||||
return if @added_objects.blank?
|
||||
|
||||
local_reference_objects = @added_objects.filter { |ref| ref.target_status.account.local? }
|
||||
return if local_reference_objects.empty?
|
||||
|
||||
|
@ -80,6 +82,7 @@ class ProcessReferencesService < BaseService
|
|||
return if removed_references.empty?
|
||||
|
||||
statuses = Status.where(id: removed_references)
|
||||
|
||||
@status.reference_objects.where(target_status: statuses).destroy_all
|
||||
statuses.each do |status|
|
||||
status.decrement_count!(:status_referred_by_count)
|
||||
|
|
|
@ -12,6 +12,10 @@
|
|||
.fields-group
|
||||
= f.input :filter_action, as: :radio_buttons, collection: %i(warn hide), include_blank: false, wrapper: :with_block_label, label_method: ->(action) { safe_join([t("simple_form.labels.filters.actions.#{action}"), content_tag(:span, t("simple_form.hints.filters.actions.#{action}"), class: 'hint')]) }, hint: t('simple_form.hints.filters.action'), required: true
|
||||
|
||||
.fields-group
|
||||
= f.input :exclude_follows, wrapper: :with_label, kmyblue: true, label: t('simple_form.labels.filters.options.exclude_follows')
|
||||
= f.input :exclude_localusers, wrapper: :with_label, kmyblue: true, label: t('simple_form.labels.filters.options.exclude_localusers')
|
||||
|
||||
%hr.spacer/
|
||||
|
||||
- unless f.object.statuses.empty?
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue