From 72abf052693ed0c70c7437da24ce860b8f81fad2 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 16 Jan 2025 04:00:04 -0500 Subject: [PATCH 1/5] Add "needs refresh" scenario to `api/v1/polls` request spec (#33608) --- spec/requests/api/v1/polls_spec.rb | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/spec/requests/api/v1/polls_spec.rb b/spec/requests/api/v1/polls_spec.rb index fd38297931..c93231e1ee 100644 --- a/spec/requests/api/v1/polls_spec.rb +++ b/spec/requests/api/v1/polls_spec.rb @@ -36,6 +36,31 @@ RSpec.describe 'Polls' do end end + context 'when poll is remote and needs refresh' do + let(:poll) { Fabricate(:poll, last_fetched_at: nil, account: remote_account, status: status) } + let(:remote_account) { Fabricate :account, domain: 'host.example' } + let(:service) { instance_double(ActivityPub::FetchRemotePollService, call: nil) } + let(:status) { Fabricate(:status, visibility: 'public', account: remote_account) } + + before { allow(ActivityPub::FetchRemotePollService).to receive(:new).and_return(service) } + + it 'returns poll data and calls fetch remote service' do + subject + + expect(response) + .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body).to match( + a_hash_including( + id: poll.id.to_s + ) + ) + expect(service) + .to have_received(:call).with(poll, user.account) + end + end + context 'when parent status is private' do let(:visibility) { 'private' } From 998cf0dd53de5d19ef47c3e8de575d07b05f42eb Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 16 Jan 2025 04:03:46 -0500 Subject: [PATCH 2/5] Convert `auth/setup` spec controller->system/request (#33604) --- app/controllers/auth/setup_controller.rb | 2 +- .../controllers/auth/setup_controller_spec.rb | 25 ---------------- spec/requests/auth/setup_spec.rb | 27 +++++++++++++++++ spec/system/auth/setup_spec.rb | 30 +++++++++++++++++++ 4 files changed, 58 insertions(+), 26 deletions(-) delete mode 100644 spec/controllers/auth/setup_controller_spec.rb create mode 100644 spec/requests/auth/setup_spec.rb create mode 100644 spec/system/auth/setup_spec.rb diff --git a/app/controllers/auth/setup_controller.rb b/app/controllers/auth/setup_controller.rb index ad872dc607..376a30c16f 100644 --- a/app/controllers/auth/setup_controller.rb +++ b/app/controllers/auth/setup_controller.rb @@ -18,7 +18,7 @@ class Auth::SetupController < ApplicationController if @user.update(user_params) @user.resend_confirmation_instructions unless @user.confirmed? - redirect_to auth_setup_path, notice: I18n.t('auth.setup.new_confirmation_instructions_sent') + redirect_to auth_setup_path, notice: t('auth.setup.new_confirmation_instructions_sent') else render :show end diff --git a/spec/controllers/auth/setup_controller_spec.rb b/spec/controllers/auth/setup_controller_spec.rb deleted file mode 100644 index 28b07cb4b2..0000000000 --- a/spec/controllers/auth/setup_controller_spec.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Auth::SetupController do - render_views - - describe 'GET #show' do - context 'with a signed out request' do - it 'returns http redirect' do - get :show - expect(response).to be_redirect - end - end - - context 'with an unconfirmed signed in user' do - before { sign_in Fabricate(:user, confirmed_at: nil) } - - it 'returns http success' do - get :show - expect(response).to have_http_status(200) - end - end - end -end diff --git a/spec/requests/auth/setup_spec.rb b/spec/requests/auth/setup_spec.rb new file mode 100644 index 0000000000..fa3c196805 --- /dev/null +++ b/spec/requests/auth/setup_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Auth Setup' do + describe 'GET /auth/setup' do + context 'with a signed out request' do + it 'redirects to root' do + get '/auth/setup' + + expect(response) + .to redirect_to(new_user_session_url) + end + end + + context 'with a confirmed signed in user' do + before { sign_in Fabricate(:user, confirmed_at: 2.days.ago) } + + it 'redirects to root' do + get '/auth/setup' + + expect(response) + .to redirect_to(root_url) + end + end + end +end diff --git a/spec/system/auth/setup_spec.rb b/spec/system/auth/setup_spec.rb new file mode 100644 index 0000000000..154f8cd5fa --- /dev/null +++ b/spec/system/auth/setup_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Auth Setup' do + context 'with an unconfirmed signed in user' do + let(:user) { Fabricate(:user, confirmed_at: nil) } + + before { sign_in(user) } + + it 'can update email address' do + visit auth_setup_path + + expect(page) + .to have_content(I18n.t('auth.setup.title')) + + find('summary.lead').click + fill_in 'user_email', with: 'new-email@example.host' + + expect { submit_form } + .to(change { user.reload.unconfirmed_email }) + expect(page) + .to have_content(I18n.t('auth.setup.new_confirmation_instructions_sent')) + end + + def submit_form + find('[name=button]').click + end + end +end From 3db84989039c2dbba5dc6797092fa7cbbbcb7db8 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 16 Jan 2025 04:09:06 -0500 Subject: [PATCH 3/5] Fix `Style/MutableConstant` cop (#33602) --- .rubocop_todo.yml | 9 --------- app/models/tag.rb | 8 ++++---- app/services/delete_account_service.rb | 2 +- lib/mastodon/migration_warning.rb | 2 +- 4 files changed, 6 insertions(+), 15 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 38aec67bef..f2d3418b64 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -69,15 +69,6 @@ Style/MapToHash: Exclude: - 'app/models/status.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: literals, strict -Style/MutableConstant: - Exclude: - - 'app/models/tag.rb' - - 'app/services/delete_account_service.rb' - - 'lib/mastodon/migration_warning.rb' - # Configuration parameters: AllowedMethods. # AllowedMethods: respond_to_missing? Style/OptionalBooleanParameter: diff --git a/app/models/tag.rb b/app/models/tag.rb index c9115b905b..d29cd220f0 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -35,11 +35,11 @@ class Tag < ApplicationRecord has_one :trend, class_name: 'TagTrend', inverse_of: :tag, dependent: :destroy HASHTAG_SEPARATORS = "_\u00B7\u30FB\u200c" - HASHTAG_FIRST_SEQUENCE_CHUNK_ONE = "[[:word:]_][[:word:]#{HASHTAG_SEPARATORS}]*[[:alpha:]#{HASHTAG_SEPARATORS}]" - HASHTAG_FIRST_SEQUENCE_CHUNK_TWO = "[[:word:]#{HASHTAG_SEPARATORS}]*[[:word:]_]" - HASHTAG_FIRST_SEQUENCE = "(#{HASHTAG_FIRST_SEQUENCE_CHUNK_ONE}#{HASHTAG_FIRST_SEQUENCE_CHUNK_TWO})" + HASHTAG_FIRST_SEQUENCE_CHUNK_ONE = "[[:word:]_][[:word:]#{HASHTAG_SEPARATORS}]*[[:alpha:]#{HASHTAG_SEPARATORS}]".freeze + HASHTAG_FIRST_SEQUENCE_CHUNK_TWO = "[[:word:]#{HASHTAG_SEPARATORS}]*[[:word:]_]".freeze + HASHTAG_FIRST_SEQUENCE = "(#{HASHTAG_FIRST_SEQUENCE_CHUNK_ONE}#{HASHTAG_FIRST_SEQUENCE_CHUNK_TWO})".freeze HASHTAG_LAST_SEQUENCE = '([[:word:]_]*[[:alpha:]][[:word:]_]*)' - HASHTAG_NAME_PAT = "#{HASHTAG_FIRST_SEQUENCE}|#{HASHTAG_LAST_SEQUENCE}" + HASHTAG_NAME_PAT = "#{HASHTAG_FIRST_SEQUENCE}|#{HASHTAG_LAST_SEQUENCE}".freeze HASHTAG_RE = %r{(? Date: Thu, 16 Jan 2025 10:13:39 +0100 Subject: [PATCH 4/5] New Crowdin Translations (automated) (#33609) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/sk.json | 9 +++++++++ config/locales/activerecord.de.yml | 2 ++ config/locales/eo.yml | 7 +++++++ config/locales/simple_form.de.yml | 1 + config/locales/sk.yml | 2 ++ 5 files changed, 21 insertions(+) diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index d0617a52c8..00eb948985 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -378,6 +378,8 @@ "ignore_notifications_modal.not_followers_title": "Nevšímať si oznámenia od ľudí, ktorí ťa nenasledujú?", "ignore_notifications_modal.not_following_title": "Nevšímať si oznámenia od ľudí, ktorých nenasleduješ?", "ignore_notifications_modal.private_mentions_title": "Nevšímať si oznámenia o nevyžiadaných súkromných spomínaniach?", + "interaction_modal.action.favourite": "Pre pokračovanie si musíš obľúbiť zo svojho účtu.", + "interaction_modal.action.follow": "Pre pokračovanie musíš nasledovať zo svojho účtu.", "interaction_modal.action.reply": "Pre pokračovanie musíš odpovedať s tvojho účtu.", "interaction_modal.action.vote": "Pre pokračovanie musíš hlasovať s tvojho účtu.", "interaction_modal.go": "Prejdi", @@ -389,6 +391,7 @@ "interaction_modal.title.reblog": "Zdieľať príspevok od {name}", "interaction_modal.title.reply": "Odpovedať na príspevok od {name}", "interaction_modal.title.vote": "Hlasuj v ankete od {name}", + "interaction_modal.username_prompt": "Napr. {example}", "intervals.full.days": "{number, plural, one {# deň} few {# dni} many {# dní} other {# dní}}", "intervals.full.hours": "{number, plural, one {# hodina} few {# hodiny} many {# hodín} other {# hodín}}", "intervals.full.minutes": "{number, plural, one {# minúta} few {# minúty} many {# minút} other {# minút}}", @@ -517,6 +520,7 @@ "notification.moderation_warning": "Dostal/a si varovanie od moderátora", "notification.moderation_warning.action_delete_statuses": "Niektoré z tvojich príspevkov boli odstránené.", "notification.moderation_warning.action_disable": "Tvoj účet bol vypnutý.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Niektoré tvoje príspevky boli označené za chúlostivé.", "notification.moderation_warning.action_none": "Tvoj účet dostal upozornenie od moderátora.", "notification.moderation_warning.action_sensitive": "Tvoje príspevky budú odteraz označované ako chúlostivé.", "notification.moderation_warning.action_silence": "Tvoj účet bol obmedzený.", @@ -575,9 +579,11 @@ "notifications.policy.accept_hint": "Ukáž v oznámeniach", "notifications.policy.drop": "Ignoruj", "notifications.policy.filter": "Triediť", + "notifications.policy.filter_limited_accounts_hint": "Obmedzené moderátormi servera", "notifications.policy.filter_limited_accounts_title": "Moderované účty", "notifications.policy.filter_new_accounts_title": "Nové účty", "notifications.policy.filter_not_followers_title": "Ľudia, ktorí ťa nenasledujú", + "notifications.policy.filter_not_following_hint": "Pokiaľ ich ručne neschváliš", "notifications.policy.filter_not_following_title": "Ľudia, ktorých nenasleduješ", "notifications.policy.filter_private_mentions_title": "Nevyžiadané priame spomenutia", "notifications.policy.title": "Spravuj oznámenia od…", @@ -625,6 +631,7 @@ "privacy_policy.title": "Pravidlá ochrany súkromia", "recommended": "Odporúčané", "refresh": "Obnoviť", + "regeneration_indicator.please_stand_by": "Prosím, čakajte.", "regeneration_indicator.preparing_your_home_feed": "Pripravuje sa tvoj domáci kanál…", "relative_time.days": "{number} dní", "relative_time.full.days": "Pred {number, plural, one {# dňom} other {# dňami}}", @@ -716,6 +723,7 @@ "server_banner.about_active_users": "Ľudia používajúci tento server za posledných 30 dní (aktívni používatelia za mesiac)", "server_banner.active_users": "Aktívne účty", "server_banner.administered_by": "Správa servera:", + "server_banner.is_one_of_many": "{domain} je jeden z mnohých nezávislých Mastodon serverov, ktoré môžeš použiť na zúčastňovanie sa v rámci fediversa.", "server_banner.server_stats": "Štatistiky servera:", "sign_in_banner.create_account": "Vytvoriť účet", "sign_in_banner.sign_in": "Prihlásiť sa", @@ -758,6 +766,7 @@ "status.reblogs.empty": "Nikto ešte tento príspevok nezdieľal. Keď tak niekto urobí, zobrazí sa to tu.", "status.redraft": "Vymazať a prepísať", "status.remove_bookmark": "Odstrániť záložku", + "status.remove_favourite": "Odstráň z obľúbených", "status.replied_in_thread": "Odpovedal/a vo vlákne", "status.replied_to": "Odpoveď na {name}", "status.reply": "Odpovedať", diff --git a/config/locales/activerecord.de.yml b/config/locales/activerecord.de.yml index bdd916634a..99d50d49e4 100644 --- a/config/locales/activerecord.de.yml +++ b/config/locales/activerecord.de.yml @@ -24,6 +24,8 @@ de: models: account: attributes: + fields: + fields_with_values_missing_labels: enthält Werte, bei denen Beschriftungen fehlen username: invalid: nur Buchstaben, Ziffern und Unterstriche reserved: ist bereits vergeben diff --git a/config/locales/eo.yml b/config/locales/eo.yml index ee45921aaa..400b03958e 100644 --- a/config/locales/eo.yml +++ b/config/locales/eo.yml @@ -936,17 +936,22 @@ eo: generate: Uzi ŝablonon generates: action: Generi + chance_to_review_html: "La generataj kondiĉoj de uzado ne aŭtomate publikiĝos. Estos oportuni por vi kontroli la rezultojn. Bonvole entajpu la necesajn detalojn por daŭrigi." + explanation_html: La modelo por la kondiĉoj de la servo disponeblas sole por informi. Ĝi nepre ne estas leĝa konsilo pri iu ajn temo. Bonvole konsultu vian propran leĝan konsilanton pri via situacio kaj iuj leĝaj neklarecoj. title: Agordo de kondiĉoj de uzado history: Historio live: Antaŭmontro no_history: Ankoraŭ ne estas registritaj ŝanĝoj de la kondiĉoj de la servo. no_terms_of_service_html: Vi nuntempe ne havas iujn ajn kondiĉojn de la servo agordita. La kondiĉoj de la servo celas doni klarecon kaj protekti vin kontraŭ eblaj respondecoj en disputoj kun viaj uzantoj. + notified_on_html: Uzantojn sciigis je %{date} notify_users: Informu uzantojn preview: + explanation_html: 'La retmesaĝo estos alsendata al %{display_count} uzantoj, kiuj kreis konton antaŭ %{date}. La sekvonta teksto inkluziviĝos en la retmesaĝo:' send_preview: Sendu antaŭrigardon al %{email} send_to_all: one: Sendi %{display_count} retpoŝton other: Sendi %{display_count} retpoŝtojn + title: Antaŭmontri sciigon pri la kondiĉoj de la servo publish: Publikigi published_on_html: Publikigita je %{date} save_draft: Konservi malneton @@ -1930,6 +1935,8 @@ eo: subject: Via konto estas alirita de nova IP-adreso title: Nova saluto terms_of_service_changed: + agreement: Se vi daŭrige uzos %{domain}, vi aŭtomate interkonsentos pri ĉi tiuj kondiĉoj. Se vi malkonsentas pri la novaj kondiĉoj, vi ĉiutempe rajtas nuligi la interkonsenton kun %{domain} per forigi vian konton. + changelog: 'Facile dirite, la ŝanĝoj estas la jenaj:' description: 'Vi ricevas ĉi tiun retmesaĝon ĉar ni faras iujn ŝanĝojn al niaj servokondiĉoj ĉe %{domain}. Ni instigas vin revizii la ĝisdatigitajn kondiĉojn tute ĉi tie:' description_html: Vi ricevas ĉi tiun retmesaĝon ĉar ni faras iujn ŝanĝojn al niaj servokondiĉoj ĉe %{domain}. Ni instigas vin revizii la ĝisdatigitajn kondiĉojn plene ĉi tie. sign_off: La teamo de %{domain} diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml index 201af831ad..ea0681e1af 100644 --- a/config/locales/simple_form.de.yml +++ b/config/locales/simple_form.de.yml @@ -137,6 +137,7 @@ de: admin_email: Rechtliche Hinweise umfassen Gegendarstellungen, Gerichtsbeschlüsse, Anfragen zum Herunternehmen von Inhalten und Anfragen von Strafverfolgungsbehörden. arbitration_address: Kann wie die Anschrift hierüber sein oder „N/A“, falls eine E-Mail verwendet wird arbitration_website: Kann ein Webformular sein oder „N/A“, falls eine E-Mail verwendet wird + dmca_address: US-Betreiber sollten die im „DMCA Designated Agent Directory“ eingetragene Adresse verwenden. Eine Postfachadresse ist auf direkte Anfrage verfügbar. Verwenden Sie die „DMCA Designated Agent Post Box Waiver“-Anfrage, um per E-Mail die Urheberrechtsbehörde darüber zu unterrichten, dass Sie Inhalte per Heimarbeit moderieren, eventuelle Rache oder Vergeltung für Ihre Handlungen befürchten und deshalb eine Postfachadresse benötigen, um Ihre Privatadresse nicht preiszugeben. dmca_email: Kann dieselbe E-Mail wie bei „E-Mail-Adresse für rechtliche Hinweise“ sein domain: Einzigartige Identifizierung des angebotenen Online-Services. jurisdiction: Gib das Land an, in dem die Person lebt, die alle Rechnungen bezahlt. Falls es sich dabei um ein Unternehmen oder eine andere Einrichtung handelt, gib das Land mit dem Sitz an, sowie die Stadt oder Region. diff --git a/config/locales/sk.yml b/config/locales/sk.yml index 72ff7af37f..e7fb218e2d 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -28,6 +28,7 @@ sk: admin: account_actions: action: Vykonaj + already_silenced: Tento účet už bol obmedzený. title: Vykonaj moderovací úkon voči %{acct} account_moderation_notes: create: Zanechaj poznámku @@ -204,6 +205,7 @@ sk: enable_user: Povoľ užívateľa memorialize_account: Zmena na „in memoriam“ promote_user: Povýš užívateľskú rolu + publish_terms_of_service: Zverejni podmienky prevozu reject_appeal: Zamietni námietku reject_user: Zamietni užívateľa remove_avatar_user: Vymaž avatar From da4e55eb17e459fbc6d1a19fac3303508324324c Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 16 Jan 2025 11:10:08 +0100 Subject: [PATCH 5/5] Merge commit from fork --- app/lib/delivery_failure_tracker.rb | 2 ++ .../activitypub/process_account_service.rb | 27 ++++++++++++++----- spec/lib/delivery_failure_tracker_spec.rb | 4 +-- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/app/lib/delivery_failure_tracker.rb b/app/lib/delivery_failure_tracker.rb index e17b45d667..96292923f4 100644 --- a/app/lib/delivery_failure_tracker.rb +++ b/app/lib/delivery_failure_tracker.rb @@ -46,6 +46,8 @@ class DeliveryFailureTracker urls.reject do |url| host = Addressable::URI.parse(url).normalized_host unavailable_domains_map[host] + rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError + true end end diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index df6f23c021..e5c2319728 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -9,6 +9,8 @@ class ActivityPub::ProcessAccountService < BaseService SUBDOMAINS_RATELIMIT = 10 DISCOVERIES_PER_REQUEST = 400 + VALID_URI_SCHEMES = %w(http https).freeze + # Should be called with confirmed valid JSON # and WebFinger-resolved username and domain def call(username, domain, json, options = {}) @@ -96,16 +98,28 @@ class ActivityPub::ProcessAccountService < BaseService end def set_immediate_protocol_attributes! - @account.inbox_url = @json['inbox'] || '' - @account.outbox_url = @json['outbox'] || '' - @account.shared_inbox_url = (@json['endpoints'].is_a?(Hash) ? @json['endpoints']['sharedInbox'] : @json['sharedInbox']) || '' - @account.followers_url = @json['followers'] || '' + @account.inbox_url = valid_collection_uri(@json['inbox']) + @account.outbox_url = valid_collection_uri(@json['outbox']) + @account.shared_inbox_url = valid_collection_uri(@json['endpoints'].is_a?(Hash) ? @json['endpoints']['sharedInbox'] : @json['sharedInbox']) + @account.followers_url = valid_collection_uri(@json['followers']) @account.url = url || @uri @account.uri = @uri @account.actor_type = actor_type @account.created_at = @json['published'] if @json['published'].present? end + def valid_collection_uri(uri) + uri = uri.first if uri.is_a?(Array) + uri = uri['id'] if uri.is_a?(Hash) + return '' unless uri.is_a?(String) + + parsed_uri = Addressable::URI.parse(uri) + + VALID_URI_SCHEMES.include?(parsed_uri.scheme) && parsed_uri.host.present? ? parsed_uri : '' + rescue Addressable::URI::InvalidURIError + '' + end + def set_immediate_attributes! @account.featured_collection_url = @json['featured'] || '' @account.display_name = @json['name'] || '' @@ -268,10 +282,11 @@ class ActivityPub::ProcessAccountService < BaseService end def collection_info(type) - return [nil, nil] if @json[type].blank? + collection_uri = valid_collection_uri(@json[type]) + return [nil, nil] if collection_uri.blank? return @collections[type] if @collections.key?(type) - collection = fetch_resource_without_id_validation(@json[type]) + collection = fetch_resource_without_id_validation(collection_uri) total_items = collection.is_a?(Hash) && collection['totalItems'].present? && collection['totalItems'].is_a?(Numeric) ? collection['totalItems'] : nil has_first_page = collection.is_a?(Hash) && collection['first'].present? diff --git a/spec/lib/delivery_failure_tracker_spec.rb b/spec/lib/delivery_failure_tracker_spec.rb index 40c8adc4c8..34912c8133 100644 --- a/spec/lib/delivery_failure_tracker_spec.rb +++ b/spec/lib/delivery_failure_tracker_spec.rb @@ -42,8 +42,8 @@ RSpec.describe DeliveryFailureTracker do Fabricate(:unavailable_domain, domain: 'foo.bar') end - it 'removes URLs that are unavailable' do - results = described_class.without_unavailable(['http://example.com/good/inbox', 'http://foo.bar/unavailable/inbox']) + it 'removes URLs that are bogus or unavailable' do + results = described_class.without_unavailable(['http://example.com/good/inbox', 'http://foo.bar/unavailable/inbox', '{foo:']) expect(results).to include('http://example.com/good/inbox') expect(results).to_not include('http://foo.bar/unavailable/inbox')