Skip to content

Commit 40dd185

Browse files
authored
Merge pull request #1061 from kmycode/kb-draft-20.5
Release: 20.5
2 parents 2be210d + 7d1139f commit 40dd185

File tree

11 files changed

+266
-10
lines changed

11 files changed

+266
-10
lines changed

CHANGELOG.md

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,121 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [4.5.0] - UNRELEASED
6+
7+
### Added
8+
9+
- **Add support for allowing and authoring quotes** (#35355, #35578, #35614, #35618, #35624, #35626, #35652, #35629, #35665, #35653, #35670, #35677, #35690, #35697, #35689, #35699, #35700, #35701, #35709, #35714, #35713, #35715, #35725, #35749, #35769, #35780, #35762, #35804, #35808, #35805, #35819, #35824, #35828, #35822, #35835, #35865, #35860, #35832, #35891, #35894, #35895, #35820, #35917, #35924, #35925, #35914, #35930, #35941, #35939, #35948, #35955, #35967, #35990, #35991, #35975, #35971, #36002, #35986, #36031, #36034, #36038, #36054, #36052, #36055, #36065, #36068, #36083, #36087, #36080, #36091, #36090, #36118, #36119, #36128, #36094, #36129, #36138, #36132, #36151, #36158, #36171, #36194, #36220, #36169, #36130, #36249, #36153, #36299, #36291, #36301, #36315, #36317, #36364, #36383, #36381, #36459, #36464, #36461, #36516 and #36528 by @ChaosExAnima, @ClearlyClaire, @Lycolia, @diondiondion, and @tribela)\
10+
This includes a revamp of the composer interface.\
11+
See https://blog.joinmastodon.org/2025/09/introducing-quote-posts/ for a user-centric overview of the feature, and https://docs.joinmastodon.org/client/quotes/ for API documentation.
12+
- **Add support for fetching and refreshing replies to the web UI** (#35210, #35496, #35575, #35500, #35577, #35602, #35603, #35654, #36141, #36237, #36172, #36256, #36271, #36334, #36382, #36239, #36484 and #36481 by @ClearlyClaire, @Gargron, and @diondiondion)
13+
- **Add ability to block words in usernames** (#35407, #35655, and #35806 by @ClearlyClaire and @Gargron)
14+
- Add support for displaying of quote posts in Moderator UI (#35964 by @ThisIsMissEm)
15+
- Add support for displaying link previews for Admin UI (#35958 by @ThisIsMissEm)
16+
- Add support for dynamic viewport height (#36272 by @e1berd)
17+
- Add support for numeric-based URIs for new local accounts (#32724, #36304, #36316, and #36365 by @ClearlyClaire)
18+
- Add Traditional Mongolian to posting languages (#36196 by @shimon1024)
19+
- Add example post with manual quote approval policy to `dev:populate_sample_data` (#36099 by @ClearlyClaire)
20+
- Add server-side support for handling posts with a quote policy allowing followers to quote (#36093 and #36127 by @ClearlyClaire)
21+
- Add schema.org markup to SEO-enabled posts (#36075 by @Gargron)
22+
- Add migration to fill unset default quote policy based on default post privacy (#36041 by @ClearlyClaire)
23+
- Add support for exposing conversation context for new public conversations according to FEP-7888 (#35959 and #36064 by @ClearlyClaire and @jesseplusplus)
24+
- Add digest re-check before removing followers in synchronization mechanism (#34273 by @ClearlyClaire)
25+
- Add “Posting defaults” setting page, moving existing settings from “Other” (#35896, #36033, #35966, #35969, and #36084 by @ClearlyClaire and @diondiondion)
26+
- Add support for displaying Valkey version on admin dashboard (#35785 by @ykzts)
27+
- Add delivery failure tracking and handling to FASP jobs (#35625, #35628, and #35723 by @oneiros)
28+
- Add example of quote post with a preview card to development sample data (#35616 by @ClearlyClaire)
29+
- Add second set of blocked text that applies to accounts regardless of account age for spam-blocking (#35563 by @ClearlyClaire)
30+
- Added emoji from Twemoji v16 (#36501 and #36530 by @ChaosExAnima)
31+
- Add experimental feature to select custom emoji rendering (#35229, #35282, #35253, #35424, #35473, #35483, #35505, #35568, #35605, #35659, #35664, #35739, #35985, #36051, #36071, #36137, #36165, #36248, #36262, #36275, #36293, #36341, #36342, #36366, #36377, #36378, #36385, #36393, #36397, #36403, #36413, #36410, #36454, #36402, #36503, #36502 and #36532 by @ChaosExAnima and @braddunbar)\
32+
This also completely reworks the processing and rendering of emojis and server-rendered HTML in statuses and other places.
33+
34+
### Changed
35+
36+
- Change confirmation dialogs for follow button actions “unfollow”, “unblock”, and “withdraw request” (#36289 by @diondiondion)
37+
- Change “Follow” button labels (#36264 by @diondiondion)
38+
- Change appearance settings to introduce new Advanced settings section (#36496 and #36506 by @diondiondion)
39+
- Change display of content warnings in Admin UI (#35935 by @ThisIsMissEm)
40+
- Change index on `follows` table to improve performance of some queries (#36374 by @ClearlyClaire)
41+
- Change links to accounts in settings and moderation views to link to local view unless account is suspended (#36340 by @diondiondion)
42+
- Change redirection for denied registration from web app to sign-in page with error message (#36384 by @ClearlyClaire)
43+
- Change `timeline_preview` setting into four more granular settings (#36338, #36467 and #36497 by @ClearlyClaire)
44+
- Change wording and design of interaction dialog to simplify it (#36124 by @diondiondion)
45+
- Change dropdown menus to allow disabled items to be focused (#36078 by @diondiondion)
46+
- Change modal background colours in light mode (#36069 by @diondiondion)
47+
- Change “Posting defaults” settings page to enforce `nobody` quote policy for `private` default visibility (#36040 by @ClearlyClaire)
48+
- Change description of “Quiet public” (#36032 by @ClearlyClaire)
49+
- Change “Boost with original visibility” to “Share again with your followers” (#36035 by @ClearlyClaire)
50+
- Change handling of push subscriptions to automatically delete invalid ones on delivery (#35987 by @ThisIsMissEm)
51+
- Change design of quote posts in web UI (#35584 and #35834 by @ClearlyClaire and @Gargron)
52+
- Change auditable accounts to be sorted by username in admin action logs interface (#35272 by @breadtk)
53+
- Change order of translation restoration and service credit on post card (#33619 by @colindean)
54+
- Change position of ‘add more’ to be inside table toolbar on reports (#35963 by @ThisIsMissEm)
55+
- Change docker-compose.yml sidekiq health check to work for both 4.4 and 4.5 (#36498 by @ClearlyClaire)
56+
57+
### Fixed
58+
59+
- Fix relationship not being fetched to evaluate whether to show a quote post (#36517 by @ClearlyClaire)
60+
- Fix rendering of poll options in status history modal (#35633 by @ThisIsMissEm)
61+
- Fix “mute” button being displayed to unauthenticated visitors in hashtag dropdown (#36353 by @mkljczk)
62+
- Fix overflow handling of `.more-from-author` (#36310 by @edent)
63+
- Fix unfortunate action button wrapping in admin area (#36247 by @diondiondion)
64+
- Fix translate button width in Safari (#36164 and #36216 by @diondiondion)
65+
- Fix login page linking to other pages within OAuth authorization flow (#36115 by @Gargron)
66+
- Fix stale search results being displayed in Web UI while new query is in progress (#36053 by @ChaosExAnima)
67+
- Fix YouTube iframe not being able to start at a defined time (#26584 by @BrunoViveiros)
68+
- Fix banned text being able to be circumvented via unicode (#35978 by @Gargron)
69+
- Fix batch table toolbar displaying under status media (#35962 by @ThisIsMissEm)
70+
- Fix incorrect RSS feed MIME type in gzip_types directive (#35562 by @iioflow)
71+
- Fix 404 error after deleting status from detail view (#35800) (#35881 by @crafkaz)
72+
- Fix feeds keyboard navigation issues (#35853, #35864, and #36267 by @braddunbar and @diondiondion)
73+
- Fix layout shift caused by “Who to follow” widget (#35861 by @diondiondion)
74+
- Fix Vagrantfile (#35765 by @ClearlyClaire)
75+
- Fix reply indicator displaying wrong avatar in rare cases (#35756 by @ClearlyClaire)
76+
- Fix `Chewy::UndefinedUpdateStrategy` in `dev:populate_sample_data` task when Elasticsearch is enabled (#35615 by @ClearlyClaire)
77+
- Fix unnecessary account note addition for already-muted moved-to users (#35566 by @mjankowski)
78+
- Fix seeded admin user creation failing on specific configurations (#35565 by @oneiros)
79+
- Fix media modal images in Web UI having redundant `title` attribute (#35468 by @mayank99)
80+
- Fix inconsistent default privacy post setting when unset in settings (#35422 by @oneiros)
81+
- Fix glitchy status keyboard navigation (#35455 and #35504 by @diondiondion)
82+
- Fix post being submitted when pressing “Enter” in the CW field (#35445 by @diondiondion)
83+
84+
## [4.4.8] - 2025-10-21
85+
86+
### Security
87+
88+
- Fix quote control bypass ([GHSA-8h43-rcqj-wpc6](https://github.com/mastodon/mastodon/security/advisories/GHSA-8h43-rcqj-wpc6))
89+
90+
## [4.4.7] - 2025-10-15
91+
92+
### Fixed
93+
94+
- Fix forwarder being called with `nil` status when quote post is soft-deleted (#36463 by @ClearlyClaire)
95+
- Fix moderation warning e-mails that include posts (#36462 by @ClearlyClaire)
96+
- Fix allow_referrer_origin typo (#36460 by @ShadowJonathan)
97+
98+
## [4.4.6] - 2025-10-13
99+
100+
### Security
101+
102+
- Update dependencies `rack` and `uri`
103+
- Fix streaming server connection not being closed on user suspension (by @ThisIsMissEm, [GHSA-r2fh-jr9c-9pxh](https://github.com/mastodon/mastodon/security/advisories/GHSA-r2fh-jr9c-9pxh))
104+
- Fix password change through admin CLI not invalidating existing sessions and access tokens (by @ThisIsMissEm, [GHSA-f3q3-rmf7-9655](https://github.com/mastodon/mastodon/security/advisories/GHSA-f3q3-rmf7-9655))
105+
- Fix streaming server allowing access to public timelines even without the `read` or `read:statuses` OAuth scopes (by @ThisIsMissEm, [GHSA-7gwh-mw97-qjgp](https://github.com/mastodon/mastodon/security/advisories/GHSA-7gwh-mw97-qjgp))
106+
107+
### Added
108+
109+
- Add support for processing quotes of deleted posts signaled through a `Tombstone` (#36381 by @ClearlyClaire)
110+
111+
### Fixed
112+
113+
- Fix quote post state sometimes not being updated through streaming server (#36408 by @ClearlyClaire)
114+
- Fix inconsistent “pending tags” count on admin dashboard (#36404 by @mjankowski)
115+
- Fix JSON payload being potentially mutated when processing interaction policies (#36392 by @ClearlyClaire)
116+
- Fix quotes not being displayed in email notifications (#36379 by @diondiondion)
117+
- Fix redirect to external object when URL is missing or malformed (#36347 by @ClearlyClaire)
118+
- Fix quotes not being displayed in the featured carousel (#36335 by @diondiondion)
119+
5120
## [4.4.5] - 2025-09-23
6121

7122
### Security

app/lib/activitypub/activity/quote_request.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ def perform
77
return if non_matching_uri_hosts?(@account.uri, @json['id'])
88

99
quoted_status = status_from_uri(object_uri)
10-
return if quoted_status.nil? || !quoted_status.account.local? || !quoted_status.distributable?
10+
return if quoted_status.nil? || !quoted_status.account.local? || !quoted_status.distributable? || quoted_status.reblog?
1111

1212
if Mastodon::Feature.outgoing_quotes_enabled? && StatusPolicy.new(@account, quoted_status).quote?
1313
accept_quote_request!(quoted_status)

app/models/concerns/status/interaction_policy_concern.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def quote_policy_as_keys(kind)
2929

3030
# Returns `:automatic`, `:manual`, `:unknown` or `:denied`
3131
def quote_policy_for_account(other_account, preloaded_relations: {})
32-
return :denied if other_account.nil? || direct_visibility? || limited_visibility?
32+
return :denied if other_account.nil? || direct_visibility? || limited_visibility? || reblog?
3333

3434
following_author = nil
3535

app/models/quote.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class Quote < ApplicationRecord
3939
validates :activity_uri, presence: true, if: -> { account.local? && quoted_account&.remote? }
4040
validates :approval_uri, absence: true, if: -> { quoted_account&.local? }
4141
validate :validate_visibility
42+
validate :validate_original_quoted_status
4243

4344
after_create_commit :increment_counter_caches!
4445
after_destroy_commit :decrement_counter_caches!
@@ -85,6 +86,10 @@ def validate_visibility
8586
errors.add(:quoted_status_id, :visibility_mismatch)
8687
end
8788

89+
def validate_original_quoted_status
90+
errors.add(:quoted_status_id, :reblog_unallowed) if quoted_status&.reblog?
91+
end
92+
8893
def set_activity_uri
8994
self.activity_uri = [ActivityPub::TagManager.instance.uri_for(account), '/quote_requests/', SecureRandom.uuid].join
9095
end

app/serializers/rest/base_quote_serializer.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def state
1414
end
1515

1616
def quoted_status
17-
object.quoted_status if object.accepted? && object.quoted_status.present? && !status_filter.filtered_for_quote?
17+
object.quoted_status if object.accepted? && object.quoted_status.present? && !object.quoted_status&.reblog? && !status_filter.filtered_for_quote?
1818
end
1919

2020
private

app/services/activitypub/verify_quote_service.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def fetch_quoted_post_if_needed!(uri, prefetched_body: nil)
8181

8282
status ||= ActivityPub::FetchRemoteStatusService.new.call(uri, on_behalf_of: @quote.account.followers.local.first, prefetched_body:, request_id: @request_id, depth: @depth + 1)
8383

84-
@quote.update(quoted_status: status) if status.present?
84+
@quote.update(quoted_status: status) if status.present? && !status.reblog?
8585
rescue Mastodon::RecursionLimitExceededError, Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS => e
8686
@fetching_error = e
8787
end
@@ -99,7 +99,7 @@ def import_quoted_post_if_needed!(uri)
9999

100100
status = ActivityPub::FetchRemoteStatusService.new.call(object['id'], prefetched_body: object, on_behalf_of: @quote.account.followers.local.first, request_id: @request_id, depth: @depth)
101101

102-
if status.present?
102+
if status.present? && !status.reblog?
103103
@quote.update(quoted_status: status)
104104
true
105105
else

docker-compose.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ services:
5959
web:
6060
# You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes
6161
build: .
62-
image: kmyblue:20.4
62+
image: kmyblue:20.5
6363
restart: always
6464
env_file: .env.production
6565
command: bundle exec puma -C config/puma.rb
@@ -83,7 +83,7 @@ services:
8383
build:
8484
dockerfile: ./streaming/Dockerfile
8585
context: .
86-
image: kmyblue-streaming:20.4
86+
image: kmyblue-streaming:20.5
8787
restart: always
8888
env_file: .env.production
8989
command: node ./streaming/index.js
@@ -101,7 +101,7 @@ services:
101101

102102
sidekiq:
103103
build: .
104-
image: kmyblue:20.4
104+
image: kmyblue:20.5
105105
restart: always
106106
env_file: .env.production
107107
command: bundle exec sidekiq

lib/mastodon/version.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def kmyblue_major
1313
end
1414

1515
def kmyblue_minor
16-
4
16+
5
1717
end
1818

1919
def kmyblue_flag
@@ -35,7 +35,7 @@ def patch
3535
end
3636

3737
def default_prerelease
38-
'alpha.2'
38+
'beta.2'
3939
end
4040

4141
def prerelease

spec/lib/activitypub/activity/create_spec.rb

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1855,6 +1855,60 @@ def activity_for_object(json)
18551855
end
18561856
end
18571857

1858+
context 'with a quote of a known reblog that is otherwise valid' do
1859+
let(:quoted_account) { Fabricate(:account, domain: 'quoted.example.com') }
1860+
let(:quoted_status) { Fabricate(:status, account: quoted_account, reblog: Fabricate(:status)) }
1861+
let(:approval_uri) { 'https://quoted.example.com/quote-approval' }
1862+
1863+
let(:object_json) do
1864+
build_object(
1865+
type: 'Note',
1866+
content: 'woah what she said is amazing',
1867+
quote: ActivityPub::TagManager.instance.uri_for(quoted_status),
1868+
quoteAuthorization: approval_uri
1869+
)
1870+
end
1871+
1872+
before do
1873+
stub_request(:get, approval_uri).to_return(headers: { 'Content-Type': 'application/activity+json' }, body: Oj.dump({
1874+
'@context': [
1875+
'https://www.w3.org/ns/activitystreams',
1876+
{
1877+
QuoteAuthorization: 'https://w3id.org/fep/044f#QuoteAuthorization',
1878+
gts: 'https://gotosocial.org/ns#',
1879+
interactionPolicy: {
1880+
'@id': 'gts:interactionPolicy',
1881+
'@type': '@id',
1882+
},
1883+
interactingObject: {
1884+
'@id': 'gts:interactingObject',
1885+
'@type': '@id',
1886+
},
1887+
interactionTarget: {
1888+
'@id': 'gts:interactionTarget',
1889+
'@type': '@id',
1890+
},
1891+
},
1892+
],
1893+
type: 'QuoteAuthorization',
1894+
id: approval_uri,
1895+
attributedTo: ActivityPub::TagManager.instance.uri_for(quoted_status.account),
1896+
interactingObject: object_json[:id],
1897+
interactionTarget: ActivityPub::TagManager.instance.uri_for(quoted_status),
1898+
}))
1899+
end
1900+
1901+
it 'creates a status without the verified quote' do
1902+
expect { subject.perform }.to change(sender.statuses, :count).by(1)
1903+
1904+
status = sender.statuses.first
1905+
expect(status).to_not be_nil
1906+
expect(status.quote).to_not be_nil
1907+
expect(status.quote.state).to_not eq 'accepted'
1908+
expect(status.quote.quoted_status).to be_nil
1909+
end
1910+
end
1911+
18581912
context 'when a vote to a local poll' do
18591913
let(:poll) { Fabricate(:poll, options: %w(Yellow Blue)) }
18601914
let!(:local_status) { Fabricate(:status, poll: poll) }

spec/models/concerns/status/interaction_policy_concern_spec.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,22 @@
1515
describe '#quote_policy_for_account' do
1616
let(:account) { Fabricate(:account) }
1717

18+
context 'when the account is the author' do
19+
let(:status) { Fabricate(:status, account: account, quote_approval_policy: 0) }
20+
21+
it 'returns :automatic' do
22+
expect(status.quote_policy_for_account(account)).to eq :automatic
23+
end
24+
25+
context 'when it is a reblog' do
26+
let(:status) { Fabricate(:status, account: account, quote_approval_policy: 0, reblog: Fabricate(:status)) }
27+
28+
it 'returns :automatic' do
29+
expect(status.quote_policy_for_account(account)).to eq :denied
30+
end
31+
end
32+
end
33+
1834
context 'when the account is not following the user' do
1935
it 'returns :manual because of the public entry in the manual policy' do
2036
expect(status.quote_policy_for_account(account)).to eq :manual

0 commit comments

Comments
 (0)