From 5f37b33730d4573997df8693a578ebed84786219 Mon Sep 17 00:00:00 2001 From: nira <44765674+gitnira@users.noreply.github.com> Date: Wed, 2 Jul 2025 17:58:20 +0900 Subject: [PATCH 1/2] kb-release --- .../activitypub/contexts_controller.rb | 23 ++++ .../activitypub/references_controller.rb | 90 ++++++++++++++ .../admin/friend_servers_controller.rb | 93 ++++++++++++++ .../admin/ng_rule_histories_controller.rb | 24 ++++ app/controllers/admin/ng_rules_controller.rb | 115 ++++++++++++++++++ .../admin/ng_words/keywords_controller.rb | 34 ++++++ .../admin/ng_words/settings_controller.rb | 11 ++ .../admin/ng_words/white_list_controller.rb | 34 ++++++ app/controllers/admin/ng_words_controller.rb | 56 +++++++++ .../admin/ngword_histories_controller.rb | 19 +++ .../admin/sensitive_words_controller.rb | 47 +++++++ .../admin/special_domains_controller.rb | 34 ++++++ .../admin/special_instances_controller.rb | 34 ++++++ .../api/v1/accounts/antennas_controller.rb | 18 +++ .../api/v1/accounts/circles_controller.rb | 18 +++ .../accounts/exclude_antennas_controller.rb | 18 +++ .../api/v1/accounts/pins_controller.rb | 30 +++++ .../api/v1/antennas/accounts_controller.rb | 95 +++++++++++++++ .../api/v1/antennas/domains_controller.rb | 54 ++++++++ .../antennas/exclude_accounts_controller.rb | 104 ++++++++++++++++ .../v1/antennas/exclude_domains_controller.rb | 46 +++++++ .../antennas/exclude_keywords_controller.rb | 46 +++++++ .../v1/antennas/exclude_tags_controller.rb | 50 ++++++++ .../api/v1/antennas/keywords_controller.rb | 62 ++++++++++ .../api/v1/antennas/tags_controller.rb | 58 +++++++++ app/controllers/api/v1/antennas_controller.rb | 47 +++++++ .../statuses_controller.rb | 94 ++++++++++++++ .../api/v1/bookmark_categories_controller.rb | 47 +++++++ .../api/v1/circles/accounts_controller.rb | 93 ++++++++++++++ .../api/v1/circles/statuses_controller.rb | 65 ++++++++++ app/controllers/api/v1/circles_controller.rb | 47 +++++++ .../api/v1/emoji_reactions_controller.rb | 63 ++++++++++ .../api/v1/reaction_deck_controller.rb | 92 ++++++++++++++ .../bookmark_categories_controller.rb | 18 +++ ...emoji_reactioned_by_accounts_controller.rb | 76 ++++++++++++ .../v1/statuses/emoji_reactions_controller.rb | 63 ++++++++++ .../statuses/mentioned_accounts_controller.rb | 74 +++++++++++ .../referred_by_statuses_controller.rb | 80 ++++++++++++ .../api/v1/timelines/antenna_controller.rb | 51 ++++++++ .../preferences/custom_css_controller.rb | 9 ++ .../preferences/reaching_controller.rb | 9 ++ .../settings/privacy_extra_controller.rb | 27 ++++ app/controllers/system_css_controller.rb | 8 ++ app/controllers/user_custom_css_controller.rb | 16 +++ app/helpers/dtl_helper.rb | 11 ++ app/helpers/follow_helper.rb | 32 +++++ app/helpers/high_load_helper.rb | 8 ++ app/helpers/kmyblue_capabilities_helper.rb | 56 +++++++++ app/helpers/ng_rule_helper.rb | 28 +++++ app/helpers/registration_limitation_helper.rb | 55 +++++++++ app/javascript/entrypoints/inert.ts | 4 + app/javascript/entrypoints/mailer.ts | 3 + app/javascript/entrypoints/public-path.ts | 23 ++++ app/javascript/images/quote-stripes.svg | 10 ++ 54 files changed, 2422 insertions(+) create mode 100644 app/controllers/activitypub/contexts_controller.rb create mode 100644 app/controllers/activitypub/references_controller.rb create mode 100644 app/controllers/admin/friend_servers_controller.rb create mode 100644 app/controllers/admin/ng_rule_histories_controller.rb create mode 100644 app/controllers/admin/ng_rules_controller.rb create mode 100644 app/controllers/admin/ng_words/keywords_controller.rb create mode 100644 app/controllers/admin/ng_words/settings_controller.rb create mode 100644 app/controllers/admin/ng_words/white_list_controller.rb create mode 100644 app/controllers/admin/ng_words_controller.rb create mode 100644 app/controllers/admin/ngword_histories_controller.rb create mode 100644 app/controllers/admin/sensitive_words_controller.rb create mode 100644 app/controllers/admin/special_domains_controller.rb create mode 100644 app/controllers/admin/special_instances_controller.rb create mode 100644 app/controllers/api/v1/accounts/antennas_controller.rb create mode 100644 app/controllers/api/v1/accounts/circles_controller.rb create mode 100644 app/controllers/api/v1/accounts/exclude_antennas_controller.rb create mode 100644 app/controllers/api/v1/accounts/pins_controller.rb create mode 100644 app/controllers/api/v1/antennas/accounts_controller.rb create mode 100644 app/controllers/api/v1/antennas/domains_controller.rb create mode 100644 app/controllers/api/v1/antennas/exclude_accounts_controller.rb create mode 100644 app/controllers/api/v1/antennas/exclude_domains_controller.rb create mode 100644 app/controllers/api/v1/antennas/exclude_keywords_controller.rb create mode 100644 app/controllers/api/v1/antennas/exclude_tags_controller.rb create mode 100644 app/controllers/api/v1/antennas/keywords_controller.rb create mode 100644 app/controllers/api/v1/antennas/tags_controller.rb create mode 100644 app/controllers/api/v1/antennas_controller.rb create mode 100644 app/controllers/api/v1/bookmark_categories/statuses_controller.rb create mode 100644 app/controllers/api/v1/bookmark_categories_controller.rb create mode 100644 app/controllers/api/v1/circles/accounts_controller.rb create mode 100644 app/controllers/api/v1/circles/statuses_controller.rb create mode 100644 app/controllers/api/v1/circles_controller.rb create mode 100644 app/controllers/api/v1/emoji_reactions_controller.rb create mode 100644 app/controllers/api/v1/reaction_deck_controller.rb create mode 100644 app/controllers/api/v1/statuses/bookmark_categories_controller.rb create mode 100644 app/controllers/api/v1/statuses/emoji_reactioned_by_accounts_controller.rb create mode 100644 app/controllers/api/v1/statuses/emoji_reactions_controller.rb create mode 100644 app/controllers/api/v1/statuses/mentioned_accounts_controller.rb create mode 100644 app/controllers/api/v1/statuses/referred_by_statuses_controller.rb create mode 100644 app/controllers/api/v1/timelines/antenna_controller.rb create mode 100644 app/controllers/settings/preferences/custom_css_controller.rb create mode 100644 app/controllers/settings/preferences/reaching_controller.rb create mode 100644 app/controllers/settings/privacy_extra_controller.rb create mode 100644 app/controllers/system_css_controller.rb create mode 100644 app/controllers/user_custom_css_controller.rb create mode 100644 app/helpers/dtl_helper.rb create mode 100644 app/helpers/follow_helper.rb create mode 100644 app/helpers/high_load_helper.rb create mode 100644 app/helpers/kmyblue_capabilities_helper.rb create mode 100644 app/helpers/ng_rule_helper.rb create mode 100644 app/helpers/registration_limitation_helper.rb create mode 100644 app/javascript/entrypoints/inert.ts create mode 100644 app/javascript/entrypoints/mailer.ts create mode 100644 app/javascript/entrypoints/public-path.ts create mode 100644 app/javascript/images/quote-stripes.svg diff --git a/app/controllers/activitypub/contexts_controller.rb b/app/controllers/activitypub/contexts_controller.rb new file mode 100644 index 00000000000000..a3263ed82ec01f --- /dev/null +++ b/app/controllers/activitypub/contexts_controller.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class ActivityPub::ContextsController < ActivityPub::BaseController + include SignatureVerification + + vary_by -> { 'Signature' if authorized_fetch_mode? } + + before_action :set_context + + def show + expires_in 3.minutes, public: true + render json: @context, + serializer: ActivityPub::ContextSerializer, + adapter: ActivityPub::Adapter, + content_type: 'application/activity+json' + end + + private + + def set_context + @context = Conversation.find(params[:id]) + end +end diff --git a/app/controllers/activitypub/references_controller.rb b/app/controllers/activitypub/references_controller.rb new file mode 100644 index 00000000000000..7412540fe440be --- /dev/null +++ b/app/controllers/activitypub/references_controller.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +class ActivityPub::ReferencesController < ActivityPub::BaseController + include SignatureVerification + include Authorization + include AccountOwnedConcern + + before_action :require_signature!, if: :authorized_fetch_mode? + before_action :set_status + + def index + expires_in 0, public: public_fetch_mode? + render json: references_collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json', skip_activities: true + end + + private + + def pundit_user + signed_request_account + end + + def set_status + @status = @account.statuses.find(params[:status_id]) + authorize @status, :show? + rescue Mastodon::NotPermittedError + not_found + end + + def load_statuses + cached_references + end + + def cached_references + preload_collection(Status.where(id: results).reorder(:id), Status) + end + + def results + @results ||= begin + references = @status.reference_objects.order(target_status_id: :asc) + references = references.where('target_status_id > ?', page_params[:min_id]) if page_params[:min_id].present? + references = references.limit(limit_param(references_limit)) + references.pluck(:target_status_id) + end + end + + def references_limit + StatusReference::REFERENCES_LIMIT + end + + def pagination_min_id + results.last + end + + def records_continue? + results.size == limit_param(references_limit) + end + + def references_collection_presenter + page = ActivityPub::CollectionPresenter.new( + id: ActivityPub::TagManager.instance.references_uri_for(@status, page_params), + type: :unordered, + part_of: ActivityPub::TagManager.instance.references_uri_for(@status), + items: load_statuses.map(&:uri), + next: next_page + ) + + return page if page_requested? + + ActivityPub::CollectionPresenter.new( + type: :unordered, + id: ActivityPub::TagManager.instance.references_uri_for(@status), + size: @status.status_referred_by_count, + first: page + ) + end + + def page_requested? + truthy_param?(:page) + end + + def next_page + return unless records_continue? + + ActivityPub::TagManager.instance.references_uri_for(@status, page_params.merge(min_id: pagination_min_id)) + end + + def page_params + params_slice(:min_id, :limit).merge(page: true) + end +end diff --git a/app/controllers/admin/friend_servers_controller.rb b/app/controllers/admin/friend_servers_controller.rb new file mode 100644 index 00000000000000..ec41ba672c833c --- /dev/null +++ b/app/controllers/admin/friend_servers_controller.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +module Admin + class FriendServersController < BaseController + before_action :set_friend, except: [:index, :new, :create] + before_action :warn_signatures_not_enabled!, only: [:new, :edit, :create, :follow, :unfollow, :accept, :reject] + + def index + authorize :friend_server, :update? + @friends = FriendDomain.all + end + + def new + authorize :friend_server, :update? + @friend = FriendDomain.new + end + + def edit + authorize :friend_server, :update? + end + + def create + authorize :friend_server, :update? + + @friend = FriendDomain.new(resource_params) + + if @friend.save + @friend.follow! + redirect_to admin_friend_servers_path + else + render action: :new + end + end + + def update + authorize :friend_server, :update? + + if @friend.update(update_resource_params) + redirect_to admin_friend_servers_path + else + render action: :edit + end + end + + def destroy + authorize :friend_server, :update? + @friend.destroy + redirect_to admin_friend_servers_path + end + + def follow + authorize :friend_server, :update? + @friend.follow! + render action: :edit + end + + def unfollow + authorize :friend_server, :update? + @friend.unfollow! + render action: :edit + end + + def accept + authorize :friend_server, :update? + @friend.accept! + render action: :edit + end + + def reject + authorize :friend_server, :update? + @friend.reject! + render action: :edit + end + + private + + def set_friend + @friend = FriendDomain.find(params[:id]) + end + + def resource_params + params.expect(friend_domain: [:domain, :inbox_url, :available, :pseudo_relay, :delivery_local, :unlocked, :allow_all_posts]) + end + + def update_resource_params + params.expect(friend_domain: [:inbox_url, :available, :pseudo_relay, :delivery_local, :unlocked, :allow_all_posts]) + end + + def warn_signatures_not_enabled! + flash.now[:error] = I18n.t('admin.relays.signatures_not_enabled') if authorized_fetch_mode? + end + end +end diff --git a/app/controllers/admin/ng_rule_histories_controller.rb b/app/controllers/admin/ng_rule_histories_controller.rb new file mode 100644 index 00000000000000..9dccefaf49a703 --- /dev/null +++ b/app/controllers/admin/ng_rule_histories_controller.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Admin + class NgRuleHistoriesController < BaseController + before_action :set_ng_rule + before_action :set_histories + + PER_PAGE = 20 + + def show + authorize :ng_words, :show? + end + + private + + def set_ng_rule + @ng_rule = ::NgRule.find(params[:id]) + end + + def set_histories + @histories = NgRuleHistory.where(ng_rule_id: params[:id]).order(id: :desc).page(params[:page]).per(PER_PAGE) + end + end +end diff --git a/app/controllers/admin/ng_rules_controller.rb b/app/controllers/admin/ng_rules_controller.rb new file mode 100644 index 00000000000000..0bdda41c0cd514 --- /dev/null +++ b/app/controllers/admin/ng_rules_controller.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +module Admin + class NgRulesController < BaseController + before_action :set_ng_rule, only: [:edit, :update, :destroy, :duplicate] + + def index + authorize :ng_words, :show? + + @ng_rules = ::NgRule.order(id: :asc) + end + + def new + authorize :ng_words, :show? + + @ng_rule = ::NgRule.build + end + + def edit + authorize :ng_words, :show? + end + + def create + authorize :ng_words, :create? + + begin + test_words! + rescue + flash[:alert] = I18n.t('admin.ng_rules.test_error') + redirect_to new_admin_ng_rule_path + return + end + + @ng_rule = ::NgRule.build(resource_params) + + if @ng_rule.save + redirect_to admin_ng_rules_path + else + render :new + end + end + + def update + authorize :ng_words, :create? + + begin + test_words! + rescue + flash[:alert] = I18n.t('admin.ng_rules.test_error') + redirect_to edit_admin_ng_rule_path(id: @ng_rule.id) + return + end + + if @ng_rule.update(resource_params) + redirect_to admin_ng_rules_path + else + render :edit + end + end + + def duplicate + authorize :ng_words, :create? + + @ng_rule = @ng_rule.copy! + + flash[:alert] = I18n.t('admin.ng_rules.copy_error') unless @ng_rule.save + + redirect_to admin_ng_rules_path + end + + def destroy + authorize :ng_words, :create? + + @ng_rule.destroy + redirect_to admin_ng_rules_path + end + + private + + def set_ng_rule + @ng_rule = ::NgRule.find(params[:id]) + end + + def resource_params + params.expect(ng_rule: [:title, :expires_in, :available, :account_domain, :account_username, :account_display_name, + :account_note, :account_field_name, :account_field_value, :account_avatar_state, + :account_header_state, :account_include_local, :status_spoiler_text, :status_text, :status_tag, + :status_sensitive_state, :status_cw_state, :status_media_state, :status_poll_state, + :status_mention_state, :status_reference_state, + :status_quote_state, :status_reply_state, :status_media_threshold, :status_poll_threshold, + :status_mention_threshold, :status_allow_follower_mention, + :reaction_allow_follower, :emoji_reaction_name, :emoji_reaction_origin_domain, + :status_reference_threshold, :account_allow_followed_by_local, :record_history_also_local, + status_visibility: [], status_searchability: [], reaction_type: []]) + end + + def test_words! + arr = [ + resource_params[:account_domain], + resource_params[:account_username], + resource_params[:account_display_name], + resource_params[:account_note], + resource_params[:account_field_name], + resource_params[:account_field_value], + resource_params[:status_spoiler_text], + resource_params[:status_text], + resource_params[:status_tag], + resource_params[:emoji_reaction_name], + resource_params[:emoji_reaction_origin_domain], + ].compact_blank.join("\n") + + Admin::NgRule.extract_test!(arr) if arr.present? + end + end +end diff --git a/app/controllers/admin/ng_words/keywords_controller.rb b/app/controllers/admin/ng_words/keywords_controller.rb new file mode 100644 index 00000000000000..10969204e8d72e --- /dev/null +++ b/app/controllers/admin/ng_words/keywords_controller.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Admin + class NgWords::KeywordsController < NgWordsController + def show + super + @ng_words = ::NgWord.caches.presence || [::NgWord.new] + end + + protected + + def validate + begin + ::NgWord.save_from_raws(settings_params_test) + return true + rescue + flash[:alert] = I18n.t('admin.ng_words.test_error') + redirect_to after_update_redirect_path + end + + false + end + + def avoid_save? + true + end + + private + + def after_update_redirect_path + admin_ng_words_keywords_path + end + end +end diff --git a/app/controllers/admin/ng_words/settings_controller.rb b/app/controllers/admin/ng_words/settings_controller.rb new file mode 100644 index 00000000000000..63edadfce5e37f --- /dev/null +++ b/app/controllers/admin/ng_words/settings_controller.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Admin + class NgWords::SettingsController < NgWordsController + protected + + def after_update_redirect_path + admin_ng_words_settings_path + end + end +end diff --git a/app/controllers/admin/ng_words/white_list_controller.rb b/app/controllers/admin/ng_words/white_list_controller.rb new file mode 100644 index 00000000000000..8fdb7df32731f5 --- /dev/null +++ b/app/controllers/admin/ng_words/white_list_controller.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Admin + class NgWords::WhiteListController < NgWordsController + def show + super + @white_list_domains = SpecifiedDomain.white_list_domain_caches.presence || [SpecifiedDomain.new] + end + + protected + + def validate + begin + SpecifiedDomain.save_from_raws_as_white_list(settings_params_list) + return true + rescue + flash[:alert] = I18n.t('admin.ng_words.save_error') + redirect_to after_update_redirect_path + end + + false + end + + def after_update_redirect_path + admin_ng_words_white_list_path + end + + private + + def settings_params_list + params.require(:form_admin_settings)[:specified_domains] + end + end +end diff --git a/app/controllers/admin/ng_words_controller.rb b/app/controllers/admin/ng_words_controller.rb new file mode 100644 index 00000000000000..9e437f8c8bdf15 --- /dev/null +++ b/app/controllers/admin/ng_words_controller.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module Admin + class NgWordsController < BaseController + def show + authorize :ng_words, :show? + + @admin_settings = Form::AdminSettings.new + end + + def create + authorize :ng_words, :create? + + return unless validate + + if avoid_save? + flash[:notice] = I18n.t('generic.changes_saved_msg') + redirect_to after_update_redirect_path + return + end + + @admin_settings = Form::AdminSettings.new(settings_params) + + if @admin_settings.save + flash[:notice] = I18n.t('generic.changes_saved_msg') + redirect_to after_update_redirect_path + else + render :show + end + end + + protected + + def validate + true + end + + def after_update_redirect_path + admin_ng_words_path + end + + def avoid_save? + false + end + + private + + def settings_params + params.expect(form_admin_settings: [*Form::AdminSettings::KEYS]) + end + + def settings_params_test + params.expect(form_admin_settings: [ng_words_test: [keywords: [], regexps: [], strangers: [], temporary_ids: []]])['ng_words_test'] + end + end +end diff --git a/app/controllers/admin/ngword_histories_controller.rb b/app/controllers/admin/ngword_histories_controller.rb new file mode 100644 index 00000000000000..90f13db2fe7927 --- /dev/null +++ b/app/controllers/admin/ngword_histories_controller.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Admin + class NgwordHistoriesController < BaseController + before_action :set_histories + + PER_PAGE = 20 + + def index + authorize :ng_words, :show? + end + + private + + def set_histories + @histories = NgwordHistory.order(id: :desc).page(params[:page]).per(PER_PAGE) + end + end +end diff --git a/app/controllers/admin/sensitive_words_controller.rb b/app/controllers/admin/sensitive_words_controller.rb new file mode 100644 index 00000000000000..716dcc708a2025 --- /dev/null +++ b/app/controllers/admin/sensitive_words_controller.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Admin + class SensitiveWordsController < BaseController + def show + authorize :sensitive_words, :show? + + @admin_settings = Form::AdminSettings.new + @sensitive_words = ::SensitiveWord.caches.presence || [::SensitiveWord.new] + end + + def create + authorize :sensitive_words, :create? + + begin + ::SensitiveWord.save_from_raws(settings_params_test) + rescue + flash[:alert] = I18n.t('admin.ng_words.test_error') + redirect_to after_update_redirect_path + return + end + + @admin_settings = Form::AdminSettings.new(settings_params) + + if @admin_settings.save + flash[:notice] = I18n.t('generic.changes_saved_msg') + redirect_to after_update_redirect_path + else + render :index + end + end + + private + + def after_update_redirect_path + admin_sensitive_words_path + end + + def settings_params + params.expect(form_admin_settings: [*Form::AdminSettings::KEYS]) + end + + def settings_params_test + params.require(:form_admin_settings)[:sensitive_words_test] + end + end +end diff --git a/app/controllers/admin/special_domains_controller.rb b/app/controllers/admin/special_domains_controller.rb new file mode 100644 index 00000000000000..b36fe28d6e9b2c --- /dev/null +++ b/app/controllers/admin/special_domains_controller.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Admin + class SpecialDomainsController < BaseController + def show + authorize :instance, :show? + + @admin_settings = Form::AdminSettings.new + end + + def create + authorize :instance, :destroy? + + @admin_settings = Form::AdminSettings.new(settings_params) + + if @admin_settings.save + flash[:notice] = I18n.t('generic.changes_saved_msg') + redirect_to after_update_redirect_path + else + render :show + end + end + + private + + def after_update_redirect_path + admin_special_domains_path + end + + def settings_params + params.expect(form_admin_settings: [*Form::AdminSettings::KEYS]) + end + end +end diff --git a/app/controllers/admin/special_instances_controller.rb b/app/controllers/admin/special_instances_controller.rb new file mode 100644 index 00000000000000..a16bae13efe931 --- /dev/null +++ b/app/controllers/admin/special_instances_controller.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Admin + class SpecialInstancesController < BaseController + def show + authorize :instance, :show? + + @admin_settings = Form::AdminSettings.new + end + + def create + authorize :instance, :destroy? + + @admin_settings = Form::AdminSettings.new(settings_params) + + if @admin_settings.save + flash[:notice] = I18n.t('generic.changes_saved_msg') + redirect_to after_update_redirect_path + else + render :show + end + end + + private + + def after_update_redirect_path + admin_special_instances_path + end + + def settings_params + params.expect(form_admin_settings: [*Form::AdminSettings::KEYS]) + end + end +end diff --git a/app/controllers/api/v1/accounts/antennas_controller.rb b/app/controllers/api/v1/accounts/antennas_controller.rb new file mode 100644 index 00000000000000..957a4fb555679e --- /dev/null +++ b/app/controllers/api/v1/accounts/antennas_controller.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class Api::V1::Accounts::AntennasController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:lists' } + before_action :require_user! + before_action :set_account + + def index + @antennas = @account.suspended? ? [] : @account.joined_antennas.where(account: current_account) + render json: @antennas, each_serializer: REST::AntennaSerializer + end + + private + + def set_account + @account = Account.find(params[:account_id]) + end +end diff --git a/app/controllers/api/v1/accounts/circles_controller.rb b/app/controllers/api/v1/accounts/circles_controller.rb new file mode 100644 index 00000000000000..1b21eb7ce45776 --- /dev/null +++ b/app/controllers/api/v1/accounts/circles_controller.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class Api::V1::Accounts::CirclesController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:lists' } + before_action :require_user! + before_action :set_account + + def index + @circles = @account.suspended? ? [] : @account.joined_circles.where(account: current_account) + render json: @circles, each_serializer: REST::CircleSerializer + end + + private + + def set_account + @account = Account.find(params[:account_id]) + end +end diff --git a/app/controllers/api/v1/accounts/exclude_antennas_controller.rb b/app/controllers/api/v1/accounts/exclude_antennas_controller.rb new file mode 100644 index 00000000000000..65d75dbc6ec635 --- /dev/null +++ b/app/controllers/api/v1/accounts/exclude_antennas_controller.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class Api::V1::Accounts::ExcludeAntennasController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:lists' } + before_action :require_user! + before_action :set_account + + def index + @antennas = @account.suspended? ? [] : current_account.antennas.where("exclude_accounts @> '#{@account.id}'") + render json: @antennas, each_serializer: REST::AntennaSerializer + end + + private + + def set_account + @account = Account.find(params[:account_id]) + end +end diff --git a/app/controllers/api/v1/accounts/pins_controller.rb b/app/controllers/api/v1/accounts/pins_controller.rb new file mode 100644 index 00000000000000..0eb13c048ce759 --- /dev/null +++ b/app/controllers/api/v1/accounts/pins_controller.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +class Api::V1::Accounts::PinsController < Api::BaseController + include Authorization + + before_action -> { doorkeeper_authorize! :write, :'write:accounts' } + before_action :require_user! + before_action :set_account + + def create + AccountPin.find_or_create_by!(account: current_account, target_account: @account) + render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter + end + + def destroy + pin = AccountPin.find_by(account: current_account, target_account: @account) + pin&.destroy! + render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter + end + + private + + def set_account + @account = Account.find(params[:account_id]) + end + + def relationships_presenter + AccountRelationshipsPresenter.new([@account], current_user.account_id) + end +end diff --git a/app/controllers/api/v1/antennas/accounts_controller.rb b/app/controllers/api/v1/antennas/accounts_controller.rb new file mode 100644 index 00000000000000..c50cbcdf3f887b --- /dev/null +++ b/app/controllers/api/v1/antennas/accounts_controller.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +class Api::V1::Antennas::AccountsController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:lists' }, only: [:show] + before_action -> { doorkeeper_authorize! :write, :'write:lists' }, except: [:show] + + before_action :require_user! + before_action :set_antenna + + after_action :insert_pagination_headers, only: :show + + def show + @accounts = load_accounts + render json: @accounts, each_serializer: REST::AccountSerializer + end + + def create + ApplicationRecord.transaction do + antenna_accounts.each do |account| + @antenna.antenna_accounts.create!(account: account, exclude: false) + @antenna.update!(any_accounts: false) if @antenna.any_accounts + end + end + + render_empty + end + + def destroy + AntennaAccount.where(antenna: @antenna, account_id: account_ids).destroy_all + @antenna.update!(any_accounts: true) unless @antenna.antenna_accounts.where(exclude: false).any? + render_empty + end + + private + + def set_antenna + @antenna = Antenna.where(account: current_account).find(params[:antenna_id]) + end + + def load_accounts + if unlimited? + @antenna.accounts.without_suspended.includes(:account_stat).all + else + @antenna.accounts.without_suspended.includes(:account_stat).paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id]) + end + end + + def antenna_accounts + Account.find(account_ids) + end + + def account_ids + Array(resource_params[:account_ids]) + end + + def resource_params + params.permit(account_ids: []) + end + + def insert_pagination_headers + set_pagination_headers(next_path, prev_path) + end + + def next_path + return if unlimited? + + api_v1_list_accounts_url pagination_params(max_id: pagination_max_id) if records_continue? + end + + def prev_path + return if unlimited? + + api_v1_list_accounts_url pagination_params(since_id: pagination_since_id) unless @accounts.empty? + end + + def pagination_max_id + @accounts.last.id + end + + def pagination_since_id + @accounts.first.id + end + + def records_continue? + @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) + end + + def pagination_params(core_params) + params.slice(:limit).permit(:limit).merge(core_params) + end + + def unlimited? + params[:limit] == '0' + end +end diff --git a/app/controllers/api/v1/antennas/domains_controller.rb b/app/controllers/api/v1/antennas/domains_controller.rb new file mode 100644 index 00000000000000..554b8d613cda04 --- /dev/null +++ b/app/controllers/api/v1/antennas/domains_controller.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +class Api::V1::Antennas::DomainsController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:lists' }, only: [:show] + before_action -> { doorkeeper_authorize! :write, :'write:lists' }, except: [:show] + + before_action :require_user! + before_action :set_antenna + + def show + @domains = load_domains + @exclude_domains = load_exclude_domains + render json: { domains: @domains, exclude_domains: @exclude_domains } + end + + def create + ApplicationRecord.transaction do + domains.each do |domain| + @antenna.antenna_domains.create!(name: domain, exclude: false) + @antenna.update!(any_domains: false) if @antenna.any_domains + end + end + + render_empty + end + + def destroy + AntennaDomain.where(antenna: @antenna, name: domains).destroy_all + @antenna.update!(any_domains: true) unless @antenna.antenna_domains.where(exclude: false).any? + render_empty + end + + private + + def set_antenna + @antenna = Antenna.where(account: current_account).find(params[:antenna_id]) + end + + def load_domains + @antenna.antenna_domains.pluck(:name) + end + + def load_exclude_domains + @antenna.exclude_domains || [] + end + + def domains + Array(resource_params[:domains]) + end + + def resource_params + params.permit(domains: []) + end +end diff --git a/app/controllers/api/v1/antennas/exclude_accounts_controller.rb b/app/controllers/api/v1/antennas/exclude_accounts_controller.rb new file mode 100644 index 00000000000000..cdb9173c117331 --- /dev/null +++ b/app/controllers/api/v1/antennas/exclude_accounts_controller.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +class Api::V1::Antennas::ExcludeAccountsController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:lists' }, only: [:show] + before_action -> { doorkeeper_authorize! :write, :'write:lists' }, except: [:show] + + before_action :require_user! + before_action :set_antenna + + after_action :insert_pagination_headers, only: :show + + def show + @accounts = load_accounts + render json: @accounts, each_serializer: REST::AccountSerializer + end + + def create + new_accounts = @antenna.exclude_accounts || [] + antenna_accounts.each do |account| + raise Mastodon::ValidationError, I18n.t('antennas.errors.duplicate_account') if new_accounts.include?(account.id) + + new_accounts << account.id + end + + raise Mastodon::ValidationError, I18n.t('antennas.errors.limit.accounts') if new_accounts.size > Antenna::ACCOUNTS_PER_ANTENNA_LIMIT + + @antenna.update!(exclude_accounts: new_accounts) + + render_empty + end + + def destroy + new_accounts = @antenna.exclude_accounts || [] + new_accounts -= antenna_accounts.pluck(:id) + + @antenna.update!(exclude_accounts: new_accounts) + + render_empty + end + + private + + def set_antenna + @antenna = Antenna.where(account: current_account).find(params[:antenna_id]) + end + + def load_accounts + return [] if @antenna.exclude_accounts.nil? + + if unlimited? + Account.where(id: @antenna.exclude_accounts).without_suspended.includes(:account_stat).all + else + Account.where(id: @antenna.exclude_accounts).without_suspended.includes(:account_stat).paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id]) + end + end + + def antenna_accounts + Account.find(account_ids) + end + + def account_ids + Array(resource_params[:account_ids]) + end + + def resource_params + params.permit(account_ids: []) + end + + def insert_pagination_headers + set_pagination_headers(next_path, prev_path) + end + + def next_path + return if unlimited? + + api_v1_list_accounts_url pagination_params(max_id: pagination_max_id) if records_continue? + end + + def prev_path + return if unlimited? + + api_v1_list_accounts_url pagination_params(since_id: pagination_since_id) unless @accounts.empty? + end + + def pagination_max_id + @accounts.last.id + end + + def pagination_since_id + @accounts.first.id + end + + def records_continue? + @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) + end + + def pagination_params(core_params) + params.slice(:limit).permit(:limit).merge(core_params) + end + + def unlimited? + params[:limit] == '0' + end +end diff --git a/app/controllers/api/v1/antennas/exclude_domains_controller.rb b/app/controllers/api/v1/antennas/exclude_domains_controller.rb new file mode 100644 index 00000000000000..235a44d5933816 --- /dev/null +++ b/app/controllers/api/v1/antennas/exclude_domains_controller.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +class Api::V1::Antennas::ExcludeDomainsController < Api::BaseController + before_action -> { doorkeeper_authorize! :write, :'write:lists' } + + before_action :require_user! + before_action :set_antenna + + def create + new_domains = @antenna.exclude_domains || [] + domains.each do |domain| + raise Mastodon::ValidationError, I18n.t('antennas.errors.duplicate_domain') if new_domains.include?(domain) + + new_domains << domain + end + + raise Mastodon::ValidationError, I18n.t('antennas.errors.limit.domains') if new_domains.size > Antenna::KEYWORDS_PER_ANTENNA_LIMIT + + @antenna.update!(exclude_domains: new_domains) + + render_empty + end + + def destroy + new_domains = @antenna.exclude_domains || [] + new_domains -= domains + + @antenna.update!(exclude_domains: new_domains) + + render_empty + end + + private + + def set_antenna + @antenna = Antenna.where(account: current_account).find(params[:antenna_id]) + end + + def domains + Array(resource_params[:domains]) + end + + def resource_params + params.permit(domains: []) + end +end diff --git a/app/controllers/api/v1/antennas/exclude_keywords_controller.rb b/app/controllers/api/v1/antennas/exclude_keywords_controller.rb new file mode 100644 index 00000000000000..171dac5f807412 --- /dev/null +++ b/app/controllers/api/v1/antennas/exclude_keywords_controller.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +class Api::V1::Antennas::ExcludeKeywordsController < Api::BaseController + before_action -> { doorkeeper_authorize! :write, :'write:lists' } + + before_action :require_user! + before_action :set_antenna + + def create + new_keywords = @antenna.exclude_keywords || [] + keywords.each do |keyword| + raise Mastodon::ValidationError, I18n.t('antennas.errors.duplicate_keyword') if new_keywords.include?(keyword) + + new_keywords << keyword + end + + raise Mastodon::ValidationError, I18n.t('antennas.errors.limit.keywords') if new_keywords.size > Antenna::KEYWORDS_PER_ANTENNA_LIMIT + + @antenna.update!(exclude_keywords: new_keywords) + + render_empty + end + + def destroy + new_keywords = @antenna.exclude_keywords || [] + new_keywords -= keywords + + @antenna.update!(exclude_keywords: new_keywords) + + render_empty + end + + private + + def set_antenna + @antenna = Antenna.where(account: current_account).find(params[:antenna_id]) + end + + def keywords + Array(resource_params[:keywords]) + end + + def resource_params + params.permit(keywords: []) + end +end diff --git a/app/controllers/api/v1/antennas/exclude_tags_controller.rb b/app/controllers/api/v1/antennas/exclude_tags_controller.rb new file mode 100644 index 00000000000000..bf9e087369205c --- /dev/null +++ b/app/controllers/api/v1/antennas/exclude_tags_controller.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +class Api::V1::Antennas::ExcludeTagsController < Api::BaseController + before_action -> { doorkeeper_authorize! :write, :'write:lists' } + + before_action :require_user! + before_action :set_antenna + + def create + new_tags = @antenna.exclude_tags || [] + tags.map(&:id).each do |tag| + raise Mastodon::ValidationError, I18n.t('antennas.errors.duplicate_tag') if new_tags.include?(tag) + + new_tags << tag + end + + raise Mastodon::ValidationError, I18n.t('antennas.errors.limit.tags') if new_tags.size > Antenna::TAGS_PER_ANTENNA_LIMIT + + @antenna.update!(exclude_tags: new_tags) + + render_empty + end + + def destroy + new_tags = @antenna.exclude_tags || [] + new_tags -= exist_tags.pluck(:id) + + @antenna.update!(exclude_tags: new_tags) + + render_empty + end + + private + + def set_antenna + @antenna = Antenna.where(account: current_account).find(params[:antenna_id]) + end + + def tags + Tag.find_or_create_by_names(Array(resource_params[:tags])) + end + + def exist_tags + Tag.matching_name(Array(resource_params[:tags])) + end + + def resource_params + params.permit(tags: []) + end +end diff --git a/app/controllers/api/v1/antennas/keywords_controller.rb b/app/controllers/api/v1/antennas/keywords_controller.rb new file mode 100644 index 00000000000000..5260a66bc033c4 --- /dev/null +++ b/app/controllers/api/v1/antennas/keywords_controller.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +class Api::V1::Antennas::KeywordsController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:lists' }, only: [:show] + before_action -> { doorkeeper_authorize! :write, :'write:lists' }, except: [:show] + + before_action :require_user! + before_action :set_antenna + + def show + @keywords = load_keywords + @exclude_keywords = load_exclude_keywords + render json: { keywords: @keywords, exclude_keywords: @exclude_keywords } + end + + def create + new_keywords = @antenna.keywords || [] + keywords.each do |keyword| + raise Mastodon::ValidationError, I18n.t('antennas.errors.duplicate_keyword') if new_keywords.include?(keyword) + raise Mastodon::ValidationError, I18n.t('antennas.errors.too_short_keyword') if keyword.length < 2 + + new_keywords << keyword + end + + raise Mastodon::ValidationError, I18n.t('antennas.errors.limit.keywords') if new_keywords.size > Antenna::KEYWORDS_PER_ANTENNA_LIMIT + + @antenna.update!(keywords: new_keywords, any_keywords: new_keywords.empty?) + + render_empty + end + + def destroy + new_keywords = @antenna.keywords || [] + new_keywords -= keywords + + @antenna.update!(keywords: new_keywords, any_keywords: new_keywords.empty?) + + render_empty + end + + private + + def set_antenna + @antenna = Antenna.where(account: current_account).find(params[:antenna_id]) + end + + def load_keywords + @antenna.keywords || [] + end + + def load_exclude_keywords + @antenna.exclude_keywords || [] + end + + def keywords + Array(resource_params[:keywords]) + end + + def resource_params + params.permit(keywords: []) + end +end diff --git a/app/controllers/api/v1/antennas/tags_controller.rb b/app/controllers/api/v1/antennas/tags_controller.rb new file mode 100644 index 00000000000000..fe0bb6b4ebd873 --- /dev/null +++ b/app/controllers/api/v1/antennas/tags_controller.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +class Api::V1::Antennas::TagsController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:lists' }, only: [:show] + before_action -> { doorkeeper_authorize! :write, :'write:lists' }, except: [:show] + + before_action :require_user! + before_action :set_antenna + + def show + @tags = load_tags + @exclude_tags = load_exclude_tags + render json: { tags: @tags, exclude_tags: @exclude_tags.pluck(:name) } + end + + def create + ApplicationRecord.transaction do + tags.each do |tag| + @antenna.antenna_tags.create!(tag: tag, exclude: false) + @antenna.update!(any_tags: false) if @antenna.any_tags + end + end + + render_empty + end + + def destroy + AntennaTag.where(antenna: @antenna, tag: exist_tags).destroy_all + @antenna.update!(any_tags: true) unless @antenna.antenna_tags.where(exclude: false).any? + render_empty + end + + private + + def set_antenna + @antenna = Antenna.where(account: current_account).find(params[:antenna_id]) + end + + def load_tags + @antenna.tags.pluck(:name) + end + + def load_exclude_tags + Tag.where(id: @antenna.exclude_tags || []) + end + + def tags + Tag.find_or_create_by_names(Array(resource_params[:tags])) + end + + def exist_tags + Tag.matching_name(Array(resource_params[:tags])) + end + + def resource_params + params.permit(tags: []) + end +end diff --git a/app/controllers/api/v1/antennas_controller.rb b/app/controllers/api/v1/antennas_controller.rb new file mode 100644 index 00000000000000..4040263c005048 --- /dev/null +++ b/app/controllers/api/v1/antennas_controller.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +class Api::V1::AntennasController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:lists' }, only: [:index, :show] + before_action -> { doorkeeper_authorize! :write, :'write:lists' }, except: [:index, :show] + + before_action :require_user! + before_action :set_antenna, except: [:index, :create] + + rescue_from ArgumentError do |e| + render json: { error: e.to_s }, status: 422 + end + + def index + @antennas = Antenna.where(account: current_account).all + render json: @antennas, each_serializer: REST::AntennaSerializer + end + + def show + render json: @antenna, serializer: REST::AntennaSerializer + end + + def create + @antenna = Antenna.create!(antenna_params.merge(account: current_account)) + render json: @antenna, serializer: REST::AntennaSerializer + end + + def update + @antenna.update!(antenna_params) + render json: @antenna, serializer: REST::AntennaSerializer + end + + def destroy + @antenna.destroy! + render_empty + end + + private + + def set_antenna + @antenna = Antenna.where(account: current_account).find(params[:id]) + end + + def antenna_params + params.permit(:title, :list_id, :insert_feeds, :stl, :ltl, :with_media_only, :ignore_reblog, :favourite) + end +end diff --git a/app/controllers/api/v1/bookmark_categories/statuses_controller.rb b/app/controllers/api/v1/bookmark_categories/statuses_controller.rb new file mode 100644 index 00000000000000..a195fce97de301 --- /dev/null +++ b/app/controllers/api/v1/bookmark_categories/statuses_controller.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +class Api::V1::BookmarkCategories::StatusesController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:lists' }, only: [:show] + before_action -> { doorkeeper_authorize! :write, :'write:lists' }, except: [:show] + + before_action :require_user! + before_action :set_bookmark_category + + after_action :insert_pagination_headers, only: :show + + def show + @statuses = load_statuses + render json: @statuses, each_serializer: REST::StatusSerializer + end + + def create + ApplicationRecord.transaction do + bookmark_category_statuses.each do |status| + Bookmark.find_or_create_by!(account: current_account, status: status) + @bookmark_category.statuses << status + end + end + + render_empty + end + + def destroy + BookmarkCategoryStatus.where(bookmark_category: @bookmark_category, status_id: status_ids).destroy_all + render_empty + end + + private + + def set_bookmark_category + @bookmark_category = current_account.bookmark_categories.find(params[:bookmark_category_id]) + end + + def load_statuses + if unlimited? + @bookmark_category.statuses.includes(:status_stat).all + else + @bookmark_category.statuses.includes(:status_stat).paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id]) + end + end + + def bookmark_category_statuses + Status.find(status_ids) + end + + def status_ids + Array(resource_params[:status_ids]) + end + + def resource_params + params.permit(status_ids: []) + end + + def insert_pagination_headers + set_pagination_headers(next_path, prev_path) + end + + def next_path + return if unlimited? + + api_v1_bookmark_category_statuses_url pagination_params(max_id: pagination_max_id) if records_continue? + end + + def prev_path + return if unlimited? + + api_v1_bookmark_category_statuses_url pagination_params(since_id: pagination_since_id) unless @statuses.empty? + end + + def pagination_max_id + @statuses.last.id + end + + def pagination_since_id + @statuses.first.id + end + + def records_continue? + @statuses.size == limit_param(DEFAULT_STATUSES_LIMIT) + end + + def pagination_params(core_params) + params.slice(:limit).permit(:limit).merge(core_params) + end + + def unlimited? + params[:limit] == '0' + end +end diff --git a/app/controllers/api/v1/bookmark_categories_controller.rb b/app/controllers/api/v1/bookmark_categories_controller.rb new file mode 100644 index 00000000000000..c32828630d0b8c --- /dev/null +++ b/app/controllers/api/v1/bookmark_categories_controller.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +class Api::V1::BookmarkCategoriesController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:lists' }, only: [:index, :show] + before_action -> { doorkeeper_authorize! :write, :'write:lists' }, except: [:index, :show] + + before_action :require_user! + before_action :set_bookmark_category, except: [:index, :create] + + rescue_from ArgumentError do |e| + render json: { error: e.to_s }, status: 422 + end + + def index + @bookmark_categories = BookmarkCategory.where(account: current_account).all + render json: @bookmark_categories, each_serializer: REST::BookmarkCategorySerializer + end + + def show + render json: @bookmark_category, serializer: REST::BookmarkCategorySerializer + end + + def create + @bookmark_category = BookmarkCategory.create!(bookmark_category_params.merge(account: current_account)) + render json: @bookmark_category, serializer: REST::BookmarkCategorySerializer + end + + def update + @bookmark_category.update!(bookmark_category_params) + render json: @bookmark_category, serializer: REST::BookmarkCategorySerializer + end + + def destroy + @bookmark_category.destroy! + render_empty + end + + private + + def set_bookmark_category + @bookmark_category = BookmarkCategory.where(account: current_account).find(params[:id]) + end + + def bookmark_category_params + params.permit(:title) + end +end diff --git a/app/controllers/api/v1/circles/accounts_controller.rb b/app/controllers/api/v1/circles/accounts_controller.rb new file mode 100644 index 00000000000000..e0d43bd9507a1b --- /dev/null +++ b/app/controllers/api/v1/circles/accounts_controller.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +class Api::V1::Circles::AccountsController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:lists' }, only: [:show] + before_action -> { doorkeeper_authorize! :write, :'write:lists' }, except: [:show] + + before_action :require_user! + before_action :set_circle + + after_action :insert_pagination_headers, only: :show + + def show + @accounts = load_accounts + render json: @accounts, each_serializer: REST::AccountSerializer + end + + def create + ApplicationRecord.transaction do + circle_accounts.each do |account| + @circle.accounts << account + end + end + + render_empty + end + + def destroy + CircleAccount.where(circle: @circle, account_id: account_ids).destroy_all + render_empty + end + + private + + def set_circle + @circle = Circle.where(account: current_account).find(params[:circle_id]) + end + + def load_accounts + if unlimited? + @circle.accounts.without_suspended.includes(:account_stat).all + else + @circle.accounts.without_suspended.includes(:account_stat).paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id]) + end + end + + def circle_accounts + Account.find(account_ids) + end + + def account_ids + Array(resource_params[:account_ids]) + end + + def resource_params + params.permit(account_ids: []) + end + + def insert_pagination_headers + set_pagination_headers(next_path, prev_path) + end + + def next_path + return if unlimited? + + api_v1_circle_accounts_url pagination_params(max_id: pagination_max_id) if records_continue? + end + + def prev_path + return if unlimited? + + api_v1_circle_accounts_url pagination_params(since_id: pagination_since_id) unless @accounts.empty? + end + + def pagination_max_id + @accounts.last.id + end + + def pagination_since_id + @accounts.first.id + end + + def records_continue? + @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) + end + + def pagination_params(core_params) + params.slice(:limit).permit(:limit).merge(core_params) + end + + def unlimited? + params[:limit] == '0' + end +end diff --git a/app/controllers/api/v1/circles/statuses_controller.rb b/app/controllers/api/v1/circles/statuses_controller.rb new file mode 100644 index 00000000000000..705731936b27c3 --- /dev/null +++ b/app/controllers/api/v1/circles/statuses_controller.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +class Api::V1::Circles::StatusesController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:lists' }, only: [:show] + + before_action :require_user! + before_action :set_circle + + after_action :insert_pagination_headers, only: :show + + def show + @statuses = load_statuses + render json: @statuses, each_serializer: REST::StatusSerializer + end + + private + + def set_circle + @circle = current_account.circles.find(params[:circle_id]) + end + + def load_statuses + if unlimited? + @circle.statuses.includes(:status_stat).all + else + @circle.statuses.includes(:status_stat).paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id]) + end + end + + def insert_pagination_headers + set_pagination_headers(next_path, prev_path) + end + + def next_path + return if unlimited? + + api_v1_circle_statuses_url pagination_params(max_id: pagination_max_id) if records_continue? + end + + def prev_path + return if unlimited? + + api_v1_circle_statuses_url pagination_params(since_id: pagination_since_id) unless @statuses.empty? + end + + def pagination_max_id + @statuses.last.id + end + + def pagination_since_id + @statuses.first.id + end + + def records_continue? + @statuses.size == limit_param(DEFAULT_STATUSES_LIMIT) + end + + def pagination_params(core_params) + params.slice(:limit).permit(:limit).merge(core_params) + end + + def unlimited? + params[:limit] == '0' + end +end diff --git a/app/controllers/api/v1/circles_controller.rb b/app/controllers/api/v1/circles_controller.rb new file mode 100644 index 00000000000000..53c9adf14ebe9b --- /dev/null +++ b/app/controllers/api/v1/circles_controller.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +class Api::V1::CirclesController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:lists' }, only: [:index, :show] + before_action -> { doorkeeper_authorize! :write, :'write:lists' }, except: [:index, :show] + + before_action :require_user! + before_action :set_circle, except: [:index, :create] + + rescue_from ArgumentError do |e| + render json: { error: e.to_s }, status: 422 + end + + def index + @circles = Circle.where(account: current_account).all + render json: @circles, each_serializer: REST::CircleSerializer + end + + def show + render json: @circle, serializer: REST::CircleSerializer + end + + def create + @circle = Circle.create!(circle_params.merge(account: current_account)) + render json: @circle, serializer: REST::CircleSerializer + end + + def update + @circle.update!(circle_params) + render json: @circle, serializer: REST::CircleSerializer + end + + def destroy + @circle.destroy! + render_empty + end + + private + + def set_circle + @circle = Circle.where(account: current_account).find(params[:id]) + end + + def circle_params + params.permit(:title) + end +end diff --git a/app/controllers/api/v1/emoji_reactions_controller.rb b/app/controllers/api/v1/emoji_reactions_controller.rb new file mode 100644 index 00000000000000..5e913eef2a26d0 --- /dev/null +++ b/app/controllers/api/v1/emoji_reactions_controller.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +class Api::V1::EmojiReactionsController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:favourites' } + before_action :require_user! + after_action :insert_pagination_headers + + def index + @statuses = load_statuses + render json: @statuses, each_serializer: REST::StatusSerializer, + relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), + emoji_reaction_permitted_account_ids: EmojiReactionAccountsPresenter.new(@statuses, current_user&.account_id) + end + + private + + def load_statuses + cached_emoji_reactions + end + + def cached_emoji_reactions + preload_collection(results.map(&:status), EmojiReaction) + end + + def results + @results ||= account_emoji_reactions.joins(:status).eager_load(:status).to_a_paginated_by_id( + limit_param(DEFAULT_STATUSES_LIMIT), + params_slice(:max_id, :since_id, :min_id) + ) + end + + def account_emoji_reactions + current_account.emoji_reactions + end + + def insert_pagination_headers + set_pagination_headers(next_path, prev_path) + end + + def next_path + api_v1_emoji_reactions_url pagination_params(max_id: pagination_max_id) if records_continue? + end + + def prev_path + api_v1_emoji_reactions_url pagination_params(min_id: pagination_since_id) unless results.empty? + end + + def pagination_max_id + results.last.id + end + + def pagination_since_id + results.first.id + end + + def records_continue? + results.size == limit_param(DEFAULT_STATUSES_LIMIT) + end + + def pagination_params(core_params) + params.slice(:limit).permit(:limit).merge(core_params) + end +end diff --git a/app/controllers/api/v1/reaction_deck_controller.rb b/app/controllers/api/v1/reaction_deck_controller.rb new file mode 100644 index 00000000000000..6229eb89d424ac --- /dev/null +++ b/app/controllers/api/v1/reaction_deck_controller.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +class Api::V1::ReactionDeckController < Api::BaseController + include RoutingHelper + + before_action -> { doorkeeper_authorize! :read, :'read:lists' }, only: [:index] + before_action -> { doorkeeper_authorize! :write, :'write:lists' }, only: [:create] + + before_action :require_user! + before_action :set_deck, only: [:index, :create] + + rescue_from ArgumentError do |e| + render json: { error: e.to_s }, status: 422 + end + + def index + render json: remove_metas(@deck) + end + + def create + deck = [] + + shortcodes = [] + (deck_params['emojis'] || []).each do |shortcode| + shortcodes << shortcode.delete(':') + break if shortcodes.length >= User::REACTION_DECK_MAX + end + + custom_emojis = CustomEmoji.where(shortcode: shortcodes, domain: nil) + + shortcodes.each do |shortcode| + custom_emoji = custom_emojis.find { |em| em.shortcode == shortcode } + + emoji_data = {} + + if custom_emoji + emoji_data['name'] = custom_emoji.shortcode + emoji_data['url'] = full_asset_url(custom_emoji.image.url) + emoji_data['static_url'] = full_asset_url(custom_emoji.image.url(:static)) + emoji_data['width'] = custom_emoji.image_width + emoji_data['height'] = custom_emoji.image_height + emoji_data['custom_emoji_id'] = custom_emoji.id + else + emoji_data['name'] = shortcode + end + + deck << emoji_data + end + + current_user.settings['reaction_deck'] = deck.to_json + current_user.save! + + render json: remove_metas(deck) + end + + private + + def set_deck + deck = current_user.setting_reaction_deck ? JSON.parse(current_user.setting_reaction_deck) : [] + @deck = remove_unused_custom_emojis(deck) + end + + def remove_unused_custom_emojis(deck) + custom_ids = [] + deck.each do |item| + custom_ids << item['custom_emoji_id'].to_i if item.key?('custom_emoji_id') + end + custom_emojis = CustomEmoji.where(id: custom_ids) + + deck.each do |item| + next if item['custom_emoji_id'].nil? + + custom_emoji = custom_emojis.find { |em| em.id == item['custom_emoji_id'].to_i } + remove = custom_emoji.nil? || custom_emoji.disabled + item['remove'] = remove if remove + end + deck.filter { |item| !item.key?('remove') } + end + + def remove_metas(deck) + deck.tap do |d| + d.each do |item| + item.delete('custom_emoji_id') + # item.delete('id') if item.key?('id') + end + end + end + + def deck_params + params + end +end diff --git a/app/controllers/api/v1/statuses/bookmark_categories_controller.rb b/app/controllers/api/v1/statuses/bookmark_categories_controller.rb new file mode 100644 index 00000000000000..9d65b96296a3e2 --- /dev/null +++ b/app/controllers/api/v1/statuses/bookmark_categories_controller.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class Api::V1::Statuses::BookmarkCategoriesController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:lists' } + before_action :require_user! + before_action :set_status + + def index + @statuses = @status.deleted_at.present? ? [] : @status.joined_bookmark_categories.where(account: current_account) + render json: @statuses, each_serializer: REST::BookmarkCategorySerializer + end + + private + + def set_status + @status = Status.find(params[:status_id]) + end +end diff --git a/app/controllers/api/v1/statuses/emoji_reactioned_by_accounts_controller.rb b/app/controllers/api/v1/statuses/emoji_reactioned_by_accounts_controller.rb new file mode 100644 index 00000000000000..9c2fb3d4a58fa1 --- /dev/null +++ b/app/controllers/api/v1/statuses/emoji_reactioned_by_accounts_controller.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +class Api::V1::Statuses::EmojiReactionedByAccountsController < Api::BaseController + include Authorization + + before_action -> { authorize_if_got_token! :read, :'read:accounts' } + before_action :set_status + after_action :insert_pagination_headers + + def index + @accounts = load_accounts + render json: @accounts, each_serializer: REST::EmojiReactionAccountSerializer + end + + private + + def load_accounts + return [] unless Setting.enable_emoji_reaction + return [] if current_account.nil? && @status.account.emoji_reaction_policy != :allow + return [] if current_account.present? && !@status.account.show_emoji_reaction?(current_account) + + scope = default_accounts + scope = scope.where.not(account_id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? + scope.merge(paginated_emoji_reactions).to_a + end + + def default_accounts + EmojiReaction + .where(status_id: @status.id) + .includes(:account) + .where(account: { suspended_at: nil }) + end + + def paginated_emoji_reactions + EmojiReaction.paginate_by_max_id( + limit_param(DEFAULT_ACCOUNTS_LIMIT), + params[:max_id], + params[:since_id] + ) + end + + def insert_pagination_headers + set_pagination_headers(next_path, prev_path) + end + + def next_path + api_v1_status_emoji_reactioned_by_index_url pagination_params(max_id: pagination_max_id) if records_continue? + end + + def prev_path + api_v1_status_emoji_reactioned_by_index_url pagination_params(since_id: pagination_since_id) unless @accounts.empty? + end + + def pagination_max_id + @accounts.last.id + end + + def pagination_since_id + @accounts.first.id + end + + def records_continue? + @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) + end + + def set_status + @status = Status.find(params[:status_id]) + authorize @status, :show? + rescue Mastodon::NotPermittedError + not_found + end + + def pagination_params(core_params) + params.slice(:limit).permit(:limit).merge(core_params) + end +end diff --git a/app/controllers/api/v1/statuses/emoji_reactions_controller.rb b/app/controllers/api/v1/statuses/emoji_reactions_controller.rb new file mode 100644 index 00000000000000..1f103beb719a38 --- /dev/null +++ b/app/controllers/api/v1/statuses/emoji_reactions_controller.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +class Api::V1::Statuses::EmojiReactionsController < Api::BaseController + include Authorization + + before_action -> { doorkeeper_authorize! :write, :'write:favourites' } + before_action :require_user! + before_action :set_status, only: %i(create update) + before_action :set_status_without_authorize, only: [:destroy] + + def create + create_private(params[:emoji] || params[:id]) + end + + # For compatible with Fedibird API + def update + create_private(params[:id]) + end + + def destroy + emoji = params[:emoji] || params[:id] + + if emoji + shortcode, domain = emoji.split('@') + emoji_reaction = EmojiReaction.where(account_id: current_account.id).where(status_id: @status.id).where(name: shortcode) + .find { |reaction| domain == '' ? reaction.custom_emoji.nil? : reaction.custom_emoji&.domain == domain } + + authorize @status, :show? if emoji_reaction.nil? + + UnEmojiReactService.new.call(current_account, @status, emoji_reaction) if emoji_reaction.present? + else + authorize @status, :show? + end + + render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new( + [@status], current_account.id + ) + rescue Mastodon::NotPermittedError + not_found + end + + private + + def create_private(emoji) + count = EmojiReaction.where(account: current_account, status: @status).count + raise Mastodon::ValidationError, I18n.t('reactions.errors.limit_reached') if count >= EmojiReaction::EMOJI_REACTION_PER_ACCOUNT_LIMIT + raise Mastodon::ValidationError, I18n.t('reactions.errors.disabled') unless Setting.enable_emoji_reaction + + EmojiReactService.new.call(current_account, @status, emoji) + render json: @status, serializer: REST::StatusSerializer + end + + def set_status + set_status_without_authorize + authorize @status, :show? + rescue Mastodon::NotPermittedError + not_found + end + + def set_status_without_authorize + @status = Status.find(params[:status_id]) + end +end diff --git a/app/controllers/api/v1/statuses/mentioned_accounts_controller.rb b/app/controllers/api/v1/statuses/mentioned_accounts_controller.rb new file mode 100644 index 00000000000000..4d905ef1a6f95a --- /dev/null +++ b/app/controllers/api/v1/statuses/mentioned_accounts_controller.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +class Api::V1::Statuses::MentionedAccountsController < Api::BaseController + include Authorization + + before_action -> { authorize_if_got_token! :read, :'read:accounts' } + before_action :set_status + after_action :insert_pagination_headers + + def index + cache_if_unauthenticated! + @accounts = load_accounts + render json: @accounts, each_serializer: REST::AccountSerializer + end + + private + + def load_accounts + scope = default_accounts + scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? + scope.merge(paginated_mentioned_users).to_a + end + + def default_accounts + Account + .without_suspended + .includes(:mentions, :account_stat) + .references(:mentions) + .where(mentions: { status_id: @status.id }) + end + + def paginated_mentioned_users + Mention.paginate_by_max_id( + limit_param(DEFAULT_ACCOUNTS_LIMIT), + params[:max_id], + params[:since_id] + ) + end + + def insert_pagination_headers + set_pagination_headers(next_path, prev_path) + end + + def next_path + api_v1_status_mentioned_by_index_url pagination_params(max_id: pagination_max_id) if records_continue? + end + + def prev_path + api_v1_status_mentioned_by_index_url pagination_params(since_id: pagination_since_id) unless @accounts.empty? + end + + def pagination_max_id + @accounts.last.mentions.last.id + end + + def pagination_since_id + @accounts.first.mentions.first.id + end + + def records_continue? + @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) + end + + def set_status + @status = Status.find(params[:status_id]) + authorize @status, :show_mentioned_users? + rescue Mastodon::NotPermittedError + not_found + end + + def pagination_params(core_params) + params.slice(:limit).permit(:limit).merge(core_params) + end +end diff --git a/app/controllers/api/v1/statuses/referred_by_statuses_controller.rb b/app/controllers/api/v1/statuses/referred_by_statuses_controller.rb new file mode 100644 index 00000000000000..c13c5ff0e8519d --- /dev/null +++ b/app/controllers/api/v1/statuses/referred_by_statuses_controller.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +class Api::V1::Statuses::ReferredByStatusesController < Api::BaseController + include Authorization + + before_action -> { authorize_if_got_token! :read, :'read:accounts' } + before_action :set_status + after_action :insert_pagination_headers + + def index + @statuses = load_statuses + render json: @statuses, each_serializer: REST::StatusSerializer, + relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), + emoji_reaction_permitted_account_ids: EmojiReactionAccountsPresenter.new(@statuses, current_user&.account_id) + end + + private + + def load_statuses + cached_references + end + + def cached_references + results + end + + def results + return @results if @results + + account = current_user&.account + statuses = Status.where(id: @status.referenced_by_status_objects.select(:status_id)) + account_ids = statuses.map(&:account_id).uniq + domains = statuses.filter_map(&:account_domain).uniq + relations = account&.relations_map(account_ids, domains) || {} + + statuses = preload_collection_paginated_by_id( + statuses, + Status, + limit_param(DEFAULT_STATUSES_LIMIT), + params_slice(:max_id, :since_id, :min_id) + ) + + @results = statuses.filter { |status| !StatusFilter.new(status, account, relations).filtered? } + end + + def insert_pagination_headers + set_pagination_headers(next_path, prev_path) + end + + def next_path + api_v1_status_referred_by_index_url pagination_params(max_id: pagination_max_id) if records_continue? + end + + def prev_path + api_v1_status_referred_by_index_url pagination_params(min_id: pagination_since_id) unless results.empty? + end + + def pagination_max_id + results.last.id + end + + def pagination_since_id + results.first.id + end + + def records_continue? + results.size == limit_param(DEFAULT_STATUSES_LIMIT) + end + + def set_status + @status = Status.find(params[:status_id]) + authorize @status, :show? + rescue Mastodon::NotPermittedError + not_found + end + + def pagination_params(core_params) + params.slice(:limit).permit(:limit).merge(core_params) + end +end diff --git a/app/controllers/api/v1/timelines/antenna_controller.rb b/app/controllers/api/v1/timelines/antenna_controller.rb new file mode 100644 index 00000000000000..69554361be0468 --- /dev/null +++ b/app/controllers/api/v1/timelines/antenna_controller.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +class Api::V1::Timelines::AntennaController < Api::V1::Timelines::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:lists' } + before_action :require_user! + before_action :set_antenna + before_action :set_statuses + + PERMITTED_PARAMS = %i(limit).freeze + + def show + render json: @statuses, + each_serializer: REST::StatusSerializer, + relationships: StatusRelationshipsPresenter.new(@statuses, current_user.account_id) + end + + private + + def set_antenna + @antenna = Antenna.where(account: current_account).find(params[:id]) + end + + def set_statuses + @statuses = cached_list_statuses + end + + def cached_list_statuses + preload_collection list_statuses, Status + end + + def list_statuses + list_feed.get( + limit_param(DEFAULT_STATUSES_LIMIT), + params[:max_id], + params[:since_id], + params[:min_id] + ) + end + + def list_feed + AntennaFeed.new(@antenna) + end + + def next_path + api_v1_timelines_antenna_url params[:id], next_path_params + end + + def prev_path + api_v1_timelines_antenna_url params[:id], prev_path_params + end +end diff --git a/app/controllers/settings/preferences/custom_css_controller.rb b/app/controllers/settings/preferences/custom_css_controller.rb new file mode 100644 index 00000000000000..6a7369ec49183f --- /dev/null +++ b/app/controllers/settings/preferences/custom_css_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Settings::Preferences::CustomCssController < Settings::Preferences::BaseController + private + + def after_update_redirect_path + settings_preferences_custom_css_path + end +end diff --git a/app/controllers/settings/preferences/reaching_controller.rb b/app/controllers/settings/preferences/reaching_controller.rb new file mode 100644 index 00000000000000..bd3e6ae9b2eb5e --- /dev/null +++ b/app/controllers/settings/preferences/reaching_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Settings::Preferences::ReachingController < Settings::Preferences::BaseController + private + + def after_update_redirect_path + settings_preferences_reaching_path + end +end diff --git a/app/controllers/settings/privacy_extra_controller.rb b/app/controllers/settings/privacy_extra_controller.rb new file mode 100644 index 00000000000000..f1292e644cfe03 --- /dev/null +++ b/app/controllers/settings/privacy_extra_controller.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class Settings::PrivacyExtraController < Settings::BaseController + before_action :set_account + + def show; end + + def update + if UpdateAccountService.new.call(@account, account_params.except(:settings)) + current_user.update!(settings_attributes: account_params[:settings]) + ActivityPub::UpdateDistributionWorker.perform_async(@account.id) + redirect_to settings_privacy_extra_path, notice: I18n.t('generic.changes_saved_msg') + else + render :show + end + end + + private + + def account_params + params.expect(account: [settings: UserSettings.keys]) + end + + def set_account + @account = current_account + end +end diff --git a/app/controllers/system_css_controller.rb b/app/controllers/system_css_controller.rb new file mode 100644 index 00000000000000..dd904918946ccc --- /dev/null +++ b/app/controllers/system_css_controller.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class SystemCssController < ActionController::Base # rubocop:disable Rails/ApplicationController + def show + expires_in 3.minutes, public: true + render content_type: 'text/css' + end +end diff --git a/app/controllers/user_custom_css_controller.rb b/app/controllers/user_custom_css_controller.rb new file mode 100644 index 00000000000000..2535e07c035d8f --- /dev/null +++ b/app/controllers/user_custom_css_controller.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class UserCustomCssController < ActionController::Base # rubocop:disable Rails/ApplicationController + before_action :authenticate_user! + + def show + render content_type: 'text/css' + end + + private + + def user_custom_css + current_user.custom_css_text + end + helper_method :user_custom_css +end diff --git a/app/helpers/dtl_helper.rb b/app/helpers/dtl_helper.rb new file mode 100644 index 00000000000000..aa2c414c5f6167 --- /dev/null +++ b/app/helpers/dtl_helper.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module DtlHelper + def dtl_enabled? + ENV.fetch('DTL_ENABLED', 'false') == 'true' + end + + def dtl_tag_name + ENV.fetch('DTL_TAG', 'kmyblue') + end +end diff --git a/app/helpers/follow_helper.rb b/app/helpers/follow_helper.rb new file mode 100644 index 00000000000000..a02e2a8832fec5 --- /dev/null +++ b/app/helpers/follow_helper.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module FollowHelper + def request_pending_follow?(source_account, target_account) + target_account.locked? || source_account.silenced? || block_straight_follow?(source_account) || + ((source_account.bot? || proxy_account?(source_account)) && target_account.user&.setting_lock_follow_from_bot) + end + + def block_straight_follow?(account) + return false if account.local? + + DomainBlock.reject_straight_follow?(account.domain) + end + + def proxy_account?(account) + (account.username.downcase.include?('_proxy') || + account.username.downcase.end_with?('proxy') || + account.username.downcase.include?('_bot_') || + account.username.downcase.end_with?('bot') || + account.display_name&.downcase&.include?('proxy') || + account.display_name&.include?('プロキシ') || + account.note&.include?('プロキシ')) && + (account.following_count.zero? || account.following_count > account.followers_count) && + proxyable_software?(account) + end + + def proxyable_software?(account) + return false if account.local? + + InstanceInfo.proxy_account_software?(account.domain) + end +end diff --git a/app/helpers/high_load_helper.rb b/app/helpers/high_load_helper.rb new file mode 100644 index 00000000000000..b4606c039fdd1f --- /dev/null +++ b/app/helpers/high_load_helper.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module HighLoadHelper + def allow_high_load? + ENV.fetch('ALLOW_HIGH_LOAD', 'true') == 'true' + end + module_function :allow_high_load? +end diff --git a/app/helpers/kmyblue_capabilities_helper.rb b/app/helpers/kmyblue_capabilities_helper.rb new file mode 100644 index 00000000000000..279505bec872f1 --- /dev/null +++ b/app/helpers/kmyblue_capabilities_helper.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module KmyblueCapabilitiesHelper + def fedibird_capabilities + capabilities = %i( + enable_wide_emoji + kmyblue_searchability + searchability + kmyblue_markdown + kmyblue_reaction_deck + kmyblue_visibility_login + status_reference + visibility_mutual + visibility_limited + kmyblue_limited_scope + kmyblue_antenna + kmyblue_bookmark_category + kmyblue_quote + kmyblue_searchability_limited + kmyblue_circle_history + kmyblue_list_notification + kmyblue_server_features + favourite_list + kmyblue_favourite_antenna + ) + + capabilities << :full_text_search if Chewy.enabled? + if Setting.enable_emoji_reaction + capabilities << :emoji_reaction + capabilities << :enable_wide_emoji_reaction + end + capabilities << :kmyblue_visibility_public_unlisted if Setting.enable_public_unlisted_visibility + capabilities << :kmyblue_searchability_public_unlisted if Setting.enable_public_unlisted_visibility + capabilities << :kmyblue_no_public_visibility unless Setting.enable_public_visibility + capabilities << :timeline_no_local unless Setting.enable_local_timeline + + capabilities + end + + def capabilities_for_nodeinfo + capabilities = %i( + enable_wide_emoji + status_reference + quote + emoji_keywords + circle + ) + + if Setting.enable_emoji_reaction + capabilities << :emoji_reaction + capabilities << :enable_wide_emoji_reaction + end + + capabilities + end +end diff --git a/app/helpers/ng_rule_helper.rb b/app/helpers/ng_rule_helper.rb new file mode 100644 index 00000000000000..104442b1171981 --- /dev/null +++ b/app/helpers/ng_rule_helper.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module NgRuleHelper + def check_invalid_status_for_ng_rule!(account, **options) + (check_for_ng_rule!(account, **options) { |rule| !rule.check_status_or_record! }).none? + end + + def check_invalid_reaction_for_ng_rule!(account, **options) + (check_for_ng_rule!(account, **options) { |rule| !rule.check_reaction_or_record! }).none? + end + + private + + def check_for_ng_rule!(account, **options, &block) + NgRule.cached_rules + .map { |raw_rule| Admin::NgRule.new(raw_rule, account, **options) } + .filter(&block) + end + + def do_account_action_for_rule!(account, action) + case action + when :silence + account.silence! + when :suspend + account.suspend! + end + end +end diff --git a/app/helpers/registration_limitation_helper.rb b/app/helpers/registration_limitation_helper.rb new file mode 100644 index 00000000000000..c295dc074665b4 --- /dev/null +++ b/app/helpers/registration_limitation_helper.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module RegistrationLimitationHelper + def reach_registrations_limit? + ((Setting.registrations_limit.presence || 0).positive? && Setting.registrations_limit <= user_count_for_registration) || + ((Setting.registrations_limit_per_day.presence || 0).positive? && Setting.registrations_limit_per_day <= today_increase_user_count) + end + + def user_count_for_registration + Rails.cache.fetch('registrations:user_count') { User.confirmed.enabled.joins(:account).merge(Account.without_suspended).count } + end + + def today_increase_user_count + today_date = Time.now.utc.beginning_of_day.to_i + count = 0 + + if Rails.cache.fetch('registrations:today_date') { today_date } == today_date + count = Rails.cache.fetch('registrations:today_increase_user_count') { today_increase_user_count_value } + else + count = today_increase_user_count_value + Rails.cache.write('registrations:today_date', today_date) + Rails.cache.write('registrations:today_increase_user_count', count) + end + + count + end + + def today_increase_user_count_value + User.confirmed.enabled.where(users: { created_at: Time.now.utc.beginning_of_day.. }).joins(:account).merge(Account.without_suspended).count + end + + def registrations_in_time? + start_hour = Setting.registrations_start_hour + end_hour = Setting.registrations_end_hour + secondary_start_hour = Setting.registrations_secondary_start_hour + secondary_end_hour = Setting.registrations_secondary_end_hour + + start_hour = 0 unless start_hour.is_a?(Integer) + end_hour = 0 unless end_hour.is_a?(Integer) + secondary_start_hour = 0 unless secondary_start_hour.is_a?(Integer) + secondary_end_hour = 0 unless secondary_end_hour.is_a?(Integer) + + return true if start_hour >= end_hour && secondary_start_hour >= secondary_end_hour + + current_hour = Time.now.utc.hour + + (start_hour < end_hour && end_hour.positive? && current_hour.between?(start_hour, end_hour - 1)) || + (secondary_start_hour < secondary_end_hour && secondary_end_hour.positive? && current_hour.between?(secondary_start_hour, secondary_end_hour - 1)) + end + + def reset_registration_limit_caches! + Rails.cache.delete('registrations:user_count') + Rails.cache.delete('registrations:today_increase_user_count') + end +end diff --git a/app/javascript/entrypoints/inert.ts b/app/javascript/entrypoints/inert.ts new file mode 100644 index 00000000000000..7c04a97fafd8a6 --- /dev/null +++ b/app/javascript/entrypoints/inert.ts @@ -0,0 +1,4 @@ +/* Placeholder file to have `inert.scss` compiled by Webpack + This is used by the `wicg-inert` polyfill */ + +import '../styles/inert.scss'; diff --git a/app/javascript/entrypoints/mailer.ts b/app/javascript/entrypoints/mailer.ts new file mode 100644 index 00000000000000..a2ad5e73ac20e8 --- /dev/null +++ b/app/javascript/entrypoints/mailer.ts @@ -0,0 +1,3 @@ +import '../styles/mailer.scss'; + +require.context('../icons'); diff --git a/app/javascript/entrypoints/public-path.ts b/app/javascript/entrypoints/public-path.ts new file mode 100644 index 00000000000000..ac4b9355b952d7 --- /dev/null +++ b/app/javascript/entrypoints/public-path.ts @@ -0,0 +1,23 @@ +// Dynamically set webpack's loading path depending on a meta header, in order +// to share the same assets regardless of instance configuration. +// See https://webpack.js.org/guides/public-path/#on-the-fly + +function removeOuterSlashes(string: string) { + return string.replace(/^\/*/, '').replace(/\/*$/, ''); +} + +function formatPublicPath(host = '', path = '') { + let formattedHost = removeOuterSlashes(host); + if (formattedHost && !/^http/i.test(formattedHost)) { + formattedHost = `//${formattedHost}`; + } + const formattedPath = removeOuterSlashes(path); + return `${formattedHost}/${formattedPath}/`; +} + +const cdnHost = document.querySelector('meta[name=cdn-host]'); + +__webpack_public_path__ = formatPublicPath( + cdnHost ? cdnHost.content : '', + process.env.PUBLIC_OUTPUT_PATH, +); diff --git a/app/javascript/images/quote-stripes.svg b/app/javascript/images/quote-stripes.svg new file mode 100644 index 00000000000000..1234d4d0a65718 --- /dev/null +++ b/app/javascript/images/quote-stripes.svg @@ -0,0 +1,10 @@ + + + + + + + + + + From e813255adaca88a26aca38ad7a95846b571e46a1 Mon Sep 17 00:00:00 2001 From: nira <44765674+gitnira@users.noreply.github.com> Date: Wed, 2 Jul 2025 17:58:53 +0900 Subject: [PATCH 2/2] kb-release --- .devcontainer/compose.yaml | 4 +- .env.production.sample | 1 + .env.test | 2 + .github/FUNDING.yml | 1 + .github/ISSUE_TEMPLATE/1.bug_report.yml | 74 + .github/ISSUE_TEMPLATE/2.feature_request.yml | 16 + .../ISSUE_TEMPLATE/3.spec_change_request.yml | 28 + .github/ISSUE_TEMPLATE/3.troubleshooting.yml | 8 +- .github/ISSUE_TEMPLATE/config.yml | 6 +- .github/renovate.json5 | 21 +- .github/workflows/bundler-audit.yml | 3 + .github/workflows/check-i18n.yml | 6 + .github/workflows/codeql.yml | 6 + .github/workflows/format-check.yml | 3 + .github/workflows/lint-css.yml | 3 + .github/workflows/lint-haml.yml | 3 + .github/workflows/lint-js.yml | 3 + .github/workflows/lint-ruby.yml | 3 + .github/workflows/test-js.yml | 5 +- .github/workflows/test-migrations.yml | 3 + .github/workflows/test-ruby.yml | 130 +- .gitignore | 9 +- .haml-lint.yml | 2 + .haml-lint_todo.yml | 36 + .nvmrc | 2 +- .prettierignore | 10 +- .rubocop/metrics.yml | 6 + .rubocop/naming.yml | 3 - .rubocop/rspec.yml | 3 +- .rubocop_todo.yml | 59 +- .ruby-version | 2 +- AUTHORS_KB.md | 18 + CHANGELOG.md | 288 - CONTRIBUTING.md | 100 +- Dockerfile | 4 +- FEDERATION.md | 1 - Gemfile | 30 +- Gemfile.lock | 285 +- Procfile.dev | 2 +- README.md | 164 +- SECURITY.md | 26 +- app.json | 4 + app/chewy/accounts_index.rb | 88 +- app/chewy/public_statuses_index.rb | 58 +- app/chewy/statuses_index.rb | 89 +- app/chewy/tags_index.rb | 37 +- app/controllers/accounts_controller.rb | 6 +- .../activitypub/outboxes_controller.rb | 2 +- app/controllers/admin/accounts_controller.rb | 24 +- .../admin/custom_emojis_controller.rb | 32 +- .../admin/domain_blocks_controller.rb | 14 +- .../admin/export_domain_blocks_controller.rb | 50 +- app/controllers/admin/instances_controller.rb | 6 +- .../admin/reports/actions_controller.rb | 4 +- app/controllers/admin/rules_controller.rb | 34 +- .../settings/registrations_controller.rb | 9 + app/controllers/admin/statuses_controller.rb | 68 +- .../admin/trends/tags_controller.rb | 6 +- app/controllers/api/base_controller.rb | 10 +- app/controllers/api/fasp/base_controller.rb | 35 +- .../api/v1/accounts/credentials_controller.rb | 2 + .../v1/accounts/featured_tags_controller.rb | 2 +- .../api/v1/accounts/search_controller.rb | 1 + .../api/v1/accounts/statuses_controller.rb | 4 +- app/controllers/api/v1/accounts_controller.rb | 8 +- .../api/v1/admin/domain_blocks_controller.rb | 6 +- .../api/v1/bookmarks_controller.rb | 4 +- .../api/v1/favourites_controller.rb | 4 +- .../api/v1/featured_tags_controller.rb | 2 +- app/controllers/api/v1/filters_controller.rb | 6 +- .../api/v1/instances/rules_controller.rb | 2 +- .../instances/terms_of_services_controller.rb | 3 +- app/controllers/api/v1/lists_controller.rb | 19 +- app/controllers/api/v1/statuses_controller.rb | 23 +- .../api/v1/suggestions_controller.rb | 2 +- app/controllers/api/v1/tags_controller.rb | 13 +- .../api/v1/timelines/home_controller.rb | 6 +- .../api/v1/timelines/public_controller.rb | 4 +- .../api/v1/timelines/tag_controller.rb | 4 +- app/controllers/api/v2/filters_controller.rb | 2 +- .../api/v2/instances_controller.rb | 2 +- app/controllers/api/v2/search_controller.rb | 24 +- .../api/v2/suggestions_controller.rb | 16 - app/controllers/application_controller.rb | 2 +- .../auth/confirmations_controller.rb | 6 + .../auth/registrations_controller.rb | 4 +- app/controllers/auth/sessions_controller.rb | 26 +- app/controllers/backups_controller.rb | 13 +- app/controllers/concerns/cache_concern.rb | 10 + app/controllers/concerns/localized.rb | 2 +- .../concerns/signature_verification.rb | 173 +- .../concerns/web_app_controller_concern.rb | 15 - app/controllers/filters_controller.rb | 2 +- .../follower_accounts_controller.rb | 4 +- .../following_accounts_controller.rb | 4 +- app/controllers/media_proxy_controller.rb | 52 +- .../oauth/authorizations_controller.rb | 4 +- .../authorized_applications_controller.rb | 4 +- app/controllers/oauth/tokens_controller.rb | 2 +- app/controllers/oauth/userinfo_controller.rb | 4 +- .../settings/featured_tags_controller.rb | 2 +- .../settings/preferences/base_controller.rb | 12 +- .../settings/preferences/other_controller.rb | 7 + .../settings/profiles_controller.rb | 2 +- .../statuses_cleanup_controller.rb | 2 +- app/controllers/statuses_controller.rb | 36 +- .../terms_of_service_controller.rb | 11 - .../well_known/oauth_metadata_controller.rb | 6 +- app/helpers/accounts_helper.rb | 12 +- app/helpers/admin/accounts_helper.rb | 1 + app/helpers/admin/action_logs_helper.rb | 4 +- app/helpers/application_helper.rb | 33 +- app/helpers/authorized_fetch_helper.rb | 4 +- app/helpers/context_helper.rb | 11 +- app/helpers/domain_control_helper.rb | 2 +- app/helpers/formatting_helper.rb | 24 +- app/helpers/json_ld_helper.rb | 33 +- app/helpers/media_component_helper.rb | 4 +- app/helpers/registration_helper.rb | 4 +- app/helpers/routing_helper.rb | 19 +- app/helpers/statuses_helper.rb | 3 + app/helpers/theme_helper.rb | 24 +- app/javascript/entrypoints/admin.tsx | 103 +- app/javascript/entrypoints/application.ts | 9 +- app/javascript/entrypoints/embed.tsx | 4 + app/javascript/entrypoints/error.ts | 1 + app/javascript/entrypoints/public.tsx | 9 +- .../entrypoints/remote_interaction_helper.ts | 2 + app/javascript/entrypoints/share.tsx | 4 + app/javascript/entrypoints/sign_up.ts | 1 + .../icons/android-chrome-144x144.png | Bin 5810 -> 8852 bytes .../icons/android-chrome-192x192.png | Bin 8741 -> 11510 bytes .../icons/android-chrome-256x256.png | Bin 11993 -> 14862 bytes app/javascript/icons/android-chrome-36x36.png | Bin 950 -> 1835 bytes .../icons/android-chrome-384x384.png | Bin 21112 -> 24227 bytes app/javascript/icons/android-chrome-48x48.png | Bin 1384 -> 3216 bytes .../icons/android-chrome-512x512.png | Bin 31858 -> 38207 bytes app/javascript/icons/android-chrome-72x72.png | Bin 2262 -> 4724 bytes app/javascript/icons/android-chrome-96x96.png | Bin 3306 -> 6150 bytes .../icons/apple-touch-icon-1024x1024.png | Bin 77950 -> 114584 bytes .../icons/apple-touch-icon-114x114.png | Bin 4123 -> 7114 bytes .../icons/apple-touch-icon-120x120.png | Bin 4366 -> 7494 bytes .../icons/apple-touch-icon-144x144.png | Bin 5810 -> 8852 bytes .../icons/apple-touch-icon-152x152.png | Bin 6177 -> 9125 bytes .../icons/apple-touch-icon-167x167.png | Bin 7041 -> 10012 bytes .../icons/apple-touch-icon-180x180.png | Bin 7709 -> 10755 bytes .../icons/apple-touch-icon-57x57.png | Bin 1673 -> 3787 bytes .../icons/apple-touch-icon-60x60.png | Bin 1761 -> 3950 bytes .../icons/apple-touch-icon-72x72.png | Bin 2262 -> 4724 bytes .../icons/apple-touch-icon-76x76.png | Bin 2360 -> 4918 bytes app/javascript/icons/favicon-16x16.png | Bin 588 -> 986 bytes app/javascript/icons/favicon-32x32.png | Bin 1114 -> 1715 bytes app/javascript/icons/favicon-48x48.png | Bin 1680 -> 3216 bytes app/javascript/images/logo-symbol-icon.svg | 2 +- app/javascript/mastodon/actions/accounts.js | 4 - .../mastodon/actions/accounts_typed.ts | 28 +- app/javascript/mastodon/actions/antennas.js | 87 + .../mastodon/actions/antennas_typed.ts | 13 + .../mastodon/actions/bookmark_categories.js | 189 + .../actions/bookmark_categories_typed.ts | 13 + app/javascript/mastodon/actions/bookmarks.js | 2 + app/javascript/mastodon/actions/circles.js | 196 + .../mastodon/actions/circles_typed.ts | 13 + app/javascript/mastodon/actions/compose.js | 92 +- .../mastodon/actions/dropdown_menu.ts | 6 +- .../mastodon/actions/emoji_reactions.js | 94 + app/javascript/mastodon/actions/favourites.js | 2 + .../mastodon/actions/featured_tags.js | 34 + .../mastodon/actions/importer/index.js | 7 +- .../mastodon/actions/importer/normalizer.js | 38 +- .../mastodon/actions/interactions.js | 317 +- app/javascript/mastodon/actions/lists.js | 30 +- .../mastodon/actions/lists_typed.ts | 8 +- .../mastodon/actions/notification_groups.ts | 16 +- .../mastodon/actions/notifications.js | 31 +- .../mastodon/actions/reaction_deck.js | 79 + app/javascript/mastodon/actions/search.ts | 9 +- app/javascript/mastodon/actions/statuses.js | 69 +- app/javascript/mastodon/actions/streaming.js | 14 +- app/javascript/mastodon/actions/tags.js | 81 + app/javascript/mastodon/actions/tags_typed.ts | 39 +- app/javascript/mastodon/actions/timelines.js | 2 + app/javascript/mastodon/api.ts | 9 +- app/javascript/mastodon/api/accounts.ts | 23 +- app/javascript/mastodon/api/antennas.ts | 143 + .../mastodon/api/bookmark_categories.ts | 49 + app/javascript/mastodon/api/circles.ts | 35 + app/javascript/mastodon/api/lists.ts | 2 - app/javascript/mastodon/api/notifications.ts | 15 +- app/javascript/mastodon/api/polls.ts | 4 +- app/javascript/mastodon/api/tags.ts | 21 +- app/javascript/mastodon/api_types/accounts.ts | 35 +- app/javascript/mastodon/api_types/antennas.ts | 17 + .../mastodon/api_types/bookmark_categories.ts | 6 + app/javascript/mastodon/api_types/circles.ts | 6 + .../mastodon/api_types/custom_emoji.ts | 5 + app/javascript/mastodon/api_types/lists.ts | 5 + .../mastodon/api_types/notifications.ts | 28 + app/javascript/mastodon/api_types/statuses.ts | 17 +- app/javascript/mastodon/api_types/tags.ts | 1 - app/javascript/mastodon/blurhash.ts | 10 +- app/javascript/mastodon/common.js | 11 + .../autosuggest_emoji-test.jsx.snap | 6 +- .../__snapshots__/avatar-test.jsx.snap | 6 +- .../avatar_overlay-test.jsx.snap | 4 +- .../__snapshots__/button-test.jsx.snap | 16 +- .../__snapshots__/display_name-test.jsx.snap | 4 +- .../components/__tests__/avatar-test.jsx | 6 +- .../components/__tests__/button-test.jsx | 6 +- .../mastodon/components/account.tsx | 261 + .../mastodon/components/admin/Counter.jsx | 14 +- .../mastodon/components/admin/Dimension.jsx | 18 +- .../components/admin/ReportReasonSelector.jsx | 2 +- .../mastodon/components/alt_text_badge.tsx | 1 - .../components/autosuggest_textarea.jsx | 8 - app/javascript/mastodon/components/avatar.tsx | 34 +- app/javascript/mastodon/components/button.tsx | 65 + .../mastodon/components/column_header.tsx | 10 +- .../mastodon/components/compacted_status.jsx | 580 + .../mastodon/components/content_warning.tsx | 2 +- .../mastodon/components/counters.tsx | 14 - .../mastodon/components/dropdown_menu.jsx | 345 + .../mastodon/components/dropdown_selector.tsx | 8 +- .../containers/dropdown_menu_container.js | 32 + .../components/edited_timestamp/index.jsx | 77 + .../mastodon/components/emoji_view.jsx | 31 + .../mastodon/components/filter_warning.tsx | 18 +- .../mastodon/components/follow_button.tsx | 15 +- .../mastodon/components/hashtag.tsx | 6 +- .../mastodon/components/hashtag_bar.tsx | 29 +- .../components/hover_card_account.tsx | 139 +- app/javascript/mastodon/components/icon.tsx | 8 +- .../mastodon/components/icon_button.tsx | 188 +- .../mastodon/components/icon_with_badge.tsx | 2 +- .../mastodon/components/load_more.tsx | 12 +- .../mastodon/components/loading_indicator.tsx | 27 +- .../mastodon/components/media_gallery.jsx | 34 +- .../mastodon/components/more_from_author.jsx | 17 + .../mastodon/components/navigation_portal.tsx | 25 + .../picture_in_picture_placeholder.jsx | 37 + app/javascript/mastodon/components/poll.tsx | 4 +- .../mastodon/components/scrollable_list.jsx | 2 +- .../components/searchability_icon.tsx | 80 + .../mastodon/components/short_number.tsx | 8 +- app/javascript/mastodon/components/status.jsx | 157 +- .../mastodon/components/status_action_bar.jsx | 179 +- .../mastodon/components/status_banner.tsx | 88 +- .../mastodon/components/status_content.jsx | 12 +- .../components/status_emoji_reactions_bar.jsx | 110 + .../mastodon/components/status_list.jsx | 6 +- .../mastodon/components/visibility_icon.tsx | 72 +- .../containers/compacted_status_container.jsx | 78 + .../mastodon/containers/compose_container.jsx | 2 + .../containers/dropdown_menu_container.js | 50 + .../mastodon/containers/mastodon.jsx | 6 +- .../mastodon/containers/media_container.jsx | 12 +- .../mastodon/containers/status_container.jsx | 36 +- .../mastodon/features/about/index.jsx | 144 +- .../account/components/account_note.jsx | 173 + .../account/components/featured_tags.jsx | 51 + .../containers/account_note_container.js | 19 + .../containers/featured_tags_container.js | 17 + .../mastodon/features/account/navigation.jsx | 52 + .../features/account_gallery/index.tsx | 83 +- .../components/account_header.tsx | 433 +- .../features/account_timeline/index.jsx | 46 +- .../features/alt_text_modal/index.tsx | 85 +- .../mastodon/features/antenna_adder/index.tsx | 232 + .../features/antenna_timeline/index.jsx | 206 + .../mastodon/features/antennas/filtering.tsx | 730 + .../mastodon/features/antennas/index.tsx | 190 + .../mastodon/features/antennas/members.tsx | 392 + .../mastodon/features/antennas/new.tsx | 537 + .../mastodon/features/audio/index.jsx | 588 + .../mastodon/features/audio/visualizer.js | 136 + .../features/bookmark_categories/index.tsx | 181 + .../features/bookmark_categories/new.tsx | 167 + .../bookmark_category_adder/index.tsx | 268 + .../bookmark_category_statuses/index.jsx | 195 + .../features/bookmarked_statuses/index.jsx | 117 + .../mastodon/features/circle_adder/index.tsx | 213 + .../features/circle_statuses/index.jsx | 195 + .../mastodon/features/circles/index.tsx | 145 + .../mastodon/features/circles/members.tsx | 377 + .../mastodon/features/circles/new.tsx | 212 + .../closed_registrations_modal/index.jsx | 12 +- .../compose/components/action_bar.jsx | 68 + .../compose/components/circle_dropdown.jsx | 273 + .../compose/components/compose_form.jsx | 59 +- .../components/emoji_picker_dropdown.jsx | 37 +- .../components/expiration_dropdown.jsx | 276 + .../components/featured_tags_dropdown.jsx | 266 + .../compose/components/navigation_bar.jsx | 35 + .../compose/components/privacy_dropdown.jsx | 99 +- .../features/compose/components/search.tsx | 100 +- .../components/searchability_dropdown.jsx | 298 + .../features/compose/components/upload.tsx | 4 +- .../compose/components/upload_form.tsx | 68 +- .../compose/components/upload_progress.tsx | 3 +- .../features/compose/components/warning.tsx | 59 +- .../containers/circle_dropdown_container.js | 33 + .../containers/compose_form_container.js | 12 + .../emoji_picker_dropdown_container.js | 55 +- .../expiration_dropdown_container.js | 30 + .../featured_tags_dropdown_container.js | 30 + .../containers/markdown_button_container.js | 32 + .../containers/poll_button_container.js | 2 +- .../containers/privacy_dropdown_container.js | 11 + .../searchability_dropdown_container.js | 30 + .../containers/upload_button_container.js | 3 +- .../mastodon/features/compose/index.jsx | 138 + .../components/conversation.jsx | 4 +- .../directory/components/account_card.tsx | 15 +- .../mastodon/features/directory/index.tsx | 9 +- .../mastodon/features/emoji/emoji.js | 4 +- .../features/emoji/emoji_compressed.d.ts | 58 + .../features/emoji/emoji_compressed.js | 201 + .../mastodon/features/emoji/emoji_map.json | 2 +- .../features/emoji/emoji_mart_data_light.ts | 7 +- .../mastodon/features/emoji/emoji_picker.js | 7 + .../mastodon/features/emoji/emoji_sheet.json | 1 + .../emoji/emoji_unicode_mapping_light.ts | 5 +- .../mastodon/features/emoji/emoji_utils.js | 2 +- .../features/emoji/unicode_to_filename.ts | 26 + .../features/emoji/unicode_to_filename_s.js | 27 + .../features/emoji/unicode_to_unified_name.ts | 21 + .../emoji/unicode_to_unified_name_s.js | 22 + .../features/emoji_reacted_statuses/index.jsx | 114 + .../features/emoji_reactions/index.jsx | 122 + .../features/explore/components/card.jsx | 75 + .../features/explore/components/story.jsx | 2 +- .../mastodon/features/explore/index.tsx | 9 +- .../mastodon/features/explore/links.jsx | 10 + .../mastodon/features/explore/statuses.jsx | 2 + .../mastodon/features/explore/tags.jsx | 10 + .../features/favourited_statuses/index.jsx | 117 + .../mastodon/features/favourites/index.jsx | 2 + .../mastodon/features/firehose/index.jsx | 30 +- .../mastodon/features/followed_tags/index.jsx | 95 + .../mastodon/features/followers/index.jsx | 8 +- .../mastodon/features/following/index.jsx | 10 +- .../components/announcements.jsx | 1 + .../getting_started/components/trends.jsx | 54 + .../containers/trends_container.js | 15 + .../features/getting_started/index.jsx | 195 + .../components/hashtag_header.tsx | 66 +- .../components/column_settings.tsx | 13 - .../mastodon/features/home_timeline/index.jsx | 9 +- .../features/keyboard_shortcuts/index.jsx | 4 + .../mastodon/features/link_timeline/index.tsx | 4 +- .../mastodon/features/list_adder/index.tsx | 9 +- .../mastodon/features/list_timeline/index.jsx | 24 + .../mastodon/features/lists/index.tsx | 32 +- .../mastodon/features/lists/members.tsx | 14 +- .../mastodon/features/lists/new.tsx | 144 +- .../features/mentioned_users/index.jsx | 90 + .../components/column_settings.jsx | 45 +- .../components/moderation_warning.tsx | 4 + .../notifications/components/notification.jsx | 140 +- .../components/notification_request.jsx | 7 +- .../notifications_permission_banner.jsx | 53 + .../containers/notification_container.js | 9 + .../features/notifications/requests.jsx | 6 +- .../components/avatar_group.tsx | 31 + .../components/embedded_status.tsx | 5 +- .../notification_emoji_reaction.tsx | 66 + .../components/notification_follow.tsx | 6 +- .../components/notification_group.tsx | 27 + .../notification_group_with_status.tsx | 66 +- .../components/notification_list_status.tsx | 61 + .../components/notification_mention.tsx | 7 +- .../notification_status_reference.tsx | 34 + .../components/notification_with_status.tsx | 9 +- .../features/notifications_v2/filter_bar.tsx | 26 + .../mastodon/features/onboarding/follows.tsx | 2 +- .../picture_in_picture/components/footer.jsx | 195 + .../features/picture_in_picture/index.tsx | 14 +- .../components/reaction_emoji.jsx | 84 + .../mastodon/features/reaction_deck/index.jsx | 174 + .../report/components/status_check_box.jsx | 2 +- .../mastodon/features/report/rules.jsx | 6 +- .../mastodon/features/search/index.tsx | 8 +- .../features/status/components/action_bar.jsx | 135 +- .../status/components/detailed_status.tsx | 129 +- .../mastodon/features/status/index.jsx | 199 +- .../features/status_references/index.jsx | 97 + .../ui/components/__tests__/column-test.jsx | 4 +- .../features/ui/components/actions_modal.jsx | 48 + .../features/ui/components/audio_modal.jsx | 74 + .../features/ui/components/boost_modal.tsx | 4 +- .../features/ui/components/column_link.jsx | 52 + .../features/ui/components/columns_area.jsx | 21 +- .../features/ui/components/compose_panel.jsx | 64 + .../confirmation_modal.tsx | 12 +- .../confirmation_modals/delete_antenna.tsx | 58 + .../delete_bookmark_category.tsx | 59 + .../confirmation_modals/delete_circle.tsx | 58 + .../confirmation_modals/edit_status.tsx | 45 + .../components/confirmation_modals/index.ts | 9 +- .../components/confirmation_modals/reply.tsx | 46 + .../ui/components/disabled_account_banner.jsx | 85 + .../features/ui/components/header.jsx | 129 + .../features/ui/components/list_panel.jsx | 57 + .../features/ui/components/media_modal.jsx | 6 +- .../features/ui/components/modal_root.jsx | 29 +- .../ui/components/navigation_panel.jsx | 244 + .../features/ui/components/sign_in_banner.jsx | 56 + .../features/ui/components/upload_area.tsx | 4 + .../features/ui/components/video_modal.jsx | 2 +- .../features/ui/components/zoomable_image.tsx | 11 +- .../ui/containers/status_list_container.js | 15 +- app/javascript/mastodon/features/ui/index.jsx | 71 +- .../features/ui/util/async-components.js | 200 +- .../mastodon/features/video/index.tsx | 20 +- app/javascript/mastodon/hooks/useLinks.ts | 21 +- app/javascript/mastodon/initial_state.js | 51 + .../mastodon/load_keyboard_extensions.js | 2 +- app/javascript/mastodon/locales/af.json | 10 + app/javascript/mastodon/locales/an.json | 16 + app/javascript/mastodon/locales/ar.json | 21 + app/javascript/mastodon/locales/ast.json | 17 +- app/javascript/mastodon/locales/az.json | 16 +- app/javascript/mastodon/locales/be.json | 24 + app/javascript/mastodon/locales/bg.json | 47 +- app/javascript/mastodon/locales/bn.json | 18 + app/javascript/mastodon/locales/br.json | 37 +- app/javascript/mastodon/locales/bs.json | 1 + app/javascript/mastodon/locales/ca.json | 81 +- app/javascript/mastodon/locales/ckb.json | 19 + app/javascript/mastodon/locales/co.json | 13 + app/javascript/mastodon/locales/cs.json | 106 +- app/javascript/mastodon/locales/cy.json | 256 +- app/javascript/mastodon/locales/da.json | 226 +- app/javascript/mastodon/locales/de.json | 110 +- app/javascript/mastodon/locales/el.json | 90 +- app/javascript/mastodon/locales/en-GB.json | 76 +- app/javascript/mastodon/locales/en.json | 305 +- app/javascript/mastodon/locales/eo.json | 113 +- app/javascript/mastodon/locales/es-AR.json | 90 +- app/javascript/mastodon/locales/es-MX.json | 98 +- app/javascript/mastodon/locales/es.json | 92 +- app/javascript/mastodon/locales/et.json | 108 +- app/javascript/mastodon/locales/eu.json | 21 + app/javascript/mastodon/locales/fa.json | 80 +- app/javascript/mastodon/locales/fi.json | 94 +- app/javascript/mastodon/locales/fil.json | 11 +- app/javascript/mastodon/locales/fo.json | 90 +- app/javascript/mastodon/locales/fr-CA.json | 35 +- app/javascript/mastodon/locales/fr.json | 35 +- app/javascript/mastodon/locales/fy.json | 109 +- app/javascript/mastodon/locales/ga.json | 98 +- app/javascript/mastodon/locales/gd.json | 150 +- app/javascript/mastodon/locales/gl.json | 94 +- app/javascript/mastodon/locales/he.json | 104 +- app/javascript/mastodon/locales/hi.json | 18 + app/javascript/mastodon/locales/hr.json | 18 + app/javascript/mastodon/locales/hu.json | 90 +- app/javascript/mastodon/locales/hy.json | 16 + app/javascript/mastodon/locales/ia.json | 54 +- app/javascript/mastodon/locales/id.json | 24 +- app/javascript/mastodon/locales/ie.json | 19 + app/javascript/mastodon/locales/ig.json | 5 + app/javascript/mastodon/locales/io.json | 25 + app/javascript/mastodon/locales/is.json | 94 +- app/javascript/mastodon/locales/it.json | 77 +- app/javascript/mastodon/locales/ja.json | 268 +- app/javascript/mastodon/locales/ka.json | 11 + app/javascript/mastodon/locales/kab.json | 81 +- app/javascript/mastodon/locales/kk.json | 49 +- app/javascript/mastodon/locales/kn.json | 5 + app/javascript/mastodon/locales/ko.json | 118 +- app/javascript/mastodon/locales/ku.json | 61 +- app/javascript/mastodon/locales/kw.json | 13 + app/javascript/mastodon/locales/la.json | 22 +- app/javascript/mastodon/locales/lad.json | 56 +- .../mastodon/locales/load_locale.ts | 19 +- app/javascript/mastodon/locales/lt.json | 42 +- app/javascript/mastodon/locales/lv.json | 230 +- app/javascript/mastodon/locales/mk.json | 10 + app/javascript/mastodon/locales/ml.json | 15 + app/javascript/mastodon/locales/mr.json | 7 + app/javascript/mastodon/locales/ms.json | 124 +- app/javascript/mastodon/locales/my.json | 18 + app/javascript/mastodon/locales/nan.json | 519 +- app/javascript/mastodon/locales/ne.json | 11 + app/javascript/mastodon/locales/nl.json | 102 +- app/javascript/mastodon/locales/nn.json | 114 +- app/javascript/mastodon/locales/no.json | 151 +- app/javascript/mastodon/locales/oc.json | 18 + app/javascript/mastodon/locales/pa.json | 16 + app/javascript/mastodon/locales/pl.json | 25 + app/javascript/mastodon/locales/pt-BR.json | 56 +- app/javascript/mastodon/locales/pt-PT.json | 114 +- app/javascript/mastodon/locales/ro.json | 19 + app/javascript/mastodon/locales/ru.json | 453 +- app/javascript/mastodon/locales/ry.json | 6 + app/javascript/mastodon/locales/sa.json | 18 + app/javascript/mastodon/locales/sc.json | 21 + app/javascript/mastodon/locales/sco.json | 17 + app/javascript/mastodon/locales/si.json | 492 +- app/javascript/mastodon/locales/sk.json | 37 +- app/javascript/mastodon/locales/sl.json | 25 + app/javascript/mastodon/locales/sq.json | 88 +- app/javascript/mastodon/locales/sr-Latn.json | 19 + app/javascript/mastodon/locales/sr.json | 19 + app/javascript/mastodon/locales/sv.json | 85 +- app/javascript/mastodon/locales/szl.json | 5 + app/javascript/mastodon/locales/ta.json | 14 + app/javascript/mastodon/locales/tai.json | 4 + app/javascript/mastodon/locales/te.json | 16 +- app/javascript/mastodon/locales/th.json | 29 +- app/javascript/mastodon/locales/tok.json | 275 +- app/javascript/mastodon/locales/tr.json | 90 +- app/javascript/mastodon/locales/tt.json | 15 + app/javascript/mastodon/locales/ug.json | 39 +- app/javascript/mastodon/locales/uk.json | 55 +- app/javascript/mastodon/locales/ur.json | 15 + app/javascript/mastodon/locales/uz.json | 15 + app/javascript/mastodon/locales/vi.json | 292 +- app/javascript/mastodon/locales/zgh.json | 7 + app/javascript/mastodon/locales/zh-CN.json | 73 +- app/javascript/mastodon/locales/zh-HK.json | 67 +- app/javascript/mastodon/locales/zh-TW.json | 92 +- app/javascript/mastodon/main.jsx | 49 + app/javascript/mastodon/models/account.ts | 34 + app/javascript/mastodon/models/antenna.ts | 24 + .../mastodon/models/bookmark_category.ts | 18 + app/javascript/mastodon/models/circle.ts | 16 + .../mastodon/models/custom_emoji.ts | 7 +- .../mastodon/models/dropdown_menu.ts | 34 +- app/javascript/mastodon/models/list.ts | 3 + .../mastodon/models/notification_group.ts | 87 + app/javascript/mastodon/performance.js | 13 + app/javascript/mastodon/polyfills/index.ts | 5 +- app/javascript/mastodon/polyfills/intl.ts | 8 +- app/javascript/mastodon/reducers/accounts.ts | 2 +- .../mastodon/reducers/accounts_map.js | 23 + app/javascript/mastodon/reducers/antennas.ts | 52 + .../mastodon/reducers/bookmark_categories.ts | 70 + app/javascript/mastodon/reducers/circles.ts | 52 + app/javascript/mastodon/reducers/compose.js | 187 +- app/javascript/mastodon/reducers/contexts.js | 114 + .../mastodon/reducers/dropdown_menu.ts | 8 +- app/javascript/mastodon/reducers/filters.js | 1 + .../mastodon/reducers/followed_tags.js | 43 + app/javascript/mastodon/reducers/index.ts | 39 +- app/javascript/mastodon/reducers/lists.ts | 11 +- .../mastodon/reducers/notification_groups.ts | 35 + .../mastodon/reducers/notifications.js | 2 + .../mastodon/reducers/reaction_deck.js | 13 + .../mastodon/reducers/relationships.ts | 4 +- app/javascript/mastodon/reducers/settings.js | 32 +- .../mastodon/reducers/status_lists.js | 131 +- app/javascript/mastodon/reducers/statuses.js | 40 +- .../mastodon/reducers/user_lists.js | 90 +- app/javascript/mastodon/selectors/accounts.ts | 13 - app/javascript/mastodon/selectors/antennas.ts | 15 + .../mastodon/selectors/bookmark_categories.ts | 17 + app/javascript/mastodon/selectors/circles.ts | 15 + app/javascript/mastodon/selectors/index.js | 33 +- app/javascript/mastodon/selectors/lists.ts | 23 +- .../mastodon/service_worker/entry.js | 90 + .../service_worker/web_push_locales.js | 41 + .../service_worker/web_push_notifications.js | 5 +- app/javascript/mastodon/store/index.ts | 1 - .../mastodon/store/middlewares/loading_bar.ts | 4 +- app/javascript/mastodon/store/store.ts | 32 +- .../mastodon/store/typed_functions.ts | 4 +- app/javascript/mastodon/stream.js | 2 + app/javascript/mastodon/test_helpers.tsx | 53 + app/javascript/mastodon/test_setup.js | 1 + app/javascript/mastodon/utils/environment.ts | 8 +- app/javascript/mastodon/utils/filters.ts | 3 +- app/javascript/mastodon/utils/mentions.ts | 29 + app/javascript/mastodon/utils/scrollbar.ts | 31 + .../material-icons/400-24px/cloud.svg | 2 +- .../400-24px/compare_arrows-fill.svg | 1 + .../400-24px/compare_arrows.svg | 1 + .../400-24px/dangerous-fill.svg | 1 + .../material-icons/400-24px/dangerous.svg | 1 + .../material-icons/400-24px/dns-fill.svg | 1 + .../material-icons/400-24px/dns.svg | 1 + .../400-24px/format_quote-fill.svg | 1 + .../material-icons/400-24px/format_quote.svg | 1 + .../material-icons/400-24px/key-fill.svg | 1 + .../material-icons/400-24px/key.svg | 2 +- .../material-icons/400-24px/markdown-fill.svg | 1 + .../material-icons/400-24px/markdown.svg | 1 + .../material-icons/400-24px/mood.svg | 2 +- .../400-24px/no_encryption-fill.svg | 1 + .../material-icons/400-24px/no_encryption.svg | 1 + .../material-icons/400-24px/shield-fill.svg | 1 + .../material-icons/400-24px/shield.svg | 1 + .../400-24px/sticky_note-fill.svg | 1 + .../material-icons/400-24px/sticky_note.svg | 1 + .../material-icons/400-24px/timer-fill.svg | 1 + .../material-icons/400-24px/timer.svg | 1 + .../material-icons/400-24px/title-fill.svg | 1 + .../material-icons/400-24px/title.svg | 1 + .../material-icons/400-24px/wifi-fill.svg | 1 + .../material-icons/400-24px/wifi.svg | 1 + app/javascript/material-icons/README.md | 13 +- app/javascript/styles/contrast/diff.scss | 4 + app/javascript/styles/contrast/variables.scss | 7 +- app/javascript/styles/fonts/inter.scss | 4 +- app/javascript/styles/fonts/roboto-mono.scss | 9 +- app/javascript/styles/fonts/roboto.scss | 32 +- app/javascript/styles/full-dark.scss | 4 + .../styles/full-dark/css_variables.scss | 4 + app/javascript/styles/full-dark/diff.scss | 42 + .../styles/full-dark/variables.scss | 14 + app/javascript/styles/inert.scss | 14 + app/javascript/styles/mailer.scss | 988 ++ .../styles/mastodon-light/diff.scss | 49 + .../styles/mastodon-light/variables.scss | 9 +- .../styles/mastodon/_functions.scss | 10 - .../styles/mastodon/_variables.scss | 15 +- app/javascript/styles/mastodon/about.scss | 54 +- app/javascript/styles/mastodon/accounts.scss | 22 +- app/javascript/styles/mastodon/admin.scss | 53 +- app/javascript/styles/mastodon/basics.scss | 68 +- .../styles/mastodon/components.scss | 1838 +-- .../styles/mastodon/css_variables.scss | 22 +- .../styles/mastodon/emoji_picker.scss | 5 + app/javascript/styles/mastodon/forms.scss | 60 +- app/javascript/styles/mastodon/rich_text.scss | 47 +- app/javascript/styles/mastodon/tables.scss | 3 +- app/javascript/types/image.d.ts | 19 +- app/lib/account_statuses_filter.rb | 36 +- app/lib/activitypub/activity.rb | 7 +- app/lib/activitypub/activity/accept.rb | 13 + app/lib/activitypub/activity/announce.rb | 3 + app/lib/activitypub/activity/create.rb | 266 +- app/lib/activitypub/activity/delete.rb | 47 +- app/lib/activitypub/activity/follow.rb | 56 +- app/lib/activitypub/activity/like.rb | 165 +- app/lib/activitypub/activity/reject.rb | 13 + app/lib/activitypub/activity/undo.rb | 73 +- app/lib/activitypub/activity/update.rb | 10 + app/lib/activitypub/case_transform.rb | 12 +- .../activitypub/parser/custom_emoji_parser.rb | 12 + .../parser/media_attachment_parser.rb | 4 +- app/lib/activitypub/parser/status_parser.rb | 149 +- app/lib/activitypub/tag_manager.rb | 119 +- app/lib/admin/metrics/dimension.rb | 2 + .../dimension/software_versions_dimension.rb | 6 +- .../dimension/space_usage_dimension.rb | 7 +- app/lib/admin/metrics/measure.rb | 2 + .../admin/system_check/elasticsearch_check.rb | 21 +- .../admin/system_check/media_privacy_check.rb | 2 +- app/lib/admin/system_check/message.rb | 2 +- .../system_check/sidekiq_process_check.rb | 2 +- .../system_check/software_version_check.rb | 2 +- app/lib/annual_report/archetype.rb | 2 +- .../annual_report/most_reblogged_accounts.rb | 2 +- app/lib/annual_report/time_series.rb | 26 +- app/lib/annual_report/type_distribution.rb | 2 +- app/lib/chewy_config.rb | 59 + app/lib/emoji_formatter.rb | 11 +- app/lib/entity_cache.rb | 6 +- app/lib/fasp/request.rb | 42 +- app/lib/feed_manager.rb | 86 +- app/lib/importer/base_importer.rb | 5 +- .../public_statuses_index_importer.rb | 5 +- app/lib/importer/statuses_index_importer.rb | 17 +- app/lib/inline_renderer.rb | 5 + app/lib/rate_limiter.rb | 5 + app/lib/redis_connection.rb | 9 +- app/lib/request.rb | 2 +- app/lib/search_query_transformer.rb | 245 +- app/lib/status_cache_hydrator.rb | 74 +- app/lib/status_filter.rb | 7 +- app/lib/status_reach_finder.rb | 144 +- app/lib/text_formatter.rb | 148 +- app/lib/vacuum/feeds_vacuum.rb | 11 + app/lib/vacuum/list_statuses_vacuum.rb | 17 + app/lib/vacuum/ng_histories_vacuum.rb | 18 + app/lib/vacuum/statuses_vacuum.rb | 21 +- app/lib/web_push_request.rb | 4 +- app/lib/webfinger.rb | 4 +- app/mailers/admin_mailer.rb | 8 + app/mailers/user_mailer.rb | 4 - app/models/account.rb | 52 +- app/models/account_filter.rb | 2 + app/models/account_statuses_cleanup_policy.rb | 17 +- app/models/account_suggestions.rb | 1 - app/models/account_warning.rb | 1 + app/models/admin/action_log_filter.rb | 4 + app/models/admin/import.rb | 4 +- app/models/admin/ng_rule.rb | 208 + app/models/admin/ng_word.rb | 114 + app/models/admin/sensitive_word.rb | 34 + app/models/admin/status_batch_action.rb | 40 +- app/models/announcement.rb | 2 +- app/models/antenna.rb | 133 + app/models/antenna_account.rb | 27 + app/models/antenna_domain.rb | 26 + app/models/antenna_feed.rb | 7 + app/models/antenna_tag.rb | 27 + app/models/bookmark_category.rb | 29 + app/models/bookmark_category_status.rb | 38 + app/models/circle.rb | 31 + app/models/circle_account.rb | 39 + app/models/circle_status.rb | 26 + app/models/concerns/account/associations.rb | 18 +- app/models/concerns/account/avatar.rb | 8 +- app/models/concerns/account/interactions.rb | 118 +- .../concerns/account/master_settings.rb | 30 + app/models/concerns/account/other_settings.rb | 102 + app/models/concerns/account/search.rb | 31 +- app/models/concerns/account/suspensions.rb | 18 + app/models/concerns/attachmentable.rb | 2 +- app/models/concerns/legacy_otp_secret.rb | 77 + app/models/concerns/notification/groups.rb | 4 +- .../concerns/status/domain_block_concern.rb | 18 + .../concerns/status/safe_reblog_insert.rb | 18 +- app/models/concerns/status/search_concern.rb | 50 +- .../concerns/status/snapshot_concern.rb | 2 +- .../concerns/status/threading_concern.rb | 9 + app/models/concerns/status/visibility.rb | 58 +- app/models/concerns/user/has_settings.rb | 196 + app/models/concerns/user/omniauthable.rb | 7 +- app/models/context.rb | 2 +- app/models/conversation.rb | 15 +- app/models/custom_css.rb | 16 + app/models/custom_emoji.rb | 54 +- app/models/custom_filter.rb | 63 +- app/models/custom_filter_keyword.rb | 2 +- app/models/domain_block.rb | 90 +- app/models/emoji_reaction.rb | 86 + app/models/fasp.rb | 2 - app/models/fasp/provider.rb | 6 - app/models/favourite.rb | 4 +- app/models/featured_tag.rb | 26 +- app/models/form/account_batch.rb | 48 + app/models/form/admin_settings.rb | 62 +- app/models/friend_domain.rb | 180 + app/models/home_feed.rb | 34 +- app/models/import.rb | 46 + app/models/instance.rb | 3 +- app/models/instance_info.rb | 137 + app/models/list.rb | 21 +- app/models/list_status.rb | 21 + app/models/media_attachment.rb | 18 +- app/models/mention.rb | 2 + app/models/ng_rule.rb | 114 + app/models/ng_rule_history.rb | 35 + app/models/ng_word.rb | 88 + app/models/ngword_history.rb | 22 + app/models/notification.rb | 41 +- app/models/notification_group.rb | 37 +- app/models/pending_follow_request.rb | 19 + app/models/pending_status.rb | 18 + app/models/preview_card.rb | 2 +- app/models/public_feed.rb | 20 +- app/models/rule.rb | 22 - app/models/scheduled_expiration_status.rb | 36 + app/models/sensitive_word.rb | 92 + app/models/site_upload.rb | 2 +- app/models/software_update.rb | 8 + app/models/specified_domain.rb | 78 + app/models/status.rb | 220 +- app/models/status_capability_token.rb | 25 + app/models/status_edit.rb | 4 +- app/models/status_reference.rb | 48 + app/models/status_stat.rb | 38 +- app/models/tag.rb | 1 + app/models/tag_feed.rb | 6 +- app/models/terms_of_service.rb | 26 +- app/models/trends/statuses.rb | 20 +- app/models/trends/tags.rb | 3 +- app/models/user.rb | 68 +- app/models/user_role.rb | 9 +- app/models/user_settings.rb | 62 +- app/models/user_settings/dsl.rb | 4 + app/models/user_settings/glue.rb | 2 +- app/models/user_settings/namespace.rb | 8 + app/models/user_settings/setting.rb | 22 + app/models/web/push_subscription.rb | 38 +- app/models/webauthn_credential.rb | 2 +- app/models/webhook.rb | 13 +- app/policies/account_policy.rb | 8 + app/policies/admin/status_policy.rb | 2 +- app/policies/backup_policy.rb | 2 +- app/policies/friend_server_policy.rb | 7 + app/policies/ng_words_policy.rb | 11 + app/policies/sensitive_words_policy.rb | 11 + app/policies/status_policy.rb | 65 +- .../activitypub/activity_presenter.rb | 8 +- .../misskey_emoji_license_presenter.rb | 5 + .../emoji_reaction_accounts_presenter.rb | 22 + app/presenters/initial_state_presenter.rb | 2 +- app/presenters/instance_presenter.rb | 2 +- app/presenters/oauth_metadata_presenter.rb | 2 +- .../status_relationships_presenter.rb | 78 +- app/presenters/tag_relationships_presenter.rb | 14 +- .../activity_for_friend_serializer.rb | 22 + .../activity_for_misskey_serializer.rb | 22 + .../activitypub/actor_serializer.rb | 29 +- .../activitypub/context_serializer.rb | 21 + .../activitypub/emoji_reaction_serializer.rb | 35 + .../activitypub/emoji_serializer.rb | 19 +- .../misskey_emoji_license_serializer.rb | 5 + .../activitypub/note_for_friend_serializer.rb | 11 + .../note_for_misskey_serializer.rb | 7 + .../activitypub/note_serializer.rb | 78 +- .../undo_emoji_reaction_serializer.rb | 23 + app/serializers/initial_state_serializer.rb | 57 +- app/serializers/node_info/serializer.rb | 11 +- app/serializers/oauth_metadata_serializer.rb | 2 +- app/serializers/oauth_userinfo_serializer.rb | 2 +- app/serializers/oembed_serializer.rb | 2 +- app/serializers/rest/account_serializer.rb | 34 +- .../rest/admin/domain_block_serializer.rb | 4 +- app/serializers/rest/antenna_serializer.rb | 35 + .../rest/application_serializer.rb | 2 +- .../rest/bookmark_category_serializer.rb | 9 + app/serializers/rest/circle_serializer.rb | 9 + app/serializers/rest/context_serializer.rb | 1 + .../rest/credential_account_serializer.rb | 1 + .../rest/custom_emoji_serializer.rb | 27 +- .../rest/custom_emoji_slim_serializer.rb | 59 + .../rest/domain_block_serializer.rb | 10 +- .../rest/emoji_reaction_account_serializer.rb | 34 + .../rest/emoji_reaction_serializer.rb | 23 + ...ji_reactions_grouped_by_name_serializer.rb | 47 + app/serializers/rest/filter_serializer.rb | 2 +- app/serializers/rest/instance_serializer.rb | 41 +- app/serializers/rest/list_serializer.rb | 16 +- .../rest/media_attachment_serializer.rb | 4 +- .../rest/notification_group_serializer.rb | 22 +- .../rest/notification_serializer.rb | 16 +- .../rest/notify_emoji_reaction_serializer.rb | 64 + app/serializers/rest/poll_serializer.rb | 2 +- .../rest/preferences_serializer.rb | 10 + .../rest/privacy_policy_serializer.rb | 2 +- app/serializers/rest/rule_serializer.rb | 8 +- .../rest/status_edit_serializer.rb | 9 +- .../rest/status_internal_serializer.rb | 9 + app/serializers/rest/status_serializer.rb | 116 +- app/serializers/rest/tag_serializer.rb | 9 - .../rest/terms_of_service_serializer.rb | 2 +- app/serializers/rest/v1/filter_serializer.rb | 6 +- .../rest/v1/instance_serializer.rb | 30 +- .../rest/web_push_subscription_serializer.rb | 4 +- .../web/notification_serializer.rb | 2 +- app/serializers/webfinger_serializer.rb | 2 +- app/services/account_search_service.rb | 65 +- .../activate_follow_requests_service.rb | 32 + .../activate_remote_statuses_service.rb | 31 + .../fetch_featured_collection_service.rb | 7 +- .../activitypub/fetch_references_service.rb | 49 + .../activitypub/process_account_service.rb | 156 +- .../activitypub/process_collection_service.rb | 10 +- .../process_status_update_service.rb | 160 +- app/services/after_block_service.rb | 5 + app/services/approve_appeal_service.rb | 11 +- app/services/backup_service.rb | 2 +- app/services/batched_remove_status_service.rb | 9 + app/services/block_domain_service.rb | 13 +- app/services/concerns/account_scope.rb | 41 + app/services/concerns/payloadable.rb | 4 +- app/services/create_featured_tag_service.rb | 24 +- app/services/delete_account_service.rb | 33 + app/services/delivery_antenna_service.rb | 186 + app/services/emoji_react_service.rb | 85 + app/services/fan_out_on_write_service.rb | 73 +- app/services/favourite_service.rb | 3 + app/services/fetch_link_card_service.rb | 26 +- app/services/fetch_resource_service.rb | 4 +- app/services/follow_service.rb | 7 +- app/services/import_service.rb | 144 + app/services/notify_service.rb | 3 + app/services/post_status_service.rb | 192 +- app/services/precompute_feed_service.rb | 2 +- app/services/process_conversation_service.rb | 33 + app/services/process_mentions_service.rb | 32 +- app/services/process_references_service.rb | 254 + app/services/reblog_service.rb | 11 +- app/services/remove_featured_tag_service.rb | 19 +- app/services/remove_status_service.rb | 49 +- app/services/report_service.rb | 2 +- app/services/search_service.rb | 8 +- app/services/searchability_update_service.rb | 27 + app/services/software_update_check_service.rb | 10 +- app/services/statuses_search_service.rb | 10 +- app/services/suspend_account_service.rb | 42 +- app/services/translate_status_service.rb | 2 +- app/services/un_emoji_react_service.rb | 82 + app/services/unsuspend_account_service.rb | 37 +- app/services/update_account_service.rb | 12 +- .../update_status_expiration_service.rb | 32 + app/services/update_status_service.rb | 136 +- app/services/vote_service.rb | 3 + app/validators/emoji_reaction_validator.rb | 40 + app/validators/poll_options_validator.rb | 2 +- app/views/accounts/show.html.haml | 8 +- app/views/admin/accounts/_account.html.haml | 2 +- app/views/admin/accounts/_buttons.html.haml | 7 +- .../admin/accounts/_local_account.html.haml | 4 +- app/views/admin/accounts/index.html.haml | 29 +- .../announcements/previews/show.html.haml | 2 - .../custom_emojis/_custom_emoji.html.haml | 7 + app/views/admin/custom_emojis/edit.html.haml | 45 + app/views/admin/custom_emojis/index.html.haml | 4 +- app/views/admin/custom_emojis/new.html.haml | 13 + .../_domain_block_list.html.haml | 32 + app/views/admin/domain_blocks/_form.html.haml | 8 + .../_domain_block.html.haml | 7 + .../friend_servers/_friend_domain.html.haml | 31 + .../friend_servers/_friend_fields.html.haml | 20 + app/views/admin/friend_servers/edit.html.haml | 32 + .../admin/friend_servers/index.html.haml | 20 + app/views/admin/friend_servers/new.html.haml | 9 + app/views/admin/instances/show.html.haml | 24 +- .../ng_rule_histories/_history.html.haml | 42 + .../admin/ng_rule_histories/show.html.haml | 25 + app/views/admin/ng_rules/_ng_rule.html.haml | 25 + .../admin/ng_rules/_ng_rule_fields.html.haml | 123 + app/views/admin/ng_rules/edit.html.haml | 9 + app/views/admin/ng_rules/index.html.haml | 14 + app/views/admin/ng_rules/new.html.haml | 8 + .../ng_words/keywords/_ng_word.html.haml | 10 + .../admin/ng_words/keywords/show.html.haml | 43 + .../admin/ng_words/settings/show.html.haml | 27 + .../admin/ng_words/shared/_links.html.haml | 6 + .../white_list/_specified_domain.html.haml | 6 + .../admin/ng_words/white_list/show.html.haml | 41 + .../admin/ngword_histories/_history.html.haml | 36 + .../admin/ngword_histories/index.html.haml | 22 + .../admin/report_notes/_report_note.html.haml | 9 +- app/views/admin/reports/_actions.html.haml | 15 +- .../reports/_media_attachments.html.haml | 15 +- app/views/admin/rules/_rule.html.haml | 4 +- app/views/admin/rules/edit.html.haml | 21 - app/views/admin/rules/index.html.haml | 15 +- .../sensitive_words/_sensitive_word.html.haml | 12 + .../admin/sensitive_words/show.html.haml | 42 + .../settings/content_retention/show.html.haml | 1 + .../admin/settings/discovery/show.html.haml | 40 +- .../settings/registrations/show.html.haml | 21 + .../admin/special_domains/show.html.haml | 17 + .../admin/special_instances/show.html.haml | 14 + app/views/admin/statuses/show.html.haml | 13 + .../admin/terms_of_service/index.html.haml | 1 + .../new_pending_friend_server.text.erb | 5 + app/views/application/_sidebar.html.haml | 16 + .../application/mailer/_account.html.haml | 10 +- .../confirmations/limitation_error.html.haml | 11 + app/views/auth/registrations/new.html.haml | 6 +- app/views/auth/registrations/rules.html.haml | 5 +- app/views/auth/sessions/new.html.haml | 11 + app/views/auth/sessions/two_factor.html.haml | 2 +- app/views/auth/setup/show.html.haml | 2 +- app/views/auth/shared/_links.html.haml | 3 + app/views/custom_css/show_system.css.erb | 0 app/views/filters/_filter_fields.html.haml | 8 + app/views/follower_accounts/index.html.haml | 3 +- app/views/following_accounts/index.html.haml | 3 +- app/views/home/index.html.haml | 5 +- app/views/layouts/admin.html.haml | 4 +- app/views/layouts/application.html.haml | 19 +- app/views/layouts/auth.html.haml | 2 +- app/views/layouts/embedded.html.haml | 11 +- app/views/layouts/error.html.haml | 6 +- app/views/layouts/helper_frame.html.haml | 2 + app/views/layouts/mailer.html.haml | 2 +- app/views/layouts/modal.html.haml | 2 +- app/views/media/player.html.haml | 2 +- app/views/relationships/show.html.haml | 2 +- .../remote_interaction_helper/index.html.haml | 2 +- .../preferences/appearance/show.html.haml | 49 + .../preferences/custom_css/show.html.haml | 37 + .../preferences/notifications/show.html.haml | 3 +- .../settings/preferences/other/show.html.haml | 63 +- .../preferences/reaching/show.html.haml | 98 + app/views/settings/privacy/show.html.haml | 29 +- .../settings/privacy_extra/show.html.haml | 59 + app/views/settings/profiles/show.html.haml | 23 +- .../shared/_profile_navigation.html.haml | 1 + .../webauthn_credentials/new.html.haml | 2 +- app/views/shared/_web_app.html.haml | 7 +- app/views/shares/show.html.haml | 2 +- app/views/statuses/show.html.haml | 8 +- app/views/statuses_cleanup/show.html.haml | 8 + app/views/system_css/show.css.erb | 0 app/views/tags/show.html.haml | 3 +- app/views/user_custom_css/show.css.erb | 1 + app/views/user_mailer/welcome.html.haml | 1 + app/views/user_mailer/welcome.text.erb | 6 +- app/workers/activate_remote_account_worker.rb | 14 + .../activitypub/distribution_worker.rb | 67 +- .../emoji_reaction_distribution_worker.rb | 41 + .../activitypub/fetch_all_replies_worker.rb | 35 +- .../activitypub/fetch_instance_info_worker.rb | 79 + .../activitypub/fetch_remote_status_worker.rb | 17 + .../forward_conversation_worker.rb | 46 + .../activitypub/raw_distribution_worker.rb | 28 + .../status_update_distribution_worker.rb | 67 +- .../synchronize_featured_collection_worker.rb | 2 +- ...ribute_announcement_notification_worker.rb | 17 +- ...te_terms_of_service_notification_worker.rb | 21 +- app/workers/delivery_emoji_reaction_worker.rb | 53 + app/workers/domain_block_worker.rb | 4 +- app/workers/feed_insert_worker.rb | 46 +- app/workers/fetch_reply_worker.rb | 2 +- app/workers/import/relationship_worker.rb | 57 + app/workers/import_worker.rb | 17 + app/workers/merge_worker.rb | 2 +- app/workers/process_references_worker.rb | 13 + app/workers/redownload_avatar_worker.rb | 2 +- app/workers/redownload_header_worker.rb | 2 +- app/workers/remove_expired_status_worker.rb | 16 + .../scheduler/scheduled_statuses_scheduler.rb | 11 + .../scheduler/self_destruct_scheduler.rb | 2 +- app/workers/scheduler/vacuum_scheduler.rb | 10 + app/workers/searchability_update_worker.rb | 13 + app/workers/unfilter_notifications_worker.rb | 21 +- app/workers/unfollow_follow_worker.rb | 2 +- babel.config.js | 80 + bin/webpack | 19 + bin/webpack-dev-server | 19 + config/application.rb | 14 +- config/database.yml | 2 +- config/elasticsearch.default-ja-sudachi.yml | 234 + config/elasticsearch.default.yml | 177 + config/environments/development.rb | 15 +- config/environments/production.rb | 61 +- config/environments/test.rb | 12 +- config/i18n-tasks.yml | 3 +- config/initializers/1_hosts.rb | 2 +- .../initializers/2_limited_federation_mode.rb | 7 + config/initializers/3_omniauth.rb | 12 +- config/initializers/cache_buster.rb | 11 + config/initializers/chewy.rb | 8 +- .../initializers/content_security_policy.rb | 7 +- config/initializers/deprecations.rb | 21 +- config/initializers/devise.rb | 8 +- config/initializers/doorkeeper.rb | 2 + config/initializers/ffmpeg.rb | 4 +- config/initializers/inflections.rb | 1 - config/initializers/mime_types.rb | 3 + config/initializers/paperclip.rb | 12 +- config/initializers/prometheus_exporter.rb | 10 +- config/initializers/sidekiq.rb | 12 +- config/initializers/simple_form.rb | 31 +- config/initializers/strong_migrations.rb | 2 +- config/initializers/vapid.rb | 16 + config/locales/activerecord.da.yml | 2 +- config/locales/activerecord.el.yml | 2 - config/locales/activerecord.eo.yml | 2 - config/locales/activerecord.es-MX.yml | 2 +- config/locales/activerecord.et.yml | 8 - config/locales/activerecord.fa.yml | 2 - config/locales/activerecord.fy.yml | 6 - config/locales/activerecord.ga.yml | 2 - config/locales/activerecord.gd.yml | 13 - config/locales/activerecord.ia.yml | 2 - config/locales/activerecord.ja.yml | 6 - config/locales/activerecord.nn.yml | 6 - config/locales/activerecord.ru.yml | 38 +- config/locales/activerecord.si.yml | 50 - config/locales/an.yml | 1 + config/locales/ar.yml | 2 + config/locales/ast.yml | 1 + config/locales/be.yml | 2 + config/locales/bg.yml | 2 + config/locales/br.yml | 9 +- config/locales/ca.yml | 39 +- config/locales/cs.yml | 33 +- config/locales/cy.yml | 77 +- config/locales/da.yml | 151 +- config/locales/de.yml | 55 +- config/locales/devise.da.yml | 2 +- config/locales/devise.en.yml | 1 + config/locales/devise.et.yml | 6 +- config/locales/devise.ja.yml | 1 + config/locales/devise.lv.yml | 4 +- config/locales/devise.nan.yml | 6 +- config/locales/devise.ru.yml | 166 +- config/locales/devise.si.yml | 18 - config/locales/devise.vi.yml | 4 +- config/locales/doorkeeper.da.yml | 8 +- config/locales/doorkeeper.kab.yml | 10 +- config/locales/doorkeeper.lv.yml | 34 +- config/locales/doorkeeper.ms.yml | 10 +- config/locales/doorkeeper.ru.yml | 176 +- config/locales/doorkeeper.si.yml | 17 - config/locales/doorkeeper.vi.yml | 4 +- config/locales/doorkeeper.zh-HK.yml | 1 - config/locales/el.yml | 64 +- config/locales/en-GB.yml | 2 + config/locales/en.yml | 405 +- config/locales/eo.yml | 67 +- config/locales/es-AR.yml | 31 +- config/locales/es-MX.yml | 33 +- config/locales/es.yml | 33 +- config/locales/et.yml | 42 +- config/locales/eu.yml | 2 + config/locales/fa.yml | 52 +- config/locales/fi.yml | 45 +- config/locales/fil.yml | 7 - config/locales/fo.yml | 26 +- config/locales/fr-CA.yml | 15 +- config/locales/fr.yml | 15 +- config/locales/fy.yml | 65 +- config/locales/ga.yml | 61 +- config/locales/gd.yml | 112 +- config/locales/gl.yml | 67 +- config/locales/he.yml | 69 +- config/locales/hu.yml | 31 +- config/locales/ia.yml | 29 +- config/locales/ie.yml | 2 + config/locales/io.yml | 1 + config/locales/is.yml | 31 +- config/locales/it.yml | 26 +- config/locales/ja.yml | 420 +- config/locales/kab.yml | 13 +- config/locales/ko.yml | 33 +- config/locales/lad.yml | 28 +- config/locales/lt.yml | 6 +- config/locales/lv.yml | 313 +- config/locales/ms.yml | 15 +- config/locales/my.yml | 1 + config/locales/nan.yml | 312 +- config/locales/nl.yml | 33 +- config/locales/nn.yml | 88 +- config/locales/no.yml | 5 +- config/locales/pl.yml | 4 +- config/locales/pt-BR.yml | 40 +- config/locales/pt-PT.yml | 105 +- config/locales/ru.yml | 142 +- config/locales/sc.yml | 1 + config/locales/simple_form.bg.yml | 6 +- config/locales/simple_form.ca.yml | 8 +- config/locales/simple_form.cs.yml | 8 +- config/locales/simple_form.cy.yml | 10 +- config/locales/simple_form.da.yml | 36 +- config/locales/simple_form.de.yml | 14 +- config/locales/simple_form.el.yml | 21 - config/locales/simple_form.en.yml | 148 +- config/locales/simple_form.eo.yml | 9 +- config/locales/simple_form.es-AR.yml | 6 +- config/locales/simple_form.es-MX.yml | 22 +- config/locales/simple_form.es.yml | 6 +- config/locales/simple_form.et.yml | 14 - config/locales/simple_form.fa.yml | 12 - config/locales/simple_form.fi.yml | 6 +- config/locales/simple_form.fil.yml | 5 - config/locales/simple_form.fo.yml | 6 +- config/locales/simple_form.fr-CA.yml | 1 + config/locales/simple_form.fr.yml | 1 + config/locales/simple_form.fy.yml | 22 - config/locales/simple_form.ga.yml | 15 - config/locales/simple_form.gd.yml | 47 - config/locales/simple_form.gl.yml | 10 +- config/locales/simple_form.he.yml | 8 +- config/locales/simple_form.hu.yml | 6 +- config/locales/simple_form.ia.yml | 14 - config/locales/simple_form.is.yml | 6 +- config/locales/simple_form.it.yml | 6 +- config/locales/simple_form.ja.yml | 162 +- config/locales/simple_form.kab.yml | 6 +- config/locales/simple_form.ko.yml | 4 - config/locales/simple_form.lad.yml | 6 - config/locales/simple_form.lt.yml | 1 + config/locales/simple_form.lv.yml | 27 +- config/locales/simple_form.nl.yml | 6 +- config/locales/simple_form.nn.yml | 16 +- config/locales/simple_form.no.yml | 2 +- config/locales/simple_form.pt-BR.yml | 6 +- config/locales/simple_form.pt-PT.yml | 18 +- config/locales/simple_form.ru.yml | 190 +- config/locales/simple_form.si.yml | 157 - config/locales/simple_form.sl.yml | 1 + config/locales/simple_form.sq.yml | 3 +- config/locales/simple_form.sv.yml | 25 - config/locales/simple_form.th.yml | 3 - config/locales/simple_form.tr.yml | 6 +- config/locales/simple_form.uk.yml | 1 + config/locales/simple_form.vi.yml | 65 +- config/locales/simple_form.zh-CN.yml | 6 +- config/locales/simple_form.zh-HK.yml | 9 - config/locales/simple_form.zh-TW.yml | 5 +- config/locales/sk.yml | 2 + config/locales/sl.yml | 2 + config/locales/sq.yml | 31 +- config/locales/sr-Latn.yml | 2 + config/locales/sr.yml | 2 + config/locales/sv.yml | 65 +- config/locales/th.yml | 10 +- config/locales/tok.yml | 45 +- config/locales/tr.yml | 33 +- config/locales/uk.yml | 9 +- config/locales/vi.yml | 115 +- config/locales/zh-CN.yml | 55 +- config/locales/zh-HK.yml | 48 +- config/locales/zh-TW.yml | 35 +- config/mastodon.yml | 5 +- config/navigation.rb | 22 +- config/puma.rb | 6 - config/roles.yml | 3 + config/routes.rb | 5 +- config/routes/admin.rb | 45 +- config/routes/api.rb | 54 +- config/routes/fasp.rb | 10 - config/routes/settings.rb | 3 + config/routes/web_app.rb | 6 + config/settings.yml | 18 + config/sidekiq.yml | 6 +- config/themes.yml | 1 + config/webpack/configuration.js | 28 + config/webpack/development.js | 62 + config/webpack/production.js | 74 + config/webpack/rules/babel.js | 28 + config/webpack/rules/css.js | 28 + config/webpack/rules/file.js | 22 + config/webpack/rules/index.js | 18 + config/webpack/rules/mark.js | 8 + config/webpack/rules/material_icons.js | 14 + config/webpack/rules/node_modules.js | 23 + config/webpack/rules/tesseract.js | 13 + config/webpack/shared.js | 113 + config/webpacker.yml | 94 + ...5805_add_superapp_to_oauth_applications.rb | 2 +- ..._add_in_reply_to_account_id_to_statuses.rb | 5 + ...203041_add_website_to_oauth_application.rb | 2 +- .../20180812173710_copy_status_stats.rb | 4 + ...207011115_downcase_custom_emoji_domains.rb | 4 + ...add_last_used_at_to_oauth_access_tokens.rb | 2 +- .../20230215074423_move_user_settings.rb | 6 + .../20230222232121_create_emoji_reactions.rb | 15 + ...416_add_emoji_reactions_to_status_stats.rb | 7 + ...8061833_add_image_size_to_custom_emojis.rb | 21 + ...roup_message_following_only_to_accounts.rb | 7 + ...group_allow_private_message_to_accounts.rb | 7 + ...roup_activitypub_count_to_account_stats.rb | 7 + ...18_create_scheduled_expiration_statuses.rb | 14 + ...405121613_add_searchability_to_statuses.rb | 7 + ...405121625_add_searchability_to_accounts.rb | 9 + ...1523_change_searchability_default_value.rb | 7 + ...d_emoji_reactions_count_to_status_stats.rb | 9 + ...20230412005311_add_markdown_to_statuses.rb | 9 + ...0412073021_add_markdown_to_status_edits.rb | 9 + ...tions_count_per_account_to_status_stats.rb | 9 + ...is_to_account_statuses_cleanup_policies.rb | 12 + db/migrate/20230423002728_create_antennas.rb | 42 + ...3233429_add_dissubscribable_to_accounts.rb | 11 + ...20230426013738_add_excludes_to_antennas.rb | 23 + ...6_add_reject_favourite_to_domain_blocks.rb | 12 + ...650_add_reject_sending_to_domain_blocks.rb | 29 + ...0230427122753_add_some_to_domain_blocks.rb | 23 + ...30427233749_add_hidden_to_domain_blocks.rb | 21 + ...dd_emoji_reaction_streaming_to_accounts.rb | 9 + ...t_invalid_subscription_to_domain_blocks.rb | 23 + ...045358_change_antennas_list_to_nullable.rb | 15 + .../20230510000439_add_stl_to_antennas.rb | 10 + ..._emoji_reaction_streaming_from_accounts.rb | 15 + ...510033040_add_ignore_reblog_to_antennas.rb | 10 + ...eply_exclude_followers_to_domain_blocks.rb | 11 + ...20230514030455_add_settings_to_accounts.rb | 7 + ...30521122642_add_aliases_to_custom_emoji.rb | 7 + ...82252_add_is_sensitive_to_custom_emojis.rb | 11 + ...0522093135_add_license_to_custom_emojis.rb | 7 + ...20230705232953_create_status_references.rb | 12 + ...tatus_referred_by_count_to_status_stats.rb | 13 + ...14004824_add_exclude_options_to_filters.rb | 11 + .../20230804222017_create_instance_infoes.rb | 14 + ...12083752_create_status_capability_token.rb | 12 + ...812130612_add_limited_scope_to_statuses.rb | 7 + ...9084858_add_no_insert_feeds_to_antennas.rb | 23 + db/migrate/20230821061713_create_circles.rb | 21 + ...2041804_add_antenna_elements_uniqueness.rb | 17 + ...230826023400_create_bookmark_categories.rb | 27 + .../20230911022527_add_ltl_to_antennas.rb | 15 + ...2836_add_attribute_to_status_references.rb | 15 + .../20230923103430_create_circle_statuses.rb | 22 + ...30233930_add_quote_to_status_references.rb | 24 + .../20231001031337_add_quote_to_statuses.rb | 32 + ...050733_add_with_quote_to_custom_filters.rb | 15 + .../20231005074832_create_friend_domains.rb | 26 + ...0102_add_reject_friend_to_domain_blocks.rb | 15 + ...808_improve_search_for_account_statuses.rb | 15 + ...15_add_delivery_local_to_friend_domains.rb | 23 + ...231023083359_convert_dtl_force_settings.rb | 52 + .../20231028004612_create_list_statuses.rb | 22 + .../20231028005948_add_notify_to_list.rb | 15 + ...5225839_add_master_settings_to_accounts.rb | 33 + ...15001356_add_inbox_url_to_conversations.rb | 16 + ...dex_on_conversations_ancestor_status_id.rb | 9 + ...4_add_index_on_statuses_conversation_id.rb | 9 + ...improve_index_for_public_timeline_speed.rb | 20 + .../20231214225249_index_to_statuses_url.rb | 13 + ...ove_hidden_anonymous_from_domain_blocks.rb | 18 + .../20240117021025_remove_unused_table.rb | 14 + ...31131_add_block_trends_to_domain_blocks.rb | 9 + .../20240212224800_add_uri_to_favourites.rb | 10 + .../20240216042730_create_ngword_histories.rb | 17 + ...217022038_add_count_to_ngword_histories.rb | 11 + ...17093511_add_remote_pending_to_accounts.rb | 11 + ...17230006_create_pending_follow_requests.rb | 17 + db/migrate/20240218233621_create_ng_rules.rb | 64 + ...033337_remove_group_attrs_from_accounts.rb | 10 + .../20240227225017_create_pending_statuses.rb | 13 + ..._remove_reject_reply_from_domain_blocks.rb | 9 + .../20240312230204_create_sensitive_words.rb | 71 + db/migrate/20240320231633_create_ng_words.rb | 63 + ...20240401222541_create_specified_domains.rb | 41 + ...0034_move_account_warning_notifications.rb | 15 + ...tion_warning_notifications_from_account.rb | 14 + ...evert_media_file_size_column_to_big_int.rb | 42 + ...3700_add_with_profile_to_custom_filters.rb | 7 + .../20240828123604_create_custom_csses.rb | 12 + ...829_add_favourite_to_lists_and_antennas.rb | 17 + ...23091137_remove_half_warn_filter_option.rb | 11 + ...32529_set_antenna_list_id_default_value.rb | 7 + ...9130537_remove_boosts_widening_audience.rb | 31 +- ...index_oauth_access_tokens_refresh_token.rb | 2 +- ...x_oauth_access_tokens_resource_owner_id.rb | 2 +- ...up_message_following_only_from_accounts.rb | 9 + ...ove_remote_uri_from_local_custom_emojis.rb | 21 + ...20231022074913_add_statuses_quote_index.rb | 13 + ...2353_remove_legacy_domain_block_columns.rb | 18 + ...230358_fix_uri_index_to_emoji_reactions.rb | 28 + ...4_improve_remote_pending_accounts_index.rb | 11 + ..._index_to_sort_for_ng_word_created_date.rb | 9 + ...80905_migrate_devise_two_factor_secrets.rb | 87 +- ...0326231854_improve_preview_cards_vacuum.rb | 10 + ..._indices_for_kmyblue_original_functions.rb | 13 + ...813_remove_old_public_index_to_statuses.rb | 6 +- ...ew_public_index_for_kmyblue_to_statuses.rb | 9 + ...d_public_index_for_original_to_statuses.rb | 15 + db/schema.rb | 544 +- dist/nginx-before-certbot.conf | 7 + docker-compose.yml | 19 +- docs/DEVELOPMENT.md | 34 +- eslint.config.mjs | 42 +- install/12.0/setup-imagemagick-7.sh | 82 + install/12.0/setup1.sh | 209 + install/12.0/setup2.sh | 68 + install/13.0/setup-imagemagick-7.sh | 82 + install/13.0/setup1.sh | 209 + install/13.0/setup2.sh | 68 + install/5.0/setup-imagemagick-7.sh | 82 + install/5.0/setup1.sh | 140 + install/5.0/setup2.sh | 44 + install/5.0/setup3.sh | 27 + install/5.0/setup4.sh | 72 + install/9.0/setup-imagemagick-7.sh | 82 + install/9.0/setup1.sh | 140 + install/9.0/setup2.sh | 44 + install/9.0/setup3.sh | 27 + install/9.0/setup4.sh | 72 + jest.config.js | 28 + lib/chewy/index_extensions.rb | 7 - lib/mastodon/cli/accounts.rb | 9 +- lib/mastodon/cli/cache.rb | 1 + lib/mastodon/cli/emoji.rb | 16 + lib/mastodon/cli/feeds.rb | 25 + lib/mastodon/cli/main.rb | 4 + lib/mastodon/cli/media.rb | 2 + lib/mastodon/cli/ohagi.rb | 33 + lib/mastodon/cli/search.rb | 20 +- lib/mastodon/middleware/public_file_server.rb | 13 +- lib/mastodon/migration_warning.rb | 2 +- lib/mastodon/redis_configuration.rb | 26 +- lib/mastodon/version.rb | 62 +- lib/paperclip/color_extractor.rb | 2 +- lib/premailer_bundled_asset_strategy.rb | 31 +- lib/redis/namespace_extensions.rb | 16 + lib/sanitize_ext/sanitize_config.rb | 26 +- lib/stoplight/redis_data_store_extensions.rb | 17 + lib/tasks/assets.rake | 4 +- lib/tasks/dangerous.rake | 239 + lib/tasks/db.rake | 2 +- lib/tasks/emojis.rake | 135 +- lib/tasks/mastodon.rake | 6 +- lib/tasks/repo.rake | 2 +- lib/tasks/tests.rake | 8 +- lib/tasks/webpacker.rake | 34 + lib/webpacker/helper_extensions.rb | 27 + lib/webpacker/manifest_extensions.rb | 17 + package.json | 115 +- postcss.config.js | 15 + public/emoji/1f468-200d-1f466-200d-1f466.svg | 2 +- public/emoji/1f468-200d-1f466.svg | 2 +- public/emoji/1f468-200d-1f467-200d-1f466.svg | 2 +- public/emoji/1f468-200d-1f467-200d-1f467.svg | 2 +- public/emoji/1f468-200d-1f467.svg | 2 +- ...1f468-200d-1f468-200d-1f466-200d-1f466.svg | 2 +- public/emoji/1f468-200d-1f468-200d-1f466.svg | 2 +- ...1f468-200d-1f468-200d-1f467-200d-1f466.svg | 2 +- ...1f468-200d-1f468-200d-1f467-200d-1f467.svg | 2 +- public/emoji/1f468-200d-1f468-200d-1f467.svg | 2 +- ...1f468-200d-1f469-200d-1f466-200d-1f466.svg | 2 +- public/emoji/1f468-200d-1f469-200d-1f466.svg | 2 +- ...1f468-200d-1f469-200d-1f467-200d-1f466.svg | 2 +- ...1f468-200d-1f469-200d-1f467-200d-1f467.svg | 2 +- public/emoji/1f468-200d-1f469-200d-1f467.svg | 2 +- public/emoji/1f469-200d-1f466-200d-1f466.svg | 2 +- public/emoji/1f469-200d-1f466.svg | 2 +- public/emoji/1f469-200d-1f467-200d-1f466.svg | 2 +- public/emoji/1f469-200d-1f467-200d-1f467.svg | 2 +- public/emoji/1f469-200d-1f467.svg | 2 +- ...1f469-200d-1f469-200d-1f466-200d-1f466.svg | 2 +- public/emoji/1f469-200d-1f469-200d-1f466.svg | 2 +- ...1f469-200d-1f469-200d-1f467-200d-1f466.svg | 2 +- ...1f469-200d-1f469-200d-1f467-200d-1f467.svg | 2 +- public/emoji/1f469-200d-1f469-200d-1f467.svg | 2 +- public/emoji/1f46a.svg | 2 +- public/emoji/sheet_15.png | Bin 0 -> 1327037 bytes public/favicon.ico | Bin 0 -> 9662 bytes public/robots.txt | 3 + scalingo.json | 4 + spec/config/initializers/rack/attack_spec.rb | 79 +- .../export_domain_blocks_controller_spec.rb | 16 +- .../v1/circles/statuses_controller_spec.rb | 43 + .../api/v1/lists_controller_spec.rb | 63 + .../application_controller_spec.rb | 18 +- .../auth/registrations_controller_spec.rb | 170 +- .../auth/sessions_controller_spec.rb | 39 + .../account_controller_concern_spec.rb | 5 +- spec/controllers/concerns/localized_spec.rb | 4 +- .../concerns/user_tracking_concern_spec.rb | 4 +- .../oauth/authorizations_controller_spec.rb | 6 +- ...authorized_applications_controller_spec.rb | 6 +- .../relationships_controller_spec.rb | 6 +- .../settings/imports_controller_spec.rb | 12 +- .../confirmations_controller_spec.rb | 28 +- spec/fabricators/account_fabricator.rb | 1 - .../fabricators/antenna_account_fabricator.rb | 7 + spec/fabricators/antenna_domain_fabricator.rb | 7 + spec/fabricators/antenna_fabricator.rb | 7 + spec/fabricators/antenna_tag_fabricator.rb | 7 + .../bookmark_category_fabricator.rb | 6 + .../bookmark_category_status_fabricator.rb | 9 + spec/fabricators/circle_account_fabricator.rb | 7 + spec/fabricators/circle_fabricator.rb | 6 + spec/fabricators/circle_status_fabricator.rb | 7 + spec/fabricators/custom_filter_fabricator.rb | 2 + spec/fabricators/emoji_reaction_fabricator.rb | 7 + spec/fabricators/fasp/provider_fabricator.rb | 24 - spec/fabricators/friend_domain_fabricator.rb | 10 + spec/fabricators/import_fabricator.rb | 7 + spec/fabricators/instance_info_fabricator.rb | 7 + spec/fabricators/list_status_fabricator.rb | 6 + spec/fabricators/ng_rule_fabricator.rb | 7 + .../fabricators/ng_rule_history_fabricator.rb | 8 + spec/fabricators/ng_word_fabricator.rb | 5 + spec/fabricators/ngword_history_fabricator.rb | 9 + .../pending_follow_request_fabricator.rb | 7 + spec/fabricators/pending_status_fabricator.rb | 7 + .../scheduled_expiration_status_fabricator.rb | 7 + spec/fabricators/sensitive_word_fabricator.rb | 5 + .../specified_domain_fabricator.rb | 5 + .../status_reference_fabricator.rb | 8 + .../web_push_subscription_fabricator.rb | 2 - spec/fixtures/files/avatar.avif | Bin 0 -> 7742 bytes spec/fixtures/files/domain_blocks.csv | 9 +- spec/helpers/application_helper_spec.rb | 4 + spec/helpers/home_helper_spec.rb | 2 +- spec/helpers/json_ld_helper_spec.rb | 21 + spec/helpers/languages_helper_spec.rb | 36 - spec/helpers/statuses_helper_spec.rb | 30 + spec/helpers/theme_helper_spec.rb | 15 +- spec/lib/account_statuses_filter_spec.rb | 111 +- spec/lib/activitypub/activity/accept_spec.rb | 72 + .../lib/activitypub/activity/announce_spec.rb | 32 + spec/lib/activitypub/activity/create_spec.rb | 1826 ++- spec/lib/activitypub/activity/delete_spec.rb | 64 +- spec/lib/activitypub/activity/follow_spec.rb | 373 +- spec/lib/activitypub/activity/like_spec.rb | 643 +- spec/lib/activitypub/activity/reject_spec.rb | 60 + spec/lib/activitypub/activity/remove_spec.rb | 65 +- spec/lib/activitypub/activity/undo_spec.rb | 118 +- spec/lib/activitypub/activity/update_spec.rb | 62 + .../parser/custom_emoji_parser_spec.rb | 53 + .../activitypub/parser/status_parser_spec.rb | 135 +- spec/lib/activitypub/tag_manager_spec.rb | 100 + .../system_check/elasticsearch_check_spec.rb | 3 - .../system_check/media_privacy_check_spec.rb | 6 +- spec/lib/admin/system_check/message_spec.rb | 2 +- .../sidekiq_process_check_spec.rb | 4 +- .../software_version_check_spec.rb | 2 +- spec/lib/cache_buster_spec.rb | 10 +- spec/lib/emoji_formatter_spec.rb | 6 +- spec/lib/fasp/request_spec.rb | 6 +- spec/lib/feed_manager_spec.rb | 48 + spec/lib/link_details_extractor_spec.rb | 10 +- spec/lib/mastodon/cli/accounts_spec.rb | 19 - spec/lib/mastodon/cli/ip_blocks_spec.rb | 12 +- spec/lib/mastodon/redis_configuration_spec.rb | 53 +- spec/lib/ostatus/tag_manager_spec.rb | 24 +- spec/lib/search_query_transformer_spec.rb | 22 +- spec/lib/status_cache_hydrator_spec.rb | 186 - spec/lib/status_reach_finder_spec.rb | 398 +- spec/lib/text_formatter_spec.rb | 5 +- spec/lib/vacuum/feeds_vacuum_spec.rb | 14 + spec/lib/vacuum/list_statuses_vacuum_spec.rb | 30 + spec/lib/vacuum/ng_histories_vacuum_spec.rb | 28 + spec/lib/vacuum/statuses_vacuum_spec.rb | 54 + spec/mailers/admin_mailer_spec.rb | 36 +- spec/mailers/notification_mailer_spec.rb | 26 +- spec/mailers/previews/admin_mailer_preview.rb | 5 + spec/mailers/previews/user_mailer_preview.rb | 5 - spec/mailers/user_mailer_spec.rb | 107 +- spec/models/account_spec.rb | 234 +- spec/models/admin/ng_rule_spec.rb | 312 + spec/models/admin/ng_word_spec.rb | 77 + spec/models/admin/sensitive_word_spec.rb | 75 + .../concerns/account/interactions_spec.rb | 142 + .../concerns/status/threading_concern_spec.rb | 52 + spec/models/custom_emoji_spec.rb | 46 + spec/models/custom_filter_spec.rb | 2 +- spec/models/doorkeeper/application_spec.rb | 37 - spec/models/featured_tag_spec.rb | 2 +- spec/models/form/custom_emoji_batch_spec.rb | 4 +- spec/models/form/import_spec.rb | 2 +- spec/models/friend_domain_spec.rb | 88 + spec/models/home_feed_spec.rb | 82 +- spec/models/import_spec.rb | 10 + spec/models/instance_info_spec.rb | 95 + spec/models/instance_spec.rb | 4 +- spec/models/invite_spec.rb | 2 +- spec/models/ip_block_spec.rb | 2 +- spec/models/login_activity_spec.rb | 2 +- spec/models/media_attachment_spec.rb | 2 +- spec/models/mute_spec.rb | 2 +- spec/models/ng_rule_spec.rb | 29 + spec/models/notification_group_spec.rb | 94 + spec/models/poll_spec.rb | 2 +- spec/models/preview_card_provider_spec.rb | 2 +- spec/models/preview_card_trend_spec.rb | 2 +- spec/models/public_feed_spec.rb | 132 +- spec/models/remote_follow_spec.rb | 11 +- spec/models/rule_spec.rb | 31 - spec/models/session_activation_spec.rb | 2 +- spec/models/status_spec.rb | 104 +- spec/models/status_trend_spec.rb | 2 +- spec/models/tag_feed_spec.rb | 66 + spec/models/tag_spec.rb | 2 +- spec/models/tag_trend_spec.rb | 2 +- spec/models/terms_of_service_spec.rb | 132 - spec/models/trends/statuses_spec.rb | 23 + spec/models/trends/tags_spec.rb | 27 + spec/models/user_role_spec.rb | 6 +- spec/models/user_spec.rb | 73 +- spec/models/webauthn_credential_spec.rb | 2 +- spec/models/webhook_spec.rb | 2 - .../admin/fasp/provider_policy_spec.rb | 4 +- spec/policies/status_policy_spec.rb | 137 + spec/presenters/instance_presenter_spec.rb | 2 +- spec/rails_helper.rb | 2 +- spec/requests/admin/instances_spec.rb | 13 +- .../api/v1/accounts/featured_tags_spec.rb | 8 +- spec/requests/api/v1/accounts/pins_spec.rb | 45 + spec/requests/api/v1/accounts_spec.rb | 56 +- .../api/v1/admin/domain_blocks_spec.rb | 43 +- spec/requests/api/v1/antennas_spec.rb | 257 + spec/requests/api/v1/apps/credentials_spec.rb | 4 +- spec/requests/api/v1/apps_spec.rb | 2 +- spec/requests/api/v1/circles/accounts_spec.rb | 165 + spec/requests/api/v1/circles_spec.rb | 193 + spec/requests/api/v1/featured_tags_spec.rb | 4 +- .../api/v1/instances/domain_blocks_spec.rb | 55 +- spec/requests/api/v1/lists_spec.rb | 119 +- .../api/v1/push/subscriptions_spec.rb | 3 +- .../api/v1/statuses/emoji_reactions_spec.rb | 271 + spec/requests/api/v1/statuses_spec.rb | 91 +- spec/requests/api/v1/tags_spec.rb | 112 - .../requests/api/v1/timelines/antenna_spec.rb | 55 + spec/requests/api/v1/timelines/home_spec.rb | 11 +- spec/requests/api/v1/timelines/public_spec.rb | 85 +- spec/requests/api/v2/filters_spec.rb | 48 +- spec/requests/api/v2/search_spec.rb | 9 - spec/requests/api/v2/suggestions_spec.rb | 9 - spec/requests/cache_spec.rb | 6 +- spec/requests/content_security_policy_spec.rb | 22 +- spec/requests/instance_actor_spec.rb | 10 +- spec/requests/invites_spec.rb | 2 + spec/requests/link_headers_spec.rb | 4 +- spec/requests/oauth/token_spec.rb | 10 +- spec/requests/oauth/userinfo_spec.rb | 2 +- spec/requests/omniauth_callbacks_spec.rb | 12 +- .../remote_interaction_helper_spec.rb | 10 +- spec/requests/signature_verification_spec.rb | 701 +- spec/requests/status_show_page_spec.rb | 2 +- spec/requests/user_custom_css_spec.rb | 48 + spec/requests/well_known/host_meta_spec.rb | 4 +- spec/requests/well_known/webfinger_spec.rb | 14 +- .../services/statuses_search_service_spec.rb | 321 + .../activitypub/emoji_serializer_spec.rb | 33 + .../note_for_misskey_serializer_spec.rb | 51 + .../activitypub/note_serializer_spec.rb | 78 +- spec/serializers/node_info/serializer_spec.rb | 37 + .../rest/custom_emoji_serializer_spec.rb | 30 +- .../rest/instance_serializer_spec.rb | 12 +- spec/serializers/rest/rule_serializer_spec.rb | 3 +- .../activate_follow_requests_service_spec.rb | 55 + .../activate_remote_statuses_service_spec.rb | 67 + .../fetch_featured_collection_service_spec.rb | 68 +- .../fetch_references_service_spec.rb | 128 + .../fetch_remote_account_service_spec.rb | 9 +- .../fetch_remote_actor_service_spec.rb | 9 +- .../fetch_remote_key_service_spec.rb | 1 + .../process_account_service_spec.rb | 404 +- .../process_status_update_service_spec.rb | 659 +- spec/services/after_block_service_spec.rb | 19 + spec/services/backup_service_spec.rb | 13 +- spec/services/block_domain_service_spec.rb | 21 + spec/services/bulk_import_row_service_spec.rb | 12 +- .../create_featured_tag_service_spec.rb | 6 +- spec/services/delete_account_service_spec.rb | 61 +- .../services/delivery_antenna_service_spec.rb | 413 + spec/services/emoji_react_service_spec.rb | 298 + .../services/fan_out_on_write_service_spec.rb | 631 +- spec/services/favourite_service_spec.rb | 27 + spec/services/fetch_link_card_service_spec.rb | 104 +- spec/services/follow_service_spec.rb | 26 + spec/services/import_service_spec.rb | 242 + spec/services/post_status_service_spec.rb | 624 +- .../services/process_mentions_service_spec.rb | 23 + .../process_references_service_spec.rb | 464 + spec/services/reblog_service_spec.rb | 63 + .../remove_featured_tag_service_spec.rb | 10 +- spec/services/remove_status_service_spec.rb | 74 +- spec/services/report_service_spec.rb | 6 +- spec/services/resolve_account_service_spec.rb | 4 + spec/services/search_service_spec.rb | 2 +- .../software_update_check_service_spec.rb | 4 +- spec/services/suspend_account_service_spec.rb | 6 +- spec/services/un_emoji_react_service_spec.rb | 175 + spec/services/unallow_domain_service_spec.rb | 4 +- .../unsuspend_account_service_spec.rb | 11 +- spec/services/update_account_service_spec.rb | 8 + .../update_status_expiration_service_spec.rb | 74 + spec/services/update_status_service_spec.rb | 260 + spec/support/browser_errors.rb | 21 +- spec/support/capybara.rb | 28 +- spec/support/domain_helpers.rb | 4 - spec/support/examples/mailers.rb | 2 +- .../models/concerns/account/search.rb | 27 + .../models/concerns/account_avatar.rb | 9 + .../models/concerns/status/visibility.rb | 144 +- spec/support/fasp/provider_request_helper.rb | 35 +- spec/support/stories/profile_stories.rb | 3 +- spec/support/streaming_server_manager.rb | 2 +- spec/support/system_helpers.rb | 6 - spec/system/account_notes_spec.rb | 4 +- .../admin/account_moderation_notes_spec.rb | 2 +- spec/system/admin/domain_allows_spec.rb | 6 +- spec/system/admin/rules_spec.rb | 25 - spec/system/admin/trends/tags_spec.rb | 15 - spec/system/captcha_spec.rb | 2 +- spec/system/invites_spec.rb | 5 +- spec/system/log_out_spec.rb | 18 +- spec/system/new_statuses_spec.rb | 22 +- spec/system/profile_spec.rb | 4 +- spec/system/redirections_spec.rb | 2 +- spec/system/settings/applications_spec.rb | 2 +- spec/system/share_entrypoint_spec.rb | 16 +- .../activitypub/distribution_worker_spec.rb | 60 +- .../fetch_all_replies_worker_spec.rb | 7 +- .../fetch_instance_info_worker_spec.rb | 117 + .../fetch_remote_status_worker_spec.rb | 41 + .../status_update_distribution_worker_spec.rb | 44 +- ...rms_of_service_notification_worker_spec.rb | 8 +- spec/workers/bulk_import_worker_spec.rb | 12 +- spec/workers/domain_block_worker_spec.rb | 2 +- spec/workers/feed_insert_worker_spec.rb | 42 + spec/workers/import/row_worker_spec.rb | 114 +- spec/workers/import_worker_spec.rb | 23 + spec/workers/move_worker_spec.rb | 34 +- .../workers/process_references_worker_spec.rb | 17 + ...lish_scheduled_announcement_worker_spec.rb | 15 +- ...ccounts_statuses_cleanup_scheduler_spec.rb | 2 +- .../unfilter_notifications_worker_spec.rb | 24 +- .../web/push_notification_worker_spec.rb | 12 +- streaming/eslint.config.mjs | 6 +- streaming/index.js | 80 +- streaming/package.json | 4 +- streaming/redis.js | 12 +- stylelint.config.js | 4 +- tsconfig.json | 19 +- yarn.lock | 12188 +++++++++++----- 1685 files changed, 65863 insertions(+), 20915 deletions(-) create mode 100644 .github/FUNDING.yml create mode 100644 .github/ISSUE_TEMPLATE/1.bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/2.feature_request.yml create mode 100644 .github/ISSUE_TEMPLATE/3.spec_change_request.yml create mode 100644 .haml-lint_todo.yml create mode 100644 AUTHORS_KB.md create mode 100644 app/javascript/mastodon/actions/antennas.js create mode 100644 app/javascript/mastodon/actions/antennas_typed.ts create mode 100644 app/javascript/mastodon/actions/bookmark_categories.js create mode 100644 app/javascript/mastodon/actions/bookmark_categories_typed.ts create mode 100644 app/javascript/mastodon/actions/circles.js create mode 100644 app/javascript/mastodon/actions/circles_typed.ts create mode 100644 app/javascript/mastodon/actions/emoji_reactions.js create mode 100644 app/javascript/mastodon/actions/featured_tags.js create mode 100644 app/javascript/mastodon/actions/reaction_deck.js create mode 100644 app/javascript/mastodon/actions/tags.js create mode 100644 app/javascript/mastodon/api/antennas.ts create mode 100644 app/javascript/mastodon/api/bookmark_categories.ts create mode 100644 app/javascript/mastodon/api/circles.ts create mode 100644 app/javascript/mastodon/api_types/antennas.ts create mode 100644 app/javascript/mastodon/api_types/bookmark_categories.ts create mode 100644 app/javascript/mastodon/api_types/circles.ts create mode 100644 app/javascript/mastodon/common.js create mode 100644 app/javascript/mastodon/components/account.tsx create mode 100644 app/javascript/mastodon/components/button.tsx create mode 100644 app/javascript/mastodon/components/compacted_status.jsx create mode 100644 app/javascript/mastodon/components/dropdown_menu.jsx create mode 100644 app/javascript/mastodon/components/edited_timestamp/containers/dropdown_menu_container.js create mode 100644 app/javascript/mastodon/components/edited_timestamp/index.jsx create mode 100644 app/javascript/mastodon/components/emoji_view.jsx create mode 100644 app/javascript/mastodon/components/more_from_author.jsx create mode 100644 app/javascript/mastodon/components/navigation_portal.tsx create mode 100644 app/javascript/mastodon/components/picture_in_picture_placeholder.jsx create mode 100644 app/javascript/mastodon/components/searchability_icon.tsx create mode 100644 app/javascript/mastodon/components/status_emoji_reactions_bar.jsx create mode 100644 app/javascript/mastodon/containers/compacted_status_container.jsx create mode 100644 app/javascript/mastodon/containers/dropdown_menu_container.js create mode 100644 app/javascript/mastodon/features/account/components/account_note.jsx create mode 100644 app/javascript/mastodon/features/account/components/featured_tags.jsx create mode 100644 app/javascript/mastodon/features/account/containers/account_note_container.js create mode 100644 app/javascript/mastodon/features/account/containers/featured_tags_container.js create mode 100644 app/javascript/mastodon/features/account/navigation.jsx create mode 100644 app/javascript/mastodon/features/antenna_adder/index.tsx create mode 100644 app/javascript/mastodon/features/antenna_timeline/index.jsx create mode 100644 app/javascript/mastodon/features/antennas/filtering.tsx create mode 100644 app/javascript/mastodon/features/antennas/index.tsx create mode 100644 app/javascript/mastodon/features/antennas/members.tsx create mode 100644 app/javascript/mastodon/features/antennas/new.tsx create mode 100644 app/javascript/mastodon/features/audio/index.jsx create mode 100644 app/javascript/mastodon/features/audio/visualizer.js create mode 100644 app/javascript/mastodon/features/bookmark_categories/index.tsx create mode 100644 app/javascript/mastodon/features/bookmark_categories/new.tsx create mode 100644 app/javascript/mastodon/features/bookmark_category_adder/index.tsx create mode 100644 app/javascript/mastodon/features/bookmark_category_statuses/index.jsx create mode 100644 app/javascript/mastodon/features/bookmarked_statuses/index.jsx create mode 100644 app/javascript/mastodon/features/circle_adder/index.tsx create mode 100644 app/javascript/mastodon/features/circle_statuses/index.jsx create mode 100644 app/javascript/mastodon/features/circles/index.tsx create mode 100644 app/javascript/mastodon/features/circles/members.tsx create mode 100644 app/javascript/mastodon/features/circles/new.tsx create mode 100644 app/javascript/mastodon/features/compose/components/action_bar.jsx create mode 100644 app/javascript/mastodon/features/compose/components/circle_dropdown.jsx create mode 100644 app/javascript/mastodon/features/compose/components/expiration_dropdown.jsx create mode 100644 app/javascript/mastodon/features/compose/components/featured_tags_dropdown.jsx create mode 100644 app/javascript/mastodon/features/compose/components/navigation_bar.jsx create mode 100644 app/javascript/mastodon/features/compose/components/searchability_dropdown.jsx create mode 100644 app/javascript/mastodon/features/compose/containers/circle_dropdown_container.js create mode 100644 app/javascript/mastodon/features/compose/containers/expiration_dropdown_container.js create mode 100644 app/javascript/mastodon/features/compose/containers/featured_tags_dropdown_container.js create mode 100644 app/javascript/mastodon/features/compose/containers/markdown_button_container.js create mode 100644 app/javascript/mastodon/features/compose/containers/searchability_dropdown_container.js create mode 100644 app/javascript/mastodon/features/compose/index.jsx create mode 100644 app/javascript/mastodon/features/emoji/emoji_compressed.d.ts create mode 100644 app/javascript/mastodon/features/emoji/emoji_compressed.js create mode 100644 app/javascript/mastodon/features/emoji/emoji_picker.js create mode 100644 app/javascript/mastodon/features/emoji/emoji_sheet.json create mode 100644 app/javascript/mastodon/features/emoji/unicode_to_filename.ts create mode 100644 app/javascript/mastodon/features/emoji/unicode_to_filename_s.js create mode 100644 app/javascript/mastodon/features/emoji/unicode_to_unified_name.ts create mode 100644 app/javascript/mastodon/features/emoji/unicode_to_unified_name_s.js create mode 100644 app/javascript/mastodon/features/emoji_reacted_statuses/index.jsx create mode 100644 app/javascript/mastodon/features/emoji_reactions/index.jsx create mode 100644 app/javascript/mastodon/features/explore/components/card.jsx create mode 100644 app/javascript/mastodon/features/favourited_statuses/index.jsx create mode 100644 app/javascript/mastodon/features/followed_tags/index.jsx create mode 100644 app/javascript/mastodon/features/getting_started/components/trends.jsx create mode 100644 app/javascript/mastodon/features/getting_started/containers/trends_container.js create mode 100644 app/javascript/mastodon/features/getting_started/index.jsx create mode 100644 app/javascript/mastodon/features/mentioned_users/index.jsx create mode 100644 app/javascript/mastodon/features/notifications/components/notifications_permission_banner.jsx create mode 100644 app/javascript/mastodon/features/notifications_v2/components/avatar_group.tsx create mode 100644 app/javascript/mastodon/features/notifications_v2/components/notification_emoji_reaction.tsx create mode 100644 app/javascript/mastodon/features/notifications_v2/components/notification_list_status.tsx create mode 100644 app/javascript/mastodon/features/notifications_v2/components/notification_status_reference.tsx create mode 100644 app/javascript/mastodon/features/picture_in_picture/components/footer.jsx create mode 100644 app/javascript/mastodon/features/reaction_deck/components/reaction_emoji.jsx create mode 100644 app/javascript/mastodon/features/reaction_deck/index.jsx create mode 100644 app/javascript/mastodon/features/status_references/index.jsx create mode 100644 app/javascript/mastodon/features/ui/components/actions_modal.jsx create mode 100644 app/javascript/mastodon/features/ui/components/audio_modal.jsx create mode 100644 app/javascript/mastodon/features/ui/components/column_link.jsx create mode 100644 app/javascript/mastodon/features/ui/components/compose_panel.jsx create mode 100644 app/javascript/mastodon/features/ui/components/confirmation_modals/delete_antenna.tsx create mode 100644 app/javascript/mastodon/features/ui/components/confirmation_modals/delete_bookmark_category.tsx create mode 100644 app/javascript/mastodon/features/ui/components/confirmation_modals/delete_circle.tsx create mode 100644 app/javascript/mastodon/features/ui/components/confirmation_modals/edit_status.tsx create mode 100644 app/javascript/mastodon/features/ui/components/confirmation_modals/reply.tsx create mode 100644 app/javascript/mastodon/features/ui/components/disabled_account_banner.jsx create mode 100644 app/javascript/mastodon/features/ui/components/header.jsx create mode 100644 app/javascript/mastodon/features/ui/components/list_panel.jsx create mode 100644 app/javascript/mastodon/features/ui/components/navigation_panel.jsx create mode 100644 app/javascript/mastodon/features/ui/components/sign_in_banner.jsx create mode 100644 app/javascript/mastodon/main.jsx create mode 100644 app/javascript/mastodon/models/antenna.ts create mode 100644 app/javascript/mastodon/models/bookmark_category.ts create mode 100644 app/javascript/mastodon/models/circle.ts create mode 100644 app/javascript/mastodon/reducers/accounts_map.js create mode 100644 app/javascript/mastodon/reducers/antennas.ts create mode 100644 app/javascript/mastodon/reducers/bookmark_categories.ts create mode 100644 app/javascript/mastodon/reducers/circles.ts create mode 100644 app/javascript/mastodon/reducers/contexts.js create mode 100644 app/javascript/mastodon/reducers/followed_tags.js create mode 100644 app/javascript/mastodon/reducers/reaction_deck.js create mode 100644 app/javascript/mastodon/selectors/antennas.ts create mode 100644 app/javascript/mastodon/selectors/bookmark_categories.ts create mode 100644 app/javascript/mastodon/selectors/circles.ts create mode 100644 app/javascript/mastodon/service_worker/entry.js create mode 100644 app/javascript/mastodon/service_worker/web_push_locales.js create mode 100644 app/javascript/mastodon/test_helpers.tsx create mode 100644 app/javascript/mastodon/test_setup.js create mode 100644 app/javascript/mastodon/utils/mentions.ts create mode 100644 app/javascript/mastodon/utils/scrollbar.ts create mode 100644 app/javascript/material-icons/400-24px/compare_arrows-fill.svg create mode 100644 app/javascript/material-icons/400-24px/compare_arrows.svg create mode 100644 app/javascript/material-icons/400-24px/dangerous-fill.svg create mode 100644 app/javascript/material-icons/400-24px/dangerous.svg create mode 100644 app/javascript/material-icons/400-24px/dns-fill.svg create mode 100644 app/javascript/material-icons/400-24px/dns.svg create mode 100644 app/javascript/material-icons/400-24px/format_quote-fill.svg create mode 100644 app/javascript/material-icons/400-24px/format_quote.svg create mode 100644 app/javascript/material-icons/400-24px/key-fill.svg create mode 100644 app/javascript/material-icons/400-24px/markdown-fill.svg create mode 100644 app/javascript/material-icons/400-24px/markdown.svg create mode 100644 app/javascript/material-icons/400-24px/no_encryption-fill.svg create mode 100644 app/javascript/material-icons/400-24px/no_encryption.svg create mode 100644 app/javascript/material-icons/400-24px/shield-fill.svg create mode 100644 app/javascript/material-icons/400-24px/shield.svg create mode 100644 app/javascript/material-icons/400-24px/sticky_note-fill.svg create mode 100644 app/javascript/material-icons/400-24px/sticky_note.svg create mode 100644 app/javascript/material-icons/400-24px/timer-fill.svg create mode 100644 app/javascript/material-icons/400-24px/timer.svg create mode 100644 app/javascript/material-icons/400-24px/title-fill.svg create mode 100644 app/javascript/material-icons/400-24px/title.svg create mode 100644 app/javascript/material-icons/400-24px/wifi-fill.svg create mode 100644 app/javascript/material-icons/400-24px/wifi.svg create mode 100644 app/javascript/styles/full-dark.scss create mode 100644 app/javascript/styles/full-dark/css_variables.scss create mode 100644 app/javascript/styles/full-dark/diff.scss create mode 100644 app/javascript/styles/full-dark/variables.scss create mode 100644 app/javascript/styles/inert.scss create mode 100644 app/javascript/styles/mailer.scss create mode 100644 app/lib/chewy_config.rb create mode 100644 app/lib/vacuum/list_statuses_vacuum.rb create mode 100644 app/lib/vacuum/ng_histories_vacuum.rb create mode 100644 app/models/admin/ng_rule.rb create mode 100644 app/models/admin/ng_word.rb create mode 100644 app/models/admin/sensitive_word.rb create mode 100644 app/models/antenna.rb create mode 100644 app/models/antenna_account.rb create mode 100644 app/models/antenna_domain.rb create mode 100644 app/models/antenna_feed.rb create mode 100644 app/models/antenna_tag.rb create mode 100644 app/models/bookmark_category.rb create mode 100644 app/models/bookmark_category_status.rb create mode 100644 app/models/circle.rb create mode 100644 app/models/circle_account.rb create mode 100644 app/models/circle_status.rb create mode 100644 app/models/concerns/account/master_settings.rb create mode 100644 app/models/concerns/account/other_settings.rb create mode 100644 app/models/concerns/legacy_otp_secret.rb create mode 100644 app/models/concerns/status/domain_block_concern.rb create mode 100644 app/models/custom_css.rb create mode 100644 app/models/emoji_reaction.rb create mode 100644 app/models/friend_domain.rb create mode 100644 app/models/import.rb create mode 100644 app/models/instance_info.rb create mode 100644 app/models/list_status.rb create mode 100644 app/models/ng_rule.rb create mode 100644 app/models/ng_rule_history.rb create mode 100644 app/models/ng_word.rb create mode 100644 app/models/ngword_history.rb create mode 100644 app/models/pending_follow_request.rb create mode 100644 app/models/pending_status.rb create mode 100644 app/models/scheduled_expiration_status.rb create mode 100644 app/models/sensitive_word.rb create mode 100644 app/models/specified_domain.rb create mode 100644 app/models/status_capability_token.rb create mode 100644 app/models/status_reference.rb create mode 100644 app/policies/friend_server_policy.rb create mode 100644 app/policies/ng_words_policy.rb create mode 100644 app/policies/sensitive_words_policy.rb create mode 100644 app/presenters/activitypub/misskey_emoji_license_presenter.rb create mode 100644 app/presenters/emoji_reaction_accounts_presenter.rb create mode 100644 app/serializers/activitypub/activity_for_friend_serializer.rb create mode 100644 app/serializers/activitypub/activity_for_misskey_serializer.rb create mode 100644 app/serializers/activitypub/context_serializer.rb create mode 100644 app/serializers/activitypub/emoji_reaction_serializer.rb create mode 100644 app/serializers/activitypub/misskey_emoji_license_serializer.rb create mode 100644 app/serializers/activitypub/note_for_friend_serializer.rb create mode 100644 app/serializers/activitypub/note_for_misskey_serializer.rb create mode 100644 app/serializers/activitypub/undo_emoji_reaction_serializer.rb create mode 100644 app/serializers/rest/antenna_serializer.rb create mode 100644 app/serializers/rest/bookmark_category_serializer.rb create mode 100644 app/serializers/rest/circle_serializer.rb create mode 100644 app/serializers/rest/custom_emoji_slim_serializer.rb create mode 100644 app/serializers/rest/emoji_reaction_account_serializer.rb create mode 100644 app/serializers/rest/emoji_reaction_serializer.rb create mode 100644 app/serializers/rest/emoji_reactions_grouped_by_name_serializer.rb create mode 100644 app/serializers/rest/notify_emoji_reaction_serializer.rb create mode 100644 app/serializers/rest/status_internal_serializer.rb create mode 100644 app/services/activate_follow_requests_service.rb create mode 100644 app/services/activate_remote_statuses_service.rb create mode 100644 app/services/activitypub/fetch_references_service.rb create mode 100644 app/services/concerns/account_scope.rb create mode 100644 app/services/delivery_antenna_service.rb create mode 100644 app/services/emoji_react_service.rb create mode 100644 app/services/import_service.rb create mode 100644 app/services/process_conversation_service.rb create mode 100644 app/services/process_references_service.rb create mode 100644 app/services/searchability_update_service.rb create mode 100644 app/services/un_emoji_react_service.rb create mode 100644 app/services/update_status_expiration_service.rb create mode 100644 app/validators/emoji_reaction_validator.rb create mode 100644 app/views/admin/custom_emojis/edit.html.haml create mode 100644 app/views/admin/domain_blocks/_domain_block_list.html.haml create mode 100644 app/views/admin/friend_servers/_friend_domain.html.haml create mode 100644 app/views/admin/friend_servers/_friend_fields.html.haml create mode 100644 app/views/admin/friend_servers/edit.html.haml create mode 100644 app/views/admin/friend_servers/index.html.haml create mode 100644 app/views/admin/friend_servers/new.html.haml create mode 100644 app/views/admin/ng_rule_histories/_history.html.haml create mode 100644 app/views/admin/ng_rule_histories/show.html.haml create mode 100644 app/views/admin/ng_rules/_ng_rule.html.haml create mode 100644 app/views/admin/ng_rules/_ng_rule_fields.html.haml create mode 100644 app/views/admin/ng_rules/edit.html.haml create mode 100644 app/views/admin/ng_rules/index.html.haml create mode 100644 app/views/admin/ng_rules/new.html.haml create mode 100644 app/views/admin/ng_words/keywords/_ng_word.html.haml create mode 100644 app/views/admin/ng_words/keywords/show.html.haml create mode 100644 app/views/admin/ng_words/settings/show.html.haml create mode 100644 app/views/admin/ng_words/shared/_links.html.haml create mode 100644 app/views/admin/ng_words/white_list/_specified_domain.html.haml create mode 100644 app/views/admin/ng_words/white_list/show.html.haml create mode 100644 app/views/admin/ngword_histories/_history.html.haml create mode 100644 app/views/admin/ngword_histories/index.html.haml create mode 100644 app/views/admin/sensitive_words/_sensitive_word.html.haml create mode 100644 app/views/admin/sensitive_words/show.html.haml create mode 100644 app/views/admin/special_domains/show.html.haml create mode 100644 app/views/admin/special_instances/show.html.haml create mode 100644 app/views/admin_mailer/new_pending_friend_server.text.erb create mode 100644 app/views/application/_sidebar.html.haml create mode 100644 app/views/auth/confirmations/limitation_error.html.haml create mode 100644 app/views/custom_css/show_system.css.erb create mode 100644 app/views/settings/preferences/custom_css/show.html.haml create mode 100644 app/views/settings/preferences/reaching/show.html.haml create mode 100644 app/views/settings/privacy_extra/show.html.haml create mode 100644 app/views/system_css/show.css.erb create mode 100644 app/views/user_custom_css/show.css.erb create mode 100644 app/workers/activate_remote_account_worker.rb create mode 100644 app/workers/activitypub/emoji_reaction_distribution_worker.rb create mode 100644 app/workers/activitypub/fetch_instance_info_worker.rb create mode 100644 app/workers/activitypub/fetch_remote_status_worker.rb create mode 100644 app/workers/activitypub/forward_conversation_worker.rb create mode 100644 app/workers/delivery_emoji_reaction_worker.rb create mode 100644 app/workers/import/relationship_worker.rb create mode 100644 app/workers/import_worker.rb create mode 100644 app/workers/process_references_worker.rb create mode 100644 app/workers/remove_expired_status_worker.rb create mode 100644 app/workers/searchability_update_worker.rb create mode 100644 babel.config.js create mode 100644 bin/webpack create mode 100644 bin/webpack-dev-server create mode 100644 config/elasticsearch.default-ja-sudachi.yml create mode 100644 config/elasticsearch.default.yml create mode 100644 config/initializers/2_limited_federation_mode.rb create mode 100644 config/initializers/cache_buster.rb create mode 100644 config/initializers/vapid.rb create mode 100644 config/webpack/configuration.js create mode 100644 config/webpack/development.js create mode 100644 config/webpack/production.js create mode 100644 config/webpack/rules/babel.js create mode 100644 config/webpack/rules/css.js create mode 100644 config/webpack/rules/file.js create mode 100644 config/webpack/rules/index.js create mode 100644 config/webpack/rules/mark.js create mode 100644 config/webpack/rules/material_icons.js create mode 100644 config/webpack/rules/node_modules.js create mode 100644 config/webpack/rules/tesseract.js create mode 100644 config/webpack/shared.js create mode 100644 config/webpacker.yml create mode 100644 db/migrate/20230222232121_create_emoji_reactions.rb create mode 100644 db/migrate/20230223102416_add_emoji_reactions_to_status_stats.rb create mode 100644 db/migrate/20230308061833_add_image_size_to_custom_emojis.rb create mode 100644 db/migrate/20230314021909_add_group_message_following_only_to_accounts.rb create mode 100644 db/migrate/20230314081013_add_group_allow_private_message_to_accounts.rb create mode 100644 db/migrate/20230314121142_add_group_activitypub_count_to_account_stats.rb create mode 100644 db/migrate/20230320234918_create_scheduled_expiration_statuses.rb create mode 100644 db/migrate/20230405121613_add_searchability_to_statuses.rb create mode 100644 db/migrate/20230405121625_add_searchability_to_accounts.rb create mode 100644 db/migrate/20230406041523_change_searchability_default_value.rb create mode 100644 db/migrate/20230410004651_add_emoji_reactions_count_to_status_stats.rb create mode 100644 db/migrate/20230412005311_add_markdown_to_statuses.rb create mode 100644 db/migrate/20230412073021_add_markdown_to_status_edits.rb create mode 100644 db/migrate/20230414010523_add_emoji_reactions_count_per_account_to_status_stats.rb create mode 100644 db/migrate/20230420081634_add_min_emojis_to_account_statuses_cleanup_policies.rb create mode 100644 db/migrate/20230423002728_create_antennas.rb create mode 100644 db/migrate/20230423233429_add_dissubscribable_to_accounts.rb create mode 100644 db/migrate/20230426013738_add_excludes_to_antennas.rb create mode 100644 db/migrate/20230427022606_add_reject_favourite_to_domain_blocks.rb create mode 100644 db/migrate/20230427072650_add_reject_sending_to_domain_blocks.rb create mode 100644 db/migrate/20230427122753_add_some_to_domain_blocks.rb create mode 100644 db/migrate/20230427233749_add_hidden_to_domain_blocks.rb create mode 100644 db/migrate/20230428111230_add_emoji_reaction_streaming_to_accounts.rb create mode 100644 db/migrate/20230430110057_add_reject_invalid_subscription_to_domain_blocks.rb create mode 100644 db/migrate/20230509045358_change_antennas_list_to_nullable.rb create mode 100644 db/migrate/20230510000439_add_stl_to_antennas.rb create mode 100644 db/migrate/20230510004621_remove_stop_emoji_reaction_streaming_from_accounts.rb create mode 100644 db/migrate/20230510033040_add_ignore_reblog_to_antennas.rb create mode 100644 db/migrate/20230512122757_add_reject_reply_exclude_followers_to_domain_blocks.rb create mode 100644 db/migrate/20230514030455_add_settings_to_accounts.rb create mode 100644 db/migrate/20230521122642_add_aliases_to_custom_emoji.rb create mode 100644 db/migrate/20230522082252_add_is_sensitive_to_custom_emojis.rb create mode 100644 db/migrate/20230522093135_add_license_to_custom_emojis.rb create mode 100644 db/migrate/20230705232953_create_status_references.rb create mode 100644 db/migrate/20230706031715_add_status_referred_by_count_to_status_stats.rb create mode 100644 db/migrate/20230714004824_add_exclude_options_to_filters.rb create mode 100644 db/migrate/20230804222017_create_instance_infoes.rb create mode 100644 db/migrate/20230812083752_create_status_capability_token.rb create mode 100644 db/migrate/20230812130612_add_limited_scope_to_statuses.rb create mode 100644 db/migrate/20230819084858_add_no_insert_feeds_to_antennas.rb create mode 100644 db/migrate/20230821061713_create_circles.rb create mode 100644 db/migrate/20230822041804_add_antenna_elements_uniqueness.rb create mode 100644 db/migrate/20230826023400_create_bookmark_categories.rb create mode 100644 db/migrate/20230911022527_add_ltl_to_antennas.rb create mode 100644 db/migrate/20230919232836_add_attribute_to_status_references.rb create mode 100644 db/migrate/20230923103430_create_circle_statuses.rb create mode 100644 db/migrate/20230930233930_add_quote_to_status_references.rb create mode 100644 db/migrate/20231001031337_add_quote_to_statuses.rb create mode 100644 db/migrate/20231001050733_add_with_quote_to_custom_filters.rb create mode 100644 db/migrate/20231005074832_create_friend_domains.rb create mode 100644 db/migrate/20231006030102_add_reject_friend_to_domain_blocks.rb create mode 100644 db/migrate/20231007090808_improve_search_for_account_statuses.rb create mode 100644 db/migrate/20231009235215_add_delivery_local_to_friend_domains.rb create mode 100644 db/migrate/20231023083359_convert_dtl_force_settings.rb create mode 100644 db/migrate/20231028004612_create_list_statuses.rb create mode 100644 db/migrate/20231028005948_add_notify_to_list.rb create mode 100644 db/migrate/20231105225839_add_master_settings_to_accounts.rb create mode 100644 db/migrate/20231115001356_add_inbox_url_to_conversations.rb create mode 100644 db/migrate/20231130031209_add_index_on_conversations_ancestor_status_id.rb create mode 100644 db/migrate/20231130083634_add_index_on_statuses_conversation_id.rb create mode 100644 db/migrate/20231212225737_improve_index_for_public_timeline_speed.rb create mode 100644 db/migrate/20231214225249_index_to_statuses_url.rb create mode 100644 db/migrate/20240109035435_remove_hidden_anonymous_from_domain_blocks.rb create mode 100644 db/migrate/20240117021025_remove_unused_table.rb create mode 100644 db/migrate/20240121231131_add_block_trends_to_domain_blocks.rb create mode 100644 db/migrate/20240212224800_add_uri_to_favourites.rb create mode 100644 db/migrate/20240216042730_create_ngword_histories.rb create mode 100644 db/migrate/20240217022038_add_count_to_ngword_histories.rb create mode 100644 db/migrate/20240217093511_add_remote_pending_to_accounts.rb create mode 100644 db/migrate/20240217230006_create_pending_follow_requests.rb create mode 100644 db/migrate/20240218233621_create_ng_rules.rb create mode 100644 db/migrate/20240227033337_remove_group_attrs_from_accounts.rb create mode 100644 db/migrate/20240227225017_create_pending_statuses.rb create mode 100644 db/migrate/20240229233617_remove_reject_reply_from_domain_blocks.rb create mode 100644 db/migrate/20240312230204_create_sensitive_words.rb create mode 100644 db/migrate/20240320231633_create_ng_words.rb create mode 100644 db/migrate/20240401222541_create_specified_domains.rb create mode 100644 db/migrate/20240426000034_move_account_warning_notifications.rb create mode 100644 db/migrate/20240426233435_migrate_moderation_warning_notifications_from_account.rb create mode 100644 db/migrate/20240509220635_revert_media_file_size_column_to_big_int.rb create mode 100644 db/migrate/20240709063700_add_with_profile_to_custom_filters.rb create mode 100644 db/migrate/20240828123604_create_custom_csses.rb create mode 100644 db/migrate/20241208232829_add_favourite_to_lists_and_antennas.rb create mode 100644 db/migrate/20250123091137_remove_half_warn_filter_option.rb create mode 100644 db/migrate/20250130232529_set_antenna_list_id_default_value.rb create mode 100644 db/post_migrate/20230314120530_remove_group_message_following_only_from_accounts.rb create mode 100644 db/post_migrate/20231021005339_remove_remote_uri_from_local_custom_emojis.rb create mode 100644 db/post_migrate/20231022074913_add_statuses_quote_index.rb create mode 100644 db/post_migrate/20240117022353_remove_legacy_domain_block_columns.rb create mode 100644 db/post_migrate/20240212230358_fix_uri_index_to_emoji_reactions.rb create mode 100644 db/post_migrate/20240217215134_improve_remote_pending_accounts_index.rb create mode 100644 db/post_migrate/20240227222450_index_to_sort_for_ng_word_created_date.rb create mode 100644 db/post_migrate/20240326231854_improve_preview_cards_vacuum.rb create mode 100644 db/post_migrate/20240327234026_fix_duplicate_indices_for_kmyblue_original_functions.rb create mode 100644 db/post_migrate/20250216231806_add_new_public_index_for_kmyblue_to_statuses.rb create mode 100644 db/post_migrate/20250216231904_remove_old_public_index_for_original_to_statuses.rb create mode 100644 dist/nginx-before-certbot.conf create mode 100644 install/12.0/setup-imagemagick-7.sh create mode 100644 install/12.0/setup1.sh create mode 100644 install/12.0/setup2.sh create mode 100644 install/13.0/setup-imagemagick-7.sh create mode 100644 install/13.0/setup1.sh create mode 100644 install/13.0/setup2.sh create mode 100644 install/5.0/setup-imagemagick-7.sh create mode 100644 install/5.0/setup1.sh create mode 100644 install/5.0/setup2.sh create mode 100644 install/5.0/setup3.sh create mode 100644 install/5.0/setup4.sh create mode 100644 install/9.0/setup-imagemagick-7.sh create mode 100644 install/9.0/setup1.sh create mode 100644 install/9.0/setup2.sh create mode 100644 install/9.0/setup3.sh create mode 100644 install/9.0/setup4.sh create mode 100644 jest.config.js create mode 100644 lib/mastodon/cli/ohagi.rb create mode 100644 lib/redis/namespace_extensions.rb create mode 100644 lib/stoplight/redis_data_store_extensions.rb create mode 100644 lib/tasks/dangerous.rake create mode 100644 lib/tasks/webpacker.rake create mode 100644 lib/webpacker/helper_extensions.rb create mode 100644 lib/webpacker/manifest_extensions.rb create mode 100644 postcss.config.js create mode 100644 public/emoji/sheet_15.png create mode 100644 public/favicon.ico create mode 100644 spec/controllers/api/v1/circles/statuses_controller_spec.rb create mode 100644 spec/controllers/api/v1/lists_controller_spec.rb create mode 100644 spec/fabricators/antenna_account_fabricator.rb create mode 100644 spec/fabricators/antenna_domain_fabricator.rb create mode 100644 spec/fabricators/antenna_fabricator.rb create mode 100644 spec/fabricators/antenna_tag_fabricator.rb create mode 100644 spec/fabricators/bookmark_category_fabricator.rb create mode 100644 spec/fabricators/bookmark_category_status_fabricator.rb create mode 100644 spec/fabricators/circle_account_fabricator.rb create mode 100644 spec/fabricators/circle_fabricator.rb create mode 100644 spec/fabricators/circle_status_fabricator.rb create mode 100644 spec/fabricators/emoji_reaction_fabricator.rb create mode 100644 spec/fabricators/friend_domain_fabricator.rb create mode 100644 spec/fabricators/import_fabricator.rb create mode 100644 spec/fabricators/instance_info_fabricator.rb create mode 100644 spec/fabricators/list_status_fabricator.rb create mode 100644 spec/fabricators/ng_rule_fabricator.rb create mode 100644 spec/fabricators/ng_rule_history_fabricator.rb create mode 100644 spec/fabricators/ng_word_fabricator.rb create mode 100644 spec/fabricators/ngword_history_fabricator.rb create mode 100644 spec/fabricators/pending_follow_request_fabricator.rb create mode 100644 spec/fabricators/pending_status_fabricator.rb create mode 100644 spec/fabricators/scheduled_expiration_status_fabricator.rb create mode 100644 spec/fabricators/sensitive_word_fabricator.rb create mode 100644 spec/fabricators/specified_domain_fabricator.rb create mode 100644 spec/fabricators/status_reference_fabricator.rb create mode 100644 spec/fixtures/files/avatar.avif create mode 100644 spec/lib/activitypub/parser/custom_emoji_parser_spec.rb create mode 100644 spec/lib/vacuum/list_statuses_vacuum_spec.rb create mode 100644 spec/lib/vacuum/ng_histories_vacuum_spec.rb create mode 100644 spec/models/admin/ng_rule_spec.rb create mode 100644 spec/models/admin/ng_word_spec.rb create mode 100644 spec/models/admin/sensitive_word_spec.rb create mode 100644 spec/models/friend_domain_spec.rb create mode 100644 spec/models/import_spec.rb create mode 100644 spec/models/instance_info_spec.rb create mode 100644 spec/models/ng_rule_spec.rb create mode 100644 spec/models/notification_group_spec.rb create mode 100644 spec/requests/api/v1/accounts/pins_spec.rb create mode 100644 spec/requests/api/v1/antennas_spec.rb create mode 100644 spec/requests/api/v1/circles/accounts_spec.rb create mode 100644 spec/requests/api/v1/circles_spec.rb create mode 100644 spec/requests/api/v1/statuses/emoji_reactions_spec.rb create mode 100644 spec/requests/api/v1/timelines/antenna_spec.rb create mode 100644 spec/requests/user_custom_css_spec.rb create mode 100644 spec/search/services/statuses_search_service_spec.rb create mode 100644 spec/serializers/activitypub/emoji_serializer_spec.rb create mode 100644 spec/serializers/activitypub/note_for_misskey_serializer_spec.rb create mode 100644 spec/serializers/node_info/serializer_spec.rb create mode 100644 spec/services/activate_follow_requests_service_spec.rb create mode 100644 spec/services/activate_remote_statuses_service_spec.rb create mode 100644 spec/services/activitypub/fetch_references_service_spec.rb create mode 100644 spec/services/delivery_antenna_service_spec.rb create mode 100644 spec/services/emoji_react_service_spec.rb create mode 100644 spec/services/import_service_spec.rb create mode 100644 spec/services/process_references_service_spec.rb create mode 100644 spec/services/un_emoji_react_service_spec.rb create mode 100644 spec/services/update_status_expiration_service_spec.rb create mode 100644 spec/workers/activitypub/fetch_instance_info_worker_spec.rb create mode 100644 spec/workers/activitypub/fetch_remote_status_worker_spec.rb create mode 100644 spec/workers/import_worker_spec.rb create mode 100644 spec/workers/process_references_worker_spec.rb diff --git a/.devcontainer/compose.yaml b/.devcontainer/compose.yaml index ced5ecfe884c88..5da1ec3a24c68a 100644 --- a/.devcontainer/compose.yaml +++ b/.devcontainer/compose.yaml @@ -9,7 +9,6 @@ services: environment: RAILS_ENV: development NODE_ENV: development - VITE_RUBY_HOST: 0.0.0.0 BIND: 0.0.0.0 BOOTSNAP_CACHE_DIR: /tmp REDIS_HOST: redis @@ -23,12 +22,11 @@ services: ES_PORT: '9200' LIBRE_TRANSLATE_ENDPOINT: http://libretranslate:5000 LOCAL_DOMAIN: ${LOCAL_DOMAIN:-localhost:3000} - VITE_DEV_SERVER_PUBLIC: ${VITE_DEV_SERVER_PUBLIC:-localhost:3036} # Overrides default command so things don't shut down after the process ends. command: sleep infinity ports: - '3000:3000' - - '3036:3036' + - '3035:3035' - '4000:4000' networks: - external_network diff --git a/.env.production.sample b/.env.production.sample index 15004b9d0d9fde..4afaf8d7568e92 100644 --- a/.env.production.sample +++ b/.env.production.sample @@ -43,6 +43,7 @@ ES_PASS=password # Make sure to use `bundle exec rails secret` to generate secrets # ------- SECRET_KEY_BASE= +OTP_SECRET= # Encryption secrets # ------------------ diff --git a/.env.test b/.env.test index d2763e582ae926..52379687b41bd7 100644 --- a/.env.test +++ b/.env.test @@ -3,6 +3,8 @@ NODE_ENV=production # Federation LOCAL_DOMAIN=cb6e6126.ngrok.io LOCAL_HTTPS=true +# Elasticsearch +ES_PREFIX=test # Secret values required by ActiveRecord encryption feature # Use `bin/rails db:encryption:init` to generate fresh secrets diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000000000..fa7a0c5353a706 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: https://fantia.jp/fanclubs/484677 diff --git a/.github/ISSUE_TEMPLATE/1.bug_report.yml b/.github/ISSUE_TEMPLATE/1.bug_report.yml new file mode 100644 index 00000000000000..10421eed7b17f4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1.bug_report.yml @@ -0,0 +1,74 @@ +name: バグ報告 +description: kmyblueのバグ報告(ただし情報改竄、秘密情報の漏洩、システムの破損などが発生するバグは、こちらではなく「Security」タブよりセキュリティインシデントとして報告してください) +labels: [bug] +body: + - type: textarea + attributes: + label: バグの再現手順 + description: どのように操作したらバグが発生したのか、バグが発生する直前までの手順を順番に詳しく教えてください + value: | + 1. + 2. + 3. + ... + validations: + required: true + - type: textarea + attributes: + label: 期待する動作 + description: どのように動いてほしかったですか? + validations: + required: true + - type: textarea + attributes: + label: 実際の動作 + description: どのようなバグが発生しましたか? + validations: + required: true + - type: textarea + attributes: + label: 詳しい情報 + validations: + required: false + - type: input + attributes: + label: バグが発生したkmyblueサーバーのドメイン + description: サーバー固有の問題の可能性もありますので、プライバシー上可能な範囲内で、できるだけ書いてください + placeholder: kmy.blue + validations: + required: false + - type: input + attributes: + label: バグが発生したkmyblueのバージョン + description: | + Mastodonではなくkmyblueのバージョンを記述してください。例えばバージョン表記が `v4.2.0+kmyblue.5.1-LTS` の場合、バージョンは `5.1`になります + + バージョンは、PCだと画面左下、スマホだと概要画面の一番下に書いてあります + placeholder: '5.1' + validations: + required: true + - type: input + attributes: + label: ブラウザの名前 + description: | + ブラウザの名前を書いてください。可能であればバージョンも併記してください + placeholder: Firefox 105.0.3 + validations: + required: false + - type: input + attributes: + label: OS + description: | + あなたのOSと、できればバージョンも教えてください。スマホの場合は、「Android」「iPhone」にバージョンをつけてください + placeholder: Windows11 + validations: + required: false + - type: textarea + attributes: + label: その他の詳細情報 + description: | + あなたの環境が特殊な場合、詳しいことを教えてください(例: VPS、tor、学内LANなど) + + サーバー管理者の場合は、Ruby、Node.jsのバージョン、Cloudflareの使用可否なども可能なら書いてください + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/2.feature_request.yml b/.github/ISSUE_TEMPLATE/2.feature_request.yml new file mode 100644 index 00000000000000..10fb4bb23b3abc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2.feature_request.yml @@ -0,0 +1,16 @@ +name: 機能要望 +description: 機能の提案 +labels: [enhancement] +body: + - type: textarea + attributes: + label: 欲しい機能 + description: 欲しい機能の詳細を書いてください + validations: + required: true + - type: textarea + attributes: + label: 必要性 + description: この機能はあなたにとってなぜ必要でしょうか?どういった状況で使われるものですか? + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/3.spec_change_request.yml b/.github/ISSUE_TEMPLATE/3.spec_change_request.yml new file mode 100644 index 00000000000000..e71befe859de60 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/3.spec_change_request.yml @@ -0,0 +1,28 @@ +name: 仕様変更・改善要望 +description: 既存の仕様や挙動変更の要望 +labels: [specchange] +body: + - type: markdown + attributes: + value: 意図したものとは明らかに異なる挙動をしているものはバグとして、もともと仕様として決められた動きをしているものを変更したいときはこちらでお願いします + - type: textarea + attributes: + label: 挙動を変更してほしい機能や動作 + validations: + required: true + - type: textarea + attributes: + label: 現在の挙動 + validations: + required: true + - type: textarea + attributes: + label: 変更してほしい新しい挙動 + validations: + required: true + - type: textarea + attributes: + label: 必要性 + description: この変更はあなたにとってなぜ必要でしょうか?どういった状況で使われるものですか? + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/3.troubleshooting.yml b/.github/ISSUE_TEMPLATE/3.troubleshooting.yml index 004e74bf42eb67..fa9bfc7c80d3b2 100644 --- a/.github/ISSUE_TEMPLATE/3.troubleshooting.yml +++ b/.github/ISSUE_TEMPLATE/3.troubleshooting.yml @@ -50,7 +50,7 @@ body: label: Mastodon version description: | This is displayed at the bottom of the About page, eg. `v4.4.0-alpha.1` - placeholder: v4.4.0-beta.1 + placeholder: v4.3.0 validations: required: false - type: textarea @@ -60,9 +60,9 @@ body: Details about your environment, like how Mastodon is deployed, if containers are used, version numbers, etc. value: | Please at least include those informations: - - Operating system: (eg. Ubuntu 24.04.2) - - Ruby version: (from `ruby --version`, eg. v3.4.4) - - Node.js version: (from `node --version`, eg. v22.16.0) + - Operating system: (eg. Ubuntu 22.04) + - Ruby version: (from `ruby --version`, eg. v3.4.1) + - Node.js version: (from `node --version`, eg. v20.18.0) validations: required: false - type: textarea diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index f5d3196528f92d..0086358db1eb97 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1 @@ -blank_issues_enabled: false -contact_links: - - name: GitHub Discussions - url: https://github.com/mastodon/mastodon/discussions - about: Please ask and answer questions here. +blank_issues_enabled: true diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 1850a45bbcde71..e638b9c5482f72 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -25,12 +25,26 @@ 'tesseract.js', // Requires code changes 'react-hotkeys', // Requires code changes + // Requires Webpacker upgrade or replacement + '@svgr/webpack', + '@types/webpack', + 'babel-loader', + 'compression-webpack-plugin', + 'css-loader', + 'imports-loader', + 'mini-css-extract-plugin', + 'postcss-loader', + 'sass-loader', + 'terser-webpack-plugin', + 'webpack', + 'webpack-assets-manifest', + 'webpack-bundle-analyzer', + 'webpack-dev-server', + 'webpack-cli', + // react-router: Requires manual upgrade 'history', 'react-router-dom', - - // react-spring: Requires manual upgrade when upgrading react - '@react-spring/web', ], matchUpdateTypes: ['major'], dependencyDashboardApproval: true, @@ -39,6 +53,7 @@ // Require Dependency Dashboard Approval for major version bumps of these Ruby packages matchManagers: ['bundler'], matchPackageNames: [ + 'rack', // Needs to be synced with Rails version 'strong_migrations', // Requires manual upgrade 'sidekiq', // Requires manual upgrade 'sidekiq-unique-jobs', // Requires manual upgrades and sync with Sidekiq version diff --git a/.github/workflows/bundler-audit.yml b/.github/workflows/bundler-audit.yml index fa28d28f740c45..c96e4429af154b 100644 --- a/.github/workflows/bundler-audit.yml +++ b/.github/workflows/bundler-audit.yml @@ -4,6 +4,9 @@ on: push: branches: - 'main' + - 'kb*' + - 'upstream-*' + - 'releases/*' - 'stable-*' paths: - 'Gemfile*' diff --git a/.github/workflows/check-i18n.yml b/.github/workflows/check-i18n.yml index c46090c1b565bc..63529e4f162dd2 100644 --- a/.github/workflows/check-i18n.yml +++ b/.github/workflows/check-i18n.yml @@ -4,10 +4,16 @@ on: push: branches: - 'main' + - 'kb*' + - 'upstream-*' + - 'releases/*' - 'stable-*' pull_request: branches: - 'main' + - 'kb*' + - 'upstream-*' + - 'releases/*' - 'stable-*' env: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 8690e9ed6d1639..827629d8e651e7 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -5,10 +5,16 @@ on: push: branches: - 'main' + - 'kb*' + - 'upstream-*' + - 'releases/*' - 'stable-*' pull_request: branches: - 'main' + - 'kb*' + - 'upstream-*' + - 'releases/*' - 'stable-*' schedule: - cron: '22 6 * * 1' diff --git a/.github/workflows/format-check.yml b/.github/workflows/format-check.yml index c10f350a02ef28..a190ece7dfd60b 100644 --- a/.github/workflows/format-check.yml +++ b/.github/workflows/format-check.yml @@ -4,6 +4,9 @@ on: push: branches: - 'main' + - 'kb*' + - 'upstream-*' + - 'releases/*' - 'stable-*' pull_request: diff --git a/.github/workflows/lint-css.yml b/.github/workflows/lint-css.yml index c1385bf789b0bc..ffab4880e12867 100644 --- a/.github/workflows/lint-css.yml +++ b/.github/workflows/lint-css.yml @@ -4,6 +4,9 @@ on: push: branches: - 'main' + - 'kb*' + - 'upstream-*' + - 'releases/*' - 'stable-*' paths: - 'package.json' diff --git a/.github/workflows/lint-haml.yml b/.github/workflows/lint-haml.yml index 499be2010adc99..c596261eb04fc4 100644 --- a/.github/workflows/lint-haml.yml +++ b/.github/workflows/lint-haml.yml @@ -4,6 +4,9 @@ on: push: branches: - 'main' + - 'kb*' + - 'upstream-*' + - 'releases/*' - 'stable-*' paths: - '.github/workflows/haml-lint-problem-matcher.json' diff --git a/.github/workflows/lint-js.yml b/.github/workflows/lint-js.yml index 86e9af23e7efb1..13468e77992fef 100644 --- a/.github/workflows/lint-js.yml +++ b/.github/workflows/lint-js.yml @@ -4,6 +4,9 @@ on: push: branches: - 'main' + - 'kb*' + - 'upstream-*' + - 'releases/*' - 'stable-*' paths: - 'package.json' diff --git a/.github/workflows/lint-ruby.yml b/.github/workflows/lint-ruby.yml index 87f8aee24e01e9..5bb67b108c6ea3 100644 --- a/.github/workflows/lint-ruby.yml +++ b/.github/workflows/lint-ruby.yml @@ -4,6 +4,9 @@ on: push: branches: - 'main' + - 'kb*' + - 'upstream-*' + - 'releases/*' - 'stable-*' paths: - 'Gemfile*' diff --git a/.github/workflows/test-js.yml b/.github/workflows/test-js.yml index 0699e6c9ef8193..f962f5c36f7c87 100644 --- a/.github/workflows/test-js.yml +++ b/.github/workflows/test-js.yml @@ -4,6 +4,9 @@ on: push: branches: - 'main' + - 'kb*' + - 'upstream-*' + - 'releases/*' - 'stable-*' paths: - 'package.json' @@ -40,4 +43,4 @@ jobs: uses: ./.github/actions/setup-javascript - name: JavaScript testing - run: yarn test:js + run: yarn jest --reporters github-actions summary diff --git a/.github/workflows/test-migrations.yml b/.github/workflows/test-migrations.yml index 7aab34f0cf4ee6..c4a716e8f98ff4 100644 --- a/.github/workflows/test-migrations.yml +++ b/.github/workflows/test-migrations.yml @@ -5,6 +5,9 @@ on: push: branches: - 'main' + - 'kb*' + - 'upstream-*' + - 'releases/*' - 'stable-*' paths: - 'Gemfile*' diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index 63d317250436ae..fd4c66605961d5 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -5,6 +5,9 @@ on: push: branches: - 'main' + - 'kb*' + - 'upstream-*' + - 'releases/*' - 'stable-*' pull_request: @@ -49,7 +52,7 @@ jobs: public/assets public/packs public/packs-test - tmp/cache/vite + tmp/cache/webpacker key: ${{ matrix.mode }}-assets-${{ github.head_ref || github.ref_name }}-${{ github.sha }} restore-keys: | ${{ matrix.mode }}-assets-${{ github.head_ref || github.ref_name }}-${{ github.sha }} @@ -63,7 +66,7 @@ jobs: - name: Archive asset artifacts run: | - tar --exclude={"*.br","*.gz"} -zcf artifacts.tar.gz public/assets public/packs* tmp/cache/vite/last-build*.json + tar --exclude={"*.br","*.gz"} -zcf artifacts.tar.gz public/assets public/packs* - uses: actions/upload-artifact@v4 if: matrix.mode == 'test' @@ -119,6 +122,7 @@ jobs: CAS_ENABLED: true BUNDLE_WITH: 'pam_authentication test' GITHUB_RSPEC: ${{ matrix.ruby-version == '.ruby-version' && github.event.pull_request && 'true' }} + ES_ENABLED: false strategy: fail-fast: false @@ -143,7 +147,7 @@ jobs: uses: ./.github/actions/setup-ruby with: ruby-version: ${{ matrix.ruby-version}} - additional-system-dependencies: ffmpeg libpam-dev + additional-system-dependencies: ffmpeg imagemagick libpam-dev - name: Load database schema run: | @@ -173,8 +177,8 @@ jobs: env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - test-imagemagick: - name: ImageMagick tests + test-libvips: + name: Libvips tests runs-on: ubuntu-latest needs: @@ -220,7 +224,7 @@ jobs: CAS_ENABLED: true BUNDLE_WITH: 'pam_authentication test' GITHUB_RSPEC: ${{ matrix.ruby-version == '.ruby-version' && github.event.pull_request && 'true' }} - MASTODON_USE_LIBVIPS: false + MASTODON_USE_LIBVIPS: true strategy: fail-fast: false @@ -245,7 +249,7 @@ jobs: uses: ./.github/actions/setup-ruby with: ruby-version: ${{ matrix.ruby-version}} - additional-system-dependencies: ffmpeg imagemagick libpam-dev + additional-system-dependencies: ffmpeg libpam-dev - name: Load database schema run: './bin/rails db:create db:schema:load db:seed' @@ -297,6 +301,7 @@ jobs: DB_PASS: postgres RAILS_ENV: test BUNDLE_WITH: test + ES_ENABLED: false LOCAL_DOMAIN: localhost:3000 LOCAL_HTTPS: false @@ -324,7 +329,7 @@ jobs: uses: ./.github/actions/setup-ruby with: ruby-version: ${{ matrix.ruby-version}} - additional-system-dependencies: ffmpeg + additional-system-dependencies: ffmpeg imagemagick - name: Set up Javascript environment uses: ./.github/actions/setup-javascript @@ -332,21 +337,6 @@ jobs: - name: Load database schema run: './bin/rails db:create db:schema:load db:seed' - - name: Cache Playwright Chromium browser - id: playwright-cache - uses: actions/cache@v4 - with: - path: ~/.cache/ms-playwright - key: playwright-browsers-${{ runner.os }}-${{ hashFiles('yarn.lock') }} - - - name: Install Playwright Chromium browser (with deps) - if: steps.playwright-cache.outputs.cache-hit != 'true' - run: yarn run playwright install --with-deps chromium - - - name: Install Playwright Chromium browser deps - if: steps.playwright-cache.outputs.cache-hit == 'true' - run: yarn run playwright install-deps chromium - - run: bin/rspec spec/system --tag streaming --tag js - name: Archive logs @@ -360,7 +350,7 @@ jobs: uses: actions/upload-artifact@v4 if: failure() with: - name: e2e-screenshots-${{ matrix.ruby-version }} + name: e2e-screenshots path: tmp/capybara/ test-search: @@ -458,7 +448,7 @@ jobs: uses: ./.github/actions/setup-ruby with: ruby-version: ${{ matrix.ruby-version}} - additional-system-dependencies: ffmpeg + additional-system-dependencies: ffmpeg imagemagick - name: Set up Javascript environment uses: ./.github/actions/setup-javascript @@ -481,3 +471,93 @@ jobs: with: name: test-search-screenshots path: tmp/capybara/ + + test-back-and-return: + name: Back to original and return test + runs-on: ubuntu-latest + + needs: + - build + + services: + postgres: + image: postgres:14-alpine + env: + POSTGRES_PASSWORD: postgres + POSTGRES_USER: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + redis: + image: redis:7-alpine + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 6379:6379 + + env: + DB_HOST: localhost + DB_USER: postgres + DB_PASS: postgres + DISABLE_SIMPLECOV: ${{ matrix.ruby-version != '.ruby-version' }} + RAILS_ENV: test + ALLOW_NOPAM: true + PAM_ENABLED: true + PAM_DEFAULT_SERVICE: pam_test + PAM_CONTROLLED_SERVICE: pam_test_controlled + OIDC_ENABLED: true + OIDC_SCOPE: read + SAML_ENABLED: true + CAS_ENABLED: true + BUNDLE_WITH: 'pam_authentication test' + GITHUB_RSPEC: ${{ matrix.ruby-version == '.ruby-version' && github.event.pull_request && 'true' }} + ES_ENABLED: false + BACK_UPSTREAM_FORCE: true + + strategy: + fail-fast: false + matrix: + ruby-version: + - '.ruby-version' + steps: + - uses: actions/checkout@v4 + + - uses: actions/download-artifact@v4 + with: + path: './' + name: ${{ github.sha }} + + - name: Expand archived asset artifacts + run: | + tar xvzf artifacts.tar.gz + + - name: Set up Ruby environment + uses: ./.github/actions/setup-ruby + with: + ruby-version: ${{ matrix.ruby-version}} + additional-system-dependencies: ffmpeg imagemagick libpam-dev + + - name: Load database schema + run: './bin/rails db:create db:schema:load db:seed' + + - name: Back to upstream schema + run: 'bundle exec rake dangerous:back_upstream' + + - name: Return to kmyblue + run: './bin/rails db:migrate' + + - run: bin/rspec + + - name: Upload coverage reports to Codecov + if: matrix.ruby-version == '.ruby-version' + uses: codecov/codecov-action@v3 + with: + files: coverage/lcov/mastodon-back-ret.lcov diff --git a/.gitignore b/.gitignore index db63bc07f0d003..7d60baebf86e16 100644 --- a/.gitignore +++ b/.gitignore @@ -21,13 +21,15 @@ /public/system /public/assets /public/packs -/public/packs-dev /public/packs-test .env .env.production -node_modules/ +/node_modules/ /build/ +# Ignore elasticsearch config +/.elasticsearch.yml + # Ignore Vagrant files .vagrant/ @@ -75,6 +77,3 @@ docker-compose.override.yml # Ignore local-only rspec configuration .rspec-local - -*storybook.log -storybook-static diff --git a/.haml-lint.yml b/.haml-lint.yml index 74d243a3ad63f9..7dbc88e9dbd44d 100644 --- a/.haml-lint.yml +++ b/.haml-lint.yml @@ -1,3 +1,5 @@ +inherits_from: .haml-lint_todo.yml + exclude: - 'vendor/**/*' diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml new file mode 100644 index 00000000000000..841561291f28da --- /dev/null +++ b/.haml-lint_todo.yml @@ -0,0 +1,36 @@ +# This configuration was generated by +# `haml-lint --auto-gen-config` +# on 2024-01-09 11:30:07 -0500 using Haml-Lint version 0.53.0. +# The point is for the user to remove these configuration records +# one by one as the lints are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of Haml-Lint, may require this file to be generated again. + +linters: + # Offense count: 1 + LineLength: + exclude: + - 'app/views/admin/ng_rules/_ng_rule_fields.html.haml' + - 'app/views/admin/roles/_form.html.haml' + + # Offense count: 9 + RuboCop: + exclude: + - 'app/views/home/index.html.haml' + + ViewLength: + exclude: + - 'app/views/admin/accounts/index.html.haml' + - 'app/views/admin/instances/show.html.haml' + - 'app/views/admin/ng_rules/_ng_rule_fields.html.haml' + - 'app/views/admin/settings/discovery/show.html.haml' + - 'app/views/settings/preferences/appearance/show.html.haml' + - 'app/views/settings/preferences/other/show.html.haml' + + InstanceVariables: + exclude: + - 'app/views/application/_sidebar.html.haml' + - 'app/views/admin/ng_rules/_ng_rule_fields.html.haml' + - 'app/views/admin/ng_words/keywords/_ng_word.html.haml' + - 'app/views/admin/ng_words/white_list/_specified_domain.html.haml' + - 'app/views/admin/sensitive_words/_sensitive_word.html.haml' diff --git a/.nvmrc b/.nvmrc index 4a203c23d8335c..744ca17ec07de0 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22.17 +22.14 diff --git a/.prettierignore b/.prettierignore index 098dac6717786f..80b4c0159e210e 100644 --- a/.prettierignore +++ b/.prettierignore @@ -18,6 +18,10 @@ !/log/.keep /tmp /coverage +/public/system +/public/assets +/public/packs +/public/packs-test .env .env.production .env.development @@ -56,11 +60,10 @@ docker-compose.override.yml /public/packs /public/packs-test /public/system -/public/vite* # Ignore emoji map file /app/javascript/mastodon/features/emoji/emoji_map.json -/app/javascript/mastodon/features/emoji/emoji_data.json +/app/javascript/mastodon/features/emoji/emoji_sheet.json # Ignore locale files /app/javascript/mastodon/locales/*.json @@ -81,6 +84,3 @@ AUTHORS.md # Process a few selected JS files !lint-staged.config.js - -# Ignore config YAML files that include ERB/ruby code prettier does not understand -/config/email.yml diff --git a/.rubocop/metrics.yml b/.rubocop/metrics.yml index 89532af42abb15..2828d5e4a3fb3d 100644 --- a/.rubocop/metrics.yml +++ b/.rubocop/metrics.yml @@ -1,6 +1,7 @@ --- Metrics/AbcSize: Exclude: + - 'app/serializers/initial_state_serializer.rb' - lib/mastodon/cli/*.rb Metrics/BlockLength: @@ -11,6 +12,11 @@ Metrics/ClassLength: Metrics/CyclomaticComplexity: Exclude: + - 'app/lib/feed_manager.rb' + - 'app/policies/status_policy.rb' + - 'app/services/activitypub/process_account_service.rb' + - 'app/services/delivery_antenna_service.rb' + - 'app/services/post_status_service.rb' - lib/mastodon/cli/*.rb Metrics/MethodLength: diff --git a/.rubocop/naming.yml b/.rubocop/naming.yml index 37d3a17efdbdb3..da6ad4ac579a63 100644 --- a/.rubocop/naming.yml +++ b/.rubocop/naming.yml @@ -1,6 +1,3 @@ --- Naming/BlockForwarding: EnforcedStyle: explicit - -Naming/PredicateMethod: - Enabled: false diff --git a/.rubocop/rspec.yml b/.rubocop/rspec.yml index 27f703444ea3da..940701c7226919 100644 --- a/.rubocop/rspec.yml +++ b/.rubocop/rspec.yml @@ -8,6 +8,8 @@ RSpec/MultipleExpectations: RSpec/MultipleMemoizedHelpers: Max: 20 # Overrides default of 5 + Exclude: + - 'spec/services/delete_account_service_spec.rb' RSpec/NamedSubject: EnforcedStyle: named_only @@ -23,6 +25,5 @@ RSpec/SpecFilePathFormat: ActivityPub: activitypub DeepL: deepl FetchOEmbedService: fetch_oembed_service - OAuth: oauth OEmbedController: oembed_controller OStatus: ostatus diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 4ec92f341217a6..8232ec8ec3c4d7 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --auto-gen-only-exclude --no-offense-counts --no-auto-gen-timestamp` -# using RuboCop version 1.77.0. +# using RuboCop version 1.75.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -26,14 +26,69 @@ Metrics/CyclomaticComplexity: # Configuration parameters: AllowedMethods, AllowedPatterns. Metrics/PerceivedComplexity: Max: 27 + Exclude: + - 'app/policies/status_policy.rb' + - 'app/services/delivery_antenna_service.rb' + - 'app/services/post_status_service.rb' + +Rails/OutputSafety: + Exclude: + - 'config/initializers/simple_form.rb' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowedVars, DefaultToNil. +# Configuration parameters: AllowedVars. Style/FetchEnvVar: Exclude: + - 'config/environments/production.rb' + - 'config/initializers/2_limited_federation_mode.rb' + - 'config/initializers/3_omniauth.rb' + - 'config/initializers/cache_buster.rb' + - 'config/initializers/devise.rb' - 'config/initializers/paperclip.rb' + - 'config/initializers/vapid.rb' + - 'lib/tasks/repo.rake' + +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, MaxUnannotatedPlaceholdersAllowed, Mode, AllowedMethods, AllowedPatterns. +# SupportedStyles: annotated, template, unannotated +# AllowedMethods: redirect +Style/FormatStringToken: + Exclude: + - 'config/initializers/devise.rb' + - 'lib/paperclip/color_extractor.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: MinBodyLength, AllowConsecutiveConditionals. Style/GuardClause: Enabled: false + +# This cop supports unsafe autocorrection (--autocorrect-all). +Style/HashTransformValues: + Exclude: + - 'app/serializers/rest/web_push_subscription_serializer.rb' + - 'app/services/import_service.rb' + +# Configuration parameters: AllowedMethods. +# AllowedMethods: respond_to_missing? +Style/OptionalBooleanParameter: + Exclude: + - 'app/lib/admin/system_check/message.rb' + - 'app/lib/request.rb' + - 'app/lib/webfinger.rb' + - 'app/services/block_domain_service.rb' + - 'app/services/fetch_resource_service.rb' + - 'app/workers/domain_block_worker.rb' + - 'app/workers/unfollow_follow_worker.rb' + +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: short, verbose +Style/PreferredHashMethods: + Exclude: + - 'config/initializers/paperclip.rb' + +# This cop supports safe autocorrection (--autocorrect). +Style/RedundantConstantBase: + Exclude: + - 'config/environments/production.rb' + - 'config/initializers/sidekiq.rb' diff --git a/.ruby-version b/.ruby-version index f9892605c753d1..4d9d11cf505d5d 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.4.4 +3.4.2 diff --git a/AUTHORS_KB.md b/AUTHORS_KB.md new file mode 100644 index 00000000000000..2cda5fd31d9f89 --- /dev/null +++ b/AUTHORS_KB.md @@ -0,0 +1,18 @@ +# Authors for kmyblue fork + +## 貢献者 + +kmyblueフォークは、以下の方の貢献によって成り立っています。 +本家Mastodonの貢献者については、`AUTHORS.md`をご覧ください。 + +- [aoisensi](https://github.com/aoisensi) +- [KMY](https://github.com/kmycode) +- [S-H-GAMELINKS](https://github.com/S-H-GAMELINKS) +- [Yuicho](https://github.com/yuicho) + +## 特記 + +kmyblueフォークの開発にあたって、API・Activity仕様の設計(一部機能については内部仕様)策定の過程で下記リポジトリのコードを参考にしました。 +kmyblueフォークに直接貢献したわけではありませんが、以下のリポジトリにある絵文字リアクション機能・検索範囲機能のコードのうち一部にkmyblueへ転写した箇所がございますため、お名前記載させていただきます。 + +- [Fedibird](https://github.com/fedibird/mastodon) diff --git a/CHANGELOG.md b/CHANGELOG.md index efdd3adf120264..a9819a6c790ca4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,294 +2,6 @@ All notable changes to this project will be documented in this file. -## [4.4.0] - UNRELEASED - -### Added - -- **Add “Followers you know” widget to user profiles and hover cards** (#34652, #34678, #34681, #34697, #34699, #34769, #34774 and #34914 by @diondiondion) -- **Add featured tab to profiles on web UI and rework pinned posts** (#34405, #34483, #34491, #34754, #34855, #34858, #34868, #34869, #34927, #34995, #35056 and #34931 by @ChaosExAnima, @ClearlyClaire, @Gargron, and @diondiondion) -- Add endorsed accounts to featured tab in web UI (#34421 and #34568 by @Gargron)\ - This also includes the following new REST API endpoints: - - `GET /api/v1/accounts/:id/endorsements`: https://docs.joinmastodon.org/methods/accounts/#endorsements - - `POST /api/v1/accounts/:id/endorse`: https://docs.joinmastodon.org/methods/accounts/#endorse - - `POST /api/v1/accounts/:id/unendorse`: https://docs.joinmastodon.org/methods/accounts/#unendorse -- Add ability to add and remove hashtags from featured tags in web UI (#34489, #34887, and #34490 by @ClearlyClaire and @Gargron)\ - This is achieved through the new REST API endpoints: - - `POST /api/v1/tags/:id/feature`: https://docs.joinmastodon.org/methods/tags/#feature - - `POST /api/v1/tags/:id/unfeature`: https://docs.joinmastodon.org/methods/tags/#unfeature -- Add reminder when about to post without alt text in web UI (#33760 and #33784 by @Gargron) -- Add a warning in Web UI when composing a post when the selected and detected language are different (#33042, #33683, #33700, #33724, #33770, and #34193 by @ClearlyClaire and @Gargron) -- Add support for verifying and displaying remote quote posts (#34370, #34481, #34510, #34551, #34480, #34479, #34553, #34584, #34623, #34738, #34766, #34770, #34772, #34773, #34786, #34790, #34864, #34957, #34961, #35016, #35022, #35036, #34946, #34945 and #34958 by @ClearlyClaire and @diondiondion)\ - Support for verifying remote quotes according to [FEP-044f](https://codeberg.org/fediverse/fep/src/branch/main/fep/044f/fep-044f.md) and displaying them in the Web UI has been implemented.\ - Quoting other people is not implemented yet, and it is currently not possible to mark your own posts as allowing quotes. However, a new “Who can quote” setting has been added to the “Posting defaults” section of the user settings. This setting allows you to set a default that will be used for new posts made on Mastodon 4.5 and newer, when quote posts will be fully implemented.\ - In the REST API, quote posts are represented by a new `quote` attribute on `Status` and `StatusEdit` entities: https://docs.joinmastodon.org/entities/StatusEdit/#quote https://docs.joinmastodon.org/entities/Status/#quote -- Add ability to reorder and translate server rules (#34637, #34737, #34494, #34756, #34820, #34997, #35170, #35174 and #35174 by @ChaosExAnima and @ClearlyClaire)\ - Rules are now shown in the user’s language, if a translation has been set.\ - In the REST API, `Rule` entities now have a new `translations` attribute: https://docs.joinmastodon.org/entities/Rule/#translations -- Add emoji from Twemoji 15.1.0, including in the emoji picker/completion (#33395, #34321, #34620, and #34677 by @ChaosExAnima, @ClearlyClaire, @TheEssem, and @eramdam) -- Add option to remove account from followers in web UI (#34488 by @Gargron) -- Add relationship tags to profiles and hover cards in web UI (#34467 and #34792 by @Gargron and @diondiondion) -- Add ability to open posts in a new tab by middle-clicking in web UI (#32988, #33106, #33419, and #34700 by @ClearlyClaire, @Gargron, and @tribela) -- Add new filter action to blur media (#34256 by @ClearlyClaire)\ - In the REST API, this adds a new possible value of `blur` to the `filter_action` attribute: https://docs.joinmastodon.org/entities/Filter/#filter_action -- Add dropdown menu to hashtag links in web UI (#34393 by @Gargron) -- **Add server setting to allow referrer** (#33214, #33239, #33903, and #34731 by @ChaosExAnima, @ClearlyClaire, @Gargron, and @renchap)\ - In order to protect the privacy of users of small or thematic servers, Mastodon previously avoided transmitting referrer information when clicking outside links, which unfortunately made Mastodon completely invisible to other websites, even though the privacy implications on large generic servers are very limited.\ - Server administrators can now chose to opt in to transmit referrer information when following an external link. Only the domain name is transmitted, not the referrer path. -- Add double tap to zoom and swipe to dismiss to media modal in web UI (#34210 by @Gargron) -- Add link from Web UI for Hashtags to the Moderation UI (#31448 by @ThisIsMissEm) -- **Add terms of service** (#33055, #33233, #33230, #33703, #33699, #33994, #33993, #34105, #34122, #34200, #34527, #35053, #35115, #35126 and #35127 by @ClearlyClaire, @Gargron, @mjankowski, and @oneiros)\ - Server administrators can now fill in Terms of Service and notify their users of upcoming changes. -- Add optional bulk mailer settings (#35191 and #35203 by @oneiros)\ - This adds the optional environment variables `BULK_SMTP_PORT`, `BULK_SMTP_SERVER`, `BULK_SMTP_LOGIN` and so on analogous to `SMTP_PORT`, `SMTP_SERVER`, `SMTP_LOGIN` and related SMTP configuration environment variables.\ - When `BULK_SMTP_SERVER` is set, this group of variables is used instead of the regular ones for sending announcement notification emails and Terms of Service notification emails. -- **Add age verification on sign-up** (#34150, #34663, and #34636 by @ClearlyClaire and @Gargron)\ - Server administrators now have a setting to set a minimum age requirement for creating a new server, asking users for their date of birth. The date of birth is checked against the minimum age requirement server-side but not stored.\ - The following REST API changes have been made to accommodate this: - - `registrations.min_age` has been added to the `Instance` entity: https://docs.joinmastodon.org/entities/Instance/#registrations-min_age - - the `date_of_birth` parameter has been added to the account creation API: https://docs.joinmastodon.org/methods/accounts/#create -- Add ability to dismiss alt text badge by tapping it in web UI (#33737 by @Gargron) -- Add loading indicator to timeline gap indicators in web UI (#33762 by @Gargron) -- Add interaction modal when trying to interact with a poll while logged out (#32609 by @ThisIsMissEm) -- **Add experimental FASP support** (#34031, #34415, #34765, #34965, #34964, #34033 and #35218 by @oneiros)\ - This is a first step towards supporting “Fediverse Auxiliary Service Providers” (https://github.com/mastodon/fediverse_auxiliary_service_provider_specifications). This is mostly interesting to developers who would like to implement their own FASP, but also includes the capability to share data with a discovery provider (see https://www.fediscovery.org). -- Add ability for admins to send announcements to all users via email (#33928 and #34411 by @ClearlyClaire)\ - This is meant for critical announcements only, as this will potentially send a lot of emails and cannot be opted out of by users. -- Add Server Moderation Notes (#31529 by @ThisIsMissEm) -- Add loading spinner to “Post” button when sending a post (#35153 by @diondiondion) -- Add option to use system scrollbar styling (#32117 by @vmstan) -- Add hover cards to follow suggestions (#33749 by @ClearlyClaire) -- Add `t` hotkey for post translations (#33441 by @ClearlyClaire) -- Add timestamp to all announcements in Web UI (#18329 by @ClearlyClaire) -- Add dropdown menu with quick actions to lists of accounts in web UI (#34391, #34709, and #34767 by @Gargron, @diondiondion, and @mkljczk) -- Add support for displaying “year in review” notification in web UI (#32710, #32765, #32709, #32807, #32914, #33148, and #33882 by @Gargron and @mjankowski)\ - Note that the notification is currently not generated automatically, and at the moment requires a manual undocumented administrator action. -- Add experimental support for receiving HTTP Message Signatures (RFC9421) (#34814, #35033 and #35109 by @oneiros)\ - For now, this needs to be explicitly enabled through the `http_message_signatures` feature flag (`EXPERIMENTAL_FEATURES=http_message_signatures`). This currently only covers verifying such signatures (inbound HTTP requests), not issuing them (outbound HTTP requests). -- Add experimental Async Refreshes API (#34918 by @oneiros) -- Add experimental server-side feature to fetch remote replies (#32615, #34147, #34149, #34151, #34615, #34682, and #34702 by @ClearlyClaire and @sneakers-the-rat)\ - This experimental feature causes the server to recursively fetch replies in background tasks whenever a user opens a remote post. This happens asynchronously and the client is currently not notified of the existence of new replies, which will thus only be displayed the next time this post’s context gets requested.\ - This feature needs to be explicitly enabled server-side by setting `FETCH_REPLIES_ENABLED` environment variable to `true`. -- Add simple feature flag system through the `EXPERIMENTAL_FEATURES` environment variable (#34038 and #34124 by @oneiros)\ - This allows enabling comma-separated feature flags for experimental features.\ - The current supported feature flags are `inbound_quotes`, `fasp` and `http_message_signatures`. -- Add `dev:populate_sample_data` rake task to populate test data (#34676, #34733, #34771, #34787, and #34791 by @ClearlyClaire and @diondiondion) -- Add support for displaying fallback representation when receiving MathML (#27107 by @4e554c4c) -- Add warning for Elasticsearch index analyzers mismatch (#34515 and #34567 by @ClearlyClaire and @Gargron) -- Add `-only-mapping` option to `tootctl search deploy` (#34466 and #34566 by @Gargron) -- Add server-side support for grouping account sign-up notifications (#34298 by @ClearlyClaire) -- Add `registrations.reason_required` attribute to `/api/v2/instance` response (#34280 by @ClearlyClaire)\ - This is documented at https://docs.joinmastodon.org/entities/Instance/#registrations-reason_required -- Add `EXTRA_MEDIA_HOSTS` environment variable to add extra hosts to Content-Security-Policy (#34184 by @shleeable) -- Add `Deprecation` headers on deprecated API endpoints (#34262 and #34397 by @ClearlyClaire)\ - This is documented at https://docs.joinmastodon.org/api/guidelines/#deprecations -- Add `about`, `privacy_policy` and `terms_of_service` URLs to `/api/v2/instance` (#33849 by @ClearlyClaire) -- Add API to delete media attachments that are not in use (#33991 and #34035 by @ClearlyClaire and @ThisIsMissEm)\ - `DELETE /api/v1/media/:id`: https://docs.joinmastodon.org/methods/media/#delete -- Add optional `delete_media` parameter to `DELETE /api/v1/statuses/:id` (#33988 by @ClearlyClaire)\ - This is documented at https://docs.joinmastodon.org/methods/statuses/#delete -- Add `og:locale` to expose status language in OpenGraph previews (#34012 by @ThisIsMissEm) -- Add `-skip-filled-timeline` option to `tootctl feed build` to skip half-filled feeds (#33844 by @ClearlyClaire) -- Add support for changing the base Docker registry with the `BASE_REGISTRY` `ARG` (#33712 by @wolfspyre) -- Add an optional metric exporter (#33734, #33840, #34172, #34192, #34223, and #35005 by @oneiros and @renchap)\ - Optionally enable the `prometheus_exporter` ruby gem (see https://github.com/discourse/prometheus_exporter) to collect and expose metrics. See the documentation for all the details: https://docs.joinmastodon.org/admin/config/#prometheus -- Add `attribution_domains` attribute to `PATCH /api/v1/accounts/update_credentials` (#32730 by @c960657)\ - This is documented at https://docs.joinmastodon.org/methods/accounts/#update_credentials -- Add support for standard WebPush in addition to previous draft (#33572, #33528, and #33587 by @ClearlyClaire and @p1gp1g) -- Add support for Active Record query log tags (#33342 by @renchap) -- Add OTel trace & span IDs to logs (#33339 and #33362 by @renchap) -- Add missing `on_delete: :cascade` foreign keys option to various database columns (#33175 by @mjankowski) -- Add explicit migration breakpoints (#33089 by @ClearlyClaire) -- Add rel alternate rss/json links to pages for tags (#33179 by @mjankowski) -- Add media attachment description limit to instance API response (#33153 by @mjankowski)\ - This adds the `configuration.media_attachments.description_limit` attribute to the `Instance` entity, documented at https://docs.joinmastodon.org/entities/Instance/#description_limit -- Add `maxlength` to registration reason input (#33162 by @mjankowski) -- Add `REPLICA_PREPARED_STATEMENTS` and `REPLICA_DB_TASKS` environment variables (#32908 by @shleeable)\ - See documentation at https://docs.joinmastodon.org/admin/scaling/#read-replicas -- Add a range of reserved usernames to reduce potential misuse by malicious actors (#32828 by @jmking-iftas) -- Add operations on relays to the admin audit log (#32819 by @ThisIsMissEm) -- Add userinfo OAuth endpoint (#32548 by @ThisIsMissEm) -- Add the standard VCS attributes to OpenTelemetry spans (#32904 by @renchap) -- Add endpoint to remove web push subscription (#32626 by @oneiros)\ - Mastodon now sets a new `Unsubscribe-URL` request header when performing WebPush requests. This URL can be used by the WebPush server to disable the WebPush subscription on Mastodon’s side in case of unfixable errors. -- Add missing content warning text to RSS feeds (#32406 by @mjankowski) -- Add Swiss German to languages dropdown (#29281 by @FlohEinstein) - -### Changed - -- Change design of navigation panel in Web UI, change layout on narrow screens (#34910, #34987, #35017, #34986, #35029, #35065, #35067, #35072, #35074, #35075, #35101, #35173, #35183, #35193 and #35225 by @ClearlyClaire, @Gargron, and @diondiondion) -- Change design of lists in web UI (#32881, #33054, and #33036 by @Gargron) -- Change design of edit media modal in web UI (#33516, #33702, #33725, #33725, #33771, and #34345 by @Gargron) -- Change design of audio player in web UI (#34520, #34740, #34865, #34929, #34933, and #35034 by @ClearlyClaire, @Gargron, and @diondiondion) -- Change design of interaction modal in web UI (#33278 by @Gargron) -- Change list timelines to reflect added and removed users retroactively (#32930 by @Gargron) -- Change account search to be more forgiving of spaces (#34455 by @Gargron) -- Change unfollow button label from “Mutual” to “Unfollow” in web UI (#34392 by @Gargron) -- Change “Specific people” to “Private mention” in menu in web UI (#33963 by @Gargron) -- Change "Explore" to "Trending" and remove explanation banners (#34985 by @Gargron) -- Change media attachments of moderated posts to not be accessible (#34872 by @Gargron) - Moderators will still be able to access them while they are kept, but they won't be accessible to the public in the meantime. -- Change language names in compose box language picker to be localized (#33402 by @c960657) -- Change onboarding flow in web UI (#32998, #33119, #33471 and #34962 by @ClearlyClaire and @Gargron) -- Change Advanced Web UI to use the new main menu instead of the “Getting started” column (#35117 by @diondiondion) -- Change emoji categories in admin interface to be ordered by name (#33630 by @ShadowJonathan) -- Change design of rich text elements in web UI (#32633 by @Gargron) -- Change wording of “single choice” to “pick one” in poll authoring form (#32397 by @ThisIsMissEm) -- Change returned favorite and boost counts to use those provided by the remote server, if available (#32620, #34594, #34618, and #34619 by @ClearlyClaire and @sneakers-the-rat) -- Change label of favourite notifications on private mentions (#31659 by @ClearlyClaire) -- Change wording of "discard draft?" confirmation dialogs (#35192 by @diondiondion) -- Change `libvips` to be enabled by default in place of ImageMagick (#34741 and #34753 by @ClearlyClaire and @diondiondion) -- Change avatar and header size limits from 2MB to 8MB when using libvips (#33002 by @Gargron) -- Change search to use query params in web UI (#32949 and #33670 by @ClearlyClaire and @Gargron) -- Change build system from Webpack to Vite (#34454, #34450, #34758, #34768, #34813, #34808, #34837, #34732, #35007, #35035 and #35177 by @ChaosExAnima, @ClearlyClaire, @mjankowski, and @renchap) -- Change account creation API to forbid creation from user tokens (#34828 by @ThisIsMissEm) -- Change `/api/v2/instance` to be enabled without authentication when limited federation mode is enabled (#34576 by @ClearlyClaire) -- Change `DEFAULT_LOCALE` to not override unauthenticated users’ browser language (#34535 by @ClearlyClaire)\ - If you want to preserve the old behavior, you can add `FORCE_DEFAULT_LOCALE=true`. -- Change size of profile picture on profile page from 90px to 92px (#34807 by @larouxn) -- Change passthrough video processing to emit `moov` atom at start of video (#34726 by @ClearlyClaire) -- Change kerning to be disabled for Japanese text to preserve monospaced alignment for readability (#34448 by @nagutabby) -- Change error handling of various endpoints to return 422 instead of 500 on invalid parameters (#29308, #34434, and #34452 by @danielmbrasil and @mjankowski) -- Change Web UI to use `