- removes some duplicate logic in ./internal/ap and moves more of the functions to ./internal/ap/properties.go with standardized interfaces
- updates various areas of the codebase to rely on these GetOne___() functions
- pulls in latest gopkg for log.Formatted() function for nicer log formatting of variables in errors
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4905
this follows on from the work of @cdn0x12 in https://codeberg.org/superseriousbusiness/gotosocial/pulls/4795 but modifies the implementation to better make use of our multi lookup database and cache calls.
in addition to the above, this moves much of the surfacing (timelining and notifying) logic to derefencer hooks that are now setup to be called on dereference of accounts, statuses, media and emojis. though only statuses and media are hooked up for now.
Co-authored-by: cdn0x12 <git@cdn0x12.dev>
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4814
- adds a new cleaner package task to cleanup old remote status threads, i.e. those older than a given date that have zero local replies, zero local favourites and no boosts of any of the constituents.
- moves a bunch of status deletion logic of related models to the database
- improves status related model deletion performance by adding multi delete queries
- improves some query performance by removing step of converting ID string slices to accountIDValue slices by using some "unsafe" memory casting (with appropriate compile-time checks in place)
- adds support for deprecating + renaming configuration fields
- adds new "longer" duration and cron expression types for our job scheduling configuration (+ deprecates the configuration fields where replacing)
in a later PR i'll add support for calling it from the frontend as an admin panel action type dealio
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4808
adds configurables for timeline cache lengths, while keeping the defaults the same.
at some point we need to update the docs to recommend that postgres users increase their default cache sizes and disable timeline cache timeouts, as it's becoming increasingly clear that postgres timeline queries are prohibitively expensive, even just refreshing the cache.
extra: this also removes mutex protection from the global config state, since we don't actually perform any kind of concurrent writes, only ever reads (which is fine).
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4807
- github.com/minio/minio-go/v7: v7.0.100 -> v7.1.0
- github.com/tdewolff/minify/v2: v2.24.12 -> v2.24.13
- modernc.org/sqlite: v1.49.1 -> v1.50.0
note: the modernc.org/sqlite version bump also fixes the build issue i introduced with our fork recently, oops!
also, to those paying attention, given that github.com/ncruces/go-sqlite3 no longer requires a WASM runtime (it is now a transpile), come-next-release we will be updating the `nowasm` build tag behaviour to default to using github.com/ncruces/go-sqlite3 instead of modernc.org/sqlite. the [support matrix](https://github.com/ncruces/go-sqlite3/wiki/Support-matrix) now lists a lot more platforms as having acceptable performance, but we'll keep modernc.org/sqlite around under the `moderncsqlite3` for you BSD users on non-amd64/arm64 platforms.
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4806
- adds home timeline specific index (mostly used by postgres, but used by sqlite for larger queries, all credit to @cdn0x12)
- adds local timeline specific index (again, all credit to @cdn0x12)
- fine tunes some existing indices to further reduce size with partial clauses
- fine tunes some existing indices to reduce columns not actually queried on
Co-Authored by: @cdn0x12
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4761
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
Undoes my own recent change that blocks on media loading for timeline endpoints, to improve latency (which definitely had a bit of a regression after v0.21.0). Replaces the existing code that blocks on media loading in the surfacer with a more optimized check that reduces mutex locks and shouldn't lead to any accidental double loads.
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4756
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
# Description
- replaces status deletion in most cases with "stubbing" (similar to our account deletion)
- deleted status stubs are now cleaned up by a background cleanup routine (in the future this will be configurable and accessible via CLI and web)
- also adds a TODO.md to the repository root to track maintainer todos and the like!
- in a later PR i'll optimize both this and the last database migration to not drop + recreate so many indices for release users. (it's only useful having them separate like this for snapshot users)
## Checklist
- [x] I/we have read the [GoToSocial contribution guidelines](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/CONTRIBUTING.md).
- [x] I/we have not used so-called 'AI' to create the proposed changes.
- [x] I/we have discussed the proposed changes already, either in an issue on the repository, or in the Matrix chat.
- [x] I/we have performed a self-review of added code.
- [x] I/we have written code that is legible and maintainable by others.
- [x] I/we have commented the added code, particularly in hard-to-understand areas.
- [ ] I/we have made any necessary changes to documentation.
- [x] I/we have added tests that cover new code.
- [x] I/we have run tests and they pass locally with the changes.
- [x] I/we have run `go fmt ./...` and `golangci-lint run`.
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4752
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
# Description
- replaces a bunch of boolean (and like) columns with a single "statuses.flags" column
- adds a potential deleted value for the new "statuses.flags" column
- replaces useless migration.Down functions with empty txs with empty functions
In a future PR this will update our status deletion logic to instead stub-out a particular status row instead of deleting it, where necessary, to maintain status threads. In the process of replacing a bunch of columns with a single bit field this should also free up some storage space on the statuses table! It also replaces a bunch of indices with partials which again should further reduce space! ~~This is yet to be tested, so we shall see :p~~
Just finished testing this on my own instance...
- previous database size: 24827580416
- after database size: 21751169024
So 3GB of savings from a 24GB database, that's pretty damn impressive!
## Checklist
- [x] I/we have read the [GoToSocial contribution guidelines](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/CONTRIBUTING.md).
- [x] I/we have not used so-called 'AI' to create the proposed changes.
- [x] I/we have discussed the proposed changes already, either in an issue on the repository, or in the Matrix chat.
- [x] I/we have performed a self-review of added code.
- [x] I/we have written code that is legible and maintainable by others.
- [x] I/we have commented the added code, particularly in hard-to-understand areas.
- [ ] I/we have made any necessary changes to documentation.
- [ ] I/we have added tests that cover new code.
- [x] I/we have run tests and they pass locally with the changes.
- [x] I/we have run `go fmt ./...` and `golangci-lint run`.
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4716
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
ensures that status and account media attachments are loaded, synchronously, before return to the API whether via specific endpoints or timeline streamed insert. i'm not 💯 happy with the API of this, but when i do the ingester change i'm gonna do some fiddling with the API of this too since it could fit in the same sorta place.
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4705
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
- removes some superfluous code in the expiryTime type, also fixes handling for the zero value type
- moves the status.AccountID == requester.ID check further up in the function to remove unnecessary calculations
- fixes some bugs i introduced regarding not checking boost author account correctly 🙈
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4703
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
Previously, domain limit policies of hide were still storing an API model result, which indicates the status needs to be returned to the frontend with the result. Which in the case of "HIDE" we don't want to do! This corrects it to *not set an API model* when the domain limit policy is to simply hide statuses.
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4701
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
Just looking back over the changes from when I was ill, I noticed in particular one place where logging could be a lot better if we included the object as a key-value field instead of in the quoted message string, (also should use less allocations). As part of doing this it adds a useful `gtserror.WithField()` function that allows attaching logging fields to an error that later get pulled out, for example, in the logging middleware.
Then also undoes a use of `slices.Contains()` that wasn't actually checking if the slice contained anything as it's syntactically confusing 😅
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4674
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
- code.superseriousbusiness.org/gopkg: v0.0.0-20251207012115-5bde591107ab -> v0.0.0-20260117214252-d095ed821f5a
- codeberg.org/gruf/go-structr: v0.9.14 -> v0.9.15
- codeberg.org/gruf/go-kv/v2: v2.0.8 -> v2.0.9
fixes a bug i noticed in my string formatting library while attempting to print some absurd structure (the entirety of our State{} object), now it's all good :)
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4637
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
- drops `cached` columns from both media and emoji tables, instead relying on empty path
- drops `processed` column from media table, as it was totally unused
- adds `error` uint32 column to both media and emoji tables, and tracks basic error details encountered when trying to process remote media / emoji
- updates our media / emoji processor getter functions to attempt retries only if error situation is expected to be non-permanent
- adds a new `error` field to api attachment models to include error details string if failed to download
- updates media placeholder text generation to include error details string
Todos:
- ~~update cleaner to handle the new manner of `cached` flag (i.e. paths being set)~~
- ~~update tests~~
Nice-to-haves in later PRs:
- add 'force' flag for admins on media / emoji getter endpoints to allow forcing retry regardless of error type
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4625
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
# Description
A small improvement to performance for ffmpeg nowasm builds by limiting the number of required goroutines.
## Checklist
- [x] I/we have read the [GoToSocial contribution guidelines](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/CONTRIBUTING.md).
- [x] I/we have not used so-called 'AI' to create the proposed changes.
- [x] I/we have discussed the proposed changes already, either in an issue on the repository, or in the Matrix chat.
- [x] I/we have performed a self-review of added code.
- [x] I/we have written code that is legible and maintainable by others.
- [x] I/we have commented the added code, particularly in hard-to-understand areas.
- [ ] I/we have made any necessary changes to documentation.
- [ ] I/we have added tests that cover new code.
- [x] I/we have run tests and they pass locally with the changes.
- [x] I/we have run `go fmt ./...` and `golangci-lint run`.
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4628
Reviewed-by: tobi <kipvandenbos@noreply.codeberg.org>
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
- codeberg.org/gruf/go-errors/v2: v2.3.3 -> v2.3.4
- github.com/miekg/dns: v1.1.68 -> v1.1.69
note:
github.com/miekg/dns has now moved to codeberg.org/gruf/miekg/dns as a v2 of the library, though it does rely on Go 1.25 now. once we bump our version to 1.25 we can pull in the latest.
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4620
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
# Description
Add support for configuring timeline cache eviction timeouts.
## Checklist
- [x] I/we have read the [GoToSocial contribution guidelines](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/CONTRIBUTING.md).
- [x] I/we have not used so-called 'AI' to create the proposed changes.
- [x] I/we have discussed the proposed changes already, either in an issue on the repository, or in the Matrix chat.
- [x] I/we have performed a self-review of added code.
- [x] I/we have written code that is legible and maintainable by others.
- [x] I/we have commented the added code, particularly in hard-to-understand areas.
- [x] I/we have made any necessary changes to documentation.
- [ ] I/we have added tests that cover new code.
- [x] I/we have run tests and they pass locally with the changes.
- [x] I/we have run `go fmt ./...` and `golangci-lint run`.
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4616
Reviewed-by: tobi <kipvandenbos@noreply.codeberg.org>
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
# Description
This moves our `internal/{log/util/xslices}` packages out of the gotosocial repo and internal our external `code.superseriousbusiness.org/gopkg` repo. But it maintains an `internal/gtslog` subpkg for initialization of our logging configuration and syslog specifics (which aren't included in the external repo).
## Checklist
- [x] I/we have read the [GoToSocial contribution guidelines](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/CONTRIBUTING.md).
- [x] I/we have not used so-called 'AI' to create the proposed changes.
- [x] I/we have discussed the proposed changes already, either in an issue on the repository, or in the Matrix chat.
- [x] I/we have performed a self-review of added code.
- [x] I/we have written code that is legible and maintainable by others.
- [x] I/we have commented the added code, particularly in hard-to-understand areas.
- [ ] I/we have made any necessary changes to documentation.
- [ ] I/we have added tests that cover new code.
- [x] I/we have run tests and they pass locally with the changes.
- [x] I/we have run `go fmt ./...` and `golangci-lint run`.
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4610
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
# Description
This updates our account statuses function to only apply filtering when requesting accounts other than your own. This should prevent confusing situations like https://codeberg.org/superseriousbusiness/gotosocial/issues/4594 occurring.
## Checklist
- [x] I/we have read the [GoToSocial contribution guidelines](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/CONTRIBUTING.md).
- [x] I/we have not used so-called 'AI' to create the proposed changes.
- [x] I/we have discussed the proposed changes already, either in an issue on the repository, or in the Matrix chat.
- [x] I/we have performed a self-review of added code.
- [x] I/we have written code that is legible and maintainable by others.
- [x] I/we have commented the added code, particularly in hard-to-understand areas.
- [ ] I/we have made any necessary changes to documentation.
- [ ] I/we have added tests that cover new code.
- [x] I/we have run tests and they pass locally with the changes.
- [x] I/we have run `go fmt ./...` and `golangci-lint run`.
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4597
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
# Description
Does as it says on the tin! Adds caching for local, public and tag timelines. The public and local timelines are instance-level, i.e. they are not keyed by account. And the tag timeline cache introduces intelligent unloading of timelines after a certain amount of time since some 'last use'.
This also removes the caching of "prepared" API models from timeline caches. It turns out I forgot to recache prepared models each time they were prepared, so the (good) performance we were getting was already only relying on the initial model supplied on individual insert, which in most cases will likely have been unprepared anyway. Removing the caching of prepared models also gains us the performance improvement that we can *massively* reduce the number of invalidate calls we were making to timeline caches, which removes a boatload of mutex locking and hashmap lookups. And it makes it incredibly simple to have instance-wide caches for timelines, that later get filtered post-caching-stage depending on the account viewing it!
~~Depends: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4569~~
## Checklist
- [x] I/we have read the [GoToSocial contribution guidelines](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/CONTRIBUTING.md).
- [x] I/we have discussed the proposed changes already, either in an issue on the repository, or in the Matrix chat.
- [x] I/we have not leveraged AI to create the proposed changes.
- [x] I/we have performed a self-review of added code.
- [x] I/we have written code that is legible and maintainable by others.
- [x] I/we have commented the added code, particularly in hard-to-understand areas.
- [ ] I/we have made any necessary changes to documentation.
- [ ] I/we have added tests that cover new code.
- [x] I/we have run tests and they pass locally with the changes.
- [x] I/we have run `go fmt ./...` and `golangci-lint run`.
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4582
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
# Description
This fixes the issue in which statuses of followed tags show up in home timelines without first checking for user mutes.
It also massively increases code reuse in the worker timeline functions.
## Checklist
- [x] I/we have read the [GoToSocial contribution guidelines](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/CONTRIBUTING.md).
- [x] I/we have discussed the proposed changes already, either in an issue on the repository, or in the Matrix chat.
- [x] I/we have not leveraged AI to create the proposed changes.
- [x] I/we have performed a self-review of added code.
- [x] I/we have written code that is legible and maintainable by others.
- [x] I/we have commented the added code, particularly in hard-to-understand areas.
- [ ] I/we have made any necessary changes to documentation.
- [ ] I/we have added tests that cover new code.
- [x] I/we have run tests and they pass locally with the changes.
- [x] I/we have run `go fmt ./...` and `golangci-lint run`.
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4569
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
Fixes emoji async loading by having placeholder emojis return a path that correctly resolves to the correct emoji with our HTTP routing (due to a quirk in which we ignore the path extension). This ensures the emoji gets correctly resolved, and if necessary will block until it has been loaded by ProcessingEmoji{}. In some sense it is quite hacky, but also quite elegant in how it relies on existing lock structures we already had in place to block on load without needing to stream emoji model changes to websocket APIs.
closes https://codeberg.org/superseriousbusiness/gotosocial/issues/4529
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4553
Reviewed-by: tobi <kipvandenbos@noreply.codeberg.org>
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
This removes our hard reliance on the `.Cached` field of media and emojis to determine whether it exists in storage. We still make use of it as a useful flag to know whether to even bother checking storage, but we ultimately rely on the `ErrNotFound` response of storage to determine whether the media exists and needs recaching. This now removes our hard reliance on performing the `FixCacheStatus()` cleanup operations for media and emojis, which should reduce a whole bunch of S3 storage driver calls (thus, reducing cost for metered S3 buckets).
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4545
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
Fixes some validation issues relating to author information often expected to be valid email addresses, which our @displayname@username is not. There's still a few more validation issues, but honestly if we're going have better support I think it might be worth dropping gorilla/feeds for our own tagged XML / JSON structs.
Also does a bunch of housekeeping in the typeutils package removing error returns where never used / only ever logged, removing unused contexts etc.
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4517
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
- sets the nomsgpack build tag to instruct gin to build without msgpack support (which we don't use, anyway)
- sets the stdlibjson build tag to instruct minio to use the stdlib JSON encoder / decoder instead of goccy/json
both of the above net a size decrease of ~7MB
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4519
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
Reduces both code complexity and the number of separate database transactions we need to make by moving account statistics operations into the database as side-effects of the operations that effect them. In contrast to currently, where we manually update account statistics at the application layer.
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4496
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
# Description
This should fix the recent 'already exists' errors appearing, surfaced after the async emoji API change.
## Checklist
- [x] I/we have read the [GoToSocial contribution guidelines](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/CONTRIBUTING.md).
- [x] I/we have discussed the proposed changes already, either in an issue on the repository, or in the Matrix chat.
- [x] I/we have not leveraged AI to create the proposed changes.
- [x] I/we have performed a self-review of added code.
- [x] I/we have written code that is legible and maintainable by others.
- [x] I/we have commented the added code, particularly in hard-to-understand areas.
- [ ] I/we have made any necessary changes to documentation.
- [ ] I/we have added tests that cover new code.
- [x] I/we have run tests and they pass locally with the changes.
- [x] I/we have run `go fmt ./...` and `golangci-lint run`.
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4505
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
# Description
Updates our dereferencer emoji handling to work asynchronously when going through the route of account or status dereferencing.
closes https://codeberg.org/superseriousbusiness/gotosocial/issues/4485
## Checklist
- [x] I/we have read the [GoToSocial contribution guidelines](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/CONTRIBUTING.md).
- [x] I/we have discussed the proposed changes already, either in an issue on the repository, or in the Matrix chat.
- [x] I/we have not leveraged AI to create the proposed changes.
- [x] I/we have performed a self-review of added code.
- [x] I/we have written code that is legible and maintainable by others.
- [ ] I/we have commented the added code, particularly in hard-to-understand areas.
- [ ] I/we have made any necessary changes to documentation.
- [ ] I/we have added tests that cover new code.
- [ ] I/we have run tests and they pass locally with the changes.
- [x] I/we have run `go fmt ./...` and `golangci-lint run`.
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4486
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
# Description
fixes possible race condition of existing status being out-of-date in enrichStatus()
## Checklist
- [x] I/we have read the [GoToSocial contribution guidelines](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/CONTRIBUTING.md).
- [x] I/we have discussed the proposed changes already, either in an issue on the repository, or in the Matrix chat.
- [x] I/we have not leveraged AI to create the proposed changes.
- [x] I/we have performed a self-review of added code.
- [x] I/we have written code that is legible and maintainable by others.
- [x] I/we have commented the added code, particularly in hard-to-understand areas.
- [ ] I/we have made any necessary changes to documentation.
- [x] I/we have added tests that cover new code.
- [x] I/we have run tests and they pass locally with the changes.
- [x] I/we have run `go fmt ./...` and `golangci-lint run`.
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4470
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
- github.com/ncruces/go-sqlite3
- codeberg.org/gruf/go-mempool
- codeberg.org/gruf/go-structr (changes related on the above) *
- codeberg.org/gruf/go-mutexes (changes related on the above) *
* this is largely just fiddling around with package internals in structr and mutexes to rely on changes in mempool, which added a new concurrency-safe pool
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4468
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>