Merge branch 'kb_development' into upstream-20231021
This commit is contained in:
commit
b992e673c7
25 changed files with 667 additions and 114 deletions
2
.github/ISSUE_TEMPLATE/1.bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/1.bug_report.yml
vendored
|
@ -1,5 +1,5 @@
|
||||||
name: バグ報告
|
name: バグ報告
|
||||||
description: kmyblueのバグ報告
|
description: kmyblueのバグ報告(ただし情報改竄、秘密情報の漏洩、システムの破損などが発生するバグは、こちらではなく「Security」タブよりセキュリティインシデントとして報告してください)
|
||||||
labels: [bug]
|
labels: [bug]
|
||||||
body:
|
body:
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
|
33
SECURITY.md
33
SECURITY.md
|
@ -1,22 +1,25 @@
|
||||||
# Security Policy
|
# セキュリティポリシー
|
||||||
|
|
||||||
If you believe you've identified a security vulnerability in Mastodon (a bug that allows something to happen that shouldn't be possible), you can either:
|
kmyblueのプログラムにおいてセキュリティインシデントを発見した場合、kmyblueに報告してください。
|
||||||
|
|
||||||
- open a [Github security issue on the Mastodon project](https://github.com/mastodon/mastodon/security/advisories/new)
|
kmyblueにセキュリティインシデントを報告する場合、以下の手順を踏んでください。
|
||||||
- reach us at <security@joinmastodon.org>
|
|
||||||
|
|
||||||
You should _not_ report such issues on public GitHub issues or in other public spaces to give us time to publish a fix for the issue without exposing Mastodon's users to increased risk.
|
- [こちらのリンクから新規インシデントを起票してください](https://github.com/kmycode/mastodon/security/advisories/new)
|
||||||
|
- メール <tt@kmycode.net>、または[@askyq@kmy.blue](https://kmy.blue/@askyq)宛に、**セキュリティインシデントを起票したことだけ**を連絡してください。セキュリティインシデントの内容は、絶対に連絡に含めないでください(リンクくらいなら含めていいかな)
|
||||||
|
|
||||||
## Scope
|
他のkmyblueフォークの利用者の安全のために少しでも時間稼ぎをしなければいけないので、この問題をIssueを含む公開された場所で記述しないでください。
|
||||||
|
|
||||||
A "vulnerability in Mastodon" is a vulnerability in the code distributed through our main source code repository on GitHub. Vulnerabilities that are specific to a given installation (e.g. misconfiguration) should be reported to the owner of that installation and not us.
|
## 範囲
|
||||||
|
|
||||||
## Supported Versions
|
こちらが対応できる範囲は、当リポジトリで公開しているソースコードのみとなります。当リポジトリの依存パッケージ内に問題がある場合は、そちらに報告してください。
|
||||||
|
|
||||||
| Version | Supported |
|
もしあなたに専門知識があり、それが本家Mastodon由来の問題であると信じるに足る根拠がある場合、kmyblueではなくMastodonのほうに報告してください。kmyblueに報告されても、Mastodonより先に修正してしまうことでMastodonにセキュリティリスクを発生させる可能性がありますし、本家Mastodonの対応を待つにしてもkmyblueのほうに来てしまったセキュリティインシデントの対応に困ります(本家がなかなか対応してくれない可能性を考えると削除しづらい)。もし間違ってkmyblueに来た場合、kmyblue開発者の責任で振り分けを行います。
|
||||||
| ------- | ---------------- |
|
|
||||||
| 4.2.x | Yes |
|
## サポートするバージョン
|
||||||
| 4.1.x | Yes |
|
|
||||||
| 4.0.x | Until 2023-10-31 |
|
下記以外のバージョンは、セキュリティインシデントを起票されても対応しません。
|
||||||
| 3.5.x | Until 2023-12-31 |
|
|
||||||
| < 3.5 | No |
|
- 最新メジャーバージョン、かつ、最新マイナーバージョン
|
||||||
|
- 最新メジャーバージョンのサポートは、次のメジャーバージョンが出た時点で終了します
|
||||||
|
- LTS
|
||||||
|
- LTSのサポートは、次のLTSが出た時点で終了します(ただし移行期間があってもいいと思ってるので、1〜3ヶ月以内ならセキュリティインシデントの程度に応じて対応する可能性があります)
|
||||||
|
|
|
@ -30,6 +30,8 @@ module ContextHelper
|
||||||
other_setting: { 'fedibird' => 'http://fedibird.com/ns#', 'otherSetting' => 'fedibird:otherSetting' },
|
other_setting: { 'fedibird' => 'http://fedibird.com/ns#', 'otherSetting' => 'fedibird:otherSetting' },
|
||||||
references: { 'fedibird' => 'http://fedibird.com/ns#', 'references' => { '@id' => 'fedibird:references', '@type' => '@id' } },
|
references: { 'fedibird' => 'http://fedibird.com/ns#', 'references' => { '@id' => 'fedibird:references', '@type' => '@id' } },
|
||||||
quote_uri: { 'fedibird' => 'http://fedibird.com/ns#', 'quoteUri' => 'fedibird:quoteUri' },
|
quote_uri: { 'fedibird' => 'http://fedibird.com/ns#', 'quoteUri' => 'fedibird:quoteUri' },
|
||||||
|
keywords: { 'schema' => 'http://schema.org#', 'keywords' => 'schema:keywords' },
|
||||||
|
license: { 'schema' => 'http://schema.org#', 'license' => 'schema:license' },
|
||||||
olm: {
|
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',
|
'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' },
|
'claim' => { '@type' => '@id', '@id' => 'toot:claim' },
|
||||||
|
|
|
@ -274,6 +274,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||||
emoji.image_remote_url = custom_emoji_parser.image_remote_url
|
emoji.image_remote_url = custom_emoji_parser.image_remote_url
|
||||||
emoji.license = custom_emoji_parser.license
|
emoji.license = custom_emoji_parser.license
|
||||||
emoji.is_sensitive = custom_emoji_parser.is_sensitive
|
emoji.is_sensitive = custom_emoji_parser.is_sensitive
|
||||||
|
emoji.aliases = custom_emoji_parser.aliases
|
||||||
emoji.save
|
emoji.save
|
||||||
rescue Seahorse::Client::NetworkingError => e
|
rescue Seahorse::Client::NetworkingError => e
|
||||||
Rails.logger.warn "Error storing emoji: #{e}"
|
Rails.logger.warn "Error storing emoji: #{e}"
|
||||||
|
|
|
@ -127,6 +127,7 @@ class ActivityPub::Activity::Like < ActivityPub::Activity
|
||||||
emoji.image_remote_url = custom_emoji_parser.image_remote_url
|
emoji.image_remote_url = custom_emoji_parser.image_remote_url
|
||||||
emoji.license = custom_emoji_parser.license
|
emoji.license = custom_emoji_parser.license
|
||||||
emoji.is_sensitive = custom_emoji_parser.is_sensitive
|
emoji.is_sensitive = custom_emoji_parser.is_sensitive
|
||||||
|
emoji.aliases = custom_emoji_parser.aliases
|
||||||
emoji.save
|
emoji.save
|
||||||
rescue Seahorse::Client::NetworkingError => e
|
rescue Seahorse::Client::NetworkingError => e
|
||||||
Rails.logger.warn "Error storing emoji: #{e}"
|
Rails.logger.warn "Error storing emoji: #{e}"
|
||||||
|
|
|
@ -15,6 +15,10 @@ class ActivityPub::Parser::CustomEmojiParser
|
||||||
@json['name']&.delete(':')
|
@json['name']&.delete(':')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def aliases
|
||||||
|
as_array(@json['keywords'])
|
||||||
|
end
|
||||||
|
|
||||||
def image_remote_url
|
def image_remote_url
|
||||||
@json.dig('icon', 'url')
|
@json.dig('icon', 'url')
|
||||||
end
|
end
|
||||||
|
|
|
@ -399,6 +399,7 @@ class Account < ApplicationRecord
|
||||||
|
|
||||||
def allow_emoji_reaction?(account)
|
def allow_emoji_reaction?(account)
|
||||||
return false if account.nil?
|
return false if account.nil?
|
||||||
|
return true unless local? || account.local?
|
||||||
|
|
||||||
show_emoji_reaction?(account)
|
show_emoji_reaction?(account)
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
class ActivityPub::EmojiSerializer < ActivityPub::Serializer
|
class ActivityPub::EmojiSerializer < ActivityPub::Serializer
|
||||||
include RoutingHelper
|
include RoutingHelper
|
||||||
|
|
||||||
context_extensions :emoji
|
context_extensions :emoji, :license, :keywords
|
||||||
|
|
||||||
attributes :id, :type, :domain, :name, :is_sensitive, :updated
|
attributes :id, :type, :domain, :name, :keywords, :is_sensitive, :updated
|
||||||
|
|
||||||
attribute :license, if: -> { object.license.present? }
|
attribute :license, if: -> { object.license.present? }
|
||||||
|
|
||||||
|
@ -23,6 +23,10 @@ class ActivityPub::EmojiSerializer < ActivityPub::Serializer
|
||||||
object.domain.presence || Rails.configuration.x.local_domain
|
object.domain.presence || Rails.configuration.x.local_domain
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def keywords
|
||||||
|
object.aliases
|
||||||
|
end
|
||||||
|
|
||||||
def icon
|
def icon
|
||||||
object.image
|
object.image
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,6 +5,8 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
||||||
include Redisable
|
include Redisable
|
||||||
include Lockable
|
include Lockable
|
||||||
|
|
||||||
|
class AbortError < ::StandardError; end
|
||||||
|
|
||||||
def call(status, activity_json, object_json, request_id: nil)
|
def call(status, activity_json, object_json, request_id: nil)
|
||||||
raise ArgumentError, 'Status has unsaved changes' if status.changed?
|
raise ArgumentError, 'Status has unsaved changes' if status.changed?
|
||||||
|
|
||||||
|
@ -30,6 +32,9 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
||||||
handle_implicit_update!
|
handle_implicit_update!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@status
|
||||||
|
rescue AbortError
|
||||||
|
@status.reload
|
||||||
@status
|
@status
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -46,6 +51,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
||||||
update_poll!
|
update_poll!
|
||||||
update_immediate_attributes!
|
update_immediate_attributes!
|
||||||
update_metadata!
|
update_metadata!
|
||||||
|
validate_status_mentions!
|
||||||
create_edits!
|
create_edits!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -158,6 +164,15 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
||||||
!Admin::NgWord.reject?("#{@status_parser.spoiler_text}\n#{@status_parser.text}") && !Admin::NgWord.hashtag_reject?(@raw_tags.size)
|
!Admin::NgWord.reject?("#{@status_parser.spoiler_text}\n#{@status_parser.text}") && !Admin::NgWord.hashtag_reject?(@raw_tags.size)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate_status_mentions!
|
||||||
|
raise AbortError if mention_to_stranger? && Admin::NgWord.stranger_mention_reject?("#{@status.spoiler_text}\n#{@status.text}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def mention_to_stranger?
|
||||||
|
@status.mentions.map(&:account).to_a.any? { |mentioned_account| mentioned_account.id != @status.account.id && !mentioned_account.following?(@status.account) } ||
|
||||||
|
(@status.thread.present? && @status.thread.account.id != @status.account.id && !@status.thread.account.following?(@status.account))
|
||||||
|
end
|
||||||
|
|
||||||
def update_immediate_attributes!
|
def update_immediate_attributes!
|
||||||
@status.text = @status_parser.text || ''
|
@status.text = @status_parser.text || ''
|
||||||
@status.spoiler_text = @status_parser.spoiler_text || ''
|
@status.spoiler_text = @status_parser.spoiler_text || ''
|
||||||
|
@ -247,6 +262,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
||||||
emoji.image_remote_url = custom_emoji_parser.image_remote_url
|
emoji.image_remote_url = custom_emoji_parser.image_remote_url
|
||||||
emoji.license = custom_emoji_parser.license
|
emoji.license = custom_emoji_parser.license
|
||||||
emoji.is_sensitive = custom_emoji_parser.is_sensitive
|
emoji.is_sensitive = custom_emoji_parser.is_sensitive
|
||||||
|
emoji.aliases = custom_emoji_parser.aliases
|
||||||
emoji.save
|
emoji.save
|
||||||
rescue Seahorse::Client::NetworkingError => e
|
rescue Seahorse::Client::NetworkingError => e
|
||||||
Rails.logger.warn "Error storing emoji: #{e}"
|
Rails.logger.warn "Error storing emoji: #{e}"
|
||||||
|
|
|
@ -16,8 +16,9 @@ module Payloadable
|
||||||
always_sign = options.delete(:always_sign)
|
always_sign = options.delete(:always_sign)
|
||||||
payload = ActiveModelSerializers::SerializableResource.new(record, options.merge(serializer: serializer, adapter: ActivityPub::Adapter)).as_json
|
payload = ActiveModelSerializers::SerializableResource.new(record, options.merge(serializer: serializer, adapter: ActivityPub::Adapter)).as_json
|
||||||
object = record.respond_to?(:virtual_object) ? record.virtual_object : record
|
object = record.respond_to?(:virtual_object) ? record.virtual_object : record
|
||||||
|
bearcap = object.is_a?(String) && record.respond_to?(:type) && (record.type == 'Create' || record.type == 'Update')
|
||||||
|
|
||||||
if ((object.respond_to?(:sign?) && object.sign?) && signer && (always_sign || signing_enabled?)) || object.is_a?(String)
|
if ((object.respond_to?(:sign?) && object.sign?) && signer && (always_sign || signing_enabled?)) || bearcap
|
||||||
ActivityPub::LinkedDataSignature.new(payload).sign!(signer, sign_with: sign_with)
|
ActivityPub::LinkedDataSignature.new(payload).sign!(signer, sign_with: sign_with)
|
||||||
else
|
else
|
||||||
payload
|
payload
|
||||||
|
|
|
@ -214,7 +214,7 @@ class PostStatusService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def mention_to_stranger?
|
def mention_to_stranger?
|
||||||
@status.mentions.map(&:account).to_a.any? { |mentioned_account| mentioned_account.id != @account && !mentioned_account.following?(@account) } ||
|
@status.mentions.map(&:account).to_a.any? { |mentioned_account| mentioned_account.id != @account.id && !mentioned_account.following?(@account) } ||
|
||||||
(@in_reply_to && @in_reply_to.account.id != @account.id && !@in_reply_to.account.following?(@account))
|
(@in_reply_to && @in_reply_to.account.id != @account.id && !@in_reply_to.account.following?(@account))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -35,10 +35,13 @@ class UpdateStatusService < BaseService
|
||||||
update_poll! if @options.key?(:poll)
|
update_poll! if @options.key?(:poll)
|
||||||
update_immediate_attributes!
|
update_immediate_attributes!
|
||||||
create_edit! unless @options[:no_history]
|
create_edit! unless @options[:no_history]
|
||||||
|
|
||||||
|
reset_preview_card!
|
||||||
|
process_mentions_service.call(@status)
|
||||||
|
validate_status_mentions!
|
||||||
end
|
end
|
||||||
|
|
||||||
queue_poll_notifications!
|
queue_poll_notifications!
|
||||||
reset_preview_card!
|
|
||||||
update_metadata!
|
update_metadata!
|
||||||
update_references!
|
update_references!
|
||||||
broadcast_updates!
|
broadcast_updates!
|
||||||
|
@ -81,6 +84,15 @@ class UpdateStatusService < BaseService
|
||||||
raise Mastodon::ValidationError, I18n.t('statuses.too_many_hashtags') if Admin::NgWord.hashtag_reject_with_extractor?(@options[:text])
|
raise Mastodon::ValidationError, I18n.t('statuses.too_many_hashtags') if Admin::NgWord.hashtag_reject_with_extractor?(@options[:text])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate_status_mentions!
|
||||||
|
raise Mastodon::ValidationError, I18n.t('statuses.contains_ng_words') if mention_to_stranger? && Setting.stranger_mention_from_local_ng && Admin::NgWord.stranger_mention_reject?("#{@options[:spoiler_text]}\n#{@options[:text]}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def mention_to_stranger?
|
||||||
|
@status.mentions.map(&:account).to_a.any? { |mentioned_account| mentioned_account.id != @status.account.id && !mentioned_account.following?(@status.account) } ||
|
||||||
|
(@status.thread.present? && @status.thread.account.id != @status.account.id && !@status.thread.account.following?(@status.account))
|
||||||
|
end
|
||||||
|
|
||||||
def validate_media!
|
def validate_media!
|
||||||
return [] if @options[:media_ids].blank? || !@options[:media_ids].is_a?(Enumerable)
|
return [] if @options[:media_ids].blank? || !@options[:media_ids].is_a?(Enumerable)
|
||||||
|
|
||||||
|
@ -167,7 +179,6 @@ class UpdateStatusService < BaseService
|
||||||
|
|
||||||
def update_metadata!
|
def update_metadata!
|
||||||
ProcessHashtagsService.new.call(@status)
|
ProcessHashtagsService.new.call(@status)
|
||||||
process_mentions_service.call(@status)
|
|
||||||
|
|
||||||
@status.update(limited_scope: :circle) if process_mentions_service.mentions?
|
@status.update(limited_scope: :circle) if process_mentions_service.mentions?
|
||||||
end
|
end
|
||||||
|
|
44
app/views/admin/domain_blocks/_domain_block_list.html.haml
Normal file
44
app/views/admin/domain_blocks/_domain_block_list.html.haml
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
%h4= I18n.t('admin.domain_blocks.headers.harassment')
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= f.input :reject_favourite, as: :boolean, kmyblue: true, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_favourite'), hint: I18n.t('admin.domain_blocks.reject_favourite_hint')
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= f.input :reject_reply, as: :boolean, kmyblue: true, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_reply'), hint: I18n.t('admin.domain_blocks.reject_reply_hint')
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= f.input :reject_reply_exclude_followers, as: :boolean, kmyblue: true, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_reply_exclude_followers'), hint: I18n.t('admin.domain_blocks.reject_reply_exclude_followers_hint')
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= f.input :reject_hashtag, as: :boolean, kmyblue: true, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_hashtag'), hint: I18n.t('admin.domain_blocks.reject_hashtag_hint')
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= f.input :reject_straight_follow, as: :boolean, kmyblue: true, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_straight_follow'), hint: I18n.t('admin.domain_blocks.reject_straight_follow_hint')
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= f.input :reject_new_follow, as: :boolean, kmyblue: true, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_new_follow'), hint: I18n.t('admin.domain_blocks.reject_new_follow_hint')
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= f.input :reject_friend, as: :boolean, kmyblue: true, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_friend'), hint: I18n.t('admin.domain_blocks.reject_friend_hint')
|
||||||
|
|
||||||
|
%h4= I18n.t('admin.domain_blocks.headers.invalid_privacy')
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= f.input :reject_send_not_public_searchability, as: :boolean, kmyblue: true, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_send_not_public_searchability'), hint: I18n.t('admin.domain_blocks.reject_send_not_public_searchability_hint')
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= f.input :reject_send_dissubscribable, as: :boolean, kmyblue: true, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_send_dissubscribable'), hint: I18n.t('admin.domain_blocks.reject_send_dissubscribable_hint')
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= f.input :detect_invalid_subscription, as: :boolean, kmyblue: true, wrapper: :with_label, label: I18n.t('admin.domain_blocks.detect_invalid_subscription'), hint: I18n.t('admin.domain_blocks.detect_invalid_subscription_hint')
|
||||||
|
|
||||||
|
%h4= I18n.t('admin.domain_blocks.headers.disagreement')
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= f.input :reject_send_public_unlisted, as: :boolean, kmyblue: true, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_send_public_unlisted'), hint: I18n.t('admin.domain_blocks.reject_send_public_unlisted_hint')
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= f.input :reject_send_media, as: :boolean, kmyblue: true, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_send_media'), hint: I18n.t('admin.domain_blocks.reject_send_media_hint')
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= f.input :reject_send_sensitive, as: :boolean, kmyblue: true, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_send_sensitive'), hint: I18n.t('admin.domain_blocks.reject_send_sensitive_hint')
|
|
@ -11,48 +11,13 @@
|
||||||
.fields-row__column.fields-row__column-6.fields-group
|
.fields-row__column.fields-row__column-6.fields-group
|
||||||
= f.input :severity, collection: DomainBlock.severities.keys, wrapper: :with_label, include_blank: false, label_method: ->(type) { t("admin.domain_blocks.new.severity.#{type}") }, hint: t('admin.domain_blocks.new.severity.desc_html')
|
= f.input :severity, collection: DomainBlock.severities.keys, wrapper: :with_label, include_blank: false, label_method: ->(type) { t("admin.domain_blocks.new.severity.#{type}") }, hint: t('admin.domain_blocks.new.severity.desc_html')
|
||||||
|
|
||||||
|
= render 'domain_block_list', f: f
|
||||||
|
|
||||||
|
%h4= I18n.t('admin.domain_blocks.headers.mastodon_default')
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :reject_media, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_media'), hint: I18n.t('admin.domain_blocks.reject_media_hint')
|
= f.input :reject_media, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_media'), hint: I18n.t('admin.domain_blocks.reject_media_hint')
|
||||||
|
|
||||||
.fields-group
|
|
||||||
= f.input :reject_favourite, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_favourite'), hint: I18n.t('admin.domain_blocks.reject_favourite_hint')
|
|
||||||
|
|
||||||
.fields-group
|
|
||||||
= f.input :reject_reply, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_reply'), hint: I18n.t('admin.domain_blocks.reject_reply_hint')
|
|
||||||
|
|
||||||
.fields-group
|
|
||||||
= f.input :reject_reply_exclude_followers, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_reply_exclude_followers'), hint: I18n.t('admin.domain_blocks.reject_reply_exclude_followers_hint')
|
|
||||||
|
|
||||||
.fields-group
|
|
||||||
= f.input :reject_send_not_public_searchability, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_send_not_public_searchability'), hint: I18n.t('admin.domain_blocks.reject_send_not_public_searchability_hint')
|
|
||||||
|
|
||||||
.fields-group
|
|
||||||
= f.input :reject_send_dissubscribable, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_send_dissubscribable'), hint: I18n.t('admin.domain_blocks.reject_send_dissubscribable_hint')
|
|
||||||
|
|
||||||
.fields-group
|
|
||||||
= f.input :reject_send_public_unlisted, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_send_public_unlisted'), hint: I18n.t('admin.domain_blocks.reject_send_public_unlisted_hint')
|
|
||||||
|
|
||||||
.fields-group
|
|
||||||
= f.input :reject_send_media, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_send_media'), hint: I18n.t('admin.domain_blocks.reject_send_media_hint')
|
|
||||||
|
|
||||||
.fields-group
|
|
||||||
= f.input :reject_send_sensitive, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_send_sensitive'), hint: I18n.t('admin.domain_blocks.reject_send_sensitive_hint')
|
|
||||||
|
|
||||||
.fields-group
|
|
||||||
= f.input :reject_hashtag, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_hashtag'), hint: I18n.t('admin.domain_blocks.reject_hashtag_hint')
|
|
||||||
|
|
||||||
.fields-group
|
|
||||||
= f.input :reject_straight_follow, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_straight_follow'), hint: I18n.t('admin.domain_blocks.reject_straight_follow_hint')
|
|
||||||
|
|
||||||
.fields-group
|
|
||||||
= f.input :reject_new_follow, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_new_follow'), hint: I18n.t('admin.domain_blocks.reject_new_follow_hint')
|
|
||||||
|
|
||||||
.fields-group
|
|
||||||
= f.input :reject_friend, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_friend'), hint: I18n.t('admin.domain_blocks.reject_friend_hint')
|
|
||||||
|
|
||||||
.fields-group
|
|
||||||
= f.input :detect_invalid_subscription, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.detect_invalid_subscription'), hint: I18n.t('admin.domain_blocks.detect_invalid_subscription_hint')
|
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :reject_reports, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_reports'), hint: I18n.t('admin.domain_blocks.reject_reports_hint')
|
= f.input :reject_reports, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_reports'), hint: I18n.t('admin.domain_blocks.reject_reports_hint')
|
||||||
|
|
||||||
|
@ -69,7 +34,7 @@
|
||||||
= f.input :hidden, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.hidden'), hint: I18n.t('admin.domain_blocks.hidden_hint')
|
= f.input :hidden, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.hidden'), hint: I18n.t('admin.domain_blocks.hidden_hint')
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :hidden_anonymous, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.hidden_anonymous'), hint: I18n.t('admin.domain_blocks.hidden_anonymous_hint')
|
= f.input :hidden_anonymous, kmyblue: true, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.hidden_anonymous'), hint: I18n.t('admin.domain_blocks.hidden_anonymous_hint')
|
||||||
|
|
||||||
.actions
|
.actions
|
||||||
= f.button :button, t('generic.save_changes'), type: :submit
|
= f.button :button, t('generic.save_changes'), type: :submit
|
||||||
|
|
|
@ -11,48 +11,13 @@
|
||||||
.fields-row__column.fields-row__column-6.fields-group
|
.fields-row__column.fields-row__column-6.fields-group
|
||||||
= f.input :severity, collection: DomainBlock.severities.keys, wrapper: :with_label, include_blank: false, label_method: ->(type) { t(".severity.#{type}") }, hint: t('.severity.desc_html')
|
= f.input :severity, collection: DomainBlock.severities.keys, wrapper: :with_label, include_blank: false, label_method: ->(type) { t(".severity.#{type}") }, hint: t('.severity.desc_html')
|
||||||
|
|
||||||
|
= render 'domain_block_list', f: f
|
||||||
|
|
||||||
|
%h4= I18n.t('admin.domain_blocks.headers.mastodon_default')
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :reject_media, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_media'), hint: I18n.t('admin.domain_blocks.reject_media_hint')
|
= f.input :reject_media, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_media'), hint: I18n.t('admin.domain_blocks.reject_media_hint')
|
||||||
|
|
||||||
.fields-group
|
|
||||||
= f.input :reject_favourite, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_favourite'), hint: I18n.t('admin.domain_blocks.reject_favourite_hint')
|
|
||||||
|
|
||||||
.fields-group
|
|
||||||
= f.input :reject_reply, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_reply'), hint: I18n.t('admin.domain_blocks.reject_reply_hint')
|
|
||||||
|
|
||||||
.fields-group
|
|
||||||
= f.input :reject_reply_exclude_followers, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_reply_exclude_followers'), hint: I18n.t('admin.domain_blocks.reject_reply_exclude_followers_hint')
|
|
||||||
|
|
||||||
.fields-group
|
|
||||||
= f.input :reject_send_not_public_searchability, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_send_not_public_searchability'), hint: I18n.t('admin.domain_blocks.reject_send_not_public_searchability_hint')
|
|
||||||
|
|
||||||
.fields-group
|
|
||||||
= f.input :reject_send_dissubscribable, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_send_dissubscribable'), hint: I18n.t('admin.domain_blocks.reject_send_dissubscribable_hint')
|
|
||||||
|
|
||||||
.fields-group
|
|
||||||
= f.input :reject_send_public_unlisted, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_send_public_unlisted'), hint: I18n.t('admin.domain_blocks.reject_send_public_unlisted_hint')
|
|
||||||
|
|
||||||
.fields-group
|
|
||||||
= f.input :reject_send_media, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_send_media'), hint: I18n.t('admin.domain_blocks.reject_send_media_hint')
|
|
||||||
|
|
||||||
.fields-group
|
|
||||||
= f.input :reject_send_sensitive, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_send_sensitive'), hint: I18n.t('admin.domain_blocks.reject_send_sensitive_hint')
|
|
||||||
|
|
||||||
.fields-group
|
|
||||||
= f.input :reject_hashtag, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_hashtag'), hint: I18n.t('admin.domain_blocks.reject_hashtag_hint')
|
|
||||||
|
|
||||||
.fields-group
|
|
||||||
= f.input :reject_straight_follow, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_straight_follow'), hint: I18n.t('admin.domain_blocks.reject_straight_follow_hint')
|
|
||||||
|
|
||||||
.fields-group
|
|
||||||
= f.input :reject_new_follow, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_new_follow'), hint: I18n.t('admin.domain_blocks.reject_new_follow_hint')
|
|
||||||
|
|
||||||
.fields-group
|
|
||||||
= f.input :reject_friend, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_friend'), hint: I18n.t('admin.domain_blocks.reject_friend_hint')
|
|
||||||
|
|
||||||
.fields-group
|
|
||||||
= f.input :detect_invalid_subscription, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.detect_invalid_subscription'), hint: I18n.t('admin.domain_blocks.detect_invalid_subscription_hint')
|
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :reject_reports, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_reports'), hint: I18n.t('admin.domain_blocks.reject_reports_hint')
|
= f.input :reject_reports, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_reports'), hint: I18n.t('admin.domain_blocks.reject_reports_hint')
|
||||||
|
|
||||||
|
@ -69,7 +34,7 @@
|
||||||
= f.input :hidden, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.hidden'), hint: I18n.t('admin.domain_blocks.hidden_hint')
|
= f.input :hidden, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.hidden'), hint: I18n.t('admin.domain_blocks.hidden_hint')
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :hidden_anonymous, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.hidden_anonymous'), hint: I18n.t('admin.domain_blocks.hidden_anonymous_hint')
|
= f.input :hidden_anonymous, kmyblue: true, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.hidden_anonymous'), hint: I18n.t('admin.domain_blocks.hidden_anonymous_hint')
|
||||||
|
|
||||||
.actions
|
.actions
|
||||||
= f.button :button, t('.create'), type: :submit
|
= f.button :button, t('.create'), type: :submit
|
||||||
|
|
|
@ -15,15 +15,27 @@ class ActivityPub::StatusUpdateDistributionWorker < ActivityPub::DistributionWor
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def activity
|
def build_activity(for_misskey: false, for_friend: false)
|
||||||
ActivityPub::ActivityPresenter.new(
|
ActivityPub::ActivityPresenter.new(
|
||||||
id: [ActivityPub::TagManager.instance.uri_for(@status), '#updates/', @status.edited_at.to_i].join,
|
id: [ActivityPub::TagManager.instance.uri_for(@status), '#updates/', @status.edited_at.to_i].join,
|
||||||
type: 'Update',
|
type: 'Update',
|
||||||
actor: ActivityPub::TagManager.instance.uri_for(@status.account),
|
actor: ActivityPub::TagManager.instance.uri_for(@status.account),
|
||||||
published: @status.edited_at,
|
published: @status.edited_at,
|
||||||
to: ActivityPub::TagManager.instance.to(@status),
|
to: for_friend ? ActivityPub::TagManager.instance.to_for_friend(@status) : ActivityPub::TagManager.instance.to(@status),
|
||||||
cc: ActivityPub::TagManager.instance.cc(@status),
|
cc: for_misskey ? ActivityPub::TagManager.instance.cc_for_misskey : ActivityPub::TagManager.instance.cc(@status),
|
||||||
virtual_object: @status
|
virtual_object: @status
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def activity
|
||||||
|
build_activity
|
||||||
|
end
|
||||||
|
|
||||||
|
def activity_for_misskey
|
||||||
|
build_activity(for_misskey: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def activity_for_friend
|
||||||
|
build_activity(for_friend: true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -415,6 +415,11 @@ en:
|
||||||
existing_domain_block: You have already imposed stricter limits on %{name}.
|
existing_domain_block: You have already imposed stricter limits on %{name}.
|
||||||
existing_domain_block_html: You have already imposed stricter limits on %{name}, you need to <a href="%{unblock_url}">unblock it</a> first.
|
existing_domain_block_html: You have already imposed stricter limits on %{name}, you need to <a href="%{unblock_url}">unblock it</a> first.
|
||||||
export: Export
|
export: Export
|
||||||
|
headers:
|
||||||
|
disagreement: Protect sensitive posts from political disagreement
|
||||||
|
harassment: Harassment or spam
|
||||||
|
invalid_privacy: Privacy is not protected
|
||||||
|
mastodon_default: Original Mastodon supports
|
||||||
import: Import
|
import: Import
|
||||||
new:
|
new:
|
||||||
create: Create block
|
create: Create block
|
||||||
|
|
|
@ -402,12 +402,17 @@ ja:
|
||||||
created_msg: ドメインブロック処理を完了しました
|
created_msg: ドメインブロック処理を完了しました
|
||||||
destroyed_msg: ドメインブロックを外しました
|
destroyed_msg: ドメインブロックを外しました
|
||||||
detect_invalid_subscription: 不正な購読を行うサーバーとしてマークする
|
detect_invalid_subscription: 不正な購読を行うサーバーとしてマークする
|
||||||
detect_invalid_subscription_hint: Misskey、Calckeyなどは購読機能で未フォローユーザーの未収載投稿を拾います。これをマークしたサーバーは、ユーザーが任意で配送を拒否できます。停止とは無関係です
|
detect_invalid_subscription_hint: 未フォローユーザーの未収載投稿が自由に購読・検索できるサーバーとしてマークします。Misskeyサーバーはこのチェックに関係なく、自動で制限が有効とみなされます。各ユーザーはそれぞれのプライバシー追加設定でMisskeyサーバーへの配送制限を有効にすることで、これらのサーバーへローカル公開・未収載投稿を鍵付きで配信できるようになります。停止とは無関係です
|
||||||
domain: ドメイン
|
domain: ドメイン
|
||||||
edit: ドメインブロックを編集
|
edit: ドメインブロックを編集
|
||||||
existing_domain_block: あなたは既に%{name}さんに厳しい制限を課しています。
|
existing_domain_block: あなたは既に%{name}さんに厳しい制限を課しています。
|
||||||
existing_domain_block_html: 既に%{name}に対して、より厳しい制限を課しています。先に<a href="%{unblock_url}">その制限を解除</a>する必要があります。
|
existing_domain_block_html: 既に%{name}に対して、より厳しい制限を課しています。先に<a href="%{unblock_url}">その制限を解除</a>する必要があります。
|
||||||
export: エクスポート
|
export: エクスポート
|
||||||
|
headers:
|
||||||
|
disagreement: 政治的な意見の相違からの敏感な投稿の保護
|
||||||
|
harassment: 嫌がらせまたはスパム
|
||||||
|
invalid_privacy: プライバシーが守られていない
|
||||||
|
mastodon_default: 本家Mastodonの設定項目
|
||||||
hidden: 非公開にする
|
hidden: 非公開にする
|
||||||
hidden_hint: 公開することで当サーバーの安全が脅かされる場合、このドメインブロックを非公開にすることができます。
|
hidden_hint: 公開することで当サーバーの安全が脅かされる場合、このドメインブロックを非公開にすることができます。
|
||||||
hidden_anonymous: 未ログインユーザーに非公開にする
|
hidden_anonymous: 未ログインユーザーに非公開にする
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
|
||||||
|
|
||||||
|
class RemoveRemoteUriFromLocalCustomEmojis < ActiveRecord::Migration[7.0]
|
||||||
|
include Mastodon::MigrationHelpers
|
||||||
|
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
class CustomEmoji < ApplicationRecord; end
|
||||||
|
|
||||||
|
def up
|
||||||
|
safety_assured do
|
||||||
|
CustomEmoji.transaction do
|
||||||
|
CustomEmoji.where(domain: nil).update_all(image_remote_url: nil, uri: nil) # rubocop:disable Rails/SkipsModelValidations
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down; end
|
||||||
|
end
|
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.0].define(version: 2023_10_09_235215) do
|
ActiveRecord::Schema[7.0].define(version: 2023_10_21_005339) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
|
||||||
|
|
|
@ -246,6 +246,207 @@ RSpec.describe Account do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#allow_emoji_reaction?' do
|
||||||
|
let(:policy) { :allow }
|
||||||
|
let(:reactioned) { Fabricate(:user, settings: { emoji_reaction_policy: policy }).account }
|
||||||
|
let(:followee) { Fabricate(:account) }
|
||||||
|
let(:follower) { Fabricate(:account) }
|
||||||
|
let(:mutual) { Fabricate(:account) }
|
||||||
|
let(:anyone) { Fabricate(:account) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
follower.follow!(reactioned)
|
||||||
|
reactioned.follow!(followee)
|
||||||
|
mutual.follow!(reactioned)
|
||||||
|
reactioned.follow!(mutual)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when policy is arrow' do
|
||||||
|
it 'allows anyone' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(anyone)).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows followee' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(followee)).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows follower' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(follower)).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows mutual' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(mutual)).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows self' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(reactioned)).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when policy is following_only' do
|
||||||
|
let(:policy) { :following_only }
|
||||||
|
|
||||||
|
it 'allows anyone' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(anyone)).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows followee' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(followee)).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows follower' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(follower)).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows mutual' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(mutual)).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows self' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(reactioned)).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when policy is followers_only' do
|
||||||
|
let(:policy) { :followers_only }
|
||||||
|
|
||||||
|
it 'allows anyone' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(anyone)).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows followee' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(followee)).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows follower' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(follower)).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows mutual' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(mutual)).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows self' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(reactioned)).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when policy is mutuals_only' do
|
||||||
|
let(:policy) { :mutuals_only }
|
||||||
|
|
||||||
|
it 'allows anyone' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(anyone)).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows followee' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(followee)).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows follower' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(follower)).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows mutual' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(mutual)).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows self' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(reactioned)).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when policy is outside_only' do
|
||||||
|
let(:policy) { :outside_only }
|
||||||
|
|
||||||
|
it 'allows anyone' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(anyone)).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows followee' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(followee)).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows follower' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(follower)).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows mutual' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(mutual)).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows self' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(reactioned)).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when policy is block' do
|
||||||
|
let(:policy) { :block }
|
||||||
|
|
||||||
|
it 'allows anyone' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(anyone)).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows followee' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(followee)).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows follower' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(follower)).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows mutual' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(mutual)).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows self' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(reactioned)).to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when reactioned is remote user' do
|
||||||
|
let(:reactioned) { Fabricate(:account, domain: 'foo.bar', uri: 'https://foo.bar/actor', settings: { emoji_reaction_policy: :following_only }) }
|
||||||
|
|
||||||
|
it 'allows anyone' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(anyone)).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows followee' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(followee)).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when reactor is remote user' do
|
||||||
|
let(:anyone) { Fabricate(:account, domain: 'foo.bar', uri: 'https://foo.bar/actor/anyone') }
|
||||||
|
let(:policy) { :following_only }
|
||||||
|
|
||||||
|
it 'allows anyone' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(anyone)).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows followee' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(followee)).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when both are remote user' do
|
||||||
|
let(:reactioned) { Fabricate(:account, domain: 'foo.bar', uri: 'https://foo.bar/actor', settings: { emoji_reaction_policy: policy }) }
|
||||||
|
let(:anyone) { Fabricate(:account, domain: 'foo.bar', uri: 'https://foo.bar/actor/anyone') }
|
||||||
|
let(:followee) { Fabricate(:account, domain: 'foo.bar', uri: 'https://foo.bar/actor/followee') }
|
||||||
|
|
||||||
|
it 'allows anyone' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(anyone)).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with blocking' do
|
||||||
|
let(:policy) { :block }
|
||||||
|
|
||||||
|
it 'allows anyone' do
|
||||||
|
expect(reactioned.allow_emoji_reaction?(anyone)).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#favourited?' do
|
describe '#favourited?' do
|
||||||
subject { Fabricate(:account) }
|
subject { Fabricate(:account) }
|
||||||
|
|
||||||
|
|
|
@ -9,19 +9,24 @@ end
|
||||||
RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do
|
RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do
|
||||||
subject { described_class.new }
|
subject { described_class.new }
|
||||||
|
|
||||||
let!(:status) { Fabricate(:status, text: 'Hello world', account: Fabricate(:account, domain: 'example.com')) }
|
let(:thread) { nil }
|
||||||
|
let!(:status) { Fabricate(:status, text: 'Hello world', account: Fabricate(:account, domain: 'example.com'), thread: thread) }
|
||||||
|
let(:json_tags) do
|
||||||
|
[
|
||||||
|
{ type: 'Hashtag', name: 'hoge' },
|
||||||
|
{ type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(alice) },
|
||||||
|
]
|
||||||
|
end
|
||||||
|
let(:content) { 'Hello universe' }
|
||||||
let(:payload) do
|
let(:payload) do
|
||||||
{
|
{
|
||||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
id: 'foo',
|
id: 'foo',
|
||||||
type: 'Note',
|
type: 'Note',
|
||||||
summary: 'Show more',
|
summary: 'Show more',
|
||||||
content: 'Hello universe',
|
content: content,
|
||||||
updated: '2021-09-08T22:39:25Z',
|
updated: '2021-09-08T22:39:25Z',
|
||||||
tag: [
|
tag: json_tags,
|
||||||
{ type: 'Hashtag', name: 'hoge' },
|
|
||||||
{ type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(alice) },
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
let(:json) { Oj.load(Oj.dump(payload)) }
|
let(:json) { Oj.load(Oj.dump(payload)) }
|
||||||
|
@ -462,5 +467,161 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do
|
||||||
subject.call(status, json, json)
|
subject.call(status, json, json)
|
||||||
expect(status.reload.edited_at.to_s).to eq '2021-09-08 22:39:25 UTC'
|
expect(status.reload.edited_at.to_s).to eq '2021-09-08 22:39:25 UTC'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'ng word is set' do
|
||||||
|
let(:json_tags) { [] }
|
||||||
|
|
||||||
|
context 'when hit ng words' do
|
||||||
|
let(:content) { 'ng word test' }
|
||||||
|
|
||||||
|
it 'update status' do
|
||||||
|
Form::AdminSettings.new(ng_words: 'test').save
|
||||||
|
|
||||||
|
subject.call(status, json, json)
|
||||||
|
expect(status.reload.text).to_not eq content
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when not hit ng words' do
|
||||||
|
let(:content) { 'ng word aiueo' }
|
||||||
|
|
||||||
|
it 'update status' do
|
||||||
|
Form::AdminSettings.new(ng_words: 'test').save
|
||||||
|
|
||||||
|
subject.call(status, json, json)
|
||||||
|
expect(status.reload.text).to eq content
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when hit ng words for mention' do
|
||||||
|
let(:json_tags) do
|
||||||
|
[
|
||||||
|
{ type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(alice) },
|
||||||
|
]
|
||||||
|
end
|
||||||
|
let(:content) { 'ng word test' }
|
||||||
|
|
||||||
|
it 'update status' do
|
||||||
|
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save
|
||||||
|
|
||||||
|
subject.call(status, json, json)
|
||||||
|
expect(status.reload.text).to_not eq content
|
||||||
|
expect(status.mentioned_accounts.pluck(:id)).to_not include alice.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when hit ng words for mention but local posts are not checked' do
|
||||||
|
let(:json_tags) do
|
||||||
|
[
|
||||||
|
{ type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(alice) },
|
||||||
|
]
|
||||||
|
end
|
||||||
|
let(:content) { 'ng word test' }
|
||||||
|
|
||||||
|
it 'update status' do
|
||||||
|
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '0').save
|
||||||
|
|
||||||
|
subject.call(status, json, json)
|
||||||
|
expect(status.reload.text).to_not eq content
|
||||||
|
expect(status.mentioned_accounts.pluck(:id)).to_not include alice.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when hit ng words for mention to follower' do
|
||||||
|
let(:json_tags) do
|
||||||
|
[
|
||||||
|
{ type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(alice) },
|
||||||
|
]
|
||||||
|
end
|
||||||
|
let(:content) { 'ng word test' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
alice.follow!(status.account)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'update status' do
|
||||||
|
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test').save
|
||||||
|
|
||||||
|
subject.call(status, json, json)
|
||||||
|
expect(status.reload.text).to eq content
|
||||||
|
expect(status.mentioned_accounts.pluck(:id)).to include alice.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when hit ng words for reply' do
|
||||||
|
let(:json_tags) do
|
||||||
|
[
|
||||||
|
{ type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(alice) },
|
||||||
|
]
|
||||||
|
end
|
||||||
|
let(:content) { 'ng word test' }
|
||||||
|
let(:thread) { Fabricate(:status, account: alice) }
|
||||||
|
|
||||||
|
it 'update status' do
|
||||||
|
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test').save
|
||||||
|
|
||||||
|
subject.call(status, json, json)
|
||||||
|
expect(status.reload.text).to_not eq content
|
||||||
|
expect(status.mentioned_accounts.pluck(:id)).to_not include alice.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when hit ng words for reply to follower' do
|
||||||
|
let(:json_tags) do
|
||||||
|
[
|
||||||
|
{ type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(alice) },
|
||||||
|
]
|
||||||
|
end
|
||||||
|
let(:content) { 'ng word test' }
|
||||||
|
let(:thread) { Fabricate(:status, account: alice) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
alice.follow!(status.account)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'update status' do
|
||||||
|
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test').save
|
||||||
|
|
||||||
|
subject.call(status, json, json)
|
||||||
|
expect(status.reload.text).to eq content
|
||||||
|
expect(status.mentioned_accounts.pluck(:id)).to include alice.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when using hashtag under limit' do
|
||||||
|
let(:json_tags) do
|
||||||
|
[
|
||||||
|
{ type: 'Hashtag', name: 'a' },
|
||||||
|
{ type: 'Hashtag', name: 'b' },
|
||||||
|
]
|
||||||
|
end
|
||||||
|
let(:content) { 'ohagi is good' }
|
||||||
|
|
||||||
|
it 'update status' do
|
||||||
|
Form::AdminSettings.new(post_hash_tags_max: 2).save
|
||||||
|
|
||||||
|
subject.call(status, json, json)
|
||||||
|
expect(status.reload.text).to eq content
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when using hashtag over limit' do
|
||||||
|
let(:json_tags) do
|
||||||
|
[
|
||||||
|
{ type: 'Hashtag', name: 'a' },
|
||||||
|
{ type: 'Hashtag', name: 'b' },
|
||||||
|
{ type: 'Hashtag', name: 'c' },
|
||||||
|
]
|
||||||
|
end
|
||||||
|
let(:content) { 'ohagi is good' }
|
||||||
|
|
||||||
|
it 'update status' do
|
||||||
|
Form::AdminSettings.new(post_hash_tags_max: 2).save
|
||||||
|
|
||||||
|
subject.call(status, json, json)
|
||||||
|
expect(status.reload.text).to_not eq content
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,6 +9,8 @@ RSpec.describe BackupService, type: :service do
|
||||||
let!(:attachment) { Fabricate(:media_attachment, account: user.account) }
|
let!(:attachment) { Fabricate(:media_attachment, account: user.account) }
|
||||||
let!(:status) { Fabricate(:status, account: user.account, text: 'Hello', visibility: :public, media_attachments: [attachment]) }
|
let!(:status) { Fabricate(:status, account: user.account, text: 'Hello', visibility: :public, media_attachments: [attachment]) }
|
||||||
let!(:private_status) { Fabricate(:status, account: user.account, text: 'secret', visibility: :private) }
|
let!(:private_status) { Fabricate(:status, account: user.account, text: 'secret', visibility: :private) }
|
||||||
|
let!(:limited_status) { Fabricate(:status, account: user.account, text: 'sec mutual', visibility: :limited, limited_scope: :mutual) }
|
||||||
|
let!(:reblog_status) { Fabricate(:status, account: user.account, reblog_of_id: Fabricate(:status).id) }
|
||||||
let!(:favourite) { Fabricate(:favourite, account: user.account) }
|
let!(:favourite) { Fabricate(:favourite, account: user.account) }
|
||||||
let!(:bookmark) { Fabricate(:bookmark, account: user.account) }
|
let!(:bookmark) { Fabricate(:bookmark, account: user.account) }
|
||||||
let!(:backup) { Fabricate(:backup, user: user) }
|
let!(:backup) { Fabricate(:backup, user: user) }
|
||||||
|
@ -60,10 +62,12 @@ RSpec.describe BackupService, type: :service do
|
||||||
aggregate_failures do
|
aggregate_failures do
|
||||||
expect(json['@context']).to_not be_nil
|
expect(json['@context']).to_not be_nil
|
||||||
expect(json['type']).to eq 'OrderedCollection'
|
expect(json['type']).to eq 'OrderedCollection'
|
||||||
expect(json['totalItems']).to eq 2
|
expect(json['totalItems']).to eq 4
|
||||||
expect(json['orderedItems'][0]['@context']).to be_nil
|
expect(json['orderedItems'][0]['@context']).to be_nil
|
||||||
expect(json['orderedItems'][0]).to include_create_item(status)
|
expect(json['orderedItems'][0]).to include_create_item(status)
|
||||||
expect(json['orderedItems'][1]).to include_create_item(private_status)
|
expect(json['orderedItems'][1]).to include_create_item(private_status)
|
||||||
|
expect(json['orderedItems'][2]).to include_create_item(limited_status)
|
||||||
|
expect(json['orderedItems'][3]).to include_announce_item(reblog_status)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -98,4 +102,11 @@ RSpec.describe BackupService, type: :service do
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def include_announce_item(status)
|
||||||
|
include({
|
||||||
|
'type' => 'Announce',
|
||||||
|
'object' => ActivityPub::TagManager.instance.uri_for(status.reblog),
|
||||||
|
})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -510,6 +510,26 @@ RSpec.describe PostStatusService, type: :service do
|
||||||
expect(status).to be_persisted
|
expect(status).to be_persisted
|
||||||
expect(status.text).to eq text
|
expect(status.text).to eq text
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'using hashtag under limit' do
|
||||||
|
account = Fabricate(:account)
|
||||||
|
text = '#a #b'
|
||||||
|
Form::AdminSettings.new(post_hash_tags_max: 2).save
|
||||||
|
|
||||||
|
status = subject.call(account, text: text)
|
||||||
|
|
||||||
|
expect(status).to be_persisted
|
||||||
|
expect(status.tags.count).to eq 2
|
||||||
|
expect(status.text).to eq text
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'using hashtag over limit' do
|
||||||
|
account = Fabricate(:account)
|
||||||
|
text = '#a #b #c'
|
||||||
|
Form::AdminSettings.new(post_hash_tags_max: 2).save
|
||||||
|
|
||||||
|
expect { subject.call(account, text: text) }.to raise_error Mastodon::ValidationError
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_status_with_options(**options)
|
def create_status_with_options(**options)
|
||||||
|
|
|
@ -218,4 +218,103 @@ RSpec.describe UpdateStatusService, type: :service do
|
||||||
subject.call(status, status.account_id, text: 'Bar')
|
subject.call(status, status.account_id, text: 'Bar')
|
||||||
expect(ActivityPub::DistributionWorker).to have_received(:perform_async)
|
expect(ActivityPub::DistributionWorker).to have_received(:perform_async)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'ng word is set' do
|
||||||
|
let(:account) { Fabricate(:account) }
|
||||||
|
let(:status) { PostStatusService.new.call(account, text: 'ohagi') }
|
||||||
|
|
||||||
|
it 'hit ng words' do
|
||||||
|
text = 'ng word test'
|
||||||
|
Form::AdminSettings.new(ng_words: 'test').save
|
||||||
|
|
||||||
|
expect { subject.call(status, status.account_id, text: text) }.to raise_error(Mastodon::ValidationError)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'not hit ng words' do
|
||||||
|
text = 'ng word aiueo'
|
||||||
|
Form::AdminSettings.new(ng_words: 'test').save
|
||||||
|
|
||||||
|
status2 = subject.call(status, status.account_id, text: text)
|
||||||
|
|
||||||
|
expect(status2).to be_persisted
|
||||||
|
expect(status2.text).to eq text
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'hit ng words for mention' do
|
||||||
|
Fabricate(:account, username: 'ohagi', domain: nil)
|
||||||
|
text = 'ng word test @ohagi'
|
||||||
|
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save
|
||||||
|
|
||||||
|
expect { subject.call(status, status.account_id, text: text) }.to raise_error(Mastodon::ValidationError)
|
||||||
|
expect(status.reload.text).to_not eq text
|
||||||
|
expect(status.mentioned_accounts.pluck(:username)).to_not include 'ohagi'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'hit ng words for mention but local posts are not checked' do
|
||||||
|
Fabricate(:account, username: 'ohagi', domain: nil)
|
||||||
|
text = 'ng word test @ohagi'
|
||||||
|
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '0').save
|
||||||
|
|
||||||
|
status2 = subject.call(status, status.account_id, text: text)
|
||||||
|
|
||||||
|
expect(status2).to be_persisted
|
||||||
|
expect(status2.text).to eq text
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'hit ng words for mention to follower' do
|
||||||
|
mentioned = Fabricate(:account, username: 'ohagi', domain: nil)
|
||||||
|
mentioned.follow!(account)
|
||||||
|
text = 'ng word test @ohagi'
|
||||||
|
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save
|
||||||
|
|
||||||
|
status2 = subject.call(status, status.account_id, text: text)
|
||||||
|
|
||||||
|
expect(status2).to be_persisted
|
||||||
|
expect(status2.text).to eq text
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'hit ng words for reply' do
|
||||||
|
text = 'ng word test'
|
||||||
|
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save
|
||||||
|
|
||||||
|
status = PostStatusService.new.call(account, text: 'hello', thread: Fabricate(:status))
|
||||||
|
|
||||||
|
expect { subject.call(status, status.account_id, text: text) }.to raise_error(Mastodon::ValidationError)
|
||||||
|
expect(status.reload.text).to_not eq text
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'hit ng words for reply to follower' do
|
||||||
|
mentioned = Fabricate(:account, username: 'ohagi', domain: nil)
|
||||||
|
mentioned.follow!(account)
|
||||||
|
text = 'ng word test'
|
||||||
|
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save
|
||||||
|
|
||||||
|
status = PostStatusService.new.call(account, text: 'hello', thread: Fabricate(:status, account: mentioned))
|
||||||
|
|
||||||
|
status = subject.call(status, status.account_id, text: text)
|
||||||
|
|
||||||
|
expect(status).to be_persisted
|
||||||
|
expect(status.text).to eq text
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'using hashtag under limit' do
|
||||||
|
text = '#a #b'
|
||||||
|
Form::AdminSettings.new(post_hash_tags_max: 2).save
|
||||||
|
|
||||||
|
subject.call(status, status.account_id, text: text)
|
||||||
|
|
||||||
|
expect(status.reload.tags.count).to eq 2
|
||||||
|
expect(status.text).to eq text
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'using hashtag over limit' do
|
||||||
|
text = '#a #b #c'
|
||||||
|
Form::AdminSettings.new(post_hash_tags_max: 2).save
|
||||||
|
|
||||||
|
expect { subject.call(status, status.account_id, text: text) }.to raise_error Mastodon::ValidationError
|
||||||
|
|
||||||
|
expect(status.reload.tags.count).to eq 0
|
||||||
|
expect(status.text).to_not eq text
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue