Merge remote-tracking branch 'parent/main' into upstream-20231116

This commit is contained in:
KMY 2023-11-15 13:01:29 +09:00
commit 24371d6b2a
87 changed files with 566 additions and 347 deletions

View file

@ -94,7 +94,7 @@ jobs:
DB_HOST: localhost DB_HOST: localhost
DB_USER: postgres DB_USER: postgres
DB_PASS: postgres DB_PASS: postgres
DISABLE_SIMPLECOV: true DISABLE_SIMPLECOV: ${{ matrix.ruby-version != '.ruby-version' }}
RAILS_ENV: test RAILS_ENV: test
ALLOW_NOPAM: true ALLOW_NOPAM: true
PAM_ENABLED: true PAM_ENABLED: true
@ -138,6 +138,12 @@ jobs:
- run: bin/rspec - 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.lcov
test-e2e: test-e2e:
name: End to End testing name: End to End testing
runs-on: ubuntu-latest runs-on: ubuntu-latest

View file

@ -41,23 +41,6 @@ Metrics/CyclomaticComplexity:
Metrics/PerceivedComplexity: Metrics/PerceivedComplexity:
Max: 27 Max: 27
RSpec/AnyInstance:
Exclude:
- 'spec/controllers/activitypub/inboxes_controller_spec.rb'
- 'spec/controllers/admin/accounts_controller_spec.rb'
- 'spec/controllers/admin/resets_controller_spec.rb'
- 'spec/controllers/auth/sessions_controller_spec.rb'
- 'spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb'
- 'spec/controllers/settings/two_factor_authentication/recovery_codes_controller_spec.rb'
- 'spec/lib/request_spec.rb'
- 'spec/lib/status_filter_spec.rb'
- 'spec/models/account_spec.rb'
- 'spec/models/setting_spec.rb'
- 'spec/services/activitypub/process_collection_service_spec.rb'
- 'spec/validators/follow_limit_validator_spec.rb'
- 'spec/workers/activitypub/delivery_worker_spec.rb'
- 'spec/workers/web/push_notification_worker_spec.rb'
# Configuration parameters: CountAsOne. # Configuration parameters: CountAsOne.
RSpec/ExampleLength: RSpec/ExampleLength:
Max: 22 Max: 22

View file

@ -139,6 +139,7 @@ group :test do
# Coverage formatter for RSpec test if DISABLE_SIMPLECOV is false # Coverage formatter for RSpec test if DISABLE_SIMPLECOV is false
gem 'simplecov', '~> 0.22', require: false gem 'simplecov', '~> 0.22', require: false
gem 'simplecov-lcov', '~> 0.8', require: false
# Stub web requests for specs # Stub web requests for specs
gem 'webmock', '~> 3.18' gem 'webmock', '~> 3.18'

View file

@ -727,6 +727,7 @@ GEM
simplecov-html (~> 0.11) simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1) simplecov_json_formatter (~> 0.1)
simplecov-html (0.12.3) simplecov-html (0.12.3)
simplecov-lcov (0.8.0)
simplecov_json_formatter (0.1.4) simplecov_json_formatter (0.1.4)
smart_properties (1.17.0) smart_properties (1.17.0)
sprockets (3.7.2) sprockets (3.7.2)
@ -939,6 +940,7 @@ DEPENDENCIES
simple-navigation (~> 4.4) simple-navigation (~> 4.4)
simple_form (~> 5.2) simple_form (~> 5.2)
simplecov (~> 0.22) simplecov (~> 0.22)
simplecov-lcov (~> 0.8)
sprockets (~> 3.7.2) sprockets (~> 3.7.2)
sprockets-rails (~> 3.4) sprockets-rails (~> 3.4)
stackprof stackprof

View file

@ -1,4 +1,4 @@
web: env PORT=3000 RAILS_ENV=development bundle exec puma -C config/puma.rb web: env PORT=3000 RAILS_ENV=development bundle exec puma -C config/puma.rb
sidekiq: env PORT=3000 RAILS_ENV=development bundle exec sidekiq sidekiq: env PORT=3000 RAILS_ENV=development bundle exec sidekiq
stream: env PORT=4000 yarn run start stream: env PORT=4000 yarn workspace @mastodon/streaming start
webpack: bin/webpack-dev-server webpack: bin/webpack-dev-server

View file

@ -7,6 +7,7 @@ class Api::BaseController < ApplicationController
include RateLimitHeaders include RateLimitHeaders
include AccessTokenTrackingConcern include AccessTokenTrackingConcern
include ApiCachingConcern include ApiCachingConcern
include Api::ContentSecurityPolicy
skip_before_action :require_functional!, unless: :limited_federation_mode? skip_before_action :require_functional!, unless: :limited_federation_mode?
@ -17,26 +18,6 @@ class Api::BaseController < ApplicationController
protect_from_forgery with: :null_session protect_from_forgery with: :null_session
content_security_policy do |p|
# Set every directive that does not have a fallback
p.default_src :none
p.frame_ancestors :none
p.form_action :none
# Disable every directive with a fallback to cut on response size
p.base_uri false
p.font_src false
p.img_src false
p.style_src false
p.media_src false
p.frame_src false
p.manifest_src false
p.connect_src false
p.script_src false
p.child_src false
p.worker_src false
end
rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e| rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
render json: { error: e.to_s }, status: 422 render json: { error: e.to_s }, status: 422
end end

View file

@ -13,7 +13,7 @@ class Api::V1::Instances::DomainBlocksController < Api::V1::Instances::BaseContr
cache_if_unauthenticated! cache_if_unauthenticated!
end end
render json: @domain_blocks, each_serializer: REST::DomainBlockSerializer, with_comment: (Setting.show_domain_blocks_rationale == 'all' || (Setting.show_domain_blocks_rationale == 'users' && user_signed_in?)) render json: @domain_blocks, each_serializer: REST::DomainBlockSerializer, with_comment: show_rationale_in_response?
end end
private private
@ -26,4 +26,16 @@ class Api::V1::Instances::DomainBlocksController < Api::V1::Instances::BaseContr
@domain_blocks = DomainBlock.with_user_facing_limitations.by_severity @domain_blocks = DomainBlock.with_user_facing_limitations.by_severity
@domain_blocks = @domain_blocks.filter { |block| !block.hidden_anonymous } unless user_signed_in? @domain_blocks = @domain_blocks.filter { |block| !block.hidden_anonymous } unless user_signed_in?
end end
def show_rationale_in_response?
always_show_rationale? || show_rationale_for_user?
end
def always_show_rationale?
Setting.show_domain_blocks_rationale == 'all'
end
def show_rationale_for_user?
Setting.show_domain_blocks_rationale == 'users' && user_signed_in?
end
end end

View file

@ -0,0 +1,33 @@
# frozen_string_literal: true
class Api::V1::Timelines::BaseController < Api::BaseController
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
private
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def pagination_max_id
@statuses.last.id
end
def pagination_since_id
@statuses.first.id
end
def next_path_params
permitted_params.merge(max_id: pagination_max_id)
end
def prev_path_params
permitted_params.merge(min_id: pagination_since_id)
end
def permitted_params
params
.slice(*self.class::PERMITTED_PARAMS)
.permit(*self.class::PERMITTED_PARAMS)
end
end

View file

@ -1,9 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::Timelines::HomeController < Api::BaseController class Api::V1::Timelines::HomeController < Api::V1::Timelines::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: [:show] before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: [:show]
before_action :require_user!, only: [:show] before_action :require_user!, only: [:show]
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
PERMITTED_PARAMS = %i(local limit).freeze
def show def show
with_read_replica do with_read_replica do
@ -42,27 +43,11 @@ class Api::V1::Timelines::HomeController < Api::BaseController
HomeFeed.new(current_account) HomeFeed.new(current_account)
end end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def pagination_params(core_params)
params.slice(:local, :limit).permit(:local, :limit).merge(core_params)
end
def next_path def next_path
api_v1_timelines_home_url pagination_params(max_id: pagination_max_id) api_v1_timelines_home_url next_path_params
end end
def prev_path def prev_path
api_v1_timelines_home_url pagination_params(min_id: pagination_since_id) api_v1_timelines_home_url prev_path_params
end
def pagination_max_id
@statuses.last.id
end
def pagination_since_id
@statuses.first.id
end end
end end

View file

@ -1,12 +1,12 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::Timelines::ListController < Api::BaseController class Api::V1::Timelines::ListController < Api::V1::Timelines::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:lists' } before_action -> { doorkeeper_authorize! :read, :'read:lists' }
before_action :require_user! before_action :require_user!
before_action :set_list before_action :set_list
before_action :set_statuses before_action :set_statuses
after_action :insert_pagination_headers, unless: -> { @statuses.empty? } PERMITTED_PARAMS = %i(limit).freeze
def show def show
render json: @statuses, render json: @statuses,
@ -41,27 +41,11 @@ class Api::V1::Timelines::ListController < Api::BaseController
ListFeed.new(@list) ListFeed.new(@list)
end end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
def next_path def next_path
api_v1_timelines_list_url params[:id], pagination_params(max_id: pagination_max_id) api_v1_timelines_list_url params[:id], next_path_params
end end
def prev_path def prev_path
api_v1_timelines_list_url params[:id], pagination_params(min_id: pagination_since_id) api_v1_timelines_list_url params[:id], prev_path_params
end
def pagination_max_id
@statuses.last.id
end
def pagination_since_id
@statuses.first.id
end end
end end

View file

@ -1,8 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::Timelines::PublicController < Api::BaseController class Api::V1::Timelines::PublicController < Api::V1::Timelines::BaseController
before_action :require_user!, only: [:show], if: :require_auth? before_action :require_user!, only: [:show], if: :require_auth?
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
PERMITTED_PARAMS = %i(local remote limit only_media).freeze
def show def show
cache_if_unauthenticated! cache_if_unauthenticated!
@ -44,27 +45,11 @@ class Api::V1::Timelines::PublicController < Api::BaseController
) )
end end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def pagination_params(core_params)
params.slice(:local, :remote, :limit, :only_media).permit(:local, :remote, :limit, :only_media).merge(core_params)
end
def next_path def next_path
api_v1_timelines_public_url pagination_params(max_id: pagination_max_id) api_v1_timelines_public_url next_path_params
end end
def prev_path def prev_path
api_v1_timelines_public_url pagination_params(min_id: pagination_since_id) api_v1_timelines_public_url prev_path_params
end
def pagination_max_id
@statuses.last.id
end
def pagination_since_id
@statuses.first.id
end end
end end

View file

@ -1,9 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::Timelines::TagController < Api::BaseController class Api::V1::Timelines::TagController < Api::V1::Timelines::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: :show, if: :require_auth? before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: :show, if: :require_auth?
before_action :load_tag before_action :load_tag
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
PERMITTED_PARAMS = %i(local limit only_media).freeze
def show def show
cache_if_unauthenticated! cache_if_unauthenticated!
@ -53,27 +54,11 @@ class Api::V1::Timelines::TagController < Api::BaseController
) )
end end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def pagination_params(core_params)
params.slice(:local, :limit, :only_media).permit(:local, :limit, :only_media).merge(core_params)
end
def next_path def next_path
api_v1_timelines_tag_url params[:id], pagination_params(max_id: pagination_max_id) api_v1_timelines_tag_url params[:id], next_path_params
end end
def prev_path def prev_path
api_v1_timelines_tag_url params[:id], pagination_params(min_id: pagination_since_id) api_v1_timelines_tag_url params[:id], prev_path_params
end
def pagination_max_id
@statuses.last.id
end
def pagination_since_id
@statuses.first.id
end end
end end

View file

@ -0,0 +1,27 @@
# frozen_string_literal: true
module Api::ContentSecurityPolicy
extend ActiveSupport::Concern
included do
content_security_policy do |policy|
# Set every directive that does not have a fallback
policy.default_src :none
policy.frame_ancestors :none
policy.form_action :none
# Disable every directive with a fallback to cut on response size
policy.base_uri false
policy.font_src false
policy.img_src false
policy.style_src false
policy.media_src false
policy.frame_src false
policy.manifest_src false
policy.connect_src false
policy.script_src false
policy.child_src false
policy.worker_src false
end
end
end

View file

@ -219,7 +219,7 @@ class ListTimeline extends PureComponent {
</div> </div>
<div className='setting-toggle'> <div className='setting-toggle'>
<Toggle id={`list-${id}-exclusive`} defaultChecked={isExclusive} onChange={this.onExclusiveToggle} /> <Toggle id={`list-${id}-exclusive`} checked={isExclusive} onChange={this.onExclusiveToggle} />
<label htmlFor={`list-${id}-exclusive`} className='setting-toggle__label'> <label htmlFor={`list-${id}-exclusive`} className='setting-toggle__label'>
<FormattedMessage id='lists.exclusive' defaultMessage='Hide these posts from home or STL' /> <FormattedMessage id='lists.exclusive' defaultMessage='Hide these posts from home or STL' />
</label> </label>

View file

@ -14,6 +14,7 @@
"account.badges.group": "Groep", "account.badges.group": "Groep",
"account.block": "Blokkeer @{name}", "account.block": "Blokkeer @{name}",
"account.block_domain": "Blokkeer domein {domain}", "account.block_domain": "Blokkeer domein {domain}",
"account.block_short": "Blokkeer",
"account.blocked": "Geblokkeer", "account.blocked": "Geblokkeer",
"account.browse_more_on_origin_server": "Verken die oorspronklike profiel", "account.browse_more_on_origin_server": "Verken die oorspronklike profiel",
"account.cancel_follow_request": "Herroep volgversoek", "account.cancel_follow_request": "Herroep volgversoek",
@ -45,6 +46,7 @@
"account.posts_with_replies": "Plasings en antwoorde", "account.posts_with_replies": "Plasings en antwoorde",
"account.report": "Rapporteer @{name}", "account.report": "Rapporteer @{name}",
"account.requested": "Wag op goedkeuring. Klik om volgversoek te kanselleer", "account.requested": "Wag op goedkeuring. Klik om volgversoek te kanselleer",
"account.requested_follow": "{name} het versoek om jou te volg",
"account.share": "Deel @{name} se profiel", "account.share": "Deel @{name} se profiel",
"account.show_reblogs": "Wys aangestuurde plasings van @{name}", "account.show_reblogs": "Wys aangestuurde plasings van @{name}",
"account.statuses_counter": "{count, plural, one {{counter} Plaas} other {{counter} Plasings}}", "account.statuses_counter": "{count, plural, one {{counter} Plaas} other {{counter} Plasings}}",
@ -82,6 +84,7 @@
"column.community": "Plaaslike tydlyn", "column.community": "Plaaslike tydlyn",
"column.directory": "Blaai deur profiele", "column.directory": "Blaai deur profiele",
"column.domain_blocks": "Geblokkeerde domeine", "column.domain_blocks": "Geblokkeerde domeine",
"column.favourites": "Gunstelinge",
"column.follow_requests": "Volgversoeke", "column.follow_requests": "Volgversoeke",
"column.home": "Tuis", "column.home": "Tuis",
"column.lists": "Lyste", "column.lists": "Lyste",
@ -271,6 +274,7 @@
"privacy.unlisted.short": "Ongelys", "privacy.unlisted.short": "Ongelys",
"privacy_policy.last_updated": "Laaste bywerking op {date}", "privacy_policy.last_updated": "Laaste bywerking op {date}",
"privacy_policy.title": "Privaatheidsbeleid", "privacy_policy.title": "Privaatheidsbeleid",
"regeneration_indicator.sublabel": "Jou tuis-voer word voorberei!",
"reply_indicator.cancel": "Kanselleer", "reply_indicator.cancel": "Kanselleer",
"report.placeholder": "Type or paste additional comments", "report.placeholder": "Type or paste additional comments",
"report.submit": "Submit report", "report.submit": "Submit report",

View file

@ -201,7 +201,7 @@
"disabled_account_banner.text": "Ваш уліковы запіс {disabledAccount} часова адключаны.", "disabled_account_banner.text": "Ваш уліковы запіс {disabledAccount} часова адключаны.",
"dismissable_banner.community_timeline": "Гэта самыя апошнія допісы ад людзей, уліковыя запісы якіх размяшчаюцца на {domain}.", "dismissable_banner.community_timeline": "Гэта самыя апошнія допісы ад людзей, уліковыя запісы якіх размяшчаюцца на {domain}.",
"dismissable_banner.dismiss": "Адхіліць", "dismissable_banner.dismiss": "Адхіліць",
"dismissable_banner.explore_links": "Гэтыя навіны абмяркоўваюцца прама зараз на гэтым і іншых серверах дэцэнтралізаванай сеткі.", "dismissable_banner.explore_links": "Гэтыя навіны абмяркоўваюцца цяпер на гэтым і іншых серверах дэцэнтралізаванай сеткі.",
"dismissable_banner.explore_statuses": "Допісы з гэтага і іншых сервераў дэцэнтралізаванай сеткі, якія набіраюць папулярнасць прама зараз.", "dismissable_banner.explore_statuses": "Допісы з гэтага і іншых сервераў дэцэнтралізаванай сеткі, якія набіраюць папулярнасць прама зараз.",
"dismissable_banner.explore_tags": "Гэтыя хэштэгі зараз набіраюць папулярнасць сярод людзей на гэтым і іншых серверах дэцэнтралізаванай сеткі", "dismissable_banner.explore_tags": "Гэтыя хэштэгі зараз набіраюць папулярнасць сярод людзей на гэтым і іншых серверах дэцэнтралізаванай сеткі",
"dismissable_banner.public_timeline": "Гэта апошнія публічныя допісы людзей з усей сеткі, за якімі сочаць карыстальнікі {domain}.", "dismissable_banner.public_timeline": "Гэта апошнія публічныя допісы людзей з усей сеткі, за якімі сочаць карыстальнікі {domain}.",
@ -482,7 +482,7 @@
"onboarding.share.lead": "Дайце людзям ведаць, як яны могуць знайсці вас на Mastodon!", "onboarding.share.lead": "Дайце людзям ведаць, як яны могуць знайсці вас на Mastodon!",
"onboarding.share.message": "Я {username} на #Mastodon! Сачыце за мной на {url}", "onboarding.share.message": "Я {username} на #Mastodon! Сачыце за мной на {url}",
"onboarding.share.next_steps": "Магчымыя наступныя крокі:", "onboarding.share.next_steps": "Магчымыя наступныя крокі:",
"onboarding.share.title": "Падзяліцеся сваім профілем", "onboarding.share.title": "Абагульце свой профіль",
"onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:", "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
"onboarding.start.skip": "Want to skip right ahead?", "onboarding.start.skip": "Want to skip right ahead?",
"onboarding.start.title": "Вы зрабілі гэта!", "onboarding.start.title": "Вы зрабілі гэта!",
@ -493,7 +493,7 @@
"onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.", "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
"onboarding.steps.setup_profile.title": "Customize your profile", "onboarding.steps.setup_profile.title": "Customize your profile",
"onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!", "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
"onboarding.steps.share_profile.title": "Share your profile", "onboarding.steps.share_profile.title": "Абагульць ваш профіль у Mastodon",
"onboarding.tips.2fa": "<strong>Ці вы ведаеце?</strong> Вы можаце абараніць свой уліковы запіс, усталяваўшы двухфактарную аўтэнтыфікацыю ў наладах уліковага запісу. Яна працуе з любой праграмай TOTP на ваш выбар, нумар тэлефона не патрэбны!", "onboarding.tips.2fa": "<strong>Ці вы ведаеце?</strong> Вы можаце абараніць свой уліковы запіс, усталяваўшы двухфактарную аўтэнтыфікацыю ў наладах уліковага запісу. Яна працуе з любой праграмай TOTP на ваш выбар, нумар тэлефона не патрэбны!",
"onboarding.tips.accounts_from_other_servers": "<strong>Ці вы ведаеце?</strong> Паколькі Mastodon дэцэнтралізаваны, некаторыя профілі, якія вам трапляюцца, будуць размяшчацца на іншых серверах, адрозных ад вашага. І ўсё ж вы можаце бесперашкодна ўзаемадзейнічаць з імі! Іх сервер пазначаны ў другой палове імя карыстальніка!", "onboarding.tips.accounts_from_other_servers": "<strong>Ці вы ведаеце?</strong> Паколькі Mastodon дэцэнтралізаваны, некаторыя профілі, якія вам трапляюцца, будуць размяшчацца на іншых серверах, адрозных ад вашага. І ўсё ж вы можаце бесперашкодна ўзаемадзейнічаць з імі! Іх сервер пазначаны ў другой палове імя карыстальніка!",
"onboarding.tips.migration": "<strong>Ці вы ведаеце?</strong> Калі вы адчуваеце, што {domain} не з'яўляецца для вас лепшым выбарам у будучыні, вы можаце перайсці на іншы сервер Mastodon, не губляючы сваіх падпісчыкаў. Вы нават можаце стварыць свой уласны сервер!", "onboarding.tips.migration": "<strong>Ці вы ведаеце?</strong> Калі вы адчуваеце, што {domain} не з'яўляецца для вас лепшым выбарам у будучыні, вы можаце перайсці на іншы сервер Mastodon, не губляючы сваіх падпісчыкаў. Вы нават можаце стварыць свой уласны сервер!",

View file

@ -222,6 +222,7 @@
"emoji_button.search_results": "Resultats de la cerca", "emoji_button.search_results": "Resultats de la cerca",
"emoji_button.symbols": "Símbols", "emoji_button.symbols": "Símbols",
"emoji_button.travel": "Viatges i llocs", "emoji_button.travel": "Viatges i llocs",
"empty_column.account_hides_collections": "Aquest usuari ha elegit no mostrar aquesta informació",
"empty_column.account_suspended": "Compte suspès", "empty_column.account_suspended": "Compte suspès",
"empty_column.account_timeline": "No hi ha tuts aquí!", "empty_column.account_timeline": "No hi ha tuts aquí!",
"empty_column.account_unavailable": "Perfil no disponible", "empty_column.account_unavailable": "Perfil no disponible",

View file

@ -222,6 +222,7 @@
"emoji_button.search_results": "Sykresultaten", "emoji_button.search_results": "Sykresultaten",
"emoji_button.symbols": "Symboalen", "emoji_button.symbols": "Symboalen",
"emoji_button.travel": "Reizgje en lokaasjes", "emoji_button.travel": "Reizgje en lokaasjes",
"empty_column.account_hides_collections": "Dizze brûker hat derfoar keazen dizze ynformaasje net beskikber te meitsjen",
"empty_column.account_suspended": "Account beskoattele", "empty_column.account_suspended": "Account beskoattele",
"empty_column.account_timeline": "Hjir binne gjin berjochten!", "empty_column.account_timeline": "Hjir binne gjin berjochten!",
"empty_column.account_unavailable": "Profyl net beskikber", "empty_column.account_unavailable": "Profyl net beskikber",

View file

@ -41,6 +41,8 @@
"account.languages": "Keisti prenumeruojamas kalbas", "account.languages": "Keisti prenumeruojamas kalbas",
"account.locked_info": "Šios paskyros privatumo būsena nustatyta kaip užrakinta. Savininkas (-ė) rankiniu būdu peržiūri, kas gali sekti.", "account.locked_info": "Šios paskyros privatumo būsena nustatyta kaip užrakinta. Savininkas (-ė) rankiniu būdu peržiūri, kas gali sekti.",
"account.media": "Medija", "account.media": "Medija",
"account.mention": "Paminėti @{name}",
"account.moved_to": "{name} nurodė, kad dabar jų nauja paskyra yra:",
"account.mute": "Užtildyti @{name}", "account.mute": "Užtildyti @{name}",
"account.muted": "Užtildytas", "account.muted": "Užtildytas",
"account.posts": "Toots", "account.posts": "Toots",
@ -53,10 +55,15 @@
"account.unfollow": "Nebesekti", "account.unfollow": "Nebesekti",
"account.unmute_short": "Atitildyti", "account.unmute_short": "Atitildyti",
"account_note.placeholder": "Click to add a note", "account_note.placeholder": "Click to add a note",
"alert.unexpected.title": "Oi!", "alert.unexpected.message": "Įvyko netikėta klaida.",
"alert.unexpected.title": "Ups!",
"announcement.announcement": "Skelbimas", "announcement.announcement": "Skelbimas",
"attachments_list.unprocessed": "(neapdorotas)",
"audio.hide": "Slėpti garsą", "audio.hide": "Slėpti garsą",
"autosuggest_hashtag.per_week": "{count} per savaitę", "autosuggest_hashtag.per_week": "{count} per savaitę",
"boost_modal.combo": "Gali spausti {combo}, kad praleisti kitą kartą",
"bundle_column_error.copy_stacktrace": "Kopijuoti klaidos ataskaitą",
"bundle_column_error.error.body": "Užklausos puslapio nepavyko atvaizduoti. Tai gali būti dėl mūsų kodo klaidos arba naršyklės suderinamumo problemos.",
"bundle_column_error.error.title": "O, ne!", "bundle_column_error.error.title": "O, ne!",
"column.domain_blocks": "Hidden domains", "column.domain_blocks": "Hidden domains",
"column.lists": "Sąrašai", "column.lists": "Sąrašai",

View file

@ -222,6 +222,7 @@
"emoji_button.search_results": "Rezultati iskanja", "emoji_button.search_results": "Rezultati iskanja",
"emoji_button.symbols": "Simboli", "emoji_button.symbols": "Simboli",
"emoji_button.travel": "Potovanja in kraji", "emoji_button.travel": "Potovanja in kraji",
"empty_column.account_hides_collections": "Ta uporabnik se je odločil, da te informacije ne bo dal na voljo",
"empty_column.account_suspended": "Račun je suspendiran", "empty_column.account_suspended": "Račun je suspendiran",
"empty_column.account_timeline": "Tukaj ni objav!", "empty_column.account_timeline": "Tukaj ni objav!",
"empty_column.account_unavailable": "Profil ni na voljo", "empty_column.account_unavailable": "Profil ni na voljo",

View file

@ -222,6 +222,7 @@
"emoji_button.search_results": "Rezultati pretrage", "emoji_button.search_results": "Rezultati pretrage",
"emoji_button.symbols": "Simboli", "emoji_button.symbols": "Simboli",
"emoji_button.travel": "Putovanja i mesta", "emoji_button.travel": "Putovanja i mesta",
"empty_column.account_hides_collections": "Ovaj korisnik je odlučio da ove informacije ne učini dostupnim",
"empty_column.account_suspended": "Nalog je suspendovan", "empty_column.account_suspended": "Nalog je suspendovan",
"empty_column.account_timeline": "Nema objava ovde!", "empty_column.account_timeline": "Nema objava ovde!",
"empty_column.account_unavailable": "Profil je nedostupan", "empty_column.account_unavailable": "Profil je nedostupan",

View file

@ -222,6 +222,7 @@
"emoji_button.search_results": "Резултати претраге", "emoji_button.search_results": "Резултати претраге",
"emoji_button.symbols": "Симболи", "emoji_button.symbols": "Симболи",
"emoji_button.travel": "Путовања и места", "emoji_button.travel": "Путовања и места",
"empty_column.account_hides_collections": "Овај корисник је одлучио да ове информације не учини доступним",
"empty_column.account_suspended": "Налог је суспендован", "empty_column.account_suspended": "Налог је суспендован",
"empty_column.account_timeline": "Нема објава овде!", "empty_column.account_timeline": "Нема објава овде!",
"empty_column.account_unavailable": "Профил је недоступан", "empty_column.account_unavailable": "Профил је недоступан",

View file

@ -39,7 +39,7 @@ class Tag < ApplicationRecord
HASHTAG_RE = %r{(?<![=/)\w])#(#{HASHTAG_NAME_PAT})}i HASHTAG_RE = %r{(?<![=/)\w])#(#{HASHTAG_NAME_PAT})}i
HASHTAG_NAME_RE = /\A(#{HASHTAG_NAME_PAT})\z/i HASHTAG_NAME_RE = /\A(#{HASHTAG_NAME_PAT})\z/i
HASHTAG_INVALID_CHARS_RE = /[^[:alnum:]#{HASHTAG_SEPARATORS}]/ HASHTAG_INVALID_CHARS_RE = /[^[:alnum:]\u0E47-\u0E4E#{HASHTAG_SEPARATORS}]/
validates :name, presence: true, format: { with: HASHTAG_NAME_RE } validates :name, presence: true, format: { with: HASHTAG_NAME_RE }
validates :display_name, format: { with: HASHTAG_NAME_RE } validates :display_name, format: { with: HASHTAG_NAME_RE }

View file

@ -53,3 +53,7 @@ af:
position: position:
elevated: kan nie hoër as jou huidige rol wees nie elevated: kan nie hoër as jou huidige rol wees nie
own_role: kan nie verander word met jou huidige rol nie own_role: kan nie verander word met jou huidige rol nie
webhook:
attributes:
events:
invalid_permissions: geleenthede waartoe jy nie toegang het nie mag nie ingesluit word nie

View file

@ -5,7 +5,23 @@ af:
contact_unavailable: NVT contact_unavailable: NVT
hosted_on: Mastodon gehuisves op %{domain} hosted_on: Mastodon gehuisves op %{domain}
title: Aangaande title: Aangaande
accounts:
follow: Volg
followers:
one: Volgeling
other: Volgelinge
following: Volg
nothing_here: Daar is niks hier nie!
posts:
one: Plasing
other: Plasings
posts_tab_heading: Plasings
admin: admin:
account_actions:
action: Voer aksie uit
title: Voer modereer aksie uit op %{acct}
account_moderation_notes:
create: Los nota
accounts: accounts:
location: location:
local: Plaaslik local: Plaaslik
@ -102,6 +118,7 @@ af:
types: types:
bookmarks: Boekmerke bookmarks: Boekmerke
invites: invites:
invalid: Hierdie uitnodiging is nie geldig nie
title: Nooi ander title: Nooi ander
login_activities: login_activities:
description_html: Indien jy onbekende aktiwiteite gewaar, oorweeg dit om jou wagwoord te verander en tweefaktorverifikasie te aktiveer. description_html: Indien jy onbekende aktiwiteite gewaar, oorweeg dit om jou wagwoord te verander en tweefaktorverifikasie te aktiveer.

View file

@ -1418,6 +1418,7 @@ be:
'86400': 1 дзень '86400': 1 дзень
expires_in_prompt: Ніколі expires_in_prompt: Ніколі
generate: Стварыць запрашальную спасылку generate: Стварыць запрашальную спасылку
invalid: Гэта запрашэнне несапраўднае
invited_by: 'Вас запрасіў(-ла):' invited_by: 'Вас запрасіў(-ла):'
max_uses: max_uses:
few: "%{count} выкарыстанні" few: "%{count} выкарыстанні"

View file

@ -1358,6 +1358,7 @@ ca:
'86400': 1 dia '86400': 1 dia
expires_in_prompt: Mai expires_in_prompt: Mai
generate: Genera generate: Genera
invalid: Aquesta invitació no és vàlida
invited_by: 'Has estat invitat per:' invited_by: 'Has estat invitat per:'
max_uses: max_uses:
one: 1 ús one: 1 ús

View file

@ -1468,6 +1468,7 @@ cy:
'86400': 1 diwrnod '86400': 1 diwrnod
expires_in_prompt: Byth expires_in_prompt: Byth
generate: Cynhyrchu dolen wahoddiad generate: Cynhyrchu dolen wahoddiad
invalid: Nid yw'r gwahoddiad hwn yn ddilys
invited_by: 'Cawsoch eich gwahodd gan:' invited_by: 'Cawsoch eich gwahodd gan:'
max_uses: max_uses:
few: "%{count} defnydd" few: "%{count} defnydd"

View file

@ -1110,6 +1110,7 @@ da:
functional: Din konto er fuldt funktionel. functional: Din konto er fuldt funktionel.
pending: Din ansøgning afventer gennemgang af vores medarbejdere. Dette kan tage noget tid. Du modtager en e-mail, hvis din ansøgning godkendes. pending: Din ansøgning afventer gennemgang af vores medarbejdere. Dette kan tage noget tid. Du modtager en e-mail, hvis din ansøgning godkendes.
redirecting_to: Din konto er inaktiv, da den pt. er omdirigerer til %{acct}. redirecting_to: Din konto er inaktiv, da den pt. er omdirigerer til %{acct}.
self_destruct: Da %{domain} er under nedlukning, vil kontoadgangen være begrænset.
view_strikes: Se tidligere anmeldelser af din konto view_strikes: Se tidligere anmeldelser af din konto
too_fast: Formularen indsendt for hurtigt, forsøg igen. too_fast: Formularen indsendt for hurtigt, forsøg igen.
use_security_key: Brug sikkerhedsnøgle use_security_key: Brug sikkerhedsnøgle
@ -1367,6 +1368,7 @@ da:
'86400': 1 dag '86400': 1 dag
expires_in_prompt: Aldrig expires_in_prompt: Aldrig
generate: Generér invitationslink generate: Generér invitationslink
invalid: Denne invitation er ikke gyldig
invited_by: 'Du blev inviteret af:' invited_by: 'Du blev inviteret af:'
max_uses: max_uses:
one: 1 benyttelse one: 1 benyttelse
@ -1579,6 +1581,9 @@ da:
over_daily_limit: Den daglige grænse på %{limit} planlagte indlæg er nået over_daily_limit: Den daglige grænse på %{limit} planlagte indlæg er nået
over_total_limit: Grænsen på %{limit} planlagte indlæg er nået over_total_limit: Grænsen på %{limit} planlagte indlæg er nået
too_soon: Den planlagte dato skal være i fremtiden too_soon: Den planlagte dato skal være i fremtiden
self_destruct:
lead_html: Desværre lukker <strong>%{domain}</strong> permanent. Har man en konto dér, vil fortsat brug heraf ikke være mulig. Man kan dog stadig anmode om en sikkerhedskopi af sine data.
title: Denne server er under nedlukning
sessions: sessions:
activity: Seneste aktivitet activity: Seneste aktivitet
browser: Browser browser: Browser

View file

@ -1368,6 +1368,7 @@ de:
'86400': 1 Tag '86400': 1 Tag
expires_in_prompt: Nie expires_in_prompt: Nie
generate: Einladungslink erstellen generate: Einladungslink erstellen
invalid: Diese Einladung ist ungültig
invited_by: 'Du wurdest eingeladen von:' invited_by: 'Du wurdest eingeladen von:'
max_uses: max_uses:
one: Eine Verwendung one: Eine Verwendung

View file

@ -18,7 +18,7 @@ be:
unconfirmed: Вы павінны пацвердзіць свой адрас электроннай пошты, перш чым працягнуць unconfirmed: Вы павінны пацвердзіць свой адрас электроннай пошты, перш чым працягнуць
mailer: mailer:
confirmation_instructions: confirmation_instructions:
action: Пацвердзіце адрас электроннай пошты action: Пацвердзіць адрас электроннай пошты
action_with_app: Пацвердзіць і вярнуцца да %{app} action_with_app: Пацвердзіць і вярнуцца да %{app}
explanation: Вы стварылі ўліковы запіс на %{host} з гэтым адрасам электроннай пошты. Вам спатрэбіцца ўсяго адзін клік, каб пацвердзіць яго. Калі гэта былі не вы, то проста праігнаруйце гэты ліст. explanation: Вы стварылі ўліковы запіс на %{host} з гэтым адрасам электроннай пошты. Вам спатрэбіцца ўсяго адзін клік, каб пацвердзіць яго. Калі гэта былі не вы, то проста праігнаруйце гэты ліст.
explanation_when_pending: Вы падалі заяўку на запрашэнне на %{host} з гэтым адрасам электроннай пошты. Як толькі вы пацвердзіце свой адрас электроннай пошты, мы разгледзім вашу заяўку. Вы можаце ўвайсці, каб змяніць свае дадзеныя або выдаліць свой уліковы запіс, але вы не можаце атрымаць доступ да большасці функцый, пакуль ваш уліковы запіс не будзе зацверджаны. Калі ваша заяўка будзе адхілена, вашы даныя будуць выдалены, таму ад вас не спатрэбіцца ніякіх дадатковых дзеянняў. Калі гэта былі не вы, ігнаруйце гэты ліст explanation_when_pending: Вы падалі заяўку на запрашэнне на %{host} з гэтым адрасам электроннай пошты. Як толькі вы пацвердзіце свой адрас электроннай пошты, мы разгледзім вашу заяўку. Вы можаце ўвайсці, каб змяніць свае дадзеныя або выдаліць свой уліковы запіс, але вы не можаце атрымаць доступ да большасці функцый, пакуль ваш уліковы запіс не будзе зацверджаны. Калі ваша заяўка будзе адхілена, вашы даныя будуць выдалены, таму ад вас не спатрэбіцца ніякіх дадатковых дзеянняў. Калі гэта былі не вы, ігнаруйце гэты ліст

View file

@ -149,6 +149,7 @@ af:
write:blocks: blokkeer rekeninge en domeine write:blocks: blokkeer rekeninge en domeine
write:bookmarks: laat n boekmerk by plasings write:bookmarks: laat n boekmerk by plasings
write:conversations: doof en wis gesprekke uit write:conversations: doof en wis gesprekke uit
write:favourites: gunsteling plasings
write:filters: skep filters write:filters: skep filters
write:follows: volg mense write:follows: volg mense
write:lists: skep lyste write:lists: skep lyste

View file

@ -1368,6 +1368,7 @@ es-AR:
'86400': 1 día '86400': 1 día
expires_in_prompt: Nunca expires_in_prompt: Nunca
generate: Generar enlace de invitación generate: Generar enlace de invitación
invalid: Esta invitación no es válida
invited_by: 'Fuiste invitado por:' invited_by: 'Fuiste invitado por:'
max_uses: max_uses:
one: 1 uso one: 1 uso

View file

@ -1368,6 +1368,7 @@ es-MX:
'86400': 1 día '86400': 1 día
expires_in_prompt: Nunca expires_in_prompt: Nunca
generate: Generar generate: Generar
invalid: Esta invitación no es válida
invited_by: 'Fuiste invitado por:' invited_by: 'Fuiste invitado por:'
max_uses: max_uses:
one: 1 uso one: 1 uso

View file

@ -1368,6 +1368,7 @@ es:
'86400': 1 día '86400': 1 día
expires_in_prompt: Nunca expires_in_prompt: Nunca
generate: Generar generate: Generar
invalid: Esta invitación no es válida
invited_by: 'Fuiste invitado por:' invited_by: 'Fuiste invitado por:'
max_uses: max_uses:
one: 1 uso one: 1 uso

View file

@ -1363,6 +1363,7 @@ eu:
'86400': Egun 1 '86400': Egun 1
expires_in_prompt: Inoiz ez expires_in_prompt: Inoiz ez
generate: Sortu generate: Sortu
invalid: Gonbidapen hau ez da baliozkoa
invited_by: 'Honek gonbidatu zaitu:' invited_by: 'Honek gonbidatu zaitu:'
max_uses: max_uses:
one: Erabilera 1 one: Erabilera 1

View file

@ -1157,6 +1157,7 @@ fa:
'86400': ۱ روز '86400': ۱ روز
expires_in_prompt: هیچ وقت expires_in_prompt: هیچ وقت
generate: ساختن generate: ساختن
invalid: این دعوت‌نامه معتبر نیست
invited_by: 'دعوت‌کنندهٔ شما:' invited_by: 'دعوت‌کنندهٔ شما:'
max_uses: max_uses:
one: ۱ بار one: ۱ بار

View file

@ -1368,6 +1368,7 @@ fo:
'86400': 1 dag '86400': 1 dag
expires_in_prompt: Ongantíð expires_in_prompt: Ongantíð
generate: Ger innbjóðingarleinki generate: Ger innbjóðingarleinki
invalid: Henda innbjóðing er ikki gildug
invited_by: 'Tú var bjóðað/ur av:' invited_by: 'Tú var bjóðað/ur av:'
max_uses: max_uses:
one: 1 brúk one: 1 brúk

View file

@ -1368,6 +1368,7 @@ fr-QC:
'86400': 1 jour '86400': 1 jour
expires_in_prompt: Jamais expires_in_prompt: Jamais
generate: Générer un lien d'invitation generate: Générer un lien d'invitation
invalid: Cette invitation nest pas valide
invited_by: 'Vous avez été invité·e par:' invited_by: 'Vous avez été invité·e par:'
max_uses: max_uses:
one: 1 utilisation one: 1 utilisation

View file

@ -1368,6 +1368,7 @@ fr:
'86400': 1 jour '86400': 1 jour
expires_in_prompt: Jamais expires_in_prompt: Jamais
generate: Générer un lien d'invitation generate: Générer un lien d'invitation
invalid: Cette invitation nest pas valide
invited_by: 'Vous avez été invité·e par:' invited_by: 'Vous avez été invité·e par:'
max_uses: max_uses:
one: 1 utilisation one: 1 utilisation

View file

@ -1368,6 +1368,7 @@ fy:
'86400': 1 dei '86400': 1 dei
expires_in_prompt: Nea expires_in_prompt: Nea
generate: Utnûgingskeppeling generearje generate: Utnûgingskeppeling generearje
invalid: Dizze útnûging is net jildich
invited_by: 'Jo binne útnûge troch:' invited_by: 'Jo binne útnûge troch:'
max_uses: max_uses:
one: 1 kear one: 1 kear

View file

@ -1368,6 +1368,7 @@ gl:
'86400': 1 día '86400': 1 día
expires_in_prompt: Nunca expires_in_prompt: Nunca
generate: Xerar generate: Xerar
invalid: Este convite non é válido
invited_by: 'Convidoute:' invited_by: 'Convidoute:'
max_uses: max_uses:
one: 1 uso one: 1 uso

View file

@ -113,8 +113,8 @@ he:
previous_strikes_description_html: previous_strikes_description_html:
many: לחשבון הזה יש <strong>%{count}</strong> פסילות. many: לחשבון הזה יש <strong>%{count}</strong> פסילות.
one: לחשבון הזה פסילה <strong>אחת</strong>. one: לחשבון הזה פסילה <strong>אחת</strong>.
other: לחשבון הזה <strong>%{count}</strong> פסילות. other: לחשבון הזה יש <strong>%{count}</strong> פסילות.
two: לחשבון הזה <strong>%{count}</strong> פסילות. two: לחשבון הזה יש <strong>שתי</strong> פסילות.
promote: להעלות בדרגה promote: להעלות בדרגה
protocol: פרטיכל protocol: פרטיכל
public: פומבי public: פומבי
@ -1418,6 +1418,7 @@ he:
'86400': יום אחד '86400': יום אחד
expires_in_prompt: לעולם לא expires_in_prompt: לעולם לא
generate: יצירת קישור להזמנה generate: יצירת קישור להזמנה
invalid: הזמנה זו אינה תקפה
invited_by: הוזמנת ע"י invited_by: הוזמנת ע"י
max_uses: max_uses:
many: "%{count} שימושים" many: "%{count} שימושים"

View file

@ -1368,6 +1368,7 @@ hu:
'86400': 1 nap '86400': 1 nap
expires_in_prompt: Soha expires_in_prompt: Soha
generate: Generálás generate: Generálás
invalid: Ez a meghívó nem érvényes
invited_by: 'Téged meghívott:' invited_by: 'Téged meghívott:'
max_uses: max_uses:
one: 1 használat one: 1 használat

View file

@ -1372,6 +1372,7 @@ is:
'86400': 1 dagur '86400': 1 dagur
expires_in_prompt: Aldrei expires_in_prompt: Aldrei
generate: Útbúa boðstengil generate: Útbúa boðstengil
invalid: Þetta boð er ekki gilt
invited_by: 'Þér var boðið af:' invited_by: 'Þér var boðið af:'
max_uses: max_uses:
one: 1 afnot one: 1 afnot

View file

@ -1370,6 +1370,7 @@ it:
'86400': 1 giorno '86400': 1 giorno
expires_in_prompt: Mai expires_in_prompt: Mai
generate: Genera generate: Genera
invalid: Questo invito non è valido
invited_by: 'Sei stato invitato da:' invited_by: 'Sei stato invitato da:'
max_uses: max_uses:
one: un uso one: un uso

View file

@ -1345,6 +1345,7 @@ ko:
'86400': 1 '86400': 1
expires_in_prompt: 영원히 expires_in_prompt: 영원히
generate: 초대 링크 생성하기 generate: 초대 링크 생성하기
invalid: 이 초대는 올바르지 않습니다
invited_by: '당신을 초대한 사람:' invited_by: '당신을 초대한 사람:'
max_uses: max_uses:
other: "%{count}회" other: "%{count}회"

View file

@ -383,6 +383,7 @@ lt:
'86400': 1 dienos '86400': 1 dienos
expires_in_prompt: Niekada expires_in_prompt: Niekada
generate: Generuoti generate: Generuoti
invalid: Šis kvietimas negalioja.
invited_by: 'Jus pakvietė:' invited_by: 'Jus pakvietė:'
max_uses: max_uses:
few: "%{count} panaudojimai" few: "%{count} panaudojimai"

View file

@ -1368,6 +1368,7 @@ nl:
'86400': 1 dag '86400': 1 dag
expires_in_prompt: Nooit expires_in_prompt: Nooit
generate: Uitnodigingslink genereren generate: Uitnodigingslink genereren
invalid: Deze uitnodiging is niet geldig
invited_by: 'Jij bent uitgenodigd door:' invited_by: 'Jij bent uitgenodigd door:'
max_uses: max_uses:
one: 1 keer one: 1 keer

View file

@ -1368,6 +1368,7 @@ nn:
'86400': 1 dag '86400': 1 dag
expires_in_prompt: Aldri expires_in_prompt: Aldri
generate: Lag innbydingslenkje generate: Lag innbydingslenkje
invalid: Denne invitasjonen er ikkje gyldig
invited_by: 'Du vart innboden av:' invited_by: 'Du vart innboden av:'
max_uses: max_uses:
one: 1 bruk one: 1 bruk

View file

@ -1368,6 +1368,7 @@
'86400': 1 dag '86400': 1 dag
expires_in_prompt: Aldri expires_in_prompt: Aldri
generate: Generer generate: Generer
invalid: Denne invitasjonen er ikke gyldig
invited_by: 'Du ble invitert av:' invited_by: 'Du ble invitert av:'
max_uses: max_uses:
one: 1 bruk one: 1 bruk

View file

@ -1418,6 +1418,7 @@ pl:
'86400': dobie '86400': dobie
expires_in_prompt: Nigdy expires_in_prompt: Nigdy
generate: Wygeneruj generate: Wygeneruj
invalid: Niepoprawne zaproszenie
invited_by: 'Zostałeś(-aś) zaproszony(-a) przez:' invited_by: 'Zostałeś(-aś) zaproszony(-a) przez:'
max_uses: max_uses:
few: "%{count} użycia" few: "%{count} użycia"

View file

@ -1368,6 +1368,7 @@ pt-BR:
'86400': 1 dia '86400': 1 dia
expires_in_prompt: Nunca expires_in_prompt: Nunca
generate: Gerar convite generate: Gerar convite
invalid: Este convite não é válido
invited_by: 'Você recebeu convite de:' invited_by: 'Você recebeu convite de:'
max_uses: max_uses:
one: 1 uso one: 1 uso

View file

@ -1368,6 +1368,7 @@ pt-PT:
'86400': 1 dia '86400': 1 dia
expires_in_prompt: Nunca expires_in_prompt: Nunca
generate: Gerar hiperligação de convite generate: Gerar hiperligação de convite
invalid: Este convite não é válido
invited_by: 'Foi convidado por:' invited_by: 'Foi convidado por:'
max_uses: max_uses:
one: 1 uso one: 1 uso

View file

@ -53,7 +53,7 @@ be:
password: Не менш за 8 сімвалаў password: Не менш за 8 сімвалаў
phrase: Параўнанне адбудзецца нягледзячы на рэгістр тэксту і папярэджанні аб змесціве допісу phrase: Параўнанне адбудзецца нягледзячы на рэгістр тэксту і папярэджанні аб змесціве допісу
scopes: Якімі API праграм будзе дазволена карыстацца. Калі вы абярэце найвышэйшы ўзровень, не трэба абіраць асобныя. scopes: Якімі API праграм будзе дазволена карыстацца. Калі вы абярэце найвышэйшы ўзровень, не трэба абіраць асобныя.
setting_aggregate_reblogs: Не паказваць новыя пашырэнні для допісаў, якія нядаўна пашырылі(уплывае выключна на будучыя пашырэнні) setting_aggregate_reblogs: Не паказваць новыя пашырэнні для допісаў, якія пашырылі нядаўна (закранае толькі нядаўнія пашырэнні)
setting_always_send_emails: Звычайна лісты з апавяшчэннямі не будуць дасылацца, калі вы актыўна карыстаецеся Mastodon setting_always_send_emails: Звычайна лісты з апавяшчэннямі не будуць дасылацца, калі вы актыўна карыстаецеся Mastodon
setting_default_sensitive: Далікатныя медыя прадвызначана схаваныя. Іх можна адкрыць адзіным клікам setting_default_sensitive: Далікатныя медыя прадвызначана схаваныя. Іх можна адкрыць адзіным клікам
setting_display_media_default: Хаваць медыя пазначаныя як далікатныя setting_display_media_default: Хаваць медыя пазначаныя як далікатныя

View file

@ -592,6 +592,8 @@ sk:
title: Ohľadom title: Ohľadom
appearance: appearance:
title: Vzhľad title: Vzhľad
content_retention:
title: Ponechanie obsahu
discovery: discovery:
follow_recommendations: Odporúčania pre nasledovanie follow_recommendations: Odporúčania pre nasledovanie
profile_directory: Katalóg profilov profile_directory: Katalóg profilov
@ -616,6 +618,7 @@ sk:
delete: Vymaž nahratý súbor delete: Vymaž nahratý súbor
destroyed_msg: Nahratie bolo zo stránky úspešne vymazané! destroyed_msg: Nahratie bolo zo stránky úspešne vymazané!
software_updates: software_updates:
critical_update: Kritické — prosím aktualizuj rýchlo
documentation_link: Zisti viac documentation_link: Zisti viac
title: Dostupné aktualizácie title: Dostupné aktualizácie
types: types:
@ -646,6 +649,10 @@ sk:
appeal_approved: Namietnuté appeal_approved: Namietnuté
appeal_rejected: Námietka zamietnutá appeal_rejected: Námietka zamietnutá
system_checks: system_checks:
elasticsearch_preset:
action: Pozri dokumentáciu
elasticsearch_preset_single_node:
action: Pozri dokumentáciu
rules_check: rules_check:
action: Spravuj serverové pravidlá action: Spravuj serverové pravidlá
message_html: Neurčil/a si žiadne serverové pravidlá. message_html: Neurčil/a si žiadne serverové pravidlá.
@ -925,6 +932,7 @@ sk:
'86400': 1 deň '86400': 1 deň
expires_in_prompt: Nikdy expires_in_prompt: Nikdy
generate: Vygeneruj generate: Vygeneruj
invalid: Táto pozvánka je neplatná
invited_by: 'Bol/a si pozvaný/á užívateľom:' invited_by: 'Bol/a si pozvaný/á užívateľom:'
max_uses: max_uses:
few: "%{count} využití" few: "%{count} využití"

View file

@ -1418,6 +1418,7 @@ sl:
'86400': 1 dan '86400': 1 dan
expires_in_prompt: Nikoli expires_in_prompt: Nikoli
generate: Ustvari generate: Ustvari
invalid: To povabilo ni veljavno
invited_by: 'Povabil/a vas je:' invited_by: 'Povabil/a vas je:'
max_uses: max_uses:
few: "%{count} uporabe" few: "%{count} uporabe"

View file

@ -1362,6 +1362,7 @@ sq:
'86400': 1 ditë '86400': 1 ditë
expires_in_prompt: Kurrë expires_in_prompt: Kurrë
generate: Prodho lidhje ftese generate: Prodho lidhje ftese
invalid: Kjo ftesë sështë e vlefshme
invited_by: 'Qetë ftuar nga:' invited_by: 'Qetë ftuar nga:'
max_uses: max_uses:
one: 1 përdorim one: 1 përdorim

View file

@ -1393,6 +1393,7 @@ sr-Latn:
'86400': 1 dan '86400': 1 dan
expires_in_prompt: Nikad expires_in_prompt: Nikad
generate: Generiši generate: Generiši
invalid: Ova pozivnica nije važeća
invited_by: 'Pozvao Vas je:' invited_by: 'Pozvao Vas je:'
max_uses: max_uses:
few: "%{count} korišćenja" few: "%{count} korišćenja"

View file

@ -1393,6 +1393,7 @@ sr:
'86400': 1 дан '86400': 1 дан
expires_in_prompt: Никад expires_in_prompt: Никад
generate: Генериши generate: Генериши
invalid: Ова позивница није важећа
invited_by: 'Позвао Вас је:' invited_by: 'Позвао Вас је:'
max_uses: max_uses:
few: "%{count} коришћења" few: "%{count} коришћења"

View file

@ -1368,6 +1368,7 @@ sv:
'86400': 1 dag '86400': 1 dag
expires_in_prompt: Aldrig expires_in_prompt: Aldrig
generate: Skapa generate: Skapa
invalid: Ogiltig inbjudan
invited_by: 'Du blev inbjuden av:' invited_by: 'Du blev inbjuden av:'
max_uses: max_uses:
one: 1 användning one: 1 användning

View file

@ -1343,6 +1343,7 @@ th:
'86400': 1 วัน '86400': 1 วัน
expires_in_prompt: ไม่เลย expires_in_prompt: ไม่เลย
generate: สร้างลิงก์เชิญ generate: สร้างลิงก์เชิญ
invalid: คำเชิญนี้ไม่ถูกต้อง
invited_by: 'คุณได้รับเชิญโดย:' invited_by: 'คุณได้รับเชิญโดย:'
max_uses: max_uses:
other: "%{count} การใช้งาน" other: "%{count} การใช้งาน"

View file

@ -1368,6 +1368,7 @@ tr:
'86400': 1 gün '86400': 1 gün
expires_in_prompt: Asla expires_in_prompt: Asla
generate: Davet bağlantısı oluştur generate: Davet bağlantısı oluştur
invalid: Bu davet geçerli değil
invited_by: 'Davet edildiniz:' invited_by: 'Davet edildiniz:'
max_uses: max_uses:
one: 1 kullanım one: 1 kullanım

View file

@ -1418,6 +1418,7 @@ uk:
'86400': 1 день '86400': 1 день
expires_in_prompt: Ніколи expires_in_prompt: Ніколи
generate: Згенерувати generate: Згенерувати
invalid: Це запрошення не дійсне
invited_by: 'Вас запросив:' invited_by: 'Вас запросив:'
max_uses: max_uses:
few: "%{count} використання" few: "%{count} використання"

View file

@ -1343,6 +1343,7 @@ zh-CN:
'86400': 1 天后 '86400': 1 天后
expires_in_prompt: 永不过期 expires_in_prompt: 永不过期
generate: 生成邀请链接 generate: 生成邀请链接
invalid: 此邀请无效
invited_by: 你的邀请人是: invited_by: 你的邀请人是:
max_uses: max_uses:
other: "%{count} 次" other: "%{count} 次"

View file

@ -1343,6 +1343,7 @@ zh-HK:
'86400': 1 天後 '86400': 1 天後
expires_in_prompt: 永不過期 expires_in_prompt: 永不過期
generate: 生成邀請連結 generate: 生成邀請連結
invalid: 此邀請無效
invited_by: 你的邀請人是: invited_by: 你的邀請人是:
max_uses: max_uses:
other: "%{count} 次" other: "%{count} 次"

View file

@ -1345,6 +1345,7 @@ zh-TW:
'86400': 1 天後 '86400': 1 天後
expires_in_prompt: 永不過期 expires_in_prompt: 永不過期
generate: 建立邀請連結 generate: 建立邀請連結
invalid: 此邀請是無效的
invited_by: 您的邀請人是: invited_by: 您的邀請人是:
max_uses: max_uses:
other: "%{count} 則" other: "%{count} 則"

View file

@ -1,11 +1,13 @@
{ {
"name": "@mastodon/mastodon", "name": "@mastodon/mastodon",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"packageManager": "yarn@4.0.2",
"engines": { "engines": {
"node": ">=18" "node": ">=18"
}, },
"workspaces": [ "workspaces": [
"." ".",
"streaming"
], ],
"scripts": { "scripts": {
"build:development": "cross-env RAILS_ENV=development NODE_ENV=development ./bin/webpack", "build:development": "cross-env RAILS_ENV=development NODE_ENV=development ./bin/webpack",
@ -76,10 +78,8 @@
"css-loader": "^5.2.7", "css-loader": "^5.2.7",
"cssnano": "^6.0.1", "cssnano": "^6.0.1",
"detect-passive-events": "^2.0.3", "detect-passive-events": "^2.0.3",
"dotenv": "^16.0.3",
"emoji-mart": "npm:emoji-mart-lazyload@latest", "emoji-mart": "npm:emoji-mart-lazyload@latest",
"escape-html": "^1.0.3", "escape-html": "^1.0.3",
"express": "^4.18.2",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
"fuzzysort": "^2.0.4", "fuzzysort": "^2.0.4",
@ -90,21 +90,15 @@
"immutable": "^4.3.0", "immutable": "^4.3.0",
"imports-loader": "^1.2.0", "imports-loader": "^1.2.0",
"intl-messageformat": "^10.3.5", "intl-messageformat": "^10.3.5",
"ioredis": "^5.3.2",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"jsdom": "^22.1.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mark-loader": "^0.1.6", "mark-loader": "^0.1.6",
"marky": "^1.2.5", "marky": "^1.2.5",
"mini-css-extract-plugin": "^1.6.2", "mini-css-extract-plugin": "^1.6.2",
"mkdirp": "^3.0.1", "mkdirp": "^3.0.1",
"npmlog": "^7.0.1",
"path-complete-extname": "^1.0.0", "path-complete-extname": "^1.0.0",
"pg": "^8.5.0",
"pg-connection-string": "^2.6.0",
"postcss": "^8.4.24", "postcss": "^8.4.24",
"postcss-loader": "^4.3.0", "postcss-loader": "^4.3.0",
"prom-client": "^15.0.0",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"punycode": "^2.3.0", "punycode": "^2.3.0",
"react": "^18.2.0", "react": "^18.2.0",
@ -143,7 +137,6 @@
"tesseract.js": "^2.1.5", "tesseract.js": "^2.1.5",
"tiny-queue": "^0.2.1", "tiny-queue": "^0.2.1",
"twitter-text": "3.1.0", "twitter-text": "3.1.0",
"uuid": "^9.0.0",
"webpack": "^4.47.0", "webpack": "^4.47.0",
"webpack-assets-manifest": "^4.0.6", "webpack-assets-manifest": "^4.0.6",
"webpack-bundle-analyzer": "^4.8.0", "webpack-bundle-analyzer": "^4.8.0",
@ -155,8 +148,7 @@
"workbox-routing": "^7.0.0", "workbox-routing": "^7.0.0",
"workbox-strategies": "^7.0.0", "workbox-strategies": "^7.0.0",
"workbox-webpack-plugin": "^7.0.0", "workbox-webpack-plugin": "^7.0.0",
"workbox-window": "^7.0.0", "workbox-window": "^7.0.0"
"ws": "^8.12.1"
}, },
"devDependencies": { "devDependencies": {
"@formatjs/cli": "^6.1.1", "@formatjs/cli": "^6.1.1",
@ -165,16 +157,13 @@
"@types/babel__core": "^7.20.1", "@types/babel__core": "^7.20.1",
"@types/emoji-mart": "^3.0.9", "@types/emoji-mart": "^3.0.9",
"@types/escape-html": "^1.0.2", "@types/escape-html": "^1.0.2",
"@types/express": "^4.17.17",
"@types/hoist-non-react-statics": "^3.3.1", "@types/hoist-non-react-statics": "^3.3.1",
"@types/http-link-header": "^1.0.3", "@types/http-link-header": "^1.0.3",
"@types/intl": "^1.2.0", "@types/intl": "^1.2.0",
"@types/jest": "^29.5.2", "@types/jest": "^29.5.2",
"@types/js-yaml": "^4.0.5", "@types/js-yaml": "^4.0.5",
"@types/lodash": "^4.14.195", "@types/lodash": "^4.14.195",
"@types/npmlog": "^4.1.4",
"@types/object-assign": "^4.0.30", "@types/object-assign": "^4.0.30",
"@types/pg": "^8.6.6",
"@types/prop-types": "^15.7.5", "@types/prop-types": "^15.7.5",
"@types/punycode": "^2.1.0", "@types/punycode": "^2.1.0",
"@types/react": "^18.2.7", "@types/react": "^18.2.7",
@ -193,7 +182,6 @@
"@types/react-toggle": "^4.0.3", "@types/react-toggle": "^4.0.3",
"@types/redux-immutable": "^4.0.3", "@types/redux-immutable": "^4.0.3",
"@types/requestidlecallback": "^0.3.5", "@types/requestidlecallback": "^0.3.5",
"@types/uuid": "^9.0.0",
"@types/webpack": "^4.41.33", "@types/webpack": "^4.41.33",
"@types/yargs": "^17.0.24", "@types/yargs": "^17.0.24",
"@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/eslint-plugin": "^6.0.0",
@ -237,16 +225,11 @@
"optional": true "optional": true
} }
}, },
"optionalDependencies": {
"bufferutil": "^4.0.7",
"utf-8-validate": "^6.0.3"
},
"lint-staged": { "lint-staged": {
"*": "prettier --ignore-unknown --write", "*": "prettier --ignore-unknown --write",
"Capfile|Gemfile|*.{rb,ruby,ru,rake}": "bundle exec rubocop --force-exclusion -a", "Capfile|Gemfile|*.{rb,ruby,ru,rake}": "bundle exec rubocop --force-exclusion -a",
"*.{haml}": "bundle exec haml-lint", "*.{haml}": "bundle exec haml-lint",
"*.{js,jsx,ts,tsx}": "eslint --fix", "*.{js,jsx,ts,tsx}": "eslint --fix",
"*.{css,scss}": "stylelint --fix" "*.{css,scss}": "stylelint --fix"
}, }
"packageManager": "yarn@4.0.1"
} }

View file

@ -58,7 +58,7 @@ RSpec.describe ActivityPub::InboxesController do
before do before do
allow(ActivityPub::FollowersSynchronizationWorker).to receive(:perform_async).and_return(nil) allow(ActivityPub::FollowersSynchronizationWorker).to receive(:perform_async).and_return(nil)
allow_any_instance_of(Account).to receive(:local_followers_hash).and_return('somehash') allow(remote_account).to receive(:local_followers_hash).and_return('somehash')
request.headers['Collection-Synchronization'] = synchronization_header request.headers['Collection-Synchronization'] = synchronization_header
post :create, body: '{}' post :create, body: '{}'

View file

@ -227,7 +227,8 @@ RSpec.describe Admin::AccountsController do
let(:account) { Fabricate(:account, domain: 'example.com') } let(:account) { Fabricate(:account, domain: 'example.com') }
before do before do
allow_any_instance_of(ResolveAccountService).to receive(:call) service = instance_double(ResolveAccountService, call: nil)
allow(ResolveAccountService).to receive(:new).and_return(service)
end end
context 'when user is admin' do context 'when user is admin' do

View file

@ -13,12 +13,20 @@ describe Admin::ResetsController do
describe 'POST #create' do describe 'POST #create' do
it 'redirects to admin accounts page' do it 'redirects to admin accounts page' do
expect_any_instance_of(User).to receive(:send_reset_password_instructions) do |value| expect do
expect(value.account_id).to eq account.id post :create, params: { account_id: account.id }
end end.to change(Devise.mailer.deliveries, :size).by(2)
post :create, params: { account_id: account.id }
expect(Devise.mailer.deliveries).to have_attributes(
first: have_attributes(
to: include(account.user.email),
subject: I18n.t('devise.mailer.password_change.subject')
),
last: have_attributes(
to: include(account.user.email),
subject: I18n.t('devise.mailer.reset_password_instructions.subject')
)
)
expect(response).to redirect_to(admin_account_path(account.id)) expect(response).to redirect_to(admin_account_path(account.id))
end end
end end

View file

@ -126,7 +126,7 @@ RSpec.describe Auth::SessionsController do
let!(:previous_login) { Fabricate(:login_activity, user: user, ip: previous_ip) } let!(:previous_login) { Fabricate(:login_activity, user: user, ip: previous_ip) }
before do before do
allow_any_instance_of(ActionDispatch::Request).to receive(:remote_ip).and_return(current_ip) allow(controller.request).to receive(:remote_ip).and_return(current_ip)
user.update(current_sign_in_at: 1.month.ago) user.update(current_sign_in_at: 1.month.ago)
post :create, params: { user: { email: user.email, password: user.password } } post :create, params: { user: { email: user.email, password: user.password } }
end end
@ -279,7 +279,9 @@ RSpec.describe Auth::SessionsController do
context 'when the server has an decryption error' do context 'when the server has an decryption error' do
before do before do
allow_any_instance_of(User).to receive(:validate_and_consume_otp!).and_raise(OpenSSL::Cipher::CipherError) allow(user).to receive(:validate_and_consume_otp!).with(user.current_otp).and_raise(OpenSSL::Cipher::CipherError)
allow(User).to receive(:find_by).with(id: user.id).and_return(user)
post :create, params: { user: { otp_attempt: user.current_otp } }, session: { attempt_user_id: user.id, attempt_user_updated_at: user.updated_at.to_s } post :create, params: { user: { otp_attempt: user.current_otp } }, session: { attempt_user_id: user.id, attempt_user_updated_at: user.updated_at.to_s }
end end

View file

@ -61,6 +61,7 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do
it 'renders page with success' do it 'renders page with success' do
prepare_user_otp_generation prepare_user_otp_generation
prepare_user_otp_consumption prepare_user_otp_consumption
allow(controller).to receive(:current_user).and_return(user)
expect do expect do
post :create, post :create,
@ -75,30 +76,28 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do
end end
def prepare_user_otp_generation def prepare_user_otp_generation
expect_any_instance_of(User).to receive(:generate_otp_backup_codes!) do |value| allow(user)
expect(value).to eq user .to receive(:generate_otp_backup_codes!)
otp_backup_codes .and_return(otp_backup_codes)
end
end end
def prepare_user_otp_consumption def prepare_user_otp_consumption
expect_any_instance_of(User).to receive(:validate_and_consume_otp!) do |value, code, options| options = { otp_secret: 'thisisasecretforthespecofnewview' }
expect(value).to eq user allow(user)
expect(code).to eq '123456' .to receive(:validate_and_consume_otp!)
expect(options).to eq({ otp_secret: 'thisisasecretforthespecofnewview' }) .with('123456', options)
true .and_return(true)
end
end end
end end
describe 'when creation fails' do describe 'when creation fails' do
subject do subject do
expect_any_instance_of(User).to receive(:validate_and_consume_otp!) do |value, code, options| options = { otp_secret: 'thisisasecretforthespecofnewview' }
expect(value).to eq user allow(user)
expect(code).to eq '123456' .to receive(:validate_and_consume_otp!)
expect(options).to eq({ otp_secret: 'thisisasecretforthespecofnewview' }) .with('123456', options)
false .and_return(false)
end allow(controller).to receive(:current_user).and_return(user)
expect do expect do
post :create, post :create,

View file

@ -9,10 +9,8 @@ describe Settings::TwoFactorAuthentication::RecoveryCodesController do
it 'updates the codes and shows them on a view when signed in' do it 'updates the codes and shows them on a view when signed in' do
user = Fabricate(:user) user = Fabricate(:user)
otp_backup_codes = user.generate_otp_backup_codes! otp_backup_codes = user.generate_otp_backup_codes!
expect_any_instance_of(User).to receive(:generate_otp_backup_codes!) do |value| allow(user).to receive(:generate_otp_backup_codes!).and_return(otp_backup_codes)
expect(value).to eq user allow(controller).to receive(:current_user).and_return(user)
otp_backup_codes
end
sign_in user, scope: :user sign_in user, scope: :user
post :create, session: { challenge_passed_at: Time.now.utc } post :create, session: { challenge_passed_at: Time.now.utc }

View file

@ -64,8 +64,11 @@ describe Request do
end end
it 'closes underlying connection' do it 'closes underlying connection' do
expect_any_instance_of(HTTP::Client).to receive(:close) allow(subject.send(:http_client)).to receive(:close)
expect { |block| subject.perform(&block) }.to yield_control expect { |block| subject.perform(&block) }.to yield_control
expect(subject.send(:http_client)).to have_received(:close)
end end
it 'returns response which implements body_with_limit' do it 'returns response which implements body_with_limit' do

View file

@ -23,7 +23,8 @@ describe StatusFilter do
context 'when status policy does not allow show' do context 'when status policy does not allow show' do
it 'filters the status' do it 'filters the status' do
allow_any_instance_of(StatusPolicy).to receive(:show?).and_return(false) policy = instance_double(StatusPolicy, show?: false)
allow(StatusPolicy).to receive(:new).and_return(policy)
expect(filter).to be_filtered expect(filter).to be_filtered
end end
@ -74,7 +75,8 @@ describe StatusFilter do
context 'when status policy does not allow show' do context 'when status policy does not allow show' do
it 'filters the status' do it 'filters the status' do
allow_any_instance_of(StatusPolicy).to receive(:show?).and_return(false) policy = instance_double(StatusPolicy, show?: false)
allow(StatusPolicy).to receive(:new).and_return(policy)
expect(filter).to be_filtered expect(filter).to be_filtered
end end

View file

@ -209,9 +209,13 @@ RSpec.describe Account do
expect(account.refresh!).to be_nil expect(account.refresh!).to be_nil
end end
it 'calls not ResolveAccountService#call' do it 'does not call ResolveAccountService#call' do
expect_any_instance_of(ResolveAccountService).to_not receive(:call).with(acct) service = instance_double(ResolveAccountService, call: nil)
allow(ResolveAccountService).to receive(:new).and_return(service)
account.refresh! account.refresh!
expect(service).to_not have_received(:call).with(acct)
end end
end end
@ -219,8 +223,12 @@ RSpec.describe Account do
let(:domain) { 'example.com' } let(:domain) { 'example.com' }
it 'calls ResolveAccountService#call' do it 'calls ResolveAccountService#call' do
expect_any_instance_of(ResolveAccountService).to receive(:call).with(acct).once service = instance_double(ResolveAccountService, call: nil)
allow(ResolveAccountService).to receive(:new).and_return(service)
account.refresh! account.refresh!
expect(service).to have_received(:call).with(acct).once
end end
end end
end end

View file

@ -52,7 +52,8 @@ RSpec.describe Setting do
before do before do
allow(RailsSettings::Settings).to receive(:object).with(key).and_return(object) allow(RailsSettings::Settings).to receive(:object).with(key).and_return(object)
allow(described_class).to receive(:default_settings).and_return(default_settings) allow(described_class).to receive(:default_settings).and_return(default_settings)
allow_any_instance_of(Settings::ScopedSettings).to receive(:thing_scoped).and_return(records) settings_double = instance_double(Settings::ScopedSettings, thing_scoped: records)
allow(Settings::ScopedSettings).to receive(:new).and_return(settings_double)
Rails.cache.delete(cache_key) Rails.cache.delete(cache_key)
end end
@ -128,7 +129,8 @@ RSpec.describe Setting do
describe '.all_as_records' do describe '.all_as_records' do
before do before do
allow_any_instance_of(Settings::ScopedSettings).to receive(:thing_scoped).and_return(records) settings_double = instance_double(Settings::ScopedSettings, thing_scoped: records)
allow(Settings::ScopedSettings).to receive(:new).and_return(settings_double)
allow(described_class).to receive(:default_settings).and_return(default_settings) allow(described_class).to receive(:default_settings).and_return(default_settings)
end end

View file

@ -102,17 +102,25 @@ describe 'GET /api/v1/accounts/relationships' do
end end
end end
it 'returns JSON with correct data on cached requests too' do it 'returns JSON with correct data on previously cached requests' do
subject # Initial request including multiple accounts in params
subject get '/api/v1/accounts/relationships', headers: headers, params: { id: [simon.id, lewis.id] }
expect(body_as_json.size).to eq(2)
# Subsequent request with different id, should override cache from first request
get '/api/v1/accounts/relationships', headers: headers, params: { id: [simon.id] }
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
json = body_as_json expect(body_as_json)
.to be_an(Enumerable)
expect(json).to be_a Enumerable .and have_attributes(
expect(json.first[:following]).to be true size: 1,
expect(json.first[:showing_reblogs]).to be true first: hash_including(
following: true,
showing_reblogs: true
)
)
end end
it 'returns JSON with correct data after change too' do it 'returns JSON with correct data after change too' do

View file

@ -0,0 +1,43 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'API namespace minimal Content-Security-Policy' do
before { stub_tests_controller }
after { Rails.application.reload_routes! }
it 'returns the correct CSP headers' do
get '/api/v1/tests'
expect(response).to have_http_status(200)
expect(response.headers['Content-Security-Policy']).to eq(minimal_csp_headers)
end
private
def stub_tests_controller
stub_const('Api::V1::TestsController', api_tests_controller)
Rails.application.routes.draw do
get '/api/v1/tests', to: 'api/v1/tests#index'
end
end
def api_tests_controller
Class.new(Api::BaseController) do
def index
head 200
end
private
def user_signed_in? = false
def current_user = nil
end
end
def minimal_csp_headers
"default-src 'none'; frame-ancestors 'none'; form-action 'none'"
end
end

View file

@ -76,7 +76,8 @@ RSpec.describe ActivityPub::ProcessCollectionService, type: :service do
let(:forwarder) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/other_account') } let(:forwarder) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/other_account') }
it 'does not process payload if no signature exists' do it 'does not process payload if no signature exists' do
allow_any_instance_of(ActivityPub::LinkedDataSignature).to receive(:verify_actor!).and_return(nil) signature_double = instance_double(ActivityPub::LinkedDataSignature, verify_actor!: nil)
allow(ActivityPub::LinkedDataSignature).to receive(:new).and_return(signature_double)
allow(ActivityPub::Activity).to receive(:factory) allow(ActivityPub::Activity).to receive(:factory)
subject.call(json, forwarder) subject.call(json, forwarder)
@ -87,7 +88,8 @@ RSpec.describe ActivityPub::ProcessCollectionService, type: :service do
it 'processes payload with actor if valid signature exists' do it 'processes payload with actor if valid signature exists' do
payload['signature'] = { 'type' => 'RsaSignature2017' } payload['signature'] = { 'type' => 'RsaSignature2017' }
allow_any_instance_of(ActivityPub::LinkedDataSignature).to receive(:verify_actor!).and_return(actor) signature_double = instance_double(ActivityPub::LinkedDataSignature, verify_actor!: actor)
allow(ActivityPub::LinkedDataSignature).to receive(:new).and_return(signature_double)
allow(ActivityPub::Activity).to receive(:factory).with(instance_of(Hash), actor, instance_of(Hash)) allow(ActivityPub::Activity).to receive(:factory).with(instance_of(Hash), actor, instance_of(Hash))
subject.call(json, forwarder) subject.call(json, forwarder)
@ -98,7 +100,8 @@ RSpec.describe ActivityPub::ProcessCollectionService, type: :service do
it 'does not process payload if invalid signature exists' do it 'does not process payload if invalid signature exists' do
payload['signature'] = { 'type' => 'RsaSignature2017' } payload['signature'] = { 'type' => 'RsaSignature2017' }
allow_any_instance_of(ActivityPub::LinkedDataSignature).to receive(:verify_actor!).and_return(nil) signature_double = instance_double(ActivityPub::LinkedDataSignature, verify_actor!: nil)
allow(ActivityPub::LinkedDataSignature).to receive(:new).and_return(signature_double)
allow(ActivityPub::Activity).to receive(:factory) allow(ActivityPub::Activity).to receive(:factory)
subject.call(json, forwarder) subject.call(json, forwarder)

View file

@ -4,13 +4,21 @@ require 'rspec/retry'
if ENV['DISABLE_SIMPLECOV'] != 'true' if ENV['DISABLE_SIMPLECOV'] != 'true'
require 'simplecov' require 'simplecov'
require 'simplecov-lcov'
SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true
SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter
SimpleCov.start 'rails' do SimpleCov.start 'rails' do
enable_coverage :branch
enable_coverage_for_eval
add_filter 'lib/linter' add_filter 'lib/linter'
add_group 'Policies', 'app/policies' add_group 'Policies', 'app/policies'
add_group 'Presenters', 'app/presenters' add_group 'Presenters', 'app/presenters'
add_group 'Serializers', 'app/serializers' add_group 'Serializers', 'app/serializers'
add_group 'Services', 'app/services' add_group 'Services', 'app/services'
add_group 'Validators', 'app/validators' add_group 'Validators', 'app/validators'
add_group 'Libraries', 'lib'
end end
end end

View file

@ -11,7 +11,8 @@ describe ActivityPub::DeliveryWorker do
let(:payload) { 'test' } let(:payload) { 'test' }
before do before do
allow_any_instance_of(Account).to receive(:remote_followers_hash).with('https://example.com/api').and_return('somehash') allow(sender).to receive(:remote_followers_hash).with('https://example.com/api').and_return('somehash')
allow(Account).to receive(:find).with(sender.id).and_return(sender)
end end
describe 'perform' do describe 'perform' do

View file

@ -23,8 +23,8 @@ describe Web::PushNotificationWorker do
describe 'perform' do describe 'perform' do
before do before do
allow_any_instance_of(subscription.class).to receive(:contact_email).and_return(contact_email) allow(subscription).to receive_messages(contact_email: contact_email, vapid_key: vapid_key)
allow_any_instance_of(subscription.class).to receive(:vapid_key).and_return(vapid_key) allow(Web::PushSubscription).to receive(:find).with(subscription.id).and_return(subscription)
allow(Webpush::Encryption).to receive(:encrypt).and_return(payload) allow(Webpush::Encryption).to receive(:encrypt).and_return(payload)
allow(JWT).to receive(:encode).and_return('jwt.encoded.payload') allow(JWT).to receive(:encode).and_return('jwt.encoded.payload')

View file

@ -2,6 +2,7 @@
const fs = require('fs'); const fs = require('fs');
const http = require('http'); const http = require('http');
const path = require('path');
const url = require('url'); const url = require('url');
const dotenv = require('dotenv'); const dotenv = require('dotenv');
@ -17,8 +18,11 @@ const WebSocket = require('ws');
const environment = process.env.NODE_ENV || 'development'; const environment = process.env.NODE_ENV || 'development';
// Correctly detect and load .env or .env.production file based on environment:
const dotenvFile = environment === 'production' ? '.env.production' : '.env';
dotenv.config({ dotenv.config({
path: environment === 'production' ? '.env.production' : '.env', path: path.resolve(__dirname, path.join('..', dotenvFile))
}); });
log.level = process.env.LOG_LEVEL || 'verbose'; log.level = process.env.LOG_LEVEL || 'verbose';

39
streaming/package.json Normal file
View file

@ -0,0 +1,39 @@
{
"name": "@mastodon/streaming",
"license": "AGPL-3.0-or-later",
"packageManager": "yarn@4.0.2",
"engines": {
"node": ">=18"
},
"description": "Mastodon's Streaming Server",
"private": true,
"repository": {
"type": "git",
"url": "https://github.com/mastodon/mastodon.git"
},
"scripts": {
"start": "node ./index.js"
},
"dependencies": {
"dotenv": "^16.0.3",
"express": "^4.18.2",
"ioredis": "^5.3.2",
"jsdom": "^22.1.0",
"npmlog": "^7.0.1",
"pg": "^8.5.0",
"pg-connection-string": "^2.6.0",
"prom-client": "^15.0.0",
"uuid": "^9.0.0",
"ws": "^8.12.1"
},
"devDependencies": {
"@types/express": "^4.17.17",
"@types/npmlog": "^4.1.4",
"@types/pg": "^8.6.6",
"@types/uuid": "^9.0.0"
},
"optionalDependencies": {
"bufferutil": "^4.0.7",
"utf-8-validate": "^6.0.3"
}
}

321
yarn.lock
View file

@ -1800,6 +1800,16 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@formatjs/ecma402-abstract@npm:1.18.0":
version: 1.18.0
resolution: "@formatjs/ecma402-abstract@npm:1.18.0"
dependencies:
"@formatjs/intl-localematcher": "npm:0.5.2"
tslib: "npm:^2.4.0"
checksum: bbdad0aee8e48baad6bfe6b2c27caf3befe35e658b922ee2f84417a819f0bdc7e849a8c0c782db8b53f5666bf19669d2b10a1104257c08796d198c87766bfc92
languageName: node
linkType: hard
"@formatjs/fast-memoize@npm:2.2.0": "@formatjs/fast-memoize@npm:2.2.0":
version: 2.2.0 version: 2.2.0
resolution: "@formatjs/fast-memoize@npm:2.2.0" resolution: "@formatjs/fast-memoize@npm:2.2.0"
@ -1820,6 +1830,17 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@formatjs/icu-messageformat-parser@npm:2.7.3":
version: 2.7.3
resolution: "@formatjs/icu-messageformat-parser@npm:2.7.3"
dependencies:
"@formatjs/ecma402-abstract": "npm:1.18.0"
"@formatjs/icu-skeleton-parser": "npm:1.7.0"
tslib: "npm:^2.4.0"
checksum: 2a51038813e5cff7e2df767e1227373d228e907adb7268fc3744b3d82c4fa69d4aa9f6020a62de2c468cf724600e9372ac07ae43a4480ed066fe34e224e80e4a
languageName: node
linkType: hard
"@formatjs/icu-skeleton-parser@npm:1.6.4": "@formatjs/icu-skeleton-parser@npm:1.6.4":
version: 1.6.4 version: 1.6.4
resolution: "@formatjs/icu-skeleton-parser@npm:1.6.4" resolution: "@formatjs/icu-skeleton-parser@npm:1.6.4"
@ -1830,25 +1851,35 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@formatjs/intl-displaynames@npm:6.6.3": "@formatjs/icu-skeleton-parser@npm:1.7.0":
version: 6.6.3 version: 1.7.0
resolution: "@formatjs/intl-displaynames@npm:6.6.3" resolution: "@formatjs/icu-skeleton-parser@npm:1.7.0"
dependencies: dependencies:
"@formatjs/ecma402-abstract": "npm:1.17.4" "@formatjs/ecma402-abstract": "npm:1.18.0"
"@formatjs/intl-localematcher": "npm:0.5.1"
tslib: "npm:^2.4.0" tslib: "npm:^2.4.0"
checksum: b0520cb744a51290fbcde80860f39ed9c9df9b81beae98986e1fc089ef635f7699c750631fa42a559f3678d1dd02b14904614e70360477d18e68d3eba6592390 checksum: 2e4db815247ddb10f7990bbb501c85b854ee951ee45143673eb91b4392b11d0a8312327adb8b624c6a2fdafab12083904630d6d22475503d025f1612da4dcaee
languageName: node languageName: node
linkType: hard linkType: hard
"@formatjs/intl-listformat@npm:7.5.2": "@formatjs/intl-displaynames@npm:6.6.4":
version: 7.5.2 version: 6.6.4
resolution: "@formatjs/intl-listformat@npm:7.5.2" resolution: "@formatjs/intl-displaynames@npm:6.6.4"
dependencies: dependencies:
"@formatjs/ecma402-abstract": "npm:1.17.4" "@formatjs/ecma402-abstract": "npm:1.18.0"
"@formatjs/intl-localematcher": "npm:0.5.1" "@formatjs/intl-localematcher": "npm:0.5.2"
tslib: "npm:^2.4.0" tslib: "npm:^2.4.0"
checksum: 54fa03da4ea45504681d6d87d72d1cac574809ce43f965fa4b845e83be3072d92324c58cec57ad386827087fb1d6ecae438d29576f30176bf52eb212e454bce2 checksum: 009e443dd0d10776b8573d0181407d4c0d6c7a2ff537a5ea1e36413d1b08db9c21dfef272eabab8efabd01a58b64f663a30e4d584fd761df3fd68a5d23fe444b
languageName: node
linkType: hard
"@formatjs/intl-listformat@npm:7.5.3":
version: 7.5.3
resolution: "@formatjs/intl-listformat@npm:7.5.3"
dependencies:
"@formatjs/ecma402-abstract": "npm:1.18.0"
"@formatjs/intl-localematcher": "npm:0.5.2"
tslib: "npm:^2.4.0"
checksum: de741ce84b16fed57016afbfe446ebd57cd23a046859a9353f5d455f8bc9114493bf83b9e18429268c7ce8f77bc54516a9b8190baf09fbb25c9b06cfc80101d4
languageName: node languageName: node
linkType: hard linkType: hard
@ -1861,34 +1892,43 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@formatjs/intl-pluralrules@npm:^5.2.2": "@formatjs/intl-localematcher@npm:0.5.2":
version: 5.2.9 version: 0.5.2
resolution: "@formatjs/intl-pluralrules@npm:5.2.9" resolution: "@formatjs/intl-localematcher@npm:0.5.2"
dependencies: dependencies:
"@formatjs/ecma402-abstract": "npm:1.17.4"
"@formatjs/intl-localematcher": "npm:0.5.1"
tslib: "npm:^2.4.0" tslib: "npm:^2.4.0"
checksum: a6ca5c498ce542facacf8ce8640d4ba068f9119b758547a23614b50611eb385a46abd386ff88fa423211355ec463cf102c2c908b74f6e23a5bc9e2a23873dc29 checksum: 4b3ae75470e3e53ffa39b2d46e65a2a4c9c4becbc0aac989b0694370e10c6687643660a045512d676509bc29b257fe5726fbb028de12f889be02c2d20b6527e6
languageName: node languageName: node
linkType: hard linkType: hard
"@formatjs/intl@npm:2.9.8": "@formatjs/intl-pluralrules@npm:^5.2.2":
version: 2.9.8 version: 5.2.10
resolution: "@formatjs/intl@npm:2.9.8" resolution: "@formatjs/intl-pluralrules@npm:5.2.10"
dependencies: dependencies:
"@formatjs/ecma402-abstract": "npm:1.17.4" "@formatjs/ecma402-abstract": "npm:1.18.0"
"@formatjs/intl-localematcher": "npm:0.5.2"
tslib: "npm:^2.4.0"
checksum: 1050416613e80bff2c58546c80c8d52ed97847d13c90535a53d058e44969369b50e1cfdb464e9e9ef4802c934c84ea0e656c3f4e3b4d5ac7496b722c759da4cf
languageName: node
linkType: hard
"@formatjs/intl@npm:2.9.9":
version: 2.9.9
resolution: "@formatjs/intl@npm:2.9.9"
dependencies:
"@formatjs/ecma402-abstract": "npm:1.18.0"
"@formatjs/fast-memoize": "npm:2.2.0" "@formatjs/fast-memoize": "npm:2.2.0"
"@formatjs/icu-messageformat-parser": "npm:2.7.2" "@formatjs/icu-messageformat-parser": "npm:2.7.3"
"@formatjs/intl-displaynames": "npm:6.6.3" "@formatjs/intl-displaynames": "npm:6.6.4"
"@formatjs/intl-listformat": "npm:7.5.2" "@formatjs/intl-listformat": "npm:7.5.3"
intl-messageformat: "npm:10.5.7" intl-messageformat: "npm:10.5.8"
tslib: "npm:^2.4.0" tslib: "npm:^2.4.0"
peerDependencies: peerDependencies:
typescript: 5 typescript: 5
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 6341f4bfb56a0e14373395b1232e1eeb8e64588a8c3d4614cd2b06f71d4e65dbd4a79e3a1c07e1b6c20c48e399ac2385977b01a559e1d2bd1a1d226e0eae3058 checksum: b26904da605ab309535dfbbfbd403a3bb33d51d3c969c548b88fa04755be3aff60b1bddd1c453514a84048c7432271cef507ac66de32dcfa66b3f842a1ddb977
languageName: node languageName: node
linkType: hard linkType: hard
@ -1912,6 +1952,26 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@formatjs/ts-transformer@npm:3.13.9":
version: 3.13.9
resolution: "@formatjs/ts-transformer@npm:3.13.9"
dependencies:
"@formatjs/icu-messageformat-parser": "npm:2.7.3"
"@types/json-stable-stringify": "npm:^1.0.32"
"@types/node": "npm:14 || 16 || 17"
chalk: "npm:^4.0.0"
json-stable-stringify: "npm:^1.0.1"
tslib: "npm:^2.4.0"
typescript: "npm:5"
peerDependencies:
ts-jest: ">=27"
peerDependenciesMeta:
ts-jest:
optional: true
checksum: 4e313b967e45aae79246174c3181d31cc7cd297380d3a880a98fc0be16d76b783868712151e840ea16d22e2fbec0388b1005f688b6d4cb74ee4411b43f6d33f4
languageName: node
linkType: hard
"@gamestdio/websocket@npm:^0.3.2": "@gamestdio/websocket@npm:^0.3.2":
version: 0.3.2 version: 0.3.2
resolution: "@gamestdio/websocket@npm:0.3.2" resolution: "@gamestdio/websocket@npm:0.3.2"
@ -2327,16 +2387,13 @@ __metadata:
"@types/babel__core": "npm:^7.20.1" "@types/babel__core": "npm:^7.20.1"
"@types/emoji-mart": "npm:^3.0.9" "@types/emoji-mart": "npm:^3.0.9"
"@types/escape-html": "npm:^1.0.2" "@types/escape-html": "npm:^1.0.2"
"@types/express": "npm:^4.17.17"
"@types/hoist-non-react-statics": "npm:^3.3.1" "@types/hoist-non-react-statics": "npm:^3.3.1"
"@types/http-link-header": "npm:^1.0.3" "@types/http-link-header": "npm:^1.0.3"
"@types/intl": "npm:^1.2.0" "@types/intl": "npm:^1.2.0"
"@types/jest": "npm:^29.5.2" "@types/jest": "npm:^29.5.2"
"@types/js-yaml": "npm:^4.0.5" "@types/js-yaml": "npm:^4.0.5"
"@types/lodash": "npm:^4.14.195" "@types/lodash": "npm:^4.14.195"
"@types/npmlog": "npm:^4.1.4"
"@types/object-assign": "npm:^4.0.30" "@types/object-assign": "npm:^4.0.30"
"@types/pg": "npm:^8.6.6"
"@types/prop-types": "npm:^15.7.5" "@types/prop-types": "npm:^15.7.5"
"@types/punycode": "npm:^2.1.0" "@types/punycode": "npm:^2.1.0"
"@types/react": "npm:^18.2.7" "@types/react": "npm:^18.2.7"
@ -2355,7 +2412,6 @@ __metadata:
"@types/react-toggle": "npm:^4.0.3" "@types/react-toggle": "npm:^4.0.3"
"@types/redux-immutable": "npm:^4.0.3" "@types/redux-immutable": "npm:^4.0.3"
"@types/requestidlecallback": "npm:^0.3.5" "@types/requestidlecallback": "npm:^0.3.5"
"@types/uuid": "npm:^9.0.0"
"@types/webpack": "npm:^4.41.33" "@types/webpack": "npm:^4.41.33"
"@types/yargs": "npm:^17.0.24" "@types/yargs": "npm:^17.0.24"
"@typescript-eslint/eslint-plugin": "npm:^6.0.0" "@typescript-eslint/eslint-plugin": "npm:^6.0.0"
@ -2371,7 +2427,6 @@ __metadata:
babel-plugin-preval: "npm:^5.1.0" babel-plugin-preval: "npm:^5.1.0"
babel-plugin-transform-react-remove-prop-types: "npm:^0.4.24" babel-plugin-transform-react-remove-prop-types: "npm:^0.4.24"
blurhash: "npm:^2.0.5" blurhash: "npm:^2.0.5"
bufferutil: "npm:^4.0.7"
circular-dependency-plugin: "npm:^5.2.2" circular-dependency-plugin: "npm:^5.2.2"
classnames: "npm:^2.3.2" classnames: "npm:^2.3.2"
cocoon-js-vanilla: "npm:^1.3.0" cocoon-js-vanilla: "npm:^1.3.0"
@ -2382,7 +2437,6 @@ __metadata:
css-loader: "npm:^5.2.7" css-loader: "npm:^5.2.7"
cssnano: "npm:^6.0.1" cssnano: "npm:^6.0.1"
detect-passive-events: "npm:^2.0.3" detect-passive-events: "npm:^2.0.3"
dotenv: "npm:^16.0.3"
emoji-mart: "npm:emoji-mart-lazyload@latest" emoji-mart: "npm:emoji-mart-lazyload@latest"
escape-html: "npm:^1.0.3" escape-html: "npm:^1.0.3"
eslint: "npm:^8.41.0" eslint: "npm:^8.41.0"
@ -2396,7 +2450,6 @@ __metadata:
eslint-plugin-promise: "npm:~6.1.1" eslint-plugin-promise: "npm:~6.1.1"
eslint-plugin-react: "npm:~7.33.0" eslint-plugin-react: "npm:~7.33.0"
eslint-plugin-react-hooks: "npm:^4.6.0" eslint-plugin-react-hooks: "npm:^4.6.0"
express: "npm:^4.18.2"
file-loader: "npm:^6.2.0" file-loader: "npm:^6.2.0"
font-awesome: "npm:^4.7.0" font-awesome: "npm:^4.7.0"
fuzzysort: "npm:^2.0.4" fuzzysort: "npm:^2.0.4"
@ -2408,25 +2461,19 @@ __metadata:
immutable: "npm:^4.3.0" immutable: "npm:^4.3.0"
imports-loader: "npm:^1.2.0" imports-loader: "npm:^1.2.0"
intl-messageformat: "npm:^10.3.5" intl-messageformat: "npm:^10.3.5"
ioredis: "npm:^5.3.2"
jest: "npm:^29.5.0" jest: "npm:^29.5.0"
jest-environment-jsdom: "npm:^29.5.0" jest-environment-jsdom: "npm:^29.5.0"
js-yaml: "npm:^4.1.0" js-yaml: "npm:^4.1.0"
jsdom: "npm:^22.1.0"
lint-staged: "npm:^15.0.0" lint-staged: "npm:^15.0.0"
lodash: "npm:^4.17.21" lodash: "npm:^4.17.21"
mark-loader: "npm:^0.1.6" mark-loader: "npm:^0.1.6"
marky: "npm:^1.2.5" marky: "npm:^1.2.5"
mini-css-extract-plugin: "npm:^1.6.2" mini-css-extract-plugin: "npm:^1.6.2"
mkdirp: "npm:^3.0.1" mkdirp: "npm:^3.0.1"
npmlog: "npm:^7.0.1"
path-complete-extname: "npm:^1.0.0" path-complete-extname: "npm:^1.0.0"
pg: "npm:^8.5.0"
pg-connection-string: "npm:^2.6.0"
postcss: "npm:^8.4.24" postcss: "npm:^8.4.24"
postcss-loader: "npm:^4.3.0" postcss-loader: "npm:^4.3.0"
prettier: "npm:^3.0.0" prettier: "npm:^3.0.0"
prom-client: "npm:^15.0.0"
prop-types: "npm:^15.8.1" prop-types: "npm:^15.8.1"
punycode: "npm:^2.3.0" punycode: "npm:^2.3.0"
react: "npm:^18.2.0" react: "npm:^18.2.0"
@ -2469,8 +2516,6 @@ __metadata:
tiny-queue: "npm:^0.2.1" tiny-queue: "npm:^0.2.1"
twitter-text: "npm:3.1.0" twitter-text: "npm:3.1.0"
typescript: "npm:^5.0.4" typescript: "npm:^5.0.4"
utf-8-validate: "npm:^6.0.3"
uuid: "npm:^9.0.0"
webpack: "npm:^4.47.0" webpack: "npm:^4.47.0"
webpack-assets-manifest: "npm:^4.0.6" webpack-assets-manifest: "npm:^4.0.6"
webpack-bundle-analyzer: "npm:^4.8.0" webpack-bundle-analyzer: "npm:^4.8.0"
@ -2484,13 +2529,7 @@ __metadata:
workbox-strategies: "npm:^7.0.0" workbox-strategies: "npm:^7.0.0"
workbox-webpack-plugin: "npm:^7.0.0" workbox-webpack-plugin: "npm:^7.0.0"
workbox-window: "npm:^7.0.0" workbox-window: "npm:^7.0.0"
ws: "npm:^8.12.1"
yargs: "npm:^17.7.2" yargs: "npm:^17.7.2"
dependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
peerDependenciesMeta: peerDependenciesMeta:
react: react:
optional: true optional: true
@ -2501,6 +2540,34 @@ __metadata:
languageName: unknown languageName: unknown
linkType: soft linkType: soft
"@mastodon/streaming@workspace:streaming":
version: 0.0.0-use.local
resolution: "@mastodon/streaming@workspace:streaming"
dependencies:
"@types/express": "npm:^4.17.17"
"@types/npmlog": "npm:^4.1.4"
"@types/pg": "npm:^8.6.6"
"@types/uuid": "npm:^9.0.0"
bufferutil: "npm:^4.0.7"
dotenv: "npm:^16.0.3"
express: "npm:^4.18.2"
ioredis: "npm:^5.3.2"
jsdom: "npm:^22.1.0"
npmlog: "npm:^7.0.1"
pg: "npm:^8.5.0"
pg-connection-string: "npm:^2.6.0"
prom-client: "npm:^15.0.0"
utf-8-validate: "npm:^6.0.3"
uuid: "npm:^9.0.0"
ws: "npm:^8.12.1"
dependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
languageName: unknown
linkType: soft
"@material-symbols/svg-600@npm:^0.14.0": "@material-symbols/svg-600@npm:^0.14.0":
version: 0.14.0 version: 0.14.0
resolution: "@material-symbols/svg-600@npm:0.14.0" resolution: "@material-symbols/svg-600@npm:0.14.0"
@ -3015,21 +3082,21 @@ __metadata:
linkType: hard linkType: hard
"@types/body-parser@npm:*": "@types/body-parser@npm:*":
version: 1.19.4 version: 1.19.5
resolution: "@types/body-parser@npm:1.19.4" resolution: "@types/body-parser@npm:1.19.5"
dependencies: dependencies:
"@types/connect": "npm:*" "@types/connect": "npm:*"
"@types/node": "npm:*" "@types/node": "npm:*"
checksum: bec2b8a97861a960ee415f7ab3c2aeb7f4d779fd364d27ddee46057897ea571735f1f854f5ee41682964315d4e3699f62427998b9c21851d773398ef535f0612 checksum: aebeb200f25e8818d8cf39cd0209026750d77c9b85381cdd8deeb50913e4d18a1ebe4b74ca9b0b4d21952511eeaba5e9fbbf739b52731a2061e206ec60d568df
languageName: node languageName: node
linkType: hard linkType: hard
"@types/connect@npm:*": "@types/connect@npm:*":
version: 3.4.37 version: 3.4.38
resolution: "@types/connect@npm:3.4.37" resolution: "@types/connect@npm:3.4.38"
dependencies: dependencies:
"@types/node": "npm:*" "@types/node": "npm:*"
checksum: 79fd5c32a8bb5c9548369e6da3221b6a820f3a8c5396d50f6f642712b9f4c1c881ef86bdf48994a4a279e81998563410b8843c5a10dde5521d5ef6a8ae944c3b checksum: 2e1cdba2c410f25649e77856505cd60223250fa12dff7a503e492208dbfdd25f62859918f28aba95315251fd1f5e1ffbfca1e25e73037189ab85dd3f8d0a148c
languageName: node languageName: node
linkType: hard linkType: hard
@ -3074,14 +3141,14 @@ __metadata:
linkType: hard linkType: hard
"@types/express-serve-static-core@npm:^4.17.33": "@types/express-serve-static-core@npm:^4.17.33":
version: 4.17.39 version: 4.17.41
resolution: "@types/express-serve-static-core@npm:4.17.39" resolution: "@types/express-serve-static-core@npm:4.17.41"
dependencies: dependencies:
"@types/node": "npm:*" "@types/node": "npm:*"
"@types/qs": "npm:*" "@types/qs": "npm:*"
"@types/range-parser": "npm:*" "@types/range-parser": "npm:*"
"@types/send": "npm:*" "@types/send": "npm:*"
checksum: b23b005fddd2ba3f7142ec9713f06b5582c7712cdf99c3419d3972364903b348a103c3264d9a761d6497140e3b89bd416454684c4bdeff206b4c59b86e96428a checksum: dc166cbf4475c00a81fbcab120bf7477c527184be11ae149df7f26d9c1082114c68f8d387a2926fe80291b06477c8bbd9231ff4f5775de328e887695aefce269
languageName: node languageName: node
linkType: hard linkType: hard
@ -3134,9 +3201,9 @@ __metadata:
linkType: hard linkType: hard
"@types/http-errors@npm:*": "@types/http-errors@npm:*":
version: 2.0.3 version: 2.0.4
resolution: "@types/http-errors@npm:2.0.3" resolution: "@types/http-errors@npm:2.0.4"
checksum: 717ce3e8f49a1facb7130fed934108fa8a51ab02089a1049c782e353e0e08e79bdfaac054c2a94db14ea400302e523276387363aa820eaf0031af8ba5d2941dc checksum: 494670a57ad4062fee6c575047ad5782506dd35a6b9ed3894cea65830a94367bd84ba302eb3dde331871f6d70ca287bfedb1b2cf658e6132cd2cbd427ab56836
languageName: node languageName: node
linkType: hard linkType: hard
@ -3238,16 +3305,16 @@ __metadata:
linkType: hard linkType: hard
"@types/mime@npm:*": "@types/mime@npm:*":
version: 3.0.3 version: 3.0.4
resolution: "@types/mime@npm:3.0.3" resolution: "@types/mime@npm:3.0.4"
checksum: cef99f8cdc42af9de698027c2a20ba5df12bc9a89dcf5513e70103ebb55e00c5f5c585d02411f4b42fde0e78488342f1b1d3e3546a59a3da42e95fdc616e01eb checksum: db478bc0f99e40f7b3e01d356a9bdf7817060808a294978111340317bcd80ca35382855578c5b60fbc84ae449674bd9bb38427b18417e1f8f19e4f72f8b242cd
languageName: node languageName: node
linkType: hard linkType: hard
"@types/mime@npm:^1": "@types/mime@npm:^1":
version: 1.3.4 version: 1.3.5
resolution: "@types/mime@npm:1.3.4" resolution: "@types/mime@npm:1.3.5"
checksum: a0a16d26c0e70a1b133e26e7c46b70b3136b7e894396bdb7de1c642f4ac87fdbbba26bf56cf73f001312289d89de4f1c06ab745d9445850df45a5a802564c4d6 checksum: c2ee31cd9b993804df33a694d5aa3fa536511a49f2e06eeab0b484fef59b4483777dbb9e42a4198a0809ffbf698081fdbca1e5c2218b82b91603dfab10a10fbc
languageName: node languageName: node
linkType: hard linkType: hard
@ -3351,16 +3418,16 @@ __metadata:
linkType: hard linkType: hard
"@types/qs@npm:*": "@types/qs@npm:*":
version: 6.9.9 version: 6.9.10
resolution: "@types/qs@npm:6.9.9" resolution: "@types/qs@npm:6.9.10"
checksum: aede2a4181a49ae8548a1354bac3f8235cb0c5aab066b10875a3e68e88a199e220f4284e7e2bb75a3c18e5d4ff6abe1a6ce0389ef31b63952cc45e0f4d885ba0 checksum: 6be12e5f062d1b41eb037d59bf9cb65bc9410cedd5e6da832dfd7c8e2b3f4c91e81c9b90b51811140770e5052c6c4e8361181bd9437ddcd4515dc128b7c00353
languageName: node languageName: node
linkType: hard linkType: hard
"@types/range-parser@npm:*": "@types/range-parser@npm:*":
version: 1.2.6 version: 1.2.7
resolution: "@types/range-parser@npm:1.2.6" resolution: "@types/range-parser@npm:1.2.7"
checksum: 46e7fffc54cdacc8fb0cd576f8f9a6436453f0176205d6ec55434a460c7677e78e688673426d5db5e480501b2943ba08a16ececa3a354c222093551c7217fb8f checksum: 361bb3e964ec5133fa40644a0b942279ed5df1949f21321d77de79f48b728d39253e5ce0408c9c17e4e0fd95ca7899da36841686393b9f7a1e209916e9381a3c
languageName: node languageName: node
linkType: hard linkType: hard
@ -3546,23 +3613,23 @@ __metadata:
linkType: hard linkType: hard
"@types/send@npm:*": "@types/send@npm:*":
version: 0.17.3 version: 0.17.4
resolution: "@types/send@npm:0.17.3" resolution: "@types/send@npm:0.17.4"
dependencies: dependencies:
"@types/mime": "npm:^1" "@types/mime": "npm:^1"
"@types/node": "npm:*" "@types/node": "npm:*"
checksum: 773a0cb55ea03eefbe9a0e6d42114e0f84968db30954a131aae9ba7e9ab984a4776915447ebdeab4412d7f11750126614b0b75e99413f75810045bdb3196554a checksum: 7f17fa696cb83be0a104b04b424fdedc7eaba1c9a34b06027239aba513b398a0e2b7279778af521f516a397ced417c96960e5f50fcfce40c4bc4509fb1a5883c
languageName: node languageName: node
linkType: hard linkType: hard
"@types/serve-static@npm:*": "@types/serve-static@npm:*":
version: 1.15.4 version: 1.15.5
resolution: "@types/serve-static@npm:1.15.4" resolution: "@types/serve-static@npm:1.15.5"
dependencies: dependencies:
"@types/http-errors": "npm:*" "@types/http-errors": "npm:*"
"@types/mime": "npm:*" "@types/mime": "npm:*"
"@types/node": "npm:*" "@types/node": "npm:*"
checksum: 061b38993bf8f2b5033f57147c8ec90e1d1a0d6f734958ceb531ba7cc31192fd272c999cdbc57ede8672787e3aa171ec142dc65a467c04078e43823e7476eb49 checksum: 811d1a2f7e74a872195e7a013bcd87a2fb1edf07eaedcb9dcfd20c1eb4bc56ad4ea0d52141c13192c91ccda7c8aeb8a530d8a7e60b9c27f5990d7e62e0fecb03
languageName: node languageName: node
linkType: hard linkType: hard
@ -4694,21 +4761,21 @@ __metadata:
linkType: hard linkType: hard
"babel-plugin-formatjs@npm:^10.5.1": "babel-plugin-formatjs@npm:^10.5.1":
version: 10.5.9 version: 10.5.10
resolution: "babel-plugin-formatjs@npm:10.5.9" resolution: "babel-plugin-formatjs@npm:10.5.10"
dependencies: dependencies:
"@babel/core": "npm:^7.10.4" "@babel/core": "npm:^7.10.4"
"@babel/helper-plugin-utils": "npm:^7.10.4" "@babel/helper-plugin-utils": "npm:^7.10.4"
"@babel/plugin-syntax-jsx": "npm:7" "@babel/plugin-syntax-jsx": "npm:7"
"@babel/traverse": "npm:7" "@babel/traverse": "npm:7"
"@babel/types": "npm:^7.12.11" "@babel/types": "npm:^7.12.11"
"@formatjs/icu-messageformat-parser": "npm:2.7.2" "@formatjs/icu-messageformat-parser": "npm:2.7.3"
"@formatjs/ts-transformer": "npm:3.13.8" "@formatjs/ts-transformer": "npm:3.13.9"
"@types/babel__core": "npm:^7.1.7" "@types/babel__core": "npm:^7.1.7"
"@types/babel__helper-plugin-utils": "npm:^7.10.0" "@types/babel__helper-plugin-utils": "npm:^7.10.0"
"@types/babel__traverse": "npm:^7.1.7" "@types/babel__traverse": "npm:^7.1.7"
tslib: "npm:^2.4.0" tslib: "npm:^2.4.0"
checksum: 5e4127cf7b4b9b3306a9d0ab5b029831712d22db5e2117225ce706b55d222d09a7eba1f3720fdad7a99f61843b5cba107296fc11ae00a6f0941217d9322aa02e checksum: bff65cd2a88a0ae00eabab1d022ffc44c4385b7e529cac42375bb1828c678c7a71a78f644512e5d1dd8cd532d418c16acdbabcef2bf6670e24404f4f164a74ce
languageName: node languageName: node
linkType: hard linkType: hard
@ -6398,6 +6465,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"debounce@npm:^1.2.1":
version: 1.2.1
resolution: "debounce@npm:1.2.1"
checksum: 6c9320aa0973fc42050814621a7a8a78146c1975799b5b3cc1becf1f77ba9a5aa583987884230da0842a03f385def452fad5d60db97c3d1c8b824e38a8edf500
languageName: node
linkType: hard
"debug@npm:2.6.9, debug@npm:^2.2.0, debug@npm:^2.3.3": "debug@npm:2.6.9, debug@npm:^2.2.0, debug@npm:^2.3.3":
version: 2.6.9 version: 2.6.9
resolution: "debug@npm:2.6.9" resolution: "debug@npm:2.6.9"
@ -8871,7 +8945,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"html-escaper@npm:^2.0.0": "html-escaper@npm:^2.0.0, html-escaper@npm:^2.0.2":
version: 2.0.2 version: 2.0.2
resolution: "html-escaper@npm:2.0.2" resolution: "html-escaper@npm:2.0.2"
checksum: 208e8a12de1a6569edbb14544f4567e6ce8ecc30b9394fcaa4e7bb1e60c12a7c9a1ed27e31290817157e8626f3a4f29e76c8747030822eb84a6abb15c255f0a0 checksum: 208e8a12de1a6569edbb14544f4567e6ce8ecc30b9394fcaa4e7bb1e60c12a7c9a1ed27e31290817157e8626f3a4f29e76c8747030822eb84a6abb15c255f0a0
@ -9271,15 +9345,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"intl-messageformat@npm:10.5.7, intl-messageformat@npm:^10.3.5": "intl-messageformat@npm:10.5.8, intl-messageformat@npm:^10.3.5":
version: 10.5.7 version: 10.5.8
resolution: "intl-messageformat@npm:10.5.7" resolution: "intl-messageformat@npm:10.5.8"
dependencies: dependencies:
"@formatjs/ecma402-abstract": "npm:1.17.4" "@formatjs/ecma402-abstract": "npm:1.18.0"
"@formatjs/fast-memoize": "npm:2.2.0" "@formatjs/fast-memoize": "npm:2.2.0"
"@formatjs/icu-messageformat-parser": "npm:2.7.2" "@formatjs/icu-messageformat-parser": "npm:2.7.3"
tslib: "npm:^2.4.0" tslib: "npm:^2.4.0"
checksum: 7f341b3eb5b3d402167c99ca7fb98720c7ad553bed8a490b2210bd90ea9009a09f9030939307fecb111fce1454f31b4298b4f0a346999af627c86f8164a5c547 checksum: 1d2854aae8471ec48165ca265760d6c5b1814eca831c88db698eb29b5ed20bee21ca8533090c9d28d9c6f1d844dda210b0bc58a2e036446158fae0845e5eed4f
languageName: node languageName: node
linkType: hard linkType: hard
@ -11000,20 +11074,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"lodash.escape@npm:^4.0.1":
version: 4.0.1
resolution: "lodash.escape@npm:4.0.1"
checksum: 90ade409cec05b6869090476952fdfb84d4d87b1ff4a0e03ebd590f980d9a1248d93ba14579f10d80c6429e4d6af13ba137c28db64cae6dadb71442e54a3ad2b
languageName: node
linkType: hard
"lodash.flatten@npm:^4.4.0":
version: 4.4.0
resolution: "lodash.flatten@npm:4.4.0"
checksum: 97e8f0d6b61fe4723c02ad0c6e67e51784c4a2c48f56ef283483e556ad01594cf9cec9c773e177bbbdbdb5d19e99b09d2487cb6b6e5dc405c2693e93b125bd3a
languageName: node
linkType: hard
"lodash.get@npm:^4.0": "lodash.get@npm:^4.0":
version: 4.4.2 version: 4.4.2
resolution: "lodash.get@npm:4.4.2" resolution: "lodash.get@npm:4.4.2"
@ -11028,13 +11088,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"lodash.invokemap@npm:^4.6.0":
version: 4.6.0
resolution: "lodash.invokemap@npm:4.6.0"
checksum: 2bcc5f4b8782a316d55ff139215eb797f576f0f6d3db2755ebba7b35fd6061f8cbe81702a72a30bc6d70073a5dcc461f7570eaddcc9184c2e42ec3023645c6a1
languageName: node
linkType: hard
"lodash.isarguments@npm:^3.1.0": "lodash.isarguments@npm:^3.1.0":
version: 3.1.0 version: 3.1.0
resolution: "lodash.isarguments@npm:3.1.0" resolution: "lodash.isarguments@npm:3.1.0"
@ -11077,13 +11130,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"lodash.pullall@npm:^4.2.0":
version: 4.2.0
resolution: "lodash.pullall@npm:4.2.0"
checksum: b129e8d879258c7db04a7dc1c23dd9e37c52f63a04e105faa8d2ab55e97b5a170d5e15cffbb732a36e7f48c4345c07b6fbddfe50e1f5ec301492b6f64a92040c
languageName: node
linkType: hard
"lodash.sortby@npm:^4.7.0": "lodash.sortby@npm:^4.7.0":
version: 4.7.0 version: 4.7.0
resolution: "lodash.sortby@npm:4.7.0" resolution: "lodash.sortby@npm:4.7.0"
@ -11105,13 +11151,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"lodash.uniqby@npm:^4.7.0":
version: 4.7.0
resolution: "lodash.uniqby@npm:4.7.0"
checksum: c505c0de20ca759599a2ba38710e8fb95ff2d2028e24d86c901ef2c74be8056518571b9b754bfb75053b2818d30dd02243e4a4621a6940c206bbb3f7626db656
languageName: node
linkType: hard
"lodash@npm:^4.17.10, lodash@npm:^4.17.11, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.20, lodash@npm:^4.17.21": "lodash@npm:^4.17.10, lodash@npm:^4.17.11, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.20, lodash@npm:^4.17.21":
version: 4.17.21 version: 4.17.21
resolution: "lodash@npm:4.17.21" resolution: "lodash@npm:4.17.21"
@ -13678,18 +13717,18 @@ __metadata:
linkType: hard linkType: hard
"react-intl@npm:^6.4.2": "react-intl@npm:^6.4.2":
version: 6.5.4 version: 6.5.5
resolution: "react-intl@npm:6.5.4" resolution: "react-intl@npm:6.5.5"
dependencies: dependencies:
"@formatjs/ecma402-abstract": "npm:1.17.4" "@formatjs/ecma402-abstract": "npm:1.18.0"
"@formatjs/icu-messageformat-parser": "npm:2.7.2" "@formatjs/icu-messageformat-parser": "npm:2.7.3"
"@formatjs/intl": "npm:2.9.8" "@formatjs/intl": "npm:2.9.9"
"@formatjs/intl-displaynames": "npm:6.6.3" "@formatjs/intl-displaynames": "npm:6.6.4"
"@formatjs/intl-listformat": "npm:7.5.2" "@formatjs/intl-listformat": "npm:7.5.3"
"@types/hoist-non-react-statics": "npm:^3.3.1" "@types/hoist-non-react-statics": "npm:^3.3.1"
"@types/react": "npm:16 || 17 || 18" "@types/react": "npm:16 || 17 || 18"
hoist-non-react-statics: "npm:^3.3.2" hoist-non-react-statics: "npm:^3.3.2"
intl-messageformat: "npm:10.5.7" intl-messageformat: "npm:10.5.8"
tslib: "npm:^2.4.0" tslib: "npm:^2.4.0"
peerDependencies: peerDependencies:
react: ^16.6.0 || 17 || 18 react: ^16.6.0 || 17 || 18
@ -13697,7 +13736,7 @@ __metadata:
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 1117a7f866b103abf88a4087f5fe8b854d9c069c69444c592f8431e7d28c9b90423f7b50e550be0f2f173b7563e943bcc9238e80f6747181f81861275f6e2ce7 checksum: 9ff6200f195557804b735d618ee593aed7848e84213ac4eb9c57708f55c0d93232e0dd338c990348ba3b1d73dca071502a2051d4a2790838d962c3ccde87fa6c
languageName: node languageName: node
linkType: hard linkType: hard
@ -17019,29 +17058,25 @@ __metadata:
linkType: hard linkType: hard
"webpack-bundle-analyzer@npm:^4.8.0": "webpack-bundle-analyzer@npm:^4.8.0":
version: 4.9.1 version: 4.10.0
resolution: "webpack-bundle-analyzer@npm:4.9.1" resolution: "webpack-bundle-analyzer@npm:4.10.0"
dependencies: dependencies:
"@discoveryjs/json-ext": "npm:0.5.7" "@discoveryjs/json-ext": "npm:0.5.7"
acorn: "npm:^8.0.4" acorn: "npm:^8.0.4"
acorn-walk: "npm:^8.0.0" acorn-walk: "npm:^8.0.0"
commander: "npm:^7.2.0" commander: "npm:^7.2.0"
debounce: "npm:^1.2.1"
escape-string-regexp: "npm:^4.0.0" escape-string-regexp: "npm:^4.0.0"
gzip-size: "npm:^6.0.0" gzip-size: "npm:^6.0.0"
html-escaper: "npm:^2.0.2"
is-plain-object: "npm:^5.0.0" is-plain-object: "npm:^5.0.0"
lodash.debounce: "npm:^4.0.8"
lodash.escape: "npm:^4.0.1"
lodash.flatten: "npm:^4.4.0"
lodash.invokemap: "npm:^4.6.0"
lodash.pullall: "npm:^4.2.0"
lodash.uniqby: "npm:^4.7.0"
opener: "npm:^1.5.2" opener: "npm:^1.5.2"
picocolors: "npm:^1.0.0" picocolors: "npm:^1.0.0"
sirv: "npm:^2.0.3" sirv: "npm:^2.0.3"
ws: "npm:^7.3.1" ws: "npm:^7.3.1"
bin: bin:
webpack-bundle-analyzer: lib/bin/analyzer.js webpack-bundle-analyzer: lib/bin/analyzer.js
checksum: dd047c306471e6c389d6d4156ff22402e587140310a065a6191ee380f8251063f73a8ec6ac6d977c1cd634dbb717e2522b5d0b6cc9b0e847d4f15737fd9c65c9 checksum: f812a8d3c0198ce518baf742bff656526f3eae69fb7a64c7f0c9cff202f6fb3380cabf3baaae965b8d6ffbbb6fb802eacb373fca03a596a38b01b84cfb2e8329
languageName: node languageName: node
linkType: hard linkType: hard