From 84c5ffb565b9aff3d143d3316315773bc9a805db Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 19 Aug 2025 16:16:30 +0200 Subject: [PATCH 1/8] Fix self-destruct scheduler behavior on some Redis setups (#35823) --- app/workers/scheduler/self_destruct_scheduler.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/workers/scheduler/self_destruct_scheduler.rb b/app/workers/scheduler/self_destruct_scheduler.rb index d0b6ce8a07..12cb60135b 100644 --- a/app/workers/scheduler/self_destruct_scheduler.rb +++ b/app/workers/scheduler/self_destruct_scheduler.rb @@ -21,8 +21,9 @@ class Scheduler::SelfDestructScheduler def sidekiq_overwhelmed? redis_mem_info = Sidekiq.redis_info + maxmemory = [redis_mem_info['maxmemory'].to_f, redis_mem_info['total_system_memory'].to_f].filter(&:positive?).min - Sidekiq::Stats.new.enqueued > MAX_ENQUEUED || redis_mem_info['used_memory'].to_f > redis_mem_info['total_system_memory'].to_f * MAX_REDIS_MEM_USAGE + Sidekiq::Stats.new.enqueued > MAX_ENQUEUED || redis_mem_info['used_memory'].to_f > maxmemory * MAX_REDIS_MEM_USAGE end def delete_accounts! From d2b544e5843589db60e440fb6c638e7d647d68b3 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 25 Aug 2025 14:25:35 +0200 Subject: [PATCH 2/8] =?UTF-8?q?Fix=20Edit=20as=20well=20as=20=E2=80=9CDele?= =?UTF-8?q?te=20&=20Redraft=E2=80=9D=20on=20a=20poll=20not=20inserting=20e?= =?UTF-8?q?mpty=20option=20(#35892)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/javascript/mastodon/actions/compose.js | 15 ++++++++++----- app/javascript/mastodon/actions/statuses.js | 13 +++++++++---- app/javascript/mastodon/reducers/compose.js | 14 ++++++++++++-- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index aa1c6de20e..5f99182cc9 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -96,11 +96,16 @@ export const ensureComposeIsVisible = (getState) => { }; export function setComposeToStatus(status, text, spoiler_text) { - return{ - type: COMPOSE_SET_STATUS, - status, - text, - spoiler_text, + return (dispatch, getState) => { + const maxOptions = getState().server.getIn(['server', 'configuration', 'polls', 'max_options']); + + dispatch({ + type: COMPOSE_SET_STATUS, + status, + text, + spoiler_text, + maxOptions, + }); }; } diff --git a/app/javascript/mastodon/actions/statuses.js b/app/javascript/mastodon/actions/statuses.js index 1e4e545d8c..cc50a6b622 100644 --- a/app/javascript/mastodon/actions/statuses.js +++ b/app/javascript/mastodon/actions/statuses.js @@ -90,10 +90,15 @@ export function fetchStatusFail(id, error, skipLoading) { } export function redraft(status, raw_text) { - return { - type: REDRAFT, - status, - raw_text, + return (dispatch, getState) => { + const maxOptions = getState().server.getIn(['server', 'configuration', 'polls', 'max_options']); + + dispatch({ + type: REDRAFT, + status, + raw_text, + maxOptions, + }); }; } diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index bfa2ec6a06..8b5e131511 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -504,8 +504,13 @@ export default function compose(state = initialState, action) { } if (action.status.get('poll')) { + let options = ImmutableList(action.status.get('poll').options.map(x => x.title)); + if (options.size < action.maxOptions) { + options = options.push(''); + } + map.set('poll', ImmutableMap({ - options: action.status.getIn(['poll', 'options']).map(x => x.get('title')), + options: options, multiple: action.status.getIn(['poll', 'multiple']), expires_in: expiresInFromExpiresAt(action.status.getIn(['poll', 'expires_at'])), })); @@ -533,8 +538,13 @@ export default function compose(state = initialState, action) { } if (action.status.get('poll')) { + let options = ImmutableList(action.status.get('poll').options.map(x => x.title)); + if (options.size < action.maxOptions) { + options = options.push(''); + } + map.set('poll', ImmutableMap({ - options: action.status.getIn(['poll', 'options']).map(x => x.get('title')), + options: options, multiple: action.status.getIn(['poll', 'multiple']), expires_in: expiresInFromExpiresAt(action.status.getIn(['poll', 'expires_at'])), })); From a3d4b7c9b9759e12d3e2bc3b4afbec195647caf9 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 27 Aug 2025 10:13:34 +0200 Subject: [PATCH 3/8] Fix API return types for interaction API helpers (#35915) --- app/javascript/mastodon/api/interactions.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/javascript/mastodon/api/interactions.ts b/app/javascript/mastodon/api/interactions.ts index 118b5f06d2..62808dcddc 100644 --- a/app/javascript/mastodon/api/interactions.ts +++ b/app/javascript/mastodon/api/interactions.ts @@ -1,10 +1,11 @@ import { apiRequestPost } from 'mastodon/api'; -import type { Status, StatusVisibility } from 'mastodon/models/status'; +import type { ApiStatusJSON } from 'mastodon/api_types/statuses'; +import type { StatusVisibility } from 'mastodon/models/status'; export const apiReblog = (statusId: string, visibility: StatusVisibility) => - apiRequestPost<{ reblog: Status }>(`v1/statuses/${statusId}/reblog`, { + apiRequestPost<{ reblog: ApiStatusJSON }>(`v1/statuses/${statusId}/reblog`, { visibility, }); export const apiUnreblog = (statusId: string) => - apiRequestPost(`v1/statuses/${statusId}/unreblog`); + apiRequestPost(`v1/statuses/${statusId}/unreblog`); From 6b78be274b3410b8657e44edd6e95dd628dbe971 Mon Sep 17 00:00:00 2001 From: fiona Date: Tue, 2 Sep 2025 12:25:55 +0000 Subject: [PATCH 4/8] Fix handling of edited status with new media and no text (#35970) --- .../process_status_update_service.rb | 2 ++ .../process_status_update_service_spec.rb | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index 80d5ed68d3..8792b94550 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -100,6 +100,8 @@ class ActivityPub::ProcessStatusUpdateService < BaseService @status.ordered_media_attachment_ids = @next_media_attachments.map(&:id) @media_attachments_changed = true if @status.ordered_media_attachment_ids != previous_media_attachments_ids + + @status.media_attachments.reload if @media_attachments_changed end def download_media_files! diff --git a/spec/services/activitypub/process_status_update_service_spec.rb b/spec/services/activitypub/process_status_update_service_spec.rb index abf9c1dd93..2c880365ce 100644 --- a/spec/services/activitypub/process_status_update_service_spec.rb +++ b/spec/services/activitypub/process_status_update_service_spec.rb @@ -343,6 +343,42 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do end end + context 'when originally without media attachments and text is removed' do + before do + stub_request(:get, 'https://example.com/foo.png').to_return(body: attachment_fixture('emojo.png')) + end + + let(:payload) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: 'foo', + type: 'Note', + content: '', + updated: '2021-09-08T22:39:25Z', + attachment: [ + { type: 'Image', mediaType: 'image/png', url: 'https://example.com/foo.png' }, + ], + } + end + + it 'updates media attachments, fetches attachment, records media and text removal in edit' do + subject.call(status, json, json) + + expect(status.reload.ordered_media_attachments.first) + .to be_present + .and(have_attributes(remote_url: 'https://example.com/foo.png')) + + expect(a_request(:get, 'https://example.com/foo.png')) + .to have_been_made + + expect(status.edits.reload.last.ordered_media_attachment_ids) + .to_not be_empty + + expect(status.edits.reload.last.text) + .to_not be_present + end + end + context 'when originally with media attachments' do let(:media_attachments) { [Fabricate(:media_attachment, remote_url: 'https://example.com/foo.png'), Fabricate(:media_attachment, remote_url: 'https://example.com/unused.png')] } From 6e406e119fcfa0312b5fb0e52f73465a2cceb406 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 25 Aug 2025 14:25:35 +0200 Subject: [PATCH 5/8] =?UTF-8?q?[Glitch]=20Fix=20Edit=20as=20well=20as=20?= =?UTF-8?q?=E2=80=9CDelete=20&=20Redraft=E2=80=9D=20on=20a=20poll=20not=20?= =?UTF-8?q?inserting=20empty=20option?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port a48567784c1d57b6f5016697514812af85389d2e to glitch-soc Signed-off-by: Claire --- .../flavours/glitch/actions/compose.js | 17 +++++++++++------ .../flavours/glitch/actions/statuses.js | 15 ++++++++++----- .../flavours/glitch/reducers/compose.js | 14 ++++++++++++-- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js index e850a83a85..06c8ed5186 100644 --- a/app/javascript/flavours/glitch/actions/compose.js +++ b/app/javascript/flavours/glitch/actions/compose.js @@ -102,12 +102,17 @@ export const ensureComposeIsVisible = (getState) => { }; export function setComposeToStatus(status, text, spoiler_text, content_type) { - return{ - type: COMPOSE_SET_STATUS, - status, - text, - spoiler_text, - content_type, + return (dispatch, getState) => { + const maxOptions = getState().server.getIn(['server', 'configuration', 'polls', 'max_options']); + + dispatch({ + type: COMPOSE_SET_STATUS, + status, + text, + spoiler_text, + content_type, + maxOptions + }); }; } diff --git a/app/javascript/flavours/glitch/actions/statuses.js b/app/javascript/flavours/glitch/actions/statuses.js index 2b6b589b37..e852b54855 100644 --- a/app/javascript/flavours/glitch/actions/statuses.js +++ b/app/javascript/flavours/glitch/actions/statuses.js @@ -90,11 +90,16 @@ export function fetchStatusFail(id, error, skipLoading) { } export function redraft(status, raw_text, content_type) { - return { - type: REDRAFT, - status, - raw_text, - content_type, + return (dispatch, getState) => { + const maxOptions = getState().server.getIn(['server', 'configuration', 'polls', 'max_options']); + + dispatch({ + type: REDRAFT, + status, + raw_text, + content_type, + maxOptions, + }); }; } diff --git a/app/javascript/flavours/glitch/reducers/compose.js b/app/javascript/flavours/glitch/reducers/compose.js index 04a2857836..d32dc604da 100644 --- a/app/javascript/flavours/glitch/reducers/compose.js +++ b/app/javascript/flavours/glitch/reducers/compose.js @@ -623,8 +623,13 @@ export default function compose(state = initialState, action) { } if (action.status.get('poll')) { + let options = ImmutableList(action.status.get('poll').options.map(x => x.title)); + if (options.size < action.maxOptions) { + options = options.push(''); + } + map.set('poll', ImmutableMap({ - options: action.status.getIn(['poll', 'options']).map(x => x.get('title')), + options: options, multiple: action.status.getIn(['poll', 'multiple']), expires_in: expiresInFromExpiresAt(action.status.getIn(['poll', 'expires_at'])), })); @@ -653,8 +658,13 @@ export default function compose(state = initialState, action) { } if (action.status.get('poll')) { + let options = ImmutableList(action.status.get('poll').options.map(x => x.title)); + if (options.size < action.maxOptions) { + options = options.push(''); + } + map.set('poll', ImmutableMap({ - options: action.status.getIn(['poll', 'options']).map(x => x.get('title')), + options: options, multiple: action.status.getIn(['poll', 'multiple']), expires_in: expiresInFromExpiresAt(action.status.getIn(['poll', 'expires_at'])), })); From 0eeb0f00bade5b43c0823207be964419c30ca691 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 27 Aug 2025 10:13:34 +0200 Subject: [PATCH 6/8] [Glitch] Fix API return types for interaction API helpers Port 8777443c9bec554bc50d222511df7915c57c4170 to glitch-soc Signed-off-by: Claire --- app/javascript/flavours/glitch/api/interactions.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/javascript/flavours/glitch/api/interactions.ts b/app/javascript/flavours/glitch/api/interactions.ts index 172f97a256..75092f4e72 100644 --- a/app/javascript/flavours/glitch/api/interactions.ts +++ b/app/javascript/flavours/glitch/api/interactions.ts @@ -1,10 +1,11 @@ import { apiRequestPost } from 'flavours/glitch/api'; -import type { Status, StatusVisibility } from 'flavours/glitch/models/status'; +import type { ApiStatusJSON } from 'flavours/glitch/api_types/statuses'; +import type { StatusVisibility } from 'flavours/glitch/models/status'; export const apiReblog = (statusId: string, visibility: StatusVisibility) => - apiRequestPost<{ reblog: Status }>(`v1/statuses/${statusId}/reblog`, { + apiRequestPost<{ reblog: ApiStatusJSON }>(`v1/statuses/${statusId}/reblog`, { visibility, }); export const apiUnreblog = (statusId: string) => - apiRequestPost(`v1/statuses/${statusId}/unreblog`); + apiRequestPost(`v1/statuses/${statusId}/unreblog`); From 055be70c5908a030196d21201fc50705a5409a19 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 5 Sep 2025 12:17:02 +0200 Subject: [PATCH 7/8] Fix editing or deleting and redrafting polls in 4.3 (#36036) --- app/javascript/mastodon/reducers/compose.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index 8b5e131511..3275e16049 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -504,7 +504,7 @@ export default function compose(state = initialState, action) { } if (action.status.get('poll')) { - let options = ImmutableList(action.status.get('poll').options.map(x => x.title)); + let options = action.status.getIn(['poll', 'options']).map(x => x.get('title')); if (options.size < action.maxOptions) { options = options.push(''); } @@ -538,7 +538,7 @@ export default function compose(state = initialState, action) { } if (action.status.get('poll')) { - let options = ImmutableList(action.status.get('poll').options.map(x => x.title)); + let options = action.status.getIn(['poll', 'options']).map(x => x.get('title')); if (options.size < action.maxOptions) { options = options.push(''); } From d595641b733ad48d579065bc2b6628134711040f Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 5 Sep 2025 12:17:02 +0200 Subject: [PATCH 8/8] [Glitch] Fix editing or deleting and redrafting polls in 4.3 Port 055be70c5908a030196d21201fc50705a5409a19 to glitch-soc Signed-off-by: Claire --- app/javascript/flavours/glitch/reducers/compose.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/flavours/glitch/reducers/compose.js b/app/javascript/flavours/glitch/reducers/compose.js index d32dc604da..6e2b6674e4 100644 --- a/app/javascript/flavours/glitch/reducers/compose.js +++ b/app/javascript/flavours/glitch/reducers/compose.js @@ -623,7 +623,7 @@ export default function compose(state = initialState, action) { } if (action.status.get('poll')) { - let options = ImmutableList(action.status.get('poll').options.map(x => x.title)); + let options = action.status.getIn(['poll', 'options']).map(x => x.get('title')); if (options.size < action.maxOptions) { options = options.push(''); } @@ -658,7 +658,7 @@ export default function compose(state = initialState, action) { } if (action.status.get('poll')) { - let options = ImmutableList(action.status.get('poll').options.map(x => x.title)); + let options = action.status.getIn(['poll', 'options']).map(x => x.get('title')); if (options.size < action.maxOptions) { options = options.push(''); }