From b14cbb3ee952b963c2be68db0de850d37729279e Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 27 Jan 2026 22:30:17 +0100 Subject: [PATCH] Merge pull request #3364 from ClearlyClaire/glitch-soc/features/local-only-drop-emoji Deprecate eye emoji in favor of a bespoke API parameter --- app/controllers/api/v1/statuses_controller.rb | 2 + .../flavours/glitch/actions/compose.js | 5 +- .../flavours/glitch/reducers/compose.js | 1 - app/models/status.rb | 8 ++- app/services/post_status_service.rb | 1 + lib/mastodon/version.rb | 1 + spec/requests/api/v1/statuses_spec.rb | 57 +++++++++++++++++++ 7 files changed, 69 insertions(+), 6 deletions(-) diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index cdb056a49c..a2db011439 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -93,6 +93,7 @@ class Api::V1::StatusesController < Api::BaseController application: doorkeeper_token.application, poll: status_params[:poll], content_type: status_params[:content_type], + local_only: status_params[:local_only], allowed_mentions: status_params[:allowed_mentions], idempotency: request.headers['Idempotency-Key'], with_rate_limit: true @@ -192,6 +193,7 @@ class Api::V1::StatusesController < Api::BaseController :language, :scheduled_at, :content_type, + :local_only, allowed_mentions: [], media_ids: [], media_attributes: [ diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js index a7d491377b..742f6467c7 100644 --- a/app/javascript/flavours/glitch/actions/compose.js +++ b/app/javascript/flavours/glitch/actions/compose.js @@ -228,10 +228,6 @@ export function submitCompose(overridePrivacy = null, successCallback = undefine return; } - if (getState().getIn(['compose', 'advanced_options', 'do_not_federate'])) { - status = status + ' 👁️'; - } - dispatch(submitComposeRequest()); // If we're editing a post with media attachments, those have not @@ -262,6 +258,7 @@ export function submitCompose(overridePrivacy = null, successCallback = undefine status, spoiler_text, content_type: getState().getIn(['compose', 'content_type']), + local_only: getState().getIn(['compose', 'advanced_options', 'do_not_federate']), in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null), media_ids: media.map(item => item.get('id')), media_attributes, diff --git a/app/javascript/flavours/glitch/reducers/compose.js b/app/javascript/flavours/glitch/reducers/compose.js index 74b1666796..eb867cd3c1 100644 --- a/app/javascript/flavours/glitch/reducers/compose.js +++ b/app/javascript/flavours/glitch/reducers/compose.js @@ -632,7 +632,6 @@ export const composeReducer = (state = initialState, action) => { case REDRAFT: { const do_not_federate = !!action.status.get('local_only'); let text = action.raw_text || unescapeHTML(expandMentions(action.status)); - if (do_not_federate) text = text.replace(/ ?👁\ufe0f?\u200b?$/, ''); return state.withMutations(map => { map.set('text', text); map.set('content_type', action.content_type || 'text/plain'); diff --git a/app/models/status.rb b/app/models/status.rb index 8761487c33..60b8f120cf 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -496,7 +496,13 @@ class Status < ApplicationRecord def set_local_only return unless account.domain.nil? && !attribute_changed?(:local_only) - self.local_only = marked_local_only? + self.local_only = true if thread&.local_only? && local_only.nil? + + if reblog? + self.local_only = reblog.local_only + elsif local_only.nil? + self.local_only = marked_local_only? + end end def set_conversation diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 12bb8a083d..404e1b4f92 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -247,6 +247,7 @@ class PostStatusService < BaseService language: valid_locale_cascade(@options[:language], @account.user&.preferred_posting_language, I18n.default_locale), application: @options[:application], content_type: @options[:content_type] || @account.user&.setting_default_content_type, + local_only: @options[:local_only], rate_limit: @options[:with_rate_limit], quote_approval_policy: @options[:quote_approval_policy], }.compact diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index f532276f85..947106aeae 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -46,6 +46,7 @@ module Mastodon def api_versions { mastodon: 7, + glitch: 1, } end diff --git a/spec/requests/api/v1/statuses_spec.rb b/spec/requests/api/v1/statuses_spec.rb index 3fbf26c54a..31ca3b910c 100644 --- a/spec/requests/api/v1/statuses_spec.rb +++ b/spec/requests/api/v1/statuses_spec.rb @@ -437,6 +437,63 @@ RSpec.describe '/api/v1/statuses' do end end end + + context 'with local_only param set to true' do + let(:params) { { status: 'Hello world', local_only: true } } + + it 'returns a local-only post' do + subject + + expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body[:content]).to match(/Hello world/) + expect(response.parsed_body[:local_only]).to be true + end + end + + context 'with local_only param set to false' do + let(:params) { { status: 'Hello world', local_only: false } } + + it 'returns a non-local-only post' do + subject + + expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body[:content]).to match(/Hello world/) + expect(response.parsed_body[:local_only]).to be false + end + end + + context 'with local_only param omitted' do + let(:params) { { status: 'Hello world' } } + + it 'returns a non-local-only post' do + subject + + expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body[:content]).to match(/Hello world/) + expect(response.parsed_body[:local_only]).to be false + end + end + + context 'with local_only param omitted in reply to a local-only post' do + let(:local_only_post) { Fabricate(:status, local_only: true) } + let(:params) { { status: 'Hello world', in_reply_to_id: local_only_post.id } } + + it 'returns a non-local-only post' do + subject + + expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body[:content]).to match(/Hello world/) + expect(response.parsed_body[:local_only]).to be true + end + end end describe 'DELETE /api/v1/statuses/:id' do