diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index efc1289731..db9fcce905 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -147,6 +147,7 @@ class Api::V1::StatusesController < Api::BaseController :language, :markdown, :scheduled_at, + :status_reference_ids, allowed_mentions: [], media_ids: [], media_attributes: [ diff --git a/app/javascript/mastodon/components/status_action_bar.jsx b/app/javascript/mastodon/components/status_action_bar.jsx index ced931f3f8..593fd55461 100644 --- a/app/javascript/mastodon/components/status_action_bar.jsx +++ b/app/javascript/mastodon/components/status_action_bar.jsx @@ -394,9 +394,15 @@ class StatusActionBar extends ImmutablePureComponent { ); + const following = !account.getIn(['other_settings', 'emoji_reaction_must_follower']) || (relationship && relationship.get('following')); + const followed = !account.getIn(['other_settings', 'emoji_reaction_must_following']) || (relationship && relationship.get('followed_by')); + const denyFromAll = !account.getIn(['other_settings', 'emoji_reaction_deny_from_all']); const emojiPickerButton = ( ); + const emojiPickerDropdown = (writtenByMe || ((denyFromAll) && (following) && (followed))) && ( + + ); return (
@@ -404,7 +410,7 @@ class StatusActionBar extends ImmutablePureComponent { - + {emojiPickerDropdown} {filterButton} diff --git a/app/models/account.rb b/app/models/account.rb index 41830211e8..670b2c6083 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -335,6 +335,27 @@ class Account < ApplicationRecord false end + def emoji_reactions_must_following? + return user&.settings&.[]('emoji_reactions.must_be_following') || false if user.present? + return settings['emoji_reactions_must_be_following'] || false if settings.present? + + false + end + + def emoji_reactions_must_follower? + return user&.settings&.[]('emoji_reactions.must_be_follower') || false if user.present? + return settings['emoji_reaction_must_be_follower'] || false if settings.present? + + false + end + + def emoji_reactions_deny_from_all? + return user&.settings&.[]('emoji_reactions.deny_from_all') || false if user.present? + return settings['emoji_reaction_deny_from_all'] || false if settings.present? + + false + end + def public_settings config = { 'noindex' => noindex?, @@ -343,6 +364,9 @@ class Account < ApplicationRecord 'hide_statuses_count' => hide_statuses_count?, 'hide_following_count' => hide_following_count?, 'hide_followers_count' => hide_followers_count?, + 'emoji_reaction_must_following' => emoji_reactions_must_following?, + 'emoji_reaction_must_follower' => emoji_reactions_must_follower?, + 'emoji_reaction_deny_from_all' => emoji_reactions_deny_from_all?, } config = config.merge(settings) if settings.present? config diff --git a/app/models/user_settings.rb b/app/models/user_settings.rb index a4a3548704..34ebe0484d 100644 --- a/app/models/user_settings.rb +++ b/app/models/user_settings.rb @@ -69,6 +69,12 @@ class UserSettings setting :must_be_following_dm, default: false end + namespace :emoji_reactions do + setting :must_be_follower, default: false + setting :must_be_following, default: false + setting :deny_from_all, default: false + end + def initialize(original_hash) @original_hash = original_hash || {} end diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb index 6ed9154e97..a1bc6742c0 100644 --- a/app/serializers/activitypub/note_serializer.rb +++ b/app/serializers/activitypub/note_serializer.rb @@ -9,7 +9,9 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer :in_reply_to, :published, :url, :attributed_to, :to, :cc, :sensitive, :atom_uri, :in_reply_to_atom_uri, - :conversation, :searchable_by, :references + :conversation, :searchable_by + + attribute :references, if: :not_private_post? attribute :content attribute :content_map, if: :language? @@ -150,6 +152,10 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer object.account.local? end + def not_private_post? + !object.private_visibility? + end + def poll_options object.preloadable_poll.loaded_options end diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb index 5243ed516c..58e8117ead 100644 --- a/app/services/notify_service.rb +++ b/app/services/notify_service.rb @@ -51,6 +51,18 @@ class NotifyService < BaseService @recipient.user.settings['interactions.must_be_following'] && !following_sender? end + def optional_non_follower_emoji_reaction? + emoji_reaction? && @recipient.user.settings['emoji_reactions.must_be_follower'] && !@notification.from_account.following?(@recipient) + end + + def optional_non_following_emoji_reaction? + emoji_reaction? && @recipient.user.settings['emoji_reactions.must_be_following'] && !following_sender? + end + + def emoji_reaction? + @notification.type == :emoji_reaction + end + def message? @notification.type == :mention end @@ -120,6 +132,8 @@ class NotifyService < BaseService blocked ||= optional_non_follower? blocked ||= optional_non_following? blocked ||= optional_non_following_and_direct? + blocked ||= optional_non_follower_emoji_reaction? + blocked ||= optional_non_following_emoji_reaction? blocked ||= conversation_muted? blocked ||= blocked_mention? if @notification.type == :mention blocked diff --git a/app/services/search_service.rb b/app/services/search_service.rb index 17f8a75ec7..fedc1f7129 100644 --- a/app/services/search_service.rb +++ b/app/services/search_service.rb @@ -11,6 +11,7 @@ class SearchService < BaseService @offset = options[:type].blank? ? 0 : options[:offset].to_i @resolve = options[:resolve] || false @following = options[:following] || false + @searchability = options[:searchability] || 'public' default_results.tap do |results| next if @query.blank? || @limit.zero? diff --git a/app/services/update_status_service.rb b/app/services/update_status_service.rb index fb94dd7b17..8ca2abe2ee 100644 --- a/app/services/update_status_service.rb +++ b/app/services/update_status_service.rb @@ -17,6 +17,7 @@ class UpdateStatusService < BaseService # @option options [Boolean] :sensitive # @option options [Boolean] :markdown # @option options [String] :language + # @option [Enumerable] :status_reference_ids Optional array def call(status, account_id, options = {}) @status = status @options = options @@ -36,6 +37,7 @@ class UpdateStatusService < BaseService queue_poll_notifications! reset_preview_card! + update_references! update_metadata! broadcast_updates! @@ -139,6 +141,11 @@ class UpdateStatusService < BaseService LinkCrawlWorker.perform_async(@status.id) end + def update_references! + reference_ids = (@options[:status_reference_ids] || []).map(&:to_i).filter(&:positive?) + ProcessReferencesWorker.perform_async(@status.id, reference_ids, []) + end + def update_metadata! ProcessHashtagsService.new.call(@status) ProcessMentionsService.new.call(@status) diff --git a/app/validators/emoji_reaction_validator.rb b/app/validators/emoji_reaction_validator.rb index 9d1ff556a6..4d45a77468 100644 --- a/app/validators/emoji_reaction_validator.rb +++ b/app/validators/emoji_reaction_validator.rb @@ -8,6 +8,7 @@ class EmojiReactionValidator < ActiveModel::Validator emoji_reaction.errors.add(:name, I18n.t('reactions.errors.unrecognized_emoji')) if emoji_reaction.custom_emoji_id.blank? && !unicode_emoji?(emoji_reaction.name) emoji_reaction.errors.add(:name, I18n.t('reactions.errors.unrecognized_emoji')) if emoji_reaction.custom_emoji_id.present? && disabled_custom_emoji?(emoji_reaction.custom_emoji) + emoji_reaction.errors.add(:name, I18n.t('reactions.errors.banned')) if deny_from_all?(emoji_reaction) || non_follower?(emoji_reaction) || non_following?(emoji_reaction) end private @@ -19,4 +20,25 @@ class EmojiReactionValidator < ActiveModel::Validator def disabled_custom_emoji?(custom_emoji) custom_emoji.nil? ? false : custom_emoji.disabled end + + def deny_from_all?(emoji_reaction) + return false if emoji_reaction.status.account.user.nil? + return false if emoji_reaction.status.account_id == emoji_reaction.account_id + + emoji_reaction.status.account.user.settings['emoji_reactions.deny_from_all'] + end + + def non_following?(emoji_reaction) + return false if emoji_reaction.status.account.user.nil? + return false if emoji_reaction.status.account_id == emoji_reaction.account_id + + emoji_reaction.status.account.user.settings['emoji_reactions.must_be_following'] && !emoji_reaction.status.account.following?(emoji_reaction.account) + end + + def non_follower?(emoji_reaction) + return false if emoji_reaction.status.account.user.nil? + return false if emoji_reaction.status.account_id == emoji_reaction.account_id + + emoji_reaction.status.account.user.settings['emoji_reactions.must_be_follower'] && !emoji_reaction.account.following?(emoji_reaction.status.account) + end end diff --git a/app/views/settings/preferences/notifications/show.html.haml b/app/views/settings/preferences/notifications/show.html.haml index 2730507922..a87b765da9 100644 --- a/app/views/settings/preferences/notifications/show.html.haml +++ b/app/views/settings/preferences/notifications/show.html.haml @@ -33,6 +33,9 @@ = ff.input :'interactions.must_be_follower', wrapper: :with_label, label: I18n.t('simple_form.labels.interactions.must_be_follower') = ff.input :'interactions.must_be_following', wrapper: :with_label, label: I18n.t('simple_form.labels.interactions.must_be_following') = ff.input :'interactions.must_be_following_dm', wrapper: :with_label, label: I18n.t('simple_form.labels.interactions.must_be_following_dm') + = ff.input :'emoji_reactions.must_be_follower', kmyblue: true, wrapper: :with_label, label: I18n.t('simple_form.labels.emoji_reactions.must_be_follower') + = ff.input :'emoji_reactions.must_be_following', kmyblue: true, wrapper: :with_label, label: I18n.t('simple_form.labels.emoji_reactions.must_be_following') + = ff.input :'emoji_reactions.deny_from_all', kmyblue: true, wrapper: :with_label, label: I18n.t('simple_form.labels.emoji_reactions.deny_from_all') = f.simple_fields_for :settings, current_user.settings do |ff| .fields-group diff --git a/config/locales/en.yml b/config/locales/en.yml index 5fc0b6429f..9867ab6a58 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1590,6 +1590,7 @@ en: title: Privacy Policy reactions: errors: + banned: Banned reaction from the user duplication: Cannot react same things limit_reached: Limit of different reactions reached unrecognized_emoji: is not a recognized emoji diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 929971b06f..1dbd259a34 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -1577,6 +1577,7 @@ ja: title: プライバシーポリシー reactions: errors: + banned: 指定ユーザーからのリアクションは禁止されています duplication: 同じリアクションを複数行おうとしました limit_reached: リアクションの種類が上限に達しました unrecognized_emoji: は絵文字として認識されていません diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 6af12751a8..749ab6eb82 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -257,6 +257,10 @@ en: whole_word: Whole word email_domain_block: with_dns_records: Include MX records and IPs of the domain + emoji_reactions: + deny_from_all: Block all stamps + must_be_follower: Block stamps from non-followers + must_be_following: Block stamps from people you don't follow featured_tag: name: Hashtag filters: diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml index 6e4d7663a9..cbddd7d72f 100644 --- a/config/locales/simple_form.ja.yml +++ b/config/locales/simple_form.ja.yml @@ -265,6 +265,10 @@ ja: whole_word: 単語全体にマッチ email_domain_block: with_dns_records: ドメインのMXレコードとIPアドレスを含む + emoji_reactions: + deny_from_all: 自分以外すべてのスタンプをブロック + must_be_follower: フォロワー以外からのスタンプをブロック + must_be_following: フォローしていないユーザーからのスタンプをブロック featured_tag: name: ハッシュタグ filters: