Add antenna and bio-searchability support
This commit is contained in:
parent
7e125b276f
commit
2fef21664b
35 changed files with 775 additions and 6 deletions
76
app/controllers/antennas_controller.rb
Normal file
76
app/controllers/antennas_controller.rb
Normal file
|
@ -0,0 +1,76 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AntennasController < ApplicationController
|
||||
layout 'admin'
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :set_antenna, only: [:edit, :update, :destroy]
|
||||
before_action :set_lists, only: [:new, :edit]
|
||||
before_action :set_body_classes
|
||||
before_action :set_cache_headers
|
||||
|
||||
def index
|
||||
@antennas = current_account.antennas.includes(:antenna_domains).includes(:antenna_tags).includes(:antenna_accounts)
|
||||
end
|
||||
|
||||
def new
|
||||
@antenna = current_account.antennas.build
|
||||
@antenna.antenna_domains.build
|
||||
@antenna.antenna_tags.build
|
||||
@antenna.antenna_accounts.build
|
||||
end
|
||||
|
||||
def create
|
||||
@antenna = current_account.antennas.build(thin_resource_params)
|
||||
|
||||
saved = @antenna.save
|
||||
saved = @antenna.update(resource_params) if saved
|
||||
|
||||
if saved
|
||||
redirect_to antennas_path
|
||||
else
|
||||
render action: :new
|
||||
end
|
||||
end
|
||||
|
||||
def edit; end
|
||||
|
||||
def update
|
||||
if @antenna.update(resource_params)
|
||||
redirect_to antennas_path
|
||||
else
|
||||
render action: :edit
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@antenna.destroy
|
||||
redirect_to antennas_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_antenna
|
||||
@antenna = current_account.antennas.find(params[:id])
|
||||
end
|
||||
|
||||
def set_lists
|
||||
@lists = current_account.owned_lists
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:antenna).permit(:title, :list, :available, :expires_in, :keywords_raw, :exclude_keywords_raw, :domains_raw, :exclude_domains_raw, :accounts_raw, :exclude_accounts_raw, :tags_raw, :exclude_tags_raw)
|
||||
end
|
||||
|
||||
def thin_resource_params
|
||||
params.require(:antenna).permit(:title, :list)
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'admin'
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.cache_control.replace(private: true, no_store: true)
|
||||
end
|
||||
end
|
|
@ -30,6 +30,7 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
|
|||
:bot,
|
||||
:discoverable,
|
||||
:searchability,
|
||||
:dissubscribable,
|
||||
:hide_collections,
|
||||
fields_attributes: [:name, :value]
|
||||
)
|
||||
|
|
|
@ -20,7 +20,7 @@ class Settings::ProfilesController < Settings::BaseController
|
|||
private
|
||||
|
||||
def account_params
|
||||
params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, :my_actor_type, :searchability, :group_allow_private_message, :discoverable, :hide_collections, fields_attributes: [:name, :value])
|
||||
params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, :my_actor_type, :searchability, :dissubscribable, :group_allow_private_message, :discoverable, :hide_collections, fields_attributes: [:name, :value])
|
||||
end
|
||||
|
||||
def set_account
|
||||
|
|
|
@ -23,6 +23,7 @@ module ContextHelper
|
|||
voters_count: { 'toot' => 'http://joinmastodon.org/ns#', 'votersCount' => 'toot:votersCount' },
|
||||
emoji_reactions: { 'fedibird' => 'http://fedibird.com/ns#', 'emojiReactions' => { '@id' => "fedibird:emojiReactions", '@type' => '@id' } },
|
||||
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' } },
|
||||
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
|
||||
|
|
|
@ -1062,6 +1062,13 @@ a.name-tag,
|
|||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.listname {
|
||||
color: $dark-text-color;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.expiration {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
|
|
@ -449,10 +449,26 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
end
|
||||
end
|
||||
|
||||
SCAN_SEARCHABILITY_RE = /\[searchability:(public|followers|reactors|private)\]/.freeze
|
||||
|
||||
def searchability
|
||||
searchability = searchability_from_audience
|
||||
|
||||
return nil if searchability.nil?
|
||||
if searchability.nil?
|
||||
note = @account&.note
|
||||
return nil unless note.present?
|
||||
|
||||
searchability_bio = note.scan(SCAN_SEARCHABILITY_RE).first
|
||||
return nil unless searchability_bio
|
||||
|
||||
searchability = searchability_bio[0]
|
||||
return nil if searchability.nil?
|
||||
|
||||
searchability = :public if searchability == 'public'
|
||||
searchability = :unlisted if searchability == 'followers'
|
||||
searchability = :direct if searchability == 'private'
|
||||
searchability = :private if searchability == 'reactors'
|
||||
end
|
||||
|
||||
visibility = visibility_from_audience_with_silence
|
||||
|
||||
|
|
|
@ -186,6 +186,10 @@ class ActivityPub::TagManager
|
|||
nil
|
||||
end
|
||||
|
||||
def subscribable_by(account)
|
||||
account.dissubscribable ? [] : [COLLECTIONS[:public]]
|
||||
end
|
||||
|
||||
def searchable_by(status)
|
||||
searchable_by =
|
||||
case status.compute_searchability_activitypub
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
# requested_review_at :datetime
|
||||
# group_allow_private_message :boolean
|
||||
# searchability :integer default("private"), not null
|
||||
# dissubscribable :boolean default(FALSE), not null
|
||||
#
|
||||
|
||||
class Account < ApplicationRecord
|
||||
|
|
203
app/models/antenna.rb
Normal file
203
app/models/antenna.rb
Normal file
|
@ -0,0 +1,203 @@
|
|||
# == Schema Information
|
||||
#
|
||||
# Table name: antennas
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# account_id :bigint(8) not null
|
||||
# list_id :bigint(8) not null
|
||||
# title :string default(""), not null
|
||||
# keywords :jsonb
|
||||
# exclude_keywords :jsonb
|
||||
# any_domains :boolean default(TRUE), not null
|
||||
# any_tags :boolean default(TRUE), not null
|
||||
# any_accounts :boolean default(TRUE), not null
|
||||
# any_keywords :boolean default(TRUE), not null
|
||||
# available :boolean default(TRUE), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# expires_at :datetime
|
||||
# with_media_only :boolean default(FALSE), not null
|
||||
#
|
||||
class Antenna < ApplicationRecord
|
||||
include Expireable
|
||||
|
||||
has_many :antenna_domains, inverse_of: :antenna, dependent: :destroy
|
||||
has_many :antenna_tags, inverse_of: :antenna, dependent: :destroy
|
||||
has_many :antenna_accounts, inverse_of: :antenna, dependent: :destroy
|
||||
|
||||
belongs_to :account
|
||||
belongs_to :list
|
||||
|
||||
scope :all_keywords, -> { where(any_keywords: true) }
|
||||
scope :all_domains, -> { where(any_domains: true) }
|
||||
scope :all_accounts, -> { where(any_accounts: true) }
|
||||
scope :all_tags, -> { where(any_tags: true) }
|
||||
scope :availables, -> { where(available: true).where(Arel.sql('any_keywords = FALSE OR any_domains = FALSE OR any_accounts = FALSE OR any_tags = FALSE')) }
|
||||
|
||||
def enabled?
|
||||
enabled_config? && !expired?
|
||||
end
|
||||
|
||||
def enabled_config?
|
||||
available && enabled_config_raws?
|
||||
end
|
||||
|
||||
def enabled_config_raws?
|
||||
!(any_keywords && any_domains && any_accounts && any_tags)
|
||||
end
|
||||
|
||||
def expires_in
|
||||
return @expires_in if defined?(@expires_in)
|
||||
return nil if expires_at.nil?
|
||||
|
||||
[30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].find { |expires_in| expires_in.from_now >= expires_at }
|
||||
end
|
||||
|
||||
def context
|
||||
context = []
|
||||
context << 'domain' if !any_domains
|
||||
context << 'tag' if !any_tags
|
||||
context << 'keyword' if !any_keywords
|
||||
context << 'account' if !any_accounts
|
||||
context
|
||||
end
|
||||
|
||||
def list=(list_id)
|
||||
list_id = list_id.to_i if list_id.is_a?(String)
|
||||
if list_id.is_a?(Numeric)
|
||||
self[:list_id] = list_id
|
||||
else
|
||||
self[:list] = list_id
|
||||
end
|
||||
end
|
||||
|
||||
def keywords_raw
|
||||
return '' if !keywords.present?
|
||||
|
||||
keywords.join("\n")
|
||||
end
|
||||
|
||||
def keywords_raw=(raw)
|
||||
keywords = raw.split(/\R/).filter { |r| r.present? && r.length >= 2 }.uniq
|
||||
self[:keywords] = keywords
|
||||
self[:any_keywords] = !keywords.any? && !exclude_keywords&.any?
|
||||
end
|
||||
|
||||
def exclude_keywords_raw
|
||||
return '' if !exclude_keywords.present?
|
||||
|
||||
exclude_keywords.join("\n")
|
||||
end
|
||||
|
||||
def exclude_keywords_raw=(raw)
|
||||
exclude_keywords = raw.split(/\R/).filter { |r| r.present? }.uniq
|
||||
self[:exclude_keywords] = exclude_keywords
|
||||
self[:any_keywords] = !keywords&.any? && !exclude_keywords.any?
|
||||
end
|
||||
|
||||
def tags_raw
|
||||
antenna_tags.where(exclude: false).map(&:tag).map(&:name).join("\n")
|
||||
end
|
||||
|
||||
def tags_raw=(raw)
|
||||
return if tags_raw == raw
|
||||
|
||||
tag_names = raw.split(/\R/).filter { |r| r.present? }.map { |r| r.start_with?('#') ? r[1..-1] : r }.uniq
|
||||
|
||||
antenna_tags.where(exclude: false).destroy_all
|
||||
Tag.find_or_create_by_names(tag_names).each do |tag|
|
||||
antenna_tags.create!(tag: tag, exclude: false)
|
||||
end
|
||||
self[:any_tags] = !tag_names.any?
|
||||
end
|
||||
|
||||
def exclude_tags_raw
|
||||
antenna_tags.where(exclude: true).map(&:tag).map(&:name).join("\n")
|
||||
end
|
||||
|
||||
def exclude_tags_raw=(raw)
|
||||
return if exclude_tags_raw == raw
|
||||
|
||||
tag_names = raw.split(/\R/).filter { |r| r.present? }.map { |r| r.start_with?('#') ? r[1..-1] : r }.uniq
|
||||
|
||||
antenna_tags.where(exclude: true).destroy_all
|
||||
Tag.find_or_create_by_names(tag_names).each do |tag|
|
||||
antenna_tags.create!(tag: tag, exclude: true)
|
||||
end
|
||||
end
|
||||
|
||||
def domains_raw
|
||||
antenna_domains.where(exclude: false).map(&:name).join("\n")
|
||||
end
|
||||
|
||||
def domains_raw=(raw)
|
||||
return if domains_raw == raw
|
||||
|
||||
domain_names = raw.split(/\R/).filter { |r| r.present? }.uniq
|
||||
|
||||
antenna_domains.where(exclude: false).destroy_all
|
||||
domain_names.each do |domain|
|
||||
antenna_domains.create!(name: domain, exclude: false)
|
||||
end
|
||||
self[:any_domains] = !domain_names.any?
|
||||
end
|
||||
|
||||
def exclude_domains_raw
|
||||
antenna_domains.where(exclude: true).map(&:name).join("\n")
|
||||
end
|
||||
|
||||
def exclude_domains_raw=(raw)
|
||||
return if exclude_domains_raw == raw
|
||||
|
||||
domain_names = raw.split(/\R/).filter { |r| r.present? }.uniq
|
||||
|
||||
antenna_domains.where(exclude: true).destroy_all
|
||||
domain_names.each do |domain|
|
||||
antenna_domains.create!(name: domain, exclude: true)
|
||||
end
|
||||
end
|
||||
|
||||
def accounts_raw
|
||||
antenna_accounts.where(exclude: false).map(&:account).map { |account| account.domain ? "@#{account.username}@#{account.domain}" : "@#{account.username}" }.join("\n")
|
||||
end
|
||||
|
||||
def accounts_raw=(raw)
|
||||
return if accounts_raw == raw
|
||||
|
||||
account_names = raw.split(/\R/).filter { |r| r.present? }.map { |r| r.start_with?('@') ? r[1..-1] : r }.uniq
|
||||
|
||||
hit = false
|
||||
antenna_accounts.where(exclude: false).destroy_all
|
||||
account_names.each do |name|
|
||||
username, domain = name.split('@')
|
||||
account = Account.find_by(username: username, domain: domain)
|
||||
if account.present?
|
||||
antenna_accounts.create!(account: account, exclude: false)
|
||||
hit = true
|
||||
end
|
||||
end
|
||||
self[:any_accounts] = !hit
|
||||
end
|
||||
|
||||
def exclude_accounts_raw
|
||||
antenna_accounts.where(exclude: true).map(&:account).map { |account| account.domain ? "@#{account.username}@#{account.domain}" : "@#{account.username}" }.join("\n")
|
||||
end
|
||||
|
||||
def exclude_accounts_raw=(raw)
|
||||
return if exclude_accounts_raw == raw
|
||||
|
||||
account_names = raw.split(/\R/).filter { |r| r.present? }.map { |r| r.start_with?('@') ? r[1..-1] : r }.uniq
|
||||
|
||||
hit = false
|
||||
antenna_accounts.where(exclude: true).destroy_all
|
||||
account_names.each do |name|
|
||||
username, domain = name.split('@')
|
||||
account = Account.find_by(username: username, domain: domain)
|
||||
if account.present?
|
||||
antenna_accounts.create!(account: account, exclude: true)
|
||||
hit = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
17
app/models/antenna_account.rb
Normal file
17
app/models/antenna_account.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
# == Schema Information
|
||||
#
|
||||
# Table name: antenna_accounts
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# antenna_id :bigint(8) not null
|
||||
# account_id :bigint(8) not null
|
||||
# exclude :boolean default(FALSE), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
class AntennaAccount < ApplicationRecord
|
||||
|
||||
belongs_to :antenna
|
||||
belongs_to :account
|
||||
|
||||
end
|
16
app/models/antenna_domain.rb
Normal file
16
app/models/antenna_domain.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
# == Schema Information
|
||||
#
|
||||
# Table name: antenna_domains
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# antenna_id :bigint(8) not null
|
||||
# name :string
|
||||
# exclude :boolean default(FALSE), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
class AntennaDomain < ApplicationRecord
|
||||
|
||||
belongs_to :antenna
|
||||
|
||||
end
|
17
app/models/antenna_tag.rb
Normal file
17
app/models/antenna_tag.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
# == Schema Information
|
||||
#
|
||||
# Table name: antenna_tags
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# antenna_id :bigint(8) not null
|
||||
# tag_id :bigint(8) not null
|
||||
# exclude :boolean default(FALSE), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
class AntennaTag < ApplicationRecord
|
||||
|
||||
belongs_to :antenna
|
||||
belongs_to :tag
|
||||
|
||||
end
|
|
@ -39,6 +39,8 @@ module AccountAssociations
|
|||
|
||||
has_many :report_notes, dependent: :destroy
|
||||
has_many :custom_filters, inverse_of: :account, dependent: :destroy
|
||||
has_many :antennas, inverse_of: :account, dependent: :destroy
|
||||
has_many :antenna_accounts, inverse_of: :account, dependent: :destroy
|
||||
|
||||
# Moderation notes
|
||||
has_many :account_moderation_notes, dependent: :destroy, inverse_of: :account
|
||||
|
|
|
@ -23,6 +23,7 @@ class List < ApplicationRecord
|
|||
|
||||
has_many :list_accounts, inverse_of: :list, dependent: :destroy
|
||||
has_many :accounts, through: :list_accounts
|
||||
has_many :antennas, inverse_of: :list, dependent: :destroy
|
||||
|
||||
validates :title, presence: true
|
||||
|
||||
|
|
|
@ -27,6 +27,8 @@ class Tag < ApplicationRecord
|
|||
has_many :featured_tags, dependent: :destroy, inverse_of: :tag
|
||||
has_many :followers, through: :passive_relationships, source: :account
|
||||
|
||||
has_one :antenna_tag, dependent: :destroy, inverse_of: :tag
|
||||
|
||||
HASHTAG_SEPARATORS = "_\u00B7\u30FB\u200c"
|
||||
HASHTAG_FIRST_SEQUENCE_CHUNK_ONE = "[[:word:]_][[:word:]#{HASHTAG_SEPARATORS}]*[[:alpha:]#{HASHTAG_SEPARATORS}]"
|
||||
HASHTAG_FIRST_SEQUENCE_CHUNK_TWO = "[[:word:]#{HASHTAG_SEPARATORS}]*[[:word:]_]"
|
||||
|
|
|
@ -7,13 +7,13 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
|
|||
context :security
|
||||
|
||||
context_extensions :manually_approves_followers, :featured, :also_known_as,
|
||||
:moved_to, :property_value, :discoverable, :olm, :suspended, :searchable_by
|
||||
:moved_to, :property_value, :discoverable, :olm, :suspended, :searchable_by, :subscribable_by
|
||||
|
||||
attributes :id, :type, :following, :followers,
|
||||
:inbox, :outbox, :featured, :featured_tags,
|
||||
:preferred_username, :name, :summary,
|
||||
:url, :manually_approves_followers,
|
||||
:discoverable, :published, :searchable_by
|
||||
:discoverable, :published, :searchable_by, :subscribable_by
|
||||
|
||||
has_one :public_key, serializer: ActivityPub::PublicKeySerializer
|
||||
|
||||
|
@ -166,6 +166,10 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
|
|||
ActivityPub::TagManager.instance.account_searchable_by(object)
|
||||
end
|
||||
|
||||
def subscribable_by
|
||||
ActivityPub::TagManager.instance.subscribable_by(object)
|
||||
end
|
||||
|
||||
class CustomEmojiSerializer < ActivityPub::EmojiSerializer
|
||||
end
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ class REST::AccountSerializer < ActiveModel::Serializer
|
|||
include FormattingHelper
|
||||
|
||||
attributes :id, :username, :acct, :display_name, :locked, :bot, :discoverable, :group, :created_at,
|
||||
:note, :url, :avatar, :avatar_static, :header, :header_static, :searchability,
|
||||
:note, :url, :avatar, :avatar_static, :header, :header_static, :searchability, :dissubscribable,
|
||||
:followers_count, :following_count, :statuses_count, :last_status_at
|
||||
|
||||
has_one :moved_to_account, key: :moved, serializer: REST::AccountSerializer, if: :moved_and_not_nested?
|
||||
|
|
|
@ -78,6 +78,7 @@ class ActivityPub::ProcessAccountService < BaseService
|
|||
@account.suspension_origin = :local if auto_suspend?
|
||||
@account.silenced_at = domain_block.created_at if auto_silence?
|
||||
@account.searchability = :private # not null
|
||||
@account.dissubscribable = false # not null
|
||||
@account.save
|
||||
end
|
||||
|
||||
|
@ -115,6 +116,7 @@ class ActivityPub::ProcessAccountService < BaseService
|
|||
@account.also_known_as = as_array(@json['alsoKnownAs'] || []).map { |item| value_or_id(item) }
|
||||
@account.discoverable = @json['discoverable'] || false
|
||||
@account.searchability = searchability_from_audience
|
||||
@account.dissubscribable = !subscribable(@account.note)
|
||||
end
|
||||
|
||||
def set_fetchable_key!
|
||||
|
@ -249,6 +251,20 @@ class ActivityPub::ProcessAccountService < BaseService
|
|||
end
|
||||
end
|
||||
|
||||
def subscribable_by
|
||||
return nil if @json['subscribableBy'].nil?
|
||||
|
||||
@subscribable_by = as_array(@json['subscribableBy']).map { |x| value_or_id(x) }
|
||||
end
|
||||
|
||||
def subscribable(note)
|
||||
if subscribable_by.nil?
|
||||
!note.include?('[subscribable:no]')
|
||||
else
|
||||
subscribable_by.any? { |uri| ActivityPub::TagManager.instance.public_collection?(uri) }
|
||||
end
|
||||
end
|
||||
|
||||
def property_values
|
||||
return unless @json['attachment'].is_a?(Array)
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ class DeleteAccountService < BaseService
|
|||
account_pins
|
||||
active_relationships
|
||||
aliases
|
||||
antennas
|
||||
block_relationships
|
||||
blocked_by_relationships
|
||||
conversation_mutes
|
||||
|
|
|
@ -49,6 +49,7 @@ class FanOutOnWriteService < BaseService
|
|||
when :public, :unlisted, :public_unlisted, :private
|
||||
deliver_to_all_followers!
|
||||
deliver_to_lists!
|
||||
deliver_to_antennas! if [:public, :public_unlisted].include?(@status.visibility.to_sym) && !@status.account.dissubscribable
|
||||
when :limited
|
||||
deliver_to_mentioned_followers!
|
||||
else
|
||||
|
@ -115,6 +116,34 @@ class FanOutOnWriteService < BaseService
|
|||
end
|
||||
end
|
||||
|
||||
def deliver_to_antennas!
|
||||
lists = []
|
||||
antennas = Antenna.availables
|
||||
antennas = antennas.left_joins(:antenna_accounts).where(any_accounts: true).or(Antenna.availables.left_joins(:antenna_accounts) .where(antenna_accounts: { exclude: false, account: @status.account }))
|
||||
antennas = antennas.left_joins(:antenna_domains) .where(any_domains: true) .or(Antenna.availables.left_joins(:antenna_accounts).left_joins(:antenna_domains) .where(antenna_domains: { exclude: false, name: @status.account.domain }))
|
||||
antennas = antennas.left_joins(:antenna_tags) .where(any_tags: true) .or(Antenna.availables.left_joins(:antenna_accounts).left_joins(:antenna_domains).left_joins(:antenna_tags).where(antenna_tags: { exclude: false, tag: @status.tags }))
|
||||
antennas = antennas.where(account: @status.account.followers) if @status.visibility.to_sym == :unlisted
|
||||
antennas.in_batches do |ans|
|
||||
ans.each do |antenna|
|
||||
next if !antenna.enabled?
|
||||
next if antenna.keywords.any? && !([nil, :public].include?(@status.searchability&.to_sym))
|
||||
next if antenna.keywords.any? && !antenna.keywords.any? { |keyword| @status.text.include?(keyword) }
|
||||
next if antenna.exclude_keywords.any? && antenna.exclude_keywords.any? { |keyword| @status.text.include?(keyword) }
|
||||
next if antenna.antenna_accounts.where(exclude: true, account: @status.account).any?
|
||||
next if antenna.antenna_domains.where(exclude: true, name: @status.account.domain).any?
|
||||
next if antenna.antenna_tags.where(exclude: true, tag: @status.tags).any?
|
||||
lists << antenna.list
|
||||
end
|
||||
end
|
||||
lists = lists.uniq
|
||||
|
||||
if lists.any?
|
||||
FeedInsertWorker.push_bulk(lists) do |list|
|
||||
[@status.id, list.id, 'list', { 'update' => update? }]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def deliver_to_mentioned_followers!
|
||||
@status.mentions.joins(:account).merge(@account.followers_for_local_distribution).select(:id, :account_id).reorder(nil).find_in_batches do |mentions|
|
||||
FeedInsertWorker.push_bulk(mentions) do |mention|
|
||||
|
|
74
app/views/antennas/_antenna.html.haml
Normal file
74
app/views/antennas/_antenna.html.haml
Normal file
|
@ -0,0 +1,74 @@
|
|||
.filters-list__item{ class: [(antenna.expired? || !antenna.enabled_config?) && 'expired'] }
|
||||
= link_to edit_antenna_path(antenna), class: 'filters-list__item__title' do
|
||||
= antenna.title
|
||||
|
||||
- if !antenna.enabled_config?
|
||||
.expiration{ title: t('antennas.index.disabled') }
|
||||
= t('antennas.index.disabled')
|
||||
- elsif antenna.expires?
|
||||
.expiration{ title: t('antennas.index.expires_on', date: l(antenna.expires_at)) }
|
||||
- if antenna.expired?
|
||||
= t('invites.expired')
|
||||
- else
|
||||
= t('antennas.index.expires_in', distance: distance_of_time_in_words_to_now(antenna.expires_at))
|
||||
|
||||
.listname
|
||||
= antenna.list.title
|
||||
|
||||
.filters-list__item__permissions
|
||||
%ul.permissions-list
|
||||
- unless antenna.antenna_domains.empty?
|
||||
%li.permissions-list__item
|
||||
.permissions-list__item__icon
|
||||
= fa_icon('sitemap')
|
||||
.permissions-list__item__text
|
||||
.permissions-list__item__text__title
|
||||
= t('antennas.index.domains', count: antenna.antenna_domains.size)
|
||||
.permissions-list__item__text__type
|
||||
- domains = antenna.antenna_domains.map { |domain| domain.name }
|
||||
- domains = domains.take(5) + ['…'] if domains.size > 5 # TODO
|
||||
= domains.join(', ')
|
||||
- unless antenna.antenna_accounts.empty?
|
||||
%li.permissions-list__item
|
||||
.permissions-list__item__icon
|
||||
= fa_icon('users')
|
||||
.permissions-list__item__text
|
||||
.permissions-list__item__text__title
|
||||
= t('antennas.index.accounts', count: antenna.antenna_accounts.size)
|
||||
.permissions-list__item__text__type
|
||||
- accounts = antenna.antenna_accounts.map { |account| account.account.domain ? "@#{account.account.username}@#{account.account.domain}" : "@#{account.account.username}" }
|
||||
- accounts = accounts.take(5) + ['…'] if accounts.size > 5 # TODO
|
||||
= accounts.join(', ')
|
||||
- unless antenna.keywords.nil? || antenna.keywords.empty?
|
||||
%li.permissions-list__item
|
||||
.permissions-list__item__icon
|
||||
= fa_icon('paragraph')
|
||||
.permissions-list__item__text
|
||||
.permissions-list__item__text__title
|
||||
= t('antennas.index.keywords', count: antenna.keywords.size)
|
||||
.permissions-list__item__text__type
|
||||
- keywords = antenna.keywords
|
||||
- keywords = keywords.take(5) + ['…'] if keywords.size > 5 # TODO
|
||||
= keywords.join(', ')
|
||||
- unless antenna.antenna_tags.empty?
|
||||
%li.permissions-list__item
|
||||
.permissions-list__item__icon
|
||||
= fa_icon('hashtag')
|
||||
.permissions-list__item__text
|
||||
.permissions-list__item__text__title
|
||||
= t('antennas.index.tags', count: antenna.antenna_tags.size)
|
||||
.permissions-list__item__text__type
|
||||
- tags = antenna.antenna_tags.map { |tag| tag.tag.name }
|
||||
- tags = keywords.take(5) + ['…'] if tags.size > 5 # TODO
|
||||
= tags.join(', ')
|
||||
|
||||
.announcements-list__item__action-bar
|
||||
.announcements-list__item__meta
|
||||
- if antenna.enabled_config_raws?
|
||||
= t('antennas.index.contexts', contexts: antenna.context.map { |context| I18n.t("antennas.contexts.#{context}") }.join(', '))
|
||||
- else
|
||||
= t('antennas.errors.empty_contexts')
|
||||
|
||||
%div
|
||||
= table_link_to 'pencil', t('antennas.edit.title'), edit_antenna_path(antenna)
|
||||
= table_link_to 'times', t('antennas.index.delete'), antenna_path(antenna), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }
|
51
app/views/antennas/_antenna_fields.html.haml
Normal file
51
app/views/antennas/_antenna_fields.html.haml
Normal file
|
@ -0,0 +1,51 @@
|
|||
%p= t 'antennas.edit.description'
|
||||
%hr.spacer/
|
||||
|
||||
.fields-row
|
||||
.fields-row__column.fields-row__column-6.fields-group
|
||||
= f.input :title, as: :string, wrapper: :with_label, hint: false
|
||||
.fields-row__column.fields-row__column-6.fields-group
|
||||
= f.input :expires_in, wrapper: :with_label, collection: [30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].map(&:to_i), label_method: lambda { |i| I18n.t("invites.expires_in.#{i}") }, include_blank: I18n.t('invites.expires_in_prompt')
|
||||
|
||||
.fields-row
|
||||
.fields-group.fields-row__column.fields-row__column-6
|
||||
= f.input :list, collection: lists, wrapper: :with_label, label_method: lambda { |list| list.title }, label: t('antennas.edit.list'), selected: f.object.list&.id, hint: false
|
||||
.fields-group.fields-row__column.fields-row__column-6
|
||||
= f.input :available, wrapper: :with_label, label: t('antennas.edit.available'), hint: false
|
||||
|
||||
%hr.spacer/
|
||||
%p.hint= t 'antennas.edit.hint'
|
||||
%hr.spacer/
|
||||
|
||||
%h4= t('antennas.contexts.domain')
|
||||
|
||||
.fields-row
|
||||
.fields-row__column.fields-row__column-6.fields-group
|
||||
= f.input :domains_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.domains_raw')
|
||||
.fields-row__column.fields-row__column-6.fields-group
|
||||
= f.input :exclude_domains_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.exclude_domains_raw')
|
||||
|
||||
%h4= t('antennas.contexts.account')
|
||||
%p.hint= t 'antennas.edit.accounts_hint'
|
||||
|
||||
.fields-row
|
||||
.fields-row__column.fields-row__column-6.fields-group
|
||||
= f.input :accounts_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.accounts_raw')
|
||||
.fields-row__column.fields-row__column-6.fields-group
|
||||
= f.input :exclude_accounts_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.exclude_accounts_raw')
|
||||
|
||||
%h4= t('antennas.contexts.tag')
|
||||
|
||||
.fields-row
|
||||
.fields-row__column.fields-row__column-6.fields-group
|
||||
= f.input :tags_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.tags_raw')
|
||||
.fields-row__column.fields-row__column-6.fields-group
|
||||
= f.input :exclude_tags_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.exclude_tags_raw')
|
||||
|
||||
%h4= t('antennas.contexts.keyword')
|
||||
|
||||
.fields-row
|
||||
.fields-row__column.fields-row__column-6.fields-group
|
||||
= f.input :keywords_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.keywords_raw')
|
||||
.fields-row__column.fields-row__column-6.fields-group
|
||||
= f.input :exclude_keywords_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.exclude_keywords_raw')
|
8
app/views/antennas/edit.html.haml
Normal file
8
app/views/antennas/edit.html.haml
Normal file
|
@ -0,0 +1,8 @@
|
|||
- content_for :page_title do
|
||||
= t('antennas.edit.title')
|
||||
|
||||
= simple_form_for @antenna, url: antenna_path(@antenna), method: :put do |f|
|
||||
= render 'antenna_fields', f: f, lists: @lists
|
||||
|
||||
.actions
|
||||
= f.button :button, t('generic.save_changes'), type: :submit
|
14
app/views/antennas/index.html.haml
Normal file
14
app/views/antennas/index.html.haml
Normal file
|
@ -0,0 +1,14 @@
|
|||
- content_for :page_title do
|
||||
= t('antennas.index.title')
|
||||
|
||||
- content_for :heading_actions do
|
||||
= link_to t('antennas.new.title'), new_antenna_path, class: 'button'
|
||||
|
||||
.flash-message.alert
|
||||
%strong= t('antennas.beta')
|
||||
|
||||
- if @antennas.empty?
|
||||
.muted-hint.center-text= t 'antennas.index.empty'
|
||||
- else
|
||||
.applications-list
|
||||
= render partial: 'antenna', collection: @antennas
|
8
app/views/antennas/new.html.haml
Normal file
8
app/views/antennas/new.html.haml
Normal file
|
@ -0,0 +1,8 @@
|
|||
- content_for :page_title do
|
||||
= t('antennas.new.title')
|
||||
|
||||
= simple_form_for @antenna, url: antennas_path do |f|
|
||||
= render 'antenna_fields', f: f, lists: @lists
|
||||
|
||||
.actions
|
||||
= f.button :button, t('antennas.new.save'), type: :submit
|
|
@ -38,6 +38,9 @@
|
|||
.fields-group
|
||||
= f.input :hide_collections, as: :boolean, wrapper: :with_label, label: t('simple_form.labels.defaults.setting_hide_network'), hint: t('simple_form.hints.defaults.setting_hide_network')
|
||||
|
||||
.fields-group
|
||||
= f.input :dissubscribable, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.dissubscribable')
|
||||
|
||||
%hr.spacer/
|
||||
|
||||
.fields-row
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue