diff --git a/.buildpacks b/.buildpacks
deleted file mode 100644
index 31dd57096..000000000
--- a/.buildpacks
+++ /dev/null
@@ -1 +0,0 @@
-https://github.com/hashnuke/heroku-buildpack-elixir
diff --git a/.woodpecker/build-amd64.yml b/.woodpecker/build-amd64.yml
index 938c62665..32bbd2456 100644
--- a/.woodpecker/build-amd64.yml
+++ b/.woodpecker/build-amd64.yml
@@ -1,4 +1,5 @@
-platform: linux/amd64
+labels:
+ platform: linux/amd64
depends_on:
- test
@@ -34,7 +35,7 @@ variables:
- &clean "(rm -rf release || true) && (rm -rf _build || true) && (rm -rf /root/.mix)"
- &mix-clean "mix deps.clean --all && mix clean"
-pipeline:
+steps:
# Canonical amd64
debian-bookworm:
image: hexpm/elixir:1.15.4-erlang-26.0.2-debian-bookworm-20230612
diff --git a/.woodpecker/build-arm64.yml b/.woodpecker/build-arm64.yml
index 1ce1fac44..7bcdc1435 100644
--- a/.woodpecker/build-arm64.yml
+++ b/.woodpecker/build-arm64.yml
@@ -1,4 +1,5 @@
-platform: linux/arm64
+labels:
+ platform: linux/aarch64
depends_on:
- test
@@ -34,7 +35,7 @@ variables:
- &clean "(rm -rf release || true) && (rm -rf _build || true) && (rm -rf /root/.mix)"
- &mix-clean "mix deps.clean --all && mix clean"
-pipeline:
+steps:
# Canonical arm64
debian-bookworm:
image: hexpm/elixir:1.15.4-erlang-26.0.2-debian-bookworm-20230612
diff --git a/.woodpecker/docs.yml b/.woodpecker/docs.yml
index cc4017659..be7444e28 100644
--- a/.woodpecker/docs.yml
+++ b/.woodpecker/docs.yml
@@ -1,4 +1,5 @@
-platform: linux/amd64
+labels:
+ platform: linux/amd64
depends_on:
- test
@@ -45,7 +46,7 @@ variables:
- &clean "(rm -rf release || true) && (rm -rf _build || true) && (rm -rf /root/.mix)"
- &mix-clean "mix deps.clean --all && mix clean"
-pipeline:
+steps:
docs:
<<: *on-point-release
secrets:
diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index 8308e57d7..623494301 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -1,4 +1,5 @@
-platform: linux/amd64
+labels:
+ platform: linux/amd64
variables:
- &scw-secrets
@@ -41,9 +42,9 @@ variables:
- &clean "(rm -rf release || true) && (rm -rf _build || true) && (rm -rf /root/.mix)"
- &mix-clean "mix deps.clean --all && mix clean"
-pipeline:
+steps:
lint:
- image: akkoma/ci-base:1.15-otp26
+ image: akkoma/ci-base:1.16-otp26
<<: *on-pr-open
environment:
MIX_ENV: test
diff --git a/.woodpecker/test.yml b/.woodpecker/test.yml
index c8b819ce8..81c779b50 100644
--- a/.woodpecker/test.yml
+++ b/.woodpecker/test.yml
@@ -1,4 +1,5 @@
-platform: linux/amd64
+labels:
+ platform: linux/amd64
depends_on:
- lint
@@ -12,12 +13,6 @@ matrix:
- 25
- 26
include:
- - ELIXIR_VERSION: 1.14
- OTP_VERSION: 25
- - ELIXIR_VERSION: 1.15
- OTP_VERSION: 25
- - ELIXIR_VERSION: 1.15
- OTP_VERSION: 26
- ELIXIR_VERSION: 1.16
OTP_VERSION: 26
@@ -73,7 +68,7 @@ services:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
-pipeline:
+steps:
test:
image: akkoma/ci-base:${ELIXIR_VERSION}-otp${OTP_VERSION}
<<: *on-pr-open
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e397a75d0..569c6b616 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,17 +4,64 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
-## Unreleased
+## UNRELEASED
+
+## BREAKING
+- Minimum PostgreSQL version is raised to 12
+
+## Added
+- Implement [FEP-67ff](https://codeberg.org/fediverse/fep/src/branch/main/fep/67ff/fep-67ff.md) (federation documentation)
+- Meilisearch: it is now possible to use separate keys for search and admin actions
+
+## Fixed
+- Meilisearch: order of results returned from our REST API now actually matches how Meilisearch ranks results
+
+## Changed
+- Refactored Rich Media to cache the content in the database. Fetching operations that could block status rendering have been eliminated.
+
+## 2024.04.1 (Security)
+
+## Fixed
+- Issue allowing non-owners to use media objects in posts
+- Issue allowing use of non-media objects as attachments and crashing timeline rendering
+- Issue allowing webfinger spoofing in certain situations
+
+## 2024.04
## Added
- Support for [FEP-fffd](https://codeberg.org/fediverse/fep/src/branch/main/fep/fffd/fep-fffd.md) (proxy objects)
- Verified support for elixir 1.16
+- Uploadfilter `Pleroma.Upload.Filter.Exiftool.ReadDescription` returns description values to the FE so they can pre fill the image description field
+ NOTE: this filter MUST be placed before `Exiftool.StripMetadata` to work
## Changed
+- Inbound pipeline error handing was modified somewhat, which should lead to less incomprehensible log spam. Hopefully.
+- Uploadfilter `Pleroma.Upload.Filter.Exiftool` was replaced by `Pleroma.Upload.Filter.Exiftool.StripMetadata`;
+ the latter strips all non-essential metadata by default but can be configured.
+ To regain the old behaviour of only stripping GPS data set `purge: ["gps:all"]`.
+- Uploadfilter `Pleroma.Upload.Filter.Exiftool` has been renamed to `Pleroma.Upload.Filter.Exiftool.StripMetadata`
+- MRF.InlineQuotePolicy now prefers to insert display URLs instead of ActivityPub IDs
+- Old accounts are no longer listed in WebFinger as aliases; this was breaking spec
## Fixed
- Issue preventing fetching anything from IPv6-only instances
- Issue allowing post content to leak via opengraph tags despite :estrict\_unauthenticated being set
+- Move activities no longer operate on stale user data
+- Missing definitions in our JSON-LD context
+- Issue mangling newlines in code blocks for RSS/Atom feeds
+- static\_fe squeezing non-square avatars and emoji
+- Issue leading to properly JSON-LD compacted emoji reactions being rejected
+- We now use a standard-compliant Accept header when fetching ActivityPub objects
+- /api/pleroma/notification\_settings was rejecting body parameters;
+ this also broke changing this setting via akkoma-fe
+- Issue leading to Mastodon bot accounts being rejected
+- Scope misdetection of remote posts resulting from not recognising
+ JSON-LD-compacted forms of public scope; affected e.g. federation with bovine
+- Ratelimits encountered when fetching objects are now respected; 429 responses will cause a backoff when we get one.
+
+## Removed
+- ActivityPub Client-To-Server write API endpoints have been disabled;
+ read endpoints are planned to be removed next release unless a clear need is demonstrated
## 2024.03
diff --git a/FEDERATION.md b/FEDERATION.md
new file mode 100644
index 000000000..4524ec4e2
--- /dev/null
+++ b/FEDERATION.md
@@ -0,0 +1,42 @@
+# Federation
+
+## Supported federation protocols and standards
+
+- [ActivityPub](https://www.w3.org/TR/activitypub/) (Server-to-Server)
+- [WebFinger](https://webfinger.net/)
+- [Http Signatures](https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures)
+- [NodeInfo](https://nodeinfo.diaspora.software/)
+
+## Supported FEPs
+
+- [FEP-67ff: FEDERATION](https://codeberg.org/fediverse/fep/src/branch/main/fep/67ff/fep-67ff.md)
+- [FEP-f1d5: NodeInfo in Fediverse Software](https://codeberg.org/fediverse/fep/src/branch/main/fep/f1d5/fep-f1d5.md)
+- [FEP-fffd: Proxy Objects](https://codeberg.org/fediverse/fep/src/branch/main/fep/fffd/fep-fffd.md)
+
+## ActivityPub
+
+Akkoma mostly follows the server-to-server parts of the ActivityPub standard,
+but implements quirks for Mastodon compatibility as well as Mastodon-specific
+and custom extensions.
+
+See our documentation and Mastodon’s federation information
+linked further below for details on these quirks and extensions.
+
+Akkoma does not perform JSON-LD processing.
+
+### Required extensions
+
+#### HTTP Signatures
+All AP S2S POST requests to Akkoma instances MUST be signed.
+Depending on instance configuration the same may be true for GET requests.
+
+## Nodeinfo
+
+Akkoma provides many additional entries in its nodeinfo response,
+see the documentation linked below for details.
+
+## Additional documentation
+
+- [Akkoma’s ActivityPub extensions](https://docs.akkoma.dev/develop/development/ap_extensions/)
+- [Akkoma’s nodeinfo extensions](https://docs.akkoma.dev/develop/development/nodeinfo_extensions/)
+- [Mastodon’s federation requirements](https://github.com/mastodon/mastodon/blob/main/FEDERATION.md)
diff --git a/Procfile b/Procfile
deleted file mode 100644
index 7ac187baa..000000000
--- a/Procfile
+++ /dev/null
@@ -1,2 +0,0 @@
-web: mix phx.server
-release: mix ecto.migrate
diff --git a/config/config.exs b/config/config.exs
index 3395a2452..776e7f700 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -190,8 +190,10 @@
receive_timeout: :timer.seconds(15),
proxy_url: nil,
user_agent: :default,
- pool_size: 50,
- adapter: []
+ pool_size: 10,
+ adapter: [],
+ # see: https://hexdocs.pm/finch/Finch.html#start_link/1
+ pool_max_idle_time: :timer.seconds(30)
config :pleroma, :instance,
name: "Akkoma",
@@ -458,8 +460,12 @@
Pleroma.Web.RichMedia.Parsers.TwitterCard,
Pleroma.Web.RichMedia.Parsers.OEmbed
],
- failure_backoff: :timer.minutes(20),
- ttl_setters: [Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl]
+ failure_backoff: 60_000,
+ ttl_setters: [
+ Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl,
+ Pleroma.Web.RichMedia.Parser.TTL.Opengraph
+ ],
+ max_body: 5_000_000
config :pleroma, :media_proxy,
enabled: false,
@@ -597,7 +603,9 @@
mute_expire: 5,
search_indexing: 10,
nodeinfo_fetcher: 1,
- database_prune: 1
+ database_prune: 1,
+ rich_media_backfill: 2,
+ rich_media_expiration: 2
],
plugins: [
Oban.Plugins.Pruner,
@@ -613,7 +621,8 @@
retries: [
federator_incoming: 5,
federator_outgoing: 5,
- search_indexing: 2
+ search_indexing: 2,
+ rich_media_backfill: 3
],
timeout: [
activity_expiration: :timer.seconds(5),
@@ -635,7 +644,8 @@
mute_expire: :timer.seconds(5),
search_indexing: :timer.seconds(5),
nodeinfo_fetcher: :timer.seconds(10),
- database_prune: :timer.minutes(10)
+ database_prune: :timer.minutes(10),
+ rich_media_backfill: :timer.seconds(30)
]
config :pleroma, Pleroma.Formatter,
@@ -836,8 +846,10 @@
config :pleroma, configurable_from_database: false
config :pleroma, Pleroma.Repo,
- parameters: [gin_fuzzy_search_limit: "500"],
- prepare: :unnamed
+ parameters: [
+ gin_fuzzy_search_limit: "500",
+ plan_cache_mode: "force_custom_plan"
+ ]
config :pleroma, :majic_pool, size: 2
diff --git a/config/description.exs b/config/description.exs
index ec5050be6..847f4a1a1 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -222,6 +222,26 @@
}
]
},
+ %{
+ group: :pleroma,
+ key: Pleroma.Upload.Filter.Exiftool.StripMetadata,
+ type: :group,
+ description: "Strip specified metadata from image uploads",
+ children: [
+ %{
+ key: :purge,
+ description: "Metadata fields or groups to strip",
+ type: {:list, :string},
+ suggestions: ["all", "CommonIFD0"]
+ },
+ %{
+ key: :preserve,
+ description: "Metadata fields or groups to preserve (takes precedence over stripping)",
+ type: {:list, :string},
+ suggestions: ["ColorSpaces", "Orientation"]
+ }
+ ]
+ },
%{
group: :pleroma,
key: Pleroma.Emails.Mailer,
@@ -2697,8 +2717,8 @@
%{
key: :pool_size,
type: :integer,
- description: "Number of concurrent outbound HTTP requests to allow. Default 50.",
- suggestions: [50]
+ description: "Number of concurrent outbound HTTP requests to allow PER HOST. Default 10.",
+ suggestions: [10]
},
%{
key: :adapter,
@@ -2721,6 +2741,13 @@
]
}
]
+ },
+ %{
+ key: :pool_max_idle_time,
+ type: :integer,
+ description:
+ "Number of seconds to retain an HTTP pool; pool will remain if actively in use. Default 30 seconds (in ms).",
+ suggestions: [30_000]
}
]
},
diff --git a/config/dokku.exs b/config/dokku.exs
deleted file mode 100644
index 1cc396c3d..000000000
--- a/config/dokku.exs
+++ /dev/null
@@ -1,25 +0,0 @@
-import Config
-
-config :pleroma, Pleroma.Web.Endpoint,
- http: [
- port: String.to_integer(System.get_env("PORT") || "4000"),
- protocol_options: [max_request_line_length: 8192, max_header_value_length: 8192]
- ],
- protocol: "http",
- secure_cookie_flag: false,
- url: [host: System.get_env("APP_HOST"), scheme: "https", port: 443],
- secret_key_base: "+S+ULgf7+N37c/lc9K66SMphnjQIRGklTu0BRr2vLm2ZzvK0Z6OH/PE77wlUNtvP"
-
-database_url =
- System.get_env("DATABASE_URL") ||
- raise """
- environment variable DATABASE_URL is missing.
- For example: ecto://USER:PASS@HOST/DATABASE
- """
-
-config :pleroma, Pleroma.Repo,
- # ssl: true,
- url: database_url,
- pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
-
-config :pleroma, :instance, name: "#{System.get_env("APP_NAME")} CI Instance"
diff --git a/config/test.exs b/config/test.exs
index 07a4b8c36..4c2580cf2 100644
--- a/config/test.exs
+++ b/config/test.exs
@@ -63,7 +63,8 @@
config :pleroma, :rich_media,
enabled: false,
ignore_hosts: [],
- ignore_tld: ["local", "localdomain", "lan"]
+ ignore_tld: ["local", "localdomain", "lan"],
+ max_body: 2_000_000
config :pleroma, :instance,
multi_factor_authentication: [
@@ -147,6 +148,8 @@
config :pleroma, :instances_favicons, enabled: false
config :pleroma, :instances_nodeinfo, enabled: false
+config :pleroma, Pleroma.Web.RichMedia.Backfill, provider: Pleroma.Web.RichMedia.Backfill
+
if File.exists?("./config/test.secret.exs") do
import_config "test.secret.exs"
else
diff --git a/coveralls.json b/coveralls.json
deleted file mode 100644
index 8652591ef..000000000
--- a/coveralls.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "skip_files": [
- "test/support",
- "lib/mix/tasks/pleroma/benchmark.ex",
- "lib/credo/check/consistency/file_location.ex"
- ]
-}
\ No newline at end of file
diff --git a/diagnostic-tools/binary-leak-checker.sh b/diagnostic-tools/binary-leak-checker.sh
new file mode 100755
index 000000000..191fbcb68
--- /dev/null
+++ b/diagnostic-tools/binary-leak-checker.sh
@@ -0,0 +1,10 @@
+if [ "$#" -ne 2 ]; then
+ echo "Usage: binary-leak-checker.sh "
+ exit 1
+fi
+
+echo "The command you want to run is:
+:recon.bin_leak(10)
+"
+
+iex --sname debug --remsh $1 --erl "-setcookie $2"
diff --git a/docs/docs/administration/CLI_tasks/instance.md b/docs/docs/administration/CLI_tasks/instance.md
index 5c93cdb30..3d30d1119 100644
--- a/docs/docs/administration/CLI_tasks/instance.md
+++ b/docs/docs/administration/CLI_tasks/instance.md
@@ -37,7 +37,8 @@ If any of the options are left unspecified, you will be prompted interactively.
- `--static-dir ` - the directory custom public files should be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)
- `--listen-ip ` - the ip the app should listen to, defaults to 127.0.0.1
- `--listen-port ` - the port the app should listen to, defaults to 4000
-- `--strip-uploads ` - use ExifTool to strip uploads of sensitive location data
+- `--strip-uploads-metadata ` - use ExifTool to strip uploads of metadata when possible
+- `--read-uploads-description ` - use ExifTool to read image descriptions from uploads
- `--anonymize-uploads ` - randomize uploaded filenames
- `--dedupe-uploads ` - store files based on their hash to reduce data storage requirements if duplicates are uploaded with different filenames
- `--skip-release-env` - skip generation the release environment file
diff --git a/docs/docs/clients.md b/docs/docs/clients.md
index 1452b9505..b5ec500c9 100644
--- a/docs/docs/clients.md
+++ b/docs/docs/clients.md
@@ -1,12 +1,15 @@
# Akkoma Clients
-Note: Additional clients may work, but these are known to work with Akkoma.
-Apps listed here might not support all of Akkoma's features.
+This is a list of clients that are known to work with Akkoma.
+
+!!! warning
+ **Clients listed here are not officially supported by the Akkoma project.**
+ Some Akkoma features may be unsupported by these clients.
## Multiplatform
### Kaiteki
- Homepage:
- Source Code:
-- Contact: [@kaiteki@fedi.software](https://fedi.software/@Kaiteki)
+- Contact: [@kaiteki@social.kaiteki.app](https://social.kaiteki.app/@kaiteki)
- Platforms: Web, Windows, Linux, Android
- Features: MastoAPI, Supports multiple backends
@@ -38,12 +41,6 @@ Apps listed here might not support all of Akkoma's features.
- Platforms: Android
- Features: MastoAPI, No Streaming, Emoji Reactions, Text Formatting, FE Stickers
-### Fedi
-- Homepage:
-- Source Code: Proprietary, but gratis
-- Platforms: iOS, Android
-- Features: MastoAPI, Pleroma-specific features like Reactions
-
### Tusky
- Homepage:
- Source Code:
@@ -51,12 +48,18 @@ Apps listed here might not support all of Akkoma's features.
- Platforms: Android
- Features: MastoAPI, No Streaming
+### Subway Tooter
+- Source Code:
+- Contact: [@SubwayTooter@mastodon.juggler.jp](https://mastodon.juggler.jp/@SubwayTooter)
+- Platforms: Android
+- Features: MastoAPI, Editing, Emoji Reactions (including custom emoji)
+
## Alternative Web Interfaces
-### Pinafore
-- Note: Pinafore is unmaintained (See [the author's original article](https://nolanlawson.com/2023/01/09/retiring-pinafore/) for details)
-- Homepage:
-- Source Code:
-- Contact: [@pinafore@mastodon.technology](https://mastodon.technology/users/pinafore)
+### Enafore
+- An actively developed fork of Pinafore with improved Akkoma support
+- Homepage:
+- Source Code:
+- Contact: [@enfore@enafore.social](https://meta.enafore.social/@enafore)
- Features: MastoAPI, No Streaming
### Sengi
diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md
index 59cf5177a..a9eb988c3 100644
--- a/docs/docs/configuration/cheatsheet.md
+++ b/docs/docs/configuration/cheatsheet.md
@@ -63,6 +63,8 @@ To add configuration to your config file, you can copy it from the base config.
* `local_bubble`: Array of domains representing instances closely related to yours. Used to populate the `bubble` timeline. e.g `["example.com"]`, (default: `[]`)
* `languages`: List of Language Codes used by the instance. This is used to try and set a default language from the frontend. It will try and find the first match between the languages set here and the user's browser languages. It will default to the first language in this setting if there is no match.. (default `["en"]`)
* `export_prometheus_metrics`: Enable prometheus metrics, served at `/api/v1/akkoma/metrics`, requiring the `admin:metrics` oauth scope.
+* `privileged_staff`: Set to `true` to give moderators access to a few higher responsibility actions.
+* `federated_timeline_available`: Set to `false` to remove access to the federated timeline for all users.
## :database
* `improved_hashtag_timeline`: Setting to force toggle / force disable improved hashtags timeline. `:enabled` forces hashtags to be fetched from `hashtags` table for hashtags timeline. `:disabled` forces object-embedded hashtags to be used (slower). Keep it `:auto` for automatic behaviour (it is auto-set to `:enabled` [unless overridden] when HashtagsTableMigrator completes).
@@ -654,9 +656,17 @@ This filter replaces the declared filename (not the path) of an upload.
* `text`: Text to replace filenames in links. If empty, `{random}.extension` will be used. You can get the original filename extension by using `{extension}`, for example `custom-file-name.{extension}`.
-#### Pleroma.Upload.Filter.Exiftool
+#### Pleroma.Upload.Filter.Exiftool.StripMetadata
-This filter only strips the GPS and location metadata with Exiftool leaving color profiles and attributes intact.
+This filter strips metadata with Exiftool leaving color profiles and orientation intact.
+
+* `purge`: List of Exiftool tag names or tag group names to purge
+* `preserve`: List of Exiftool tag names or tag group names to preserve even if they occur in the purge list
+
+
+#### Pleroma.Upload.Filter.Exiftool.ReadDescription
+
+This filter reads the ImageDescription and iptc:Caption-Abstract fields with Exiftool so clients can prefill the media description field.
No specific configuration.
diff --git a/docs/docs/configuration/howto_mediaproxy.md b/docs/docs/configuration/howto_mediaproxy.md
index 223ad7eed..1544a563e 100644
--- a/docs/docs/configuration/howto_mediaproxy.md
+++ b/docs/docs/configuration/howto_mediaproxy.md
@@ -6,37 +6,17 @@ With the `mediaproxy` function you can use nginx to cache this content, so users
## Activate it
-* Edit your nginx config and add the following location to your main server block:
-```
-location /proxy {
- return 404;
-}
-```
-
* Set up a subdomain for the proxy with its nginx config on the same machine
- *(the latter is not strictly required, but for simplicity we’ll assume so)*
-* In this subdomain’s server block add
-```
-location /proxy {
- proxy_cache akkoma_media_cache;
- proxy_cache_lock on;
- proxy_pass http://localhost:4000;
-}
-```
-Also add the following on top of the configuration, outside of the `server` block:
-```
-proxy_cache_path /tmp/akkoma-media-cache levels=1:2 keys_zone=akkoma_media_cache:10m max_size=10g inactive=720m use_temp_path=off;
-```
-If you came here from one of the installation guides, take a look at the example configuration `/installation/nginx/akkoma.nginx`, where this part is already included.
-
+* Edit the nginx config for the upload/MediaProxy subdomain to point to the subdomain that has been set up
* Append the following to your `prod.secret.exs` or `dev.secret.exs` (depends on which mode your instance is running):
-```
+```elixir
+# Replace media.example.td with the subdomain you set up earlier
config :pleroma, :media_proxy,
enabled: true,
proxy_opts: [
redirect_on_failure: true
],
- base_url: "https://cache.akkoma.social"
+ base_url: "https://media.example.tld"
```
You **really** should use a subdomain to serve proxied files; while we will fix bugs resulting from this, serving arbitrary remote content on your main domain namespace is a significant attack surface.
diff --git a/docs/docs/configuration/i2p.md b/docs/docs/configuration/i2p.md
index ec6266ab7..1fb18d1c0 100644
--- a/docs/docs/configuration/i2p.md
+++ b/docs/docs/configuration/i2p.md
@@ -130,59 +130,26 @@ config :pleroma, :http_security,
enabled: false
```
-Use this as the Nginx config:
-```
-proxy_cache_path /tmp/akkoma-media-cache levels=1:2 keys_zone=akkoma_media_cache:10m max_size=10g inactive=720m use_temp_path=off;
-# The above already exists in a clearnet instance's config.
-# If not, add it.
-
-server {
- listen 127.0.0.1:14447;
- server_name youri2paddress;
-
- # Comment to enable logs
- access_log /dev/null;
- error_log /dev/null;
-
- gzip_vary on;
- gzip_proxied any;
- gzip_comp_level 6;
- gzip_buffers 16 8k;
- gzip_http_version 1.1;
- gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript application/activity+json application/atom+xml;
-
- client_max_body_size 16m;
-
- location / {
-
+In the Nginx config, add the following into the `location /` block:
+```nginx
add_header X-XSS-Protection "0";
add_header X-Permitted-Cross-Domain-Policies none;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header Referrer-Policy same-origin;
-
- proxy_http_version 1.1;
- proxy_set_header Upgrade $http_upgrade;
- proxy_set_header Connection "upgrade";
- proxy_set_header Host $http_host;
-
- proxy_pass http://localhost:4000;
-
- client_max_body_size 16m;
- }
-
- location /proxy {
- proxy_cache akkoma_media_cache;
- proxy_cache_lock on;
- proxy_ignore_client_abort on;
- proxy_pass http://localhost:4000;
- }
-}
```
-reload Nginx:
+
+Change the `listen` directive to the following:
+```nginx
+listen 127.0.0.1:14447;
```
-systemctl stop i2pd.service --no-block
-systemctl start i2pd.service
+
+Set `server_name` to your i2p address.
+
+Reload Nginx:
+```
+systemctl restart i2pd.service --no-block
+systemctl reload nginx.service
```
*Notice:* The stop command initiates a graceful shutdown process, i2pd stops after finishing to route transit tunnels (maximum 10 minutes).
diff --git a/docs/docs/configuration/onion_federation.md b/docs/docs/configuration/onion_federation.md
index e4ae15fd2..26efbae42 100644
--- a/docs/docs/configuration/onion_federation.md
+++ b/docs/docs/configuration/onion_federation.md
@@ -74,56 +74,23 @@ config :pleroma, :http_security,
enabled: false
```
-Use this as the Nginx config:
-```
-proxy_cache_path /tmp/akkoma-media-cache levels=1:2 keys_zone=akkoma_media_cache:10m max_size=10g inactive=720m use_temp_path=off;
-# The above already exists in a clearnet instance's config.
-# If not, add it.
-
-server {
- listen 127.0.0.1:8099;
- server_name youronionaddress;
-
- # Comment to enable logs
- access_log /dev/null;
- error_log /dev/null;
-
- gzip_vary on;
- gzip_proxied any;
- gzip_comp_level 6;
- gzip_buffers 16 8k;
- gzip_http_version 1.1;
- gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript application/activity+json application/atom+xml;
-
- client_max_body_size 16m;
-
- location / {
-
+In the Nginx config, add the following into the `location /` block:
+```nginx
add_header X-XSS-Protection "0";
add_header X-Permitted-Cross-Domain-Policies none;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header Referrer-Policy same-origin;
-
- proxy_http_version 1.1;
- proxy_set_header Upgrade $http_upgrade;
- proxy_set_header Connection "upgrade";
- proxy_set_header Host $http_host;
-
- proxy_pass http://localhost:4000;
-
- client_max_body_size 16m;
- }
-
- location /proxy {
- proxy_cache akkoma_media_cache;
- proxy_cache_lock on;
- proxy_ignore_client_abort on;
- proxy_pass http://localhost:4000;
- }
-}
```
-reload Nginx:
+
+Change the `listen` directive to the following:
+```nginx
+listen 127.0.0.1:8099;
+```
+
+Set the `server_name` to your onion address.
+
+Reload Nginx:
```
systemctl reload nginx
```
diff --git a/docs/docs/configuration/postgresql.md b/docs/docs/configuration/postgresql.md
index 3d5b78c0d..420ba7319 100644
--- a/docs/docs/configuration/postgresql.md
+++ b/docs/docs/configuration/postgresql.md
@@ -4,47 +4,10 @@ Akkoma performance is largely dependent on performance of the underlying databas
## PGTune
-[PgTune](https://pgtune.leopard.in.ua) can be used to get recommended settings. Be sure to set "Number of Connections" to 20, otherwise it might produce settings hurtful to database performance. It is also recommended to not use "Network Storage" option.
+[PgTune](https://pgtune.leopard.in.ua) can be used to get recommended settings. Make sure to set the DB type to "Online transaction processing system" for optimal performance. Also set the number of connections to between 25 and 30. This will allow each connection to have access to more resources while still leaving some room for running maintenance tasks while the instance is still running.
-If your server runs other services, you may want to take that into account. E.g. if you have 4G ram, but 1G of it is already used for other services, it may be better to tell PGTune you only have 3G. In the end, PGTune only provides recomended settings, you can always try to finetune further.
+It is also recommended to not use "Network Storage" option.
-### Example configurations
+If your server runs other services, you may want to take that into account. E.g. if you have 4G ram, but 1G of it is already used for other services, it may be better to tell PGTune you only have 3G.
-Here are some configuration suggestions for PostgreSQL 10+.
-
-#### 1GB RAM, 1 CPU
-```
-shared_buffers = 256MB
-effective_cache_size = 768MB
-maintenance_work_mem = 64MB
-work_mem = 13107kB
-```
-
-#### 2GB RAM, 2 CPU
-```
-shared_buffers = 512MB
-effective_cache_size = 1536MB
-maintenance_work_mem = 128MB
-work_mem = 26214kB
-max_worker_processes = 2
-max_parallel_workers_per_gather = 1
-max_parallel_workers = 2
-```
-
-## Disable generic query plans
-
-When PostgreSQL receives a query, it decides on a strategy for searching the requested data, this is called a query plan. The query planner has two modes: generic and custom. Generic makes a plan for all queries of the same shape, ignoring the parameters, which is then cached and reused. Custom, on the contrary, generates a unique query plan based on query parameters.
-
-By default PostgreSQL has an algorithm to decide which mode is more efficient for particular query, however this algorithm has been observed to be wrong on some of the queries Akkoma sends, leading to serious performance loss. Therefore, it is recommended to disable generic mode.
-
-
-Akkoma already avoids generic query plans by default, however the method it uses is not the most efficient because it needs to be compatible with all supported PostgreSQL versions. For PostgreSQL 12 and higher additional performance can be gained by adding the following to Akkoma configuration:
-```elixir
-config :pleroma, Pleroma.Repo,
- prepare: :named,
- parameters: [
- plan_cache_mode: "force_custom_plan"
- ]
-```
-
-A more detailed explaination of the issue can be found at .
+In the end, PGTune only provides recomended settings, you can always try to finetune further.
diff --git a/docs/docs/configuration/search.md b/docs/docs/configuration/search.md
index 1e343032f..4c6bc412f 100644
--- a/docs/docs/configuration/search.md
+++ b/docs/docs/configuration/search.md
@@ -33,6 +33,7 @@ indexes faster when it can process many posts in a single batch.
> config :pleroma, Pleroma.Search.Meilisearch,
> url: "http://127.0.0.1:7700/",
> private_key: "private key",
+> search_key: "search key",
> initial_indexing_chunk_size: 100_000
Information about setting up meilisearch can be found in the
@@ -45,7 +46,7 @@ is hardly usable on a somewhat big instance.
### Private key authentication (optional)
To set the private key, use the `MEILI_MASTER_KEY` environment variable when starting. After setting the _master key_,
-you have to get the _private key_, which is actually used for authentication.
+you have to get the _private key_ and possibly _search key_, which are actually used for authentication.
=== "OTP"
```sh
@@ -57,7 +58,11 @@ you have to get the _private key_, which is actually used for authentication.
mix pleroma.search.meilisearch show-keys
```
-You will see a "Default Admin API Key", this is the key you actually put into your configuration file.
+You will see a "Default Admin API Key", this is the key you actually put into
+your configuration file as `private_key`. You should also see a
+"Default Search API key", put this into your config as `search_key`.
+If your version of Meilisearch only showed the former,
+just leave `search_key` completely unset in Akkoma's config.
### Initial indexing
diff --git a/docs/docs/development/ap_extensions.md b/docs/docs/development/ap_extensions.md
index bf9420272..a5e3d0186 100644
--- a/docs/docs/development/ap_extensions.md
+++ b/docs/docs/development/ap_extensions.md
@@ -4,7 +4,6 @@
The following endpoints are additionally present into our actors.
- `oauthRegistrationEndpoint` (`http://litepub.social/ns#oauthRegistrationEndpoint`)
-- `uploadMedia` (`https://www.w3.org/ns/activitystreams#uploadMedia`)
### oauthRegistrationEndpoint
@@ -12,6 +11,279 @@ Points to MastodonAPI `/api/v1/apps` for now.
See
+## Emoji reactions
+
+Emoji reactions are implemented as a new activity type `EmojiReact`.
+A single user is allowed to react multiple times with different emoji to the
+same post. However, they may only react at most once with the same emoji.
+Repeated reaction from the same user with the same emoji are to be ignored.
+Emoji reactions are also distinct from `Like` activities and a user may both
+`Like` and react to a post.
+
+!!! note
+ Misskey also supports emoji reactions, but the implementations differs.
+ It equates likes and reactions and only allows a single reaction per post.
+
+The emoji is placed in the `content` field of the activity
+and the `object` property points to the note reacting to.
+
+Emoji can either be any Unicode emoji sequence or a custom emoji.
+The latter must place their shortcode, including enclosing colons,
+into `content` and put the emoji object inside the `tag` property.
+The `tag` property MAY be omitted for Unicode emoji.
+
+An example reaction with a Unicode emoji:
+```json
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "https://example.org/schemas/litepub-0.1.jsonld",
+ {
+ "@language": "und"
+ }
+ ],
+ "type": "EmojiReact",
+ "id": "https://example.org/activities/23143872a0346141",
+ "actor": "https://example.org/users/akko",
+ "nickname": "akko",
+ "to": ["https://remote.example/users/diana", "https://example.org/users/akko/followers"],
+ "cc": ["https://www.w3.org/ns/activitystreams#Public"],
+ "content": "🧡",
+ "object": "https://remote.example/objects/9f0e93499d8314a9"
+}
+```
+
+An example reaction with a custom emoji:
+```json
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "https://example.org/schemas/litepub-0.1.jsonld",
+ {
+ "@language": "und"
+ }
+ ],
+ "type": "EmojiReact",
+ "id": "https://example.org/activities/d75586dec0541650",
+ "actor": "https://example.org/users/akko",
+ "nickname": "akko",
+ "to": ["https://remote.example/users/diana", "https://example.org/users/akko/followers"],
+ "cc": ["https://www.w3.org/ns/activitystreams#Public"],
+ "content": ":mouse:",
+ "object": "https://remote.example/objects/9f0e93499d8314a9",
+ "tag": [{
+ "type": "Emoji",
+ "id": null,
+ "name": "mouse",
+ "icon": {
+ "type": "Image",
+ "url": "https://example.org/emoji/mouse/mouse.png"
+ }
+ }]
+}
+```
+
+!!! note
+ Although an emoji reaction can only contain a single emoji,
+ for compatibility with older versions of Pleroma and Akkoma,
+ it is recommended to wrap the emoji object in a single-element array.
+
+When reacting with a remote custom emoji do not include the remote domain in `content`’s shortcode
+*(unlike in our REST API which needs the domain)*:
+```json
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "https://example.org/schemas/litepub-0.1.jsonld",
+ {
+ "@language": "und"
+ }
+ ],
+ "type": "EmojiReact",
+ "id": "https://example.org/activities/7993dcae98d8d5ec",
+ "actor": "https://example.org/users/akko",
+ "nickname": "akko",
+ "to": ["https://remote.example/users/diana", "https://example.org/users/akko/followers"],
+ "cc": ["https://www.w3.org/ns/activitystreams#Public"],
+ "content": ":hug:",
+ "object": "https://remote.example/objects/9f0e93499d8314a9",
+ "tag": [{
+ "type": "Emoji",
+ "id": "https://other.example/emojis/hug",
+ "name": "hug",
+ "icon": {
+ "type": "Image",
+ "url": "https://other.example/files/b71cea432b3fad67.webp"
+ }
+ }]
+}
+```
+
+Emoji reactions can be retracted using a standard `Undo` activity:
+```json
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "http://example.org/schemas/litepub-0.1.jsonld",
+ {
+ "@language": "und"
+ }
+ ],
+ "type": "Undo",
+ "id": "http://example.org/activities/4685792e-efb6-4309-b508-ae4f355dd695",
+ "actor": "https://example.org/users/akko",
+ "to": ["https://remote.example/users/diana", "https://example.org/users/akko/followers"],
+ "cc": ["https://www.w3.org/ns/activitystreams#Public"],
+ "object": "https://example.org/activities/23143872a0346141"
+}
+```
+
+## User profile backgrounds
+
+Akkoma federates user profile backgrounds the same way as Sharkey.
+
+An actors ActivityPub representation contains an additional
+`backgroundUrl` property containing an `Image` object. This property
+belongs to the `"sharkey": "https://joinsharkey.org/ns#"` namespace.
+
+## Quote Posts
+
+Akkoma allows referencing a single other note as a quote,
+which will be prominently displayed in the interface.
+
+The quoted post is referenced by its ActivityPub id in the `quoteUri` property.
+
+!!! note
+ Old Misskey only understood and modern Misskey still prefers
+ the `_misskey_quote` property for this. Similar some other older
+ software used `quoteUrl` or `quoteURL`.
+ All current implementations with quote support understand `quoteUri`.
+
+Example:
+```json
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "https://example.org/schemas/litepub-0.1.jsonld",
+ {
+ "@language": "und"
+ }
+ ],
+ "type": "Note",
+ "id": "https://example.org/activities/85717e587f95d5c0",
+ "actor": "https://example.org/users/akko",
+ "to": ["https://remote.example/users/diana", "https://example.org/users/akko/followers"],
+ "cc": ["https://www.w3.org/ns/activitystreams#Public"],
+ "context": "https://example.org/contexts/1",
+ "content": "Look at that!",
+ "quoteUri": "http://remote.example/status/85717e587f95d5c0",
+ "contentMap": {
+ "en": "Look at that!"
+ },
+ "source": {
+ "content": "Look at that!",
+ "mediaType": "text/plain"
+ },
+ "published": "2024-04-06T23:40:28Z",
+ "updated": "2024-04-06T23:40:28Z",
+ "attachemnt": [],
+ "tag": []
+}
+```
+
+## Threads
+
+Akkoma assigns all posts of the same thread the same `context`. This is a
+standard ActivityPub property but its meaning is left vague. Akkoma will
+always treat posts with identical `context` as part of the same thread.
+
+`context` must not be assumed to hold any meaning or be dereferencable.
+
+Incoming posts without `context` will be assigned a new context.
+
+!!! note
+ Mastodon uses the non-standard `conversation` property for the same purpose
+ *(named after an older OStatus property)*. For incoming posts without
+ `context` but with `converstions` Akkoma will use the value from
+ `conversations` to fill in `context`.
+ For outgoing posts Akkoma will duplicate the context into `conversation`.
+
+## Post Source
+
+Unlike Mastodon, Akkoma supports drafting posts in multiple source formats
+besides plaintext, like Markdown or MFM. The original input is preserved
+in the standard ActivityPub `source` property *(not supported by Mastodon)*.
+Still, `content` will always be present and contain the prerendered HTML form.
+
+Supported `mediaType` include:
+- `text/plain`
+- `text/markdown`
+- `text/bbcode`
+- `text/x.misskeymarkdown`
+
+## Post Language
+
+!!! note
+ This is also supported in and compatible with Mastodon, but since
+ joinmastodon.org doesn’t document it yet it is included here.
+ [GoToSocial](https://docs.gotosocial.org/en/latest/federation/federating_with_gotosocial/#content-contentmap-and-language)
+ has a more refined version of this which can correctly deal with multiple language entries.
+
+A post can indicate its language by including a `contentMap` object
+which contains a sub key named after the language’s ISO 639-1 code
+and it’s content identical to the post’s `content` field.
+
+Currently Akkoma, just like Mastodon, only properly supports a single language entry,
+in case of multiple entries a random language will be picked.
+Furthermore, Akkoma currently only reads the `content` field
+and never the value from `contentMap`.
+
+## Local post scope
+
+Post using this scope will never federate to other servers
+but for the sake of completeness it is listed here.
+
+In addition to the usual scopes *(public, unlisted, followers-only, direct)*
+Akkoma supports an “unlisted” post scope. Such posts will not federate to
+other instances and only be shown to logged-in users on the same instance.
+It is included into the local timeline.
+This may be useful to discuss or announce instance-specific policies and topics.
+
+A post is addressed to the local scope by including ` /#Public`
+in its `to` field. E.g. if the instance is on `https://example.org` it would use
+`https://example.org/#Public`.
+
+An implementation creating a new post MUST NOT address both the local and
+general public scope `as:Public` at the same time. A post addressing the local
+scope MUST NOT be sent to other instances or be possible to fetch by other
+instances regardless of potential other listed addressees.
+
+When receiving a remote post addressing both the public scope and what appears
+to be a local-scope identifier, the post SHOULD be treated without assigning any
+special meaning to the potential local-scope identifier.
+
+!!! note
+ Misskey-derivatives have a similar concept of non-federated posts,
+ however those are also shown publicly on the local web interface
+ and are thus visible to non-members.
+
+## List post scope
+
+Messages originally addressed to a custom list will contain
+a `listMessage` field with an unresolvable pseudo ActivityPub id.
+
+# Deprecated and Removed Extensions
+
+The following extensions were used in the past but have been dropped.
+Documentation is retained here as a reference and since old objects might
+still contains related fields.
+
+## Actor endpoints
+
+The following endpoints used to be present:
+
+- `uploadMedia` (`https://www.w3.org/ns/activitystreams#uploadMedia`)
+
### uploadMedia
Inspired by , it is part of the ActivityStreams namespace because it used to be part of the ActivityPub specification and got removed from it.
@@ -20,9 +292,8 @@ Content-Type: multipart/form-data
Parameters:
- (required) `file`: The file being uploaded
-- (optionnal) `description`: A plain-text description of the media, for accessibility purposes.
+- (optional) `description`: A plain-text description of the media, for accessibility purposes.
Response: HTTP 201 Created with the object into the body, no `Location` header provided as it doesn't have an `id`
-The object given in the reponse should then be inserted into an Object's `attachment` field.
-
+The object given in the response should then be inserted into an Object's `attachment` field.
diff --git a/docs/docs/development/nodeinfo_extensions.md b/docs/docs/development/nodeinfo_extensions.md
new file mode 100644
index 000000000..3edc31524
--- /dev/null
+++ b/docs/docs/development/nodeinfo_extensions.md
@@ -0,0 +1,141 @@
+# Nodeinfo Extensions
+
+Akkoma currently implements version 2.0 and 2.1 of nodeinfo spec,
+but provides the following additional fields.
+
+## metadata
+
+The spec leaves the content of `metadata` up to implementations
+and indeed Akkoma adds many fields here apart from the commonly
+found `nodeName` and `nodeDescription` fields.
+
+### accountActivationRequired
+Whether or not users need to confirm their email before completing registration.
+*(boolean)*
+
+!!! note
+ Not to be confused with account approval, where each registration needs to
+ be manually approved by an admin. Account approval has no nodeinfo entry.
+
+### features
+
+Array of strings denoting supported server features. E.g. a server supporting
+quote posts should include a `"quote_posting"` entry here.
+
+A non-exhaustive list of possible features:
+- `polls`
+- `quote_posting`
+- `editing`
+- `bubble_timeline`
+- `pleroma_emoji_reactions` *(Unicode emoji)*
+- `custom_emoji_reactions`
+- `akkoma_api`
+- `akkoma:machine_translation`
+- `mastodon_api`
+- `pleroma_api`
+
+### federatedTimelineAvailable
+Whether or not the “federated timeline”, i.e. a timeline containing posts from
+the entire known network, is made available.
+*(boolean)*
+
+### federation
+This section is optional and can contain various custom keys describing federation policies.
+The following are required to be presented:
+- `enabled` *(boolean)* whether the server federates at all
+
+A non-exhaustive list of optional keys:
+- `exclusions` *(boolean)* whether some federation policies are withheld
+- `mrf_simple` *(object)* describes how the Simple MRF policy is configured
+
+### fieldsLimits
+A JSON object documenting restriction for user account info fields.
+All properties are integers.
+
+- `maxFields` maximum number of account info fields local users can create
+- `maxRemoteFields` maximum number of account info fields remote users can have
+ before the user gets rejected or fields truncated
+- `nameLength` maximum length of a field’s name
+- `valueLength` maximum length of a field’s value
+
+### invitesEnabled
+Whether or not signing up via invite codes is possible.
+*(boolean)*
+
+### localBubbleInstances
+Array of domains (as strings) of other instances chosen
+by the admin which are shown in the bubble timeline.
+
+### mailerEnabled
+Whether or not the instance can send out emails.
+*(boolean)*
+
+### nodeDescription
+Human-friendly description of this instance
+*(string)*
+
+### nodeName
+Human-friendly name of this instance
+*(string)*
+
+### pollLimits
+JSON object containing limits for polls created by local users.
+All values are integers.
+- `max_options` maximum number of poll options
+- `max_option_chars` maximum characters per poll option
+- `min_expiration` minimum time in seconds a poll must be open for
+- `max_expiration` maximum time a poll is allowed to be open for
+
+### postFormats
+Array of strings containing media types for supported post source formats.
+A non-exhaustive list of possible values:
+- `text/plain`
+- `text/markdown`
+- `text/bbcode`
+- `text/x.misskeymarkdown`
+
+### private
+Whether or not unauthenticated API access is permitted.
+*(boolean)*
+
+### privilegedStaff
+Whether or not moderators are trusted to perform some
+additional tasks like e.g. issuing password reset emails.
+
+### publicTimelineVisibility
+JSON object containing boolean-valued keys reporting
+if a given timeline can be viewed without login.
+- `local`
+- `federated`
+- `bubble`
+
+### restrictedNicknames
+Array of strings listing nicknames forbidden to be used during signup.
+
+### skipThreadContainment
+Whether broken threads are filtered out
+*(boolean)*
+
+### staffAccounts
+Array containing ActivityPub IDs of local accounts
+with some form of elevated privilege on the instance.
+
+### suggestions
+JSON object containing info on whether the interaction-based
+Mastodon `/api/v1/suggestions` feature is enabled and optionally
+additional implementation-defined fields with more details
+on e.g. how suggested users are selected.
+
+!!! note
+ This has no relation to the newer /api/v2/suggestions API
+ which also (or exclusively) contains staff-curated entries.
+
+- `enabled` *(boolean)* whether or not user recommendations are enabled
+
+### uploadLimits
+JSON object documenting various upload-related size limits.
+All values are integers and in bytes.
+- `avatar` maximum size of uploaded user avatars
+- `banner` maximum size of uploaded user profile banners
+- `background` maximum size of uploaded user profile backgrounds
+- `general` maximum size for all other kinds of uploads
diff --git a/docs/docs/installation/alpine_linux_en.md b/docs/docs/installation/alpine_linux_en.md
index bdfb96d77..e5f88ac30 100644
--- a/docs/docs/installation/alpine_linux_en.md
+++ b/docs/docs/installation/alpine_linux_en.md
@@ -145,47 +145,13 @@ If you want to open your newly installed instance to the world, you should run n
doas apk add nginx
```
-* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
-
-```shell
-doas apk add certbot
-```
-
-and then set it up:
-
-```shell
-doas mkdir -p /var/lib/letsencrypt/
-doas certbot certonly --email -d --standalone
-```
-
-If that doesn’t work, make sure, that nginx is not already running. If it still doesn’t work, try setting up nginx first (change ssl “on” to “off” and try again).
-
* Copy the example nginx configuration to the nginx folder
```shell
doas cp /opt/akkoma/installation/nginx/akkoma.nginx /etc/nginx/conf.d/akkoma.conf
```
-* Before starting nginx edit the configuration and change it to your needs. You must change change `server_name` and the paths to the certificates. You can use `nano` (install with `apk add nano` if missing).
-
-```
-server {
- server_name your.domain;
- listen 80;
- ...
-}
-
-server {
- server_name your.domain;
- listen 443 ssl http2;
- ...
- ssl_trusted_certificate /etc/letsencrypt/live/your.domain/chain.pem;
- ssl_certificate /etc/letsencrypt/live/your.domain/fullchain.pem;
- ssl_certificate_key /etc/letsencrypt/live/your.domain/privkey.pem;
- ...
-}
-```
-
+* Before starting nginx edit the configuration and change it to your needs. You must change change `server_name`. You can use `nano` (install with `apk add nano` if missing).
* Enable and start nginx:
```shell
@@ -193,10 +159,37 @@ doas rc-update add nginx
doas rc-service nginx start
```
-If you need to renew the certificate in the future, uncomment the relevant location block in the nginx config and run:
+* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
```shell
-doas certbot certonly --email -d --webroot -w /var/lib/letsencrypt/
+doas apk add certbot certbot-nginx
+```
+
+and then set it up:
+
+```shell
+doas mkdir -p /var/lib/letsencrypt/
+doas certbot --email -d -d --nginx
+```
+
+If that doesn't work the first time, add `--dry-run` to further attempts to avoid being ratelimited as you identify the issue, and do not remove it until the dry run succeeds. A common source of problems are nginx config syntax errors; this can be checked for by running `nginx -t`.
+
+To automatically renew, set up a cron job like so:
+
+```shell
+# Enable the crond service
+doas rc-update add crond
+doas rc-service crond start
+
+# Test that renewals work
+doas certbot renew --cert-name yourinstance.tld --nginx --dry-run
+
+# Add the renewal task to cron
+echo '#!/bin/sh
+certbot renew --cert-name yourinstance.tld --nginx
+' | doas tee /etc/periodic/daily/renew-akkoma-cert
+doas chmod +x /etc/periodic/daily/renew-akkoma-cert
+
```
#### OpenRC service
diff --git a/docs/docs/installation/arch_linux_en.md b/docs/docs/installation/arch_linux_en.md
index 300a5d80f..b4b048b86 100644
--- a/docs/docs/installation/arch_linux_en.md
+++ b/docs/docs/installation/arch_linux_en.md
@@ -136,16 +136,17 @@ If you want to open your newly installed instance to the world, you should run n
sudo pacman -S nginx
```
-* Create directories for available and enabled sites:
+* Copy the example nginx configuration:
```shell
-sudo mkdir -p /etc/nginx/sites-{available,enabled}
+sudo cp /opt/akkoma/installation/nginx/akkoma.nginx /etc/nginx/conf.d/akkoma.conf
```
-* Append the following line at the end of the `http` block in `/etc/nginx/nginx.conf`:
+* Before starting nginx edit the configuration and change it to your needs (e.g. change servername, change cert paths)
+* Enable and start nginx:
-```Nginx
-include sites-enabled/*;
+```shell
+sudo systemctl enable --now nginx.service
```
* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
@@ -158,32 +159,18 @@ and then set it up:
```shell
sudo mkdir -p /var/lib/letsencrypt/
-sudo certbot certonly --email -d --standalone
+sudo certbot --email -d -d --nginx
```
-If that doesn’t work, make sure, that nginx is not already running. If it still doesn’t work, try setting up nginx first (change ssl “on” to “off” and try again).
+If that doesn't work the first time, add `--dry-run` to further attempts to avoid being ratelimited as you identify the issue, and do not remove it until the dry run succeeds. A common source of problems are nginx config syntax errors; this can be checked for by running `nginx -t`.
----
-
-* Copy the example nginx configuration and activate it:
+To make sure renewals work, enable the appropriate systemd timer:
```shell
-sudo cp /opt/akkoma/installation/nginx/akkoma.nginx /etc/nginx/sites-available/akkoma.nginx
-sudo ln -s /etc/nginx/sites-available/akkoma.nginx /etc/nginx/sites-enabled/akkoma.nginx
+sudo systemctl enable --now certbot-renew.timer
```
-* Before starting nginx edit the configuration and change it to your needs (e.g. change servername, change cert paths)
-* Enable and start nginx:
-
-```shell
-sudo systemctl enable --now nginx.service
-```
-
-If you need to renew the certificate in the future, uncomment the relevant location block in the nginx config and run:
-
-```shell
-sudo certbot certonly --email -d --webroot -w /var/lib/letsencrypt/
-```
+Certificate renewal should be handled automatically by Certbot from now on.
#### Other webserver/proxies
diff --git a/docs/docs/installation/debian_based_en.md b/docs/docs/installation/debian_based_en.md
index 6a0f332ec..5dddabe7f 100644
--- a/docs/docs/installation/debian_based_en.md
+++ b/docs/docs/installation/debian_based_en.md
@@ -155,23 +155,6 @@ If you want to open your newly installed instance to the world, you should run n
sudo apt install nginx
```
-* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
-
-```shell
-sudo apt install certbot
-```
-
-and then set it up:
-
-```shell
-sudo mkdir -p /var/lib/letsencrypt/
-sudo certbot certonly --email -d --standalone
-```
-
-If that doesn’t work, make sure, that nginx is not already running. If it still doesn’t work, try setting up nginx first (change ssl “on” to “off” and try again).
-
----
-
* Copy the example nginx configuration and activate it:
```shell
@@ -186,12 +169,23 @@ sudo ln -s /etc/nginx/sites-available/akkoma.nginx /etc/nginx/sites-enabled/akko
sudo systemctl enable --now nginx.service
```
-If you need to renew the certificate in the future, uncomment the relevant location block in the nginx config and run:
+* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
```shell
-sudo certbot certonly --email -d --webroot -w /var/lib/letsencrypt/
+sudo apt install certbot python3-certbot-nginx
```
+and then set it up:
+
+```shell
+sudo mkdir -p /var/lib/letsencrypt/
+sudo certbot --email -d -d --nginx
+```
+
+If that doesn't work the first time, add `--dry-run` to further attempts to avoid being ratelimited as you identify the issue, and do not remove it until the dry run succeeds. A common source of problems are nginx config syntax errors; this can be checked for by running `nginx -t`.
+
+Certificate renewal should be handled automatically by Certbot from now on.
+
#### Other webserver/proxies
You can find example configurations for them in `/opt/akkoma/installation/`.
diff --git a/docs/docs/installation/fedora_based_en.md b/docs/docs/installation/fedora_based_en.md
index 3e09f6996..40cdaf19a 100644
--- a/docs/docs/installation/fedora_based_en.md
+++ b/docs/docs/installation/fedora_based_en.md
@@ -135,23 +135,6 @@ If you want to open your newly installed instance to the world, you should run n
sudo dnf install nginx
```
-* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
-
-```shell
-sudo dnf install certbot
-```
-
-and then set it up:
-
-```shell
-sudo mkdir -p /var/lib/letsencrypt/
-sudo certbot certonly --email -d --standalone
-```
-
-If that doesn’t work, make sure, that nginx is not already running. If it still doesn’t work, try setting up nginx first (change ssl “on” to “off” and try again).
-
----
-
* Copy the example nginx configuration and activate it:
```shell
@@ -165,12 +148,23 @@ sudo cp /opt/akkoma/installation/nginx/akkoma.nginx /etc/nginx/conf.d/akkoma.con
sudo systemctl enable --now nginx.service
```
-If you need to renew the certificate in the future, uncomment the relevant location block in the nginx config and run:
+* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
```shell
-sudo certbot certonly --email -d --webroot -w /var/lib/letsencrypt/
+sudo dnf install certbot python3-certbot-nginx
```
+and then set it up:
+
+```shell
+sudo certbot --email -d -d --nginx
+```
+
+If that doesn't work the first time, add `--dry-run` to further attempts to avoid being ratelimited as you identify the issue, and do not remove it until the dry run succeeds. A common source of problems are nginx config syntax errors; this can be checked for by running `nginx -t`.
+
+Certificate renewal should be handled automatically by Certbot from now on.
+
+
#### Other webserver/proxies
You can find example configurations for them in `/opt/akkoma/installation/`.
diff --git a/docs/docs/installation/generic_dependencies.include b/docs/docs/installation/generic_dependencies.include
index d7701a28f..87669bd23 100644
--- a/docs/docs/installation/generic_dependencies.include
+++ b/docs/docs/installation/generic_dependencies.include
@@ -1,6 +1,6 @@
## Required dependencies
-* PostgreSQL 9.6+
+* PostgreSQL 12+
* Elixir 1.14+ (currently tested up to 1.16)
* Erlang OTP 25+ (currently tested up to OTP26)
* git
diff --git a/docs/docs/installation/gentoo_en.md b/docs/docs/installation/gentoo_en.md
index 76069f645..0e54a3e32 100644
--- a/docs/docs/installation/gentoo_en.md
+++ b/docs/docs/installation/gentoo_en.md
@@ -201,25 +201,6 @@ Assuming you want to open your newly installed federated social network to, well
include sites-enabled/*;
```
-* Setup your SSL cert, using your method of choice or certbot. If using certbot, install it if you haven't already:
-
-```shell
- # emerge --ask app-crypt/certbot app-crypt/certbot-nginx
-```
-
-and then set it up:
-
-```shell
- # mkdir -p /var/lib/letsencrypt/
- # certbot certonly --email -d --standalone
-```
-
-If that doesn't work the first time, add `--dry-run` to further attempts to avoid being ratelimited as you identify the issue, and do not remove it until the dry run succeeds. If that doesn’t work, make sure, that nginx is not already running. If it still doesn’t work, try setting up nginx first (change ssl “on” to “off” and try again). Often the answer to issues with certbot is to use the `--nginx` flag once you have nginx up and running.
-
-If you are using any additional subdomains, such as for a media proxy, you can re-run the same command with the subdomain in question. When it comes time to renew later, you will not need to run multiple times for each domain, one renew will handle it.
-
----
-
* Copy the example nginx configuration and activate it:
```shell
@@ -237,9 +218,24 @@ Pay special attention to the line that begins with `ssl_ecdh_curve`. It is stong
```shell
# rc-update add nginx default
- # /etc/init.d/nginx start
+ # rc-service nginx start
```
+* Setup your SSL cert, using your method of choice or certbot. If using certbot, install it if you haven't already:
+
+```shell
+ # emerge --ask app-crypt/certbot app-crypt/certbot-nginx
+```
+
+and then set it up:
+
+```shell
+ # mkdir -p /var/lib/letsencrypt/
+ # certbot --email -d -d --nginx
+```
+
+If that doesn't work the first time, add `--dry-run` to further attempts to avoid being ratelimited as you identify the issue, and do not remove it until the dry run succeeds. A common source of problems are nginx config syntax errors; this can be checked for by running `nginx -t`.
+
If you are using certbot, it is HIGHLY recommend you set up a cron job that renews your certificate, and that you install the suggested `certbot-nginx` plugin. If you don't do these things, you only have yourself to blame when your instance breaks suddenly because you forgot about it.
First, ensure that the command you will be installing into your crontab works.
diff --git a/docs/docs/installation/optional/media_graphics_packages.md b/docs/docs/installation/optional/media_graphics_packages.md
index ab9f958f9..922635fd0 100644
--- a/docs/docs/installation/optional/media_graphics_packages.md
+++ b/docs/docs/installation/optional/media_graphics_packages.md
@@ -14,7 +14,7 @@ Note: the packages are not required with the current default settings of Akkoma.
`ImageMagick` is a set of tools to create, edit, compose, or convert bitmap images.
It is required for the following Akkoma features:
- * `Pleroma.Upload.Filters.Mogrify`, `Pleroma.Upload.Filters.Mogrifun` upload filters (related config: `Plaroma.Upload/filters` in `config/config.exs`)
+ * `Pleroma.Upload.Filters.Mogrify`, `Pleroma.Upload.Filters.Mogrifun` upload filters (related config: `Pleroma.Upload/filters` in `config/config.exs`)
* Media preview proxy for still images (related config: `media_preview_proxy/enabled` in `config/config.exs`)
## `ffmpeg`
@@ -29,4 +29,5 @@ It is required for the following Akkoma features:
`exiftool` is media files metadata reader/writer.
It is required for the following Akkoma features:
- * `Pleroma.Upload.Filters.Exiftool` upload filter (related config: `Plaroma.Upload/filters` in `config/config.exs`)
+ * `Pleroma.Upload.Filters.Exiftool.StripMetadata` upload filter (related config: `Pleroma.Upload/filters` in `config/config.exs`)
+ * `Pleroma.Upload.Filters.Exiftool.ReadDescription` upload filter (related config: `Pleroma.Upload/filters` in `config/config.exs`)
diff --git a/docs/docs/installation/otp_en.md b/docs/docs/installation/otp_en.md
index 8a8ae077b..cdd1ba95d 100644
--- a/docs/docs/installation/otp_en.md
+++ b/docs/docs/installation/otp_en.md
@@ -9,7 +9,7 @@ This guide covers a installation using an OTP release. To install Akkoma from so
* For installing OTP releases on RedHat-based distros like Fedora and Centos Stream, please follow [this guide](./otp_redhat_en.md) instead.
* A (sub)domain pointed to the machine
-You will be running commands as root. If you aren't root already, please elevate your priviledges by executing `sudo su`/`su`.
+You will be running commands as root. If you aren't root already, please elevate your priviledges by executing `sudo -i`/`su`.
While in theory OTP releases are possbile to install on any compatible machine, for the sake of simplicity this guide focuses only on Debian/Ubuntu and Alpine.
@@ -176,11 +176,6 @@ su akkoma -s $SHELL -lc "./bin/pleroma stop"
### Setting up nginx and getting Let's Encrypt SSL certificaties
-#### Get a Let's Encrypt certificate
-```sh
-certbot certonly --standalone --preferred-challenges http -d yourinstance.tld
-```
-
#### Copy Akkoma nginx configuration to the nginx folder
The location of nginx configs is dependent on the distro
@@ -209,6 +204,14 @@ $EDITOR path-to-nginx-config
# Verify that the config is valid
nginx -t
```
+
+#### Get a Let's Encrypt certificate
+```sh
+certbot --nginx -d yourinstance.tld -d media.yourinstance.tld
+```
+
+If that doesn't work the first time, add `--dry-run` to further attempts to avoid being ratelimited as you identify the issue, and do not remove it until the dry run succeeds. A common source of problems are nginx config syntax errors; this can be checked for by running `nginx -t`.
+
#### Start nginx
=== "Alpine"
@@ -252,32 +255,19 @@ If everything worked, you should see Akkoma-FE when visiting your domain. If tha
## Post installation
### Setting up auto-renew of the Let's Encrypt certificate
-```sh
-# Create the directory for webroot challenges
-mkdir -p /var/lib/letsencrypt
-
-# Uncomment the webroot method
-$EDITOR path-to-nginx-config
-
-# Verify that the config is valid
-nginx -t
-```
=== "Alpine"
```
- # Restart nginx
- rc-service nginx restart
-
# Start the cron daemon and make it start on boot
rc-service crond start
rc-update add crond
# Ensure the webroot menthod and post hook is working
- certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --dry-run --post-hook 'rc-service nginx reload'
+ certbot renew --cert-name yourinstance.tld --nginx --dry-run
# Add it to the daily cron
echo '#!/bin/sh
- certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --post-hook "rc-service nginx reload"
+ certbot renew --cert-name yourinstance.tld --nginx
' > /etc/periodic/daily/renew-akkoma-cert
chmod +x /etc/periodic/daily/renew-akkoma-cert
@@ -286,22 +276,7 @@ nginx -t
```
=== "Debian/Ubuntu"
- ```
- # Restart nginx
- systemctl restart nginx
-
- # Ensure the webroot menthod and post hook is working
- certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --dry-run --post-hook 'systemctl reload nginx'
-
- # Add it to the daily cron
- echo '#!/bin/sh
- certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --post-hook "systemctl reload nginx"
- ' > /etc/cron.daily/renew-akkoma-cert
- chmod +x /etc/cron.daily/renew-akkoma-cert
-
- # If everything worked the output should contain /etc/cron.daily/renew-akkoma-cert
- run-parts --test /etc/cron.daily
- ```
+ This should be automatically enabled with the `certbot-renew.timer` systemd unit.
## Create your first user and set as admin
```sh
diff --git a/docs/docs/installation/otp_redhat_en.md b/docs/docs/installation/otp_redhat_en.md
index ea27af6f4..38c1b96db 100644
--- a/docs/docs/installation/otp_redhat_en.md
+++ b/docs/docs/installation/otp_redhat_en.md
@@ -82,6 +82,7 @@ Other than things bundled in the OTP release Akkoma depends on:
* PostgreSQL (also utilizes extensions in postgresql-contrib)
* nginx (could be swapped with another reverse proxy but this guide covers only it)
* certbot (for Let's Encrypt certificates, could be swapped with another ACME client, but this guide covers only it)
+ * If you are using certbot, also install the `python3-certbot-nginx` package for the nginx plugin
* libmagic/file
First, update your system, if not already done:
@@ -169,12 +170,6 @@ sudo -Hu akkoma ./bin/pleroma stop
### Setting up nginx and getting Let's Encrypt SSL certificaties
-#### Get a Let's Encrypt certificate
-
-```shell
-certbot certonly --standalone --preferred-challenges http -d yourinstance.tld
-```
-
#### Copy Akkoma nginx configuration to the nginx folder
```shell
@@ -195,8 +190,15 @@ sudo nginx -t
sudo systemctl start nginx
```
-At this point if you open your (sub)domain in a browser you should see a 502 error, that's because Akkoma is not started yet.
+#### Get a Let's Encrypt certificate
+```shell
+sudo certbot --email -d -d --nginx
+```
+
+If that doesn't work the first time, add `--dry-run` to further attempts to avoid being ratelimited as you identify the issue, and do not remove it until the dry run succeeds. A common source of problems are nginx config syntax errors; this can be checked for by running `nginx -t`.
+
+If you're successful with obtaining the certificates, opening your (sub)domain in a browser will result in a 502 error, since Akkoma hasn't been started yet.
### Setting up a system service
@@ -239,19 +241,11 @@ sudo nginx -t
# Restart nginx
sudo systemctl restart nginx
-# Ensure the webroot menthod and post hook is working
-sudo certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --dry-run --post-hook 'systemctl reload nginx'
-
-# Add it to the daily cron
-echo '#!/bin/sh
-certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --post-hook "systemctl reload nginx"
-' > /etc/cron.daily/renew-akkoma-cert
-sudo chmod +x /etc/cron.daily/renew-akkoma-cert
-
-# If everything worked the output should contain /etc/cron.daily/renew-akkoma-cert
-sudo run-parts --test /etc/cron.daily
+# Test that renewals work properly
+sudo certbot renew --cert-name yourinstance.tld --nginx --dry-run
```
+Assuming the commands were run successfully, certbot should be able to renew your certificates automatically via the `certbot-renew.timer` systemd unit.
## Create your first user and set as admin
```shell
diff --git a/elixir_buildpack.config b/elixir_buildpack.config
deleted file mode 100644
index ee9e051a6..000000000
--- a/elixir_buildpack.config
+++ /dev/null
@@ -1,2 +0,0 @@
-elixir_version=1.14.3
-erlang_version=25.3
diff --git a/installation/apache/akkoma-apache.conf b/installation/apache/akkoma-apache.conf
index 65bbf15e1..76357789e 100644
--- a/installation/apache/akkoma-apache.conf
+++ b/installation/apache/akkoma-apache.conf
@@ -60,7 +60,7 @@ ServerTokens Prod
Include /etc/letsencrypt/options-ssl-apache.conf
# Uncomment the following to enable MediaProxy caching on disk
- #CacheRoot /tmp/akkoma-media-cache/
+ #CacheRoot /var/tmp/akkoma-media-cache/
#CacheDirLevels 1
#CacheDirLength 2
#CacheEnable disk /proxy
diff --git a/installation/apache/apache-cache-purge.sh.example b/installation/apache/apache-cache-purge.sh.example
index 65df9cc11..59c2a5ed0 100755
--- a/installation/apache/apache-cache-purge.sh.example
+++ b/installation/apache/apache-cache-purge.sh.example
@@ -16,7 +16,7 @@
SCRIPTNAME=${0##*/}
# mod_disk_cache directory
-CACHE_DIRECTORY="/tmp/akkoma-media-cache"
+CACHE_DIRECTORY="/var/tmp/akkoma-media-cache"
## Removes an item via the htcacheclean utility
## $1 - the filename, can be a pattern .
diff --git a/installation/nginx/akkoma.nginx b/installation/nginx/akkoma.nginx
index 1d91ce22f..bdd5d0f8b 100644
--- a/installation/nginx/akkoma.nginx
+++ b/installation/nginx/akkoma.nginx
@@ -1,12 +1,9 @@
# default nginx site config for Akkoma
#
-# Simple installation instructions:
-# 1. Install your TLS certificate, possibly using Let's Encrypt.
-# 2. Replace 'example.tld' with your instance's domain wherever it appears.
-# 3. Copy this file to /etc/nginx/sites-available/ and then add a symlink to it
-# in /etc/nginx/sites-enabled/ and run 'nginx -s reload' or restart nginx.
+# See the documentation at docs.akkoma.dev for your particular distro/OS for
+# installation instructions.
-proxy_cache_path /tmp/akkoma-media-cache levels=1:2 keys_zone=akkoma_media_cache:10m max_size=10g
+proxy_cache_path /var/tmp/akkoma-media-cache levels=1:2 keys_zone=akkoma_media_cache:10m max_size=1g
inactive=720m use_temp_path=off;
# this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only
@@ -15,25 +12,19 @@ upstream phoenix {
server 127.0.0.1:4000 max_fails=5 fail_timeout=60s;
}
-server {
- server_name example.tld;
-
- listen 80;
- listen [::]:80;
-
- # Uncomment this if you need to use the 'webroot' method with certbot. Make sure
- # that the directory exists and that it is accessible by the webserver. If you followed
- # the guide, you already ran 'mkdir -p /var/lib/letsencrypt' to create the folder.
- # You may need to load this file with the ssl server block commented out, run certbot
- # to get the certificate, and then uncomment it.
- #
- # location ~ /\.well-known/acme-challenge {
- # root /var/lib/letsencrypt/;
- # }
- location / {
- return 301 https://$server_name$request_uri;
- }
-}
+# If you are setting up TLS certificates without certbot, uncomment the
+# following to enable HTTP -> HTTPS redirects. Certbot users don't need to do
+# this as it will automatically do this for you.
+# server {
+# server_name example.tld media.example.tld;
+#
+# listen 80;
+# listen [::]:80;
+#
+# location / {
+# return 301 https://$server_name$request_uri;
+# }
+# }
# Enable SSL session caching for improved performance
ssl_session_cache shared:ssl_session_cache:10m;
@@ -41,22 +32,29 @@ ssl_session_cache shared:ssl_session_cache:10m;
server {
server_name example.tld;
- listen 443 ssl http2;
- listen [::]:443 ssl http2;
- ssl_session_timeout 1d;
- ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
- ssl_session_tickets off;
+ # Once certbot is set up, this will automatically be updated to listen to
+ # port 443 with TLS alongside a redirect from plaintext HTTP.
+ listen 80;
+ listen [::]:80;
- ssl_trusted_certificate /etc/letsencrypt/live/example.tld/chain.pem;
- ssl_certificate /etc/letsencrypt/live/example.tld/fullchain.pem;
- ssl_certificate_key /etc/letsencrypt/live/example.tld/privkey.pem;
+ # If you are not using Certbot, comment out the above and uncomment/edit the following
+# listen 443 ssl http2;
+# listen [::]:443 ssl http2;
+# ssl_session_timeout 1d;
+# ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
+# ssl_session_tickets off;
+#
+# ssl_trusted_certificate /etc/letsencrypt/live/example.tld/chain.pem;
+# ssl_certificate /etc/letsencrypt/live/example.tld/fullchain.pem;
+# ssl_certificate_key /etc/letsencrypt/live/example.tld/privkey.pem;
+#
+# ssl_protocols TLSv1.2 TLSv1.3;
+# ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
+# ssl_prefer_server_ciphers off;
+# ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1;
+# ssl_stapling on;
+# ssl_stapling_verify on;
- ssl_protocols TLSv1.2 TLSv1.3;
- ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
- ssl_prefer_server_ciphers off;
- ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1;
- ssl_stapling on;
- ssl_stapling_verify on;
gzip_vary on;
gzip_proxied any;
@@ -86,27 +84,22 @@ server {
# Upload and MediaProxy Subdomain
# (see main domain setup for more details)
-server {
- server_name media.example.tld;
-
- listen 80;
- listen [::]:80;
-
- location / {
- return 301 https://$server_name$request_uri;
- }
-}
-
server {
server_name media.example.tld;
- listen 443 ssl http2;
- listen [::]:443 ssl http2;
+ # Same as above, will be updated to HTTPS once certbot is set up.
+ listen 80;
+ listen [::]:80;
- ssl_trusted_certificate /etc/letsencrypt/live/media.example.tld/chain.pem;
- ssl_certificate /etc/letsencrypt/live/media.example.tld/fullchain.pem;
- ssl_certificate_key /etc/letsencrypt/live/media.example.tld/privkey.pem;
- # .. copy all other the ssl_* and gzip_* stuff from main domain
+ # If you are not using certbot, comment the above and copy all the ssl
+ # stuff from above into here.
+
+ gzip_vary on;
+ gzip_proxied any;
+ gzip_comp_level 6;
+ gzip_buffers 16 8k;
+ gzip_http_version 1.1;
+ gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript application/activity+json application/atom+xml;
# the nginx default is 1m, not enough for large media uploads
client_max_body_size 16m;
diff --git a/installation/nginx/nginx-cache-purge.sh.example b/installation/nginx/nginx-cache-purge.sh.example
index 6cd01d1e7..be5f186e4 100755
--- a/installation/nginx/nginx-cache-purge.sh.example
+++ b/installation/nginx/nginx-cache-purge.sh.example
@@ -5,7 +5,7 @@
SCRIPTNAME=${0##*/}
# NGINX cache directory
-CACHE_DIRECTORY="/tmp/akkoma-media-cache"
+CACHE_DIRECTORY="/var/tmp/akkoma-media-cache"
## Return the files where the items are cached.
## $1 - the filename, can be a pattern .
diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex
index 6431f0a1c..52c651dc5 100644
--- a/lib/mix/pleroma.ex
+++ b/lib/mix/pleroma.ex
@@ -16,7 +16,7 @@ defmodule Mix.Pleroma do
:fast_html,
:oban
]
- @cachex_children ["object", "user", "scrubber", "web_resp"]
+ @cachex_children ["object", "user", "scrubber", "web_resp", "http_backoff"]
@doc "Common functions to be reused in mix tasks"
def start_pleroma do
Pleroma.Config.Holder.save_default()
diff --git a/lib/mix/tasks/pleroma/activity.ex b/lib/mix/tasks/pleroma/activity.ex
index 763ae6b91..b2489aadf 100644
--- a/lib/mix/tasks/pleroma/activity.ex
+++ b/lib/mix/tasks/pleroma/activity.ex
@@ -32,18 +32,11 @@ def run(["delete_by_keyword", user, keyword | _rest]) do
true -> :gin
end
- search_function =
- if :persistent_term.get({Pleroma.Repo, :postgres_version}) >= 11 do
- :websearch
- else
- :plain
- end
-
Activity
|> Activity.with_preloaded_object()
|> Activity.restrict_deactivated_users()
|> Activity.Queries.by_author(u)
- |> DatabaseSearch.query_with(index_type, keyword, search_function)
+ |> DatabaseSearch.query_with(index_type, keyword)
|> Pagination.fetch_paginated(
%{"offset" => 0, "limit" => 20, "skip_order" => false},
:offset
diff --git a/lib/mix/tasks/pleroma/diagnostics.ex b/lib/mix/tasks/pleroma/diagnostics.ex
index 87be38b78..ab7def110 100644
--- a/lib/mix/tasks/pleroma/diagnostics.ex
+++ b/lib/mix/tasks/pleroma/diagnostics.ex
@@ -17,6 +17,13 @@ def run(["http", url]) do
|> IO.inspect()
end
+ def run(["fetch_object", url]) do
+ start_pleroma()
+
+ Pleroma.Object.Fetcher.fetch_object_from_id(url)
+ |> IO.inspect()
+ end
+
def run(["home_timeline", nickname]) do
start_pleroma()
user = Repo.get_by!(User, nickname: nickname)
diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex
index 276a1bceb..ccab734f8 100644
--- a/lib/mix/tasks/pleroma/instance.ex
+++ b/lib/mix/tasks/pleroma/instance.ex
@@ -37,9 +37,9 @@ def run(["gen" | rest]) do
static_dir: :string,
listen_ip: :string,
listen_port: :string,
- strip_uploads: :string,
- anonymize_uploads: :string,
- analyze_uploads: :string
+ strip_uploads_metadata: :string,
+ read_uploads_description: :string,
+ anonymize_uploads: :string
],
aliases: [
o: :output,
@@ -192,21 +192,38 @@ def run(["gen" | rest]) do
)
|> Path.expand()
- {strip_uploads_message, strip_uploads_default} =
+ {strip_uploads_metadata_message, strip_uploads_metadata_default} =
if Pleroma.Utils.command_available?("exiftool") do
- {"Do you want to strip location (GPS) data from uploaded images? This requires exiftool, it was detected as installed. (y/n)",
+ {"Do you want to strip metadata from uploaded images? This requires exiftool, it was detected as installed. (y/n)",
"y"}
else
- {"Do you want to strip location (GPS) data from uploaded images? This requires exiftool, it was detected as not installed, please install it if you answer yes. (y/n)",
+ {"Do you want to strip metadata from uploaded images? This requires exiftool, it was detected as not installed, please install it if you answer yes. (y/n)",
"n"}
end
- strip_uploads =
+ strip_uploads_metadata =
get_option(
options,
- :strip_uploads,
- strip_uploads_message,
- strip_uploads_default
+ :strip_uploads_metadata,
+ strip_uploads_metadata_message,
+ strip_uploads_metadata_default
+ ) === "y"
+
+ {read_uploads_description_message, read_uploads_description_default} =
+ if Pleroma.Utils.command_available?("exiftool") do
+ {"Do you want to read data from uploaded files so clients can use it to prefill fields like image description? This requires exiftool, it was detected as installed. (y/n)",
+ "y"}
+ else
+ {"Do you want to read data from uploaded files so clients can use it to prefill fields like image description? This requires exiftool, it was detected as not installed, please install it if you answer yes. (y/n)",
+ "n"}
+ end
+
+ read_uploads_description =
+ get_option(
+ options,
+ :read_uploads_description,
+ read_uploads_description_message,
+ read_uploads_description_default
) === "y"
anonymize_uploads =
@@ -277,9 +294,9 @@ def run(["gen" | rest]) do
listen_port: listen_port,
upload_filters:
upload_filters(%{
- strip: strip_uploads,
- anonymize: anonymize_uploads,
- analyze: analyze_uploads
+ strip_metadata: strip_uploads_metadata,
+ read_description: read_uploads_description,
+ anonymize: anonymize_uploads
})
)
@@ -354,11 +371,20 @@ defp write_robots_txt(static_dir, indexable, template_dir) do
end
defp upload_filters(filters) when is_map(filters) do
+ enabled_filters = []
+
enabled_filters =
- if filters.strip do
- [Pleroma.Upload.Filter.Exiftool]
+ if filters.read_description do
+ enabled_filters ++ [Pleroma.Upload.Filter.Exiftool.ReadDescription]
else
- []
+ enabled_filters
+ end
+
+ enabled_filters =
+ if filters.strip_metadata do
+ enabled_filters ++ [Pleroma.Upload.Filter.Exiftool.StripMetadata]
+ else
+ enabled_filters
end
enabled_filters =
diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex
index 07c0deea0..fa4ea8da3 100644
--- a/lib/mix/tasks/pleroma/search/meilisearch.ex
+++ b/lib/mix/tasks/pleroma/search/meilisearch.ex
@@ -136,8 +136,12 @@ def run(["show-keys", master_key]) do
decoded = Jason.decode!(result.body)
if decoded["results"] do
- Enum.each(decoded["results"], fn %{"description" => desc, "key" => key} ->
- IO.puts("#{desc}: #{key}")
+ Enum.each(decoded["results"], fn
+ %{"name" => name, "key" => key} ->
+ IO.puts("#{name}: #{key}")
+
+ %{"description" => desc, "key" => key} ->
+ IO.puts("#{desc}: #{key}")
end)
else
IO.puts("Error fetching the keys, check the master key is correct: #{inspect(decoded)}")
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index bf851f808..f820cbdae 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -258,6 +258,27 @@ def get_create_by_object_ap_id(ap_id) when is_binary(ap_id) do
def get_create_by_object_ap_id(_), do: nil
+ @doc """
+ Accepts a list of `ap__id`.
+ Returns a query yielding Create activities for the given objects,
+ in the same order as they were specified in the input list.
+ """
+ @spec get_presorted_create_by_object_ap_id([String.t()]) :: Ecto.Queryable.t()
+ def get_presorted_create_by_object_ap_id(ap_ids) do
+ from(
+ a in Activity,
+ join:
+ ids in fragment(
+ "SELECT * FROM UNNEST(?::text[]) WITH ORDINALITY AS ids(ap_id, ord)",
+ ^ap_ids
+ ),
+ on:
+ ids.ap_id == fragment("?->>'object'", a.data) and
+ fragment("?->>'type'", a.data) == "Create",
+ order_by: [asc: ids.ord]
+ )
+ end
+
@doc """
Accepts `ap_id` or list of `ap_id`.
Returns a query.
diff --git a/lib/pleroma/activity/html.ex b/lib/pleroma/activity/html.ex
index e4aaad523..92840b1c0 100644
--- a/lib/pleroma/activity/html.ex
+++ b/lib/pleroma/activity/html.ex
@@ -28,7 +28,7 @@ defp get_cache_keys_for(activity_id) do
end
end
- defp add_cache_key_for(activity_id, additional_key) do
+ def add_cache_key_for(activity_id, additional_key) do
current = get_cache_keys_for(activity_id)
unless additional_key in current do
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index 28a86d0aa..506fc3daf 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -95,34 +95,17 @@ def start(_type, _args) do
opts = [strategy: :one_for_one, name: Pleroma.Supervisor, max_restarts: max_restarts]
- with {:ok, data} <- Supervisor.start_link(children, opts) do
- set_postgres_server_version()
- {:ok, data}
- else
+ case Supervisor.start_link(children, opts) do
+ {:ok, data} ->
+ {:ok, data}
+
e ->
- Logger.error("Failed to start!")
- Logger.error("#{inspect(e)}")
+ Logger.critical("Failed to start!")
+ Logger.critical("#{inspect(e)}")
e
end
end
- defp set_postgres_server_version do
- version =
- with %{rows: [[version]]} <- Ecto.Adapters.SQL.query!(Pleroma.Repo, "show server_version"),
- {num, _} <- Float.parse(version) do
- num
- else
- e ->
- Logger.warning(
- "Could not get the postgres version: #{inspect(e)}.\nSetting the default value of 9.6"
- )
-
- 9.6
- end
-
- :persistent_term.put({Pleroma.Repo, :postgres_version}, version)
- end
-
def load_custom_modules do
dir = Config.get([:modules, :runtime_dir])
@@ -179,7 +162,9 @@ defp cachex_children do
build_cachex("translations", default_ttl: :timer.hours(24 * 30), limit: 2500),
build_cachex("instances", default_ttl: :timer.hours(24), ttl_interval: 1000, limit: 2500),
build_cachex("request_signatures", default_ttl: :timer.hours(24 * 30), limit: 3000),
- build_cachex("rel_me", default_ttl: :timer.hours(24 * 30), limit: 300)
+ build_cachex("rel_me", default_ttl: :timer.hours(24 * 30), limit: 300),
+ build_cachex("host_meta", default_ttl: :timer.minutes(120), limit: 5000),
+ build_cachex("http_backoff", default_ttl: :timer.hours(24 * 30), limit: 10000)
]
end
@@ -279,7 +264,9 @@ def limiters_setup do
defp http_children do
proxy_url = Config.get([:http, :proxy_url])
proxy = Pleroma.HTTP.AdapterHelper.format_proxy(proxy_url)
- pool_size = Config.get([:http, :pool_size])
+ pool_size = Config.get([:http, :pool_size], 10)
+ pool_timeout = Config.get([:http, :pool_timeout], 60_000)
+ connection_timeout = Config.get([:http, :conn_max_idle_time], 10_000)
:public_key.cacerts_load()
@@ -289,6 +276,8 @@ defp http_children do
|> Pleroma.HTTP.AdapterHelper.add_pool_size(pool_size)
|> Pleroma.HTTP.AdapterHelper.maybe_add_proxy_pool(proxy)
|> Pleroma.HTTP.AdapterHelper.ensure_ipv6()
+ |> Pleroma.HTTP.AdapterHelper.add_default_conn_max_idle_time(connection_timeout)
+ |> Pleroma.HTTP.AdapterHelper.add_default_pool_max_idle_time(pool_timeout)
|> Keyword.put(:name, MyFinch)
[{Finch, config}]
diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex
index 3b747fd8f..4cd7e655e 100644
--- a/lib/pleroma/application_requirements.ex
+++ b/lib/pleroma/application_requirements.ex
@@ -213,7 +213,8 @@ defp do_check_pgroonga!(setting, migrate) do
defp check_system_commands!(:ok) do
filter_commands_statuses = [
- check_filter(Pleroma.Upload.Filter.Exiftool, "exiftool"),
+ check_filter(Pleroma.Upload.Filter.Exiftool.StripMetadata, "exiftool"),
+ check_filter(Pleroma.Upload.Filter.Exiftool.ReadDescription, "exiftool"),
check_filter(Pleroma.Upload.Filter.Mogrify, "mogrify"),
check_filter(Pleroma.Upload.Filter.Mogrifun, "mogrify"),
check_filter(Pleroma.Upload.Filter.AnalyzeMetadata, "mogrify"),
diff --git a/lib/pleroma/collections/fetcher.ex b/lib/pleroma/collections/fetcher.ex
index a2fcb7794..9ab883cc2 100644
--- a/lib/pleroma/collections/fetcher.ex
+++ b/lib/pleroma/collections/fetcher.ex
@@ -68,7 +68,10 @@ defp fetch_page_items(id, items \\ []) do
items
end
else
- {:error, {"Object has been deleted", _, _}} ->
+ {:error, :not_found} ->
+ items
+
+ {:error, :forbidden} ->
items
{:error, error} ->
diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex
index c299a4947..b62532595 100644
--- a/lib/pleroma/config/deprecation_warnings.ex
+++ b/lib/pleroma/config/deprecation_warnings.ex
@@ -22,6 +22,43 @@ defmodule Pleroma.Config.DeprecationWarnings do
"\n* `config :pleroma, :instance, :quarantined_instances` is now covered by `:pleroma, :mrf_simple, :reject`"}
]
+ def check_exiftool_filter do
+ filters = Config.get([Pleroma.Upload]) |> Keyword.get(:filters, [])
+
+ if Pleroma.Upload.Filter.Exiftool in filters do
+ Logger.warning("""
+ !!!DEPRECATION WARNING!!!
+ Your config is using Exiftool as a filter instead of Exiftool.StripMetadata. This should work for now, but you are advised to change to the new configuration to prevent possible issues later:
+
+ ```
+ config :pleroma, Pleroma.Upload,
+ filters: [Pleroma.Upload.Filter.Exiftool]
+ ```
+
+ Is now
+
+
+ ```
+ config :pleroma, Pleroma.Upload,
+ filters: [Pleroma.Upload.Filter.Exiftool.StripMetadata]
+ ```
+ """)
+
+ new_config =
+ filters
+ |> Enum.map(fn
+ Pleroma.Upload.Filter.Exiftool -> Pleroma.Upload.Filter.Exiftool.StripMetadata
+ filter -> filter
+ end)
+
+ Config.put([Pleroma.Upload, :filters], new_config)
+
+ :error
+ else
+ :ok
+ end
+ end
+
def check_simple_policy_tuples do
has_strings =
Config.get([:mrf_simple])
@@ -184,7 +221,8 @@ def warn do
check_simple_policy_tuples(),
check_http_adapter(),
check_uploader_base_url_set(),
- check_uploader_base_url_is_not_base_domain()
+ check_uploader_base_url_is_not_base_domain(),
+ check_exiftool_filter()
]
|> Enum.reduce(:ok, fn
:ok, :ok -> :ok
diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex
index d0cb830b7..37d6e4ea6 100644
--- a/lib/pleroma/constants.ex
+++ b/lib/pleroma/constants.ex
@@ -64,4 +64,7 @@ defmodule Pleroma.Constants do
"Service"
]
)
+
+ # Internally used as top-level types for media attachments and user images
+ const(attachment_types, do: ["Document", "Image"])
end
diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex
index bee66169d..01a5f8585 100644
--- a/lib/pleroma/html.ex
+++ b/lib/pleroma/html.ex
@@ -6,8 +6,6 @@ defmodule Pleroma.HTML do
# Scrubbers are compiled on boot so they can be configured in OTP releases
# @on_load :compile_scrubbers
- @cachex Pleroma.Config.get([:cachex, :provider], Cachex)
-
def compile_scrubbers do
dir = Path.join(:code.priv_dir(:pleroma), "scrubbers")
@@ -67,22 +65,9 @@ def ensure_scrubbed_html(
end
end
- def extract_first_external_url_from_object(%{data: %{"content" => content}} = object)
+ @spec extract_first_external_url_from_object(Pleroma.Object.t()) :: String.t() | nil
+ def extract_first_external_url_from_object(%{data: %{"content" => content}})
when is_binary(content) do
- unless object.data["fake"] do
- key = "URL|#{object.id}"
-
- @cachex.fetch!(:scrubber_cache, key, fn _key ->
- {:commit, {:ok, extract_first_external_url(content)}}
- end)
- else
- {:ok, extract_first_external_url(content)}
- end
- end
-
- def extract_first_external_url_from_object(_), do: {:error, :no_content}
-
- def extract_first_external_url(content) do
content
|> Floki.parse_fragment!()
|> Floki.find("a:not(.mention,.hashtag,.attachment,[rel~=\"tag\"])")
@@ -90,4 +75,6 @@ def extract_first_external_url(content) do
|> Floki.attribute("href")
|> Enum.at(0)
end
+
+ def extract_first_external_url_from_object(_), do: nil
end
diff --git a/lib/pleroma/http.ex b/lib/pleroma/http.ex
index 9346ffa16..3ff27915c 100644
--- a/lib/pleroma/http.ex
+++ b/lib/pleroma/http.ex
@@ -74,7 +74,12 @@ def request(method, url, body, headers, options) when is_binary(url) do
request = build_request(method, headers, options, url, body, params)
client = Tesla.client([Tesla.Middleware.FollowRedirects, Tesla.Middleware.Telemetry])
+ Logger.debug("Outbound: #{method} #{url}")
request(client, request)
+ rescue
+ e ->
+ Logger.error("Failed to fetch #{url}: #{inspect(e)}")
+ {:error, :fetch_error}
end
@spec request(Client.t(), keyword()) :: {:ok, Env.t()} | {:error, any()}
diff --git a/lib/pleroma/http/adapter_helper.ex b/lib/pleroma/http/adapter_helper.ex
index c1d95b3a4..74b0b88f1 100644
--- a/lib/pleroma/http/adapter_helper.ex
+++ b/lib/pleroma/http/adapter_helper.ex
@@ -116,6 +116,20 @@ defp maybe_add_transport_opts(opts) do
put_in(opts, [:pools, :default, :conn_opts, :transport_opts, :inet6], true)
end
+ def add_default_pool_max_idle_time(opts, pool_timeout) do
+ opts
+ |> maybe_add_pools()
+ |> maybe_add_default_pool()
+ |> put_in([:pools, :default, :pool_max_idle_time], pool_timeout)
+ end
+
+ def add_default_conn_max_idle_time(opts, connection_timeout) do
+ opts
+ |> maybe_add_pools()
+ |> maybe_add_default_pool()
+ |> put_in([:pools, :default, :conn_max_idle_time], connection_timeout)
+ end
+
@doc """
Merge default connection & adapter options with received ones.
"""
diff --git a/lib/pleroma/http/backoff.ex b/lib/pleroma/http/backoff.ex
new file mode 100644
index 000000000..b3f734a92
--- /dev/null
+++ b/lib/pleroma/http/backoff.ex
@@ -0,0 +1,121 @@
+defmodule Pleroma.HTTP.Backoff do
+ alias Pleroma.HTTP
+ require Logger
+
+ @cachex Pleroma.Config.get([:cachex, :provider], Cachex)
+ @backoff_cache :http_backoff_cache
+
+ # attempt to parse a timestamp from a header
+ # returns nil if it can't parse the timestamp
+ @spec timestamp_or_nil(binary) :: DateTime.t() | nil
+ defp timestamp_or_nil(header) do
+ case DateTime.from_iso8601(header) do
+ {:ok, stamp, _} ->
+ stamp
+
+ _ ->
+ nil
+ end
+ end
+
+ # attempt to parse the x-ratelimit-reset header from the headers
+ @spec x_ratelimit_reset(headers :: list) :: DateTime.t() | nil
+ defp x_ratelimit_reset(headers) do
+ with {_header, value} <- List.keyfind(headers, "x-ratelimit-reset", 0),
+ true <- is_binary(value) do
+ timestamp_or_nil(value)
+ else
+ _ ->
+ nil
+ end
+ end
+
+ # attempt to parse the Retry-After header from the headers
+ # this can be either a timestamp _or_ a number of seconds to wait!
+ # we'll return a datetime if we can parse it, or nil if we can't
+ @spec retry_after(headers :: list) :: DateTime.t() | nil
+ defp retry_after(headers) do
+ with {_header, value} <- List.keyfind(headers, "retry-after", 0),
+ true <- is_binary(value) do
+ # first, see if it's an integer
+ case Integer.parse(value) do
+ {seconds, ""} ->
+ Logger.debug("Parsed Retry-After header: #{seconds} seconds")
+ DateTime.utc_now() |> Timex.shift(seconds: seconds)
+
+ _ ->
+ # if it's not an integer, try to parse it as a timestamp
+ timestamp_or_nil(value)
+ end
+ else
+ _ ->
+ nil
+ end
+ end
+
+ # given a set of headers, will attempt to find the next backoff timestamp
+ # if it can't find one, it will default to 5 minutes from now
+ @spec next_backoff_timestamp(%{headers: list}) :: DateTime.t()
+ defp next_backoff_timestamp(%{headers: headers}) when is_list(headers) do
+ default_5_minute_backoff =
+ DateTime.utc_now()
+ |> Timex.shift(seconds: 5 * 60)
+
+ backoff =
+ [&x_ratelimit_reset/1, &retry_after/1]
+ |> Enum.map(& &1.(headers))
+ |> Enum.find(&(&1 != nil))
+
+ if is_nil(backoff) do
+ Logger.debug("No backoff headers found, defaulting to 5 minutes from now")
+ default_5_minute_backoff
+ else
+ Logger.debug("Found backoff header, will back off until: #{backoff}")
+ backoff
+ end
+ end
+
+ defp next_backoff_timestamp(_), do: DateTime.utc_now() |> Timex.shift(seconds: 5 * 60)
+
+ # utility function to check the HTTP response for potential backoff headers
+ # will check if we get a 429 or 503 response, and if we do, will back off for a bit
+ @spec check_backoff({:ok | :error, HTTP.Env.t()}, binary()) ::
+ {:ok | :error, HTTP.Env.t()} | {:error, :ratelimit}
+ defp check_backoff({:ok, env}, host) do
+ case env.status do
+ status when status in [429, 503] ->
+ Logger.error("Rate limited on #{host}! Backing off...")
+ timestamp = next_backoff_timestamp(env)
+ ttl = Timex.diff(timestamp, DateTime.utc_now(), :seconds)
+ # we will cache the host for 5 minutes
+ @cachex.put(@backoff_cache, host, true, ttl: ttl)
+ {:error, :ratelimit}
+
+ _ ->
+ {:ok, env}
+ end
+ end
+
+ defp check_backoff(env, _), do: env
+
+ @doc """
+ this acts as a single throughput for all GET requests
+ we will check if the host is in the cache, and if it is, we will automatically fail the request
+ this ensures that we don't hammer the server with requests, and instead wait for the backoff to expire
+ this is a very simple implementation, and can be improved upon!
+ """
+ @spec get(binary, list, list) :: {:ok | :error, HTTP.Env.t()} | {:error, :ratelimit}
+ def get(url, headers \\ [], options \\ []) do
+ %{host: host} = URI.parse(url)
+
+ case @cachex.get(@backoff_cache, host) do
+ {:ok, nil} ->
+ url
+ |> HTTP.get(headers, options)
+ |> check_backoff(host)
+
+ _ ->
+ {:error, :ratelimit}
+ end
+ end
+end
diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index 844251a18..379b361f8 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -178,7 +178,10 @@ def normalize(ap_id, options) when is_binary(ap_id) do
ap_id
Keyword.get(options, :fetch) ->
- Fetcher.fetch_object_from_id!(ap_id, options)
+ case Fetcher.fetch_object_from_id(ap_id, options) do
+ {:ok, object} -> object
+ _ -> nil
+ end
true ->
get_cached_by_ap_id(ap_id)
diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex
index 267a82b27..937026e04 100644
--- a/lib/pleroma/object/fetcher.ex
+++ b/lib/pleroma/object/fetcher.ex
@@ -122,7 +122,7 @@ def refetch_object(%Object{data: %{"id" => id}} = object) do
{:ok, object}
else
{:local, true} -> {:ok, object}
- {:id, false} -> {:error, "Object id changed on refetch"}
+ {:id, false} -> {:error, :id_mismatch}
e -> {:error, e}
end
end
@@ -136,10 +136,13 @@ def refetch_object(%Object{data: %{"id" => id}} = object) do
def fetch_object_from_id(id, options \\ []) do
with %URI{} = uri <- URI.parse(id),
# let's check the URI is even vaguely valid first
- {:scheme, true} <- {:scheme, uri.scheme == "http" or uri.scheme == "https"},
+ {:valid_uri_scheme, true} <-
+ {:valid_uri_scheme, uri.scheme == "http" or uri.scheme == "https"},
# If we have instance restrictions, apply them here to prevent fetching from unwanted instances
- {:ok, nil} <- Pleroma.Web.ActivityPub.MRF.SimplePolicy.check_reject(uri),
- {:ok, _} <- Pleroma.Web.ActivityPub.MRF.SimplePolicy.check_accept(uri),
+ {:mrf_reject_check, {:ok, nil}} <-
+ {:mrf_reject_check, Pleroma.Web.ActivityPub.MRF.SimplePolicy.check_reject(uri)},
+ {:mrf_accept_check, {:ok, _}} <-
+ {:mrf_accept_check, Pleroma.Web.ActivityPub.MRF.SimplePolicy.check_accept(uri)},
{_, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)},
{_, true} <- {:allowed_depth, Federator.allowed_thread_distance?(options[:depth])},
{_, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)},
@@ -151,20 +154,37 @@ def fetch_object_from_id(id, options \\ []) do
{:object, data, Object.normalize(activity, fetch: false)} do
{:ok, object}
else
- {:allowed_depth, false} ->
- {:error, "Max thread distance exceeded."}
+ {:allowed_depth, false} = e ->
+ log_fetch_error(id, e)
+ {:error, :allowed_depth}
- {:scheme, false} ->
- {:error, "URI Scheme Invalid"}
+ {:valid_uri_scheme, _} = e ->
+ log_fetch_error(id, e)
+ {:error, :invalid_uri_scheme}
- {:transmogrifier, {:error, {:reject, e}}} ->
- {:reject, e}
+ {:mrf_reject_check, _} = e ->
+ log_fetch_error(id, e)
+ {:reject, :mrf}
- {:transmogrifier, {:reject, e}} ->
- {:reject, e}
+ {:mrf_accept_check, _} = e ->
+ log_fetch_error(id, e)
+ {:reject, :mrf}
- {:transmogrifier, _} = e ->
- {:error, e}
+ {:containment, reason} = e ->
+ log_fetch_error(id, e)
+ {:error, reason}
+
+ {:transmogrifier, {:error, {:reject, reason}}} = e ->
+ log_fetch_error(id, e)
+ {:reject, reason}
+
+ {:transmogrifier, {:reject, reason}} = e ->
+ log_fetch_error(id, e)
+ {:reject, reason}
+
+ {:transmogrifier, reason} = e ->
+ log_fetch_error(id, e)
+ {:error, reason}
{:object, data, nil} ->
reinject_object(%Object{}, data)
@@ -175,17 +195,21 @@ def fetch_object_from_id(id, options \\ []) do
{:fetch_object, %Object{} = object} ->
{:ok, object}
- {:fetch, {:error, error}} ->
- {:error, error}
-
- {:reject, reason} ->
- {:reject, reason}
+ {:fetch, {:error, reason}} = e ->
+ log_fetch_error(id, e)
+ {:error, reason}
e ->
- e
+ log_fetch_error(id, e)
+ {:error, e}
end
end
+ defp log_fetch_error(id, error) do
+ Logger.metadata(object: id)
+ Logger.error("Object rejected while fetching #{id} #{inspect(error)}")
+ end
+
defp prepare_activity_params(data) do
%{
"type" => "Create",
@@ -199,27 +223,6 @@ defp prepare_activity_params(data) do
|> Maps.put_if_present("bcc", data["bcc"])
end
- @doc "Identical to `fetch_object_from_id/2` but just directly returns the object or on error `nil`"
- def fetch_object_from_id!(id, options \\ []) do
- with {:ok, object} <- fetch_object_from_id(id, options) do
- object
- else
- {:error, %Tesla.Mock.Error{}} ->
- nil
-
- {:error, {"Object has been deleted", _id, _code}} ->
- nil
-
- {:reject, reason} ->
- Logger.debug("Rejected #{id} while fetching: #{inspect(reason)}")
- nil
-
- e ->
- Logger.error("Error while fetching #{id}: #{inspect(e)}")
- nil
- end
- end
-
defp make_signature(id, date) do
uri = URI.parse(id)
@@ -259,8 +262,13 @@ def fetch_and_contain_remote_object_from_id(%{"id" => id}),
def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
Logger.debug("Fetching object #{id} via AP")
- with {:scheme, true} <- {:scheme, String.starts_with?(id, "http")},
- {_, :ok} <- {:local_fetch, Containment.contain_local_fetch(id)},
+ with {:valid_uri_scheme, true} <- {:valid_uri_scheme, String.starts_with?(id, "http")},
+ %URI{} = uri <- URI.parse(id),
+ {:mrf_reject_check, {:ok, nil}} <-
+ {:mrf_reject_check, Pleroma.Web.ActivityPub.MRF.SimplePolicy.check_reject(uri)},
+ {:mrf_accept_check, {:ok, _}} <-
+ {:mrf_accept_check, Pleroma.Web.ActivityPub.MRF.SimplePolicy.check_accept(uri)},
+ {:local_fetch, :ok} <- {:local_fetch, Containment.contain_local_fetch(id)},
{:ok, final_id, body} <- get_object(id),
{:ok, data} <- safe_json_decode(body),
{_, :ok} <- {:strict_id, Containment.contain_id_to_fetch(final_id, data)},
@@ -271,17 +279,29 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
{:ok, data}
else
- {:strict_id, _} ->
- {:error, "Object's ActivityPub id/url does not match final fetch URL"}
+ {:strict_id, _} = e ->
+ log_fetch_error(id, e)
+ {:error, :id_mismatch}
- {:scheme, _} ->
- {:error, "Unsupported URI scheme"}
+ {:mrf_reject_check, _} = e ->
+ log_fetch_error(id, e)
+ {:reject, :mrf}
- {:local_fetch, _} ->
- {:error, "Trying to fetch local resource"}
+ {:mrf_accept_check, _} = e ->
+ log_fetch_error(id, e)
+ {:reject, :mrf}
- {:containment, _} ->
- {:error, "Object containment failed."}
+ {:valid_uri_scheme, _} = e ->
+ log_fetch_error(id, e)
+ {:error, :invalid_uri_scheme}
+
+ {:local_fetch, _} = e ->
+ log_fetch_error(id, e)
+ {:error, :local_resource}
+
+ {:containment, reason} ->
+ log_fetch_error(id, reason)
+ {:error, reason}
{:error, e} ->
{:error, e}
@@ -292,7 +312,7 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
end
def fetch_and_contain_remote_object_from_id(_id),
- do: {:error, "id must be a string"}
+ do: {:error, :invalid_id}
defp check_crossdomain_redirect(final_host, original_url)
@@ -334,7 +354,7 @@ def get_object(id) do
with {:ok, %{body: body, status: code, headers: headers, url: final_url}}
when code in 200..299 <-
- HTTP.get(id, headers),
+ HTTP.Backoff.get(id, headers),
remote_host <-
URI.parse(final_url).host,
{:cross_domain_redirect, false} <-
@@ -356,8 +376,11 @@ def get_object(id) do
{:error, {:content_type, content_type}}
end
else
+ {:ok, %{status: code}} when code in [401, 403] ->
+ {:error, :forbidden}
+
{:ok, %{status: code}} when code in [404, 410] ->
- {:error, {"Object has been deleted", id, code}}
+ {:error, :not_found}
{:error, e} ->
{:error, e}
diff --git a/lib/pleroma/scheduled_activity.ex b/lib/pleroma/scheduled_activity.ex
index 2b156341f..5292b1491 100644
--- a/lib/pleroma/scheduled_activity.ex
+++ b/lib/pleroma/scheduled_activity.ex
@@ -28,7 +28,7 @@ defmodule Pleroma.ScheduledActivity do
timestamps()
end
- def changeset(%ScheduledActivity{} = scheduled_activity, attrs) do
+ defp changeset(%ScheduledActivity{} = scheduled_activity, attrs) do
scheduled_activity
|> cast(attrs, [:scheduled_at, :params])
|> validate_required([:scheduled_at, :params])
@@ -40,26 +40,36 @@ defp with_media_attachments(
%{changes: %{params: %{"media_ids" => media_ids} = params}} = changeset
)
when is_list(media_ids) do
- media_attachments = Utils.attachments_from_ids(%{media_ids: media_ids})
+ user = User.get_by_id(changeset.data.user_id)
- params =
- params
- |> Map.put("media_attachments", media_attachments)
- |> Map.put("media_ids", media_ids)
+ case Utils.attachments_from_ids(user, %{media_ids: media_ids}) do
+ media_attachments when is_list(media_attachments) ->
+ params =
+ params
+ |> Map.put("media_attachments", media_attachments)
+ |> Map.put("media_ids", media_ids)
- put_change(changeset, :params, params)
+ put_change(changeset, :params, params)
+
+ {:error, _} = e ->
+ e
+
+ e ->
+ {:error, e}
+ end
end
defp with_media_attachments(changeset), do: changeset
- def update_changeset(%ScheduledActivity{} = scheduled_activity, attrs) do
+ defp update_changeset(%ScheduledActivity{} = scheduled_activity, attrs) do
+ # note: should this ever allow swapping media attachments, make sure ownership is checked
scheduled_activity
|> cast(attrs, [:scheduled_at])
|> validate_required([:scheduled_at])
|> validate_scheduled_at()
end
- def validate_scheduled_at(changeset) do
+ defp validate_scheduled_at(changeset) do
validate_change(changeset, :scheduled_at, fn _, scheduled_at ->
cond do
not far_enough?(scheduled_at) ->
@@ -77,7 +87,7 @@ def validate_scheduled_at(changeset) do
end)
end
- def exceeds_daily_user_limit?(user_id, scheduled_at) do
+ defp exceeds_daily_user_limit?(user_id, scheduled_at) do
ScheduledActivity
|> where(user_id: ^user_id)
|> where([sa], type(sa.scheduled_at, :date) == type(^scheduled_at, :date))
@@ -86,7 +96,7 @@ def exceeds_daily_user_limit?(user_id, scheduled_at) do
|> Kernel.>=(Config.get([ScheduledActivity, :daily_user_limit]))
end
- def exceeds_total_user_limit?(user_id) do
+ defp exceeds_total_user_limit?(user_id) do
ScheduledActivity
|> where(user_id: ^user_id)
|> select([sa], count(sa.id))
@@ -108,20 +118,29 @@ def far_enough?(scheduled_at) do
diff > @min_offset
end
- def new(%User{} = user, attrs) do
+ defp new(%User{} = user, attrs) do
changeset(%ScheduledActivity{user_id: user.id}, attrs)
end
@doc """
Creates ScheduledActivity and add to queue to perform at scheduled_at date
"""
- @spec create(User.t(), map()) :: {:ok, ScheduledActivity.t()} | {:error, Ecto.Changeset.t()}
+ @spec create(User.t(), map()) :: {:ok, ScheduledActivity.t()} | {:error, any()}
def create(%User{} = user, attrs) do
- Multi.new()
- |> Multi.insert(:scheduled_activity, new(user, attrs))
- |> maybe_add_jobs(Config.get([ScheduledActivity, :enabled]))
- |> Repo.transaction()
- |> transaction_response
+ case new(user, attrs) do
+ %Ecto.Changeset{} = sched_data ->
+ Multi.new()
+ |> Multi.insert(:scheduled_activity, sched_data)
+ |> maybe_add_jobs(Config.get([ScheduledActivity, :enabled]))
+ |> Repo.transaction()
+ |> transaction_response
+
+ {:error, _} = e ->
+ e
+
+ e ->
+ {:error, e}
+ end
end
defp maybe_add_jobs(multi, true) do
@@ -187,17 +206,7 @@ def for_user_query(%User{} = user) do
|> where(user_id: ^user.id)
end
- def due_activities(offset \\ 0) do
- naive_datetime =
- NaiveDateTime.utc_now()
- |> NaiveDateTime.add(offset, :millisecond)
-
- ScheduledActivity
- |> where([sa], sa.scheduled_at < ^naive_datetime)
- |> Repo.all()
- end
-
- def job_query(scheduled_activity_id) do
+ defp job_query(scheduled_activity_id) do
from(j in Oban.Job,
where: j.queue == "scheduled_activities",
where: fragment("args ->> 'activity_id' = ?::text", ^to_string(scheduled_activity_id))
diff --git a/lib/pleroma/search/database_search.ex b/lib/pleroma/search/database_search.ex
index 6532dc4ef..c36b2eb40 100644
--- a/lib/pleroma/search/database_search.ex
+++ b/lib/pleroma/search/database_search.ex
@@ -33,19 +33,12 @@ def search(user, search_query, options \\ []) do
only_media = Keyword.get(options, :only_media, false)
only_local = Keyword.get(options, :local, false)
- search_function =
- if :persistent_term.get({Pleroma.Repo, :postgres_version}) >= 11 do
- :websearch
- else
- :plain
- end
-
try do
Activity
|> Activity.with_preloaded_object()
|> Activity.restrict_deactivated_users()
|> restrict_public()
- |> query_with(index_type, search_query, search_function)
+ |> query_with(index_type, search_query)
|> maybe_restrict_local(user, only_local)
|> maybe_restrict_author(author)
|> maybe_restrict_blocked(user)
@@ -92,25 +85,7 @@ def restrict_public(q) do
)
end
- def query_with(q, :gin, search_query, :plain) do
- %{rows: [[tsc]]} =
- Ecto.Adapters.SQL.query!(
- Pleroma.Repo,
- "select current_setting('default_text_search_config')::regconfig::oid;"
- )
-
- from([a, o] in q,
- where:
- fragment(
- "to_tsvector(?::oid::regconfig, ?->>'content') @@ plainto_tsquery(?)",
- ^tsc,
- o.data,
- ^search_query
- )
- )
- end
-
- def query_with(q, :gin, search_query, :websearch) do
+ def query_with(q, :gin, search_query) do
%{rows: [[tsc]]} =
Ecto.Adapters.SQL.query!(
Pleroma.Repo,
@@ -128,19 +103,7 @@ def query_with(q, :gin, search_query, :websearch) do
)
end
- def query_with(q, :rum, search_query, :plain) do
- from([a, o] in q,
- where:
- fragment(
- "? @@ plainto_tsquery(?)",
- o.fts_content,
- ^search_query
- ),
- order_by: [fragment("? <=> now()::date", o.inserted_at)]
- )
- end
-
- def query_with(q, :rum, search_query, :websearch) do
+ def query_with(q, :rum, search_query) do
from([a, o] in q,
where:
fragment(
@@ -152,8 +115,7 @@ def query_with(q, :rum, search_query, :websearch) do
)
end
- def query_with(q, :pgroonga, search_query, _) do
- # PGroonga only supports PostgreSQL version 11 or newer
+ def query_with(q, :pgroonga, search_query) do
from([a, o] in q,
where:
fragment(
diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex
index e44c2d62a..1e6936fca 100644
--- a/lib/pleroma/search/meilisearch.ex
+++ b/lib/pleroma/search/meilisearch.ex
@@ -10,11 +10,24 @@ defmodule Pleroma.Search.Meilisearch do
@behaviour Pleroma.Search.SearchBackend
- defp meili_headers do
- private_key = Pleroma.Config.get([Pleroma.Search.Meilisearch, :private_key])
+ defp meili_headers(key) do
+ key_header =
+ if is_nil(key), do: [], else: [{"Authorization", "Bearer #{key}"}]
- [{"Content-Type", "application/json"}] ++
- if is_nil(private_key), do: [], else: [{"Authorization", "Bearer #{private_key}"}]
+ [{"Content-Type", "application/json"} | key_header]
+ end
+
+ defp meili_headers_admin do
+ private_key = Pleroma.Config.get([Pleroma.Search.Meilisearch, :private_key])
+ meili_headers(private_key)
+ end
+
+ defp meili_headers_search do
+ search_key =
+ Pleroma.Config.get([Pleroma.Search.Meilisearch, :search_key]) ||
+ Pleroma.Config.get([Pleroma.Search.Meilisearch, :private_key])
+
+ meili_headers(search_key)
end
def meili_get(path) do
@@ -23,7 +36,7 @@ def meili_get(path) do
result =
Pleroma.HTTP.get(
Path.join(endpoint, path),
- meili_headers()
+ meili_headers_admin()
)
with {:ok, res} <- result do
@@ -31,14 +44,14 @@ def meili_get(path) do
end
end
- def meili_post(path, params) do
+ defp meili_search(params) do
endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url])
result =
Pleroma.HTTP.post(
- Path.join(endpoint, path),
+ Path.join(endpoint, "/indexes/objects/search"),
Jason.encode!(params),
- meili_headers()
+ meili_headers_search()
)
with {:ok, res} <- result do
@@ -54,7 +67,7 @@ def meili_put(path, params) do
:put,
Path.join(endpoint, path),
Jason.encode!(params),
- meili_headers(),
+ meili_headers_admin(),
[]
)
@@ -71,7 +84,7 @@ def meili_delete!(path) do
:delete,
Path.join(endpoint, path),
"",
- meili_headers(),
+ meili_headers_admin(),
[]
)
end
@@ -96,8 +109,7 @@ def search(user, query, options \\ []) do
|> maybe_restrict_media(only_media)
res =
- meili_post(
- "/indexes/objects/search",
+ meili_search(
%{
q: query,
offset: offset,
@@ -115,7 +127,6 @@ def search(user, query, options \\ []) do
|> Activity.with_preloaded_object()
|> Activity.restrict_deactivated_users()
|> DatabaseSearch.maybe_fetch(user, query)
- |> order_by([object: obj], desc: obj.data["published"])
|> Pleroma.Repo.all()
rescue
_ -> DatabaseSearch.maybe_fetch([], user, query)
diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex
index 3d33fcd62..c4ac2c87e 100644
--- a/lib/pleroma/signature.ex
+++ b/lib/pleroma/signature.ex
@@ -10,7 +10,7 @@ defmodule Pleroma.Signature do
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
- @known_suffixes ["/publickey", "/main-key"]
+ @known_suffixes ["/publickey", "/main-key", "#key"]
def key_id_to_actor_id(key_id) do
uri =
diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex
index b6a32d87a..426fe9f1b 100644
--- a/lib/pleroma/upload.ex
+++ b/lib/pleroma/upload.ex
@@ -13,7 +13,6 @@ defmodule Pleroma.Upload do
* `:uploader`: override uploader
* `:filters`: override filters
* `:size_limit`: override size limit
- * `:activity_type`: override activity type
The `%Pleroma.Upload{}` struct: all documented fields are meant to be overwritten in filters:
@@ -48,7 +47,6 @@ defmodule Pleroma.Upload do
@type option ::
{:type, :avatar | :banner | :background}
| {:description, String.t()}
- | {:activity_type, String.t()}
| {:size_limit, nil | non_neg_integer()}
| {:uploader, module()}
| {:filters, [module()]}
@@ -61,12 +59,23 @@ defmodule Pleroma.Upload do
width: integer(),
height: integer(),
blurhash: String.t(),
+ description: String.t(),
path: String.t()
}
@always_enabled_filters [Pleroma.Upload.Filter.Dedupe]
- defstruct [:id, :name, :tempfile, :content_type, :width, :height, :blurhash, :path]
+ defstruct [
+ :id,
+ :name,
+ :tempfile,
+ :content_type,
+ :width,
+ :height,
+ :blurhash,
+ :description,
+ :path
+ ]
@spec store(source, options :: [option()]) :: {:ok, Map.t()} | {:error, any()}
@doc "Store a file. If using a `Plug.Upload{}` as the source, be sure to use `Majic.Plug` to ensure its content_type and filename is correct."
@@ -76,7 +85,7 @@ def store(upload, opts \\ []) do
with {:ok, upload} <- prepare_upload(upload, opts),
upload = %__MODULE__{upload | path: upload.path || "#{upload.id}/#{upload.name}"},
{:ok, upload} <- Pleroma.Upload.Filter.filter(opts.filters, upload),
- description = Map.get(opts, :description) || "",
+ description = Map.get(upload, :description) || "",
{_, true} <-
{:description_limit,
String.length(description) <= Pleroma.Config.get([:instance, :description_limit])},
@@ -132,7 +141,7 @@ defp get_opts(opts) do
end
%{
- activity_type: Keyword.get(opts, :activity_type, activity_type),
+ activity_type: activity_type,
size_limit: Keyword.get(opts, :size_limit, size_limit),
uploader: Keyword.get(opts, :uploader, Pleroma.Config.get([__MODULE__, :uploader])),
filters:
@@ -152,7 +161,8 @@ defp prepare_upload(%Plug.Upload{} = file, opts) do
id: UUID.generate(),
name: file.filename,
tempfile: file.path,
- content_type: file.content_type
+ content_type: file.content_type,
+ description: opts.description
}}
end
end
@@ -172,7 +182,8 @@ defp prepare_upload(%{img: "data:image/" <> image_data}, opts) do
id: UUID.generate(),
name: hash <> "." <> ext,
tempfile: tmp_path,
- content_type: content_type
+ content_type: content_type,
+ description: opts.description
}}
end
end
diff --git a/lib/pleroma/upload/filter/exiftool/read_description.ex b/lib/pleroma/upload/filter/exiftool/read_description.ex
new file mode 100644
index 000000000..372907076
--- /dev/null
+++ b/lib/pleroma/upload/filter/exiftool/read_description.ex
@@ -0,0 +1,51 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Upload.Filter.Exiftool.ReadDescription do
+ @moduledoc """
+ Gets a valid description from the related EXIF tags and provides them in the response if no description is provided yet.
+ It will first check ImageDescription, when that doesn't probide a valid description, it will check iptc:Caption-Abstract.
+ A valid description means the fields are filled in and not too long (see `:instance, :description_limit`).
+ """
+ @behaviour Pleroma.Upload.Filter
+
+ @spec filter(Pleroma.Upload.t()) :: {:ok, any()} | {:error, String.t()}
+
+ def filter(%Pleroma.Upload{description: description})
+ when is_binary(description),
+ do: {:ok, :noop}
+
+ def filter(%Pleroma.Upload{tempfile: file} = upload),
+ do: {:ok, :filtered, upload |> Map.put(:description, read_description_from_exif_data(file))}
+
+ def filter(_, _), do: {:ok, :noop}
+
+ defp read_description_from_exif_data(file) do
+ nil
+ |> read_when_empty(file, "-ImageDescription")
+ |> read_when_empty(file, "-iptc:Caption-Abstract")
+ end
+
+ defp read_when_empty(current_description, _, _) when is_binary(current_description),
+ do: current_description
+
+ defp read_when_empty(_, file, tag) do
+ try do
+ {tag_content, 0} =
+ System.cmd("exiftool", ["-b", "-s3", "-ignoreMinorErrors", "-q", "-q", tag, file],
+ parallelism: true
+ )
+
+ tag_content = String.trim(tag_content)
+
+ if tag_content != "" and
+ String.length(tag_content) <=
+ Pleroma.Config.get([:instance, :description_limit]),
+ do: tag_content,
+ else: nil
+ rescue
+ _ in ErlangError -> nil
+ end
+ end
+end
diff --git a/lib/pleroma/upload/filter/exiftool.ex b/lib/pleroma/upload/filter/exiftool/strip_metadata.ex
similarity index 50%
rename from lib/pleroma/upload/filter/exiftool.ex
rename to lib/pleroma/upload/filter/exiftool/strip_metadata.ex
index 4d3a5f551..912ff6a92 100644
--- a/lib/pleroma/upload/filter/exiftool.ex
+++ b/lib/pleroma/upload/filter/exiftool/strip_metadata.ex
@@ -2,24 +2,42 @@
# Copyright © 2017-2021 Pleroma Authors
# SPDX-License-Identifier: AGPL-3.0-only
-defmodule Pleroma.Upload.Filter.Exiftool do
+defmodule Pleroma.Upload.Filter.Exiftool.StripMetadata do
@moduledoc """
- Strips GPS related EXIF tags and overwrites the file in place.
+ Tries to strip all image metadata but colorspace and orientation overwriting the file in place.
Also strips or replaces filesystem metadata e.g., timestamps.
"""
@behaviour Pleroma.Upload.Filter
+ alias Pleroma.Config
+
+ @purge_default ["all", "CommonIFD0"]
+ @preserve_default ["ColorSpaceTags", "Orientation"]
+
@spec filter(Pleroma.Upload.t()) :: {:ok, :noop} | {:ok, :filtered} | {:error, String.t()}
# Formats not compatible with exiftool at this time
- def filter(%Pleroma.Upload{content_type: "image/heic"}), do: {:ok, :noop}
def filter(%Pleroma.Upload{content_type: "image/webp"}), do: {:ok, :noop}
def filter(%Pleroma.Upload{content_type: "image/svg+xml"}), do: {:ok, :noop}
- def filter(%Pleroma.Upload{content_type: "image/jxl"}), do: {:ok, :noop}
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
+ purge_args =
+ Config.get([__MODULE__, :purge], @purge_default)
+ |> Enum.map(fn mgroup -> "-" <> mgroup <> "=" end)
+
+ preserve_args =
+ Config.get([__MODULE__, :preserve], @preserve_default)
+ |> Enum.map(fn mgroup -> "-" <> mgroup end)
+ |> then(fn
+ # If -TagsFromFile is not followed by tag selectors, it will copy most available tags
+ [] -> []
+ args -> ["-TagsFromFile", "@" | args]
+ end)
+
+ args = ["-ignoreMinorErrors", "-overwrite_original" | purge_args] ++ preserve_args ++ [file]
+
try do
- case System.cmd("exiftool", ["-overwrite_original", "-gps:all=", file], parallelism: true) do
+ case System.cmd("exiftool", args, parallelism: true) do
{_response, 0} -> {:ok, :filtered}
{error, 1} -> {:error, error}
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 1e06bc809..c87072300 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -155,9 +155,7 @@ def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when
# Splice in the child object if we have one.
activity = Maps.put_if_present(activity, :object, object)
- ConcurrentLimiter.limit(Pleroma.Web.RichMedia.Helpers, fn ->
- Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end)
- end)
+ Pleroma.Web.RichMedia.Card.get_by_activity(activity)
# Add local posts to search index
if local, do: Pleroma.Search.add_to_index(activity)
@@ -185,7 +183,7 @@ def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when
id: "pleroma:fakeid"
}
- Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
+ Pleroma.Web.RichMedia.Card.get_by_activity(activity)
{:ok, activity}
{:remote_limit_pass, _} ->
@@ -1545,11 +1543,19 @@ defp normalize_also_known_as(aka) when is_list(aka), do: aka
defp normalize_also_known_as(aka) when is_binary(aka), do: [aka]
defp normalize_also_known_as(nil), do: []
+ defp normalize_attachment(%{} = attachment), do: [attachment]
+ defp normalize_attachment(attachment) when is_list(attachment), do: attachment
+ defp normalize_attachment(_), do: []
+
defp object_to_user_data(data, additional) do
fields =
data
|> Map.get("attachment", [])
- |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
+ |> normalize_attachment()
+ |> Enum.filter(fn
+ %{"type" => t} -> t == "PropertyValue"
+ _ -> false
+ end)
|> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
emojis =
@@ -1705,9 +1711,7 @@ defp collection_private(%{"first" => first}) do
Fetcher.fetch_and_contain_remote_object_from_id(first) do
{:ok, false}
else
- {:error, {:ok, %{status: code}}} when code in [401, 403] -> {:ok, true}
- {:error, _} = e -> e
- e -> {:error, e}
+ {:error, _} -> {:ok, true}
end
end
@@ -1732,7 +1736,7 @@ def fetch_and_prepare_user_from_ap_id(ap_id, additional \\ []) do
Logger.debug("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
{:error, e}
- {:error, {:reject, reason} = e} ->
+ {:reject, reason} = e ->
Logger.debug("Rejected user #{ap_id}: #{inspect(reason)}")
{:error, e}
@@ -1818,19 +1822,20 @@ def fetch_and_prepare_featured_from_ap_id(ap_id) do
end
end
- def pinned_fetch_task(nil), do: nil
-
- def pinned_fetch_task(%{pinned_objects: pins}) do
- if Enum.all?(pins, fn {ap_id, _} ->
- Object.get_cached_by_ap_id(ap_id) ||
- match?({:ok, _object}, Fetcher.fetch_object_from_id(ap_id))
- end) do
- :ok
- else
- :error
- end
+ def enqueue_pin_fetches(%{pinned_objects: pins}) do
+ # enqueue a task to fetch all pinned objects
+ Enum.each(pins, fn {ap_id, _} ->
+ if is_nil(Object.get_cached_by_ap_id(ap_id)) do
+ Pleroma.Workers.RemoteFetcherWorker.enqueue("fetch_remote", %{
+ "id" => ap_id,
+ "depth" => 1
+ })
+ end
+ end)
end
+ def enqueue_pin_fetches(_), do: nil
+
def make_user_from_ap_id(ap_id, additional \\ []) do
user = User.get_cached_by_ap_id(ap_id)
@@ -1838,8 +1843,6 @@ def make_user_from_ap_id(ap_id, additional \\ []) do
Transmogrifier.upgrade_user_from_ap_id(ap_id)
else
with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id, additional) do
- {:ok, _pid} = Task.start(fn -> pinned_fetch_task(data) end)
-
user =
if data.ap_id != ap_id do
User.get_cached_by_ap_id(data.ap_id)
@@ -1851,6 +1854,7 @@ def make_user_from_ap_id(ap_id, additional \\ []) do
user
|> User.remote_user_changeset(data)
|> User.update_and_set_cache()
+ |> tap(fn _ -> enqueue_pin_fetches(data) end)
else
maybe_handle_clashing_nickname(data)
@@ -1858,6 +1862,7 @@ def make_user_from_ap_id(ap_id, additional \\ []) do
|> User.remote_user_changeset()
|> Repo.insert()
|> User.set_cache()
+ |> tap(fn _ -> enqueue_pin_fetches(data) end)
end
end
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index 793b08f3d..6642f7771 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -12,9 +12,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.InternalFetchActor
alias Pleroma.Web.ActivityPub.ObjectView
- alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.ActivityPub.Relay
- alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.UserView
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
@@ -40,11 +38,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
# Note: :following and :followers must be served even without authentication (as via :api)
plug(
EnsureAuthenticatedPlug
- when action in [:read_inbox, :update_outbox, :whoami, :upload_media]
+ when action in [:read_inbox]
)
- plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:upload_media])
-
plug(
Pleroma.Web.Plugs.Cache,
[query_params: false, tracking_fun: &__MODULE__.track_object_fetch/2]
@@ -160,7 +156,9 @@ def maybe_skip_cache(conn, user) do
end
end
- # GET /relay/following
+ @doc """
+ GET /relay/following
+ """
def relay_following(conn, _params) do
with %{halted: false} = conn <- FederatingPlug.call(conn, []) do
conn
@@ -197,7 +195,9 @@ def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) d
end
end
- # GET /relay/followers
+ @doc """
+ GET /relay/followers
+ """
def relay_followers(conn, _params) do
with %{halted: false} = conn <- FederatingPlug.call(conn, []) do
conn
@@ -317,14 +317,6 @@ def internal_fetch(conn, _params) do
|> represent_service_actor(conn)
end
- @doc "Returns the authenticated user's ActivityPub User object or a 404 Not Found if non-authenticated"
- def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
- conn
- |> put_resp_content_type("application/activity+json")
- |> put_view(UserView)
- |> render("user.json", %{user: user})
- end
-
def read_inbox(
%{assigns: %{user: %User{nickname: nickname} = user}} = conn,
%{"nickname" => nickname, "page" => page?} = params
@@ -375,105 +367,6 @@ def read_inbox(%{assigns: %{user: %User{nickname: as_nickname}}} = conn, %{
|> json(err)
end
- defp fix_user_message(%User{ap_id: actor}, %{"type" => "Create", "object" => object} = activity)
- when is_map(object) do
- length =
- [object["content"], object["summary"], object["name"]]
- |> Enum.filter(&is_binary(&1))
- |> Enum.join("")
- |> String.length()
-
- limit = Pleroma.Config.get([:instance, :limit])
-
- if length < limit do
- object =
- object
- |> Transmogrifier.strip_internal_fields()
- |> Map.put("attributedTo", actor)
- |> Map.put("actor", actor)
- |> Map.put("id", Utils.generate_object_id())
-
- {:ok, Map.put(activity, "object", object)}
- else
- {:error,
- dgettext(
- "errors",
- "Character limit (%{limit} characters) exceeded, contains %{length} characters",
- limit: limit,
- length: length
- )}
- end
- end
-
- defp fix_user_message(
- %User{ap_id: actor} = user,
- %{"type" => "Delete", "object" => object} = activity
- ) do
- with {_, %Object{data: object_data}} <- {:normalize, Object.normalize(object, fetch: false)},
- {_, true} <- {:permission, user.is_moderator || actor == object_data["actor"]} do
- {:ok, activity}
- else
- {:normalize, _} ->
- {:error, "No such object found"}
-
- {:permission, _} ->
- {:forbidden, "You can't delete this object"}
- end
- end
-
- defp fix_user_message(%User{}, activity) do
- {:ok, activity}
- end
-
- def update_outbox(
- %{assigns: %{user: %User{nickname: nickname, ap_id: actor} = user}} = conn,
- %{"nickname" => nickname} = params
- ) do
- params =
- params
- |> Map.drop(["nickname"])
- |> Map.put("id", Utils.generate_activity_id())
- |> Map.put("actor", actor)
-
- with {:ok, params} <- fix_user_message(user, params),
- {:ok, activity, _} <- Pipeline.common_pipeline(params, local: true),
- %Activity{data: activity_data} <- Activity.normalize(activity) do
- conn
- |> put_status(:created)
- |> put_resp_header("location", activity_data["id"])
- |> json(activity_data)
- else
- {:forbidden, message} ->
- conn
- |> put_status(:forbidden)
- |> json(message)
-
- {:error, message} ->
- conn
- |> put_status(:bad_request)
- |> json(message)
-
- e ->
- Logger.warning(fn -> "AP C2S: #{inspect(e)}" end)
-
- conn
- |> put_status(:bad_request)
- |> json("Bad Request")
- end
- end
-
- def update_outbox(%{assigns: %{user: %User{} = user}} = conn, %{"nickname" => nickname}) do
- err =
- dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
- nickname: nickname,
- as_nickname: user.nickname
- )
-
- conn
- |> put_status(:forbidden)
- |> json(err)
- end
-
defp errors(conn, {:error, :not_found}) do
conn
|> put_status(:not_found)
@@ -495,21 +388,6 @@ defp set_requester_reachable(%Plug.Conn{} = conn, _) do
conn
end
- def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do
- with {:ok, object} <-
- ActivityPub.upload(
- file,
- actor: User.ap_id(user),
- description: Map.get(data, "description")
- ) do
- Logger.debug(inspect(object))
-
- conn
- |> put_status(:created)
- |> json(object.data)
- end
- end
-
def pinned(conn, %{"nickname" => nickname}) do
with %User{} = user <- User.get_cached_by_nickname(nickname) do
conn
diff --git a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex
index 26d3dc592..4649db2a1 100644
--- a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex
@@ -101,10 +101,19 @@ defp get_extension_if_safe(response) do
end
end
+ defp get_int_header(headers, header_name, default \\ nil) do
+ with rawval when rawval != :undefined <- :proplists.get_value(header_name, headers),
+ {int, ""} <- Integer.parse(rawval) do
+ int
+ else
+ _ -> default
+ end
+ end
+
defp is_remote_size_within_limit?(url) do
with {:ok, %{status: status, headers: headers} = _response} when status in 200..299 <-
Pleroma.HTTP.request(:head, url, nil, [], []) do
- content_length = :proplists.get_value("content-length", headers, nil)
+ content_length = get_int_header(headers, "content-length")
size_limit = Config.get([:mrf_steal_emoji, :size_limit], @size_limit)
accept_unknown =
@@ -172,7 +181,7 @@ def filter(message), do: {:ok, message}
description: <<_::272, _::_*256>>,
key: :hosts | :rejected_shortcodes | :size_limit,
suggestions: [any(), ...],
- type: {:list, :string} | {:list, :string} | :integer
+ type: {:list, :string} | {:list, :string} | :integer | :boolean
},
...
],
@@ -209,6 +218,12 @@ def config_description do
type: :integer,
description: "File size limit (in bytes), checked before an emoji is saved to the disk",
suggestions: ["100000"]
+ },
+ %{
+ key: :download_unknown_size,
+ type: :boolean,
+ description: "Whether to download emoji if size can't be determined ahead of time",
+ suggestions: [false, true]
}
]
}
diff --git a/lib/pleroma/web/activity_pub/object_validators/user_validator.ex b/lib/pleroma/web/activity_pub/object_validators/user_validator.ex
index 90b5404f3..adb291a55 100644
--- a/lib/pleroma/web/activity_pub/object_validators/user_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/user_validator.ex
@@ -16,11 +16,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UserValidator do
alias Pleroma.Object.Containment
alias Pleroma.Signature
+ require Pleroma.Constants
+
@impl true
def validate(object, meta)
def validate(%{"type" => type, "id" => _id} = data, meta)
- when type in ["Person", "Organization", "Group", "Application"] do
+ when type in Pleroma.Constants.actor_types() do
with :ok <- validate_pubkey(data),
:ok <- validate_inbox(data),
:ok <- contain_collection_origin(data) do
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
index 963c6d8c6..0e85e47be 100644
--- a/lib/pleroma/web/activity_pub/side_effects.ex
+++ b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -225,9 +225,7 @@ def handle(%{data: %{"type" => "Create"}} = activity, meta) do
end
end
- ConcurrentLimiter.limit(Pleroma.Web.RichMedia.Helpers, fn ->
- Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end)
- end)
+ Pleroma.Web.RichMedia.Card.get_by_activity(activity)
Pleroma.Search.add_to_index(Map.put(activity, :object, object))
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index a72a431b2..ca5e85f2e 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -25,8 +25,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
import Ecto.Query
- require Logger
require Pleroma.Constants
+ require Logger
@doc """
Modifies an incoming AP object (mastodon format) to our internal format.
@@ -58,21 +58,48 @@ def fix_summary(%{"summary" => _} = object) do
def fix_summary(object), do: Map.put(object, "summary", "")
- def fix_addressing_list(map, field) do
- addrs = map[field]
-
+ defp fix_addressing_list(addrs) do
cond do
- is_list(addrs) ->
- Map.put(map, field, Enum.filter(addrs, &is_binary/1))
-
- is_binary(addrs) ->
- Map.put(map, field, [addrs])
-
- true ->
- Map.put(map, field, [])
+ is_list(addrs) -> Enum.filter(addrs, &is_binary/1)
+ is_binary(addrs) -> [addrs]
+ true -> []
end
end
+ # Due to JSON-LD simply "Public" and "as:Public" are equivalent to the full URI
+ # but to simplify later checks we only want to deal with one reperesentation internally
+ defp normalise_addressing_public_list(map, all_fields)
+
+ defp normalise_addressing_public_list(%{} = map, [field | fields]) do
+ full_uri = Pleroma.Constants.as_public()
+
+ map =
+ if map[field] != nil do
+ new_fval =
+ map[field]
+ |> fix_addressing_list()
+ |> Enum.map(fn
+ "Public" -> full_uri
+ "as:Public" -> full_uri
+ x -> x
+ end)
+
+ Map.put(map, field, new_fval)
+ else
+ map
+ end
+
+ normalise_addressing_public_list(map, fields)
+ end
+
+ defp normalise_addressing_public_list(map, _) do
+ map
+ end
+
+ defp normalise_addressing_public(map) do
+ normalise_addressing_public_list(map, ["to", "cc", "bto", "bcc"])
+ end
+
# if directMessage flag is set to true, leave the addressing alone
def fix_explicit_addressing(%{"directMessage" => true} = object, _follower_collection),
do: object
@@ -96,6 +123,10 @@ def fix_explicit_addressing(%{"to" => to, "cc" => cc} = object, follower_collect
|> Map.put("cc", final_cc)
end
+ def fix_addressing_list_key(map, field) do
+ Map.put(map, field, fix_addressing_list(map[field]))
+ end
+
def fix_addressing(object) do
{:ok, %User{follower_address: follower_collection}} =
object
@@ -103,10 +134,10 @@ def fix_addressing(object) do
|> User.get_or_fetch_by_ap_id()
object
- |> fix_addressing_list("to")
- |> fix_addressing_list("cc")
- |> fix_addressing_list("bto")
- |> fix_addressing_list("bcc")
+ |> fix_addressing_list_key("to")
+ |> fix_addressing_list_key("cc")
+ |> fix_addressing_list_key("bto")
+ |> fix_addressing_list_key("bcc")
|> fix_explicit_addressing(follower_collection)
|> CommonFixes.fix_implicit_addressing(follower_collection)
end
@@ -135,8 +166,7 @@ def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
|> Map.put("context", replied_object.data["context"] || object["conversation"])
|> Map.drop(["conversation", "inReplyToAtomUri"])
else
- e ->
- Logger.warning("Couldn't fetch reply@#{inspect(in_reply_to_id)}, error: #{inspect(e)}")
+ _ ->
object
end
else
@@ -384,11 +414,28 @@ defp get_reported(objects) do
end)
end
- def handle_incoming(data, options \\ [])
+ def handle_incoming(data, options \\ []) do
+ data = normalise_addressing_public(data)
+
+ data =
+ if data["object"] != nil do
+ object = normalise_addressing_public(data["object"])
+ Map.put(data, "object", object)
+ else
+ data
+ end
+
+ handle_incoming_normalised(data, options)
+ end
+
+ defp handle_incoming_normalised(data, options)
# Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them
# with nil ID.
- def handle_incoming(%{"type" => "Flag", "object" => objects, "actor" => actor} = data, _options) do
+ defp handle_incoming_normalised(
+ %{"type" => "Flag", "object" => objects, "actor" => actor} = data,
+ _options
+ ) do
with context <- data["context"] || Utils.generate_context_id(),
content <- data["content"] || "",
%User{} = actor <- User.get_cached_by_ap_id(actor),
@@ -409,20 +456,21 @@ def handle_incoming(%{"type" => "Flag", "object" => objects, "actor" => actor} =
end
# disallow objects with bogus IDs
- def handle_incoming(%{"id" => nil}, _options), do: :error
- def handle_incoming(%{"id" => ""}, _options), do: :error
+ defp handle_incoming_normalised(%{"id" => nil}, _options), do: :error
+ defp handle_incoming_normalised(%{"id" => ""}, _options), do: :error
# length of https:// = 8, should validate better, but good enough for now.
- def handle_incoming(%{"id" => id}, _options) when is_binary(id) and byte_size(id) < 8,
- do: :error
+ defp handle_incoming_normalised(%{"id" => id}, _options)
+ when is_binary(id) and byte_size(id) < 8,
+ do: :error
- @doc "Rewrite misskey likes into EmojiReacts"
- def handle_incoming(
- %{
- "type" => "Like",
- "content" => reaction
- } = data,
- options
- ) do
+ # Rewrite misskey likes into EmojiReacts
+ defp handle_incoming_normalised(
+ %{
+ "type" => "Like",
+ "content" => reaction
+ } = data,
+ options
+ ) do
if Pleroma.Emoji.is_unicode_emoji?(reaction) || Pleroma.Emoji.matches_shortcode?(reaction) do
data
|> Map.put("type", "EmojiReact")
@@ -434,11 +482,11 @@ def handle_incoming(
end
end
- def handle_incoming(
- %{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,
- options
- )
- when objtype in ~w{Question Answer Audio Video Event Article Note Page} do
+ defp handle_incoming_normalised(
+ %{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,
+ options
+ )
+ when objtype in ~w{Question Answer Audio Video Event Article Note Page} do
fetch_options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
object =
@@ -470,8 +518,8 @@ def handle_incoming(
end
end
- def handle_incoming(%{"type" => type} = data, _options)
- when type in ~w{Like EmojiReact Announce Add Remove} do
+ defp handle_incoming_normalised(%{"type" => type} = data, _options)
+ when type in ~w{Like EmojiReact Announce Add Remove} do
with :ok <- ObjectValidator.fetch_actor_and_object(data),
{:ok, activity, _meta} <- Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
@@ -481,11 +529,11 @@ def handle_incoming(%{"type" => type} = data, _options)
end
end
- def handle_incoming(
- %{"type" => type} = data,
- _options
- )
- when type in ~w{Update Block Follow Accept Reject} do
+ defp handle_incoming_normalised(
+ %{"type" => type} = data,
+ _options
+ )
+ when type in ~w{Update Block Follow Accept Reject} do
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
{:ok, activity, _} <-
Pipeline.common_pipeline(data, local: false) do
@@ -493,10 +541,10 @@ def handle_incoming(
end
end
- def handle_incoming(
- %{"type" => "Delete"} = data,
- _options
- ) do
+ defp handle_incoming_normalised(
+ %{"type" => "Delete"} = data,
+ _options
+ ) do
with {:ok, activity, _} <-
Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
@@ -516,15 +564,15 @@ def handle_incoming(
end
end
- def handle_incoming(
- %{
- "type" => "Undo",
- "object" => %{"type" => "Follow", "object" => followed},
- "actor" => follower,
- "id" => id
- } = _data,
- _options
- ) do
+ defp handle_incoming_normalised(
+ %{
+ "type" => "Undo",
+ "object" => %{"type" => "Follow", "object" => followed},
+ "actor" => follower,
+ "id" => id
+ } = _data,
+ _options
+ ) do
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
{:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower),
{:ok, activity} <- ActivityPub.unfollow(follower, followed, id, false) do
@@ -535,28 +583,28 @@ def handle_incoming(
end
end
- def handle_incoming(
- %{
- "type" => "Undo",
- "object" => %{"type" => type}
- } = data,
- _options
- )
- when type in ["Like", "EmojiReact", "Announce", "Block"] do
+ defp handle_incoming_normalised(
+ %{
+ "type" => "Undo",
+ "object" => %{"type" => type}
+ } = data,
+ _options
+ )
+ when type in ["Like", "EmojiReact", "Announce", "Block"] do
with {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
end
end
# For Undos that don't have the complete object attached, try to find it in our database.
- def handle_incoming(
- %{
- "type" => "Undo",
- "object" => object
- } = activity,
- options
- )
- when is_binary(object) do
+ defp handle_incoming_normalised(
+ %{
+ "type" => "Undo",
+ "object" => object
+ } = activity,
+ options
+ )
+ when is_binary(object) do
with %Activity{data: data} <- Activity.get_by_ap_id(object) do
activity
|> Map.put("object", data)
@@ -566,15 +614,15 @@ def handle_incoming(
end
end
- def handle_incoming(
- %{
- "type" => "Move",
- "actor" => origin_actor,
- "object" => origin_actor,
- "target" => target_actor
- },
- _options
- ) do
+ defp handle_incoming_normalised(
+ %{
+ "type" => "Move",
+ "actor" => origin_actor,
+ "object" => origin_actor,
+ "target" => target_actor
+ },
+ _options
+ ) do
with %User{} = origin_user <- User.get_cached_by_ap_id(origin_actor),
# Use a dramatically shortened maximum age before refresh here because it is reasonable
# for a user to
@@ -589,7 +637,7 @@ def handle_incoming(
end
end
- def handle_incoming(_, _), do: :error
+ defp handle_incoming_normalised(_, _), do: :error
@spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil
def get_obj_helper(id, options \\ []) do
@@ -833,8 +881,7 @@ def maybe_fix_object_url(%{"object" => object} = data) when is_binary(object) do
relative_object do
Map.put(data, "object", external_url)
else
- {:fetch, e} ->
- Logger.error("Couldn't fetch fixed_object@#{object} #{inspect(e)}")
+ {:fetch, _} ->
data
_ ->
@@ -987,7 +1034,7 @@ def upgrade_user_from_ap_id(ap_id) do
with %User{local: false} = user <- User.get_cached_by_ap_id(ap_id),
{:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id),
{:ok, user} <- update_user(user, data) do
- {:ok, _pid} = Task.start(fn -> ActivityPub.pinned_fetch_task(user) end)
+ ActivityPub.enqueue_pin_fetches(user)
TransmogrifierWorker.enqueue("user_upgrade", %{"user_id" => user.id})
{:ok, user}
else
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index fe70022f1..47b8e37e5 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -26,8 +26,7 @@ def render("endpoints.json", %{user: %User{local: true} = _user}) do
"oauthAuthorizationEndpoint" => url(~p"/oauth/authorize"),
"oauthRegistrationEndpoint" => url(~p"/api/v1/apps"),
"oauthTokenEndpoint" => url(~p"/oauth/token"),
- "sharedInbox" => url(~p"/inbox"),
- "uploadMedia" => url(~p"/api/ap/upload_media")
+ "sharedInbox" => url(~p"/inbox")
}
end
diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex
index ced6371d6..2d24edec4 100644
--- a/lib/pleroma/web/common_api/activity_draft.ex
+++ b/lib/pleroma/web/common_api/activity_draft.ex
@@ -41,7 +41,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
preview?: false,
changes: %{}
- def new(user, params) do
+ defp new(user, params) do
%__MODULE__{user: user}
|> put_params(params)
end
@@ -92,9 +92,14 @@ defp full_payload(%{status: status, summary: summary} = draft) do
end
end
- defp attachments(%{params: params} = draft) do
- attachments = Utils.attachments_from_ids(params)
- %__MODULE__{draft | attachments: attachments}
+ defp attachments(%{params: params, user: user} = draft) do
+ case Utils.attachments_from_ids(user, params) do
+ attachments when is_list(attachments) ->
+ %__MODULE__{draft | attachments: attachments}
+
+ {:error, reason} ->
+ add_error(draft, reason)
+ end
end
defp in_reply_to(%{params: %{in_reply_to_status_id: ""}} = draft), do: draft
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index e79b12fc9..635143621 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -22,43 +22,31 @@ defmodule Pleroma.Web.CommonAPI.Utils do
require Logger
require Pleroma.Constants
- def attachments_from_ids(%{media_ids: ids, descriptions: desc}) do
- attachments_from_ids_descs(ids, desc)
+ def attachments_from_ids(user, %{media_ids: ids}) do
+ attachments_from_ids(user, ids, [])
end
- def attachments_from_ids(%{media_ids: ids}) do
- attachments_from_ids_no_descs(ids)
+ def attachments_from_ids(_, _), do: []
+
+ defp attachments_from_ids(_user, [], acc), do: Enum.reverse(acc)
+
+ defp attachments_from_ids(user, [media_id | ids], acc) do
+ with {_, %Object{} = object} <- {:get, get_attachment(media_id)},
+ :ok <- Object.authorize_access(object, user) do
+ attachments_from_ids(user, ids, [object.data | acc])
+ else
+ {:get, _} -> attachments_from_ids(user, ids, acc)
+ {:error, reason} -> {:error, reason}
+ end
end
- def attachments_from_ids(_), do: []
-
- def attachments_from_ids_no_descs([]), do: []
-
- def attachments_from_ids_no_descs(ids) do
- Enum.map(ids, fn media_id ->
- case get_attachment(media_id) do
- %Object{data: data} -> data
- _ -> nil
- end
- end)
- |> Enum.reject(&is_nil/1)
- end
-
- def attachments_from_ids_descs([], _), do: []
-
- def attachments_from_ids_descs(ids, descs_str) do
- {_, descs} = Jason.decode(descs_str)
-
- Enum.map(ids, fn media_id ->
- with %Object{data: data} <- get_attachment(media_id) do
- Map.put(data, "name", descs[media_id])
- end
- end)
- |> Enum.reject(&is_nil/1)
- end
-
- defp get_attachment(media_id) do
- Repo.get(Object, media_id)
+ def get_attachment(media_id) do
+ with %Object{} = object <- Repo.get(Object, media_id),
+ true <- object.data["type"] in Pleroma.Constants.attachment_types() do
+ object
+ else
+ _ -> nil
+ end
end
@spec get_to_and_cc(ActivityDraft.t()) :: {list(String.t()), list(String.t())}
diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex
index 52771205e..8b0a42dc8 100644
--- a/lib/pleroma/web/feed/feed_view.ex
+++ b/lib/pleroma/web/feed/feed_view.ex
@@ -78,9 +78,7 @@ def activity_title(%{"content" => content}, opts \\ %{}) do
end
def activity_content(%{"content" => content}) do
- content
- |> String.replace(~r/[\n\r]/, "")
- |> escape()
+ escape(content)
end
def activity_content(_), do: ""
diff --git a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex
index 5918b288d..3e5a64b51 100644
--- a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex
@@ -8,6 +8,7 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.Plugs.OAuthScopesPlug
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
@@ -55,12 +56,15 @@ def create2(_conn, _data), do: {:error, :bad_request}
@doc "PUT /api/v1/media/:id"
def update(%{assigns: %{user: user}, body_params: %{description: description}} = conn, %{id: id}) do
- with %Object{} = object <- Object.get_by_id(id),
+ with {_, %Object{} = object} <- {:get, Utils.get_attachment(id)},
:ok <- Object.authorize_access(object, user),
{:ok, %Object{data: data}} <- Object.update_data(object, %{"name" => description}) do
attachment_data = Map.put(data, "id", object.id)
render(conn, "attachment.json", %{attachment: attachment_data})
+ else
+ {:get, _} -> {:error, :not_found}
+ e -> e
end
end
@@ -68,11 +72,14 @@ def update(conn, data), do: show(conn, data)
@doc "GET /api/v1/media/:id"
def show(%{assigns: %{user: user}} = conn, %{id: id}) do
- with %Object{data: data, id: object_id} = object <- Object.get_by_id(id),
+ with {_, %Object{data: data, id: object_id} = object} <- {:get, Utils.get_attachment(id)},
:ok <- Object.authorize_access(object, user) do
attachment_data = Map.put(data, "id", object_id)
render(conn, "attachment.json", %{attachment: attachment_data})
+ else
+ {:get, _} -> {:error, :not_found}
+ e -> e
end
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
index 338a35052..acb5f15a0 100644
--- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
@@ -87,7 +87,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
%{scopes: ["write:bookmarks"]} when action in [:bookmark, :unbookmark]
)
- @rate_limited_status_actions ~w(reblog unreblog favourite unfavourite create delete)a
+ @rate_limited_status_actions ~w(reblog unreblog favourite unfavourite create delete update)a
plug(
RateLimiter,
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index ac0955534..a5229f5e4 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -22,17 +22,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
alias Pleroma.Web.MediaProxy
alias Pleroma.Web.PleromaAPI.EmojiReactionController
require Logger
+ alias Pleroma.Web.RichMedia.Card
import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1, visible_for_user?: 2]
- # This is a naive way to do this, just spawning a process per activity
- # to fetch the preview. However it should be fine considering
- # pagination is restricted to 40 activities at a time
defp fetch_rich_media_for_activities(activities) do
Enum.each(activities, fn activity ->
- spawn(fn ->
- Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
- end)
+ Card.get_by_activity(activity)
end)
end
@@ -93,9 +89,7 @@ def render("index.json", opts) do
# To do: check AdminAPIControllerTest on the reasons behind nil activities in the list
activities = Enum.filter(opts.activities, & &1)
- # Start fetching rich media before doing anything else, so that later calls to get the cards
- # only block for timeout in the worst case, as opposed to
- # length(activities_with_links) * timeout
+ # Start prefetching rich media before doing anything else
fetch_rich_media_for_activities(activities)
replied_to_activities = get_replied_to_activities(activities)
@@ -309,6 +303,12 @@ def render("show.json", %{activity: %{id: id, data: %{"object" => _object}} = ac
"mastoapi:content:#{chrono_order}"
)
+ card =
+ case Card.get_by_activity(activity) do
+ %Card{} = result -> render("card.json", result)
+ _ -> nil
+ end
+
content_plaintext =
content
|> Activity.HTML.get_cached_stripped_html_for_activity(
@@ -318,8 +318,6 @@ def render("show.json", %{activity: %{id: id, data: %{"object" => _object}} = ac
summary = object.data["summary"] || ""
- card = render("card.json", Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity))
-
url =
if user.local do
url(~p[/notice/#{activity}])
@@ -528,37 +526,30 @@ def render("source.json", %{activity: %{data: %{"object" => _object}} = activity
}
end
- def render("card.json", %{rich_media: rich_media, page_url: page_url}) do
- page_url_data = URI.parse(page_url)
-
- page_url_data =
- if is_binary(rich_media["url"]) do
- URI.merge(page_url_data, URI.parse(rich_media["url"]))
- else
- page_url_data
- end
+ def render("card.json", %Card{fields: rich_media}) do
+ page_url_data = URI.parse(rich_media["url"])
page_url = page_url_data |> to_string
- image_url_data =
- if is_binary(rich_media["image"]) do
- URI.parse(rich_media["image"])
- else
- nil
- end
-
- image_url = build_image_url(image_url_data, page_url_data)
+ image_url = proxied_url(rich_media["image"], page_url_data)
+ audio_url = proxied_url(rich_media["audio"], page_url_data)
+ video_url = proxied_url(rich_media["video"], page_url_data)
%{
type: "link",
provider_name: page_url_data.host,
provider_url: page_url_data.scheme <> "://" <> page_url_data.host,
url: page_url,
- image: image_url |> MediaProxy.url(),
+ image: image_url,
+ image_description: rich_media["image:alt"] || "",
title: rich_media["title"] || "",
description: rich_media["description"] || "",
pleroma: %{
- opengraph: rich_media
+ opengraph:
+ rich_media
+ |> Maps.put_if_present("image", image_url)
+ |> Maps.put_if_present("audio", audio_url)
+ |> Maps.put_if_present("video", video_url)
}
}
end
@@ -636,6 +627,14 @@ def render("context.json", %{activity: activity, activities: activities, user: u
}
end
+ defp proxied_url(url, page_url_data) do
+ if is_binary(url) do
+ build_image_url(URI.parse(url), page_url_data) |> MediaProxy.url()
+ else
+ nil
+ end
+ end
+
def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do
object = Object.normalize(activity, fetch: false)
@@ -740,19 +739,7 @@ defp build_application(%{"type" => _type, "name" => name, "url" => url}),
defp build_application(_), do: nil
- # Workaround for Elixir issue #10771
- # Avoid applying URI.merge unless necessary
- # TODO: revert to always attempting URI.merge(image_url_data, page_url_data)
- # when Elixir 1.12 is the minimum supported version
@spec build_image_url(struct() | nil, struct()) :: String.t() | nil
- defp build_image_url(
- %URI{scheme: image_scheme, host: image_host} = image_url_data,
- %URI{} = _page_url_data
- )
- when not is_nil(image_scheme) and not is_nil(image_host) do
- image_url_data |> to_string
- end
-
defp build_image_url(%URI{} = image_url_data, %URI{} = page_url_data) do
URI.merge(page_url_data, image_url_data) |> to_string
end
diff --git a/lib/pleroma/web/rich_media/backfill.ex b/lib/pleroma/web/rich_media/backfill.ex
new file mode 100644
index 000000000..6b2373b01
--- /dev/null
+++ b/lib/pleroma/web/rich_media/backfill.ex
@@ -0,0 +1,98 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.RichMedia.Backfill do
+ use Pleroma.Workers.WorkerHelper,
+ queue: "rich_media_backfill",
+ unique: [period: 300, states: Oban.Job.states(), keys: [:op, :url_hash]]
+
+ alias Pleroma.Web.RichMedia.Card
+ alias Pleroma.Web.RichMedia.Parser
+ alias Pleroma.Web.RichMedia.Parser.TTL
+ alias Pleroma.Workers.RichMediaExpirationWorker
+
+ require Logger
+
+ @cachex Pleroma.Config.get([:cachex, :provider], Cachex)
+
+ def start(%{url: url} = args) when is_binary(url) do
+ url_hash = Card.url_to_hash(url)
+
+ args =
+ args
+ |> Map.put(:url_hash, url_hash)
+
+ __MODULE__.enqueue("rich_media_backfill", args)
+ end
+
+ def perform(%Oban.Job{args: %{"op" => "rich_media_backfill", "url" => url} = args})
+ when is_binary(url) do
+ run(args)
+ end
+
+ def run(%{"url" => url, "url_hash" => url_hash} = args) do
+ case Parser.parse(url) do
+ {:ok, fields} ->
+ {:ok, card} = Card.create(url, fields)
+
+ maybe_schedule_expiration(url, fields)
+
+ if Map.has_key?(args, "activity_id") do
+ stream_update(args)
+ end
+
+ warm_cache(url_hash, card)
+ :ok
+
+ {:error, {:invalid_metadata, fields}} ->
+ Logger.debug("Rich media incomplete or invalid metadata for #{url}: #{inspect(fields)}")
+ negative_cache(url_hash, :timer.minutes(30))
+
+ {:error, :body_too_large} ->
+ Logger.error("Rich media error for #{url}: :body_too_large")
+ negative_cache(url_hash, :timer.minutes(30))
+
+ {:error, {:content_type, type}} ->
+ Logger.debug("Rich media error for #{url}: :content_type is #{type}")
+ negative_cache(url_hash, :timer.minutes(30))
+
+ e ->
+ Logger.debug("Rich media error for #{url}: #{inspect(e)}")
+ {:error, e}
+ end
+ end
+
+ def run(e) do
+ Logger.error("Rich media failure - invalid args: #{inspect(e)}")
+ {:discard, :invalid}
+ end
+
+ defp maybe_schedule_expiration(url, fields) do
+ case TTL.process(fields, url) do
+ {:ok, ttl} when is_number(ttl) ->
+ timestamp = DateTime.from_unix!(ttl)
+
+ RichMediaExpirationWorker.new(%{"url" => url}, scheduled_at: timestamp)
+ |> Oban.insert()
+
+ _ ->
+ :ok
+ end
+ end
+
+ defp stream_update(%{"activity_id" => activity_id}) do
+ Logger.info("Rich media backfill: streaming update for activity #{activity_id}")
+
+ Pleroma.Activity.get_by_id(activity_id)
+ |> Pleroma.Activity.normalize()
+ |> Pleroma.Web.ActivityPub.ActivityPub.stream_out()
+ end
+
+ defp warm_cache(key, val), do: @cachex.put(:rich_media_cache, key, val)
+
+ def negative_cache(key, ttl \\ :timer.minutes(30)) do
+ @cachex.put(:rich_media_cache, key, nil, ttl: ttl)
+ {:discard, :error}
+ end
+end
diff --git a/lib/pleroma/web/rich_media/card.ex b/lib/pleroma/web/rich_media/card.ex
new file mode 100644
index 000000000..1b0b5a5c2
--- /dev/null
+++ b/lib/pleroma/web/rich_media/card.ex
@@ -0,0 +1,149 @@
+defmodule Pleroma.Web.RichMedia.Card do
+ use Ecto.Schema
+ import Ecto.Changeset
+ import Ecto.Query
+
+ alias Pleroma.Activity
+ alias Pleroma.HTML
+ alias Pleroma.Object
+ alias Pleroma.Repo
+ alias Pleroma.Web.RichMedia.Backfill
+ alias Pleroma.Web.RichMedia.Parser
+
+ @cachex Pleroma.Config.get([:cachex, :provider], Cachex)
+ @config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config)
+
+ @type t :: %__MODULE__{}
+
+ schema "rich_media_card" do
+ field(:url_hash, :binary)
+ field(:fields, :map)
+
+ timestamps()
+ end
+
+ @doc false
+ def changeset(card, attrs) do
+ card
+ |> cast(attrs, [:url_hash, :fields])
+ |> validate_required([:url_hash, :fields])
+ |> unique_constraint(:url_hash)
+ end
+
+ @spec create(String.t(), map()) :: {:ok, t()}
+ def create(url, fields) do
+ url_hash = url_to_hash(url)
+
+ fields = Map.put_new(fields, "url", url)
+
+ %__MODULE__{}
+ |> changeset(%{url_hash: url_hash, fields: fields})
+ |> Repo.insert(on_conflict: {:replace, [:fields]}, conflict_target: :url_hash)
+ end
+
+ @spec delete(String.t()) :: {:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()} | :ok
+ def delete(url) do
+ url_hash = url_to_hash(url)
+ @cachex.del(:rich_media_cache, url_hash)
+
+ case get_by_url(url) do
+ %__MODULE__{} = card -> Repo.delete(card)
+ nil -> :ok
+ end
+ end
+
+ @spec get_by_url(String.t() | nil) :: t() | nil | :error
+ def get_by_url(url) when is_binary(url) do
+ if @config_impl.get([:rich_media, :enabled]) do
+ url_hash = url_to_hash(url)
+
+ @cachex.fetch!(:rich_media_cache, url_hash, fn _ ->
+ result =
+ __MODULE__
+ |> where(url_hash: ^url_hash)
+ |> Repo.one()
+
+ case result do
+ %__MODULE__{} = card -> {:commit, card}
+ _ -> {:ignore, nil}
+ end
+ end)
+ else
+ :error
+ end
+ end
+
+ def get_by_url(nil), do: nil
+
+ @spec get_or_backfill_by_url(String.t(), map()) :: t() | nil
+ def get_or_backfill_by_url(url, backfill_opts \\ %{}) do
+ case get_by_url(url) do
+ %__MODULE__{} = card ->
+ card
+
+ nil ->
+ backfill_opts = Map.put(backfill_opts, :url, url)
+
+ Backfill.start(backfill_opts)
+
+ nil
+
+ :error ->
+ nil
+ end
+ end
+
+ @spec get_by_activity(Activity.t()) :: t() | nil | :error
+ # Fake/Draft activity
+ def get_by_activity(%Activity{id: "pleroma:fakeid"} = activity) do
+ with %Object{} = object <- Object.normalize(activity, fetch: false),
+ url when not is_nil(url) <- HTML.extract_first_external_url_from_object(object) do
+ case get_by_url(url) do
+ # Cache hit
+ %__MODULE__{} = card ->
+ card
+
+ # Cache miss, but fetch for rendering the Draft
+ _ ->
+ with {:ok, fields} <- Parser.parse(url),
+ {:ok, card} <- create(url, fields) do
+ card
+ else
+ _ -> nil
+ end
+ end
+ else
+ _ ->
+ nil
+ end
+ end
+
+ def get_by_activity(activity) do
+ with %Object{} = object <- Object.normalize(activity, fetch: false),
+ {_, nil} <- {:cached, get_cached_url(object, activity.id)} do
+ nil
+ else
+ {:cached, url} ->
+ get_or_backfill_by_url(url, %{activity_id: activity.id})
+
+ _ ->
+ :error
+ end
+ end
+
+ @spec url_to_hash(String.t()) :: String.t()
+ def url_to_hash(url) do
+ :crypto.hash(:sha256, url) |> Base.encode16(case: :lower)
+ end
+
+ defp get_cached_url(object, activity_id) do
+ key = "URL|#{activity_id}"
+
+ @cachex.fetch!(:scrubber_cache, key, fn _ ->
+ url = HTML.extract_first_external_url_from_object(object)
+ Activity.HTML.add_cache_key_for(activity_id, key)
+
+ {:commit, url}
+ end)
+ end
+end
diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex
index 061c1a795..79ad660a2 100644
--- a/lib/pleroma/web/rich_media/helpers.ex
+++ b/lib/pleroma/web/rich_media/helpers.ex
@@ -3,85 +3,13 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Helpers do
- alias Pleroma.Activity
alias Pleroma.Config
- alias Pleroma.HTML
- alias Pleroma.Object
- alias Pleroma.Web.RichMedia.Parser
-
- @options [
- max_body: 2_000_000,
- receive_timeout: 2_000
- ]
-
- @spec validate_page_url(URI.t() | binary()) :: :ok | :error
- defp validate_page_url(page_url) when is_binary(page_url) do
- validate_tld = Config.get([Pleroma.Formatter, :validate_tld])
-
- page_url
- |> Linkify.Parser.url?(validate_tld: validate_tld)
- |> parse_uri(page_url)
- end
-
- defp validate_page_url(%URI{host: host, scheme: "https", authority: authority})
- when is_binary(authority) do
- cond do
- host in Config.get([:rich_media, :ignore_hosts], []) ->
- :error
-
- get_tld(host) in Config.get([:rich_media, :ignore_tld], []) ->
- :error
-
- true ->
- :ok
- end
- end
-
- defp validate_page_url(_), do: :error
-
- defp parse_uri(true, url) do
- url
- |> URI.parse()
- |> validate_page_url
- end
-
- defp parse_uri(_, _), do: :error
-
- defp get_tld(host) do
- host
- |> String.split(".")
- |> Enum.reverse()
- |> hd
- end
-
- def fetch_data_for_object(object) do
- with true <- Config.get([:rich_media, :enabled]),
- {:ok, page_url} <-
- HTML.extract_first_external_url_from_object(object),
- :ok <- validate_page_url(page_url),
- {:ok, rich_media} <- Parser.parse(page_url) do
- %{page_url: page_url, rich_media: rich_media}
- else
- _ -> %{}
- end
- end
-
- def fetch_data_for_activity(%Activity{data: %{"type" => "Create"}} = activity) do
- with true <- Config.get([:rich_media, :enabled]),
- %Object{} = object <- Object.normalize(activity, fetch: false) do
- fetch_data_for_object(object)
- else
- _ -> %{}
- end
- end
-
- def fetch_data_for_activity(_), do: %{}
def rich_media_get(url) do
headers = [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}]
head_check =
- case Pleroma.HTTP.head(url, headers, @options) do
+ case Pleroma.HTTP.head(url, headers, http_options()) do
# If the HEAD request didn't reach the server for whatever reason,
# we assume the GET that comes right after won't either
{:error, _} = e ->
@@ -96,7 +24,7 @@ def rich_media_get(url) do
:ok
end
- with :ok <- head_check, do: Pleroma.HTTP.get(url, headers, @options)
+ with :ok <- head_check, do: Pleroma.HTTP.get(url, headers, http_options())
end
defp check_content_type(headers) do
@@ -112,12 +40,13 @@ defp check_content_type(headers) do
end
end
- @max_body @options[:max_body]
defp check_content_length(headers) do
+ max_body = Keyword.get(http_options(), :max_body)
+
case List.keyfind(headers, "content-length", 0) do
{_, maybe_content_length} ->
case Integer.parse(maybe_content_length) do
- {content_length, ""} when content_length <= @max_body -> :ok
+ {content_length, ""} when content_length <= max_body -> :ok
{_, ""} -> {:error, :body_too_large}
_ -> :ok
end
@@ -126,4 +55,11 @@ defp check_content_length(headers) do
:ok
end
end
+
+ defp http_options do
+ [
+ pool: :media,
+ max_body: Config.get([:rich_media, :max_body], 5_000_000)
+ ]
+ end
end
diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex
index 3ba0086f0..7f6b5d388 100644
--- a/lib/pleroma/web/rich_media/parser.ex
+++ b/lib/pleroma/web/rich_media/parser.ex
@@ -1,161 +1,41 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2021 Pleroma Authors
+# Copyright © 2017-2022 Pleroma Authors
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Parser do
require Logger
- @cachex Pleroma.Config.get([:cachex, :provider], Cachex)
+ @config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config)
defp parsers do
Pleroma.Config.get([:rich_media, :parsers])
end
- def parse(nil), do: {:error, "No URL provided"}
+ def parse(nil), do: nil
- if Pleroma.Config.get(:env) == :test do
- @spec parse(String.t()) :: {:ok, map()} | {:error, any()}
- def parse(url), do: parse_with_timeout(url)
- else
- @spec parse(String.t()) :: {:ok, map()} | {:error, any()}
- def parse(url) do
- with {:ok, data} <- get_cached_or_parse(url),
- {:ok, _} <- set_ttl_based_on_image(data, url) do
- {:ok, data}
- end
- end
-
- defp get_cached_or_parse(url) do
- case @cachex.fetch(:rich_media_cache, url, fn ->
- case parse_with_timeout(url) do
- {:ok, _} = res ->
- {:commit, res}
-
- {:error, reason} = e ->
- # Unfortunately we have to log errors here, instead of doing that
- # along with ttl setting at the bottom. Otherwise we can get log spam
- # if more than one process was waiting for the rich media card
- # while it was generated. Ideally we would set ttl here as well,
- # so we don't override it number_of_waiters_on_generation
- # times, but one, obviously, can't set ttl for not-yet-created entry
- # and Cachex doesn't support returning ttl from the fetch callback.
- log_error(url, reason)
- {:commit, e}
- end
- end) do
- {action, res} when action in [:commit, :ok] ->
- case res do
- {:ok, _data} = res ->
- res
-
- {:error, reason} = e ->
- if action == :commit, do: set_error_ttl(url, reason)
- e
- end
-
- {:error, e} ->
- {:error, {:cachex_error, e}}
- end
- end
-
- defp set_error_ttl(_url, :body_too_large), do: :ok
- defp set_error_ttl(_url, {:content_type, _}), do: :ok
-
- # The TTL is not set for the errors above, since they are unlikely to change
- # with time
-
- defp set_error_ttl(url, _reason) do
- ttl = Pleroma.Config.get([:rich_media, :failure_backoff], 60_000)
- @cachex.expire(:rich_media_cache, url, ttl)
- :ok
- end
-
- defp log_error(url, {:invalid_metadata, data}) do
- Logger.debug(fn -> "Incomplete or invalid metadata for #{url}: #{inspect(data)}" end)
- end
-
- defp log_error(url, reason) do
- Logger.warning(fn -> "Rich media error for #{url}: #{inspect(reason)}" end)
+ @spec parse(String.t()) :: {:ok, map()} | {:error, any()}
+ def parse(url) do
+ with {_, true} <- {:config, @config_impl.get([:rich_media, :enabled])},
+ :ok <- validate_page_url(url),
+ {:ok, data} <- parse_url(url) do
+ data = Map.put(data, "url", url)
+ {:ok, data}
+ else
+ {:config, _} -> {:error, :rich_media_disabled}
+ e -> e
end
end
- @doc """
- Set the rich media cache based on the expiration time of image.
-
- Adopt behaviour `Pleroma.Web.RichMedia.Parser.TTL`
-
- ## Example
-
- defmodule MyModule do
- @behaviour Pleroma.Web.RichMedia.Parser.TTL
- def ttl(data, url) do
- image_url = Map.get(data, :image)
- # do some parsing in the url and get the ttl of the image
- # and return ttl is unix time
- parse_ttl_from_url(image_url)
- end
- end
-
- Define the module in the config
-
- config :pleroma, :rich_media,
- ttl_setters: [MyModule]
- """
- @spec set_ttl_based_on_image(map(), String.t()) ::
- {:ok, Integer.t() | :noop} | {:error, :no_key}
- def set_ttl_based_on_image(data, url) do
- case get_ttl_from_image(data, url) do
- {:ok, ttl} when is_number(ttl) ->
- ttl = ttl * 1000
-
- case @cachex.expire_at(:rich_media_cache, url, ttl) do
- {:ok, true} -> {:ok, ttl}
- {:ok, false} -> {:error, :no_key}
- end
-
- _ ->
- {:ok, :noop}
- end
- end
-
- defp get_ttl_from_image(data, url) do
- [:rich_media, :ttl_setters]
- |> Pleroma.Config.get()
- |> Enum.reduce({:ok, nil}, fn
- module, {:ok, _ttl} ->
- module.ttl(data, url)
-
- _, error ->
- error
- end)
- end
-
- def parse_url(url) do
+ defp parse_url(url) do
with {:ok, %Tesla.Env{body: html}} <- Pleroma.Web.RichMedia.Helpers.rich_media_get(url),
{:ok, html} <- Floki.parse_document(html) do
html
|> maybe_parse()
- |> Map.put("url", url)
|> clean_parsed_data()
|> check_parsed_data()
end
end
- def parse_with_timeout(url) do
- try do
- task =
- Task.Supervisor.async_nolink(Pleroma.TaskSupervisor, fn ->
- parse_url(url)
- end)
-
- Task.await(task, 5000)
- catch
- :exit, {:timeout, _} ->
- Logger.warning("Timeout while fetching rich media for #{url}")
- {:error, :timeout}
- end
- end
-
defp maybe_parse(html) do
Enum.reduce_while(parsers(), %{}, fn parser, acc ->
case parser.parse(html, acc) do
@@ -181,4 +61,46 @@ defp clean_parsed_data(data) do
end)
|> Map.new()
end
+
+ @spec validate_page_url(URI.t() | binary()) :: :ok | :error
+ defp validate_page_url(page_url) when is_binary(page_url) do
+ validate_tld = @config_impl.get([Pleroma.Formatter, :validate_tld])
+
+ page_url
+ |> Linkify.Parser.url?(validate_tld: validate_tld)
+ |> parse_uri(page_url)
+ end
+
+ defp validate_page_url(%URI{host: host, scheme: "https"}) do
+ cond do
+ Linkify.Parser.ip?(host) ->
+ :error
+
+ host in @config_impl.get([:rich_media, :ignore_hosts], []) ->
+ :error
+
+ get_tld(host) in @config_impl.get([:rich_media, :ignore_tld], []) ->
+ :error
+
+ true ->
+ :ok
+ end
+ end
+
+ defp validate_page_url(_), do: :error
+
+ defp parse_uri(true, url) do
+ url
+ |> URI.parse()
+ |> validate_page_url
+ end
+
+ defp parse_uri(_, _), do: :error
+
+ defp get_tld(host) do
+ host
+ |> String.split(".")
+ |> Enum.reverse()
+ |> hd
+ end
end
diff --git a/lib/pleroma/web/rich_media/parser/ttl.ex b/lib/pleroma/web/rich_media/parser/ttl.ex
index 0b7f14fb2..ff0974857 100644
--- a/lib/pleroma/web/rich_media/parser/ttl.ex
+++ b/lib/pleroma/web/rich_media/parser/ttl.ex
@@ -3,5 +3,18 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Parser.TTL do
- @callback ttl(Map.t(), String.t()) :: Integer.t() | nil
+ @callback ttl(map(), String.t()) :: integer() | nil
+
+ @spec process(map(), String.t()) :: {:ok, integer() | nil}
+ def process(data, url) do
+ [:rich_media, :ttl_setters]
+ |> Pleroma.Config.get()
+ |> Enum.reduce_while({:ok, nil}, fn
+ module, acc ->
+ case module.ttl(data, url) do
+ ttl when is_number(ttl) -> {:halt, {:ok, ttl}}
+ _ -> {:cont, acc}
+ end
+ end)
+ end
end
diff --git a/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex b/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex
index c7eb267f3..1172a120a 100644
--- a/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex
+++ b/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2021 Pleroma Authors
+# Copyright © 2017-2022 Pleroma Authors
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl do
@@ -7,25 +7,26 @@ defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl do
@impl true
def ttl(data, _url) do
- image = Map.get(data, :image)
+ image = Map.get(data, "image")
- if is_aws_signed_url(image) do
+ if aws_signed_url?(image) do
image
|> parse_query_params()
|> format_query_params()
|> get_expiration_timestamp()
else
- {:error, "Not aws signed url #{inspect(image)}"}
+ nil
end
end
- defp is_aws_signed_url(image) when is_binary(image) and image != "" do
+ defp aws_signed_url?(image) when is_binary(image) and image != "" do
%URI{host: host, query: query} = URI.parse(image)
- String.contains?(host, "amazonaws.com") and String.contains?(query, "X-Amz-Expires")
+ is_binary(host) and String.contains?(host, "amazonaws.com") and
+ is_binary(query) and String.contains?(query, "X-Amz-Expires")
end
- defp is_aws_signed_url(_), do: nil
+ defp aws_signed_url?(_), do: nil
defp parse_query_params(image) do
%URI{query: query} = URI.parse(image)
@@ -45,6 +46,6 @@ defp get_expiration_timestamp(params) when is_map(params) do
|> Map.get("X-Amz-Date")
|> Timex.parse("{ISO:Basic:Z}")
- {:ok, Timex.to_unix(date) + String.to_integer(Map.get(params, "X-Amz-Expires"))}
+ Timex.to_unix(date) + String.to_integer(Map.get(params, "X-Amz-Expires"))
end
end
diff --git a/lib/pleroma/web/rich_media/parser/ttl/opengraph.ex b/lib/pleroma/web/rich_media/parser/ttl/opengraph.ex
new file mode 100644
index 000000000..b06889669
--- /dev/null
+++ b/lib/pleroma/web/rich_media/parser/ttl/opengraph.ex
@@ -0,0 +1,20 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.RichMedia.Parser.TTL.Opengraph do
+ @behaviour Pleroma.Web.RichMedia.Parser.TTL
+
+ @impl true
+ def ttl(%{"ttl" => ttl_string}, _url) when is_binary(ttl_string) do
+ try do
+ ttl = String.to_integer(ttl_string)
+ now = DateTime.utc_now() |> DateTime.to_unix()
+ now + ttl
+ rescue
+ _ -> nil
+ end
+ end
+
+ def ttl(_, _), do: nil
+end
diff --git a/lib/pleroma/web/rich_media/parsers/o_embed.ex b/lib/pleroma/web/rich_media/parsers/o_embed.ex
index 695740d2e..0f303176c 100644
--- a/lib/pleroma/web/rich_media/parsers/o_embed.ex
+++ b/lib/pleroma/web/rich_media/parsers/o_embed.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2021 Pleroma Authors
+# Copyright © 2017-2022 Pleroma Authors
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Parsers.OEmbed do
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index bb5091711..8524cdad6 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -804,13 +804,9 @@ defmodule Pleroma.Web.Router do
scope "/", Pleroma.Web.ActivityPub do
pipe_through([:activitypub_client])
- get("/api/ap/whoami", ActivityPubController, :whoami)
get("/users/:nickname/inbox", ActivityPubController, :read_inbox)
get("/users/:nickname/outbox", ActivityPubController, :outbox)
- post("/users/:nickname/outbox", ActivityPubController, :update_outbox)
- post("/api/ap/upload_media", ActivityPubController, :upload_media)
-
get("/users/:nickname/collections/featured", ActivityPubController, :pinned)
end
diff --git a/lib/pleroma/web/telemetry.ex b/lib/pleroma/web/telemetry.ex
index 269f9f238..0ddb2c903 100644
--- a/lib/pleroma/web/telemetry.ex
+++ b/lib/pleroma/web/telemetry.ex
@@ -151,41 +151,40 @@ defp summary_fallback_metrics(byte_unit \\ :byte) do
# phoenix.router_dispatch.stop.duration
# pleroma.repo.query.total_time
# pleroma.repo.query.queue_time
- dist_metrics =
- [
- distribution("phoenix.endpoint.stop.duration.fdist",
- event_name: [:phoenix, :endpoint, :stop],
- measurement: :duration,
- unit: {:native, :millisecond},
- reporter_options: [
- buckets: simple_buckets
- ]
- ),
- distribution("pleroma.repo.query.decode_time.fdist",
- event_name: [:pleroma, :repo, :query],
- measurement: :decode_time,
- unit: {:native, :millisecond},
- reporter_options: [
- buckets: simple_buckets_quick
- ]
- ),
- distribution("pleroma.repo.query.query_time.fdist",
- event_name: [:pleroma, :repo, :query],
- measurement: :query_time,
- unit: {:native, :millisecond},
- reporter_options: [
- buckets: simple_buckets
- ]
- ),
- distribution("pleroma.repo.query.idle_time.fdist",
- event_name: [:pleroma, :repo, :query],
- measurement: :idle_time,
- unit: {:native, :millisecond},
- reporter_options: [
- buckets: simple_buckets
- ]
- )
- ]
+ dist_metrics = [
+ distribution("phoenix.endpoint.stop.duration.fdist",
+ event_name: [:phoenix, :endpoint, :stop],
+ measurement: :duration,
+ unit: {:native, :millisecond},
+ reporter_options: [
+ buckets: simple_buckets
+ ]
+ ),
+ distribution("pleroma.repo.query.decode_time.fdist",
+ event_name: [:pleroma, :repo, :query],
+ measurement: :decode_time,
+ unit: {:native, :millisecond},
+ reporter_options: [
+ buckets: simple_buckets_quick
+ ]
+ ),
+ distribution("pleroma.repo.query.query_time.fdist",
+ event_name: [:pleroma, :repo, :query],
+ measurement: :query_time,
+ unit: {:native, :millisecond},
+ reporter_options: [
+ buckets: simple_buckets
+ ]
+ ),
+ distribution("pleroma.repo.query.idle_time.fdist",
+ event_name: [:pleroma, :repo, :query],
+ measurement: :idle_time,
+ unit: {:native, :millisecond},
+ reporter_options: [
+ buckets: simple_buckets
+ ]
+ )
+ ]
vm_metrics =
sum_counter_pair("vm.memory.total",
diff --git a/lib/pleroma/web/web_finger.ex b/lib/pleroma/web/web_finger.ex
index 9d5efbb3e..6a8c86a29 100644
--- a/lib/pleroma/web/web_finger.ex
+++ b/lib/pleroma/web/web_finger.ex
@@ -156,11 +156,21 @@ def get_template_from_xml(body) do
end
end
+ @cachex Pleroma.Config.get([:cachex, :provider], Cachex)
def find_lrdd_template(domain) do
+ @cachex.fetch!(:host_meta_cache, domain, fn _ ->
+ {:commit, fetch_lrdd_template(domain)}
+ end)
+ rescue
+ e -> {:error, "Cachex error: #{inspect(e)}"}
+ end
+
+ defp fetch_lrdd_template(domain) do
# WebFinger is restricted to HTTPS - https://tools.ietf.org/html/rfc7033#section-9.1
meta_url = "https://#{domain}/.well-known/host-meta"
- with {:ok, %{status: status, body: body}} when status in 200..299 <- HTTP.get(meta_url) do
+ with {:ok, %{status: status, body: body}} when status in 200..299 <-
+ HTTP.Backoff.get(meta_url) do
get_template_from_xml(body)
else
error ->
@@ -169,7 +179,7 @@ def find_lrdd_template(domain) do
end
end
- defp get_address_from_domain(domain, encoded_account) when is_binary(domain) do
+ defp get_address_from_domain(domain, "acct:" <> _ = encoded_account) when is_binary(domain) do
case find_lrdd_template(domain) do
{:ok, template} ->
String.replace(template, "{uri}", encoded_account)
@@ -179,6 +189,11 @@ defp get_address_from_domain(domain, encoded_account) when is_binary(domain) do
end
end
+ defp get_address_from_domain(domain, account) when is_binary(domain) do
+ encoded_account = URI.encode("acct:#{account}")
+ get_address_from_domain(domain, encoded_account)
+ end
+
defp get_address_from_domain(_, _), do: {:error, :webfinger_no_domain}
@spec finger(String.t()) :: {:ok, map()} | {:error, any()}
@@ -193,11 +208,9 @@ def finger(account) do
URI.parse(account).host
end
- encoded_account = URI.encode("acct:#{account}")
-
- with address when is_binary(address) <- get_address_from_domain(domain, encoded_account),
+ with address when is_binary(address) <- get_address_from_domain(domain, account),
{:ok, %{status: status, body: body, headers: headers}} when status in 200..299 <-
- HTTP.get(
+ HTTP.Backoff.get(
address,
[{"accept", "application/xrd+xml,application/jrd+json"}]
) do
@@ -217,10 +230,28 @@ def finger(account) do
_ ->
{:error, {:content_type, nil}}
end
+ |> case do
+ {:ok, data} -> validate_webfinger(address, data)
+ error -> error
+ end
else
error ->
Logger.debug("Couldn't finger #{account}: #{inspect(error)}")
error
end
end
+
+ defp validate_webfinger(request_url, %{"subject" => "acct:" <> acct = subject} = data) do
+ with [_name, acct_host] <- String.split(acct, "@"),
+ {_, url} <- {:address, get_address_from_domain(acct_host, subject)},
+ %URI{host: request_host} <- URI.parse(request_url),
+ %URI{host: acct_host} <- URI.parse(url),
+ {_, true} <- {:hosts_match, acct_host == request_host} do
+ {:ok, data}
+ else
+ _ -> {:error, {:webfinger_invalid, request_url, data}}
+ end
+ end
+
+ defp validate_webfinger(url, data), do: {:error, {:webfinger_invalid, url, data}}
end
diff --git a/lib/pleroma/web/xml.ex b/lib/pleroma/web/xml.ex
index e68341e20..7fe7730ea 100644
--- a/lib/pleroma/web/xml.ex
+++ b/lib/pleroma/web/xml.ex
@@ -26,7 +26,7 @@ def string_from_xpath(xpath, doc) do
def parse_document(text) do
try do
- doc = SweetXml.parse(text, dtd: :none)
+ doc = SweetXml.parse(text, dtd: :none, quiet: true)
{:ok, doc}
rescue
diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex
index 9c3d8d1c2..a663a63fe 100644
--- a/lib/pleroma/workers/receiver_worker.ex
+++ b/lib/pleroma/workers/receiver_worker.ex
@@ -14,7 +14,8 @@ def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do
else
{:error, :origin_containment_failed} -> {:discard, :origin_containment_failed}
{:error, {:reject, reason}} -> {:discard, reason}
- e -> e
+ {:error, _} = e -> e
+ e -> {:error, e}
end
end
end
diff --git a/lib/pleroma/workers/remote_fetcher_worker.ex b/lib/pleroma/workers/remote_fetcher_worker.ex
index ad4d785a1..1bc0e5d0c 100644
--- a/lib/pleroma/workers/remote_fetcher_worker.ex
+++ b/lib/pleroma/workers/remote_fetcher_worker.ex
@@ -5,10 +5,42 @@
defmodule Pleroma.Workers.RemoteFetcherWorker do
alias Pleroma.Object.Fetcher
- use Pleroma.Workers.WorkerHelper, queue: "remote_fetcher"
+ use Pleroma.Workers.WorkerHelper,
+ queue: "remote_fetcher",
+ unique: [period: 300, states: Oban.Job.states(), keys: [:op, :id]]
@impl Oban.Worker
def perform(%Job{args: %{"op" => "fetch_remote", "id" => id} = args}) do
- {:ok, _object} = Fetcher.fetch_object_from_id(id, depth: args["depth"])
+ case Fetcher.fetch_object_from_id(id, depth: args["depth"]) do
+ {:ok, _object} ->
+ :ok
+
+ {:error, :forbidden} ->
+ {:discard, :forbidden}
+
+ {:error, :not_found} ->
+ {:discard, :not_found}
+
+ {:error, :allowed_depth} ->
+ {:discard, :allowed_depth}
+
+ {:error, :invalid_uri_scheme} ->
+ {:discard, :invalid_uri_scheme}
+
+ {:error, :local_resource} ->
+ {:discard, :local_resource}
+
+ {:reject, _} ->
+ {:discard, :reject}
+
+ {:error, :id_mismatch} ->
+ {:discard, :id_mismatch}
+
+ {:error, _} = e ->
+ e
+
+ e ->
+ {:error, e}
+ end
end
end
diff --git a/lib/pleroma/workers/rich_media_expiration_worker.ex b/lib/pleroma/workers/rich_media_expiration_worker.ex
new file mode 100644
index 000000000..d7ae497a7
--- /dev/null
+++ b/lib/pleroma/workers/rich_media_expiration_worker.ex
@@ -0,0 +1,15 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Workers.RichMediaExpirationWorker do
+ alias Pleroma.Web.RichMedia.Card
+
+ use Oban.Worker,
+ queue: :rich_media_expiration
+
+ @impl Oban.Worker
+ def perform(%Job{args: %{"url" => url} = _args}) do
+ Card.delete(url)
+ end
+end
diff --git a/lib/pleroma/workers/worker_helper.ex b/lib/pleroma/workers/worker_helper.ex
index 4c0a55774..6d27151de 100644
--- a/lib/pleroma/workers/worker_helper.ex
+++ b/lib/pleroma/workers/worker_helper.ex
@@ -25,12 +25,16 @@ def sidekiq_backoff(attempt, pow \\ 4, base_backoff \\ 15) do
defmacro __using__(opts) do
caller_module = __CALLER__.module
queue = Keyword.fetch!(opts, :queue)
+ # by default just stop unintended duplicates - this can and should be overridden
+ # if you want to have a more complex uniqueness constraint
+ uniqueness = Keyword.get(opts, :unique, period: 1)
quote do
# Note: `max_attempts` is intended to be overridden in `new/2` call
use Oban.Worker,
queue: unquote(queue),
- max_attempts: 1
+ max_attempts: 1,
+ unique: unquote(uniqueness)
alias Oban.Job
diff --git a/mix.exs b/mix.exs
index fd955ace9..2cdb9a5a6 100644
--- a/mix.exs
+++ b/mix.exs
@@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do
def project do
[
app: :pleroma,
- version: version("3.12.2"),
+ version: version("3.13.2"),
elixir: "~> 1.14",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: Mix.compilers(),
@@ -79,7 +79,8 @@ def application do
:comeonin,
:fast_sanitize,
:os_mon,
- :ssl
+ :ssl,
+ :recon
],
included_applications: [:ex_syslogger]
]
@@ -126,7 +127,7 @@ defp deps do
{:ecto_enum, "~> 1.4"},
{:ecto_sql, "~> 3.10.0"},
{:postgrex, "~> 0.17.2"},
- {:oban, "~> 2.15.2"},
+ {:oban, "~> 2.17.8"},
{:gettext, "~> 0.22.3"},
{:bcrypt_elixir, "~> 3.0.1"},
{:fast_sanitize, "~> 0.2.3"},
@@ -137,7 +138,7 @@ defp deps do
{:tesla, "~> 1.7"},
{:castore, "~> 1.0"},
{:cowlib, "~> 2.12"},
- {:finch, "~> 0.16.0"},
+ {:finch, "~> 0.18.0"},
{:jason, "~> 1.4"},
{:trailing_format_plug, "~> 0.0.7"},
{:mogrify, "~> 0.9"},
@@ -158,10 +159,8 @@ defp deps do
{:floki, "~> 0.34"},
{:timex, "~> 3.7"},
{:ueberauth, "== 0.10.5"},
- {:linkify, git: "https://akkoma.dev/AkkomaGang/linkify.git"},
- {:http_signatures,
- git: "https://akkoma.dev/AkkomaGang/http_signatures.git",
- ref: "6640ce7d24c783ac2ef56e27d00d12e8dc85f396"},
+ {:linkify, "~> 0.5.3"},
+ {:http_signatures, "~> 0.1.2"},
{:telemetry, "~> 1.2"},
{:telemetry_poller, "~> 1.0"},
{:telemetry_metrics, "~> 0.6"},
diff --git a/mix.lock b/mix.lock
index c62df06f8..a4e2ddc8c 100644
--- a/mix.lock
+++ b/mix.lock
@@ -3,12 +3,12 @@
"base62": {:hex, :base62, "1.2.2", "85c6627eb609317b70f555294045895ffaaeb1758666ab9ef9ca38865b11e629", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "d41336bda8eaa5be197f1e4592400513ee60518e5b9f4dcf38f4b4dae6f377bb"},
"bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"},
"bcrypt_elixir": {:hex, :bcrypt_elixir, "3.0.1", "9be815469e6bfefec40fa74658ecbbe6897acfb57614df1416eeccd4903f602c", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "486bb95efb645d1efc6794c1ddd776a186a9a713abf06f45708a6ce324fb96cf"},
- "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"},
+ "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"cachex": {:hex, :cachex, "3.6.0", "14a1bfbeee060dd9bec25a5b6f4e4691e3670ebda28c8ba2884b12fe30b36bf8", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "ebf24e373883bc8e0c8d894a63bbe102ae13d918f790121f5cfe6e485cc8e2e2"},
"calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.1.201603 or ~> 0.5.20 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"},
"captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "90f6ce7672f70f56708792a98d98bd05176c9176", [ref: "90f6ce7672f70f56708792a98d98bd05176c9176"]},
- "castore": {:hex, :castore, "1.0.6", "ffc42f110ebfdafab0ea159cd43d31365fa0af0ce4a02ecebf1707ae619ee727", [:mix], [], "hexpm", "374c6e7ca752296be3d6780a6d5b922854ffcc74123da90f2f328996b962d33a"},
+ "castore": {:hex, :castore, "1.0.7", "b651241514e5f6956028147fe6637f7ac13802537e895a724f90bf3e36ddd1dd", [:mix], [], "hexpm", "da7785a4b0d2a021cd1292a60875a784b6caef71e76bf4917bdee1f390455cf5"},
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"},
@@ -18,7 +18,7 @@
"cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
"cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"},
- "credo": {:hex, :credo, "1.7.5", "643213503b1c766ec0496d828c90c424471ea54da77c8a168c725686377b9545", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "f799e9b5cd1891577d8c773d245668aa74a2fcd15eb277f51a0131690ebfb3fd"},
+ "credo": {:hex, :credo, "1.7.6", "b8f14011a5443f2839b04def0b252300842ce7388f3af177157c86da18dfbeea", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "146f347fb9f8cbc5f7e39e3f22f70acbef51d441baa6d10169dd604bfbc55296"},
"custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},
"db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"},
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
@@ -29,18 +29,18 @@
"eblurhash": {:hex, :eblurhash, "1.2.2", "7da4255aaea984b31bb71155f673257353b0e0554d0d30dcf859547e74602582", [:rebar3], [], "hexpm", "8c20ca00904de023a835a9dcb7b7762fed32264c85a80c3cafa85288e405044c"},
"ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"},
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
- "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.15", "0fc29dbae0e444a29bd6abeee4cf3c4c037e692a272478a234a1cc765077dbb1", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "b6127f3a5c6fc3d84895e4768cc7c199f22b48b67d6c99b13fbf4a374e73f039"},
+ "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.8.0", "440719cd74f09b3f01c84455707a2c3972b725c513808e68eb6c5b0ab82bf523", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 0.18.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "f1512812dc196bcb932a96c82e55f69b543dc125e9d39f5e3631a9c4ec65ef12"},
"ecto_sql": {:hex, :ecto_sql, "3.10.2", "6b98b46534b5c2f8b8b5f03f126e75e2a73c64f3c071149d32987a5378b0fdbd", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007"},
"elasticsearch": {:git, "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", "6cd946f75f6ab9042521a009d1d32d29a90113ca", [ref: "main"]},
- "elixir_make": {:hex, :elixir_make, "0.8.3", "d38d7ee1578d722d89b4d452a3e36bcfdc644c618f0d063b874661876e708683", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "5c99a18571a756d4af7a4d89ca75c28ac899e6103af6f223982f09ce44942cc9"},
+ "elixir_make": {:hex, :elixir_make, "0.8.4", "4960a03ce79081dee8fe119d80ad372c4e7badb84c493cc75983f9d3bc8bde0f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "6e7f1d619b5f61dfabd0a20aa268e575572b542ac31723293a4c1a567d5ef040"},
"elixir_xml_to_map": {:hex, :elixir_xml_to_map, "3.1.0", "4d6260486a8cce59e4bf3575fe2dd2a24766546ceeef9f93fcec6f7c62a2827a", [:mix], [{:erlsom, "~> 1.4", [hex: :erlsom, repo: "hexpm", optional: false]}], "hexpm", "8fe5f2e75f90bab07ee2161120c2dc038ebcae8135554f5582990f1c8c21f911"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"erlsom": {:hex, :erlsom, "1.5.1", "c8fe2babd33ff0846403f6522328b8ab676f896b793634cfe7ef181c05316c03", [:rebar3], [], "hexpm", "7965485494c5844dd127656ac40f141aadfa174839ec1be1074e7edf5b4239eb"},
"eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"},
- "ex_aws": {:hex, :ex_aws, "2.5.3", "9c2d05ba0c057395b12c7b5ca6267d14cdaec1d8e65bdf6481fe1fd245accfb4", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "67115f1d399d7ec4d191812ee565c6106cb4b1bbf19a9d4db06f265fd87da97e"},
+ "ex_aws": {:hex, :ex_aws, "2.5.4", "86c5bb870a49e0ab6f5aa5dd58cf505f09d2624ebe17530db3c1b61c88a673af", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e82bd0091bb9a5bb190139599f922ff3fc7aebcca4374d65c99c4e23aa6d1625"},
"ex_aws_s3": {:hex, :ex_aws_s3, "2.5.3", "422468e5c3e1a4da5298e66c3468b465cfd354b842e512cb1f6fbbe4e2f5bdaf", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "4f09dd372cc386550e484808c5ac5027766c8d0cd8271ccc578b82ee6ef4f3b8"},
- "ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"},
- "ex_doc": {:hex, :ex_doc, "0.32.0", "896afb57b1e00030f6ec8b2e19d3ca99a197afb23858d49d94aea673dc222f12", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "ed2c3e42c558f49bda3ff37e05713432006e1719a6c4a3320c7e4735787374e7"},
+ "ex_const": {:hex, :ex_const, "0.3.0", "9d79516679991baf540ef445438eef1455ca91cf1a3c2680d8fb9e5bea2fe4de", [:mix], [], "hexpm", "76546322abb9e40ee4a2f454cf1c8a5b25c3672fa79bed1ea52c31e0d2428ca9"},
+ "ex_doc": {:hex, :ex_doc, "0.34.0", "ab95e0775db3df71d30cf8d78728dd9261c355c81382bcd4cefdc74610bef13e", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "60734fb4c1353f270c3286df4a0d51e65a2c1d9fba66af3940847cc65a8066d7"},
"ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"},
"ex_syslogger": {:hex, :ex_syslogger, "2.0.0", "de6de5c5472a9c4fdafb28fa6610e381ae79ebc17da6490b81d785d68bd124c9", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "a52b2fe71764e9e6ecd149ab66635812f68e39279cbeee27c52c0e35e8b8019e"},
"excoveralls": {:hex, :excoveralls, "0.16.1", "0bd42ed05c7d2f4d180331a20113ec537be509da31fed5c8f7047ce59ee5a7c5", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dae763468e2008cf7075a64cb1249c97cb4bc71e236c5c2b5e5cdf1cfa2bf138"},
@@ -49,55 +49,55 @@
"fast_sanitize": {:hex, :fast_sanitize, "0.2.3", "67b93dfb34e302bef49fec3aaab74951e0f0602fd9fa99085987af05bd91c7a5", [:mix], [{:fast_html, "~> 2.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "e8ad286d10d0386e15d67d0ee125245ebcfbc7d7290b08712ba9013c8c5e56e2"},
"file_ex": {:git, "https://akkoma.dev/AkkomaGang/file_ex.git", "cc7067c7d446c2526e9ecf91d40896b088851569", [ref: "cc7067c7d446c2526e9ecf91d40896b088851569"]},
"file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"},
- "finch": {:hex, :finch, "0.16.0", "40733f02c89f94a112518071c0a91fe86069560f5dbdb39f9150042f44dcfb1a", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f660174c4d519e5fec629016054d60edd822cdfe2b7270836739ac2f97735ec5"},
+ "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"},
"flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"},
- "floki": {:hex, :floki, "0.36.1", "712b7f2ba19a4d5a47dfe3e74d81876c95bbcbee44fe551f0af3d2a388abb3da", [:mix], [], "hexpm", "21ba57abb8204bcc70c439b423fc0dd9f0286de67dc82773a14b0200ada0995f"},
+ "floki": {:hex, :floki, "0.36.2", "a7da0193538c93f937714a6704369711998a51a6164a222d710ebd54020aa7a3", [:mix], [], "hexpm", "a8766c0bc92f074e5cb36c4f9961982eda84c5d2b8e979ca67f5c268ec8ed580"},
"gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"},
"gettext": {:hex, :gettext, "0.22.3", "c8273e78db4a0bb6fba7e9f0fd881112f349a3117f7f7c598fa18c66c888e524", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "935f23447713954a6866f1bb28c3a878c4c011e802bcd68a726f5e558e4b64bd"},
"hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"},
"hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
- "http_signatures": {:git, "https://akkoma.dev/AkkomaGang/http_signatures.git", "6640ce7d24c783ac2ef56e27d00d12e8dc85f396", [ref: "6640ce7d24c783ac2ef56e27d00d12e8dc85f396"]},
+ "http_signatures": {:hex, :http_signatures, "0.1.2", "ed1cc7043abcf5bb4f30d68fb7bad9d618ec1a45c4ff6c023664e78b67d9c406", [:mix], [], "hexpm", "f08aa9ac121829dae109d608d83c84b940ef2f183ae50f2dd1e9a8bc619d8be7"},
"httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"inet_cidr": {:hex, :inet_cidr, "1.0.8", "d26bb7bdbdf21ae401ead2092bf2bb4bf57fe44a62f5eaa5025280720ace8a40", [:mix], [], "hexpm", "d5b26da66603bb56c933c65214c72152f0de9a6ea53618b56d63302a68f6a90e"},
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
"joken": {:hex, :joken, "2.6.1", "2ca3d8d7f83bf7196296a3d9b2ecda421a404634bfc618159981a960020480a1", [:mix], [{:jose, "~> 1.11.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "ab26122c400b3d254ce7d86ed066d6afad27e70416df947cdcb01e13a7382e68"},
- "jose": {:hex, :jose, "1.11.9", "c861eb99d9e9f62acd071dc5a49ffbeab9014e44490cd85ea3e49e3d36184777", [:mix, :rebar3], [], "hexpm", "b5ccc3749d2e1638c26bed806259df5bc9e438797fe60dc71e9fa0716133899b"},
+ "jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"},
"jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"},
- "linkify": {:git, "https://akkoma.dev/AkkomaGang/linkify.git", "2567e2c1073fa371fd26fd66dfa5bc77b6919c16", []},
+ "linkify": {:hex, :linkify, "0.5.3", "5f8143d8f61f5ff08d3aeeff47ef6509492b4948d8f08007fbf66e4d2246a7f2", [:mix], [], "hexpm", "3ef35a1377d47c25506e07c1c005ea9d38d700699d92ee92825f024434258177"},
"mail": {:hex, :mail, "0.3.1", "cb0a14e4ed8904e4e5a08214e686ccf6f9099346885db17d8c309381f865cc5c", [:mix], [], "hexpm", "1db701e89865c1d5fa296b2b57b1cd587587cca8d8a1a22892b35ef5a8e352a6"},
"majic": {:git, "https://akkoma.dev/AkkomaGang/majic.git", "80540b36939ec83f48e76c61e5000e0fd67706f0", [ref: "80540b36939ec83f48e76c61e5000e0fd67706f0"]},
- "makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"},
+ "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"},
- "makeup_erlang": {:hex, :makeup_erlang, "0.1.5", "e0ff5a7c708dda34311f7522a8758e23bfcd7d8d8068dc312b5eb41c6fd76eba", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "94d2e986428585a21516d7d7149781480013c56e30c6a233534bedf38867a59a"},
+ "makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"},
"meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mfm_parser": {:git, "https://akkoma.dev/AkkomaGang/mfm-parser.git", "b21ab7754024af096f2d14247574f55f0063295b", [ref: "b21ab7754024af096f2d14247574f55f0063295b"]},
"mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
- "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
+ "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"},
"mint": {:hex, :mint, "1.5.2", "4805e059f96028948870d23d7783613b7e6b0e2fb4e98d720383852a760067fd", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "d77d9e9ce4eb35941907f1d3df38d8f750c357865353e21d335bdcdf6d892a02"},
"mock": {:hex, :mock, "0.3.8", "7046a306b71db2488ef54395eeb74df0a7f335a7caca4a3d3875d1fc81c884dd", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "7fa82364c97617d79bb7d15571193fc0c4fe5afd0c932cef09426b3ee6fe2022"},
"mogrify": {:hex, :mogrify, "0.9.3", "238c782f00271dace01369ad35ae2e9dd020feee3443b9299ea5ea6bed559841", [:mix], [], "hexpm", "0189b1e1de27455f2b9ae8cf88239cefd23d38de9276eb5add7159aea51731e6"},
"mox": {:hex, :mox, "1.1.0", "0f5e399649ce9ab7602f72e718305c0f9cdc351190f72844599545e4996af73c", [:mix], [], "hexpm", "d44474c50be02d5b72131070281a5d3895c0e7a95c780e90bc0cfe712f633a13"},
- "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"},
+ "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
- "oban": {:hex, :oban, "2.15.4", "d49ab4ffb7153010e32f80fe9e56f592706238149ec579eb50f8a4e41d218856", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5fce611fdfffb13e9148df883116e5201adf1e731eb302cc88cde0588510079c"},
- "open_api_spex": {:hex, :open_api_spex, "3.18.3", "fefb84fe323cacfc92afdd0ecb9e89bc0261ae00b7e3167ffc2028ce3944de42", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "c0cfc31570199ce7e7520b494a591027da609af45f6bf9adce51e2469b1609fb"},
+ "oban": {:hex, :oban, "2.17.10", "c3e5bd739b5c3fdc38eba1d43ab270a8c6ca4463bb779b7705c69400b0d87678", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4afd027b8e2bc3c399b54318b4f46ee8c40251fb55a285cb4e38b5363f0ee7c4"},
+ "open_api_spex": {:hex, :open_api_spex, "3.19.1", "65ccb5d06e3d664d1eec7c5ea2af2289bd2f37897094a74d7219fb03fc2b5994", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "392895827ce2984a3459c91a484e70708132d8c2c6c5363972b4b91d6bbac3dd"},
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
"phoenix": {:hex, :phoenix, "1.7.12", "1cc589e0eab99f593a8aa38ec45f15d25297dd6187ee801c8de8947090b5a9d3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "d646192fbade9f485b01bc9920c139bfdd19d0f8df3d73fd8eaf2dfbe0d2837c"},
- "phoenix_ecto": {:hex, :phoenix_ecto, "4.5.1", "6fdbc334ea53620e71655664df6f33f670747b3a7a6c4041cdda3e2c32df6257", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ebe43aa580db129e54408e719fb9659b7f9e0d52b965c5be26cdca416ecead28"},
- "phoenix_html": {:hex, :phoenix_html, "3.3.3", "380b8fb45912b5638d2f1d925a3771b4516b9a78587249cabe394e0a5d579dc9", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "923ebe6fec6e2e3b3e569dfbdc6560de932cd54b000ada0208b5f45024bdd76c"},
+ "phoenix_ecto": {:hex, :phoenix_ecto, "4.6.1", "96798325fab2fed5a824ca204e877b81f9afd2e480f581e81f7b4b64a5a477f2", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.17", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "0ae544ff99f3c482b0807c5cec2c8289e810ecacabc04959d82c3337f4703391"},
+ "phoenix_html": {:hex, :phoenix_html, "3.3.4", "42a09fc443bbc1da37e372a5c8e6755d046f22b9b11343bf885067357da21cb3", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0249d3abec3714aff3415e7ee3d9786cb325be3151e6c4b3021502c585bf53fb"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.18.18", "1f38fbd7c363723f19aad1a04b5490ff3a178e37daaf6999594d5f34796c47fc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
"phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.1", "b74ccaa8046fbc388a62134360ee7d9742d5a8ae74063f34eb050279de7a99e1", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "4000eeba3f9d7d1a6bf56d2bd56733d5cadf41a7f0d8ffe5bb67e7d667e204a2"},
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
"phoenix_view": {:hex, :phoenix_view, "2.0.3", "4d32c4817fce933693741deeb99ef1392619f942633dde834a5163124813aad3", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "cd34049af41be2c627df99cd4eaa71fc52a328c0c3d8e7d4aa28f880c30e7f64"},
- "plug": {:hex, :plug, "1.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"},
+ "plug": {:hex, :plug, "1.16.0", "1d07d50cb9bb05097fdf187b31cf087c7297aafc3fed8299aac79c128a707e47", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cbf53aa1f5c4d758a7559c0bd6d59e286c2be0c6a1fac8cc3eee2f638243b93e"},
"plug_cowboy": {:hex, :plug_cowboy, "2.7.1", "87677ffe3b765bc96a89be7960f81703223fe2e21efa42c125fcd0127dd9d6b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "02dbd5f9ab571b864ae39418db7811618506256f6d13b4a45037e5fe78dc5de3"},
- "plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"},
+ "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"},
"poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
@@ -107,7 +107,7 @@
"recon": {:hex, :recon, "2.5.5", "c108a4c406fa301a529151a3bb53158cadc4064ec0c5f99b03ddb8c0e4281bdf", [:mix, :rebar3], [], "hexpm", "632a6f447df7ccc1a4a10bdcfce71514412b16660fe59deca0fcf0aa3c054404"},
"remote_ip": {:hex, :remote_ip, "1.1.0", "cb308841595d15df3f9073b7c39243a1dd6ca56e5020295cb012c76fbec50f2d", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "616ffdf66aaad6a72fc546dabf42eed87e2a99e97b09cbd92b10cc180d02ed74"},
"search_parser": {:git, "https://github.com/FloatingGhost/pleroma-contrib-search-parser.git", "08971a81e68686f9ac465cfb6661d51c5e4e1e7f", [ref: "08971a81e68686f9ac465cfb6661d51c5e4e1e7f"]},
- "sleeplocks": {:hex, :sleeplocks, "1.1.2", "d45aa1c5513da48c888715e3381211c859af34bee9b8290490e10c90bb6ff0ca", [:rebar3], [], "hexpm", "9fe5d048c5b781d6305c1a3a0f40bb3dfc06f49bf40571f3d2d0c57eaa7f59a5"},
+ "sleeplocks": {:hex, :sleeplocks, "1.1.3", "96a86460cc33b435c7310dbd27ec82ca2c1f24ae38e34f8edde97f756503441a", [:rebar3], [], "hexpm", "d3b3958552e6eb16f463921e70ae7c767519ef8f5be46d7696cc1ed649421321"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
"statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},
"sweet_xml": {:hex, :sweet_xml, "0.7.4", "a8b7e1ce7ecd775c7e8a65d501bc2cd933bff3a9c41ab763f5105688ef485d08", [:mix], [], "hexpm", "e7c4b0bdbf460c928234951def54fe87edf1a170f6896675443279e2dbeba167"},
diff --git a/priv/gettext/ca/LC_MESSAGES/errors.po b/priv/gettext/ca/LC_MESSAGES/errors.po
index 098635493..c92edc093 100644
--- a/priv/gettext/ca/LC_MESSAGES/errors.po
+++ b/priv/gettext/ca/LC_MESSAGES/errors.po
@@ -3,14 +3,16 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-07-29 11:37+0000\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: Automatically generated\n"
-"Language-Team: none\n"
+"PO-Revision-Date: 2024-04-13 22:55+0000\n"
+"Last-Translator: fadelkon \n"
+"Language-Team: Catalan \n"
"Language: ca\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: Translate Toolkit 3.7.1\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 4.18.2\n"
# # This file is a PO Template file.
# #
@@ -610,46 +612,46 @@ msgstr "El teu compte espera aprovació."
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:262
#, elixir-autogen, elixir-format
msgid "File is too large"
-msgstr ""
+msgstr "L'arxiu és massa gran"
#: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:37
#: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:48
#: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:59
-#, elixir-autogen, elixir-format, fuzzy
+#, elixir-autogen, elixir-format
msgid "Hashtag not found"
-msgstr "Llista no trobada"
+msgstr "No s'ha trobat l'etiqueta"
#: lib/pleroma/web/common_api/activity_draft.ex:144
#, elixir-autogen, elixir-format
msgid "Invalid language"
-msgstr ""
+msgstr "Llengua incompatible"
#: lib/pleroma/web/o_auth/o_auth_controller.ex:218
-#, elixir-autogen, elixir-format, fuzzy
+#, elixir-autogen, elixir-format
msgid "This action is outside of authorized scopes"
msgstr "Aquesta acció és fora dels àmbits autoritzats"
#: lib/pleroma/web/common_api/activity_draft.ex:129
#, elixir-autogen, elixir-format
msgid "You can only quote public or unlisted statuses"
-msgstr ""
+msgstr "Només pots citar publicacions desllistades o públiques"
#: lib/pleroma/web/common_api/activity_draft.ex:126
#, elixir-autogen, elixir-format
msgid "You can't quote a status that doesn't exist"
-msgstr ""
+msgstr "No pots citar una publicació que no existeix"
#: lib/pleroma/web/embed_controller.ex:35
#, elixir-autogen, elixir-format
msgid "Federated posts cannot be embedded"
-msgstr ""
+msgstr "No es poden incrustar publicacions federades"
#: lib/pleroma/web/embed_controller.ex:38
#, elixir-autogen, elixir-format
msgid "Not authorized to view this post"
-msgstr ""
+msgstr "No tens permís per veure aquesta publicació"
#: lib/pleroma/web/embed_controller.ex:32
-#, elixir-autogen, elixir-format, fuzzy
+#, elixir-autogen, elixir-format
msgid "Post not found"
-msgstr "Llista no trobada"
+msgstr "No s'ha trobat la publicació"
diff --git a/priv/gettext/ca/LC_MESSAGES/static_pages.po b/priv/gettext/ca/LC_MESSAGES/static_pages.po
index e3f563353..674980d62 100644
--- a/priv/gettext/ca/LC_MESSAGES/static_pages.po
+++ b/priv/gettext/ca/LC_MESSAGES/static_pages.po
@@ -3,8 +3,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-07-28 09:15+0000\n"
-"PO-Revision-Date: 2022-07-30 21:58+0000\n"
-"Last-Translator: sola \n"
+"PO-Revision-Date: 2024-04-13 22:55+0000\n"
+"Last-Translator: fadelkon \n"
"Language-Team: Catalan \n"
"Language: ca\n"
@@ -12,7 +12,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
-"X-Generator: Weblate 4.13.1\n"
+"X-Generator: Weblate 4.18.2\n"
## This file is a PO Template file.
##
@@ -531,25 +531,25 @@ msgid "Welcome to %{instance_name}!"
msgstr "Benvingut a %{instance_name}!"
#: lib/pleroma/emails/user_email.ex:368
-#, elixir-autogen, elixir-format, fuzzy
+#, elixir-autogen, elixir-format
msgctxt "account archive email body - admin requested"
msgid "Admin @%{admin_nickname} requested a full backup of your Akkoma account. It's ready for download:
\n%{download_url}
\n"
msgstr ""
-"L'Administrador @%{admin_nickname} ha sol·licitat una copia de seguretat "
-"completa del teu compte Akkoma. Està preparat per a descarrega:
\n"
+"L'Administrador @%{admin_nickname} ha sol·licitat una còpia de seguretat "
+"completa del teu compte Akkoma. Està preparada per descarregar:
\n"
"%{download_url}
\n"
#: lib/pleroma/emails/user_email.ex:356
-#, elixir-autogen, elixir-format, fuzzy
+#, elixir-autogen, elixir-format
msgctxt "account archive email body - self-requested"
msgid "You requested a full backup of your Akkoma account. It's ready for download:
\n%{download_url}
\n"
msgstr ""
-"Has sol·licitat una copia de seguretat completa del teu compte Akkoma. "
-"Està llest per a descarrega:
\n"
+"Has sol·licitat una còpia de seguretat completa del teu compte Akkoma. Ja "
+"la pots descarregar:
\n"
"%{download_url}
\n"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:41
-#, elixir-autogen, elixir-format, fuzzy
+#, elixir-autogen, elixir-format
msgctxt "oauth register page title"
msgid "This is your first visit! Please enter your Akkoma handle."
msgstr ""
@@ -560,22 +560,22 @@ msgstr ""
#, elixir-autogen, elixir-format
msgctxt "remote follow error message - unknown error"
msgid "Something went wrong."
-msgstr ""
+msgstr "Hi ha hagut algun problema."
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:67
#, elixir-autogen, elixir-format
msgctxt "remote follow error message - user not found"
msgid "Could not find user"
-msgstr ""
+msgstr "No s'ha trobat l'usuari/a/ï"
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:8
#, elixir-autogen, elixir-format
msgctxt "status interact authorization button"
msgid "Interact"
-msgstr ""
+msgstr "Interacciona"
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:2
-#, elixir-autogen, elixir-format, fuzzy
+#, elixir-autogen, elixir-format
msgctxt "status interact error"
msgid "Error: %{error}"
msgstr "Error: %{error}"
@@ -584,33 +584,33 @@ msgstr "Error: %{error}"
#, elixir-autogen, elixir-format
msgctxt "status interact error message - status not found"
msgid "Could not find status"
-msgstr ""
+msgstr "No s'ha trobat l'estat"
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:144
#, elixir-autogen, elixir-format
msgctxt "status interact error message - unknown error"
msgid "Something went wrong."
-msgstr ""
+msgstr "Hi ha hagut algun problema."
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "status interact header"
msgid "Interacting with %{nickname}'s %{status_link}"
-msgstr ""
+msgstr "S'està interactuant amb %{status_link} de %{nickname}"
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "status interact header - status link text"
msgid "status"
-msgstr ""
+msgstr "estat"
#: lib/pleroma/emails/user_email.ex:119
-#, elixir-autogen, elixir-format, fuzzy
+#, elixir-autogen, elixir-format
msgctxt "user invitation email body"
msgid "You are invited to %{instance_name} \n%{inviter_name} invites you to join %{instance_name}, an instance of Akkoma federated social networking platform.
\nClick the following link to register: accept invitation .
\n"
msgstr ""
-"Has estat invitat a %{instance_name} \n"
-"%{inviter_name} t'invita a unir-te a %{instance_name}, una instància de "
-"la plataforma de xarxa social federada Akkoma.
\n"
+"T'han convidat a %{instance_name} \n"
+"%{inviter_name} t'anima a unir-te a %{instance_name}, una instància de la "
+"plataforma de xarxa social federada Akkoma.
\n"
"Clica el següent enllaç per a registrar-te: accepta invitació .
\n"
+"\">accepta la invitació.
\n"
diff --git a/priv/gettext/pt/LC_MESSAGES/config_descriptions.po b/priv/gettext/pt/LC_MESSAGES/config_descriptions.po
new file mode 100644
index 000000000..f4cca250a
--- /dev/null
+++ b/priv/gettext/pt/LC_MESSAGES/config_descriptions.po
@@ -0,0 +1,6449 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2024-03-19 01:09+0000\n"
+"PO-Revision-Date: 2024-03-21 02:39+0000\n"
+"Last-Translator: Jammer Lammer \n"
+"Language-Team: Portuguese \n"
+"Language: pt\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n > 1;\n"
+"X-Generator: Weblate 4.18.2\n"
+
+## This file is a PO Template file.
+##
+## "msgid"s here are often extracted from source code.
+## Add new translations manually only if they're dynamic
+## translations that can't be statically extracted.
+##
+## Run "mix gettext.extract" to bring this file up to
+## date. Leave "msgstr"s empty as changing them here has no
+## effect: edit them in PO (.po) files instead.
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config description at :logger"
+msgid "Logger-related settings"
+msgstr "Configurações relacionadas ao registrador (logger)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config description at :mime"
+msgid "Mime Types settings"
+msgstr "Configurações dos Tipos Mime"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config description at :pleroma"
+msgid "Allows setting a token that can be used to authenticate requests with admin privileges without a normal user account token. Append the `admin_token` parameter to requests to utilize it. (Please reconsider using HTTP Basic Auth or OAuth-based authentication if possible)"
+msgstr ""
+"Permite configurar um token que pode ser usado para autenticar requisições "
+"com privilégios de administrador sem um token de usuário comum. Anexe um "
+"parâmetro `admin_token`na requisição para utilizá-la. (Por favor, considere "
+"usar um autenticação HTTP básica ou uma autenticação OAuth se possível)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config description at :pleroma"
+msgid "Authenticator"
+msgstr "Autenticador"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config label at :cors_plug"
+msgid "CORS plug config"
+msgstr "configuração do plug CORS"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config label at :logger"
+msgid "Logger"
+msgstr "Logger"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config label at :mime"
+msgid "Mime Types"
+msgstr "Tipos Mime"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config label at :pleroma"
+msgid "Pleroma Admin Token"
+msgstr "Token de Administrador Akkoma"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config label at :pleroma"
+msgid "Pleroma Authenticator"
+msgstr "Autenticador Akkoma"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config description at :logger-:console"
+msgid "Console logger settings"
+msgstr "Configurações do console do logger"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config description at :logger-:ex_syslogger"
+msgid "ExSyslogger-related settings"
+msgstr "Configurações relacionadas ao ExSyslogger"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config description at :pleroma-:activitypub"
+msgid "ActivityPub-related settings"
+msgstr "Configurações relacionadas ao ActivityPub"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config description at :pleroma-:assets"
+msgid "This section configures assets to be used with various frontends. Currently the only option relates to mascots on the mastodon frontend"
+msgstr ""
+"Essa seção configura recursos que podem ser usados com vários frontends. No "
+"momento a única opção está relacionada a mascotes do frontend do Mastodon"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config description at :pleroma-:auth"
+msgid "Authentication / authorization settings"
+msgstr "Autenticação / configurações de autorização"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config description at :pleroma-:email_notifications"
+msgid "Email notifications settings"
+msgstr "Configurações de notificação por email"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config description at :pleroma-:features"
+msgid "Customizable features"
+msgstr "Recursos customizáveis"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config description at :pleroma-:feed"
+msgid "Configure feed rendering"
+msgstr "Configurar renderização do feed"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config description at :pleroma-:frontends"
+msgid "Installed frontends management"
+msgstr "Gerenciador de frontends instalados"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config description at :pleroma-:http"
+msgid "HTTP settings"
+msgstr "Configurações do HTTP"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config description at :pleroma-:http_security"
+msgid "HTTP security settings"
+msgstr "Configurações de segurança do HTTP"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config description at :pleroma-:instance"
+msgid "Instance-related settings"
+msgstr "Configurações relacionadas à instância"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config description at :pleroma-:instances_favicons"
+msgid "Control favicons for instances"
+msgstr "Controle de favicons por instâncias"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config description at :pleroma-:ldap"
+msgid "Use LDAP for user authentication. When a user logs in to the Pleroma instance, the name and password will be verified by trying to authenticate (bind) to a LDAP server. If a user exists in the LDAP directory but there is no account with the same name yet on the Pleroma instance then a new Pleroma account will be created with the same name as the LDAP user name."
+msgstr ""
+"Use LDAP para autenticação de usuários. Quando um usuário inicia uma sessão "
+"em uma instância do Akkoma, o nome e a senha serão verificados ao tentar se "
+"autenticar (ligar) a um servidor LDAP. Se um usuário existe no diretório "
+"LDAP, mas ainda não há uma conta com o mesmo nome na instância do Akkoma, "
+"então uma nova conta Akkoma será criada com o mesmo nome do usuário no "
+"servidor LDAP."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config description at :pleroma-:majic_pool"
+msgid "Majic/libmagic configuration"
+msgstr "Configurações do majic/libmagic"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config description at :pleroma-:manifest"
+msgid "This section describe PWA manifest instance-specific values. Currently this option relate only for MastoFE."
+msgstr ""
+"Esse seção descreve valores específicos da instância do manifesto PWA. No "
+"momento essa opção refere-se apenas ao MastoFE."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config description at :pleroma-:media_preview_proxy"
+msgid "Media preview proxy"
+msgstr "Proxy de pré-visualização de mídia."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config description at :pleroma-:media_proxy"
+msgid "Media proxy"
+msgstr "Proxy de Mídia"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config description at :pleroma-:modules"
+msgid "Custom Runtime Modules"
+msgstr "Módulos Customizados de tempo de execução"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config description at :pleroma-:mrf"
+msgid "General MRF settings"
+msgstr "Configurações gerais do MRF"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config description at :pleroma-:mrf_activity_expiration"
+msgid "Adds automatic expiration to all local activities"
+msgstr "Adiciona automaticamente validade para todas as atividades locais"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config description at :pleroma-:mrf_follow_bot"
+msgid "Automatically follows newly discovered accounts."
+msgstr "Automaticamente segue contas recentemente descobertas."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_hashtag"
+msgid "Reject, TWKN-remove or Set-Sensitive messsages with specific hashtags (without the leading #)\n\nNote: This MRF Policy is always enabled, if you want to disable it you have to set empty lists.\n"
+msgstr ""
+"Reject, TWKN-remove or Set-Sensitive messsages with specific hashtags ("
+"without the leading #)\n"
+"\n"
+"Note: This MRF Policy is always enabled, if you want to disable it you have "
+"to set empty lists.\n"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_hellthread"
+msgid "Block messages with excessive user mentions"
+msgstr "Block messages with excessive user mentions"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_keyword"
+msgid "Reject or Word-Replace messages matching a keyword or [Regex](https://hexdocs.pm/elixir/Regex.html)."
+msgstr ""
+"Reject or Word-Replace messages matching a keyword or [Regex](https://hexdocs"
+".pm/elixir/Regex.html)."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_mention"
+msgid "Block messages which mention a specific user"
+msgstr "Block messages which mention a specific user"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_normalize_markup"
+msgid "MRF NormalizeMarkup settings. Scrub configured hypertext markup."
+msgstr "MRF NormalizeMarkup settings. Scrub configured hypertext markup."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_object_age"
+msgid "Rejects or delists posts based on their timestamp deviance from your server's clock."
+msgstr ""
+"Rejects or delists posts based on their timestamp deviance from your "
+"server's clock."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_rejectnonpublic"
+msgid "RejectNonPublic drops posts with non-public visibility settings."
+msgstr "RejectNonPublic drops posts with non-public visibility settings."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_simple"
+msgid "Simple ingress policies"
+msgstr "Simple ingress policies"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_steal_emoji"
+msgid "Steals emojis from selected instances when it sees them."
+msgstr "Steals emojis from selected instances when it sees them."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_subchain"
+msgid "This policy processes messages through an alternate pipeline when a given message matches certain criteria. All criteria are configured as a map of regular expressions to lists of policy modules."
+msgstr ""
+"This policy processes messages through an alternate pipeline when a given "
+"message matches certain criteria. All criteria are configured as a map of "
+"regular expressions to lists of policy modules."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_vocabulary"
+msgid "Filter messages which belong to certain activity vocabularies"
+msgstr "Filter messages which belong to certain activity vocabularies"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:oauth2"
+msgid "Configure OAuth 2 provider capabilities"
+msgstr "Configure OAuth 2 provider capabilities"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:populate_hashtags_table"
+msgid "`populate_hashtags_table` background migration settings"
+msgstr "`populate_hashtags_table` background migration settings"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:rate_limit"
+msgid "Rate limit settings. This is an advanced feature enabled only for :authentication by default."
+msgstr ""
+"Rate limit settings. This is an advanced feature enabled only for :"
+"authentication by default."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:rich_media"
+msgid "If enabled the instance will parse metadata from attached links to generate link previews"
+msgstr ""
+"If enabled the instance will parse metadata from attached links to generate "
+"link previews"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:static_fe"
+msgid "Render profiles and posts using server-generated HTML that is viewable without using JavaScript"
+msgstr ""
+"Render profiles and posts using server-generated HTML that is viewable "
+"without using JavaScript"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:streamer"
+msgid "Settings for notifications streamer"
+msgstr "Settings for notifications streamer"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:uri_schemes"
+msgid "URI schemes related settings"
+msgstr "URI schemes related settings"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:web_cache_ttl"
+msgid "The expiration time for the web responses cache. Values should be in milliseconds or `nil` to disable expiration."
+msgstr ""
+"The expiration time for the web responses cache. Values should be in "
+"milliseconds or `nil` to disable expiration."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:welcome"
+msgid "Welcome messages settings"
+msgstr "Welcome messages settings"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:workers"
+msgid "Includes custom worker options not interpretable directly by `Oban`"
+msgstr "Includes custom worker options not interpretable directly by `Oban`"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-ConcurrentLimiter"
+msgid "Limits configuration for background tasks."
+msgstr "Limits configuration for background tasks."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Oban"
+msgid "[Oban](https://github.com/sorentwo/oban) asynchronous job processor configuration."
+msgstr ""
+"[Oban](https://github.com/sorentwo/oban) asynchronous job processor "
+"configuration."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Captcha"
+msgid "Captcha-related settings"
+msgstr "Captcha-related settings"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Captcha.Kocaptcha"
+msgid "Kocaptcha is a very simple captcha service with a single API endpoint, the source code is here: https://github.com/koto-bank/kocaptcha. The default endpoint (https://captcha.kotobank.ch) is hosted by the developer."
+msgstr ""
+"Kocaptcha is a very simple captcha service with a single API endpoint, the "
+"source code is here: https://github.com/koto-bank/kocaptcha. The default "
+"endpoint (https://captcha.kotobank.ch) is hosted by the developer."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Emails.Mailer"
+msgid "Mailer-related settings"
+msgstr "Mailer-related settings"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Emails.NewUsersDigestEmail"
+msgid "New users admin email digest"
+msgstr "New users admin email digest"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Emails.UserEmail"
+msgid "Email template settings"
+msgstr "Email template settings"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Formatter"
+msgid "Configuration for Pleroma's link formatter which parses mentions, hashtags, and URLs."
+msgstr ""
+"Configuration for Pleroma's link formatter which parses mentions, hashtags, "
+"and URLs."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.ScheduledActivity"
+msgid "Scheduled activities settings"
+msgstr "Scheduled activities settings"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Upload"
+msgid "Upload general settings"
+msgstr "Upload general settings"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format
+msgctxt "config description at :pleroma-Pleroma.Upload.Filter.AnonymizeFilename"
+msgid "Filter replaces the filename of the upload"
+msgstr "Filtro troca o nome do arquivo de upload"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Upload.Filter.Mogrify"
+msgid "Uploads mogrify filter settings"
+msgstr "Uploads mogrify filter settings"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Uploaders.Local"
+msgid "Local uploader-related settings"
+msgstr "Local uploader-related settings"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Uploaders.S3"
+msgid "S3 uploader-related settings"
+msgstr "S3 uploader-related settings"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.User.Backup"
+msgid "Account Backup"
+msgstr "Account Backup"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http"
+msgid "HTTP invalidate settings"
+msgstr "HTTP invalidate settings"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Script"
+msgid "Invalidation script settings"
+msgstr "Invalidation script settings"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Web.Metadata"
+msgid "Metadata-related settings"
+msgstr "Metadata-related settings"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Web.Plugs.RemoteIp"
+msgid "`Pleroma.Web.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.\n**If your instance is not behind at least one reverse proxy, you should not enable this plug.**\n"
+msgstr ""
+"`Pleroma.Web.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git."
+"pleroma.social/pleroma/remote_ip) but with runtime configuration.\n"
+"**If your instance is not behind at least one reverse proxy, you should not "
+"enable this plug.**\n"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Web.Preload"
+msgid "Preload-related settings"
+msgstr "Preload-related settings"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Workers.PurgeExpiredActivity"
+msgid "Expired activities settings"
+msgstr "Expired activities settings"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :web_push_encryption-:vapid_details"
+msgid "Web Push Notifications configuration. You can use the mix task mix web_push.gen.keypair to generate it."
+msgstr ""
+"Web Push Notifications configuration. You can use the mix task mix "
+"web_push.gen.keypair to generate it."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :ex_aws-:s3"
+msgid "S3"
+msgstr "S3"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :logger-:console"
+msgid "Console Logger"
+msgstr "Console Logger"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :logger-:ex_syslogger"
+msgid "ExSyslogger"
+msgstr "ExSyslogger"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:activitypub"
+msgid "ActivityPub"
+msgstr "ActivityPub"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:assets"
+msgid "Assets"
+msgstr "Assets"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:auth"
+msgid "Auth"
+msgstr "Auth"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:email_notifications"
+msgid "Email notifications"
+msgstr "Email notifications"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:emoji"
+msgid "Emoji"
+msgstr "Emoji"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:features"
+msgid "Features"
+msgstr "Features"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:feed"
+msgid "Feed"
+msgstr "Feed"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations"
+msgid "Frontend configurations"
+msgstr "Frontend configurations"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontends"
+msgid "Frontends"
+msgstr "Frontends"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:http"
+msgid "HTTP"
+msgstr "HTTP"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:http_security"
+msgid "HTTP security"
+msgstr "HTTP security"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance"
+msgid "Instance"
+msgstr "Instance"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instances_favicons"
+msgid "Instances favicons"
+msgstr "Instances favicons"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:ldap"
+msgid "LDAP"
+msgstr "LDAP"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:majic_pool"
+msgid "Majic pool"
+msgstr "Majic pool"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:manifest"
+msgid "Manifest"
+msgstr "Manifest"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:markup"
+msgid "Markup Settings"
+msgstr "Markup Settings"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:media_preview_proxy"
+msgid "Media preview proxy"
+msgstr "Media preview proxy"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:media_proxy"
+msgid "Media proxy"
+msgstr "Media proxy"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:modules"
+msgid "Modules"
+msgstr "Modules"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf"
+msgid "MRF"
+msgstr "MRF"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_activity_expiration"
+msgid "MRF Activity Expiration Policy"
+msgstr "MRF Activity Expiration Policy"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_follow_bot"
+msgid "MRF FollowBot Policy"
+msgstr "MRF FollowBot Policy"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_hashtag"
+msgid "MRF Hashtag"
+msgstr "MRF Hashtag"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_hellthread"
+msgid "MRF Hellthread"
+msgstr "MRF Hellthread"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_keyword"
+msgid "MRF Keyword"
+msgstr "MRF Keyword"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_mention"
+msgid "MRF Mention"
+msgstr "MRF Mention"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_normalize_markup"
+msgid "MRF Normalize Markup"
+msgstr "MRF Normalize Markup"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_object_age"
+msgid "MRF Object Age"
+msgstr "MRF Object Age"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_rejectnonpublic"
+msgid "MRF Reject Non Public"
+msgstr "MRF Reject Non Public"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_simple"
+msgid "MRF Simple"
+msgstr "MRF Simple"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_steal_emoji"
+msgid "MRF Emojis"
+msgstr "MRF Emojis"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_subchain"
+msgid "MRF Subchain"
+msgstr "MRF Subchain"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_vocabulary"
+msgid "MRF Vocabulary"
+msgstr "MRF Vocabulary"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:oauth2"
+msgid "OAuth2"
+msgstr "OAuth2"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:populate_hashtags_table"
+msgid "Populate hashtags table"
+msgstr "Populate hashtags table"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:rate_limit"
+msgid "Rate limit"
+msgstr "Rate limit"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:restrict_unauthenticated"
+msgid "Restrict Unauthenticated"
+msgstr "Restrict Unauthenticated"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:rich_media"
+msgid "Rich media"
+msgstr "Rich media"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:static_fe"
+msgid "Static FE"
+msgstr "Static FE"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:streamer"
+msgid "Streamer"
+msgstr "Streamer"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:uri_schemes"
+msgid "URI Schemes"
+msgstr "URI Schemes"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:user"
+msgid "User"
+msgstr "User"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:web_cache_ttl"
+msgid "Web cache TTL"
+msgstr "Web cache TTL"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:welcome"
+msgid "Welcome"
+msgstr "Welcome"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:workers"
+msgid "Workers"
+msgstr "Workers"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-ConcurrentLimiter"
+msgid "ConcurrentLimiter"
+msgstr "ConcurrentLimiter"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Oban"
+msgid "Oban"
+msgstr "Oban"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Captcha"
+msgid "Pleroma.Captcha"
+msgstr "Pleroma.Captcha"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Captcha.Kocaptcha"
+msgid "Pleroma.Captcha.Kocaptcha"
+msgstr "Pleroma.Captcha.Kocaptcha"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer"
+msgid "Pleroma.Emails.Mailer"
+msgstr "Pleroma.Emails.Mailer"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.NewUsersDigestEmail"
+msgid "Pleroma.Emails.NewUsersDigestEmail"
+msgstr "Pleroma.Emails.NewUsersDigestEmail"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.UserEmail"
+msgid "Pleroma.Emails.UserEmail"
+msgstr "Pleroma.Emails.UserEmail"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Formatter"
+msgid "Linkify"
+msgstr "Linkify"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.ScheduledActivity"
+msgid "Pleroma.ScheduledActivity"
+msgstr "Pleroma.ScheduledActivity"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Upload"
+msgid "Pleroma.Upload"
+msgstr "Pleroma.Upload"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Upload.Filter.AnonymizeFilename"
+msgid "Pleroma.Upload.Filter.AnonymizeFilename"
+msgstr "Pleroma.Upload.Filter.AnonymizeFilename"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Upload.Filter.Mogrify"
+msgid "Pleroma.Upload.Filter.Mogrify"
+msgstr "Pleroma.Upload.Filter.Mogrify"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Uploaders.Local"
+msgid "Pleroma.Uploaders.Local"
+msgstr "Pleroma.Uploaders.Local"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Uploaders.S3"
+msgid "Pleroma.Uploaders.S3"
+msgstr "Pleroma.Uploaders.S3"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.User"
+msgid "Pleroma.User"
+msgstr "Pleroma.User"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.User.Backup"
+msgid "Pleroma.User.Backup"
+msgstr "Pleroma.User.Backup"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Web.ApiSpec.CastAndValidate"
+msgid "Pleroma.Web.ApiSpec.CastAndValidate"
+msgstr "Pleroma.Web.ApiSpec.CastAndValidate"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http"
+msgid "Pleroma.Web.MediaProxy.Invalidation.Http"
+msgstr "Pleroma.Web.MediaProxy.Invalidation.Http"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Script"
+msgid "Pleroma.Web.MediaProxy.Invalidation.Script"
+msgstr "Pleroma.Web.MediaProxy.Invalidation.Script"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Web.Metadata"
+msgid "Pleroma.Web.Metadata"
+msgstr "Pleroma.Web.Metadata"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Web.Plugs.RemoteIp"
+msgid "Pleroma.Web.Plugs.RemoteIp"
+msgstr "Pleroma.Web.Plugs.RemoteIp"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Web.Preload"
+msgid "Pleroma.Web.Preload"
+msgstr "Pleroma.Web.Preload"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Workers.PurgeExpiredActivity"
+msgid "Pleroma.Workers.PurgeExpiredActivity"
+msgstr "Pleroma.Workers.PurgeExpiredActivity"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :web_push_encryption-:vapid_details"
+msgid "Vapid Details"
+msgstr "Vapid Details"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :ex_aws-:s3 > :access_key_id"
+msgid "S3 access key ID"
+msgstr "S3 access key ID"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :ex_aws-:s3 > :host"
+msgid "S3 host"
+msgstr "S3 host"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :ex_aws-:s3 > :region"
+msgid "S3 region (for AWS)"
+msgstr "S3 region (for AWS)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :ex_aws-:s3 > :secret_access_key"
+msgid "Secret access key"
+msgstr "Secret access key"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :logger > :backends"
+msgid "Where logs will be sent, :console - send logs to stdout, { ExSyslogger, :ex_syslogger } - to syslog, Quack.Logger - to Slack."
+msgstr ""
+"Where logs will be sent, :console - send logs to stdout, { ExSyslogger, :"
+"ex_syslogger } - to syslog, Quack.Logger - to Slack."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :logger-:console > :format"
+msgid "Default: \"$date $time [$level] $levelpad$node $metadata $message\""
+msgstr "Default: \"$date $time [$level] $levelpad$node $metadata $message\""
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :logger-:console > :level"
+msgid "Log level"
+msgstr "Log level"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :logger-:ex_syslogger > :format"
+msgid "Default: \"$date $time [$level] $levelpad$node $metadata $message\""
+msgstr "Default: \"$date $time [$level] $levelpad$node $metadata $message\""
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :logger-:ex_syslogger > :ident"
+msgid "A string that's prepended to every message, and is typically set to the app name"
+msgstr ""
+"A string that's prepended to every message, and is typically set to the app "
+"name"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :logger-:ex_syslogger > :level"
+msgid "Log level"
+msgstr "Log level"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma > :admin_token"
+msgid "Admin token"
+msgstr "Admin token"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:activitypub > :blockers_visible"
+msgid "Whether a user can see someone who has blocked them"
+msgstr "Whether a user can see someone who has blocked them"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:activitypub > :follow_handshake_timeout"
+msgid "Following handshake timeout"
+msgstr "Following handshake timeout"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:activitypub > :note_replies_output_limit"
+msgid "The number of Note replies' URIs to be included with outgoing federation (`5` to match Mastodon hardcoded value, `0` to disable the output)"
+msgstr ""
+"The number of Note replies' URIs to be included with outgoing federation (`5`"
+" to match Mastodon hardcoded value, `0` to disable the output)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:activitypub > :outgoing_blocks"
+msgid "Whether to federate blocks to other instances"
+msgstr "Whether to federate blocks to other instances"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:activitypub > :sign_object_fetches"
+msgid "Sign object fetches with HTTP signatures"
+msgstr "Sign object fetches with HTTP signatures"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:activitypub > :unfollow_blocked"
+msgid "Whether blocks result in people getting unfollowed"
+msgstr "Whether blocks result in people getting unfollowed"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:assets > :default_mascot"
+msgid "This will be used as the default mascot on MastoFE. Default: `:pleroma_fox_tan`"
+msgstr ""
+"This will be used as the default mascot on MastoFE. Default: "
+"`:pleroma_fox_tan`"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:assets > :default_user_avatar"
+msgid "URL of the default user avatar"
+msgstr "URL of the default user avatar"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:assets > :mascots"
+msgid "Keyword of mascots, each element must contain both an URL and a mime_type key"
+msgstr ""
+"Keyword of mascots, each element must contain both an URL and a mime_type key"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:auth > :auth_template"
+msgid "Authentication form template. By default it's `show.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/show.html.ee`."
+msgstr ""
+"Authentication form template. By default it's `show.html` which corresponds "
+"to `lib/pleroma/web/templates/o_auth/o_auth/show.html.ee`."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:auth > :enforce_oauth_admin_scope_usage"
+msgid "OAuth admin scope requirement toggle. If enabled, admin actions explicitly demand admin OAuth scope(s) presence in OAuth token (client app must support admin scopes). If disabled and token doesn't have admin scope(s), `is_admin` user flag grants access to admin-specific actions."
+msgstr ""
+"OAuth admin scope requirement toggle. If enabled, admin actions explicitly "
+"demand admin OAuth scope(s) presence in OAuth token (client app must support "
+"admin scopes). If disabled and token doesn't have admin scope(s), `is_admin` "
+"user flag grants access to admin-specific actions."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:auth > :oauth_consumer_strategies"
+msgid "The list of enabled OAuth consumer strategies. By default it's set by OAUTH_CONSUMER_STRATEGIES environment variable. Each entry in this space-delimited string should be of format \"strategy\" or \"strategy:dependency\" (e.g. twitter or keycloak:ueberauth_keycloak_strategy in case dependency is named differently than ueberauth_)."
+msgstr ""
+"The list of enabled OAuth consumer strategies. By default it's set by "
+"OAUTH_CONSUMER_STRATEGIES environment variable. Each entry in this space-"
+"delimited string should be of format \"strategy\" or \"strategy:dependency\" "
+"(e.g. twitter or keycloak:ueberauth_keycloak_strategy in case dependency is "
+"named differently than ueberauth_)."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:auth > :oauth_consumer_template"
+msgid "OAuth consumer mode authentication form template. By default it's `consumer.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex`."
+msgstr ""
+"OAuth consumer mode authentication form template. By default it's `consumer."
+"html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/consumer."
+"html.eex`."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:email_notifications > :digest"
+msgid "emails of \"what you've missed\" for users who have been inactive for a while"
+msgstr ""
+"emails of \"what you've missed\" for users who have been inactive for a while"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:email_notifications > :digest > :active"
+msgid "Globally enable or disable digest emails"
+msgstr "Globally enable or disable digest emails"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:email_notifications > :digest > :inactivity_threshold"
+msgid "Minimum user inactivity threshold"
+msgstr "Minimum user inactivity threshold"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:email_notifications > :digest > :interval"
+msgid "Minimum interval between digest emails to one user"
+msgstr "Minimum interval between digest emails to one user"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:email_notifications > :digest > :schedule"
+msgid "When to send digest email, in crontab format. \"0 0 0\" is the default, meaning \"once a week at midnight on Sunday morning\"."
+msgstr ""
+"When to send digest email, in crontab format. \"0 0 0\" is the default, "
+"meaning \"once a week at midnight on Sunday morning\"."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:emoji > :default_manifest"
+msgid "Location of the JSON-manifest. This manifest contains information about the emoji-packs you can download. Currently only one manifest can be added (no arrays)."
+msgstr ""
+"Location of the JSON-manifest. This manifest contains information about the "
+"emoji-packs you can download. Currently only one manifest can be added (no "
+"arrays)."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:emoji > :groups"
+msgid "Emojis are ordered in groups (tags). This is an array of key-value pairs where the key is the group name and the value is the location or array of locations. * can be used as a wildcard."
+msgstr ""
+"Emojis are ordered in groups (tags). This is an array of key-value pairs "
+"where the key is the group name and the value is the location or array of "
+"locations. * can be used as a wildcard."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:emoji > :pack_extensions"
+msgid "A list of file extensions for emojis, when no emoji.txt for a pack is present"
+msgstr ""
+"A list of file extensions for emojis, when no emoji.txt for a pack is present"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:emoji > :shortcode_globs"
+msgid "Location of custom emoji files. * can be used as a wildcard."
+msgstr "Location of custom emoji files. * can be used as a wildcard."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:features > :improved_hashtag_timeline"
+msgid "Setting to force toggle / force disable improved hashtags timeline. `:enabled` forces hashtags to be fetched from `hashtags` table for hashtags timeline. `:disabled` forces object-embedded hashtags to be used (slower). Keep it `:auto` for automatic behaviour (it is auto-set to `:enabled` [unless overridden] when HashtagsTableMigrator completes)."
+msgstr ""
+"Setting to force toggle / force disable improved hashtags timeline. "
+"`:enabled` forces hashtags to be fetched from `hashtags` table for hashtags "
+"timeline. `:disabled` forces object-embedded hashtags to be used (slower). "
+"Keep it `:auto` for automatic behaviour (it is auto-set to `:enabled` ["
+"unless overridden] when HashtagsTableMigrator completes)."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:feed > :post_title"
+msgid "Configure title rendering"
+msgstr "Configure title rendering"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:feed > :post_title > :max_length"
+msgid "Maximum number of characters before truncating title"
+msgstr "Maximum number of characters before truncating title"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:feed > :post_title > :omission"
+msgid "Replacement which will be used after truncating string"
+msgstr "Replacement which will be used after truncating string"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe"
+msgid "Settings for Pleroma FE"
+msgstr "Settings for Pleroma FE"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :alwaysShowSubjectInput"
+msgid "When disabled, auto-hide the subject field if it's empty"
+msgstr "When disabled, auto-hide the subject field if it's empty"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :background"
+msgid "URL of the background, unless viewing a user profile with a background that is set"
+msgstr ""
+"URL of the background, unless viewing a user profile with a background that "
+"is set"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :collapseMessageWithSubject"
+msgid "When a message has a subject (aka Content Warning), collapse it by default"
+msgstr ""
+"When a message has a subject (aka Content Warning), collapse it by default"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :greentext"
+msgid "Enables green text on lines prefixed with the > character"
+msgstr "Enables green text on lines prefixed with the > character"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :hideFilteredStatuses"
+msgid "Hides filtered statuses from timelines"
+msgstr "Hides filtered statuses from timelines"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :hideMutedPosts"
+msgid "Hides muted statuses from timelines"
+msgstr "Hides muted statuses from timelines"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :hidePostStats"
+msgid "Hide notices statistics (repeats, favorites, ...)"
+msgstr "Hide notices statistics (repeats, favorites, ...)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :hideUserStats"
+msgid "Hide profile statistics (posts, posts per day, followers, followings, ...)"
+msgstr ""
+"Hide profile statistics (posts, posts per day, followers, followings, ...)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :logo"
+msgid "URL of the logo, defaults to Pleroma's logo"
+msgstr "URL of the logo, defaults to Pleroma's logo"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :logoMargin"
+msgid "Allows you to adjust vertical margins between logo boundary and navbar borders. The idea is that to have logo's image without any extra margins and instead adjust them to your need in layout."
+msgstr ""
+"Allows you to adjust vertical margins between logo boundary and navbar "
+"borders. The idea is that to have logo's image without any extra margins and "
+"instead adjust them to your need in layout."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :logoMask"
+msgid "By default it assumes logo used will be monochrome with alpha channel to be compatible with both light and dark themes. If you want a colorful logo you must disable logoMask."
+msgstr ""
+"By default it assumes logo used will be monochrome with alpha channel to be "
+"compatible with both light and dark themes. If you want a colorful logo you "
+"must disable logoMask."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :nsfwCensorImage"
+msgid "URL of the image to use for hiding NSFW media attachments in the timeline"
+msgstr ""
+"URL of the image to use for hiding NSFW media attachments in the timeline"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :postContentType"
+msgid "Default post formatting option"
+msgstr "Default post formatting option"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :redirectRootLogin"
+msgid "Relative URL which indicates where to redirect when a user is logged in"
+msgstr "Relative URL which indicates where to redirect when a user is logged in"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :redirectRootNoLogin"
+msgid "Relative URL which indicates where to redirect when a user isn't logged in"
+msgstr ""
+"Relative URL which indicates where to redirect when a user isn't logged in"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :scopeCopy"
+msgid "Copy the scope (private/unlisted/public) in replies to posts by default"
+msgstr "Copy the scope (private/unlisted/public) in replies to posts by default"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :showFeaturesPanel"
+msgid "Enables panel displaying functionality of the instance on the About page"
+msgstr ""
+"Enables panel displaying functionality of the instance on the About page"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :showInstanceSpecificPanel"
+msgid "Whether to show the instance's custom panel"
+msgstr "Whether to show the instance's custom panel"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :sidebarRight"
+msgid "Change alignment of sidebar and panels to the right"
+msgstr "Change alignment of sidebar and panels to the right"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :subjectLineBehavior"
+msgid "Allows changing the default behaviour of subject lines in replies.\n `email`: copy and preprend re:, as in email,\n `masto`: copy verbatim, as in Mastodon,\n `noop`: don't copy the subject."
+msgstr ""
+"Allows changing the default behaviour of subject lines in replies.\n"
+" `email`: copy and preprend re:, as in email,\n"
+" `masto`: copy verbatim, as in Mastodon,\n"
+" `noop`: don't copy the subject."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :theme"
+msgid "Which theme to use. Available themes are defined in styles.json"
+msgstr "Which theme to use. Available themes are defined in styles.json"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontends > :admin"
+msgid "Admin frontend"
+msgstr "Admin frontend"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontends > :admin > name"
+msgid "Name of the installed frontend. Valid config must include both `Name` and `Reference` values."
+msgstr ""
+"Name of the installed frontend. Valid config must include both `Name` and "
+"`Reference` values."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontends > :admin > ref"
+msgid "Reference of the installed frontend to be used. Valid config must include both `Name` and `Reference` values."
+msgstr ""
+"Reference of the installed frontend to be used. Valid config must include "
+"both `Name` and `Reference` values."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontends > :available"
+msgid "A map containing available frontends and parameters for their installation."
+msgstr ""
+"A map containing available frontends and parameters for their installation."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontends > :available > build_dir"
+msgid "The directory inside the zip file "
+msgstr "The directory inside the zip file "
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontends > :available > build_url"
+msgid "Either an url to a zip file containing the frontend or a template to build it by inserting the `ref`. The string `${ref}` will be replaced by the configured `ref`."
+msgstr ""
+"Either an url to a zip file containing the frontend or a template to build "
+"it by inserting the `ref`. The string `${ref}` will be replaced by the "
+"configured `ref`."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontends > :available > custom-http-headers"
+msgid "The custom HTTP headers for the frontend"
+msgstr "The custom HTTP headers for the frontend"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontends > :available > git"
+msgid "URL of the git repository of the frontend"
+msgstr "URL of the git repository of the frontend"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontends > :available > name"
+msgid "Name of the frontend."
+msgstr "Name of the frontend."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontends > :available > ref"
+msgid "Reference of the frontend to be used."
+msgstr "Reference of the frontend to be used."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontends > :primary"
+msgid "Primary frontend, the one that is served for all pages by default"
+msgstr "Primary frontend, the one that is served for all pages by default"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontends > :primary > name"
+msgid "Name of the installed frontend. Valid config must include both `Name` and `Reference` values."
+msgstr ""
+"Name of the installed frontend. Valid config must include both `Name` and "
+"`Reference` values."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontends > :primary > ref"
+msgid "Reference of the installed frontend to be used. Valid config must include both `Name` and `Reference` values."
+msgstr ""
+"Reference of the installed frontend to be used. Valid config must include "
+"both `Name` and `Reference` values."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:http > :adapter"
+msgid "Adapter specific options"
+msgstr "Adapter specific options"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:http > :adapter > :ssl_options"
+msgid "SSL options for HTTP adapter"
+msgstr "SSL options for HTTP adapter"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:http > :adapter > :ssl_options > :versions"
+msgid "List of TLS version to use"
+msgstr "List of TLS version to use"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:http > :user_agent"
+msgid "What user agent to use. Must be a string or an atom `:default`. Default value is `:default`."
+msgstr ""
+"What user agent to use. Must be a string or an atom `:default`. Default "
+"value is `:default`."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:http_security > :enabled"
+msgid "Whether the managed content security policy is enabled"
+msgstr "Whether the managed content security policy is enabled"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:http_security > :referrer_policy"
+msgid "The referrer policy to use, either \"same-origin\" or \"no-referrer\""
+msgstr "The referrer policy to use, either \"same-origin\" or \"no-referrer\""
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:http_security > :report_uri"
+msgid "Adds the specified URL to report-uri and report-to group in CSP header"
+msgstr "Adds the specified URL to report-uri and report-to group in CSP header"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:http_security > :sts"
+msgid "Whether to additionally send a Strict-Transport-Security header"
+msgstr "Whether to additionally send a Strict-Transport-Security header"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:http_security > :sts_max_age"
+msgid "The maximum age for the Strict-Transport-Security header if sent"
+msgstr "The maximum age for the Strict-Transport-Security header if sent"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :account_activation_required"
+msgid "Require users to confirm their emails before signing in"
+msgstr "Require users to confirm their emails before signing in"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :account_approval_required"
+msgid "Require users to be manually approved by an admin before signing in"
+msgstr "Require users to be manually approved by an admin before signing in"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :account_field_name_length"
+msgid "An account field name maximum length. Default: 512."
+msgstr "An account field name maximum length. Default: 512."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :account_field_value_length"
+msgid "An account field value maximum length. Default: 2048."
+msgstr "An account field value maximum length. Default: 2048."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :allow_relay"
+msgid "Permits remote instances to subscribe to all public posts of your instance. (Important!) This may increase the visibility of your instance."
+msgstr ""
+"Permits remote instances to subscribe to all public posts of your instance. "
+"(Important!) This may increase the visibility of your instance."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :allowed_post_formats"
+msgid "MIME-type list of formats allowed to be posted (transformed into HTML)"
+msgstr "MIME-type list of formats allowed to be posted (transformed into HTML)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :attachment_links"
+msgid "Enable to automatically add attachment link text to statuses"
+msgstr "Enable to automatically add attachment link text to statuses"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :autofollowed_nicknames"
+msgid "Set to nicknames of (local) users that every new user should automatically follow"
+msgstr ""
+"Set to nicknames of (local) users that every new user should automatically "
+"follow"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :autofollowing_nicknames"
+msgid "Set to nicknames of (local) users that automatically follows every newly registered user"
+msgstr ""
+"Set to nicknames of (local) users that automatically follows every newly "
+"registered user"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :avatar_upload_limit"
+msgid "File size limit of user's profile avatars"
+msgstr "File size limit of user's profile avatars"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :background_upload_limit"
+msgid "File size limit of user's profile backgrounds"
+msgstr "File size limit of user's profile backgrounds"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :banner_upload_limit"
+msgid "File size limit of user's profile banners"
+msgstr "File size limit of user's profile banners"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :cleanup_attachments"
+msgid "Enable to remove associated attachments when status is removed.\nThis will not affect duplicates and attachments without status.\nEnabling this will increase load to database when deleting statuses on larger instances.\n"
+msgstr ""
+"Enable to remove associated attachments when status is removed.\n"
+"This will not affect duplicates and attachments without status.\n"
+"Enabling this will increase load to database when deleting statuses on "
+"larger instances.\n"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :description"
+msgid "The instance's description. It can be seen in nodeinfo and `/api/v1/instance`"
+msgstr ""
+"The instance's description. It can be seen in nodeinfo and `/api/v1/instance`"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :email"
+msgid "Email used to reach an Administrator/Moderator of the instance"
+msgstr "Email used to reach an Administrator/Moderator of the instance"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :extended_nickname_format"
+msgid "Enable to use extended local nicknames format (allows underscores/dashes). This will break federation with older software for theses nicknames."
+msgstr ""
+"Enable to use extended local nicknames format (allows underscores/dashes). "
+"This will break federation with older software for theses nicknames."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :external_user_synchronization"
+msgid "Enabling following/followers counters synchronization for external users"
+msgstr ""
+"Enabling following/followers counters synchronization for external users"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :federating"
+msgid "Enable federation with other instances"
+msgstr "Enable federation with other instances"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :federation_incoming_replies_max_depth"
+msgid "Max. depth of reply-to and reply activities fetching on incoming federation, to prevent out-of-memory situations while fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes."
+msgstr ""
+"Max. depth of reply-to and reply activities fetching on incoming federation, "
+"to prevent out-of-memory situations while fetching very long threads. If set "
+"to `nil`, threads of any depth will be fetched. Lower this value if you "
+"experience out-of-memory crashes."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :federation_reachability_timeout_days"
+msgid "Timeout (in days) of each external federation target being unreachable prior to pausing federating to it"
+msgstr ""
+"Timeout (in days) of each external federation target being unreachable prior "
+"to pausing federating to it"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :instance_thumbnail"
+msgid "The instance thumbnail can be any image that represents your instance and is used by some apps or services when they display information about your instance."
+msgstr ""
+"The instance thumbnail can be any image that represents your instance and is "
+"used by some apps or services when they display information about your "
+"instance."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :invites_enabled"
+msgid "Enable user invitations for admins (depends on `registrations_open` being disabled)"
+msgstr ""
+"Enable user invitations for admins (depends on `registrations_open` being "
+"disabled)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :limit"
+msgid "Posts character limit (CW/Subject included in the counter)"
+msgstr "Posts character limit (CW/Subject included in the counter)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :limit_to_local_content"
+msgid "Limit unauthenticated users to search for local statutes and users only. Default: `:unauthenticated`."
+msgstr ""
+"Limit unauthenticated users to search for local statutes and users only. "
+"Default: `:unauthenticated`."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :max_account_fields"
+msgid "The maximum number of custom fields in the user profile. Default: 10."
+msgstr "The maximum number of custom fields in the user profile. Default: 10."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :max_pinned_statuses"
+msgid "The maximum number of pinned statuses. 0 will disable the feature."
+msgstr "The maximum number of pinned statuses. 0 will disable the feature."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :max_remote_account_fields"
+msgid "The maximum number of custom fields in the remote user profile. Default: 20."
+msgstr ""
+"The maximum number of custom fields in the remote user profile. Default: 20."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :max_report_comment_size"
+msgid "The maximum size of the report comment. Default: 1000."
+msgstr "The maximum size of the report comment. Default: 1000."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :multi_factor_authentication"
+msgid "Multi-factor authentication settings"
+msgstr "Multi-factor authentication settings"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :multi_factor_authentication > :backup_codes"
+msgid "MFA backup codes settings"
+msgstr "MFA backup codes settings"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :multi_factor_authentication > :backup_codes > :length"
+msgid "Determines the length of backup one-time pass-codes, in characters. Defaults to 16 characters."
+msgstr ""
+"Determines the length of backup one-time pass-codes, in characters. Defaults "
+"to 16 characters."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :multi_factor_authentication > :backup_codes > :number"
+msgid "Number of backup codes to generate."
+msgstr "Number of backup codes to generate."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :multi_factor_authentication > :totp"
+msgid "TOTP settings"
+msgstr "TOTP settings"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :multi_factor_authentication > :totp > :digits"
+msgid "Determines the length of a one-time pass-code, in characters. Defaults to 6 characters."
+msgstr ""
+"Determines the length of a one-time pass-code, in characters. Defaults to 6 "
+"characters."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :multi_factor_authentication > :totp > :period"
+msgid "A period for which the TOTP code will be valid, in seconds. Defaults to 30 seconds."
+msgstr ""
+"A period for which the TOTP code will be valid, in seconds. Defaults to 30 "
+"seconds."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :name"
+msgid "Name of the instance"
+msgstr "Name of the instance"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :notify_email"
+msgid "Envelope FROM address for mail sent via Pleroma"
+msgstr "Envelope FROM address for mail sent via Pleroma"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :poll_limits"
+msgid "A map with poll limits for local polls"
+msgstr "A map with poll limits for local polls"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :poll_limits > :max_expiration"
+msgid "Maximum expiration time (in seconds)"
+msgstr "Maximum expiration time (in seconds)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :poll_limits > :max_option_chars"
+msgid "Maximum number of characters per option"
+msgstr "Maximum number of characters per option"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :poll_limits > :max_options"
+msgid "Maximum number of options"
+msgstr "Maximum number of options"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :poll_limits > :min_expiration"
+msgid "Minimum expiration time (in seconds)"
+msgstr "Minimum expiration time (in seconds)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :profile_directory"
+msgid "Enable profile directory."
+msgstr "Enable profile directory."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :registration_reason_length"
+msgid "Maximum registration reason length. Default: 500."
+msgstr "Maximum registration reason length. Default: 500."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :registrations_open"
+msgid "Enable registrations for anyone. Invitations require this setting to be disabled."
+msgstr ""
+"Enable registrations for anyone. Invitations require this setting to be "
+"disabled."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :remote_limit"
+msgid "Hard character limit beyond which remote posts will be dropped"
+msgstr "Hard character limit beyond which remote posts will be dropped"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :remote_post_retention_days"
+msgid "The default amount of days to retain remote posts when pruning the database"
+msgstr ""
+"The default amount of days to retain remote posts when pruning the database"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :safe_dm_mentions"
+msgid "If enabled, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. \"@admin please keep an eye on @bad_actor\"). Default: disabled"
+msgstr ""
+"If enabled, only mentions at the beginning of a post will be used to address "
+"people in direct messages. This is to prevent accidental mentioning of "
+"people when talking about them (e.g. \"@admin please keep an eye on @"
+"bad_actor\"). Default: disabled"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :show_reactions"
+msgid "Let favourites and emoji reactions be viewed through the API."
+msgstr "Let favourites and emoji reactions be viewed through the API."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :skip_thread_containment"
+msgid "Skip filtering out broken threads. Default: enabled."
+msgstr "Skip filtering out broken threads. Default: enabled."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :static_dir"
+msgid "Instance static directory"
+msgstr "Instance static directory"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :upload_limit"
+msgid "File size limit of uploads (except for avatar, background, banner)"
+msgstr "File size limit of uploads (except for avatar, background, banner)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :user_bio_length"
+msgid "A user bio maximum length. Default: 5000."
+msgstr "A user bio maximum length. Default: 5000."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :user_name_length"
+msgid "A user name maximum length. Default: 100."
+msgstr "A user name maximum length. Default: 100."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instances_favicons > :enabled"
+msgid "Allow/disallow displaying and getting instances favicons"
+msgstr "Allow/disallow displaying and getting instances favicons"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:ldap > :base"
+msgid "LDAP base, e.g. \"dc=example,dc=com\""
+msgstr "LDAP base, e.g. \"dc=example,dc=com\""
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:ldap > :enabled"
+msgid "Enables LDAP authentication"
+msgstr "Enables LDAP authentication"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:ldap > :host"
+msgid "LDAP server hostname"
+msgstr "LDAP server hostname"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:ldap > :port"
+msgid "LDAP port, e.g. 389 or 636"
+msgstr "LDAP port, e.g. 389 or 636"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:ldap > :ssl"
+msgid "Enable to use SSL, usually implies the port 636"
+msgstr "Enable to use SSL, usually implies the port 636"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:ldap > :sslopts"
+msgid "Additional SSL options"
+msgstr "Additional SSL options"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:ldap > :sslopts > :cacertfile"
+msgid "Path to file with PEM encoded cacerts"
+msgstr "Path to file with PEM encoded cacerts"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:ldap > :sslopts > :verify"
+msgid "Type of cert verification"
+msgstr "Type of cert verification"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:ldap > :tls"
+msgid "Enable to use STARTTLS, usually implies the port 389"
+msgstr "Enable to use STARTTLS, usually implies the port 389"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:ldap > :tlsopts"
+msgid "Additional TLS options"
+msgstr "Additional TLS options"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:ldap > :tlsopts > :cacertfile"
+msgid "Path to file with PEM encoded cacerts"
+msgstr "Path to file with PEM encoded cacerts"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:ldap > :tlsopts > :verify"
+msgid "Type of cert verification"
+msgstr "Type of cert verification"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:ldap > :uid"
+msgid "LDAP attribute name to authenticate the user, e.g. when \"cn\", the filter will be \"cn=username,base\""
+msgstr ""
+"LDAP attribute name to authenticate the user, e.g. when \"cn\", the filter "
+"will be \"cn=username,base\""
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:majic_pool > :size"
+msgid "Number of majic workers to start."
+msgstr "Number of majic workers to start."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:manifest > :icons"
+msgid "Describe the icons of the app"
+msgstr "Describe the icons of the app"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:markup > :scrub_policy"
+msgid "Module names are shortened (removed leading `Pleroma.HTML.` part), but on adding custom module you need to use full name."
+msgstr ""
+"Module names are shortened (removed leading `Pleroma.HTML.` part), but on "
+"adding custom module you need to use full name."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:media_preview_proxy > :enabled"
+msgid "Enables proxying of remote media preview to the instance's proxy. Requires enabled media proxy."
+msgstr ""
+"Enables proxying of remote media preview to the instance's proxy. Requires "
+"enabled media proxy."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:media_preview_proxy > :image_quality"
+msgid "Quality of the output. Ranges from 0 (min quality) to 100 (max quality)."
+msgstr ""
+"Quality of the output. Ranges from 0 (min quality) to 100 (max quality)."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:media_preview_proxy > :min_content_length"
+msgid "Min content length (in bytes) to perform preview. Media smaller in size will be served without thumbnailing."
+msgstr ""
+"Min content length (in bytes) to perform preview. Media smaller in size will "
+"be served without thumbnailing."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:media_preview_proxy > :thumbnail_max_height"
+msgid "Max height of preview thumbnail for images (video preview always has original dimensions)."
+msgstr ""
+"Max height of preview thumbnail for images (video preview always has "
+"original dimensions)."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:media_preview_proxy > :thumbnail_max_width"
+msgid "Max width of preview thumbnail for images (video preview always has original dimensions)."
+msgstr ""
+"Max width of preview thumbnail for images (video preview always has original "
+"dimensions)."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:media_proxy > :base_url"
+msgid "The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts."
+msgstr ""
+"The base URL to access a user-uploaded file. Useful when you want to proxy "
+"the media files via another host/CDN fronts."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:media_proxy > :enabled"
+msgid "Enables proxying of remote media via the instance's proxy"
+msgstr "Enables proxying of remote media via the instance's proxy"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:media_proxy > :invalidation > :enabled"
+msgid "Enables media cache object invalidation."
+msgstr "Enables media cache object invalidation."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:media_proxy > :invalidation > :provider"
+msgid "Module which will be used to purge objects from the cache."
+msgstr "Module which will be used to purge objects from the cache."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:media_proxy > :proxy_opts"
+msgid "Internal Pleroma.ReverseProxy settings"
+msgstr "Internal Pleroma.ReverseProxy settings"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:media_proxy > :proxy_opts > :max_body_length"
+msgid "Maximum file size (in bytes) allowed through the Pleroma MediaProxy cache."
+msgstr ""
+"Maximum file size (in bytes) allowed through the Pleroma MediaProxy cache."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:media_proxy > :proxy_opts > :max_read_duration"
+msgid "Timeout (in milliseconds) of GET request to the remote URI."
+msgstr "Timeout (in milliseconds) of GET request to the remote URI."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:media_proxy > :proxy_opts > :redirect_on_failure"
+msgid "Redirects the client to the origin server upon encountering HTTP errors.\n\nNote that files larger than Max Body Length will trigger an error. (e.g., Peertube videos)\n\n\n**WARNING:** This setting will allow larger files to be accessed, but exposes the\n\nIP addresses of your users to the other servers, bypassing the MediaProxy.\n"
+msgstr ""
+"Redirects the client to the origin server upon encountering HTTP errors.\n"
+"\n"
+"Note that files larger than Max Body Length will trigger an error. (e.g., "
+"Peertube videos)\n"
+"\n"
+"\n"
+"**WARNING:** This setting will allow larger files to be accessed, but "
+"exposes the\n"
+"\n"
+"IP addresses of your users to the other servers, bypassing the MediaProxy.\n"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:modules > :runtime_dir"
+msgid "A path to custom Elixir modules (such as MRF policies)."
+msgstr "A path to custom Elixir modules (such as MRF policies)."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf > :policies"
+msgid "A list of MRF policies enabled. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name."
+msgstr ""
+"A list of MRF policies enabled. Module names are shortened (removed leading "
+"`Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need "
+"to use full name."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf > :transparency"
+msgid "Make the content of your Message Rewrite Facility settings public (via nodeinfo)"
+msgstr ""
+"Make the content of your Message Rewrite Facility settings public (via "
+"nodeinfo)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf > :transparency_exclusions"
+msgid "Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value. You can also provide a reason for excluding these instance names. The instances and reasons won't be publicly disclosed."
+msgstr ""
+"Exclude specific instance names from MRF transparency. The use of the "
+"exclusions feature will be disclosed in nodeinfo as a boolean value. You can "
+"also provide a reason for excluding these instance names. The instances and "
+"reasons won't be publicly disclosed."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_activity_expiration > :days"
+msgid "Default global expiration time for all local activities (in days)"
+msgstr "Default global expiration time for all local activities (in days)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_follow_bot > :follower_nickname"
+msgid "The name of the bot account to use for following newly discovered users."
+msgstr ""
+"The name of the bot account to use for following newly discovered users."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_hashtag > :federated_timeline_removal"
+msgid "A list of hashtags which result in message being removed from federated timelines (a.k.a unlisted)."
+msgstr ""
+"A list of hashtags which result in message being removed from federated "
+"timelines (a.k.a unlisted)."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_hashtag > :reject"
+msgid "A list of hashtags which result in message being rejected."
+msgstr "A list of hashtags which result in message being rejected."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_hashtag > :sensitive"
+msgid "A list of hashtags which result in message being set as sensitive (a.k.a NSFW/R-18)"
+msgstr ""
+"A list of hashtags which result in message being set as sensitive (a.k.a "
+"NSFW/R-18)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_hellthread > :delist_threshold"
+msgid "Number of mentioned users after which the message gets removed from timelines anddisables notifications. Set to 0 to disable."
+msgstr ""
+"Number of mentioned users after which the message gets removed from "
+"timelines anddisables notifications. Set to 0 to disable."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_hellthread > :reject_threshold"
+msgid "Number of mentioned users after which the messaged gets rejected. Set to 0 to disable."
+msgstr ""
+"Number of mentioned users after which the messaged gets rejected. Set to 0 "
+"to disable."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_keyword > :federated_timeline_removal"
+msgid " A list of patterns which result in message being removed from federated timelines (a.k.a unlisted).\n\n Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.\n"
+msgstr ""
+" A list of patterns which result in message being removed from federated "
+"timelines (a.k.a unlisted).\n"
+"\n"
+" Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex."
+"html) in the format of `~r/PATTERN/`.\n"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_keyword > :reject"
+msgid " A list of patterns which result in message being rejected.\n\n Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.\n"
+msgstr ""
+" A list of patterns which result in message being rejected.\n"
+"\n"
+" Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex."
+"html) in the format of `~r/PATTERN/`.\n"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_keyword > :replace"
+msgid " **Pattern**: a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.\n\n **Replacement**: a string. Leaving the field empty is permitted.\n"
+msgstr ""
+" **Pattern**: a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in "
+"the format of `~r/PATTERN/`.\n"
+"\n"
+" **Replacement**: a string. Leaving the field empty is permitted.\n"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_mention > :actors"
+msgid "A list of actors for which any post mentioning them will be dropped"
+msgstr "A list of actors for which any post mentioning them will be dropped"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_object_age > :actions"
+msgid "A list of actions to apply to the post. `:delist` removes the post from public timelines; `:strip_followers` removes followers from the ActivityPub recipient list ensuring they won't be delivered to home timelines; `:reject` rejects the message entirely"
+msgstr ""
+"A list of actions to apply to the post. `:delist` removes the post from "
+"public timelines; `:strip_followers` removes followers from the ActivityPub "
+"recipient list ensuring they won't be delivered to home timelines; `:reject` "
+"rejects the message entirely"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_object_age > :threshold"
+msgid "Required age (in seconds) of a post before actions are taken."
+msgstr "Required age (in seconds) of a post before actions are taken."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_rejectnonpublic > :allow_direct"
+msgid "Whether to allow direct messages"
+msgstr "Whether to allow direct messages"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_rejectnonpublic > :allow_followersonly"
+msgid "Whether to allow followers-only posts"
+msgstr "Whether to allow followers-only posts"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_simple > :accept"
+msgid "List of instances to only accept activities from (except deletes) and the reason for doing so"
+msgstr ""
+"List of instances to only accept activities from (except deletes) and the "
+"reason for doing so"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_simple > :avatar_removal"
+msgid "List of instances to strip avatars from and the reason for doing so"
+msgstr "List of instances to strip avatars from and the reason for doing so"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_simple > :banner_removal"
+msgid "List of instances to strip banners from and the reason for doing so"
+msgstr "List of instances to strip banners from and the reason for doing so"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_simple > :federated_timeline_removal"
+msgid "List of instances to remove from the Federated (aka The Whole Known Network) Timeline and the reason for doing so"
+msgstr ""
+"List of instances to remove from the Federated (aka The Whole Known Network) "
+"Timeline and the reason for doing so"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_simple > :followers_only"
+msgid "Force posts from the given instances to be visible by followers only and the reason for doing so"
+msgstr ""
+"Force posts from the given instances to be visible by followers only and the "
+"reason for doing so"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_simple > :media_nsfw"
+msgid "List of instances to tag all media as NSFW (sensitive) from and the reason for doing so"
+msgstr ""
+"List of instances to tag all media as NSFW (sensitive) from and the reason "
+"for doing so"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_simple > :media_removal"
+msgid "List of instances to strip media attachments from and the reason for doing so"
+msgstr ""
+"List of instances to strip media attachments from and the reason for doing so"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_simple > :reject"
+msgid "List of instances to reject activities from (except deletes) and the reason for doing so"
+msgstr ""
+"List of instances to reject activities from (except deletes) and the reason "
+"for doing so"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_simple > :reject_deletes"
+msgid "List of instances to reject deletions from and the reason for doing so"
+msgstr "List of instances to reject deletions from and the reason for doing so"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_simple > :report_removal"
+msgid "List of instances to reject reports from and the reason for doing so"
+msgstr "List of instances to reject reports from and the reason for doing so"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_steal_emoji > :hosts"
+msgid "List of hosts to steal emojis from"
+msgstr "List of hosts to steal emojis from"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_steal_emoji > :rejected_shortcodes"
+msgid " A list of patterns or matches to reject shortcodes with.\n\n Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.\n"
+msgstr ""
+" A list of patterns or matches to reject shortcodes with.\n"
+"\n"
+" Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex."
+"html) in the format of `~r/PATTERN/`.\n"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_steal_emoji > :size_limit"
+msgid "File size limit (in bytes), checked before an emoji is saved to the disk"
+msgstr ""
+"File size limit (in bytes), checked before an emoji is saved to the disk"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_subchain > :match_actor"
+msgid "Matches a series of regular expressions against the actor field"
+msgstr "Matches a series of regular expressions against the actor field"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_vocabulary > :accept"
+msgid "A list of ActivityStreams terms to accept. If empty, all supported messages are accepted."
+msgstr ""
+"A list of ActivityStreams terms to accept. If empty, all supported messages "
+"are accepted."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_vocabulary > :reject"
+msgid "A list of ActivityStreams terms to reject. If empty, no messages are rejected."
+msgstr ""
+"A list of ActivityStreams terms to reject. If empty, no messages are "
+"rejected."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:oauth2 > :clean_expired_tokens"
+msgid "Enable a background job to clean expired OAuth tokens. Default: disabled."
+msgstr ""
+"Enable a background job to clean expired OAuth tokens. Default: disabled."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:oauth2 > :issue_new_refresh_token"
+msgid "Keeps old refresh token or generate new refresh token when to obtain an access token"
+msgstr ""
+"Keeps old refresh token or generate new refresh token when to obtain an "
+"access token"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:oauth2 > :token_expires_in"
+msgid "The lifetime in seconds of the access token"
+msgstr "The lifetime in seconds of the access token"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:populate_hashtags_table > :fault_rate_allowance"
+msgid "Max accepted rate of objects that failed in the migration. Any value from 0.0 which tolerates no errors to 1.0 which will enable the feature even if hashtags transfer failed for all records."
+msgstr ""
+"Max accepted rate of objects that failed in the migration. Any value from "
+"0.0 which tolerates no errors to 1.0 which will enable the feature even if "
+"hashtags transfer failed for all records."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:populate_hashtags_table > :sleep_interval_ms"
+msgid "Sleep interval between each chunk of processed records in order to decrease the load on the system (defaults to 0 and should be keep default on most instances)."
+msgstr ""
+"Sleep interval between each chunk of processed records in order to decrease "
+"the load on the system (defaults to 0 and should be keep default on most "
+"instances)."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:rate_limit > :app_account_creation"
+msgid "For registering user accounts from the same IP address"
+msgstr "For registering user accounts from the same IP address"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:rate_limit > :authentication"
+msgid "For authentication create / password check / user existence check requests"
+msgstr ""
+"For authentication create / password check / user existence check requests"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:rate_limit > :relation_id_action"
+msgid "For actions on relation with a specific user (follow, unfollow)"
+msgstr "For actions on relation with a specific user (follow, unfollow)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:rate_limit > :relations_actions"
+msgid "For actions on relationships with all users (follow, unfollow)"
+msgstr "For actions on relationships with all users (follow, unfollow)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:rate_limit > :search"
+msgid "For the search requests (account & status search etc.)"
+msgstr "For the search requests (account & status search etc.)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:rate_limit > :status_id_action"
+msgid "For fav / unfav or reblog / unreblog actions on the same status by the same user"
+msgstr ""
+"For fav / unfav or reblog / unreblog actions on the same status by the same "
+"user"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:rate_limit > :statuses_actions"
+msgid "For create / delete / fav / unfav / reblog / unreblog actions on any statuses"
+msgstr ""
+"For create / delete / fav / unfav / reblog / unreblog actions on any statuses"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:rate_limit > :timeline"
+msgid "For requests to timelines (each timeline has it's own limiter)"
+msgstr "For requests to timelines (each timeline has it's own limiter)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:restrict_unauthenticated > :profiles"
+msgid "Settings for user profiles."
+msgstr "Settings for user profiles."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:restrict_unauthenticated > :timelines"
+msgid "Settings for public and federated timelines."
+msgstr "Settings for public and federated timelines."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:rich_media > :enabled"
+msgid "Enables RichMedia parsing of URLs"
+msgstr "Enables RichMedia parsing of URLs"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:rich_media > :failure_backoff"
+msgid "Amount of milliseconds after request failure, during which the request will not be retried."
+msgstr ""
+"Amount of milliseconds after request failure, during which the request will "
+"not be retried."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:rich_media > :ignore_hosts"
+msgid "List of hosts which will be ignored by the metadata parser"
+msgstr "List of hosts which will be ignored by the metadata parser"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:rich_media > :ignore_tld"
+msgid "List TLDs (top-level domains) which will ignore for parse metadata"
+msgstr "List TLDs (top-level domains) which will ignore for parse metadata"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:rich_media > :parsers"
+msgid "List of Rich Media parsers. Module names are shortened (removed leading `Pleroma.Web.RichMedia.Parsers.` part), but on adding custom module you need to use full name."
+msgstr ""
+"List of Rich Media parsers. Module names are shortened (removed leading "
+"`Pleroma.Web.RichMedia.Parsers.` part), but on adding custom module you need "
+"to use full name."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:rich_media > :ttl_setters"
+msgid "List of rich media TTL setters. Module names are shortened (removed leading `Pleroma.Web.RichMedia.Parser.` part), but on adding custom module you need to use full name."
+msgstr ""
+"List of rich media TTL setters. Module names are shortened (removed leading "
+"`Pleroma.Web.RichMedia.Parser.` part), but on adding custom module you need "
+"to use full name."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:static_fe > :enabled"
+msgid "Enables the rendering of static HTML. Default: disabled."
+msgstr "Enables the rendering of static HTML. Default: disabled."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:streamer > :overflow_workers"
+msgid "Maximum number of workers created if pool is empty"
+msgstr "Maximum number of workers created if pool is empty"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:streamer > :workers"
+msgid "Number of workers to send notifications"
+msgstr "Number of workers to send notifications"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:uri_schemes > :valid_schemes"
+msgid "List of the scheme part that is considered valid to be an URL"
+msgstr "List of the scheme part that is considered valid to be an URL"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:web_cache_ttl > :activity_pub"
+msgid "Activity pub routes (except question activities). Default: `nil` (no expiration)."
+msgstr ""
+"Activity pub routes (except question activities). Default: `nil` (no "
+"expiration)."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:web_cache_ttl > :activity_pub_question"
+msgid "Activity pub routes (question activities). Default: `30_000` (30 seconds)."
+msgstr ""
+"Activity pub routes (question activities). Default: `30_000` (30 seconds)."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:welcome > :direct_message > :enabled"
+msgid "Enables sending a direct message to newly registered users"
+msgstr "Enables sending a direct message to newly registered users"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:welcome > :direct_message > :message"
+msgid "A message that will be sent to newly registered users"
+msgstr "A message that will be sent to newly registered users"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:welcome > :direct_message > :sender_nickname"
+msgid "The nickname of the local user that sends a welcome message"
+msgstr "The nickname of the local user that sends a welcome message"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:welcome > :email > :enabled"
+msgid "Enables sending an email to newly registered users"
+msgstr "Enables sending an email to newly registered users"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:welcome > :email > :html"
+msgid "HTML content of the welcome email. EEX template with user and instance_name variables can be used."
+msgstr ""
+"HTML content of the welcome email. EEX template with user and instance_name "
+"variables can be used."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:welcome > :email > :sender"
+msgid "Email address and/or nickname that will be used to send the welcome email."
+msgstr ""
+"Email address and/or nickname that will be used to send the welcome email."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:welcome > :email > :subject"
+msgid "Subject of the welcome email. EEX template with user and instance_name variables can be used."
+msgstr ""
+"Subject of the welcome email. EEX template with user and instance_name "
+"variables can be used."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:welcome > :email > :text"
+msgid "Text content of the welcome email. EEX template with user and instance_name variables can be used."
+msgstr ""
+"Text content of the welcome email. EEX template with user and instance_name "
+"variables can be used."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:workers > :retries"
+msgid "Max retry attempts for failed jobs, per `Oban` queue"
+msgstr "Max retry attempts for failed jobs, per `Oban` queue"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-ConcurrentLimiter > Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy"
+msgid "Concurrent limits configuration for MediaProxyWarmingPolicy."
+msgstr "Concurrent limits configuration for MediaProxyWarmingPolicy."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-ConcurrentLimiter > Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy > :max_running"
+msgid "Max running concurrently jobs."
+msgstr "Max running concurrently jobs."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-ConcurrentLimiter > Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy > :max_waiting"
+msgid "Max waiting jobs."
+msgstr "Max waiting jobs."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-ConcurrentLimiter > Pleroma.Web.RichMedia.Helpers"
+msgid "Concurrent limits configuration for getting RichMedia for activities."
+msgstr "Concurrent limits configuration for getting RichMedia for activities."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-ConcurrentLimiter > Pleroma.Web.RichMedia.Helpers > :max_running"
+msgid "Max running concurrently jobs."
+msgstr "Max running concurrently jobs."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-ConcurrentLimiter > Pleroma.Web.RichMedia.Helpers > :max_waiting"
+msgid "Max waiting jobs."
+msgstr "Max waiting jobs."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Oban > :crontab"
+msgid "Settings for cron background jobs"
+msgstr "Settings for cron background jobs"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Oban > :log"
+msgid "Logs verbose mode"
+msgstr "Logs verbose mode"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Oban > :queues"
+msgid "Background jobs queues (keys: queues, values: max numbers of concurrent jobs)"
+msgstr ""
+"Background jobs queues (keys: queues, values: max numbers of concurrent jobs)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Oban > :queues > :activity_expiration"
+msgid "Activity expiration queue"
+msgstr "Activity expiration queue"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Oban > :queues > :attachments_cleanup"
+msgid "Attachment deletion queue"
+msgstr "Attachment deletion queue"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Oban > :queues > :background"
+msgid "Background queue"
+msgstr "Background queue"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Oban > :queues > :backup"
+msgid "Backup queue"
+msgstr "Backup queue"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Oban > :queues > :federator_incoming"
+msgid "Incoming federation queue"
+msgstr "Incoming federation queue"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Oban > :queues > :federator_outgoing"
+msgid "Outgoing federation queue"
+msgstr "Outgoing federation queue"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Oban > :queues > :mailer"
+msgid "Email sender queue, see Pleroma.Emails.Mailer"
+msgstr "Email sender queue, see Pleroma.Emails.Mailer"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Oban > :queues > :scheduled_activities"
+msgid "Scheduled activities queue, see Pleroma.ScheduledActivities"
+msgstr "Scheduled activities queue, see Pleroma.ScheduledActivities"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Oban > :queues > :transmogrifier"
+msgid "Transmogrifier queue"
+msgstr "Transmogrifier queue"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Oban > :queues > :web_push"
+msgid "Web push notifications queue"
+msgstr "Web push notifications queue"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Captcha > :enabled"
+msgid "Whether the captcha should be shown on registration"
+msgstr "Whether the captcha should be shown on registration"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Captcha > :method"
+msgid "The method/service to use for captcha"
+msgstr "The method/service to use for captcha"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Captcha > :seconds_valid"
+msgid "The time in seconds for which the captcha is valid"
+msgstr "The time in seconds for which the captcha is valid"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Captcha.Kocaptcha > :endpoint"
+msgid "The kocaptcha endpoint to use"
+msgstr "The kocaptcha endpoint to use"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Emails.Mailer > :adapter"
+msgid "One of the mail adapters listed in [Swoosh documentation](https://hexdocs.pm/swoosh/Swoosh.html#module-adapters)"
+msgstr ""
+"One of the mail adapters listed in [Swoosh documentation](https://hexdocs.pm/"
+"swoosh/Swoosh.html#module-adapters)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:auth"
+msgid "SMTP AUTH enforcement mode"
+msgstr "SMTP AUTH enforcement mode"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:password"
+msgid "SMTP AUTH password"
+msgstr "SMTP AUTH password"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:port"
+msgid "SMTP port"
+msgstr "SMTP port"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:relay"
+msgid "Hostname or IP address"
+msgstr "Hostname or IP address"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:retries"
+msgid "SMTP temporary (4xx) error retries"
+msgstr "SMTP temporary (4xx) error retries"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:ssl"
+msgid "Use Implicit SSL/TLS. e.g. port 465"
+msgstr "Use Implicit SSL/TLS. e.g. port 465"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:tls"
+msgid "Explicit TLS (STARTTLS) enforcement mode"
+msgstr "Explicit TLS (STARTTLS) enforcement mode"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:username"
+msgid "SMTP AUTH username"
+msgstr "SMTP AUTH username"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Emails.NewUsersDigestEmail > :enabled"
+msgid "Enables new users admin digest email when `true`"
+msgstr "Enables new users admin digest email when `true`"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Emails.UserEmail > :logo"
+msgid "A path to a custom logo. Set it to `nil` to use the default Pleroma logo."
+msgstr ""
+"A path to a custom logo. Set it to `nil` to use the default Pleroma logo."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Emails.UserEmail > :styling"
+msgid "A map with color settings for email templates."
+msgstr "A map with color settings for email templates."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Formatter > :class"
+msgid "Specify the class to be added to the generated link. Disable to clear."
+msgstr "Specify the class to be added to the generated link. Disable to clear."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Formatter > :extra"
+msgid "Link URLs with rarely used schemes (magnet, ipfs, irc, etc.)"
+msgstr "Link URLs with rarely used schemes (magnet, ipfs, irc, etc.)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Formatter > :new_window"
+msgid "Link URLs will open in a new window/tab."
+msgstr "Link URLs will open in a new window/tab."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Formatter > :rel"
+msgid "Override the rel attribute. Disable to clear."
+msgstr "Override the rel attribute. Disable to clear."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Formatter > :strip_prefix"
+msgid "Strip the scheme prefix."
+msgstr "Strip the scheme prefix."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Formatter > :truncate"
+msgid "Set to a number to truncate URLs longer than the number. Truncated URLs will end in `...`"
+msgstr ""
+"Set to a number to truncate URLs longer than the number. Truncated URLs will "
+"end in `...`"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Formatter > :validate_tld"
+msgid "Set to false to disable TLD validation for URLs/emails. Can be set to :no_scheme to validate TLDs only for URLs without a scheme (e.g `example.com` will be validated, but `http://example.loki` won't)"
+msgstr ""
+"Set to false to disable TLD validation for URLs/emails. Can be set to :"
+"no_scheme to validate TLDs only for URLs without a scheme (e.g `example.com` "
+"will be validated, but `http://example.loki` won't)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.ScheduledActivity > :daily_user_limit"
+msgid "The number of scheduled activities a user is allowed to create in a single day. Default: 25."
+msgstr ""
+"The number of scheduled activities a user is allowed to create in a single "
+"day. Default: 25."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.ScheduledActivity > :enabled"
+msgid "Whether scheduled activities are sent to the job queue to be executed"
+msgstr "Whether scheduled activities are sent to the job queue to be executed"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.ScheduledActivity > :total_user_limit"
+msgid "The number of scheduled activities a user is allowed to create in total. Default: 300."
+msgstr ""
+"The number of scheduled activities a user is allowed to create in total. "
+"Default: 300."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Upload > :base_url"
+msgid "Base URL for the uploads. Required if you use a CDN or host attachments under a different domain."
+msgstr ""
+"Base URL for the uploads. Required if you use a CDN or host attachments "
+"under a different domain."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Upload > :filename_display_max_length"
+msgid "Set max length of a filename to display. 0 = no limit. Default: 30"
+msgstr "Set max length of a filename to display. 0 = no limit. Default: 30"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Upload > :filters"
+msgid "List of filter modules for uploads. Module names are shortened (removed leading `Pleroma.Upload.Filter.` part), but on adding custom module you need to use full name."
+msgstr ""
+"List of filter modules for uploads. Module names are shortened (removed "
+"leading `Pleroma.Upload.Filter.` part), but on adding custom module you need "
+"to use full name."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Upload > :link_name"
+msgid "If enabled, a name parameter will be added to the URL of the upload. For example `https://instance.tld/media/imagehash.png?name=realname.png`."
+msgstr ""
+"If enabled, a name parameter will be added to the URL of the upload. For "
+"example `https://instance.tld/media/imagehash.png?name=realname.png`."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Upload > :proxy_remote"
+msgid "Proxy requests to the remote uploader.\n\nUseful if media upload endpoint is not internet accessible.\n"
+msgstr ""
+"Proxy requests to the remote uploader.\n"
+"\n"
+"Useful if media upload endpoint is not internet accessible.\n"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Upload > :uploader"
+msgid "Module which will be used for uploads"
+msgstr "Module which will be used for uploads"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Upload.Filter.AnonymizeFilename > :text"
+msgid "Text to replace filenames in links. If no setting, {random}.extension will be used. You can get the original filename extension by using {extension}, for example custom-file-name.{extension}."
+msgstr ""
+"Text to replace filenames in links. If no setting, {random}.extension will "
+"be used. You can get the original filename extension by using {extension}, "
+"for example custom-file-name.{extension}."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Upload.Filter.Mogrify > :args"
+msgid "List of actions for the mogrify command. It's possible to add self-written settings as string. For example `auto-orient, strip, {\"resize\", \"3840x1080>\"}` value will be parsed into valid list of the settings."
+msgstr ""
+"List of actions for the mogrify command. It's possible to add self-written "
+"settings as string. For example `auto-orient, strip, {\"resize\", \"3840x1080"
+">\"}` value will be parsed into valid list of the settings."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Uploaders.Local > :uploads"
+msgid "Path where user's uploads will be saved"
+msgstr "Path where user's uploads will be saved"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Uploaders.S3 > :bucket"
+msgid "S3 bucket"
+msgstr "S3 bucket"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Uploaders.S3 > :bucket_namespace"
+msgid "S3 bucket namespace"
+msgstr "S3 bucket namespace"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Uploaders.S3 > :streaming_enabled"
+msgid "Enable streaming uploads, when enabled the file will be sent to the server in chunks as it's being read. This may be unsupported by some providers, try disabling this if you have upload problems."
+msgstr ""
+"Enable streaming uploads, when enabled the file will be sent to the server "
+"in chunks as it's being read. This may be unsupported by some providers, try "
+"disabling this if you have upload problems."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Uploaders.S3 > :truncated_namespace"
+msgid "If you use S3 compatible service such as Digital Ocean Spaces or CDN, set folder name or \"\" etc. For example, when using CDN to S3 virtual host format, set \"\". At this time, write CNAME to CDN in Upload base_url."
+msgstr ""
+"If you use S3 compatible service such as Digital Ocean Spaces or CDN, set "
+"folder name or \"\" etc. For example, when using CDN to S3 virtual host "
+"format, set \"\". At this time, write CNAME to CDN in Upload base_url."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.User > :email_blacklist"
+msgid "List of email domains users may not register with."
+msgstr "List of email domains users may not register with."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.User > :restricted_nicknames"
+msgid "List of nicknames users may not register with."
+msgstr "List of nicknames users may not register with."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.User.Backup > :limit_days"
+msgid "Limit user to export not more often than once per N days"
+msgstr "Limit user to export not more often than once per N days"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.User.Backup > :purge_after_days"
+msgid "Remove backup achives after N days"
+msgstr "Remove backup achives after N days"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Web.ApiSpec.CastAndValidate > :strict"
+msgid "Enables strict input validation (useful in development, not recommended in production)"
+msgstr ""
+"Enables strict input validation (useful in development, not recommended in "
+"production)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http > :headers"
+msgid "HTTP headers of request"
+msgstr "HTTP headers of request"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http > :method"
+msgid "HTTP method of request. Default: :purge"
+msgstr "HTTP method of request. Default: :purge"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http > :options"
+msgid "Request options"
+msgstr "Request options"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Script > :script_path"
+msgid "Path to executable script which will purge cached items."
+msgstr "Path to executable script which will purge cached items."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Script > :url_format"
+msgid "Optional URL format preprocessing. Only required for Apache's htcacheclean."
+msgstr ""
+"Optional URL format preprocessing. Only required for Apache's htcacheclean."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Web.Metadata > :providers"
+msgid "List of metadata providers to enable"
+msgstr "List of metadata providers to enable"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Web.Metadata > :unfurl_nsfw"
+msgid "When enabled NSFW attachments will be shown in previews"
+msgstr "When enabled NSFW attachments will be shown in previews"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Web.Plugs.RemoteIp > :enabled"
+msgid "Enable/disable the plug. Default: disabled."
+msgstr "Enable/disable the plug. Default: disabled."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Web.Plugs.RemoteIp > :headers"
+msgid " A list of strings naming the HTTP headers to use when deriving the true client IP. Default: `[\"x-forwarded-for\"]`.\n"
+msgstr ""
+" A list of strings naming the HTTP headers to use when deriving the true "
+"client IP. Default: `[\"x-forwarded-for\"]`.\n"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Web.Plugs.RemoteIp > :proxies"
+msgid "A list of upstream proxy IP subnets in CIDR notation from which we will parse the content of `headers`. Defaults to `[]`. IPv4 entries without a bitmask will be assumed to be /32 and IPv6 /128."
+msgstr ""
+"A list of upstream proxy IP subnets in CIDR notation from which we will "
+"parse the content of `headers`. Defaults to `[]`. IPv4 entries without a "
+"bitmask will be assumed to be /32 and IPv6 /128."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Web.Plugs.RemoteIp > :reserved"
+msgid " A list of reserved IP subnets in CIDR notation which should be ignored if found in `headers`. Defaults to `[\"127.0.0.0/8\", \"::1/128\", \"fc00::/7\", \"10.0.0.0/8\", \"172.16.0.0/12\", \"192.168.0.0/16\"]`\n"
+msgstr ""
+" A list of reserved IP subnets in CIDR notation which should be ignored if "
+"found in `headers`. Defaults to `[\"127.0.0.0/8\", \"::1/128\", \"fc00::/7\""
+", \"10.0.0.0/8\", \"172.16.0.0/12\", \"192.168.0.0/16\"]`\n"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Web.Preload > :providers"
+msgid "List of preload providers to enable"
+msgstr "List of preload providers to enable"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Workers.PurgeExpiredActivity > :enabled"
+msgid "Enables expired activities addition & deletion"
+msgstr "Enables expired activities addition & deletion"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Workers.PurgeExpiredActivity > :min_lifetime"
+msgid "Minimum lifetime for ephemeral activity (in seconds)"
+msgstr "Minimum lifetime for ephemeral activity (in seconds)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :web_push_encryption-:vapid_details > :private_key"
+msgid "VAPID private key"
+msgstr "VAPID private key"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :web_push_encryption-:vapid_details > :public_key"
+msgid "VAPID public key"
+msgstr "VAPID public key"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :web_push_encryption-:vapid_details > :subject"
+msgid "A mailto link for the administrative contact. It's best if this email is not a personal email address, but rather a group email to the instance moderation team."
+msgstr ""
+"A mailto link for the administrative contact. It's best if this email is not "
+"a personal email address, but rather a group email to the instance "
+"moderation team."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :cors_plug > :credentials"
+msgid "Credentials"
+msgstr "Credentials"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :cors_plug > :expose"
+msgid "Expose"
+msgstr "Expose"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :cors_plug > :headers"
+msgid "Headers"
+msgstr "Headers"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :cors_plug > :max_age"
+msgid "Max age"
+msgstr "Max age"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :cors_plug > :methods"
+msgid "Methods"
+msgstr "Methods"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :ex_aws-:s3 > :access_key_id"
+msgid "Access key"
+msgstr "Access key"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :ex_aws-:s3 > :host"
+msgid "Host"
+msgstr "Host"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :ex_aws-:s3 > :region"
+msgid "Region"
+msgstr "Region"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :ex_aws-:s3 > :secret_access_key"
+msgid "Secret access key"
+msgstr "Secret access key"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :logger > :backends"
+msgid "Backends"
+msgstr "Backends"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :logger-:console > :format"
+msgid "Format"
+msgstr "Format"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :logger-:console > :level"
+msgid "Level"
+msgstr "Level"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :logger-:console > :metadata"
+msgid "Metadata"
+msgstr "Metadata"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :logger-:ex_syslogger > :format"
+msgid "Format"
+msgstr "Format"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :logger-:ex_syslogger > :ident"
+msgid "Ident"
+msgstr "Ident"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :logger-:ex_syslogger > :level"
+msgid "Level"
+msgstr "Level"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :logger-:ex_syslogger > :metadata"
+msgid "Metadata"
+msgstr "Metadata"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :mime > :types"
+msgid "Types"
+msgstr "Types"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :mime > :types > application/activity+json"
+msgid "\"application/activity+json\""
+msgstr "\"application/activity+json\""
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :mime > :types > application/jrd+json"
+msgid "\"application/jrd+json\""
+msgstr "\"application/jrd+json\""
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :mime > :types > application/ld+json"
+msgid "\"application/ld+json\""
+msgstr "\"application/ld+json\""
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :mime > :types > application/xml"
+msgid "\"application/xml\""
+msgstr "\"application/xml\""
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :mime > :types > application/xrd+xml"
+msgid "\"application/xrd+xml\""
+msgstr "\"application/xrd+xml\""
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma > :admin_token"
+msgid "Admin token"
+msgstr "Admin token"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma > Pleroma.Web.Auth.Authenticator"
+msgid "Pleroma.Web.Auth.Authenticator"
+msgstr "Pleroma.Web.Auth.Authenticator"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:activitypub > :blockers_visible"
+msgid "Blockers visible"
+msgstr "Blockers visible"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:activitypub > :follow_handshake_timeout"
+msgid "Follow handshake timeout"
+msgstr "Follow handshake timeout"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:activitypub > :note_replies_output_limit"
+msgid "Note replies output limit"
+msgstr "Note replies output limit"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:activitypub > :outgoing_blocks"
+msgid "Outgoing blocks"
+msgstr "Outgoing blocks"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:activitypub > :sign_object_fetches"
+msgid "Sign object fetches"
+msgstr "Sign object fetches"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:activitypub > :unfollow_blocked"
+msgid "Unfollow blocked"
+msgstr "Unfollow blocked"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:assets > :default_mascot"
+msgid "Default mascot"
+msgstr "Default mascot"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:assets > :default_user_avatar"
+msgid "Default user avatar"
+msgstr "Default user avatar"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:assets > :mascots"
+msgid "Mascots"
+msgstr "Mascots"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:auth > :auth_template"
+msgid "Auth template"
+msgstr "Auth template"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:auth > :enforce_oauth_admin_scope_usage"
+msgid "Enforce OAuth admin scope usage"
+msgstr "Enforce OAuth admin scope usage"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:auth > :oauth_consumer_strategies"
+msgid "OAuth consumer strategies"
+msgstr "OAuth consumer strategies"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:auth > :oauth_consumer_template"
+msgid "OAuth consumer template"
+msgstr "OAuth consumer template"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:email_notifications > :digest"
+msgid "Digest"
+msgstr "Digest"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:email_notifications > :digest > :active"
+msgid "Enabled"
+msgstr "Enabled"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:email_notifications > :digest > :inactivity_threshold"
+msgid "Inactivity threshold"
+msgstr "Inactivity threshold"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:email_notifications > :digest > :interval"
+msgid "Interval"
+msgstr "Interval"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:email_notifications > :digest > :schedule"
+msgid "Schedule"
+msgstr "Schedule"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:emoji > :default_manifest"
+msgid "Default manifest"
+msgstr "Default manifest"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:emoji > :groups"
+msgid "Groups"
+msgstr "Groups"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:emoji > :pack_extensions"
+msgid "Pack extensions"
+msgstr "Pack extensions"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:emoji > :shared_pack_cache_seconds_per_file"
+msgid "Shared pack cache s/file"
+msgstr "Shared pack cache s/file"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:emoji > :shortcode_globs"
+msgid "Shortcode globs"
+msgstr "Shortcode globs"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:features > :improved_hashtag_timeline"
+msgid "Improved hashtag timeline"
+msgstr "Improved hashtag timeline"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:feed > :post_title"
+msgid "Post title"
+msgstr "Post title"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:feed > :post_title > :max_length"
+msgid "Max length"
+msgstr "Max length"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:feed > :post_title > :omission"
+msgid "Omission"
+msgstr "Omission"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe"
+msgid "Pleroma FE"
+msgstr "Pleroma FE"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :alwaysShowSubjectInput"
+msgid "Always show subject input"
+msgstr "Always show subject input"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :background"
+msgid "Background"
+msgstr "Background"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :collapseMessageWithSubject"
+msgid "Collapse message with subject"
+msgstr "Collapse message with subject"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :greentext"
+msgid "Greentext"
+msgstr "Greentext"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :hideFilteredStatuses"
+msgid "Hide Filtered Statuses"
+msgstr "Hide Filtered Statuses"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :hideMutedPosts"
+msgid "Hide Muted Posts"
+msgstr "Hide Muted Posts"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :hidePostStats"
+msgid "Hide post stats"
+msgstr "Hide post stats"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :hideUserStats"
+msgid "Hide user stats"
+msgstr "Hide user stats"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :logo"
+msgid "Logo"
+msgstr "Logo"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :logoMargin"
+msgid "Logo margin"
+msgstr "Logo margin"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :logoMask"
+msgid "Logo mask"
+msgstr "Logo mask"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :nsfwCensorImage"
+msgid "NSFW Censor Image"
+msgstr "NSFW Censor Image"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :postContentType"
+msgid "Post Content Type"
+msgstr "Post Content Type"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :redirectRootLogin"
+msgid "Redirect root login"
+msgstr "Redirect root login"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :redirectRootNoLogin"
+msgid "Redirect root no login"
+msgstr "Redirect root no login"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :scopeCopy"
+msgid "Scope copy"
+msgstr "Scope copy"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :showFeaturesPanel"
+msgid "Show instance features panel"
+msgstr "Show instance features panel"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :showInstanceSpecificPanel"
+msgid "Show instance specific panel"
+msgstr "Show instance specific panel"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :sidebarRight"
+msgid "Sidebar on Right"
+msgstr "Sidebar on Right"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :subjectLineBehavior"
+msgid "Subject line behavior"
+msgstr "Subject line behavior"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :theme"
+msgid "Theme"
+msgstr "Theme"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontends > :admin"
+msgid "Admin"
+msgstr "Admin"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontends > :admin > name"
+msgid "Name"
+msgstr "Name"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontends > :admin > ref"
+msgid "Reference"
+msgstr "Reference"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontends > :available"
+msgid "Available"
+msgstr "Available"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontends > :available > build_dir"
+msgid "Build directory"
+msgstr "Build directory"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontends > :available > build_url"
+msgid "Build URL"
+msgstr "Build URL"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontends > :available > custom-http-headers"
+msgid "Custom HTTP headers"
+msgstr "Custom HTTP headers"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontends > :available > git"
+msgid "Git Repository URL"
+msgstr "Git Repository URL"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontends > :available > name"
+msgid "Name"
+msgstr "Name"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontends > :available > ref"
+msgid "Reference"
+msgstr "Reference"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontends > :primary"
+msgid "Primary"
+msgstr "Primary"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontends > :primary > name"
+msgid "Name"
+msgstr "Name"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontends > :primary > ref"
+msgid "Reference"
+msgstr "Reference"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:http > :adapter"
+msgid "Adapter"
+msgstr "Adapter"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:http > :adapter > :ssl_options"
+msgid "SSL Options"
+msgstr "SSL Options"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:http > :adapter > :ssl_options > :versions"
+msgid "Versions"
+msgstr "Versions"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:http > :proxy_url"
+msgid "Proxy URL"
+msgstr "Proxy URL"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:http > :user_agent"
+msgid "User agent"
+msgstr "User agent"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:http_security > :enabled"
+msgid "Enabled"
+msgstr "Enabled"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:http_security > :referrer_policy"
+msgid "Referrer policy"
+msgstr "Referrer policy"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:http_security > :report_uri"
+msgid "Report URI"
+msgstr "Report URI"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:http_security > :sts"
+msgid "STS"
+msgstr "STS"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:http_security > :sts_max_age"
+msgid "STS max age"
+msgstr "STS max age"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :account_activation_required"
+msgid "Account activation required"
+msgstr "Account activation required"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :account_approval_required"
+msgid "Account approval required"
+msgstr "Account approval required"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :account_field_name_length"
+msgid "Account field name length"
+msgstr "Account field name length"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :account_field_value_length"
+msgid "Account field value length"
+msgstr "Account field value length"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :allow_relay"
+msgid "Allow relay"
+msgstr "Allow relay"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :allowed_post_formats"
+msgid "Allowed post formats"
+msgstr "Allowed post formats"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :attachment_links"
+msgid "Attachment links"
+msgstr "Attachment links"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :autofollowed_nicknames"
+msgid "Autofollowed nicknames"
+msgstr "Autofollowed nicknames"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :autofollowing_nicknames"
+msgid "Autofollowing nicknames"
+msgstr "Autofollowing nicknames"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :avatar_upload_limit"
+msgid "Avatar upload limit"
+msgstr "Avatar upload limit"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :background_upload_limit"
+msgid "Background upload limit"
+msgstr "Background upload limit"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :banner_upload_limit"
+msgid "Banner upload limit"
+msgstr "Banner upload limit"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :cleanup_attachments"
+msgid "Cleanup attachments"
+msgstr "Cleanup attachments"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :description"
+msgid "Description"
+msgstr "Description"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :email"
+msgid "Admin Email Address"
+msgstr "Admin Email Address"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :extended_nickname_format"
+msgid "Extended nickname format"
+msgstr "Extended nickname format"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :external_user_synchronization"
+msgid "External user synchronization"
+msgstr "External user synchronization"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :federating"
+msgid "Federating"
+msgstr "Federating"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :federation_incoming_replies_max_depth"
+msgid "Fed. incoming replies max depth"
+msgstr "Fed. incoming replies max depth"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :federation_reachability_timeout_days"
+msgid "Fed. reachability timeout days"
+msgstr "Fed. reachability timeout days"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :healthcheck"
+msgid "Healthcheck"
+msgstr "Healthcheck"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :instance_thumbnail"
+msgid "Instance thumbnail"
+msgstr "Instance thumbnail"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :invites_enabled"
+msgid "Invites enabled"
+msgstr "Invites enabled"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :limit"
+msgid "Limit"
+msgstr "Limit"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :limit_to_local_content"
+msgid "Limit to local content"
+msgstr "Limit to local content"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :max_account_fields"
+msgid "Max account fields"
+msgstr "Max account fields"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :max_pinned_statuses"
+msgid "Max pinned statuses"
+msgstr "Max pinned statuses"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :max_remote_account_fields"
+msgid "Max remote account fields"
+msgstr "Max remote account fields"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :max_report_comment_size"
+msgid "Max report comment size"
+msgstr "Max report comment size"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :multi_factor_authentication"
+msgid "Multi factor authentication"
+msgstr "Multi factor authentication"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :multi_factor_authentication > :backup_codes"
+msgid "Backup codes"
+msgstr "Backup codes"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :multi_factor_authentication > :backup_codes > :length"
+msgid "Length"
+msgstr "Length"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :multi_factor_authentication > :backup_codes > :number"
+msgid "Number"
+msgstr "Number"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :multi_factor_authentication > :totp"
+msgid "TOTP settings"
+msgstr "TOTP settings"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :multi_factor_authentication > :totp > :digits"
+msgid "Digits"
+msgstr "Digits"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :multi_factor_authentication > :totp > :period"
+msgid "Period"
+msgstr "Period"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :name"
+msgid "Name"
+msgstr "Name"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :notify_email"
+msgid "Sender Email Address"
+msgstr "Sender Email Address"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :poll_limits"
+msgid "Poll limits"
+msgstr "Poll limits"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :poll_limits > :max_expiration"
+msgid "Max expiration"
+msgstr "Max expiration"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :poll_limits > :max_option_chars"
+msgid "Max option chars"
+msgstr "Max option chars"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :poll_limits > :max_options"
+msgid "Max options"
+msgstr "Max options"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :poll_limits > :min_expiration"
+msgid "Min expiration"
+msgstr "Min expiration"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :privileged_staff"
+msgid "Privileged staff"
+msgstr "Privileged staff"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :profile_directory"
+msgid "Profile directory"
+msgstr "Profile directory"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :public"
+msgid "Public"
+msgstr "Public"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :quarantined_instances"
+msgid "Quarantined instances"
+msgstr "Quarantined instances"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :registration_reason_length"
+msgid "Registration reason length"
+msgstr "Registration reason length"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :registrations_open"
+msgid "Registrations open"
+msgstr "Registrations open"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :remote_limit"
+msgid "Remote limit"
+msgstr "Remote limit"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :remote_post_retention_days"
+msgid "Remote post retention days"
+msgstr "Remote post retention days"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :safe_dm_mentions"
+msgid "Safe DM mentions"
+msgstr "Safe DM mentions"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :show_reactions"
+msgid "Show reactions"
+msgstr "Show reactions"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :skip_thread_containment"
+msgid "Skip thread containment"
+msgstr "Skip thread containment"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :static_dir"
+msgid "Static dir"
+msgstr "Static dir"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :upload_limit"
+msgid "Upload limit"
+msgstr "Upload limit"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :user_bio_length"
+msgid "User bio length"
+msgstr "User bio length"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :user_name_length"
+msgid "User name length"
+msgstr "User name length"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instances_favicons > :enabled"
+msgid "Enabled"
+msgstr "Enabled"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:ldap > :base"
+msgid "Base"
+msgstr "Base"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:ldap > :enabled"
+msgid "Enabled"
+msgstr "Enabled"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:ldap > :host"
+msgid "Host"
+msgstr "Host"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:ldap > :port"
+msgid "Port"
+msgstr "Port"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:ldap > :ssl"
+msgid "SSL"
+msgstr "SSL"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:ldap > :sslopts"
+msgid "SSL options"
+msgstr "SSL options"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:ldap > :sslopts > :cacertfile"
+msgid "Cacertfile"
+msgstr "Cacertfile"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:ldap > :sslopts > :verify"
+msgid "Verify"
+msgstr "Verify"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:ldap > :tls"
+msgid "TLS"
+msgstr "TLS"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:ldap > :tlsopts"
+msgid "TLS options"
+msgstr "TLS options"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:ldap > :tlsopts > :cacertfile"
+msgid "Cacertfile"
+msgstr "Cacertfile"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:ldap > :tlsopts > :verify"
+msgid "Verify"
+msgstr "Verify"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:ldap > :uid"
+msgid "UID"
+msgstr "UID"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:majic_pool > :size"
+msgid "Size"
+msgstr "Size"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:manifest > :background_color"
+msgid "Background color"
+msgstr "Background color"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:manifest > :icons"
+msgid "Icons"
+msgstr "Icons"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:manifest > :theme_color"
+msgid "Theme color"
+msgstr "Theme color"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:markup > :allow_fonts"
+msgid "Allow fonts"
+msgstr "Allow fonts"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:markup > :allow_headings"
+msgid "Allow headings"
+msgstr "Allow headings"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:markup > :allow_inline_images"
+msgid "Allow inline images"
+msgstr "Allow inline images"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:markup > :allow_tables"
+msgid "Allow tables"
+msgstr "Allow tables"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:markup > :scrub_policy"
+msgid "Scrub policy"
+msgstr "Scrub policy"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:media_preview_proxy > :enabled"
+msgid "Enabled"
+msgstr "Enabled"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:media_preview_proxy > :image_quality"
+msgid "Image quality"
+msgstr "Image quality"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:media_preview_proxy > :min_content_length"
+msgid "Min content length"
+msgstr "Min content length"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:media_preview_proxy > :thumbnail_max_height"
+msgid "Thumbnail max height"
+msgstr "Thumbnail max height"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:media_preview_proxy > :thumbnail_max_width"
+msgid "Thumbnail max width"
+msgstr "Thumbnail max width"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:media_proxy > :base_url"
+msgid "Base URL"
+msgstr "Base URL"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:media_proxy > :enabled"
+msgid "Enabled"
+msgstr "Enabled"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:media_proxy > :invalidation"
+msgid "Invalidation"
+msgstr "Invalidation"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:media_proxy > :invalidation > :enabled"
+msgid "Enabled"
+msgstr "Enabled"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:media_proxy > :invalidation > :provider"
+msgid "Provider"
+msgstr "Provider"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:media_proxy > :proxy_opts"
+msgid "Advanced MediaProxy Options"
+msgstr "Advanced MediaProxy Options"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:media_proxy > :proxy_opts > :max_body_length"
+msgid "Max body length"
+msgstr "Max body length"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:media_proxy > :proxy_opts > :max_read_duration"
+msgid "Max read duration"
+msgstr "Max read duration"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:media_proxy > :proxy_opts > :redirect_on_failure"
+msgid "Redirect on failure"
+msgstr "Redirect on failure"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:media_proxy > :whitelist"
+msgid "Whitelist"
+msgstr "Whitelist"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:modules > :runtime_dir"
+msgid "Runtime dir"
+msgstr "Runtime dir"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf > :policies"
+msgid "Policies"
+msgstr "Policies"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf > :transparency"
+msgid "MRF transparency"
+msgstr "MRF transparency"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf > :transparency_exclusions"
+msgid "MRF transparency exclusions"
+msgstr "MRF transparency exclusions"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_activity_expiration > :days"
+msgid "Days"
+msgstr "Days"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_follow_bot > :follower_nickname"
+msgid "Follower nickname"
+msgstr "Follower nickname"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_hashtag > :federated_timeline_removal"
+msgid "Federated timeline removal"
+msgstr "Federated timeline removal"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_hashtag > :reject"
+msgid "Reject"
+msgstr "Reject"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_hashtag > :sensitive"
+msgid "Sensitive"
+msgstr "Sensitive"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_hellthread > :delist_threshold"
+msgid "Delist threshold"
+msgstr "Delist threshold"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_hellthread > :reject_threshold"
+msgid "Reject threshold"
+msgstr "Reject threshold"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_keyword > :federated_timeline_removal"
+msgid "Federated timeline removal"
+msgstr "Federated timeline removal"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_keyword > :reject"
+msgid "Reject"
+msgstr "Reject"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_keyword > :replace"
+msgid "Replace"
+msgstr "Replace"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_mention > :actors"
+msgid "Actors"
+msgstr "Actors"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_normalize_markup > :scrub_policy"
+msgid "Scrub policy"
+msgstr "Scrub policy"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_object_age > :actions"
+msgid "Actions"
+msgstr "Actions"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_object_age > :threshold"
+msgid "Threshold"
+msgstr "Threshold"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_rejectnonpublic > :allow_direct"
+msgid "Allow direct"
+msgstr "Allow direct"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_rejectnonpublic > :allow_followersonly"
+msgid "Allow followers-only"
+msgstr "Allow followers-only"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_simple > :accept"
+msgid "Accept"
+msgstr "Accept"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_simple > :avatar_removal"
+msgid "Avatar removal"
+msgstr "Avatar removal"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_simple > :banner_removal"
+msgid "Banner removal"
+msgstr "Banner removal"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_simple > :federated_timeline_removal"
+msgid "Federated timeline removal"
+msgstr "Federated timeline removal"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_simple > :followers_only"
+msgid "Followers only"
+msgstr "Followers only"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_simple > :media_nsfw"
+msgid "Media NSFW"
+msgstr "Media NSFW"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_simple > :media_removal"
+msgid "Media removal"
+msgstr "Media removal"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_simple > :reject"
+msgid "Reject"
+msgstr "Reject"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_simple > :reject_deletes"
+msgid "Reject deletes"
+msgstr "Reject deletes"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_simple > :report_removal"
+msgid "Report removal"
+msgstr "Report removal"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_steal_emoji > :hosts"
+msgid "Hosts"
+msgstr "Hosts"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_steal_emoji > :rejected_shortcodes"
+msgid "Rejected shortcodes"
+msgstr "Rejected shortcodes"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_steal_emoji > :size_limit"
+msgid "Size limit"
+msgstr "Size limit"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_subchain > :match_actor"
+msgid "Match actor"
+msgstr "Match actor"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_vocabulary > :accept"
+msgid "Accept"
+msgstr "Accept"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_vocabulary > :reject"
+msgid "Reject"
+msgstr "Reject"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:oauth2 > :clean_expired_tokens"
+msgid "Clean expired tokens"
+msgstr "Clean expired tokens"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:oauth2 > :issue_new_refresh_token"
+msgid "Issue new refresh token"
+msgstr "Issue new refresh token"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:oauth2 > :token_expires_in"
+msgid "Token expires in"
+msgstr "Token expires in"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:populate_hashtags_table > :fault_rate_allowance"
+msgid "Fault rate allowance"
+msgstr "Fault rate allowance"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:populate_hashtags_table > :sleep_interval_ms"
+msgid "Sleep interval ms"
+msgstr "Sleep interval ms"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:rate_limit > :app_account_creation"
+msgid "App account creation"
+msgstr "App account creation"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:rate_limit > :authentication"
+msgid "Authentication"
+msgstr "Authentication"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:rate_limit > :relation_id_action"
+msgid "Relation ID action"
+msgstr "Relation ID action"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:rate_limit > :relations_actions"
+msgid "Relations actions"
+msgstr "Relations actions"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:rate_limit > :search"
+msgid "Search"
+msgstr "Search"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:rate_limit > :status_id_action"
+msgid "Status ID action"
+msgstr "Status ID action"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:rate_limit > :statuses_actions"
+msgid "Statuses actions"
+msgstr "Statuses actions"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:rate_limit > :timeline"
+msgid "Timeline"
+msgstr "Timeline"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:restrict_unauthenticated > :activities"
+msgid "Activities"
+msgstr "Activities"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:restrict_unauthenticated > :activities > :local"
+msgid "Local"
+msgstr "Local"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:restrict_unauthenticated > :activities > :remote"
+msgid "Remote"
+msgstr "Remote"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:restrict_unauthenticated > :profiles"
+msgid "Profiles"
+msgstr "Profiles"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:restrict_unauthenticated > :profiles > :local"
+msgid "Local"
+msgstr "Local"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:restrict_unauthenticated > :profiles > :remote"
+msgid "Remote"
+msgstr "Remote"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:restrict_unauthenticated > :timelines"
+msgid "Timelines"
+msgstr "Timelines"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:restrict_unauthenticated > :timelines > :federated"
+msgid "Federated"
+msgstr "Federated"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:restrict_unauthenticated > :timelines > :local"
+msgid "Local"
+msgstr "Local"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:rich_media > :enabled"
+msgid "Enabled"
+msgstr "Enabled"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:rich_media > :failure_backoff"
+msgid "Failure backoff"
+msgstr "Failure backoff"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:rich_media > :ignore_hosts"
+msgid "Ignore hosts"
+msgstr "Ignore hosts"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:rich_media > :ignore_tld"
+msgid "Ignore TLD"
+msgstr "Ignore TLD"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:rich_media > :parsers"
+msgid "Parsers"
+msgstr "Parsers"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:rich_media > :ttl_setters"
+msgid "TTL setters"
+msgstr "TTL setters"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:static_fe > :enabled"
+msgid "Enabled"
+msgstr "Enabled"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:streamer > :overflow_workers"
+msgid "Overflow workers"
+msgstr "Overflow workers"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:streamer > :workers"
+msgid "Workers"
+msgstr "Workers"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:uri_schemes > :valid_schemes"
+msgid "Valid schemes"
+msgstr "Valid schemes"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:user > :deny_follow_blocked"
+msgid "Deny follow blocked"
+msgstr "Deny follow blocked"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:web_cache_ttl > :activity_pub"
+msgid "Activity pub"
+msgstr "Activity pub"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:web_cache_ttl > :activity_pub_question"
+msgid "Activity pub question"
+msgstr "Activity pub question"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:welcome > :direct_message"
+msgid "Direct message"
+msgstr "Direct message"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:welcome > :direct_message > :enabled"
+msgid "Enabled"
+msgstr "Enabled"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:welcome > :direct_message > :message"
+msgid "Message"
+msgstr "Message"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:welcome > :direct_message > :sender_nickname"
+msgid "Sender nickname"
+msgstr "Sender nickname"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:welcome > :email"
+msgid "Email"
+msgstr "Email"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:welcome > :email > :enabled"
+msgid "Enabled"
+msgstr "Enabled"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:welcome > :email > :html"
+msgid "Html"
+msgstr "Html"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:welcome > :email > :sender"
+msgid "Sender"
+msgstr "Sender"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:welcome > :email > :subject"
+msgid "Subject"
+msgstr "Subject"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:welcome > :email > :text"
+msgid "Text"
+msgstr "Text"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:workers > :retries"
+msgid "Retries"
+msgstr "Retries"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-ConcurrentLimiter > Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy"
+msgid "Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy"
+msgstr "Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-ConcurrentLimiter > Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy > :max_running"
+msgid "Max running"
+msgstr "Max running"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-ConcurrentLimiter > Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy > :max_waiting"
+msgid "Max waiting"
+msgstr "Max waiting"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-ConcurrentLimiter > Pleroma.Web.RichMedia.Helpers"
+msgid "Pleroma.Web.RichMedia.Helpers"
+msgstr "Pleroma.Web.RichMedia.Helpers"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-ConcurrentLimiter > Pleroma.Web.RichMedia.Helpers > :max_running"
+msgid "Max running"
+msgstr "Max running"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-ConcurrentLimiter > Pleroma.Web.RichMedia.Helpers > :max_waiting"
+msgid "Max waiting"
+msgstr "Max waiting"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Oban > :crontab"
+msgid "Crontab"
+msgstr "Crontab"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Oban > :log"
+msgid "Log"
+msgstr "Log"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Oban > :queues"
+msgid "Queues"
+msgstr "Queues"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Oban > :queues > :activity_expiration"
+msgid "Activity expiration"
+msgstr "Activity expiration"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Oban > :queues > :attachments_cleanup"
+msgid "Attachments cleanup"
+msgstr "Attachments cleanup"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Oban > :queues > :background"
+msgid "Background"
+msgstr "Background"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Oban > :queues > :backup"
+msgid "Backup"
+msgstr "Backup"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Oban > :queues > :federator_incoming"
+msgid "Federator incoming"
+msgstr "Federator incoming"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Oban > :queues > :federator_outgoing"
+msgid "Federator outgoing"
+msgstr "Federator outgoing"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Oban > :queues > :mailer"
+msgid "Mailer"
+msgstr "Mailer"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Oban > :queues > :scheduled_activities"
+msgid "Scheduled activities"
+msgstr "Scheduled activities"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Oban > :queues > :transmogrifier"
+msgid "Transmogrifier"
+msgstr "Transmogrifier"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Oban > :queues > :web_push"
+msgid "Web push"
+msgstr "Web push"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Captcha > :enabled"
+msgid "Enabled"
+msgstr "Enabled"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Captcha > :method"
+msgid "Method"
+msgstr "Method"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Captcha > :seconds_valid"
+msgid "Seconds valid"
+msgstr "Seconds valid"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Captcha.Kocaptcha > :endpoint"
+msgid "Endpoint"
+msgstr "Endpoint"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > :adapter"
+msgid "Adapter"
+msgstr "Adapter"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > :enabled"
+msgid "Mailer Enabled"
+msgstr "Mailer Enabled"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.AmazonSES-:access_key"
+msgid "AWS Access Key"
+msgstr "AWS Access Key"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.AmazonSES-:region"
+msgid "AWS Region"
+msgstr "AWS Region"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.AmazonSES-:secret"
+msgid "AWS Secret Key"
+msgstr "AWS Secret Key"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Dyn-:api_key"
+msgid "Dyn API Key"
+msgstr "Dyn API Key"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Gmail-:access_token"
+msgid "GMail API Access Token"
+msgstr "GMail API Access Token"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Mailgun-:api_key"
+msgid "Mailgun API Key"
+msgstr "Mailgun API Key"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Mailgun-:domain"
+msgid "Domain"
+msgstr "Domain"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Mailjet-:api_key"
+msgid "MailJet Public API Key"
+msgstr "MailJet Public API Key"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Mailjet-:secret"
+msgid "MailJet Private API Key"
+msgstr "MailJet Private API Key"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Mandrill-:api_key"
+msgid "Mandrill API Key"
+msgstr "Mandrill API Key"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Postmark-:api_key"
+msgid "Postmark API Key"
+msgstr "Postmark API Key"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:auth"
+msgid "AUTH Mode"
+msgstr "AUTH Mode"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:password"
+msgid "Password"
+msgstr "Password"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:port"
+msgid "Port"
+msgstr "Port"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:relay"
+msgid "Relay"
+msgstr "Relay"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:retries"
+msgid "Retries"
+msgstr "Retries"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:ssl"
+msgid "Use SSL"
+msgstr "Use SSL"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:tls"
+msgid "STARTTLS Mode"
+msgstr "STARTTLS Mode"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:username"
+msgid "Username"
+msgstr "Username"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Sendgrid-:api_key"
+msgid "SendGrid API Key"
+msgstr "SendGrid API Key"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Sendmail-:cmd_args"
+msgid "Cmd args"
+msgstr "Cmd args"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Sendmail-:cmd_path"
+msgid "Cmd path"
+msgstr "Cmd path"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Sendmail-:qmail"
+msgid "Qmail compat mode"
+msgstr "Qmail compat mode"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SocketLabs-:api_key"
+msgid "SocketLabs API Key"
+msgstr "SocketLabs API Key"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SocketLabs-:server_id"
+msgid "Server ID"
+msgstr "Server ID"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SparkPost-:api_key"
+msgid "SparkPost API key"
+msgstr "SparkPost API key"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SparkPost-:endpoint"
+msgid "Endpoint"
+msgstr "Endpoint"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.NewUsersDigestEmail > :enabled"
+msgid "Enabled"
+msgstr "Enabled"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.UserEmail > :logo"
+msgid "Logo"
+msgstr "Logo"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.UserEmail > :styling"
+msgid "Styling"
+msgstr "Styling"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.UserEmail > :styling > :background_color"
+msgid "Background color"
+msgstr "Background color"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.UserEmail > :styling > :content_background_color"
+msgid "Content background color"
+msgstr "Content background color"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.UserEmail > :styling > :header_color"
+msgid "Header color"
+msgstr "Header color"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.UserEmail > :styling > :link_color"
+msgid "Link color"
+msgstr "Link color"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.UserEmail > :styling > :text_color"
+msgid "Text color"
+msgstr "Text color"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Emails.UserEmail > :styling > :text_muted_color"
+msgid "Text muted color"
+msgstr "Text muted color"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Formatter > :class"
+msgid "Class"
+msgstr "Class"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Formatter > :extra"
+msgid "Extra"
+msgstr "Extra"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Formatter > :new_window"
+msgid "New window"
+msgstr "New window"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Formatter > :rel"
+msgid "Rel"
+msgstr "Rel"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Formatter > :strip_prefix"
+msgid "Strip prefix"
+msgstr "Strip prefix"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Formatter > :truncate"
+msgid "Truncate"
+msgstr "Truncate"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Formatter > :validate_tld"
+msgid "Validate tld"
+msgstr "Validate tld"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.ScheduledActivity > :daily_user_limit"
+msgid "Daily user limit"
+msgstr "Daily user limit"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.ScheduledActivity > :enabled"
+msgid "Enabled"
+msgstr "Enabled"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.ScheduledActivity > :total_user_limit"
+msgid "Total user limit"
+msgstr "Total user limit"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Upload > :base_url"
+msgid "Base URL"
+msgstr "Base URL"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Upload > :filename_display_max_length"
+msgid "Filename display max length"
+msgstr "Filename display max length"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Upload > :filters"
+msgid "Filters"
+msgstr "Filters"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Upload > :link_name"
+msgid "Link name"
+msgstr "Link name"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Upload > :proxy_remote"
+msgid "Proxy remote"
+msgstr "Proxy remote"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Upload > :uploader"
+msgid "Uploader"
+msgstr "Uploader"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Upload.Filter.AnonymizeFilename > :text"
+msgid "Text"
+msgstr "Text"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Upload.Filter.Mogrify > :args"
+msgid "Args"
+msgstr "Args"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Uploaders.Local > :uploads"
+msgid "Uploads"
+msgstr "Uploads"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Uploaders.S3 > :bucket"
+msgid "Bucket"
+msgstr "Bucket"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Uploaders.S3 > :bucket_namespace"
+msgid "Bucket namespace"
+msgstr "Bucket namespace"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Uploaders.S3 > :streaming_enabled"
+msgid "Streaming enabled"
+msgstr "Streaming enabled"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Uploaders.S3 > :truncated_namespace"
+msgid "Truncated namespace"
+msgstr "Truncated namespace"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.User > :email_blacklist"
+msgid "Email blacklist"
+msgstr "Email blacklist"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.User > :restricted_nicknames"
+msgid "Restricted nicknames"
+msgstr "Restricted nicknames"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.User.Backup > :limit_days"
+msgid "Limit days"
+msgstr "Limit days"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.User.Backup > :purge_after_days"
+msgid "Purge after days"
+msgstr "Purge after days"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Web.ApiSpec.CastAndValidate > :strict"
+msgid "Strict"
+msgstr "Strict"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http > :headers"
+msgid "Headers"
+msgstr "Headers"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http > :method"
+msgid "Method"
+msgstr "Method"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http > :options"
+msgid "Options"
+msgstr "Options"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http > :options > :params"
+msgid "Params"
+msgstr "Params"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Script > :script_path"
+msgid "Script path"
+msgstr "Script path"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Script > :url_format"
+msgid "URL Format"
+msgstr "URL Format"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Web.Metadata > :providers"
+msgid "Providers"
+msgstr "Providers"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Web.Metadata > :unfurl_nsfw"
+msgid "Unfurl NSFW"
+msgstr "Unfurl NSFW"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Web.Plugs.RemoteIp > :enabled"
+msgid "Enabled"
+msgstr "Enabled"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Web.Plugs.RemoteIp > :headers"
+msgid "Headers"
+msgstr "Headers"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Web.Plugs.RemoteIp > :proxies"
+msgid "Proxies"
+msgstr "Proxies"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Web.Plugs.RemoteIp > :reserved"
+msgid "Reserved"
+msgstr "Reserved"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Web.Preload > :providers"
+msgid "Providers"
+msgstr "Providers"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Workers.PurgeExpiredActivity > :enabled"
+msgid "Enabled"
+msgstr "Enabled"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Workers.PurgeExpiredActivity > :min_lifetime"
+msgid "Min lifetime"
+msgstr "Min lifetime"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :web_push_encryption-:vapid_details > :private_key"
+msgid "Private key"
+msgstr "Private key"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :web_push_encryption-:vapid_details > :public_key"
+msgid "Public key"
+msgstr "Public key"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :web_push_encryption-:vapid_details > :subject"
+msgid "Subject"
+msgstr "Subject"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:activitypub > :authorized_fetch_mode"
+msgid "Authorized fetch mode"
+msgstr "Authorized fetch mode"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:activitypub > :authorized_fetch_mode"
+msgid "Require HTTP signatures on AP fetches"
+msgstr "Require HTTP signatures on AP fetches"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:activitypub > :max_collection_objects"
+msgid "The maximum number of items to fetch from a remote collections. Setting this too low can lead to only getting partial collections, but too high and you can end up fetching far too many objects."
+msgstr ""
+"The maximum number of items to fetch from a remote collections. Setting this "
+"too low can lead to only getting partial collections, but too high and you "
+"can end up fetching far too many objects."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:argos_translate"
+msgid "ArgosTranslate Settings."
+msgstr "ArgosTranslate Settings."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:argos_translate > :command_argos_translate"
+msgid "command for `argos-translate`. Can be the command if it's in your PATH, or the full path to the file."
+msgstr ""
+"command for `argos-translate`. Can be the command if it's in your PATH, or "
+"the full path to the file."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:argos_translate > :command_argospm"
+msgid "command for `argospm`. Can be the command if it's in your PATH, or the full path to the file."
+msgstr ""
+"command for `argospm`. Can be the command if it's in your PATH, or the full "
+"path to the file."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:argos_translate > :strip_html"
+msgid "Strip html from the post before translating it."
+msgstr "Strip html from the post before translating it."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:deepl"
+msgid "DeepL Settings."
+msgstr "DeepL Settings."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:deepl > :api_key"
+msgid "API key for DeepL"
+msgstr "API key for DeepL"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:deepl > :tier"
+msgid "API Tier"
+msgstr "API Tier"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations"
+msgid "This form can be used to configure a keyword list that keeps the configuration data for any kind of frontend. By default, settings for pleroma_fe and masto_fe are configured. If you want to add your own configuration your settings all fields must be complete."
+msgstr ""
+"This form can be used to configure a keyword list that keeps the "
+"configuration data for any kind of frontend. By default, settings for "
+"pleroma_fe and masto_fe are configured. If you want to add your own "
+"configuration your settings all fields must be complete."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :masto_fe"
+msgid "Settings for Masto FE"
+msgstr "Settings for Masto FE"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :masto_fe > :showInstanceSpecificPanel"
+msgid "Whenether to show the instance's specific panel"
+msgstr "Whenether to show the instance's specific panel"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :conversationDisplay"
+msgid "How to display conversations (linear or tree)"
+msgstr "How to display conversations (linear or tree)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :hideSiteFavicon"
+msgid "Whether to hide the instance favicon from the navbar"
+msgstr "Whether to hide the instance favicon from the navbar"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :hideSiteName"
+msgid "Whether to hide the site name from the navbar"
+msgstr "Whether to hide the site name from the navbar"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :renderMisskeyMarkdown"
+msgid "Whether to render Misskey-flavoured markdown"
+msgstr "Whether to render Misskey-flavoured markdown"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :showNavShortcuts"
+msgid "Whether to put extra navigation options on the navbar"
+msgstr "Whether to put extra navigation options on the navbar"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :showPanelNavShortcuts"
+msgid "Whether to put timeline nav tabs on the top of the panel"
+msgstr "Whether to put timeline nav tabs on the top of the panel"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :showWiderShortcuts"
+msgid "Whether to add extra space between navbar icons"
+msgstr "Whether to add extra space between navbar icons"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :stopGifs"
+msgid "Whether to pause animated images until they're hovered on"
+msgstr "Whether to pause animated images until they're hovered on"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontends > :mastodon"
+msgid "Mastodon frontend"
+msgstr "Mastodon frontend"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontends > :mastodon > name"
+msgid "Name of the installed frontend. Valid config must include both `Name` and `Reference` values."
+msgstr ""
+"Name of the installed frontend. Valid config must include both `Name` and "
+"`Reference` values."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontends > :mastodon > ref"
+msgid "Reference of the installed frontend to be used. Valid config must include both `Name` and `Reference` values."
+msgstr ""
+"Reference of the installed frontend to be used. Valid config must include "
+"both `Name` and `Reference` values."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontends > :swagger"
+msgid "Swagger API reference frontend"
+msgstr "Swagger API reference frontend"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontends > :swagger > enabled"
+msgid "Whether to have this enabled at all"
+msgstr "Whether to have this enabled at all"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontends > :swagger > name"
+msgid "Name of the installed frontend. Valid config must include both `Name` and `Reference` values."
+msgstr ""
+"Name of the installed frontend. Valid config must include both `Name` and "
+"`Reference` values."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontends > :swagger > ref"
+msgid "Reference of the installed frontend to be used. Valid config must include both `Name` and `Reference` values."
+msgstr ""
+"Reference of the installed frontend to be used. Valid config must include "
+"both `Name` and `Reference` values."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:http > :pool_size"
+msgid "Number of concurrent outbound HTTP requests to allow. Default 50."
+msgstr "Number of concurrent outbound HTTP requests to allow. Default 50."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:http > :pool_timeout"
+msgid "Timeout for initiating HTTP requests (in ms, default 5000)"
+msgstr "Timeout for initiating HTTP requests (in ms, default 5000)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:http > :proxy_url"
+msgid "Proxy URL - of the format http://host:port. Advise setting in .exs instead of admin-fe due to this being set at boot-time."
+msgstr ""
+"Proxy URL - of the format http://host:port. Advise setting in .exs instead "
+"of admin-fe due to this being set at boot-time."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:http > :receive_timeout"
+msgid "Timeout for waiting on remote servers to respond to HTTP requests (in ms, default 15000)"
+msgstr ""
+"Timeout for waiting on remote servers to respond to HTTP requests (in ms, "
+"default 15000)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :export_prometheus_metrics"
+msgid "Enable prometheus metrics (at /api/v1/akkoma/metrics)"
+msgstr "Enable prometheus metrics (at /api/v1/akkoma/metrics)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :languages"
+msgid "Languages the instance uses"
+msgstr "Languages the instance uses"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :local_bubble"
+msgid "List of instances that make up your local bubble (closely-related instances). Used to populate the 'bubble' timeline (domain only)."
+msgstr ""
+"List of instances that make up your local bubble (closely-related instances)"
+". Used to populate the 'bubble' timeline (domain only)."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :privileged_staff"
+msgid "Let moderators access sensitive data (e.g. updating user credentials, get password reset token, delete users, index and read private statuses)"
+msgstr ""
+"Let moderators access sensitive data (e.g. updating user credentials, get "
+"password reset token, delete users, index and read private statuses)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :public"
+msgid "Switching this on will allow unauthenticated users access to all public resources on your instance Switching it off is useful for disabling the Local Timeline and The Whole Known Network. Note: when setting to `false`, please also check `:restrict_unauthenticated` setting."
+msgstr ""
+"Switching this on will allow unauthenticated users access to all public "
+"resources on your instance Switching it off is useful for disabling the "
+"Local Timeline and The Whole Known Network. Note: when setting to `false`, "
+"please also check `:restrict_unauthenticated` setting."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :quarantined_instances"
+msgid "(Deprecated, will be removed in next release) List of ActivityPub instances where activities will not be sent, and the reason for doing so"
+msgstr ""
+"(Deprecated, will be removed in next release) List of ActivityPub instances "
+"where activities will not be sent, and the reason for doing so"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instances_nodeinfo"
+msgid "Control favicons for instances"
+msgstr "Control favicons for instances"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instances_nodeinfo > :enabled"
+msgid "Allow/disallow getting instance nodeinfo"
+msgstr "Allow/disallow getting instance nodeinfo"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:libre_translate"
+msgid "LibreTranslate Settings."
+msgstr "LibreTranslate Settings."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:libre_translate > :api_key"
+msgid "API key for libretranslate"
+msgstr "API key for libretranslate"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:libre_translate > :url"
+msgid "URL for libretranslate"
+msgstr "URL for libretranslate"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:manifest > :background_color"
+msgid "Describe the background color of the app - this is only used for mastodon-fe"
+msgstr ""
+"Describe the background color of the app - this is only used for mastodon-fe"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:manifest > :theme_color"
+msgid "Describe the theme color of the app - this is only used for mastodon-fe"
+msgstr "Describe the theme color of the app - this is only used for mastodon-fe"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf > :transparency_obfuscate_domains"
+msgid "Obfuscate domains in MRF transparency. This is useful if the domain you're blocking contains words you don't want displayed, but still want to disclose the MRF settings."
+msgstr ""
+"Obfuscate domains in MRF transparency. This is useful if the domain you're "
+"blocking contains words you don't want displayed, but still want to disclose "
+"the MRF settings."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_inline_quote"
+msgid "Force quote post URLs inline"
+msgstr "Force quote post URLs inline"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_inline_quote > :prefix"
+msgid "Prefix before the link"
+msgstr "Prefix before the link"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_simple > :handle_threads"
+msgid "Enable to filter replies to threads based from their originating instance, using the reject and accept rules"
+msgstr ""
+"Enable to filter replies to threads based from their originating instance, "
+"using the reject and accept rules"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:restrict_unauthenticated"
+msgid "Disallow unauthenticated viewing of timelines, user profiles and statuses."
+msgstr ""
+"Disallow unauthenticated viewing of timelines, user profiles and statuses."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:restrict_unauthenticated > :activities"
+msgid "Settings for posts."
+msgstr "Settings for posts."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:restrict_unauthenticated > :activities > :local"
+msgid "Disallow viewing local posts."
+msgstr "Disallow viewing local posts."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:restrict_unauthenticated > :activities > :remote"
+msgid "Disallow viewing remote posts."
+msgstr "Disallow viewing remote posts."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:restrict_unauthenticated > :profiles > :local"
+msgid "Disallow viewing local user profiles."
+msgstr "Disallow viewing local user profiles."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:restrict_unauthenticated > :profiles > :remote"
+msgid "Disallow viewing remote user profiles."
+msgstr "Disallow viewing remote user profiles."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:restrict_unauthenticated > :timelines > :federated"
+msgid "Disallow viewing the whole known network timeline."
+msgstr "Disallow viewing the whole known network timeline."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:restrict_unauthenticated > :timelines > :local"
+msgid "Disallow viewing the public timeline."
+msgstr "Disallow viewing the public timeline."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:translator"
+msgid "Translation Settings"
+msgstr "Translation Settings"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:translator > :enabled"
+msgid "Is translation enabled?"
+msgstr "Is translation enabled?"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:translator > :module"
+msgid "Translation module."
+msgstr "Translation module."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:workers > :timeout"
+msgid "Timeout for jobs, per `Oban` queue, in ms"
+msgstr "Timeout for jobs, per `Oban` queue, in ms"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Search"
+msgid "General search settings."
+msgstr "General search settings."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Search > :module"
+msgid "Selected search module."
+msgstr "Selected search module."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Search.Elasticsearch.Cluster"
+msgid "Elasticsearch settings."
+msgstr "Elasticsearch settings."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :api"
+msgid "The API module used by Elasticsearch. Should always be Elasticsearch.API.HTTP"
+msgstr ""
+"The API module used by Elasticsearch. Should always be Elasticsearch.API.HTTP"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :indexes"
+msgid "The indices to set up in Elasticsearch"
+msgstr "The indices to set up in Elasticsearch"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :indexes > :activities"
+msgid "Config for the index to use for activities"
+msgstr "Config for the index to use for activities"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :indexes > :activities > :bulk_page_size"
+msgid "Size for bulk put requests, mostly used on building the index"
+msgstr "Size for bulk put requests, mostly used on building the index"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :indexes > :activities > :bulk_wait_interval"
+msgid "Time to wait between bulk put requests (in ms)"
+msgstr "Time to wait between bulk put requests (in ms)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :indexes > :activities > :settings"
+msgid "Path to the file containing index settings for the activities index. Should contain a mapping."
+msgstr ""
+"Path to the file containing index settings for the activities index. Should "
+"contain a mapping."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :indexes > :activities > :sources"
+msgid "The internal types to use for this index"
+msgstr "The internal types to use for this index"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :indexes > :activities > :store"
+msgid "The internal store module"
+msgstr "The internal store module"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :json_library"
+msgid "The JSON module used to encode/decode when communicating with Elasticsearch"
+msgstr ""
+"The JSON module used to encode/decode when communicating with Elasticsearch"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :password"
+msgid "Password to connect to ES. Set to nil if your cluster is unauthenticated."
+msgstr ""
+"Password to connect to ES. Set to nil if your cluster is unauthenticated."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :url"
+msgid "Elasticsearch URL."
+msgstr "Elasticsearch URL."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :username"
+msgid "Username to connect to ES. Set to nil if your cluster is unauthenticated."
+msgstr ""
+"Username to connect to ES. Set to nil if your cluster is unauthenticated."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Search.Meilisearch"
+msgid "Meilisearch settings."
+msgstr "Meilisearch settings."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Search.Meilisearch > :initial_indexing_chunk_size"
+msgid "Amount of posts in a batch when running the initial indexing operation. Should probably not be more than 100000 since there's a limit on maximum insert size"
+msgstr ""
+"Amount of posts in a batch when running the initial indexing operation. "
+"Should probably not be more than 100000 since there's a limit on maximum "
+"insert size"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Search.Meilisearch > :private_key"
+msgid "Private key for meilisearch authentication, or `nil` to disable private key authentication."
+msgstr ""
+"Private key for meilisearch authentication, or `nil` to disable private key "
+"authentication."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Search.Meilisearch > :url"
+msgid "Meilisearch URL."
+msgstr "Meilisearch URL."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Web.Metadata.Providers.Theme"
+msgid "Specific provider to hand out themes to instances that scrape index.html"
+msgstr ""
+"Specific provider to hand out themes to instances that scrape index.html"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-Pleroma.Web.Metadata.Providers.Theme > :theme_color"
+msgid "The 'accent color' of the instance, used in places like misskey's instance ticker"
+msgstr ""
+"The 'accent color' of the instance, used in places like misskey's instance "
+"ticker"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:activitypub > :max_collection_objects"
+msgid "Max collection objects"
+msgstr "Max collection objects"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:argos_translate"
+msgid "Argos translate"
+msgstr "Argos translate"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:argos_translate > :command_argos_translate"
+msgid "Command argos translate"
+msgstr "Command argos translate"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:argos_translate > :command_argospm"
+msgid "Command argospm"
+msgstr "Command argospm"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:argos_translate > :strip_html"
+msgid "Strip html"
+msgstr "Strip html"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:deepl"
+msgid "DeepL"
+msgstr "DeepL"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:deepl > :api_key"
+msgid "Api key"
+msgstr "Api key"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:deepl > :tier"
+msgid "Tier"
+msgstr "Tier"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :masto_fe"
+msgid "Masto FE"
+msgstr "Masto FE"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :masto_fe > :showInstanceSpecificPanel"
+msgid "Show instance specific panel"
+msgstr "Show instance specific panel"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :conversationDisplay"
+msgid "Conversation display style"
+msgstr "Conversation display style"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :hideSiteFavicon"
+msgid "Hide site favicon"
+msgstr "Hide site favicon"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :hideSiteName"
+msgid "Hide site name"
+msgstr "Hide site name"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :renderMisskeyMarkdown"
+msgid "Render misskey markdown"
+msgstr "Render misskey markdown"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :showNavShortcuts"
+msgid "Show navbar shortcuts"
+msgstr "Show navbar shortcuts"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :showPanelNavShortcuts"
+msgid "Show timeline panel nav shortcuts"
+msgstr "Show timeline panel nav shortcuts"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :showWiderShortcuts"
+msgid "Increase navbar shortcut spacing"
+msgstr "Increase navbar shortcut spacing"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :stopGifs"
+msgid "Stop Gifs"
+msgstr "Stop Gifs"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontends > :mastodon"
+msgid "Mastodon"
+msgstr "Mastodon"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontends > :mastodon > name"
+msgid "Name"
+msgstr "Name"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontends > :mastodon > ref"
+msgid "Reference"
+msgstr "Reference"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontends > :swagger"
+msgid "Swagger"
+msgstr "Swagger"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontends > :swagger > enabled"
+msgid "Enabled"
+msgstr "Enabled"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontends > :swagger > name"
+msgid "Name"
+msgstr "Name"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontends > :swagger > ref"
+msgid "Reference"
+msgstr "Reference"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:http > :pool_size"
+msgid "Pool size"
+msgstr "Pool size"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:http > :pool_timeout"
+msgid "HTTP Pool Request Timeout"
+msgstr "HTTP Pool Request Timeout"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:http > :receive_timeout"
+msgid "HTTP Receive Timeout"
+msgstr "HTTP Receive Timeout"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :export_prometheus_metrics"
+msgid "Export prometheus metrics"
+msgstr "Export prometheus metrics"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :languages"
+msgid "Languages"
+msgstr "Languages"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :local_bubble"
+msgid "Local bubble"
+msgstr "Local bubble"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instances_nodeinfo"
+msgid "Instances nodeinfo"
+msgstr "Instances nodeinfo"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instances_nodeinfo > :enabled"
+msgid "Enabled"
+msgstr "Enabled"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:libre_translate"
+msgid "Libre translate"
+msgstr "Libre translate"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:libre_translate > :api_key"
+msgid "Api key"
+msgstr "Api key"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:libre_translate > :url"
+msgid "Url"
+msgstr "Url"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf > :transparency_obfuscate_domains"
+msgid "MRF domain obfuscation"
+msgstr "MRF domain obfuscation"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_inline_quote"
+msgid "MRF Inline Quote"
+msgstr "MRF Inline Quote"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_inline_quote > :prefix"
+msgid "Prefix"
+msgstr "Prefix"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_simple > :handle_threads"
+msgid "Apply to entire threads"
+msgstr "Apply to entire threads"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:translator"
+msgid "Translator"
+msgstr "Translator"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:translator > :enabled"
+msgid "Enabled"
+msgstr "Enabled"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:translator > :module"
+msgid "Module"
+msgstr "Module"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:workers > :timeout"
+msgid "Timeout"
+msgstr "Timeout"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Search"
+msgid "Search"
+msgstr "Search"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Search > :module"
+msgid "Module"
+msgstr "Module"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Search.Elasticsearch.Cluster"
+msgid "Elasticsearch"
+msgstr "Elasticsearch"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :api"
+msgid "Api"
+msgstr "Api"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :indexes"
+msgid "Indexes"
+msgstr "Indexes"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :indexes > :activities"
+msgid "Activities"
+msgstr "Activities"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :indexes > :activities > :bulk_page_size"
+msgid "Bulk page size"
+msgstr "Bulk page size"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :indexes > :activities > :bulk_wait_interval"
+msgid "Bulk wait interval"
+msgstr "Bulk wait interval"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :indexes > :activities > :settings"
+msgid "Settings"
+msgstr "Settings"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :indexes > :activities > :sources"
+msgid "Sources"
+msgstr "Sources"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :indexes > :activities > :store"
+msgid "Store"
+msgstr "Store"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :json_library"
+msgid "Json library"
+msgstr "Json library"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :password"
+msgid "Password"
+msgstr "Password"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :url"
+msgid "Url"
+msgstr "Url"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :username"
+msgid "Username"
+msgstr "Username"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Search.Meilisearch"
+msgid "Pleroma.Search.Meilisearch"
+msgstr "Pleroma.Search.Meilisearch"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Search.Meilisearch > :initial_indexing_chunk_size"
+msgid "Initial indexing chunk size"
+msgstr "Initial indexing chunk size"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Search.Meilisearch > :private_key"
+msgid "Private key"
+msgstr "Private key"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Search.Meilisearch > :url"
+msgid "Url"
+msgstr "Url"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Web.Metadata.Providers.Theme"
+msgid "Pleroma.Web.Metadata.Providers.Theme"
+msgstr "Pleroma.Web.Metadata.Providers.Theme"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-Pleroma.Web.Metadata.Providers.Theme > :theme_color"
+msgid "Theme color"
+msgstr "Theme color"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :healthcheck"
+msgid "If enabled, system data will be shown on `/api/v1/pleroma/healthcheck`"
+msgstr "If enabled, system data will be shown on `/api/v1/pleroma/healthcheck`"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:frontends > :pickable"
+msgid "A list containing all frontends users can pick as their preference, format is :name/:ref, e.g pleroma-fe/stable."
+msgstr ""
+"A list containing all frontends users can pick as their preference, format "
+"is :name/:ref, e.g pleroma-fe/stable."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:instance > :federated_timeline_available"
+msgid "Let people view the 'firehose' feed of all public statuses from all instances."
+msgstr ""
+"Let people view the 'firehose' feed of all public statuses from all "
+"instances."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:media_proxy > :blocklist"
+msgid "List of hosts with scheme which will not go through the MediaProxy, and will not be explicitly allowed by the Content-Security-Policy.\nThis is to be used for instances where you do not want their media to go through your server or to be accessed by clients.\n"
+msgstr ""
+"List of hosts with scheme which will not go through the MediaProxy, and will "
+"not be explicitly allowed by the Content-Security-Policy.\n"
+"This is to be used for instances where you do not want their media to go "
+"through your server or to be accessed by clients.\n"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:media_proxy > :whitelist"
+msgid "List of hosts with scheme to bypass the MediaProxy.\n\nThe media will be fetched by the client, directly from the remote server.\n\nTo allow this, it will Content-Security-Policy exceptions for each instance listed.\n\nThis is to be used for instances you trust and do not want to cache media for.\n"
+msgstr ""
+"List of hosts with scheme to bypass the MediaProxy.\n"
+"\n"
+"The media will be fetched by the client, directly from the remote server.\n"
+"\n"
+"To allow this, it will Content-Security-Policy exceptions for each instance "
+"listed.\n"
+"\n"
+"This is to be used for instances you trust and do not want to cache media "
+"for.\n"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_reject_newly_created_account_notes"
+msgid "Reject notes from accounts created too recently"
+msgstr "Reject notes from accounts created too recently"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:mrf_reject_newly_created_account_notes > :age"
+msgid "Time below which to reject (in seconds)"
+msgstr "Time below which to reject (in seconds)"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config description at :pleroma-:restrict_unauthenticated > :timelines > :bubble"
+msgid "Disallow viewing the bubble timeline."
+msgstr "Disallow viewing the bubble timeline."
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:frontends > :pickable"
+msgid "Pickable"
+msgstr "Pickable"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:instance > :federated_timeline_available"
+msgid "Federated timeline available"
+msgstr "Federated timeline available"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:media_proxy > :blocklist"
+msgid "Blocklist"
+msgstr "Blocklist"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_reject_newly_created_account_notes"
+msgid "MRF Reject New Accounts"
+msgstr "MRF Reject New Accounts"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:mrf_reject_newly_created_account_notes > :age"
+msgid "Age"
+msgstr "Age"
+
+#: lib/pleroma/docs/translator.ex:5
+#, elixir-autogen, elixir-format, fuzzy
+msgctxt "config label at :pleroma-:restrict_unauthenticated > :timelines > :bubble"
+msgid "Bubble"
+msgstr "Bubble"
diff --git a/priv/gettext/pt/LC_MESSAGES/errors.po b/priv/gettext/pt/LC_MESSAGES/errors.po
new file mode 100644
index 000000000..b38ed04cd
--- /dev/null
+++ b/priv/gettext/pt/LC_MESSAGES/errors.po
@@ -0,0 +1,657 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2024-03-18 12:55+0000\n"
+"PO-Revision-Date: 2024-03-19 01:10+0000\n"
+"Last-Translator: Jammer Lammer \n"
+"Language-Team: Portuguese \n"
+"Language: pt\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n > 1;\n"
+"X-Generator: Weblate 4.18.2\n"
+
+## This file is a PO Template file.
+##
+## `msgid`s here are often extracted from source code.
+## Add new translations manually only if they're dynamic
+## translations that can't be statically extracted.
+##
+## Run `mix gettext.extract` to bring this file up to
+## date. Leave `msgstr`s empty as changing them here as no
+## effect: edit them in PO (`.po`) files instead.
+## From Ecto.Changeset.cast/4
+msgid "can't be blank"
+msgstr "não pode estar em branco"
+
+## From Ecto.Changeset.unique_constraint/3
+msgid "has already been taken"
+msgstr "já está em uso"
+
+## From Ecto.Changeset.put_change/3
+msgid "is invalid"
+msgstr "é inválido"
+
+## From Ecto.Changeset.validate_format/3
+msgid "has invalid format"
+msgstr "tem um formato inválido"
+
+## From Ecto.Changeset.validate_subset/3
+msgid "has an invalid entry"
+msgstr "tem uma entrada inválida"
+
+## From Ecto.Changeset.validate_exclusion/3
+msgid "is reserved"
+msgstr "está reservado"
+
+## From Ecto.Changeset.validate_confirmation/3
+msgid "does not match confirmation"
+msgstr "a confirmação não coincide"
+
+## From Ecto.Changeset.no_assoc_constraint/3
+msgid "is still associated with this entry"
+msgstr "ainda está associado com essa entrada"
+
+msgid "are still associated with this entry"
+msgstr "ainda estão associados com essa entrada"
+
+## From Ecto.Changeset.validate_length/3
+msgid "should be %{count} character(s)"
+msgid_plural "should be %{count} character(s)"
+msgstr[0] "deve ser %{count} caractere"
+msgstr[1] "deve ser %{count} caracteres"
+
+msgid "should have %{count} item(s)"
+msgid_plural "should have %{count} item(s)"
+msgstr[0] "deve ter %{count} item"
+msgstr[1] "deve ter %{count} itens"
+
+msgid "should be at least %{count} character(s)"
+msgid_plural "should be at least %{count} character(s)"
+msgstr[0] "deve ter pelo menos %{count} caractere"
+msgstr[1] "deve ter pelo menos %{count} caracteres"
+
+msgid "should have at least %{count} item(s)"
+msgid_plural "should have at least %{count} item(s)"
+msgstr[0] "deve ter pelo menos %{count} item"
+msgstr[1] "deve ter pelo menos %{count} itens"
+
+msgid "should be at most %{count} character(s)"
+msgid_plural "should be at most %{count} character(s)"
+msgstr[0] "deve ter no máximo %{count} caractere"
+msgstr[1] "deve ter no máximo %{count} caracteres"
+
+msgid "should have at most %{count} item(s)"
+msgid_plural "should have at most %{count} item(s)"
+msgstr[0] "deve ter no máximo %{count} item"
+msgstr[1] "deve ter no máximo %{count} itens"
+
+## From Ecto.Changeset.validate_number/3
+msgid "must be less than %{number}"
+msgstr "deve ser menor que %{number}"
+
+msgid "must be greater than %{number}"
+msgstr "deve ser maior que %{number}"
+
+msgid "must be less than or equal to %{number}"
+msgstr "deve ser menor ou igual a %{number}"
+
+msgid "must be greater than or equal to %{number}"
+msgstr "deve ser maior ou igual a %{number}"
+
+msgid "must be equal to %{number}"
+msgstr "deve ser igual a %{number}"
+
+#: lib/pleroma/web/common_api.ex:503
+#, elixir-autogen, elixir-format
+msgid "Account not found"
+msgstr "Conta não encontrada"
+
+#: lib/pleroma/web/common_api.ex:263
+#, elixir-autogen, elixir-format
+msgid "Already voted"
+msgstr "Já votado"
+
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:427
+#, elixir-autogen, elixir-format
+msgid "Bad request"
+msgstr "Má requisição"
+
+#: lib/pleroma/web/controller_helper.ex:105
+#, elixir-autogen, elixir-format
+msgid "Can't display this activity"
+msgstr "Não é possível mostrar essa atividade"
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:335
+#, elixir-autogen, elixir-format
+msgid "Can't find user"
+msgstr "Não é possível encontrar o usuário"
+
+#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:61
+#, elixir-autogen, elixir-format
+msgid "Can't get favorites"
+msgstr "Não é possível obter os favoritos"
+
+#: lib/pleroma/web/common_api/utils.ex:480
+#, elixir-autogen, elixir-format
+msgid "Cannot post an empty status without attachments"
+msgstr "Não é possível publicar um status vazio sem anexos"
+
+#: lib/pleroma/web/common_api/utils.ex:468
+#, elixir-autogen, elixir-format
+msgid "Comment must be up to %{max_size} characters"
+msgstr "Comentários devem ter no máximo %{max_size} caracteres"
+
+#: lib/pleroma/config_db.ex:199
+#, elixir-autogen, elixir-format
+msgid "Config with params %{params} not found"
+msgstr "Configuração com parâmetros %{params} não encontrada"
+
+#: lib/pleroma/web/common_api.ex:114
+#: lib/pleroma/web/common_api.ex:118
+#, elixir-autogen, elixir-format
+msgid "Could not delete"
+msgstr "Não foi possível apagar"
+
+#: lib/pleroma/web/common_api.ex:164
+#, elixir-autogen, elixir-format
+msgid "Could not favorite"
+msgstr "Não foi possível favoritar"
+
+#: lib/pleroma/web/common_api.ex:201
+#, elixir-autogen, elixir-format
+msgid "Could not unfavorite"
+msgstr "Não foi possível eliminar favorito"
+
+#: lib/pleroma/web/common_api.ex:149
+#, elixir-autogen, elixir-format
+msgid "Could not unrepeat"
+msgstr "Não foi possível republicar"
+
+#: lib/pleroma/web/common_api.ex:510
+#: lib/pleroma/web/common_api.ex:519
+#, elixir-autogen, elixir-format
+msgid "Could not update state"
+msgstr "Não foi possível atualizar estado"
+
+#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:278
+#, elixir-autogen, elixir-format
+msgid "Error."
+msgstr "Erro."
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:104
+#, elixir-autogen, elixir-format
+msgid "Invalid CAPTCHA"
+msgstr "CAPTCHA inválido"
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:143
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:660
+#, elixir-autogen, elixir-format
+msgid "Invalid credentials"
+msgstr "Credenciais inválidas"
+
+#: lib/pleroma/web/plugs/ensure_authenticated_plug.ex:42
+#, elixir-autogen, elixir-format
+msgid "Invalid credentials."
+msgstr "Credenciais inválidas."
+
+#: lib/pleroma/web/common_api.ex:284
+#, elixir-autogen, elixir-format
+msgid "Invalid indices"
+msgstr "Índices inválidos"
+
+#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:29
+#, elixir-autogen, elixir-format
+msgid "Invalid parameters"
+msgstr "Parâmetros inválidos"
+
+#: lib/pleroma/web/common_api/utils.ex:376
+#, elixir-autogen, elixir-format
+msgid "Invalid password."
+msgstr "Senha inválida."
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:265
+#, elixir-autogen, elixir-format
+msgid "Invalid request"
+msgstr "Requisição inválida"
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:107
+#, elixir-autogen, elixir-format
+msgid "Kocaptcha service unavailable"
+msgstr "Serviço de Kocaptcha indisponível"
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:139
+#, elixir-autogen, elixir-format
+msgid "Missing parameters"
+msgstr "Parâmetros faltando"
+
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:151
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:177
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:219
+#, elixir-autogen, elixir-format
+msgid "No such permission_group"
+msgstr "Não a tal permission_group"
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:480
+#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:11
+#: lib/pleroma/web/feed/tag_controller.ex:16
+#: lib/pleroma/web/feed/user_controller.ex:70
+#: lib/pleroma/web/o_status/o_status_controller.ex:135
+#: lib/pleroma/web/plugs/uploaded_media.ex:83
+#, elixir-autogen, elixir-format
+msgid "Not found"
+msgstr "Não encontrado"
+
+#: lib/pleroma/web/common_api.ex:255
+#, elixir-autogen, elixir-format
+msgid "Poll's author can't vote"
+msgstr "Autor da enquete não pode votar"
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:478
+#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20
+#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:39
+#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:51
+#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:52
+#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71
+#, elixir-autogen, elixir-format
+msgid "Record not found"
+msgstr "Registro não encontrado"
+
+#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:35
+#: lib/pleroma/web/feed/user_controller.ex:79
+#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:42
+#: lib/pleroma/web/o_status/o_status_controller.ex:141
+#, elixir-autogen, elixir-format
+msgid "Something went wrong"
+msgstr "Algo deu errado"
+
+#: lib/pleroma/web/common_api/activity_draft.ex:156
+#, elixir-autogen, elixir-format
+msgid "The message visibility must be direct"
+msgstr "A visibilidade da mensagem deve ser direta"
+
+#: lib/pleroma/web/common_api/utils.ex:490
+#, elixir-autogen, elixir-format
+msgid "The status is over the character limit"
+msgstr "O status está acima do limite de caracteres"
+
+#: lib/pleroma/web/plugs/ensure_public_or_authenticated_plug.ex:36
+#, elixir-autogen, elixir-format
+msgid "This resource requires authentication."
+msgstr "Esse recurso requer autenticação."
+
+#: lib/pleroma/web/plugs/rate_limiter.ex:214
+#, elixir-autogen, elixir-format
+msgid "Throttled"
+msgstr "Limitado"
+
+#: lib/pleroma/web/common_api.ex:285
+#, elixir-autogen, elixir-format
+msgid "Too many choices"
+msgstr "Muitas opções"
+
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:248
+#, elixir-autogen, elixir-format
+msgid "You can't revoke your own admin status."
+msgstr "Você não pode revogar seu próprio status de administrador."
+
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:267
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:358
+#, elixir-autogen, elixir-format
+msgid "Your account is currently disabled"
+msgstr "Sua conta está atualmente desativada"
+
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:229
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:381
+#, elixir-autogen, elixir-format
+msgid "Your login is missing a confirmed e-mail address"
+msgstr "Sua conta não possui uma endereço de email confirmado"
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:368
+#, elixir-autogen, elixir-format
+msgid "can't read inbox of %{nickname} as %{as_nickname}"
+msgstr "não é possível ler o inbox de %{nickname} como %{as_nickname}"
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:467
+#, elixir-autogen, elixir-format
+msgid "can't update outbox of %{nickname} as %{as_nickname}"
+msgstr "não é possível atualizar o inbox de %{nickname} como %{nickname}"
+
+#: lib/pleroma/web/common_api.ex:455
+#, elixir-autogen, elixir-format
+msgid "conversation is already muted"
+msgstr "a conversa já está silenciada"
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:486
+#, elixir-autogen, elixir-format
+msgid "error"
+msgstr "erro"
+
+#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:34
+#, elixir-autogen, elixir-format
+msgid "mascots can only be images"
+msgstr "mascotes só podem ser imagens"
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:62
+#, elixir-autogen, elixir-format
+msgid "not found"
+msgstr "não encontrado"
+
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:462
+#, elixir-autogen, elixir-format
+msgid "Bad OAuth request."
+msgstr "Requisição de OAuth inválida."
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:113
+#, elixir-autogen, elixir-format
+msgid "CAPTCHA already used"
+msgstr "CAPTCHA já está em uso"
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:110
+#, elixir-autogen, elixir-format
+msgid "CAPTCHA expired"
+msgstr "CAPTCHA expirado"
+
+#: lib/pleroma/web/plugs/uploaded_media.ex:56
+#, elixir-autogen, elixir-format
+msgid "Failed"
+msgstr "Falhou"
+
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:478
+#, elixir-autogen, elixir-format
+msgid "Failed to authenticate: %{message}."
+msgstr "Falha ao autenticar: %{message}."
+
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:509
+#, elixir-autogen, elixir-format
+msgid "Failed to set up user account."
+msgstr "Falha ao definir conta de usuário."
+
+#: lib/pleroma/web/plugs/o_auth_scopes_plug.ex:37
+#, elixir-autogen, elixir-format
+msgid "Insufficient permissions: %{permissions}."
+msgstr "Permissões insuficientes: %{permissions}."
+
+#: lib/pleroma/web/plugs/uploaded_media.ex:98
+#, elixir-autogen, elixir-format
+msgid "Internal Error"
+msgstr "Erro Interno"
+
+#: lib/pleroma/web/o_auth/fallback_controller.ex:22
+#: lib/pleroma/web/o_auth/fallback_controller.ex:29
+#, elixir-autogen, elixir-format
+msgid "Invalid Username/Password"
+msgstr "Usuário/Senha Inválidos"
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:116
+#, elixir-autogen, elixir-format
+msgid "Invalid answer data"
+msgstr "dado de resposta inválido"
+
+#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:40
+#, elixir-autogen, elixir-format
+msgid "Nodeinfo schema version not handled"
+msgstr "Esquema de versão do Nodeinfo não tratado"
+
+#: lib/pleroma/web/o_auth/fallback_controller.ex:14
+#, elixir-autogen, elixir-format
+msgid "Unknown error, please check the details and try again."
+msgstr "Erro desconhecido. Por favor, cheque os detalhes e tente novamente."
+
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:158
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:204
+#, elixir-autogen, elixir-format
+msgid "Unlisted redirect_uri."
+msgstr "redirect_uri Não listada."
+
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:458
+#, elixir-autogen, elixir-format
+msgid "Unsupported OAuth provider: %{provider}."
+msgstr "Provedor de OAuth não suportado: %{provider}."
+
+#: lib/pleroma/uploaders/uploader.ex:74
+#, elixir-autogen, elixir-format
+msgid "Uploader callback timeout"
+msgstr "Tempo esgotado para callback do uploader"
+
+#: lib/pleroma/web/uploader_controller.ex:23
+#, elixir-autogen, elixir-format
+msgid "bad request"
+msgstr "má requisição"
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:101
+#, elixir-autogen, elixir-format
+msgid "CAPTCHA Error"
+msgstr "Erro no CAPTCHA"
+
+#: lib/pleroma/web/common_api.ex:213
+#, elixir-autogen, elixir-format
+msgid "Could not add reaction emoji"
+msgstr "Não foi possível adicionar emoji de reação"
+
+#: lib/pleroma/web/common_api.ex:224
+#, elixir-autogen, elixir-format
+msgid "Could not remove reaction emoji"
+msgstr "Não foi possível remover emoji de reação"
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:127
+#, elixir-autogen, elixir-format
+msgid "Invalid CAPTCHA (Missing parameter: %{name})"
+msgstr "CAPTCHA inválido (Parâmetro faltando: %{name})"
+
+#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:96
+#, elixir-autogen, elixir-format
+msgid "List not found"
+msgstr "Lista não encontrada"
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:150
+#, elixir-autogen, elixir-format
+msgid "Missing parameter: %{name}"
+msgstr "Parâmetro faltando: %{name}"
+
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:256
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:371
+#, elixir-autogen, elixir-format
+msgid "Password reset is required"
+msgstr "Redefinição de senha é necessária"
+
+#: lib/pleroma/tests/auth_test_controller.ex:9
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/announcement_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/config_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/frontend_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/instance_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/instance_document_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/invite_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/relay_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/report_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/status_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/user_controller.ex:6
+#: lib/pleroma/web/akkoma_api/controllers/frontend_settings_controller.ex:2
+#: lib/pleroma/web/akkoma_api/controllers/frontend_switcher.ex:2
+#: lib/pleroma/web/akkoma_api/controllers/metrics_controller.ex:2
+#: lib/pleroma/web/akkoma_api/controllers/translation_controller.ex:2
+#: lib/pleroma/web/controller_helper.ex:6
+#: lib/pleroma/web/embed_controller.ex:6
+#: lib/pleroma/web/fallback/redirect_controller.ex:6
+#: lib/pleroma/web/feed/tag_controller.ex:6
+#: lib/pleroma/web/feed/user_controller.ex:6
+#: lib/pleroma/web/mailer/subscription_controller.ex:6
+#: lib/pleroma/web/manifest_controller.ex:6
+#: lib/pleroma/web/masto_fe_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/announcement_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/app_controller.ex:11
+#: lib/pleroma/web/mastodon_api/controllers/auth_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/directory_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/filter_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/instance_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/marker_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex:14
+#: lib/pleroma/web/mastodon_api/controllers/media_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/notification_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/report_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/search_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:7
+#: lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:3
+#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:6
+#: lib/pleroma/web/media_proxy/media_proxy_controller.ex:6
+#: lib/pleroma/web/mongoose_im/mongoose_im_controller.ex:6
+#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:6
+#: lib/pleroma/web/o_auth/fallback_controller.ex:6
+#: lib/pleroma/web/o_auth/mfa_controller.ex:10
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:6
+#: lib/pleroma/web/o_status/o_status_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/app_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/backup_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/instances_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/notification_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/report_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex:7
+#: lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex:6
+#: lib/pleroma/web/static_fe/static_fe_controller.ex:6
+#: lib/pleroma/web/twitter_api/controller.ex:6
+#: lib/pleroma/web/twitter_api/controllers/password_controller.ex:10
+#: lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex:6
+#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:6
+#: lib/pleroma/web/uploader_controller.ex:6
+#: lib/pleroma/web/web_finger/web_finger_controller.ex:6
+#, elixir-autogen, elixir-format
+msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
+msgstr ""
+"Violação de segurança: Verificação do escopo do OAuth tanto não lidou quanto "
+"não explicitamente pulou."
+
+#: lib/pleroma/web/plugs/ensure_authenticated_plug.ex:32
+#, elixir-autogen, elixir-format
+msgid "Two-factor authentication enabled, you must use a access token."
+msgstr ""
+"Autenticação de dois-fatores ativada, você deve usar um token de acesso."
+
+#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
+#, elixir-autogen, elixir-format
+msgid "Web push subscription is disabled on this Pleroma instance"
+msgstr "Inscrição de web push está desativada nessa instância do Akkoma"
+
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:214
+#, elixir-autogen, elixir-format
+msgid "You can't revoke your own admin/moderator status."
+msgstr "Você não pode revogar o seu próprio status de admin/moderador."
+
+#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:202
+#, elixir-autogen, elixir-format
+msgid "authorization required for timeline view"
+msgstr "autorização necessária para visualização da linha do tempo"
+
+#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24
+#, elixir-autogen, elixir-format
+msgid "Access denied"
+msgstr "Acesso negado"
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:332
+#, elixir-autogen, elixir-format
+msgid "This API requires an authenticated user"
+msgstr "Essa API necessita de um usuário autentica"
+
+#: lib/pleroma/web/plugs/ensure_staff_privileged_plug.ex:26
+#: lib/pleroma/web/plugs/user_is_admin_plug.ex:21
+#, elixir-autogen, elixir-format
+msgid "User is not an admin."
+msgstr "Usuário não é um administrador."
+
+#: lib/pleroma/user/backup.ex:73
+#, elixir-format
+msgid "Last export was less than a day ago"
+msgid_plural "Last export was less than %{days} days ago"
+msgstr[0] "Última exportação foi a menos de um dia atrás"
+msgstr[1] "Última exportação foi a menos de %{days} atrás"
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:399
+#, elixir-autogen, elixir-format
+msgid "Character limit (%{limit} characters) exceeded, contains %{length} characters"
+msgstr ""
+"Limite de caracteres (%{limit} caracteres) excedido, pois contém %{length} "
+"caracteres"
+
+#: lib/pleroma/web/plugs/ensure_staff_privileged_plug.ex:33
+#: lib/pleroma/web/plugs/user_is_staff_plug.ex:20
+#, elixir-autogen, elixir-format
+msgid "User is not a staff member."
+msgstr "Usuário não é um membro da staff."
+
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:391
+#, elixir-autogen, elixir-format
+msgid "Your account is awaiting approval."
+msgstr "Sua conta aguarda aprovação."
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:256
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:259
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:262
+#, elixir-autogen, elixir-format
+msgid "File is too large"
+msgstr "Arquivo muito grande"
+
+#: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:37
+#: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:48
+#: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:59
+#, elixir-autogen, elixir-format
+msgid "Hashtag not found"
+msgstr "Hashtag não encontrada"
+
+#: lib/pleroma/web/common_api/activity_draft.ex:144
+#, elixir-autogen, elixir-format
+msgid "Invalid language"
+msgstr "Idioma inválido"
+
+#: lib/pleroma/web/o_auth/o_auth_controller.ex:218
+#, elixir-autogen, elixir-format
+msgid "This action is outside of authorized scopes"
+msgstr "Essa ação está fora do escopo autorizado"
+
+#: lib/pleroma/web/common_api/activity_draft.ex:129
+#, elixir-autogen, elixir-format
+msgid "You can only quote public or unlisted statuses"
+msgstr "Você pode apenas citar status públicos ou não-listados"
+
+#: lib/pleroma/web/common_api/activity_draft.ex:126
+#, elixir-autogen, elixir-format
+msgid "You can't quote a status that doesn't exist"
+msgstr "Você não pode citar um status que não existe"
+
+#: lib/pleroma/web/embed_controller.ex:35
+#, elixir-autogen, elixir-format
+msgid "Federated posts cannot be embedded"
+msgstr "Publicações federadas não podem ser embutidas"
+
+#: lib/pleroma/web/embed_controller.ex:38
+#, elixir-autogen, elixir-format
+msgid "Not authorized to view this post"
+msgstr "Não autorizado a ver essa publicação"
+
+#: lib/pleroma/web/embed_controller.ex:32
+#, elixir-autogen, elixir-format
+msgid "Post not found"
+msgstr "Publicação não encontrada"
diff --git a/priv/gettext/pt/LC_MESSAGES/posix_errors.po b/priv/gettext/pt/LC_MESSAGES/posix_errors.po
new file mode 100644
index 000000000..3e11a9987
--- /dev/null
+++ b/priv/gettext/pt/LC_MESSAGES/posix_errors.po
@@ -0,0 +1,163 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2024-03-17 22:50+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: pt\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 3.9.2\n"
+
+## This file is a PO Template file.
+##
+## `msgid`s here are often extracted from source code.
+## Add new translations manually only if they're dynamic
+## translations that can't be statically extracted.
+##
+## Run `mix gettext.extract` to bring this file up to
+## date. Leave `msgstr`s empty as changing them here as no
+## effect: edit them in PO (`.po`) files instead.
+msgid "eperm"
+msgstr ""
+
+msgid "eacces"
+msgstr ""
+
+msgid "eagain"
+msgstr ""
+
+msgid "ebadf"
+msgstr ""
+
+msgid "ebadmsg"
+msgstr ""
+
+msgid "ebusy"
+msgstr ""
+
+msgid "edeadlk"
+msgstr ""
+
+msgid "edeadlock"
+msgstr ""
+
+msgid "edquot"
+msgstr ""
+
+msgid "eexist"
+msgstr ""
+
+msgid "efault"
+msgstr ""
+
+msgid "efbig"
+msgstr ""
+
+msgid "eftype"
+msgstr ""
+
+msgid "eintr"
+msgstr ""
+
+msgid "einval"
+msgstr ""
+
+msgid "eio"
+msgstr ""
+
+msgid "eisdir"
+msgstr ""
+
+msgid "eloop"
+msgstr ""
+
+msgid "emfile"
+msgstr ""
+
+msgid "emlink"
+msgstr ""
+
+msgid "emultihop"
+msgstr ""
+
+msgid "enametoolong"
+msgstr ""
+
+msgid "enfile"
+msgstr ""
+
+msgid "enobufs"
+msgstr ""
+
+msgid "enodev"
+msgstr ""
+
+msgid "enolck"
+msgstr ""
+
+msgid "enolink"
+msgstr ""
+
+msgid "enoent"
+msgstr ""
+
+msgid "enomem"
+msgstr ""
+
+msgid "enospc"
+msgstr ""
+
+msgid "enosr"
+msgstr ""
+
+msgid "enostr"
+msgstr ""
+
+msgid "enosys"
+msgstr ""
+
+msgid "enotblk"
+msgstr ""
+
+msgid "enotdir"
+msgstr ""
+
+msgid "enotsup"
+msgstr ""
+
+msgid "enxio"
+msgstr ""
+
+msgid "eopnotsupp"
+msgstr ""
+
+msgid "eoverflow"
+msgstr ""
+
+msgid "epipe"
+msgstr ""
+
+msgid "erange"
+msgstr ""
+
+msgid "erofs"
+msgstr ""
+
+msgid "espipe"
+msgstr ""
+
+msgid "esrch"
+msgstr ""
+
+msgid "estale"
+msgstr ""
+
+msgid "etxtbsy"
+msgstr ""
+
+msgid "exdev"
+msgstr ""
diff --git a/priv/gettext/pt/LC_MESSAGES/static_pages.po b/priv/gettext/pt/LC_MESSAGES/static_pages.po
new file mode 100644
index 000000000..f64aecfba
--- /dev/null
+++ b/priv/gettext/pt/LC_MESSAGES/static_pages.po
@@ -0,0 +1,616 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2024-03-17 22:51+0000\n"
+"PO-Revision-Date: 2024-03-19 01:10+0000\n"
+"Last-Translator: Jammer Lammer \n"
+"Language-Team: Portuguese \n"
+"Language: pt\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n > 1;\n"
+"X-Generator: Weblate 4.18.2\n"
+
+## This file is a PO Template file.
+##
+## "msgid"s here are often extracted from source code.
+## Add new translations manually only if they're dynamic
+## translations that can't be statically extracted.
+##
+## Run "mix gettext.extract" to bring this file up to
+## date. Leave "msgstr"s empty as changing them here as no
+## effect: edit them in PO (.po) files instead.
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:9
+#, elixir-autogen, elixir-format
+msgctxt "remote follow authorization button"
+msgid "Authorize"
+msgstr "Autorizar"
+
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:2
+#, elixir-autogen, elixir-format
+msgctxt "remote follow error"
+msgid "Error fetching user"
+msgstr "Erro ao buscar usuário"
+
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:4
+#, elixir-autogen, elixir-format
+msgctxt "remote follow header"
+msgid "Remote follow"
+msgstr "Seguimento remoto"
+
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:8
+#, elixir-autogen, elixir-format
+msgctxt "placeholder text for auth code entry"
+msgid "Authentication code"
+msgstr "Código de autenticação"
+
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:10
+#, elixir-autogen, elixir-format
+msgctxt "placeholder text for password entry"
+msgid "Password"
+msgstr "Senha"
+
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:8
+#, elixir-autogen, elixir-format
+msgctxt "placeholder text for username entry"
+msgid "Username"
+msgstr "Nome de usuário"
+
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:13
+#, elixir-autogen, elixir-format
+msgctxt "remote follow authorization button for login"
+msgid "Authorize"
+msgstr "Autorizar"
+
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:12
+#, elixir-autogen, elixir-format
+msgctxt "remote follow authorization button for mfa"
+msgid "Authorize"
+msgstr "Autorizar"
+
+#: lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex:2
+#, elixir-autogen, elixir-format
+msgctxt "remote follow error"
+msgid "Error following account"
+msgstr "Erro ao seguir conta"
+
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:4
+#, elixir-autogen, elixir-format
+msgctxt "remote follow header, need login"
+msgid "Log in to follow"
+msgstr "Inicia a sessão para seguir"
+
+#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:4
+#, elixir-autogen, elixir-format
+msgctxt "remote follow mfa header"
+msgid "Two-factor authentication"
+msgstr "Autenticação de dois-fatores"
+
+#: lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex:4
+#, elixir-autogen, elixir-format
+msgctxt "remote follow success"
+msgid "Account followed!"
+msgstr "Conta seguida!"
+
+#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:7
+#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:7
+#, elixir-autogen, elixir-format
+msgctxt "placeholder text for account id"
+msgid "Your account ID, e.g. lain@quitter.se"
+msgstr "Sua ID de conta, ex. lain@quitter.se"
+
+#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:8
+#, elixir-autogen, elixir-format
+msgctxt "remote follow authorization button for following with a remote account"
+msgid "Follow"
+msgstr "Seguir"
+
+#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:2
+#, elixir-autogen, elixir-format
+msgctxt "remote follow error"
+msgid "Error: %{error}"
+msgstr "Erro: %{error}"
+
+#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:4
+#, elixir-autogen, elixir-format
+msgctxt "remote follow header"
+msgid "Remotely follow %{nickname}"
+msgstr "Seguir remotamente %{nickname}"
+
+#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:12
+#, elixir-autogen, elixir-format
+msgctxt "password reset button"
+msgid "Reset"
+msgstr "Resetar"
+
+#: lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex:4
+#, elixir-autogen, elixir-format
+msgctxt "password reset failed homepage link"
+msgid "Homepage"
+msgstr "Página inicial"
+
+#: lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex:1
+#, elixir-autogen, elixir-format
+msgctxt "password reset failed message"
+msgid "Password reset failed"
+msgstr "Falha ao redefinir senha"
+
+#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:8
+#, elixir-autogen, elixir-format
+msgctxt "password reset form confirm password prompt"
+msgid "Confirmation"
+msgstr "Confirmação"
+
+#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:4
+#, elixir-autogen, elixir-format
+msgctxt "password reset form password prompt"
+msgid "Password"
+msgstr "Senha"
+
+#: lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex:1
+#, elixir-autogen, elixir-format
+msgctxt "password reset invalid token message"
+msgid "Invalid Token"
+msgstr "Token Inválido"
+
+#: lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex:2
+#, elixir-autogen, elixir-format
+msgctxt "password reset successful homepage link"
+msgid "Homepage"
+msgstr "Página inicial"
+
+#: lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex:1
+#, elixir-autogen, elixir-format
+msgctxt "password reset successful message"
+msgid "Password changed!"
+msgstr "Senha alterada!"
+
+#: lib/pleroma/web/templates/feed/feed/tag.atom.eex:15
+#: lib/pleroma/web/templates/feed/feed/tag.rss.eex:7
+#, elixir-autogen, elixir-format
+msgctxt "tag feed description"
+msgid "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse."
+msgstr ""
+"Estas são notas públicas marcadas com #%{tag}. Você pode interagir com elas "
+"se você tem uma conta em qualquer lugar no fediverse."
+
+#: lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex:3
+#, elixir-autogen, elixir-format
+msgctxt "oauth authorization exists page title"
+msgid "Authorization exists"
+msgstr "Existe autorização"
+
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:37
+#, elixir-autogen, elixir-format
+msgctxt "oauth authorize approve button"
+msgid "Approve"
+msgstr "Aprovar"
+
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:35
+#, elixir-autogen, elixir-format
+msgctxt "oauth authorize cancel button"
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:26
+#, elixir-autogen, elixir-format
+msgctxt "oauth authorize message"
+msgid "Application %{client_name} is requesting access to your account."
+msgstr ""
+"A aplicação %{client_name} está requisitando acesso à sua "
+"conta."
+
+#: lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex:3
+#, elixir-autogen, elixir-format
+msgctxt "oauth authorized page title"
+msgid "Successfully authorized"
+msgstr "Autorização feita com sucesso"
+
+#: lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex:1
+#, elixir-autogen, elixir-format
+msgctxt "oauth external provider page title"
+msgid "Sign in with external provider"
+msgstr "Iniciar sessão com provedor externo"
+
+#: lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex:13
+#, elixir-autogen, elixir-format
+msgctxt "oauth external provider sign in button"
+msgid "Sign in with %{strategy}"
+msgstr "Iniciar sessão com %{strategy}"
+
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:59
+#, elixir-autogen, elixir-format
+msgctxt "oauth login button"
+msgid "Log In"
+msgstr "Iniciar sessão"
+
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:56
+#, elixir-autogen, elixir-format
+msgctxt "oauth login password prompt"
+msgid "Password"
+msgstr "Senha"
+
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:52
+#, elixir-autogen, elixir-format
+msgctxt "oauth login username prompt"
+msgid "Username"
+msgstr "Usuário"
+
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:44
+#, elixir-autogen, elixir-format
+msgctxt "oauth register nickname prompt"
+msgid "Pleroma Handle"
+msgstr "Usuário Pleroma"
+
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:42
+#, elixir-autogen, elixir-format
+msgctxt "oauth register nickname unchangeable warning"
+msgid "Choose carefully! You won't be able to change this later. You will be able to change your display name, though."
+msgstr ""
+"Escolha com cautela! Você não será capaz de mudar isso depois. No entanto, "
+"você será capaz de mudar o seu nome à mostra."
+
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:18
+#, elixir-autogen, elixir-format
+msgctxt "oauth register page email prompt"
+msgid "Email"
+msgstr "Email"
+
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:10
+#, elixir-autogen, elixir-format
+msgctxt "oauth register page fill form prompt"
+msgid "If you'd like to register a new account, please provide the details below."
+msgstr ""
+"Se você deseja registrar uma nova conta, por favor, provenha os detalhes "
+"abaixo."
+
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:35
+#, elixir-autogen, elixir-format
+msgctxt "oauth register page login button"
+msgid "Proceed as existing user"
+msgstr "Proceder como um usuário já existente"
+
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:31
+#, elixir-autogen, elixir-format
+msgctxt "oauth register page login password prompt"
+msgid "Password"
+msgstr "Senha"
+
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:24
+#, elixir-autogen, elixir-format
+msgctxt "oauth register page login prompt"
+msgid "Alternatively, sign in to connect to existing account."
+msgstr "Alternativamente, inicia uma sessão em uma conta existente."
+
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:27
+#, elixir-autogen, elixir-format
+msgctxt "oauth register page login username prompt"
+msgid "Name or email"
+msgstr "Nome ou email"
+
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:14
+#, elixir-autogen, elixir-format
+msgctxt "oauth register page nickname prompt"
+msgid "Nickname"
+msgstr "Apelido"
+
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:22
+#, elixir-autogen, elixir-format
+msgctxt "oauth register page register button"
+msgid "Proceed as new user"
+msgstr "Proceder como um novo usuário"
+
+#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:8
+#, elixir-autogen, elixir-format
+msgctxt "oauth register page title"
+msgid "Registration Details"
+msgstr "Detalhes do registro"
+
+#: lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex:2
+#, elixir-autogen, elixir-format
+msgctxt "oauth scopes message"
+msgid "The following permissions will be granted"
+msgstr "As seguintes permissões serão garantidas"
+
+#: lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex:6
+#: lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex:6
+#, elixir-autogen, elixir-format
+msgctxt "oauth token code message"
+msgid "Token code is %{token}"
+msgstr "O código do token é %{token}"
+
+#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:14
+#, elixir-autogen, elixir-format
+msgctxt "mfa auth code prompt"
+msgid "Authentication code"
+msgstr "Código de autenticação"
+
+#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:9
+#, elixir-autogen, elixir-format
+msgctxt "mfa auth page title"
+msgid "Two-factor authentication"
+msgstr "Autenticação de dois-fatores"
+
+#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:25
+#, elixir-autogen, elixir-format
+msgctxt "mfa auth page use recovery code link"
+msgid "Enter a two-factor recovery code"
+msgstr "Insira um código de recuperação de dois-fatores"
+
+#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:22
+#, elixir-autogen, elixir-format
+msgctxt "mfa auth verify code button"
+msgid "Verify"
+msgstr "Verificar"
+
+#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:9
+#, elixir-autogen, elixir-format
+msgctxt "mfa recover page title"
+msgid "Two-factor recovery"
+msgstr "Recuperação de dois-fatores"
+
+#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:14
+#, elixir-autogen, elixir-format
+msgctxt "mfa recover recovery code prompt"
+msgid "Recovery code"
+msgstr "Código de recuperação"
+
+#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:25
+#, elixir-autogen, elixir-format
+msgctxt "mfa recover use 2fa code link"
+msgid "Enter a two-factor code"
+msgstr "Insira um código de dois-fatores"
+
+#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:22
+#, elixir-autogen, elixir-format
+msgctxt "mfa recover verify recovery code button"
+msgid "Verify"
+msgstr "Verificar"
+
+#: lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex:42
+#, elixir-autogen, elixir-format
+msgctxt "static fe profile page remote follow button"
+msgid "Remote follow"
+msgstr "Seguir remotamente"
+
+#: lib/pleroma/web/templates/email/digest.html.eex:163
+#, elixir-autogen, elixir-format
+msgctxt "digest email header line"
+msgid "Hey %{nickname}, here is what you've missed!"
+msgstr "Ei, %{nickname}, veja o que você perdeu!"
+
+#: lib/pleroma/web/templates/email/digest.html.eex:544
+#, elixir-autogen, elixir-format
+msgctxt "digest email receiver address"
+msgid "The email address you are subscribed as is %{email} . "
+msgstr ""
+"O email ao qual você está inscrito é %{email} . "
+
+#: lib/pleroma/web/templates/email/digest.html.eex:538
+#, elixir-autogen, elixir-format
+msgctxt "digest email sending reason"
+msgid "You have received this email because you have signed up to receive digest emails from %{instance} Pleroma instance."
+msgstr ""
+"Você recebeu esse email porque você se inscreveu para receber resumos por "
+"email da instância Akkoma %{instance} ."
+
+#: lib/pleroma/web/templates/email/digest.html.eex:547
+#, elixir-autogen, elixir-format
+msgctxt "digest email unsubscribe action"
+msgid "To unsubscribe, please go %{here}."
+msgstr "Para se desinscrever, por favor vá %{here}."
+
+#: lib/pleroma/web/templates/email/digest.html.eex:547
+#, elixir-autogen, elixir-format
+msgctxt "digest email unsubscribe action link text"
+msgid "here"
+msgstr "aqui"
+
+#: lib/pleroma/web/templates/mailer/subscription/unsubscribe_failure.html.eex:1
+#, elixir-autogen, elixir-format
+msgctxt "mailer unsubscribe failed message"
+msgid "UNSUBSCRIBE FAILURE"
+msgstr "FALHA AO SE DESINSCREVER"
+
+#: lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex:1
+#, elixir-autogen, elixir-format
+msgctxt "mailer unsubscribe successful message"
+msgid "UNSUBSCRIBE SUCCESSFUL"
+msgstr "INSCRIÇÃO CANCELADA COM SUCESSO"
+
+#: lib/pleroma/web/templates/email/digest.html.eex:385
+#, elixir-format
+msgctxt "new followers count header"
+msgid "%{count} New Follower"
+msgid_plural "%{count} New Followers"
+msgstr[0] "%{count} Novo Seguidor"
+msgstr[1] "%{count} Novos Seguidores"
+
+#: lib/pleroma/emails/user_email.ex:384
+#, elixir-autogen, elixir-format
+msgctxt "account archive email subject"
+msgid "Your account archive is ready"
+msgstr "O arquivamento da sua conta está pronto"
+
+#: lib/pleroma/emails/user_email.ex:188
+#, elixir-autogen, elixir-format
+msgctxt "approval pending email body"
+msgid "Awaiting Approval \nYour account at %{instance_name} is being reviewed by staff. You will receive another email once your account is approved.
\n"
+msgstr ""
+" Aguardando Aprovação \n"
+"Sua conta na instância %{instance_name} está sendo revisada pela staff. "
+"Você receberá um novo email assim que a conta for aprovada.
\n"
+
+#: lib/pleroma/emails/user_email.ex:202
+#, elixir-autogen, elixir-format
+msgctxt "approval pending email subject"
+msgid "Your account is awaiting approval"
+msgstr "Sua conta está aguardando aprovação"
+
+#: lib/pleroma/emails/user_email.ex:158
+#, elixir-autogen, elixir-format
+msgctxt "confirmation email body"
+msgid "Thank you for registering on %{instance_name} \nEmail confirmation is required to activate the account.
\nPlease click the following link to activate your account .
\n"
+msgstr ""
+" Obrigado por se registrar na instância %{instance_name} \n"
+" Uma confirmação de email é necessário para ativar a sua conta
\n"
+"Por favor, clique no seguinte link para ativar a sua conta .
\n"
+
+#: lib/pleroma/emails/user_email.ex:174
+#, elixir-autogen, elixir-format
+msgctxt "confirmation email subject"
+msgid "%{instance_name} account confirmation"
+msgstr "Confirmação de conta de %{instance_name}"
+
+#: lib/pleroma/emails/user_email.ex:310
+#, elixir-autogen, elixir-format
+msgctxt "digest email subject"
+msgid "Your digest from %{instance_name}"
+msgstr "O seu resumo de %{instance_name}"
+
+#: lib/pleroma/emails/user_email.ex:81
+#, elixir-autogen, elixir-format
+msgctxt "password reset email body"
+msgid "Reset your password at %{instance_name} \nSomeone has requested password change for your account at %{instance_name}.
\nIf it was you, visit the following link to proceed: reset password .
\nIf it was someone else, nothing to worry about: your data is secure and your password has not been changed.
\n"
+msgstr ""
+"Redefina a sua senha em %{instance_name} \n"
+"Alguém requisitou uma redefinição de senha para a sua conta em "
+"%{instance_name}.
\n"
+"Caso tenha sido você, visite o seguinte link para proceder: redefinir senha .
\n"
+"Se por acaso tenha sido outra pessoa, não há com o que se preocupar: seus "
+"dados estão seguros e a sua senha não foi alterada.
\n"
+
+#: lib/pleroma/emails/user_email.ex:98
+#, elixir-autogen, elixir-format
+msgctxt "password reset email subject"
+msgid "Password reset"
+msgstr "Senha redefinida"
+
+#: lib/pleroma/emails/user_email.ex:215
+#, elixir-autogen, elixir-format
+msgctxt "successful registration email body"
+msgid "Hello @%{nickname}, \nYour account at %{instance_name} has been registered successfully.
\nNo further action is required to activate your account.
\n"
+msgstr ""
+"Olá, @%{nickname}, \n"
+"Sua conta em %{instance_name} foi criada com sucesso.
\n"
+"Nenhuma ação extra é necessária para ativar a sua conta.
\n"
+
+#: lib/pleroma/emails/user_email.ex:231
+#, elixir-autogen, elixir-format
+msgctxt "successful registration email subject"
+msgid "Account registered on %{instance_name}"
+msgstr "Conta registrada em %{instance_name}"
+
+#: lib/pleroma/emails/user_email.ex:136
+#, elixir-autogen, elixir-format
+msgctxt "user invitation email subject"
+msgid "Invitation to %{instance_name}"
+msgstr "Convite para %{instance_name}"
+
+#: lib/pleroma/emails/user_email.ex:53
+#, elixir-autogen, elixir-format
+msgctxt "welcome email html body"
+msgid "Welcome to %{instance_name}!"
+msgstr "Bem-vindo(a) à instância %{instance_name}!"
+
+#: lib/pleroma/emails/user_email.ex:41
+#, elixir-autogen, elixir-format
+msgctxt "welcome email subject"
+msgid "Welcome to %{instance_name}!"
+msgstr "Bem-vindo(a) à instância %{instance_name}!"
+
+#: lib/pleroma/emails/user_email.ex:65
+#, elixir-autogen, elixir-format
+msgctxt "welcome email text body"
+msgid "Welcome to %{instance_name}!"
+msgstr "Bem-vindo(a) à instância %{instance_name}!"
+
+#: lib/pleroma/emails/user_email.ex:368
+#, elixir-autogen, elixir-format
+msgctxt "account archive email body - admin requested"
+msgid "Admin @%{admin_nickname} requested a full backup of your Akkoma account. It's ready for download:
\n%{download_url}
\n"
+msgstr ""
+"O administrador @%{admin_nickname} requisitou um backup completo da sua "
+"conta Akkoma. Ele está pronto para download:
\n"
+"%{download_url}
\n"
+
+#: lib/pleroma/emails/user_email.ex:356
+#, elixir-autogen, elixir-format
+msgctxt "account archive email body - self-requested"
+msgid "You requested a full backup of your Akkoma account. It's ready for download:
\n%{download_url}
\n"
+msgstr ""
+" Foi requisitou uma backup completo da sua conta Akkoma. Está pronto para "
+"download:
\n"
+"%{download_url}
\n"
+
+#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:41
+#, elixir-autogen, elixir-format
+msgctxt "oauth register page title"
+msgid "This is your first visit! Please enter your Akkoma handle."
+msgstr ""
+"Esta é a sua primeira visita! Por favor, entre com o seu sobrenome Akkoma."
+
+#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:123
+#, elixir-autogen, elixir-format
+msgctxt "remote follow error message - unknown error"
+msgid "Something went wrong."
+msgstr "Algo deu errado."
+
+#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:67
+#, elixir-autogen, elixir-format
+msgctxt "remote follow error message - user not found"
+msgid "Could not find user"
+msgstr "Não foi possível encontrar o usuário"
+
+#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:8
+#, elixir-autogen, elixir-format
+msgctxt "status interact authorization button"
+msgid "Interact"
+msgstr "Interagir"
+
+#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:2
+#, elixir-autogen, elixir-format
+msgctxt "status interact error"
+msgid "Error: %{error}"
+msgstr "Erro: %{error}"
+
+#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:95
+#, elixir-autogen, elixir-format
+msgctxt "status interact error message - status not found"
+msgid "Could not find status"
+msgstr "Não foi possível achar o status"
+
+#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:144
+#, elixir-autogen, elixir-format
+msgctxt "status interact error message - unknown error"
+msgid "Something went wrong."
+msgstr "Algo deu errado."
+
+#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:4
+#, elixir-autogen, elixir-format
+msgctxt "status interact header"
+msgid "Interacting with %{nickname}'s %{status_link}"
+msgstr "Interagindo com o %{status_link} de %{nickname}"
+
+#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:4
+#, elixir-autogen, elixir-format
+msgctxt "status interact header - status link text"
+msgid "status"
+msgstr "status"
+
+#: lib/pleroma/emails/user_email.ex:119
+#, elixir-autogen, elixir-format
+msgctxt "user invitation email body"
+msgid "You are invited to %{instance_name} \n%{inviter_name} invites you to join %{instance_name}, an instance of Akkoma federated social networking platform.
\nClick the following link to register: accept invitation .
\n"
+msgstr ""
+" Você foi convidado(a) para a instância %{instance_name} \n"
+"%{inviter_name} lhe convidou a se juntar à instância %{instance_name}, "
+"uma instância Akkoma da plataforma de redes sociais federadas.
\n"
+"Clique no seguinte link para se registrar: aceitar convite .
\n"
diff --git a/priv/gettext/zh_Hant/LC_MESSAGES/static_pages.po b/priv/gettext/zh_Hant/LC_MESSAGES/static_pages.po
index 7665c9d11..89d363c28 100644
--- a/priv/gettext/zh_Hant/LC_MESSAGES/static_pages.po
+++ b/priv/gettext/zh_Hant/LC_MESSAGES/static_pages.po
@@ -8,555 +8,589 @@
### to merge POT files into PO files.
msgid ""
msgstr ""
+"PO-Revision-Date: 2024-04-22 23:54+0000\n"
+"Last-Translator: Toot \n"
+"Language-Team: Chinese (Traditional) \n"
"Language: zh_Hant\n"
+"Content-Type: text/plain; charset=UTF-8\n"
"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Weblate 4.18.2\n"
+"Content-Transfer-Encoding: 8bit\n"
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:9
#, elixir-autogen, elixir-format
msgctxt "remote follow authorization button"
msgid "Authorize"
-msgstr ""
+msgstr "批准"
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:2
#, elixir-autogen, elixir-format
msgctxt "remote follow error"
msgid "Error fetching user"
-msgstr ""
+msgstr "無法獲取用戶信息"
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "remote follow header"
msgid "Remote follow"
-msgstr ""
+msgstr "跨站關注"
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:8
#, elixir-autogen, elixir-format
msgctxt "placeholder text for auth code entry"
msgid "Authentication code"
-msgstr ""
+msgstr "驗證碼"
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:10
#, elixir-autogen, elixir-format
msgctxt "placeholder text for password entry"
msgid "Password"
-msgstr ""
+msgstr "密碼"
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:8
#, elixir-autogen, elixir-format
msgctxt "placeholder text for username entry"
msgid "Username"
-msgstr ""
+msgstr "用戶名"
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:13
#, elixir-autogen, elixir-format
msgctxt "remote follow authorization button for login"
msgid "Authorize"
-msgstr ""
+msgstr "授權"
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:12
#, elixir-autogen, elixir-format
msgctxt "remote follow authorization button for mfa"
msgid "Authorize"
-msgstr ""
+msgstr "授權"
#: lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex:2
#, elixir-autogen, elixir-format
msgctxt "remote follow error"
msgid "Error following account"
-msgstr ""
+msgstr "無法關注帳戶"
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "remote follow header, need login"
msgid "Log in to follow"
-msgstr ""
+msgstr "登錄以關注"
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "remote follow mfa header"
msgid "Two-factor authentication"
-msgstr ""
+msgstr "雙因素身份驗證"
#: lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "remote follow success"
msgid "Account followed!"
-msgstr ""
+msgstr "已關注該賬號!"
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:7
#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:7
#, elixir-autogen, elixir-format
msgctxt "placeholder text for account id"
msgid "Your account ID, e.g. lain@quitter.se"
-msgstr ""
+msgstr "你的帳號ID,比如:lain@quitter.se"
#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:8
#, elixir-autogen, elixir-format
msgctxt "remote follow authorization button for following with a remote account"
msgid "Follow"
-msgstr ""
+msgstr "關注"
#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:2
#, elixir-autogen, elixir-format
msgctxt "remote follow error"
msgid "Error: %{error}"
-msgstr ""
+msgstr "錯誤:%{error}"
#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "remote follow header"
msgid "Remotely follow %{nickname}"
-msgstr ""
+msgstr "遠程關注 %{nickname}"
#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:12
#, elixir-autogen, elixir-format
msgctxt "password reset button"
msgid "Reset"
-msgstr ""
+msgstr "重設"
#: lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "password reset failed homepage link"
msgid "Homepage"
-msgstr ""
+msgstr "主頁"
#: lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex:1
#, elixir-autogen, elixir-format
msgctxt "password reset failed message"
msgid "Password reset failed"
-msgstr ""
+msgstr "密碼重置失敗"
#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:8
#, elixir-autogen, elixir-format
msgctxt "password reset form confirm password prompt"
msgid "Confirmation"
-msgstr ""
+msgstr "確認"
#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "password reset form password prompt"
msgid "Password"
-msgstr ""
+msgstr "密碼"
#: lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex:1
#, elixir-autogen, elixir-format
msgctxt "password reset invalid token message"
msgid "Invalid Token"
-msgstr ""
+msgstr "無效 Token"
#: lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex:2
#, elixir-autogen, elixir-format
msgctxt "password reset successful homepage link"
msgid "Homepage"
-msgstr ""
+msgstr "主頁"
#: lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex:1
#, elixir-autogen, elixir-format
msgctxt "password reset successful message"
msgid "Password changed!"
-msgstr ""
+msgstr "密碼已修改!"
#: lib/pleroma/web/templates/feed/feed/tag.atom.eex:15
#: lib/pleroma/web/templates/feed/feed/tag.rss.eex:7
#, elixir-autogen, elixir-format
msgctxt "tag feed description"
msgid "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse."
-msgstr ""
+msgstr "這些是帶有 #%{tag} "
+"標籤的公開貼文。如果你在聯邦宇宙的任何地方有帳號,你可以與它們進行互動。"
#: lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex:3
#, elixir-autogen, elixir-format
msgctxt "oauth authorization exists page title"
msgid "Authorization exists"
-msgstr ""
+msgstr "已存在授權"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:37
#, elixir-autogen, elixir-format
msgctxt "oauth authorize approve button"
msgid "Approve"
-msgstr ""
+msgstr "允許"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:35
#, elixir-autogen, elixir-format
msgctxt "oauth authorize cancel button"
msgid "Cancel"
-msgstr ""
+msgstr "取消"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:26
#, elixir-autogen, elixir-format
msgctxt "oauth authorize message"
msgid "Application %{client_name} is requesting access to your account."
-msgstr ""
+msgstr "應用程序 %{client_name} 正在請求訪問您的帳戶。"
#: lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex:3
#, elixir-autogen, elixir-format
msgctxt "oauth authorized page title"
msgid "Successfully authorized"
-msgstr ""
+msgstr "授權成功"
#: lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex:1
#, elixir-autogen, elixir-format
msgctxt "oauth external provider page title"
msgid "Sign in with external provider"
-msgstr ""
+msgstr "使用外部服務進行登錄"
#: lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex:13
#, elixir-autogen, elixir-format
msgctxt "oauth external provider sign in button"
msgid "Sign in with %{strategy}"
-msgstr ""
+msgstr "用 %{strategy} 登錄"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:59
#, elixir-autogen, elixir-format
msgctxt "oauth login button"
msgid "Log In"
-msgstr ""
+msgstr "登錄"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:56
#, elixir-autogen, elixir-format
msgctxt "oauth login password prompt"
msgid "Password"
-msgstr ""
+msgstr "密碼"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:52
#, elixir-autogen, elixir-format
msgctxt "oauth login username prompt"
msgid "Username"
-msgstr ""
+msgstr "用戶名"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:44
#, elixir-autogen, elixir-format
msgctxt "oauth register nickname prompt"
msgid "Pleroma Handle"
-msgstr ""
+msgstr "Pleroma 帳號"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:42
#, elixir-autogen, elixir-format
msgctxt "oauth register nickname unchangeable warning"
msgid "Choose carefully! You won't be able to change this later. You will be able to change your display name, though."
-msgstr ""
+msgstr "選擇時要慎重!您以後將無法更改此項。不過您可以更改您的顯示名稱。"
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:18
#, elixir-autogen, elixir-format
msgctxt "oauth register page email prompt"
msgid "Email"
-msgstr ""
+msgstr "郵箱"
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:10
#, elixir-autogen, elixir-format
msgctxt "oauth register page fill form prompt"
msgid "If you'd like to register a new account, please provide the details below."
-msgstr ""
+msgstr "如果您想註冊一個新帳戶,請提供以下細節。"
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:35
#, elixir-autogen, elixir-format
msgctxt "oauth register page login button"
msgid "Proceed as existing user"
-msgstr ""
+msgstr "以現有用戶身份進行"
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:31
#, elixir-autogen, elixir-format
msgctxt "oauth register page login password prompt"
msgid "Password"
-msgstr ""
+msgstr "密碼"
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:24
#, elixir-autogen, elixir-format
msgctxt "oauth register page login prompt"
msgid "Alternatively, sign in to connect to existing account."
-msgstr ""
+msgstr "或者登錄後連接到現有賬戶。"
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:27
#, elixir-autogen, elixir-format
msgctxt "oauth register page login username prompt"
msgid "Name or email"
-msgstr ""
+msgstr "名字或者郵箱"
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:14
#, elixir-autogen, elixir-format
msgctxt "oauth register page nickname prompt"
msgid "Nickname"
-msgstr ""
+msgstr "暱稱"
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:22
#, elixir-autogen, elixir-format
msgctxt "oauth register page register button"
msgid "Proceed as new user"
-msgstr ""
+msgstr "以新用戶身份進行"
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:8
#, elixir-autogen, elixir-format
msgctxt "oauth register page title"
msgid "Registration Details"
-msgstr ""
+msgstr "註冊細節"
#: lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex:2
#, elixir-autogen, elixir-format
msgctxt "oauth scopes message"
msgid "The following permissions will be granted"
-msgstr ""
+msgstr "將授予以下權限"
#: lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex:6
#: lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex:6
#, elixir-autogen, elixir-format
msgctxt "oauth token code message"
msgid "Token code is %{token}"
-msgstr ""
+msgstr "Token 碼是 %{token}"
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:14
#, elixir-autogen, elixir-format
msgctxt "mfa auth code prompt"
msgid "Authentication code"
-msgstr ""
+msgstr "授權碼"
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:9
#, elixir-autogen, elixir-format
msgctxt "mfa auth page title"
msgid "Two-factor authentication"
-msgstr ""
+msgstr "雙因素身份驗證"
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:25
#, elixir-autogen, elixir-format
msgctxt "mfa auth page use recovery code link"
msgid "Enter a two-factor recovery code"
-msgstr ""
+msgstr "輸入一個雙因素恢復的恢復代碼"
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:22
#, elixir-autogen, elixir-format
msgctxt "mfa auth verify code button"
msgid "Verify"
-msgstr ""
+msgstr "認證"
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:9
#, elixir-autogen, elixir-format
msgctxt "mfa recover page title"
msgid "Two-factor recovery"
-msgstr ""
+msgstr "雙因素恢復"
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:14
#, elixir-autogen, elixir-format
msgctxt "mfa recover recovery code prompt"
msgid "Recovery code"
-msgstr ""
+msgstr "恢復碼"
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:25
#, elixir-autogen, elixir-format
msgctxt "mfa recover use 2fa code link"
msgid "Enter a two-factor code"
-msgstr ""
+msgstr "輸入一個雙重因素驗證碼"
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:22
#, elixir-autogen, elixir-format
msgctxt "mfa recover verify recovery code button"
msgid "Verify"
-msgstr ""
+msgstr "驗證"
#: lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex:42
#, elixir-autogen, elixir-format
msgctxt "static fe profile page remote follow button"
msgid "Remote follow"
-msgstr ""
+msgstr "跨站關注"
#: lib/pleroma/web/templates/email/digest.html.eex:163
#, elixir-autogen, elixir-format
msgctxt "digest email header line"
msgid "Hey %{nickname}, here is what you've missed!"
-msgstr ""
+msgstr "嗨 %{nickname},這是你錯過了的一些東西!"
#: lib/pleroma/web/templates/email/digest.html.eex:544
#, elixir-autogen, elixir-format
msgctxt "digest email receiver address"
msgid "The email address you are subscribed as is %{email} . "
msgstr ""
+"您訂閱的電子郵件地址是 %{email} 。 "
#: lib/pleroma/web/templates/email/digest.html.eex:538
#, elixir-autogen, elixir-format
msgctxt "digest email sending reason"
msgid "You have received this email because you have signed up to receive digest emails from %{instance} Pleroma instance."
-msgstr ""
+msgstr "您之所以會收到來自 %{instance} Akkoma "
+"實例的郵件摘要,是因爲您已經註冊了該服務實例。"
#: lib/pleroma/web/templates/email/digest.html.eex:547
#, elixir-autogen, elixir-format
msgctxt "digest email unsubscribe action"
msgid "To unsubscribe, please go %{here}."
-msgstr ""
+msgstr "取消訂閱,請點擊 %{here}."
#: lib/pleroma/web/templates/email/digest.html.eex:547
#, elixir-autogen, elixir-format
msgctxt "digest email unsubscribe action link text"
msgid "here"
-msgstr ""
+msgstr "這里"
#: lib/pleroma/web/templates/mailer/subscription/unsubscribe_failure.html.eex:1
#, elixir-autogen, elixir-format
msgctxt "mailer unsubscribe failed message"
msgid "UNSUBSCRIBE FAILURE"
-msgstr ""
+msgstr "取消訂閱失敗"
#: lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex:1
#, elixir-autogen, elixir-format
msgctxt "mailer unsubscribe successful message"
msgid "UNSUBSCRIBE SUCCESSFUL"
-msgstr ""
+msgstr "成功取消訂閱"
#: lib/pleroma/web/templates/email/digest.html.eex:385
#, elixir-format
msgctxt "new followers count header"
msgid "%{count} New Follower"
msgid_plural "%{count} New Followers"
-msgstr[0] ""
+msgstr[0] "%{count} 個新關注者"
#: lib/pleroma/emails/user_email.ex:384
#, elixir-autogen, elixir-format
msgctxt "account archive email subject"
msgid "Your account archive is ready"
-msgstr ""
+msgstr "您的帳戶檔案已經準備好了"
#: lib/pleroma/emails/user_email.ex:188
#, elixir-autogen, elixir-format
msgctxt "approval pending email body"
msgid "Awaiting Approval \nYour account at %{instance_name} is being reviewed by staff. You will receive another email once your account is approved.
\n"
msgstr ""
+"正在等待批准 \n"
+" 您在 %{instance_name} 的帳戶正在被工作人員審查。一旦您的帳戶被批准通過,"
+"您將收到另一封電子郵件。
\n"
#: lib/pleroma/emails/user_email.ex:202
#, elixir-autogen, elixir-format
msgctxt "approval pending email subject"
msgid "Your account is awaiting approval"
-msgstr ""
+msgstr "您的帳戶正在等待審批"
#: lib/pleroma/emails/user_email.ex:158
#, elixir-autogen, elixir-format
msgctxt "confirmation email body"
msgid "Thank you for registering on %{instance_name} \nEmail confirmation is required to activate the account.
\nPlease click the following link to activate your account .
\n"
msgstr ""
+"感謝註冊 %{instance_name} \n"
+"需要電子郵件確認才能激活該帳戶
\n"
+"請點擊以下鏈結以 確認您的帳戶
\n"
#: lib/pleroma/emails/user_email.ex:174
#, elixir-autogen, elixir-format
msgctxt "confirmation email subject"
msgid "%{instance_name} account confirmation"
-msgstr ""
+msgstr "%{instance_name} 帳戶確認"
#: lib/pleroma/emails/user_email.ex:310
#, elixir-autogen, elixir-format
msgctxt "digest email subject"
msgid "Your digest from %{instance_name}"
-msgstr ""
+msgstr "您來自 %{instance_name} 的摘要郵件"
#: lib/pleroma/emails/user_email.ex:81
#, elixir-autogen, elixir-format
msgctxt "password reset email body"
msgid "Reset your password at %{instance_name} \nSomeone has requested password change for your account at %{instance_name}.
\nIf it was you, visit the following link to proceed: reset password .
\nIf it was someone else, nothing to worry about: your data is secure and your password has not been changed.
\n"
msgstr ""
+"在 %{instance_name} 重置您的密碼 \n"
+"有人請求更改您在 %{instance_name} 的帳戶密碼。
\n"
+"如果這是您的操作,請點擊以下鏈結繼續重置密碼:重置密碼 。
\n"
+"如果這不是您的操作,請不用擔心:您的數據是安全的,您的密碼未被更改。
\n"
#: lib/pleroma/emails/user_email.ex:98
#, elixir-autogen, elixir-format
msgctxt "password reset email subject"
msgid "Password reset"
-msgstr ""
+msgstr "重置密碼"
#: lib/pleroma/emails/user_email.ex:215
#, elixir-autogen, elixir-format
msgctxt "successful registration email body"
msgid "Hello @%{nickname}, \nYour account at %{instance_name} has been registered successfully.
\nNo further action is required to activate your account.
\n"
msgstr ""
+"你好,@%{nickname}, \n"
+"你在 %{instance_name} 的帳戶已經成功註冊。
\n"
+"無需進行其他操作即可激活你的帳戶。
\n"
#: lib/pleroma/emails/user_email.ex:231
#, elixir-autogen, elixir-format
msgctxt "successful registration email subject"
msgid "Account registered on %{instance_name}"
-msgstr ""
+msgstr "帳號註冊在 %{instance_name}"
#: lib/pleroma/emails/user_email.ex:136
#, elixir-autogen, elixir-format
msgctxt "user invitation email subject"
msgid "Invitation to %{instance_name}"
-msgstr ""
+msgstr "邀請加入 %{instance_name}"
#: lib/pleroma/emails/user_email.ex:53
#, elixir-autogen, elixir-format
msgctxt "welcome email html body"
msgid "Welcome to %{instance_name}!"
-msgstr ""
+msgstr "歡迎來到 %{instance_name}!"
#: lib/pleroma/emails/user_email.ex:41
#, elixir-autogen, elixir-format
msgctxt "welcome email subject"
msgid "Welcome to %{instance_name}!"
-msgstr ""
+msgstr "歡迎來到 %{instance_name}!"
#: lib/pleroma/emails/user_email.ex:65
#, elixir-autogen, elixir-format
msgctxt "welcome email text body"
msgid "Welcome to %{instance_name}!"
-msgstr ""
+msgstr "歡迎來到 %{instance_name}!"
#: lib/pleroma/emails/user_email.ex:368
#, elixir-autogen, elixir-format
msgctxt "account archive email body - admin requested"
msgid "Admin @%{admin_nickname} requested a full backup of your Akkoma account. It's ready for download:
\n%{download_url}
\n"
msgstr ""
+"管理員 @%{admin_nickname} 請求對你的 Akkoma "
+"帳戶進行完整備份,備份已準備好可供下載:
\n"
+"%{download_url}
\n"
#: lib/pleroma/emails/user_email.ex:356
#, elixir-autogen, elixir-format
msgctxt "account archive email body - self-requested"
msgid "You requested a full backup of your Akkoma account. It's ready for download:
\n%{download_url}
\n"
msgstr ""
+"您請求了 Akkoma 帳戶的完整備份。備份已準備就緒,可以下載:
\n"
+"%{download_url}
\n"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:41
#, elixir-autogen, elixir-format
msgctxt "oauth register page title"
msgid "This is your first visit! Please enter your Akkoma handle."
-msgstr ""
+msgstr "這是您的第一次訪問!請填寫您的 Akkoma 帳號。"
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:123
#, elixir-autogen, elixir-format
msgctxt "remote follow error message - unknown error"
msgid "Something went wrong."
-msgstr ""
+msgstr "發生了一些錯誤。"
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:67
#, elixir-autogen, elixir-format
msgctxt "remote follow error message - user not found"
msgid "Could not find user"
-msgstr ""
+msgstr "無法找到相應用戶"
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:8
#, elixir-autogen, elixir-format
msgctxt "status interact authorization button"
msgid "Interact"
-msgstr ""
+msgstr "互動"
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:2
#, elixir-autogen, elixir-format
msgctxt "status interact error"
msgid "Error: %{error}"
-msgstr ""
+msgstr "錯誤:%{error}"
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:95
#, elixir-autogen, elixir-format
msgctxt "status interact error message - status not found"
msgid "Could not find status"
-msgstr ""
+msgstr "無法找到貼文"
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:144
#, elixir-autogen, elixir-format
msgctxt "status interact error message - unknown error"
msgid "Something went wrong."
-msgstr ""
+msgstr "發生了一些錯誤。"
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "status interact header"
msgid "Interacting with %{nickname}'s %{status_link}"
-msgstr ""
+msgstr "與 %{nickname} 的 %{status_link} 進行交互"
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "status interact header - status link text"
msgid "status"
-msgstr ""
+msgstr "貼文"
#: lib/pleroma/emails/user_email.ex:119
#, elixir-autogen, elixir-format
msgctxt "user invitation email body"
msgid "You are invited to %{instance_name} \n%{inviter_name} invites you to join %{instance_name}, an instance of Akkoma federated social networking platform.
\nClick the following link to register: accept invitation .
\n"
msgstr ""
+"您被邀請加入 %{instance_name} \n"
+"%{inviter_name} 邀請您加入 %{instance_name},這是一個使用 Akkoma "
+"聯邦社交網絡平臺的實例。
\n"
+"點擊以下鏈結註冊: 接受邀請 .
\n"
diff --git a/priv/repo/migrations/20220220135625_upload_filter_exiftool_to_exiftool_strip_location.exs b/priv/repo/migrations/20220220135625_upload_filter_exiftool_to_exiftool_strip_location.exs
new file mode 100644
index 000000000..86e02df01
--- /dev/null
+++ b/priv/repo/migrations/20220220135625_upload_filter_exiftool_to_exiftool_strip_location.exs
@@ -0,0 +1,10 @@
+defmodule Pleroma.Repo.Migrations.UploadFilterExiftoolToExiftoolStripMetadata do
+ use Ecto.Migration
+
+ # 20240425120000_upload_filter_exiftool_to_exiftool_strip_location.exs
+ # was originally committed with the id used in this file, but this breaks
+ # rollback order. Thus it was moved to 20240425120000 and this stub just prevents
+ # errors during large-scale rollbacks for anyone who already applied the old id
+ def up, do: :ok
+ def down, do: :ok
+end
diff --git a/priv/repo/migrations/20240207035927_create_rich_media_card.exs b/priv/repo/migrations/20240207035927_create_rich_media_card.exs
new file mode 100644
index 000000000..b5e48bccb
--- /dev/null
+++ b/priv/repo/migrations/20240207035927_create_rich_media_card.exs
@@ -0,0 +1,14 @@
+defmodule Pleroma.Repo.Migrations.CreateRichMediaCard do
+ use Ecto.Migration
+
+ def change do
+ create table(:rich_media_card) do
+ add(:url_hash, :bytea)
+ add(:fields, :map)
+
+ timestamps()
+ end
+
+ create(unique_index(:rich_media_card, [:url_hash]))
+ end
+end
diff --git a/priv/repo/migrations/20240425120000_upload_filter_exiftool_to_exiftool_strip_location_real.exs b/priv/repo/migrations/20240425120000_upload_filter_exiftool_to_exiftool_strip_location_real.exs
new file mode 100644
index 000000000..5d2b880db
--- /dev/null
+++ b/priv/repo/migrations/20240425120000_upload_filter_exiftool_to_exiftool_strip_location_real.exs
@@ -0,0 +1,37 @@
+defmodule Pleroma.Repo.Migrations.UploadFilterExiftoolToExiftoolStripMetadataReal do
+ use Ecto.Migration
+
+ alias Pleroma.ConfigDB
+
+ def up,
+ do:
+ ConfigDB.get_by_params(%{group: :pleroma, key: Pleroma.Upload})
+ |> update_filtername(
+ Pleroma.Upload.Filter.Exiftool,
+ Pleroma.Upload.Filter.Exiftool.StripMetadata
+ )
+
+ def down,
+ do:
+ ConfigDB.get_by_params(%{group: :pleroma, key: Pleroma.Upload})
+ |> update_filtername(
+ Pleroma.Upload.Filter.Exiftool.StripMetadata,
+ Pleroma.Upload.Filter.Exiftool
+ )
+
+ defp update_filtername(%{value: value}, from_filtername, to_filtername) do
+ new_value =
+ value
+ |> Keyword.update(:filters, [], fn filters ->
+ filters
+ |> Enum.map(fn
+ ^from_filtername -> to_filtername
+ filter -> filter
+ end)
+ end)
+
+ ConfigDB.update_or_create(%{group: :pleroma, key: Pleroma.Upload, value: new_value})
+ end
+
+ defp update_filtername(_, _, _), do: nil
+end
diff --git a/priv/repo/migrations/20240501190000_drop_unused_indexes.exs b/priv/repo/migrations/20240501190000_drop_unused_indexes.exs
new file mode 100644
index 000000000..fa5a5767b
--- /dev/null
+++ b/priv/repo/migrations/20240501190000_drop_unused_indexes.exs
@@ -0,0 +1,64 @@
+defmodule Pleroma.Repo.Migrations.DropUnusedIndexes do
+ use Ecto.Migration
+
+ def up do
+ # Leftovers from a late Pleroma migration (will not be restored on rollback)
+ drop_i(:users, [:show_birthday], :users_show_birthday_index)
+
+ drop_i(
+ :users,
+ ["date_part('month', birthday)", "date_part('day', birthday)"],
+ :users_birthday_month_day_index
+ )
+
+ # Unused
+ drop_i(:activities, ["(data->'cc')"], :activities_cc_index)
+ drop_i(:activities, ["(data->'object'->>'inReplyTo')"], :activities_in_reply_to)
+ drop_i(:activities, ["(data #> '{\"object\",\"likes\"}')"], :activities_likes)
+ drop_i(:activities, ["(data->'to')"], :activities_to_index)
+
+ drop_i(:objects, ["(data->'likes')"], :objects_likes)
+
+ drop_i(:users, [:featured_address], :users_featured_address_index)
+ drop_i(:users, [:following_address], :users_following_address_index)
+ drop_i(:users, [:invisible], :users_invisible_index)
+ drop_i(:users, [:last_status_at], :users_last_status_at_index)
+ drop_i(:users, [:tags], :users_tags_index)
+
+ drop_i(:apps, [:client_id, :client_secret], :apps_client_id_client_secret_index)
+ drop_i(:apps, [:user_id], :apps_user_id_index)
+
+ # Duplicate of primary key index (will not be restored on rollback)
+ drop_i(
+ :user_frontend_setting_profiles,
+ [:user_id, :frontend_name, :profile_name],
+ :user_frontend_setting_profiles_user_id_frontend_name_profile_name_index
+ )
+ end
+
+ def down do
+ create_i(:activities, ["(data->'cc')"], :activities_cc_index, :gin)
+ create_i(:activities, ["(data->'object'->>'inReplyTo')"], :activities_in_reply_to)
+ create_i(:activities, ["(data #> '{\"object\",\"likes\"}')"], :activities_likes, :gin)
+ create_i(:activities, ["(data->'to')"], :activities_to_index, :gin)
+
+ create_i(:objects, ["(data->'likes')"], :objects_likes, :gin)
+
+ create_i(:users, [:featured_address], :users_featured_address_index)
+ create_i(:users, [:following_address], :users_following_address_index)
+ create_i(:users, [:invisible], :users_invisible_index)
+ create_i(:users, [:last_status_at], :users_last_status_at_index)
+ create_i(:users, [:tags], :users_tags_index, :gin)
+
+ create_i(:apps, [:client_id, :client_secret], :apps_client_id_client_secret_index)
+ create_i(:apps, [:user_id], :apps_user_id_index)
+ end
+
+ defp drop_i(table, fields, name) do
+ drop_if_exists(index(table, fields, name: name))
+ end
+
+ defp create_i(table, fields, name, type \\ :btree) do
+ create_if_not_exists(index(table, fields, name: name, using: type))
+ end
+end
diff --git a/priv/repo/optional_migrations/20230422154018_drop_unused_indexes.exs b/priv/repo/optional_migrations/20230422154018_drop_unused_indexes.exs
deleted file mode 100644
index d8acb1034..000000000
--- a/priv/repo/optional_migrations/20230422154018_drop_unused_indexes.exs
+++ /dev/null
@@ -1,74 +0,0 @@
-defmodule Pleroma.Repo.Migrations.DropUnusedIndexes do
- use Ecto.Migration
-
- @disable_ddl_transaction true
-
- @disable_migration_lock true
-
- def up do
- drop_if_exists(
- index(:activities, ["(data->>'actor')", "inserted_at desc"], name: :activities_actor_index)
- )
-
- drop_if_exists(index(:activities, ["(data->'to')"], name: :activities_to_index))
-
- drop_if_exists(index(:activities, ["(data->'cc')"], name: :activities_cc_index))
-
- drop_if_exists(index(:activities, ["(split_part(actor, '/', 3))"], name: :activities_hosts))
-
- drop_if_exists(
- index(:activities, ["(data->'object'->>'inReplyTo')"], name: :activities_in_reply_to)
- )
-
- drop_if_exists(
- index(:activities, ["((data #> '{\"object\",\"likes\"}'))"], name: :activities_likes)
- )
- end
-
- def down do
- create_if_not_exists(
- index(:activities, ["(data->>'actor')", "inserted_at desc"],
- name: :activities_actor_index,
- concurrently: true
- )
- )
-
- create_if_not_exists(
- index(:activities, ["(data->'to')"],
- name: :activities_to_index,
- using: :gin,
- concurrently: true
- )
- )
-
- create_if_not_exists(
- index(:activities, ["(data->'cc')"],
- name: :activities_cc_index,
- using: :gin,
- concurrently: true
- )
- )
-
- create_if_not_exists(
- index(:activities, ["(split_part(actor, '/', 3))"],
- name: :activities_hosts,
- concurrently: true
- )
- )
-
- create_if_not_exists(
- index(:activities, ["(data->'object'->>'inReplyTo')"],
- name: :activities_in_reply_to,
- concurrently: true
- )
- )
-
- create_if_not_exists(
- index(:activities, ["((data #> '{\"object\",\"likes\"}'))"],
- name: :activities_likes,
- using: :gin,
- concurrently: true
- )
- )
- end
-end
diff --git a/test/fixtures/image_with_caption-abstract.jpg b/test/fixtures/image_with_caption-abstract.jpg
new file mode 100644
index 000000000..f982ffa81
Binary files /dev/null and b/test/fixtures/image_with_caption-abstract.jpg differ
diff --git a/test/fixtures/image_with_imagedescription_and_caption-abstract.jpg b/test/fixtures/image_with_imagedescription_and_caption-abstract.jpg
new file mode 100644
index 000000000..c82a269ef
Binary files /dev/null and b/test/fixtures/image_with_imagedescription_and_caption-abstract.jpg differ
diff --git a/test/fixtures/image_with_imagedescription_and_caption-abstract_whitespaces.jpg b/test/fixtures/image_with_imagedescription_and_caption-abstract_whitespaces.jpg
new file mode 100644
index 000000000..a232fd2a1
Binary files /dev/null and b/test/fixtures/image_with_imagedescription_and_caption-abstract_whitespaces.jpg differ
diff --git a/test/fixtures/image_with_no_description.jpg b/test/fixtures/image_with_no_description.jpg
new file mode 100644
index 000000000..ec6fc4be8
Binary files /dev/null and b/test/fixtures/image_with_no_description.jpg differ
diff --git a/test/fixtures/mastodon/service_actor.json b/test/fixtures/mastodon/service_actor.json
new file mode 100644
index 000000000..2abd20b5e
--- /dev/null
+++ b/test/fixtures/mastodon/service_actor.json
@@ -0,0 +1,117 @@
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ {
+ "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
+ "toot": "http://joinmastodon.org/ns#",
+ "featured": {
+ "@id": "toot:featured",
+ "@type": "@id"
+ },
+ "featuredTags": {
+ "@id": "toot:featuredTags",
+ "@type": "@id"
+ },
+ "alsoKnownAs": {
+ "@id": "as:alsoKnownAs",
+ "@type": "@id"
+ },
+ "movedTo": {
+ "@id": "as:movedTo",
+ "@type": "@id"
+ },
+ "schema": "http://schema.org#",
+ "PropertyValue": "schema:PropertyValue",
+ "value": "schema:value",
+ "discoverable": "toot:discoverable",
+ "Device": "toot:Device",
+ "Ed25519Signature": "toot:Ed25519Signature",
+ "Ed25519Key": "toot:Ed25519Key",
+ "Curve25519Key": "toot:Curve25519Key",
+ "EncryptedMessage": "toot:EncryptedMessage",
+ "publicKeyBase64": "toot:publicKeyBase64",
+ "deviceId": "toot:deviceId",
+ "claim": {
+ "@type": "@id",
+ "@id": "toot:claim"
+ },
+ "fingerprintKey": {
+ "@type": "@id",
+ "@id": "toot:fingerprintKey"
+ },
+ "identityKey": {
+ "@type": "@id",
+ "@id": "toot:identityKey"
+ },
+ "devices": {
+ "@type": "@id",
+ "@id": "toot:devices"
+ },
+ "messageFranking": "toot:messageFranking",
+ "messageType": "toot:messageType",
+ "cipherText": "toot:cipherText",
+ "suspended": "toot:suspended",
+ "memorial": "toot:memorial",
+ "indexable": "toot:indexable",
+ "focalPoint": {
+ "@container": "@list",
+ "@id": "toot:focalPoint"
+ }
+ }
+ ],
+ "id": "https://mastodont.cat/users/fediverse",
+ "type": "Service",
+ "following": "https://mastodont.cat/users/fediverse/following",
+ "followers": "https://mastodont.cat/users/fediverse/followers",
+ "inbox": "https://mastodont.cat/users/fediverse/inbox",
+ "outbox": "https://mastodont.cat/users/fediverse/outbox",
+ "featured": "https://mastodont.cat/users/fediverse/collections/featured",
+ "featuredTags": "https://mastodont.cat/users/fediverse/collections/tags",
+ "preferredUsername": "fediverse",
+ "name": "fediverse's stats",
+ "summary": "All fediverse alive servers stats. New refactored code!
Ask server info:
@fediverse server example.server
Ask software info:
@fediverse soft mastodon
",
+ "url": "https://mastodont.cat/@fediverse",
+ "manuallyApprovesFollowers": false,
+ "discoverable": true,
+ "indexable": false,
+ "published": "2020-05-13T00:00:00Z",
+ "memorial": false,
+ "devices": "https://mastodont.cat/users/fediverse/collections/devices",
+ "publicKey": {
+ "id": "https://mastodont.cat/users/fediverse#main-key",
+ "owner": "https://mastodont.cat/users/fediverse",
+ "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu2X8LqAR/6j95UUTG02T\nWG+PmNRWnfOl+zjDts3OctyJK7at5AwA+T0be1faHpf+oLREl/dkWXc8VQY2UJzY\n8QTuXXnIkwHAeA7WADB6kPvQhVpfGPgKD0dpAgBz9WHFquMSXcnuyt7q1CDn5wId\nRoUtkCAcg1rOX+lIAoeic5hT0O0sXLJdtaSCTZmGqkF2Cf+/16q8XhRevMRh73vP\nX2PefCr63Iy/Zh5rnVhPluQMyQ6FGxXgd5dEKJRa2kxrhIsrm0TzMX892Ev45AwI\ndppYQOQ+nLOgMYrpFNYdOmizJsn635l18K1r/tyDDAegPp6Kfa8v+BaZdOmNTFKr\n/wIDAQAB\n-----END PUBLIC KEY-----\n"
+ },
+ "tag": [],
+ "attachment": [
+ {
+ "type": "PropertyValue",
+ "name": "code",
+ "value": "https:// codeberg.org/spla/stats "
+ },
+ {
+ "type": "PropertyValue",
+ "name": "my user-agent",
+ "value": ""fediverse's stats (fediverse@mastodont.cat)""
+ },
+ {
+ "type": "PropertyValue",
+ "name": "coded by",
+ "value": "@spla "
+ }
+ ],
+ "endpoints": {
+ "sharedInbox": "https://mastodont.cat/inbox"
+ },
+ "icon": {
+ "type": "Image",
+ "mediaType": "image/png",
+ "url": "https://mastodont.cat/system/accounts/avatars/000/149/323/original/33201dbeb139a24a.png"
+ },
+ "image": {
+ "type": "Image",
+ "mediaType": "image/jpeg",
+ "url": "https://mastodont.cat/system/accounts/headers/000/149/323/original/75c861d59e5a8860.jpeg"
+ }
+}
diff --git a/test/fixtures/peertube/actor-videochannel.json b/test/fixtures/peertube/actor-videochannel.json
new file mode 100644
index 000000000..3d0ee5980
--- /dev/null
+++ b/test/fixtures/peertube/actor-videochannel.json
@@ -0,0 +1,72 @@
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ {
+ "RsaSignature2017": "https://w3id.org/security#RsaSignature2017"
+ },
+ {
+ "pt": "https://joinpeertube.org/ns#",
+ "sc": "http://schema.org/",
+ "playlists": {
+ "@id": "pt:playlists",
+ "@type": "@id"
+ },
+ "support": {
+ "@type": "sc:Text",
+ "@id": "pt:support"
+ },
+ "icons": "as:icon"
+ }
+ ],
+ "type": "Group",
+ "id": "https://spectra.video/video-channels/fediforum_demos",
+ "following": "https://spectra.video/video-channels/fediforum_demos/following",
+ "followers": "https://spectra.video/video-channels/fediforum_demos/followers",
+ "playlists": "https://spectra.video/video-channels/fediforum_demos/playlists",
+ "inbox": "https://spectra.video/video-channels/fediforum_demos/inbox",
+ "outbox": "https://spectra.video/video-channels/fediforum_demos/outbox",
+ "preferredUsername": "fediforum_demos",
+ "url": "https://spectra.video/video-channels/fediforum_demos",
+ "name": "FediForum Demos",
+ "endpoints": {
+ "sharedInbox": "https://spectra.video/inbox"
+ },
+ "publicKey": {
+ "id": "https://spectra.video/video-channels/fediforum_demos#main-key",
+ "owner": "https://spectra.video/video-channels/fediforum_demos",
+ "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxFVESAz0Z28zhXVJafzg\nKXVWS6yuZdQ4vOuA+k//ioSpNls53pI9vwQyixNa+QLdnXxm51dy//Py49wZbzAV\n2nC2FEnzcCM/EZvA4gzy7wekcjnGIz3equbdLOj3IAJJTSwCvZpW2f0poAa1CUmQ\nDRV5p3t3bjtUX5B9RnhiuDitN8qCzEeEbD9SHoyMDIACl8wXer8eyi5v98CMTHwh\nJYUJZJmS7/SSlJO2aqThEBaAYCUzVxlcXOecF1N1RWjjtwqi9xXxmlJ+teivYyST\nYfCeLmY/zZPY7OjoBxoVcVa/Yj3Wg6Nt+A5co9NATpsXmud7GWx4CvQ00uH/fa7e\nvQIDAQAB\n-----END PUBLIC KEY-----\n"
+ },
+ "published": "2024-03-26T19:34:06.073Z",
+ "icon": [
+ {
+ "type": "Image",
+ "mediaType": "image/png",
+ "height": 48,
+ "width": 48,
+ "url": "https://spectra.video/lazy-static/avatars/b13e5038-0169-420e-a6bc-4f5e0666fae6.png"
+ },
+ {
+ "type": "Image",
+ "mediaType": "image/png",
+ "height": 120,
+ "width": 120,
+ "url": "https://spectra.video/lazy-static/avatars/559b141a-96ec-4161-8889-1111b71abca0.png"
+ }
+ ],
+ "image": {
+ "type": "Image",
+ "mediaType": "image/png",
+ "height": 317,
+ "width": 1920,
+ "url": "https://spectra.video/lazy-static/banners/bbe18e2c-79ef-4640-9193-cdd743c964dd.png"
+ },
+ "summary": "Demos from the the FediForum Unconference. For the sake of simplicity, demos are [broken out into playlists](https://spectra.video/c/fediforum_demos/video-playlists) representing each FediForum Event.",
+ "support": "Check out our site: https://fediforum.org/\nFollow us on Mastodon: https://mastodon.social/@fediforum",
+ "attributedTo": [
+ {
+ "type": "Person",
+ "id": "https://spectra.video/accounts/fediforum"
+ }
+ ]
+}
diff --git a/test/fixtures/rich_media/google.html b/test/fixtures/rich_media/google.html
new file mode 100644
index 000000000..c068397a5
--- /dev/null
+++ b/test/fixtures/rich_media/google.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/rich_media/oembed.html b/test/fixtures/rich_media/oembed.html
index 55f17004b..5429630d0 100644
--- a/test/fixtures/rich_media/oembed.html
+++ b/test/fixtures/rich_media/oembed.html
@@ -1,3 +1,3 @@
diff --git a/test/fixtures/rich_media/reddit.html b/test/fixtures/rich_media/reddit.html
new file mode 100644
index 000000000..a99bb6884
--- /dev/null
+++ b/test/fixtures/rich_media/reddit.html
@@ -0,0 +1,392 @@
+Twitter/X is getting weirder; where now for security news and analysis? : cybersecurity this post was submitted on 20 Sep 2023
241 points (92% upvoted)
shortlink:
join leave 688,076 readers 592 users here now
a community for 11 years
π Rendered by PID 29 on reddit-service-r2-slowlane-65c5c76ff5-v258h at 2024-02-19 03:13:22.575220+00:00 running 5b0a0b2 country code: US.
\ No newline at end of file
diff --git a/test/fixtures/rich_media/yahoo.html b/test/fixtures/rich_media/yahoo.html
new file mode 100644
index 000000000..41d8c5cd9
--- /dev/null
+++ b/test/fixtures/rich_media/yahoo.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/tesla_mock/aimu@misskey.io.json b/test/fixtures/tesla_mock/aimu@misskey.io.json
new file mode 100644
index 000000000..9ff4cb6d0
--- /dev/null
+++ b/test/fixtures/tesla_mock/aimu@misskey.io.json
@@ -0,0 +1,64 @@
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ {
+ "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
+ "sensitive": "as:sensitive",
+ "Hashtag": "as:Hashtag",
+ "quoteUrl": "as:quoteUrl",
+ "toot": "http://joinmastodon.org/ns#",
+ "Emoji": "toot:Emoji",
+ "featured": "toot:featured",
+ "discoverable": "toot:discoverable",
+ "schema": "http://schema.org#",
+ "PropertyValue": "schema:PropertyValue",
+ "value": "schema:value",
+ "misskey": "https://misskey.io/ns#",
+ "_misskey_content": "misskey:_misskey_content",
+ "_misskey_quote": "misskey:_misskey_quote",
+ "_misskey_reaction": "misskey:_misskey_reaction",
+ "_misskey_votes": "misskey:_misskey_votes",
+ "_misskey_talk": "misskey:_misskey_talk",
+ "isCat": "misskey:isCat",
+ "vcard": "http://www.w3.org/2006/vcard/ns#"
+ }
+ ],
+ "type": "Person",
+ "id": "https://misskey.io/users/83ssedkv53",
+ "inbox": "https://misskey.io/users/83ssedkv53/inbox",
+ "outbox": "https://misskey.io/users/83ssedkv53/outbox",
+ "followers": "https://misskey.io/users/83ssedkv53/followers",
+ "following": "https://misskey.io/users/83ssedkv53/following",
+ "sharedInbox": "https://misskey.io/inbox",
+ "endpoints": {
+ "sharedInbox": "https://misskey.io/inbox"
+ },
+ "url": "https://misskey.io/@aimu",
+ "preferredUsername": "aimu",
+ "name": "あいむ",
+ "summary": "わずかな作曲要素 巣穴で独り言 Twitter https://twitter.com/aimu_53 Soundcloud https://soundcloud.com/aimu-53
",
+ "icon": {
+ "type": "Image",
+ "url": "https://s3.arkjp.net/misskey/webpublic-3f7e93c0-34f5-443c-acc0-f415cb2342b4.jpg",
+ "sensitive": false,
+ "name": null
+ },
+ "image": {
+ "type": "Image",
+ "url": "https://s3.arkjp.net/misskey/webpublic-2db63d1d-490b-488b-ab62-c93c285f26b6.png",
+ "sensitive": false,
+ "name": null
+ },
+ "tag": [],
+ "manuallyApprovesFollowers": false,
+ "discoverable": true,
+ "publicKey": {
+ "id": "https://misskey.io/users/83ssedkv53#main-key",
+ "type": "Key",
+ "owner": "https://misskey.io/users/83ssedkv53",
+ "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1ylhePJ6qGHmwHSBP17b\nIosxGaiFKvgDBgZdm8vzvKeRSqJV9uLHfZL3pO/Zt02EwaZd2GohZAtBZEF8DbMA\n3s93WAesvyGF9mjGrYYKlhp/glwyrrrbf+RdD0DLtyDwRRlrxp3pS2lLmv5Tp1Zl\npH+UKpOnNrpQqjHI5P+lEc9bnflzbRrX+UiyLNsVAP80v4wt7SZfT/telrU6mDru\n998UdfhUo7bDKeDsHG1PfLpyhhtfdoZub4kBpkyacHiwAd+CdCjR54Eu7FDwVK3p\nY3JcrT2q5stgMqN1m4QgSL4XAADIotWwDYttTJejM1n9dr+6VWv5bs0F2Q/6gxOp\nu5DQZLk4Q+64U4LWNox6jCMOq3fYe0g7QalJIHnanYQQo+XjoH6S1Aw64gQ3Ip2Y\nZBmZREAOR7GMFVDPFnVnsbCHnIAv16TdgtLgQBAihkWEUuPqITLi8PMu6kMr3uyq\nYkObEfH0TNTcqaiVpoXv791GZLEUV5ROl0FSUANLNkHZZv29xZ5JDOBOR1rNBLyH\ngVtW8rpszYqOXwzX23hh4WsVXfB7YgNvIijwjiaWbzsecleaENGEnLNMiVKVumTj\nmtyTeFJpH0+OaSrUYpemRRJizmqIjklKsNwUEwUb2WcUUg92o56T2obrBkooabZe\nwgSXSKTOcjsR/ju7+AuIyvkCAwEAAQ==\n-----END PUBLIC KEY-----\n"
+ },
+ "isCat": true,
+ "vcard:bday": "5353-05-03"
+}
diff --git a/test/fixtures/tesla_mock/bad.com_host_meta b/test/fixtures/tesla_mock/bad.com_host_meta
new file mode 100644
index 000000000..14cf3fa24
--- /dev/null
+++ b/test/fixtures/tesla_mock/bad.com_host_meta
@@ -0,0 +1,3 @@
+
+
+
diff --git a/test/fixtures/tesla_mock/misskey.io_8vs6wxufd0.json b/test/fixtures/tesla_mock/misskey.io_8vs6wxufd0.json
new file mode 100644
index 000000000..323ca10ed
--- /dev/null
+++ b/test/fixtures/tesla_mock/misskey.io_8vs6wxufd0.json
@@ -0,0 +1,44 @@
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ {
+ "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
+ "sensitive": "as:sensitive",
+ "Hashtag": "as:Hashtag",
+ "quoteUrl": "as:quoteUrl",
+ "toot": "http://joinmastodon.org/ns#",
+ "Emoji": "toot:Emoji",
+ "featured": "toot:featured",
+ "discoverable": "toot:discoverable",
+ "schema": "http://schema.org#",
+ "PropertyValue": "schema:PropertyValue",
+ "value": "schema:value",
+ "misskey": "https://misskey.io/ns#",
+ "_misskey_content": "misskey:_misskey_content",
+ "_misskey_quote": "misskey:_misskey_quote",
+ "_misskey_reaction": "misskey:_misskey_reaction",
+ "_misskey_votes": "misskey:_misskey_votes",
+ "_misskey_talk": "misskey:_misskey_talk",
+ "isCat": "misskey:isCat",
+ "vcard": "http://www.w3.org/2006/vcard/ns#"
+ }
+ ],
+ "id": "https://misskey.io/notes/8vs6wxufd0",
+ "type": "Note",
+ "attributedTo": "https://misskey.io/users/83ssedkv53",
+ "summary": null,
+ "content": "Fantiaこれできないように過去のやつは従量課金だった気がする
",
+ "_misskey_content": "Fantiaこれできないように過去のやつは従量課金だった気がする",
+ "published": "2022-01-21T16:37:12.663Z",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "cc": [
+ "https://misskey.io/users/83ssedkv53/followers"
+ ],
+ "inReplyTo": null,
+ "attachment": [],
+ "sensitive": false,
+ "tag": []
+}
diff --git a/test/fixtures/tesla_mock/webfinger_spoof.json b/test/fixtures/tesla_mock/webfinger_spoof.json
new file mode 100644
index 000000000..ebe2958ce
--- /dev/null
+++ b/test/fixtures/tesla_mock/webfinger_spoof.json
@@ -0,0 +1,28 @@
+{
+ "aliases": [
+ "https://bad.com/users/meanie",
+ "https://anotherbad.social/users/meanie"
+ ],
+ "links": [
+ {
+ "href": "https://bad.com/users/meanie",
+ "rel": "http://webfinger.net/rel/profile-page",
+ "type": "text/html"
+ },
+ {
+ "href": "https://bad.com/users/meanie",
+ "rel": "self",
+ "type": "application/activity+json"
+ },
+ {
+ "href": "https://bad.com/users/meanie",
+ "rel": "self",
+ "type": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
+ },
+ {
+ "rel": "http://ostatus.org/schema/1.0/subscribe",
+ "template": "https://bad.com/ostatus_subscribe?acct={uri}"
+ }
+ ],
+ "subject": "acct:oopsie@notwhereitshouldbe.org"
+}
diff --git a/test/fixtures/users_mock/nonsense_attachment_user.json b/test/fixtures/users_mock/nonsense_attachment_user.json
new file mode 100644
index 000000000..68042e76e
--- /dev/null
+++ b/test/fixtures/users_mock/nonsense_attachment_user.json
@@ -0,0 +1,51 @@
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ {
+ "blurhash": "toot:blurhash",
+ "Emoji": "toot:Emoji",
+ "focalPoint": {
+ "@container": "@list",
+ "@id": "toot:focalPoint"
+ },
+ "Hashtag": "as:Hashtag",
+ "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
+ "sensitive": "as:sensitive",
+ "toot": "http://joinmastodon.org/ns#",
+ "votersCount": "toot:votersCount",
+ "featured": {
+ "@id": "toot:featured",
+ "@type": "@id"
+ }
+ },
+ "https://w3id.org/security/v1"
+ ],
+ "id": "https://fedi.vision/@vote@fedi.vision/",
+ "type": "Person",
+ "toot:discoverable": true,
+ "inbox": "https://fedi.vision/@vote@fedi.vision/inbox/",
+ "publicKey": {
+ "id": "https://fedi.vision/@vote@fedi.vision/#main-key",
+ "owner": "https://fedi.vision/@vote@fedi.vision/",
+ "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj2f+uQtdoBO9X/u2Qso4\nxHYdfy8zB24m9Gg982/ts88DAMLxZUzX0JsBWT7coL0Ipf4NSbVaqS6nKrr2P8Qs\nf97wMhowyuYxK22BMPcbpfZkFj3tVT/JkDx2iujBJJ5ZBO5KRlupjDTqV4rOAY7F\n58ad0jK9PsJNJMsJ/b8+0t3Q/K+RqCGVmtK+iPSigOYoiKoquyRzHLTfP+mpOlDa\n3f+uyAbFya7CpcgBx1zz0PALWA+oh/zhZK4yT6719Esa8SDcoJ0ws70zMxWekq1A\n3ia88/Io6SY2qFNBpzzXGO3JK8OFRFtmPV8ZfAh5Pv6y52iuTJ21kxjAG7ZTP/fY\nBQIDAQAB\n-----END PUBLIC KEY-----\n"
+ },
+ "attachment": {
+ "haha": "you expected a proper object, but it was me, random nonsense"
+ },
+ "endpoints": {
+ "sharedInbox": "https://fedi.vision/inbox/"
+ },
+ "followers": "https://fedi.vision/@vote@fedi.vision/followers/",
+ "following": "https://fedi.vision/@vote@fedi.vision/following/",
+ "icon": {
+ "type": "Image",
+ "mediaType": "image/webp",
+ "url": "https://eu-central-1.linodeobjects.com:443/st4/profile_images/2024/5/9/RwqTbeYx16gauXPXvt-CaysOnGw.webp"
+ },
+ "name": "FediVision Vote Bot",
+ "outbox": "https://fedi.vision/@vote@fedi.vision/outbox/",
+ "preferredUsername": "vote",
+ "published": "2024-05-09T09:04:04Z",
+ "summary": "New in 2024, this is the bot that will count your #Fedivision vote! Accept no substitutes!
Send this account a toot in the form vote ABCD EFGH IJKL substituting the (up to) three codes for the songs you want to win. Punctuation ignored, case insensitive, order is unimportant. Only your latest toot counts, so change your vote with a new toot.
",
+ "url": "https://fedi.vision/@vote/"
+}
diff --git a/test/fixtures/users_mock/takahe_user.json b/test/fixtures/users_mock/takahe_user.json
new file mode 100644
index 000000000..247cf2052
--- /dev/null
+++ b/test/fixtures/users_mock/takahe_user.json
@@ -0,0 +1,53 @@
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ {
+ "blurhash": "toot:blurhash",
+ "Emoji": "toot:Emoji",
+ "focalPoint": {
+ "@container": "@list",
+ "@id": "toot:focalPoint"
+ },
+ "Hashtag": "as:Hashtag",
+ "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
+ "sensitive": "as:sensitive",
+ "toot": "http://joinmastodon.org/ns#",
+ "votersCount": "toot:votersCount",
+ "featured": {
+ "@id": "toot:featured",
+ "@type": "@id"
+ }
+ },
+ "https://w3id.org/security/v1"
+ ],
+ "id": "https://fedi.vision/@vote@fedi.vision/",
+ "type": "Person",
+ "toot:discoverable": true,
+ "inbox": "https://fedi.vision/@vote@fedi.vision/inbox/",
+ "publicKey": {
+ "id": "https://fedi.vision/@vote@fedi.vision/#main-key",
+ "owner": "https://fedi.vision/@vote@fedi.vision/",
+ "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj2f+uQtdoBO9X/u2Qso4\nxHYdfy8zB24m9Gg982/ts88DAMLxZUzX0JsBWT7coL0Ipf4NSbVaqS6nKrr2P8Qs\nf97wMhowyuYxK22BMPcbpfZkFj3tVT/JkDx2iujBJJ5ZBO5KRlupjDTqV4rOAY7F\n58ad0jK9PsJNJMsJ/b8+0t3Q/K+RqCGVmtK+iPSigOYoiKoquyRzHLTfP+mpOlDa\n3f+uyAbFya7CpcgBx1zz0PALWA+oh/zhZK4yT6719Esa8SDcoJ0ws70zMxWekq1A\n3ia88/Io6SY2qFNBpzzXGO3JK8OFRFtmPV8ZfAh5Pv6y52iuTJ21kxjAG7ZTP/fY\nBQIDAQAB\n-----END PUBLIC KEY-----\n"
+ },
+ "attachment": {
+ "type": "PropertyValue",
+ "value": "https:// fedivision.party/vote ",
+ "name": "More details"
+ },
+ "endpoints": {
+ "sharedInbox": "https://fedi.vision/inbox/"
+ },
+ "followers": "https://fedi.vision/@vote@fedi.vision/followers/",
+ "following": "https://fedi.vision/@vote@fedi.vision/following/",
+ "icon": {
+ "type": "Image",
+ "mediaType": "image/webp",
+ "url": "https://eu-central-1.linodeobjects.com:443/st4/profile_images/2024/5/9/RwqTbeYx16gauXPXvt-CaysOnGw.webp"
+ },
+ "name": "FediVision Vote Bot",
+ "outbox": "https://fedi.vision/@vote@fedi.vision/outbox/",
+ "preferredUsername": "vote",
+ "published": "2024-05-09T09:04:04Z",
+ "summary": "New in 2024, this is the bot that will count your #Fedivision vote! Accept no substitutes!
Send this account a toot in the form vote ABCD EFGH IJKL substituting the (up to) three codes for the songs you want to win. Punctuation ignored, case insensitive, order is unimportant. Only your latest toot counts, so change your vote with a new toot.
",
+ "url": "https://fedi.vision/@vote/"
+}
diff --git a/test/fixtures/webfinger/imposter-webfinger.json b/test/fixtures/webfinger/imposter-webfinger.json
new file mode 100644
index 000000000..e3d21a083
--- /dev/null
+++ b/test/fixtures/webfinger/imposter-webfinger.json
@@ -0,0 +1,41 @@
+{
+ "subject": "acct:oopsie@notwhereitshouldbe.com",
+ "aliases": [
+ "https://bad.com/webfingertest"
+ ],
+ "links": [
+ {
+ "rel": "http://webfinger.net/rel/profile-page",
+ "type": "text/html",
+ "href": "https://bad.com/webfingertest"
+ },
+ {
+ "rel": "self",
+ "type": "application/activity+json",
+ "href": "https://bad.com/webfingertest"
+ },
+ {
+ "rel": "http://ostatus.org/schema/1.0/subscribe",
+ "template": "https://bad.com/contact/follow?url={uri}"
+ },
+ {
+ "rel": "http://schemas.google.com/g/2010#updates-from",
+ "type": "application/atom+xml",
+ "href": ""
+ },
+ {
+ "rel": "salmon",
+ "href": "https://bad.com/salmon/friendica"
+ },
+ {
+ "rel": "http://microformats.org/profile/hcard",
+ "type": "text/html",
+ "href": "https://bad.com/hcard/friendica"
+ },
+ {
+ "rel": "http://joindiaspora.com/seed_location",
+ "type": "text/html",
+ "href": "https://bad.com"
+ }
+ ]
+}
diff --git a/test/mix/tasks/pleroma/app_test.exs b/test/mix/tasks/pleroma/app_test.exs
index 7eb4c4352..c4dcc59cb 100644
--- a/test/mix/tasks/pleroma/app_test.exs
+++ b/test/mix/tasks/pleroma/app_test.exs
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.AppTest do
- use Pleroma.DataCase, async: true
+ use Pleroma.DataCase, async: false
setup_all do
Mix.shell(Mix.Shell.Process)
@@ -50,13 +50,13 @@ test "with errors" do
defp assert_app(name, redirect, scopes) do
app = Repo.get_by(Pleroma.Web.OAuth.App, client_name: name)
- assert_receive {:mix_shell, :info, [message]}, 1_000
+ assert_receive {:mix_shell, :info, [message]}, 5_000
assert message == "#{name} successfully created:"
- assert_receive {:mix_shell, :info, [message]}, 1_000
+ assert_receive {:mix_shell, :info, [message]}, 5_000
assert message == "App client_id: #{app.client_id}"
- assert_receive {:mix_shell, :info, [message]}, 1_000
+ assert_receive {:mix_shell, :info, [message]}, 5_000
assert message == "App client_secret: #{app.client_secret}"
assert app.scopes == scopes
diff --git a/test/mix/tasks/pleroma/database_test.exs b/test/mix/tasks/pleroma/database_test.exs
index 97fa830ff..ba7d284d5 100644
--- a/test/mix/tasks/pleroma/database_test.exs
+++ b/test/mix/tasks/pleroma/database_test.exs
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.DatabaseTest do
- use Pleroma.DataCase, async: true
+ use Pleroma.DataCase, async: false
use Oban.Testing, repo: Pleroma.Repo
alias Pleroma.Activity
@@ -398,6 +398,7 @@ test "We don't have unexpected tables which may contain objects that are referen
["push_subscriptions"],
["registrations"],
["report_notes"],
+ ["rich_media_card"],
["scheduled_activities"],
["schema_migrations"],
["thread_mutes"],
diff --git a/test/mix/tasks/pleroma/ecto/rollback_test.exs b/test/mix/tasks/pleroma/ecto/rollback_test.exs
index 57a2ed655..0ddb5231a 100644
--- a/test/mix/tasks/pleroma/ecto/rollback_test.exs
+++ b/test/mix/tasks/pleroma/ecto/rollback_test.exs
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Ecto.RollbackTest do
- use Pleroma.DataCase, async: true
+ use Pleroma.DataCase, async: false
import ExUnit.CaptureLog
require Logger
diff --git a/test/mix/tasks/pleroma/ecto_test.exs b/test/mix/tasks/pleroma/ecto_test.exs
index 0164da5a8..eab2c2fcf 100644
--- a/test/mix/tasks/pleroma/ecto_test.exs
+++ b/test/mix/tasks/pleroma/ecto_test.exs
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.EctoTest do
- use ExUnit.Case, async: true
+ use ExUnit.Case, async: false
test "raise on bad path" do
assert_raise RuntimeError, ~r/Could not find migrations directory/, fn ->
diff --git a/test/mix/tasks/pleroma/emoji_test.exs b/test/mix/tasks/pleroma/emoji_test.exs
index bd20f285c..6549fbb70 100644
--- a/test/mix/tasks/pleroma/emoji_test.exs
+++ b/test/mix/tasks/pleroma/emoji_test.exs
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.EmojiTest do
- use ExUnit.Case, async: true
+ use ExUnit.Case, async: false
import ExUnit.CaptureIO
import Tesla.Mock
diff --git a/test/mix/tasks/pleroma/instance_test.exs b/test/mix/tasks/pleroma/instance_test.exs
index 17b2e3267..168343ce5 100644
--- a/test/mix/tasks/pleroma/instance_test.exs
+++ b/test/mix/tasks/pleroma/instance_test.exs
@@ -69,7 +69,9 @@ test "running gen" do
"test/uploads",
"--static-dir",
"./test/../test/instance/static/",
- "--strip-uploads",
+ "--strip-uploads-metadata",
+ "y",
+ "--read-uploads-description",
"y",
"--anonymize-uploads",
"n"
@@ -91,7 +93,10 @@ test "running gen" do
assert generated_config =~ "password: \"dbpass\""
assert generated_config =~ "configurable_from_database: true"
assert generated_config =~ "http: [ip: {127, 0, 0, 1}, port: 4000]"
- assert generated_config =~ "filters: [Pleroma.Upload.Filter.Exiftool]"
+
+ assert generated_config =~
+ "filters: [Pleroma.Upload.Filter.Exiftool.ReadDescription, Pleroma.Upload.Filter.Exiftool.StripMetadata]"
+
assert generated_config =~ "base_url: \"https://media.pleroma.social/media\""
assert File.read!(tmp_path() <> "setup.psql") == generated_setup_psql()
assert File.exists?(Path.expand("./test/instance/static/robots.txt"))
diff --git a/test/pleroma/activity_test.exs b/test/pleroma/activity_test.exs
index 4f9144f91..1943746cb 100644
--- a/test/pleroma/activity_test.exs
+++ b/test/pleroma/activity_test.exs
@@ -41,6 +41,26 @@ test "returns the activity that created an object" do
assert activity == found_activity
end
+ test "returns activities by object's AP id in requested presorted order" do
+ a1 = insert(:note_activity)
+ o1 = Object.normalize(a1, fetch: false).data["id"]
+
+ a2 = insert(:note_activity)
+ o2 = Object.normalize(a2, fetch: false).data["id"]
+
+ a3 = insert(:note_activity)
+ o3 = Object.normalize(a3, fetch: false).data["id"]
+
+ a4 = insert(:note_activity)
+ o4 = Object.normalize(a4, fetch: false).data["id"]
+
+ found_activities =
+ Activity.get_presorted_create_by_object_ap_id([o3, o2, o4, o1])
+ |> Repo.all()
+
+ assert found_activities == [a3, a2, a4, a1]
+ end
+
test "preloading a bookmark" do
user = insert(:user)
user2 = insert(:user)
diff --git a/test/pleroma/config/deprecation_warnings_test.exs b/test/pleroma/config/deprecation_warnings_test.exs
index 96d6fd739..fd8a558ee 100644
--- a/test/pleroma/config/deprecation_warnings_test.exs
+++ b/test/pleroma/config/deprecation_warnings_test.exs
@@ -11,6 +11,62 @@ defmodule Pleroma.Config.DeprecationWarningsTest do
alias Pleroma.Config
alias Pleroma.Config.DeprecationWarnings
+ describe "filter exiftool" do
+ test "gives warning when still used" do
+ clear_config(
+ [Pleroma.Upload, :filters],
+ [Pleroma.Upload.Filter.Exiftool]
+ )
+
+ assert capture_log(fn -> DeprecationWarnings.check_exiftool_filter() end) =~
+ """
+ !!!DEPRECATION WARNING!!!
+ Your config is using Exiftool as a filter instead of Exiftool.StripMetadata. This should work for now, but you are advised to change to the new configuration to prevent possible issues later:
+
+ ```
+ config :pleroma, Pleroma.Upload,
+ filters: [Pleroma.Upload.Filter.Exiftool]
+ ```
+
+ Is now
+
+
+ ```
+ config :pleroma, Pleroma.Upload,
+ filters: [Pleroma.Upload.Filter.Exiftool.StripMetadata]
+ ```
+ """
+ end
+
+ test "changes setting to exiftool strip metadata" do
+ clear_config(
+ [Pleroma.Upload, :filters],
+ [Pleroma.Upload.Filter.Exiftool, Pleroma.Upload.Filter.Exiftool.ReadDescription]
+ )
+
+ expected_config = [
+ Pleroma.Upload.Filter.Exiftool.StripMetadata,
+ Pleroma.Upload.Filter.Exiftool.ReadDescription
+ ]
+
+ capture_log(fn -> DeprecationWarnings.warn() end)
+
+ assert Config.get([Pleroma.Upload]) |> Keyword.get(:filters, []) == expected_config
+ end
+
+ test "doesn't give a warning with correct config" do
+ clear_config(
+ [Pleroma.Upload, :filters],
+ [
+ Pleroma.Upload.Filter.Exiftool.StripMetadata,
+ Pleroma.Upload.Filter.Exiftool.ReadDescription
+ ]
+ )
+
+ assert capture_log(fn -> DeprecationWarnings.check_exiftool_filter() end) == ""
+ end
+ end
+
describe "simple policy tuples" do
test "gives warning when there are still strings" do
clear_config([:mrf_simple],
diff --git a/test/pleroma/emoji/formatter_test.exs b/test/pleroma/emoji/formatter_test.exs
index eafdd5a43..5a6b05d3d 100644
--- a/test/pleroma/emoji/formatter_test.exs
+++ b/test/pleroma/emoji/formatter_test.exs
@@ -4,7 +4,7 @@
defmodule Pleroma.Emoji.FormatterTest do
alias Pleroma.Emoji.Formatter
- use Pleroma.DataCase, async: true
+ use Pleroma.DataCase, async: false
describe "emojify" do
test "it adds cool emoji" do
diff --git a/test/pleroma/html_test.exs b/test/pleroma/html_test.exs
index fe1a1ed57..beb482317 100644
--- a/test/pleroma/html_test.exs
+++ b/test/pleroma/html_test.exs
@@ -176,7 +176,7 @@ test "extracts the url" do
})
object = Object.normalize(activity, fetch: false)
- {:ok, url} = HTML.extract_first_external_url_from_object(object)
+ url = HTML.extract_first_external_url_from_object(object)
assert url == "https://github.com/komeiji-satori/Dress"
end
@@ -191,7 +191,7 @@ test "skips mentions" do
})
object = Object.normalize(activity, fetch: false)
- {:ok, url} = HTML.extract_first_external_url_from_object(object)
+ url = HTML.extract_first_external_url_from_object(object)
assert url == "https://github.com/syuilo/misskey/blob/develop/docs/setup.en.md"
@@ -207,7 +207,7 @@ test "skips hashtags" do
})
object = Object.normalize(activity, fetch: false)
- {:ok, url} = HTML.extract_first_external_url_from_object(object)
+ url = HTML.extract_first_external_url_from_object(object)
assert url == "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=72255140"
end
@@ -223,7 +223,7 @@ test "skips microformats hashtags" do
})
object = Object.normalize(activity, fetch: false)
- {:ok, url} = HTML.extract_first_external_url_from_object(object)
+ url = HTML.extract_first_external_url_from_object(object)
assert url == "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=72255140"
end
@@ -235,7 +235,7 @@ test "does not crash when there is an HTML entity in a link" do
object = Object.normalize(activity, fetch: false)
- assert {:ok, nil} = HTML.extract_first_external_url_from_object(object)
+ assert nil == HTML.extract_first_external_url_from_object(object)
end
test "skips attachment links" do
@@ -249,7 +249,7 @@ test "skips attachment links" do
object = Object.normalize(activity, fetch: false)
- assert {:ok, nil} = HTML.extract_first_external_url_from_object(object)
+ assert nil == HTML.extract_first_external_url_from_object(object)
end
end
end
diff --git a/test/pleroma/http/adapter_helper_test.exs b/test/pleroma/http/adapter_helper_test.exs
index 344332814..0951187aa 100644
--- a/test/pleroma/http/adapter_helper_test.exs
+++ b/test/pleroma/http/adapter_helper_test.exs
@@ -73,4 +73,18 @@ test "should get set" do
assert options[:pools][:default][:size] == 50
end
end
+
+ describe "pool idle time setting" do
+ test "should get set" do
+ options = AdapterHelper.add_default_pool_max_idle_time([], 50)
+ assert options[:pools][:default][:pool_max_idle_time] == 50
+ end
+ end
+
+ describe "connection timeout setting" do
+ test "should get set" do
+ options = AdapterHelper.add_default_conn_max_idle_time([], 50)
+ assert options[:pools][:default][:conn_max_idle_time] == 50
+ end
+ end
end
diff --git a/test/pleroma/http/backoff_test.exs b/test/pleroma/http/backoff_test.exs
new file mode 100644
index 000000000..8e0afad05
--- /dev/null
+++ b/test/pleroma/http/backoff_test.exs
@@ -0,0 +1,105 @@
+defmodule Pleroma.HTTP.BackoffTest do
+ @backoff_cache :http_backoff_cache
+ use Pleroma.DataCase, async: false
+ alias Pleroma.HTTP.Backoff
+
+ defp within_tolerance?(ttl, expected) do
+ ttl > expected - 15 and ttl < expected + 15
+ end
+
+ describe "get/3" do
+ test "should return {:ok, env} when not rate limited" do
+ Tesla.Mock.mock_global(fn
+ %Tesla.Env{url: "https://akkoma.dev/api/v1/instance"} ->
+ {:ok, %Tesla.Env{status: 200, body: "ok"}}
+ end)
+
+ assert {:ok, env} = Backoff.get("https://akkoma.dev/api/v1/instance")
+ assert env.status == 200
+ end
+
+ test "should return {:error, env} when rate limited" do
+ # Shove a value into the cache to simulate a rate limit
+ Cachex.put(@backoff_cache, "akkoma.dev", true)
+ assert {:error, :ratelimit} = Backoff.get("https://akkoma.dev/api/v1/instance")
+ end
+
+ test "should insert a value into the cache when rate limited" do
+ Tesla.Mock.mock_global(fn
+ %Tesla.Env{url: "https://ratelimited.dev/api/v1/instance"} ->
+ {:ok, %Tesla.Env{status: 429, body: "Rate limited"}}
+ end)
+
+ assert {:error, :ratelimit} = Backoff.get("https://ratelimited.dev/api/v1/instance")
+ assert {:ok, true} = Cachex.get(@backoff_cache, "ratelimited.dev")
+ end
+
+ test "should insert a value into the cache when rate limited with a 503 response" do
+ Tesla.Mock.mock_global(fn
+ %Tesla.Env{url: "https://ratelimited.dev/api/v1/instance"} ->
+ {:ok, %Tesla.Env{status: 503, body: "Rate limited"}}
+ end)
+
+ assert {:error, :ratelimit} = Backoff.get("https://ratelimited.dev/api/v1/instance")
+ assert {:ok, true} = Cachex.get(@backoff_cache, "ratelimited.dev")
+ end
+
+ test "should parse the value of x-ratelimit-reset, if present" do
+ ten_minutes_from_now =
+ DateTime.utc_now() |> Timex.shift(minutes: 10) |> DateTime.to_iso8601()
+
+ Tesla.Mock.mock_global(fn
+ %Tesla.Env{url: "https://ratelimited.dev/api/v1/instance"} ->
+ {:ok,
+ %Tesla.Env{
+ status: 429,
+ body: "Rate limited",
+ headers: [{"x-ratelimit-reset", ten_minutes_from_now}]
+ }}
+ end)
+
+ assert {:error, :ratelimit} = Backoff.get("https://ratelimited.dev/api/v1/instance")
+ assert {:ok, true} = Cachex.get(@backoff_cache, "ratelimited.dev")
+ {:ok, ttl} = Cachex.ttl(@backoff_cache, "ratelimited.dev")
+ assert within_tolerance?(ttl, 600)
+ end
+
+ test "should parse the value of retry-after when it's a timestamp" do
+ ten_minutes_from_now =
+ DateTime.utc_now() |> Timex.shift(minutes: 10) |> DateTime.to_iso8601()
+
+ Tesla.Mock.mock_global(fn
+ %Tesla.Env{url: "https://ratelimited.dev/api/v1/instance"} ->
+ {:ok,
+ %Tesla.Env{
+ status: 429,
+ body: "Rate limited",
+ headers: [{"retry-after", ten_minutes_from_now}]
+ }}
+ end)
+
+ assert {:error, :ratelimit} = Backoff.get("https://ratelimited.dev/api/v1/instance")
+ assert {:ok, true} = Cachex.get(@backoff_cache, "ratelimited.dev")
+ {:ok, ttl} = Cachex.ttl(@backoff_cache, "ratelimited.dev")
+ assert within_tolerance?(ttl, 600)
+ end
+
+ test "should parse the value of retry-after when it's a number of seconds" do
+ Tesla.Mock.mock_global(fn
+ %Tesla.Env{url: "https://ratelimited.dev/api/v1/instance"} ->
+ {:ok,
+ %Tesla.Env{
+ status: 429,
+ body: "Rate limited",
+ headers: [{"retry-after", "600"}]
+ }}
+ end)
+
+ assert {:error, :ratelimit} = Backoff.get("https://ratelimited.dev/api/v1/instance")
+ assert {:ok, true} = Cachex.get(@backoff_cache, "ratelimited.dev")
+ # assert that the value is 10 minutes from now
+ {:ok, ttl} = Cachex.ttl(@backoff_cache, "ratelimited.dev")
+ assert within_tolerance?(ttl, 600)
+ end
+ end
+end
diff --git a/test/pleroma/http_test.exs b/test/pleroma/http_test.exs
index e8a2538d3..8619196ad 100644
--- a/test/pleroma/http_test.exs
+++ b/test/pleroma/http_test.exs
@@ -69,4 +69,14 @@ test "returns successfully result" do
}
end
end
+
+ describe "Catching errors" do
+ test "rescues when adapter throws an error" do
+ mock(fn
+ %{method: :get, url: "http://example.com/hello"} -> raise ArgumentError
+ end)
+
+ assert HTTP.get("http://example.com/hello") == {:error, :fetch_error}
+ end
+ end
end
diff --git a/test/pleroma/object/fetcher_test.exs b/test/pleroma/object/fetcher_test.exs
index 4c4831af3..12154cb05 100644
--- a/test/pleroma/object/fetcher_test.exs
+++ b/test/pleroma/object/fetcher_test.exs
@@ -57,6 +57,9 @@ defp spoofed_object_with_ids(
body: spoofed_object_with_ids("https://patch.cx/objects/spoof_content_type")
}
+ %{method: :get, url: "https://octodon.social/users/cwebber/statuses/111647596861000656"} ->
+ %Tesla.Env{status: 403}
+
# Spoof: mismatching ids
# Variant 1: Non-exisitng fake id
%{
@@ -203,8 +206,7 @@ test "it works when fetching the OP actor errors out" do
test "it returns thread depth exceeded error if thread depth is exceeded" do
clear_config([:instance, :federation_incoming_replies_max_depth], 0)
- assert {:error, "Max thread distance exceeded."} =
- Fetcher.fetch_object_from_id(@ap_id, depth: 1)
+ assert {:error, :allowed_depth} = Fetcher.fetch_object_from_id(@ap_id, depth: 1)
end
test "it fetches object if max thread depth is restricted to 0 and depth is not specified" do
@@ -250,12 +252,12 @@ test "it does not fetch a spoofed object with wrong content type" do
end
test "it does not fetch a spoofed object with id different from URL" do
- assert {:error, "Object's ActivityPub id/url does not match final fetch URL"} =
+ assert {:error, :id_mismatch} =
Fetcher.fetch_and_contain_remote_object_from_id(
"https://patch.cx/media/03ca3c8b4ac3ddd08bf0f84be7885f2f88de0f709112131a22d83650819e36c2.json"
)
- assert {:error, "Object's ActivityPub id/url does not match final fetch URL"} =
+ assert {:error, :id_mismatch} =
Fetcher.fetch_and_contain_remote_object_from_id(
"https://patch.cx/media/spoof_stage1.json"
)
@@ -285,14 +287,14 @@ test "it accepts same-domain redirects" do
end
test "it does not fetch a spoofed object with a foreign actor" do
- assert {:error, "Object containment failed."} =
+ assert {:error, _} =
Fetcher.fetch_and_contain_remote_object_from_id(
"https://patch.cx/objects/spoof_foreign_actor"
)
end
test "it does not fetch from localhost" do
- assert {:error, "Trying to fetch local resource"} =
+ assert {:error, :local_resource} =
Fetcher.fetch_and_contain_remote_object_from_id(
Pleroma.Web.Endpoint.url() <> "/spoof_local"
)
@@ -402,16 +404,14 @@ test "all objects with fake directions are rejected by the object fetcher" do
end
test "handle HTTP 410 Gone response" do
- assert {:error,
- {"Object has been deleted", "https://mastodon.example.org/users/userisgone", 410}} ==
+ assert {:error, :not_found} ==
Fetcher.fetch_and_contain_remote_object_from_id(
"https://mastodon.example.org/users/userisgone"
)
end
test "handle HTTP 404 response" do
- assert {:error,
- {"Object has been deleted", "https://mastodon.example.org/users/userisgone404", 404}} ==
+ assert {:error, :not_found} ==
Fetcher.fetch_and_contain_remote_object_from_id(
"https://mastodon.example.org/users/userisgone404"
)
diff --git a/test/pleroma/search/database_search_test.exs b/test/pleroma/search/database_search_test.exs
index 2387ac29b..91bb4cfdf 100644
--- a/test/pleroma/search/database_search_test.exs
+++ b/test/pleroma/search/database_search_test.exs
@@ -18,21 +18,6 @@ test "it finds something" do
assert result.id == post.id
end
- test "using plainto_tsquery on postgres < 11" do
- old_version = :persistent_term.get({Pleroma.Repo, :postgres_version})
- :persistent_term.put({Pleroma.Repo, :postgres_version}, 10.0)
- on_exit(fn -> :persistent_term.put({Pleroma.Repo, :postgres_version}, old_version) end)
-
- user = insert(:user)
- {:ok, post} = CommonAPI.post(user, %{status: "it's wednesday my dudes"})
- {:ok, _post2} = CommonAPI.post(user, %{status: "it's wednesday my bros"})
-
- # plainto doesn't understand complex queries
- assert [result] = DatabaseSearch.search(nil, "wednesday -dudes")
-
- assert result.id == post.id
- end
-
test "using websearch_to_tsquery" do
user = insert(:user)
{:ok, _post} = CommonAPI.post(user, %{status: "it's wednesday my dudes"})
diff --git a/test/pleroma/signature_test.exs b/test/pleroma/signature_test.exs
index e2d02fe4c..768c78f21 100644
--- a/test/pleroma/signature_test.exs
+++ b/test/pleroma/signature_test.exs
@@ -110,7 +110,7 @@ test "it returns signature headers" do
headers = %{
host: "test.test",
- "content-length": 100
+ "content-length": "100"
}
assert_signature_equal(
@@ -127,7 +127,7 @@ test "it returns error" do
assert Signature.sign(
user,
- %{host: "test.test", "content-length": 100}
+ %{host: "test.test", "content-length": "100"}
) == {:error, []}
end
end
@@ -153,6 +153,11 @@ test "it deduces the actor ID for streams" do
{:ok, "https://example.com/users/1234"}
end
+ test "it deduces the actor ID for bridgy" do
+ assert Signature.key_id_to_actor_id("https://example.com/1234#key") ==
+ {:ok, "https://example.com/1234"}
+ end
+
test "it calls webfinger for 'acct:' accounts" do
with_mock(Pleroma.Web.WebFinger,
finger: fn _ -> {:ok, %{"ap_id" => "https://gensokyo.2hu/users/raymoo"}} end
diff --git a/test/pleroma/upload/filter/exiftool/read_description_test.exs b/test/pleroma/upload/filter/exiftool/read_description_test.exs
new file mode 100644
index 000000000..f18da2371
--- /dev/null
+++ b/test/pleroma/upload/filter/exiftool/read_description_test.exs
@@ -0,0 +1,117 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Upload.Filter.Exiftool.ReadDescriptionTest do
+ use Pleroma.DataCase, async: false
+ alias Pleroma.Upload.Filter
+
+ @uploads %Pleroma.Upload{
+ name: "image_with_imagedescription_and_caption-abstract.jpg",
+ content_type: "image/jpeg",
+ path: Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
+ tempfile: Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
+ description: nil
+ }
+
+ test "keeps description when not empty" do
+ uploads = %Pleroma.Upload{
+ name: "image_with_imagedescription_and_caption-abstract.jpg",
+ content_type: "image/jpeg",
+ path: Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
+ tempfile:
+ Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
+ description: "Some description"
+ }
+
+ assert Filter.Exiftool.ReadDescription.filter(uploads) ==
+ {:ok, :noop}
+ end
+
+ test "otherwise returns ImageDescription when present" do
+ uploads_after = %Pleroma.Upload{
+ name: "image_with_imagedescription_and_caption-abstract.jpg",
+ content_type: "image/jpeg",
+ path: Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
+ tempfile:
+ Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
+ description: "a descriptive white pixel"
+ }
+
+ assert Filter.Exiftool.ReadDescription.filter(@uploads) ==
+ {:ok, :filtered, uploads_after}
+ end
+
+ test "otherwise returns iptc:Caption-Abstract when present" do
+ upload = %Pleroma.Upload{
+ name: "image_with_caption-abstract.jpg",
+ content_type: "image/jpeg",
+ path: Path.absname("test/fixtures/image_with_caption-abstract.jpg"),
+ tempfile: Path.absname("test/fixtures/image_with_caption-abstract.jpg"),
+ description: nil
+ }
+
+ upload_after = %Pleroma.Upload{
+ name: "image_with_caption-abstract.jpg",
+ content_type: "image/jpeg",
+ path: Path.absname("test/fixtures/image_with_caption-abstract.jpg"),
+ tempfile: Path.absname("test/fixtures/image_with_caption-abstract.jpg"),
+ description: "an abstract white pixel"
+ }
+
+ assert Filter.Exiftool.ReadDescription.filter(upload) ==
+ {:ok, :filtered, upload_after}
+ end
+
+ test "otherwise returns nil" do
+ uploads = %Pleroma.Upload{
+ name: "image_with_no_description.jpg",
+ content_type: "image/jpeg",
+ path: Path.absname("test/fixtures/image_with_no_description.jpg"),
+ tempfile: Path.absname("test/fixtures/image_with_no_description.jpg"),
+ description: nil
+ }
+
+ assert Filter.Exiftool.ReadDescription.filter(uploads) ==
+ {:ok, :filtered, uploads}
+ end
+
+ test "Return nil when image description from EXIF data exceeds the maximum length" do
+ clear_config([:instance, :description_limit], 5)
+
+ assert Filter.Exiftool.ReadDescription.filter(@uploads) ==
+ {:ok, :filtered, @uploads}
+ end
+
+ test "Ignores content with only whitespace" do
+ uploads = %Pleroma.Upload{
+ name: "non-existant.jpg",
+ content_type: "image/jpeg",
+ path:
+ Path.absname(
+ "test/fixtures/image_with_imagedescription_and_caption-abstract_whitespaces.jpg"
+ ),
+ tempfile:
+ Path.absname(
+ "test/fixtures/image_with_imagedescription_and_caption-abstract_whitespaces.jpg"
+ ),
+ description: nil
+ }
+
+ assert Filter.Exiftool.ReadDescription.filter(uploads) ==
+ {:ok, :filtered, uploads}
+ end
+
+ test "Return nil when image description from EXIF data can't be read" do
+ uploads = %Pleroma.Upload{
+ name: "non-existant.jpg",
+ content_type: "image/jpeg",
+ path: Path.absname("test/fixtures/non-existant.jpg"),
+ tempfile: Path.absname("test/fixtures/non-existant_tmp.jpg"),
+ description: nil
+ }
+
+ assert Filter.Exiftool.ReadDescription.filter(uploads) ==
+ {:ok, :filtered, uploads}
+ end
+end
diff --git a/test/pleroma/upload/filter/exiftool/strip_location_test.exs b/test/pleroma/upload/filter/exiftool/strip_location_test.exs
new file mode 100644
index 000000000..2e017cd7e
--- /dev/null
+++ b/test/pleroma/upload/filter/exiftool/strip_location_test.exs
@@ -0,0 +1,148 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Upload.Filter.Exiftool.StripMetadataTest do
+ use Pleroma.DataCase
+ alias Pleroma.Upload.Filter
+
+ @tag :tmp_dir
+ test "exiftool strip metadata strips GPS etc but preserves Orientation and ColorSpace by default",
+ %{tmp_dir: tmp_dir} do
+ assert Pleroma.Utils.command_available?("exiftool")
+
+ tmpfile = Path.join(tmp_dir, "tmp.jpg")
+
+ File.cp!(
+ "test/fixtures/DSCN0010.jpg",
+ tmpfile
+ )
+
+ upload = %Pleroma.Upload{
+ name: "image_with_GPS_data.jpg",
+ content_type: "image/jpeg",
+ path: Path.absname("test/fixtures/DSCN0010.jpg"),
+ tempfile: Path.absname(tmpfile)
+ }
+
+ assert Filter.Exiftool.StripMetadata.filter(upload) == {:ok, :filtered}
+
+ exif_original = read_exif("test/fixtures/DSCN0010.jpg")
+ exif_filtered = read_exif(tmpfile)
+
+ refute exif_original == exif_filtered
+ assert String.match?(exif_original, ~r/GPS/)
+ refute String.match?(exif_filtered, ~r/GPS/)
+ assert String.match?(exif_original, ~r/Camera Model Name/)
+ refute String.match?(exif_filtered, ~r/Camera Model Name/)
+ assert String.match?(exif_original, ~r/Orientation/)
+ assert String.match?(exif_filtered, ~r/Orientation/)
+ assert String.match?(exif_original, ~r/Color Space/)
+ assert String.match?(exif_filtered, ~r/Color Space/)
+ end
+
+ # this is a nonsensical configuration, but it shouldn't explode
+ @tag :tmp_dir
+ test "exiftool strip metadata is a noop with empty purge list", %{tmp_dir: tmp_dir} do
+ assert Pleroma.Utils.command_available?("exiftool")
+ clear_config([Pleroma.Upload.Filter.Exiftool.StripMetadata, :purge], [])
+
+ tmpfile = Path.join(tmp_dir, "tmp.jpg")
+
+ File.cp!(
+ "test/fixtures/DSCN0010.jpg",
+ tmpfile
+ )
+
+ upload = %Pleroma.Upload{
+ name: "image_with_GPS_data.jpg",
+ content_type: "image/jpeg",
+ path: Path.absname("test/fixtures/DSCN0010.jpg"),
+ tempfile: Path.absname(tmpfile)
+ }
+
+ assert Filter.Exiftool.StripMetadata.filter(upload) == {:ok, :filtered}
+
+ exif_original = read_exif("test/fixtures/DSCN0010.jpg")
+ exif_filtered = read_exif(tmpfile)
+
+ assert exif_original == exif_filtered
+ end
+
+ @tag :tmp_dir
+ test "exiftool strip metadata works with empty preserve list", %{tmp_dir: tmp_dir} do
+ assert Pleroma.Utils.command_available?("exiftool")
+ clear_config([Pleroma.Upload.Filter.Exiftool.StripMetadata, :preserve], [])
+
+ tmpfile = Path.join(tmp_dir, "tmp.jpg")
+
+ File.cp!(
+ "test/fixtures/DSCN0010.jpg",
+ tmpfile
+ )
+
+ upload = %Pleroma.Upload{
+ name: "image_with_GPS_data.jpg",
+ content_type: "image/jpeg",
+ path: Path.absname("test/fixtures/DSCN0010.jpg"),
+ tempfile: Path.absname(tmpfile)
+ }
+
+ write_exif(["-ImageDescription=Trees and Houses", "-Orientation=1", tmpfile])
+ exif_extended = read_exif(tmpfile)
+ assert String.match?(exif_extended, ~r/Image Description[ \t]*:[ \t]*Trees and Houses/)
+ assert String.match?(exif_extended, ~r/Orientation/)
+
+ assert Filter.Exiftool.StripMetadata.filter(upload) == {:ok, :filtered}
+
+ exif_original = read_exif("test/fixtures/DSCN0010.jpg")
+ exif_filtered = read_exif(tmpfile)
+
+ refute exif_original == exif_filtered
+ refute exif_extended == exif_filtered
+ assert String.match?(exif_original, ~r/GPS/)
+ refute String.match?(exif_filtered, ~r/GPS/)
+ refute String.match?(exif_filtered, ~r/Image Description/)
+ refute String.match?(exif_filtered, ~r/Orientation/)
+ end
+
+ test "verify webp files are skipped" do
+ upload = %Pleroma.Upload{
+ name: "sample.webp",
+ content_type: "image/webp"
+ }
+
+ assert Filter.Exiftool.StripMetadata.filter(upload) == {:ok, :noop}
+ end
+
+ test "verify svg files are skipped" do
+ upload = %Pleroma.Upload{
+ name: "sample.svg",
+ content_type: "image/svg+xml"
+ }
+
+ assert Filter.Exiftool.StripMetadata.filter(upload) == {:ok, :noop}
+ end
+
+ defp read_exif(file) do
+ # time and file path tags cause mismatches even for byte-identical files
+ {exif_data, 0} =
+ System.cmd("exiftool", [
+ "-x",
+ "Time:All",
+ "-x",
+ "Directory",
+ "-x",
+ "FileName",
+ "-x",
+ "FileSize",
+ file
+ ])
+
+ exif_data
+ end
+
+ defp write_exif(args) do
+ {_response, 0} = System.cmd("exiftool", ["-ignoreMinorErrors", "-overwrite_original" | args])
+ end
+end
diff --git a/test/pleroma/upload/filter/exiftool_test.exs b/test/pleroma/upload/filter/exiftool_test.exs
deleted file mode 100644
index 5f38fa277..000000000
--- a/test/pleroma/upload/filter/exiftool_test.exs
+++ /dev/null
@@ -1,42 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2021 Pleroma Authors
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Upload.Filter.ExiftoolTest do
- use Pleroma.DataCase
- alias Pleroma.Upload.Filter
-
- test "apply exiftool filter" do
- assert Pleroma.Utils.command_available?("exiftool")
-
- File.cp!(
- "test/fixtures/DSCN0010.jpg",
- "test/fixtures/DSCN0010_tmp.jpg"
- )
-
- upload = %Pleroma.Upload{
- name: "image_with_GPS_data.jpg",
- content_type: "image/jpeg",
- path: Path.absname("test/fixtures/DSCN0010.jpg"),
- tempfile: Path.absname("test/fixtures/DSCN0010_tmp.jpg")
- }
-
- assert Filter.Exiftool.filter(upload) == {:ok, :filtered}
-
- {exif_original, 0} = System.cmd("exiftool", ["test/fixtures/DSCN0010.jpg"])
- {exif_filtered, 0} = System.cmd("exiftool", ["test/fixtures/DSCN0010_tmp.jpg"])
-
- refute exif_original == exif_filtered
- assert String.match?(exif_original, ~r/GPS/)
- refute String.match?(exif_filtered, ~r/GPS/)
- end
-
- test "verify webp files are skipped" do
- upload = %Pleroma.Upload{
- name: "sample.webp",
- content_type: "image/webp"
- }
-
- assert Filter.Exiftool.filter(upload) == {:ok, :noop}
- end
-end
diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs
index 96ca8d0fd..de71f4b95 100644
--- a/test/pleroma/user_test.exs
+++ b/test/pleroma/user_test.exs
@@ -765,109 +765,19 @@ test "gets an existing user by nickname starting with http" do
setup do: clear_config([Pleroma.Web.WebFinger, :update_nickname_on_user_fetch], true)
test "for mastodon" do
- Tesla.Mock.mock(fn
- %{url: "https://example.com/.well-known/host-meta"} ->
- %Tesla.Env{
- status: 302,
- headers: [{"location", "https://sub.example.com/.well-known/host-meta"}]
- }
-
- %{url: "https://sub.example.com/.well-known/host-meta"} ->
- %Tesla.Env{
- status: 200,
- body:
- "test/fixtures/webfinger/masto-host-meta.xml"
- |> File.read!()
- |> String.replace("{{domain}}", "sub.example.com")
- }
-
- %{url: "https://sub.example.com/.well-known/webfinger?resource=acct:a@example.com"} ->
- %Tesla.Env{
- status: 200,
- body:
- "test/fixtures/webfinger/masto-webfinger.json"
- |> File.read!()
- |> String.replace("{{nickname}}", "a")
- |> String.replace("{{domain}}", "example.com")
- |> String.replace("{{subdomain}}", "sub.example.com"),
- headers: [{"content-type", "application/jrd+json"}]
- }
-
- %{url: "https://sub.example.com/users/a"} ->
- %Tesla.Env{
- status: 200,
- body:
- "test/fixtures/webfinger/masto-user.json"
- |> File.read!()
- |> String.replace("{{nickname}}", "a")
- |> String.replace("{{domain}}", "sub.example.com"),
- headers: [{"content-type", "application/activity+json"}]
- }
-
- %{url: "https://sub.example.com/users/a/collections/featured"} ->
- %Tesla.Env{
- status: 200,
- body:
- File.read!("test/fixtures/users_mock/masto_featured.json")
- |> String.replace("{{domain}}", "sub.example.com")
- |> String.replace("{{nickname}}", "a"),
- headers: [{"content-type", "application/activity+json"}]
- }
- end)
-
- ap_id = "a@example.com"
+ ap_id = "a@mastodon.example"
{:ok, fetched_user} = User.get_or_fetch(ap_id)
- assert fetched_user.ap_id == "https://sub.example.com/users/a"
- assert fetched_user.nickname == "a@example.com"
+ assert fetched_user.ap_id == "https://sub.mastodon.example/users/a"
+ assert fetched_user.nickname == "a@mastodon.example"
end
test "for pleroma" do
- Tesla.Mock.mock(fn
- %{url: "https://example.com/.well-known/host-meta"} ->
- %Tesla.Env{
- status: 302,
- headers: [{"location", "https://sub.example.com/.well-known/host-meta"}]
- }
-
- %{url: "https://sub.example.com/.well-known/host-meta"} ->
- %Tesla.Env{
- status: 200,
- body:
- "test/fixtures/webfinger/pleroma-host-meta.xml"
- |> File.read!()
- |> String.replace("{{domain}}", "sub.example.com")
- }
-
- %{url: "https://sub.example.com/.well-known/webfinger?resource=acct:a@example.com"} ->
- %Tesla.Env{
- status: 200,
- body:
- "test/fixtures/webfinger/pleroma-webfinger.json"
- |> File.read!()
- |> String.replace("{{nickname}}", "a")
- |> String.replace("{{domain}}", "example.com")
- |> String.replace("{{subdomain}}", "sub.example.com"),
- headers: [{"content-type", "application/jrd+json"}]
- }
-
- %{url: "https://sub.example.com/users/a"} ->
- %Tesla.Env{
- status: 200,
- body:
- "test/fixtures/webfinger/pleroma-user.json"
- |> File.read!()
- |> String.replace("{{nickname}}", "a")
- |> String.replace("{{domain}}", "sub.example.com"),
- headers: [{"content-type", "application/activity+json"}]
- }
- end)
-
- ap_id = "a@example.com"
+ ap_id = "a@pleroma.example"
{:ok, fetched_user} = User.get_or_fetch(ap_id)
- assert fetched_user.ap_id == "https://sub.example.com/users/a"
- assert fetched_user.nickname == "a@example.com"
+ assert fetched_user.ap_id == "https://sub.pleroma.example/users/a"
+ assert fetched_user.nickname == "a@pleroma.example"
end
end
diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
index dcb5f143c..b325bcb9a 100644
--- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
@@ -1418,244 +1418,6 @@ test "It returns poll Answers when authenticated", %{conn: conn} do
end
end
- describe "POST /users/:nickname/outbox (C2S)" do
- setup do: clear_config([:instance, :limit])
-
- setup do
- [
- activity: %{
- "@context" => "https://www.w3.org/ns/activitystreams",
- "type" => "Create",
- "object" => %{
- "type" => "Note",
- "content" => "AP C2S test",
- "to" => "https://www.w3.org/ns/activitystreams#Public",
- "cc" => []
- }
- }
- ]
- end
-
- test "it rejects posts from other users / unauthenticated users", %{
- conn: conn,
- activity: activity
- } do
- user = insert(:user)
- other_user = insert(:user)
- conn = put_req_header(conn, "content-type", "application/activity+json")
-
- conn
- |> post("/users/#{user.nickname}/outbox", activity)
- |> json_response(403)
-
- conn
- |> assign(:user, other_user)
- |> post("/users/#{user.nickname}/outbox", activity)
- |> json_response(403)
- end
-
- test "it inserts an incoming create activity into the database", %{
- conn: conn,
- activity: activity
- } do
- user = insert(:user)
-
- result =
- conn
- |> assign(:user, user)
- |> put_req_header("content-type", "application/activity+json")
- |> post("/users/#{user.nickname}/outbox", activity)
- |> json_response(201)
-
- assert Activity.get_by_ap_id(result["id"])
- assert result["object"]
- assert %Object{data: object} = Object.normalize(result["object"], fetch: false)
- assert object["content"] == activity["object"]["content"]
- end
-
- test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
- user = insert(:user)
-
- activity =
- activity
- |> put_in(["object", "type"], "Benis")
-
- _result =
- conn
- |> assign(:user, user)
- |> put_req_header("content-type", "application/activity+json")
- |> post("/users/#{user.nickname}/outbox", activity)
- |> json_response(400)
- end
-
- test "it inserts an incoming sensitive activity into the database", %{
- conn: conn,
- activity: activity
- } do
- user = insert(:user)
- conn = assign(conn, :user, user)
- object = Map.put(activity["object"], "sensitive", true)
- activity = Map.put(activity, "object", object)
-
- response =
- conn
- |> put_req_header("content-type", "application/activity+json")
- |> post("/users/#{user.nickname}/outbox", activity)
- |> json_response(201)
-
- assert Activity.get_by_ap_id(response["id"])
- assert response["object"]
- assert %Object{data: response_object} = Object.normalize(response["object"], fetch: false)
- assert response_object["sensitive"] == true
- assert response_object["content"] == activity["object"]["content"]
-
- representation =
- conn
- |> put_req_header("accept", "application/activity+json")
- |> get(response["id"])
- |> json_response(200)
-
- assert representation["object"]["sensitive"] == true
- end
-
- test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
- user = insert(:user)
- activity = Map.put(activity, "type", "BadType")
-
- conn =
- conn
- |> assign(:user, user)
- |> put_req_header("content-type", "application/activity+json")
- |> post("/users/#{user.nickname}/outbox", activity)
-
- assert json_response(conn, 400)
- end
-
- test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
- note_activity = insert(:note_activity)
- note_object = Object.normalize(note_activity, fetch: false)
- user = User.get_cached_by_ap_id(note_activity.data["actor"])
-
- data = %{
- "type" => "Delete",
- "object" => %{
- "id" => note_object.data["id"]
- }
- }
-
- result =
- conn
- |> assign(:user, user)
- |> put_req_header("content-type", "application/activity+json")
- |> post("/users/#{user.nickname}/outbox", data)
- |> json_response(201)
-
- assert Activity.get_by_ap_id(result["id"])
-
- assert object = Object.get_by_ap_id(note_object.data["id"])
- assert object.data["type"] == "Tombstone"
- end
-
- test "it rejects delete activity of object from other actor", %{conn: conn} do
- note_activity = insert(:note_activity)
- note_object = Object.normalize(note_activity, fetch: false)
- user = insert(:user)
-
- data = %{
- type: "Delete",
- object: %{
- id: note_object.data["id"]
- }
- }
-
- conn =
- conn
- |> assign(:user, user)
- |> put_req_header("content-type", "application/activity+json")
- |> post("/users/#{user.nickname}/outbox", data)
-
- assert json_response(conn, 403)
- end
-
- test "it increases like count when receiving a like action", %{conn: conn} do
- note_activity = insert(:note_activity)
- note_object = Object.normalize(note_activity, fetch: false)
- user = User.get_cached_by_ap_id(note_activity.data["actor"])
-
- data = %{
- type: "Like",
- object: %{
- id: note_object.data["id"]
- }
- }
-
- conn =
- conn
- |> assign(:user, user)
- |> put_req_header("content-type", "application/activity+json")
- |> post("/users/#{user.nickname}/outbox", data)
-
- result = json_response(conn, 201)
- assert Activity.get_by_ap_id(result["id"])
-
- assert object = Object.get_by_ap_id(note_object.data["id"])
- assert object.data["like_count"] == 1
- end
-
- test "it doesn't spreads faulty attributedTo or actor fields", %{
- conn: conn,
- activity: activity
- } do
- reimu = insert(:user, nickname: "reimu")
- cirno = insert(:user, nickname: "cirno")
-
- assert reimu.ap_id
- assert cirno.ap_id
-
- activity =
- activity
- |> put_in(["object", "actor"], reimu.ap_id)
- |> put_in(["object", "attributedTo"], reimu.ap_id)
- |> put_in(["actor"], reimu.ap_id)
- |> put_in(["attributedTo"], reimu.ap_id)
-
- _reimu_outbox =
- conn
- |> assign(:user, cirno)
- |> put_req_header("content-type", "application/activity+json")
- |> post("/users/#{reimu.nickname}/outbox", activity)
- |> json_response(403)
-
- cirno_outbox =
- conn
- |> assign(:user, cirno)
- |> put_req_header("content-type", "application/activity+json")
- |> post("/users/#{cirno.nickname}/outbox", activity)
- |> json_response(201)
-
- assert cirno_outbox["attributedTo"] == nil
- assert cirno_outbox["actor"] == cirno.ap_id
-
- assert cirno_object = Object.normalize(cirno_outbox["object"], fetch: false)
- assert cirno_object.data["actor"] == cirno.ap_id
- assert cirno_object.data["attributedTo"] == cirno.ap_id
- end
-
- test "Character limitation", %{conn: conn, activity: activity} do
- clear_config([:instance, :limit], 5)
- user = insert(:user)
-
- result =
- conn
- |> assign(:user, user)
- |> put_req_header("content-type", "application/activity+json")
- |> post("/users/#{user.nickname}/outbox", activity)
- |> json_response(400)
-
- assert result == "Character limit (5 characters) exceeded, contains 11 characters"
- end
- end
-
describe "/relay/followers" do
test "it returns relay followers", %{conn: conn} do
relay_actor = Relay.get_actor()
@@ -1977,95 +1739,6 @@ test "it tracks a signed activity fetch when the json is cached", %{conn: conn}
end
end
- describe "Additional ActivityPub C2S endpoints" do
- test "GET /api/ap/whoami", %{conn: conn} do
- user = insert(:user)
-
- conn =
- conn
- |> assign(:user, user)
- |> get("/api/ap/whoami")
-
- user = User.get_cached_by_id(user.id)
-
- assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
-
- conn
- |> get("/api/ap/whoami")
- |> json_response(403)
- end
-
- setup do: clear_config([:media_proxy])
- setup do: clear_config([Pleroma.Upload])
-
- test "POST /api/ap/upload_media", %{conn: conn} do
- user = insert(:user)
-
- desc = "Description of the image"
-
- image = %Plug.Upload{
- content_type: "image/jpeg",
- path: Path.absname("test/fixtures/image.jpg"),
- filename: "an_image.jpg"
- }
-
- object =
- conn
- |> assign(:user, user)
- |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
- |> json_response(:created)
-
- assert object["name"] == desc
- assert object["type"] == "Document"
- assert object["actor"] == user.ap_id
- assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
- assert is_binary(object_href)
- assert object_mediatype == "image/jpeg"
- assert String.ends_with?(object_href, ".jpg")
-
- activity_request = %{
- "@context" => "https://www.w3.org/ns/activitystreams",
- "type" => "Create",
- "object" => %{
- "type" => "Note",
- "content" => "AP C2S test, attachment",
- "attachment" => [object],
- "to" => "https://www.w3.org/ns/activitystreams#Public",
- "cc" => []
- }
- }
-
- activity_response =
- conn
- |> assign(:user, user)
- |> post("/users/#{user.nickname}/outbox", activity_request)
- |> json_response(:created)
-
- assert activity_response["id"]
- assert activity_response["object"]
- assert activity_response["actor"] == user.ap_id
-
- assert %Object{data: %{"attachment" => [attachment]}} =
- Object.normalize(activity_response["object"], fetch: false)
-
- assert attachment["type"] == "Document"
- assert attachment["name"] == desc
-
- assert [
- %{
- "href" => ^object_href,
- "type" => "Link",
- "mediaType" => ^object_mediatype
- }
- ] = attachment["url"]
-
- # Fails if unauthenticated
- conn
- |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
- |> json_response(403)
- end
- end
-
test "pinned collection", %{conn: conn} do
clear_config([:instance, :max_pinned_statuses], 2)
user = insert(:user)
diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs
index 5ad6d4716..c8f93f84d 100644
--- a/test/pleroma/web/activity_pub/activity_pub_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_test.exs
@@ -233,6 +233,48 @@ test "works for bridgy actors" do
}
end
+ test "works for takahe actors" do
+ user_id = "https://fedi.vision/@vote@fedi.vision/"
+
+ Tesla.Mock.mock(fn
+ %{method: :get, url: ^user_id} ->
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/users_mock/takahe_user.json"),
+ headers: [{"content-type", "application/activity+json"}]
+ }
+ end)
+
+ {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
+
+ assert user.actor_type == "Person"
+
+ assert [
+ %{
+ "name" => "More details"
+ }
+ ] = user.fields
+ end
+
+ test "works for actors with malformed attachment fields" do
+ user_id = "https://fedi.vision/@vote@fedi.vision/"
+
+ Tesla.Mock.mock(fn
+ %{method: :get, url: ^user_id} ->
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/users_mock/nonsense_attachment_user.json"),
+ headers: [{"content-type", "application/activity+json"}]
+ }
+ end)
+
+ {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
+
+ assert user.actor_type == "Person"
+
+ assert [] = user.fields
+ end
+
test "fetches user featured collection" do
ap_id = "https://example.com/users/lain"
@@ -283,9 +325,7 @@ test "fetches user featured collection" do
body: featured_data,
headers: [{"content-type", "application/activity+json"}]
}
- end)
- Tesla.Mock.mock_global(fn
%{
method: :get,
url: ^object_url
@@ -298,7 +338,8 @@ test "fetches user featured collection" do
end)
{:ok, user} = ActivityPub.make_user_from_ap_id(ap_id)
- Process.sleep(50)
+ # wait for oban
+ Pleroma.Tests.ObanHelpers.perform_all()
assert user.featured_address == featured_url
assert Map.has_key?(user.pinned_objects, object_url)
diff --git a/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs b/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs
index 932251389..45fe183a4 100644
--- a/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs
+++ b/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs
@@ -202,7 +202,7 @@ test "reject unknown size", %{message: message} do
test "reject too large content-size before download", %{message: message} do
clear_config([:mrf_steal_emoji, :download_unknown_size], false)
- mock_tesla("https://example.org/emoji/firedfox.png", 200, [{"content-length", 2 ** 30}])
+ mock_tesla("https://example.org/emoji/firedfox.png", 200, [{"content-length", "#{2 ** 30}"}])
refute "firedfox" in installed()
@@ -216,7 +216,7 @@ test "reject too large content-size before download", %{message: message} do
test "accepts content-size below limit", %{message: message} do
clear_config([:mrf_steal_emoji, :download_unknown_size], false)
- mock_tesla("https://example.org/emoji/firedfox.png", 200, [{"content-length", 2}])
+ mock_tesla("https://example.org/emoji/firedfox.png", 200, [{"content-length", "2"}])
refute "firedfox" in installed()
diff --git a/test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs
index f8dec09d3..22a8d0899 100644
--- a/test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs
+++ b/test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs
@@ -12,14 +12,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidatorTest do
describe "attachments" do
test "works with apng" do
- attachment =
- %{
- "mediaType" => "image/apng",
- "name" => "",
- "type" => "Document",
- "url" =>
- "https://media.misskeyusercontent.com/io/2859c26e-cd43-4550-848b-b6243bc3fe28.apng"
- }
+ attachment = %{
+ "mediaType" => "image/apng",
+ "name" => "",
+ "type" => "Document",
+ "url" =>
+ "https://media.misskeyusercontent.com/io/2859c26e-cd43-4550-848b-b6243bc3fe28.apng"
+ }
assert {:ok, attachment} =
AttachmentValidator.cast_and_validate(attachment)
diff --git a/test/pleroma/web/activity_pub/object_validators/user_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/user_validator_test.exs
new file mode 100644
index 000000000..f96911a81
--- /dev/null
+++ b/test/pleroma/web/activity_pub/object_validators/user_validator_test.exs
@@ -0,0 +1,38 @@
+# Akkoma: Magically expressive social media
+# Copyright © 2024 Akkoma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.UserValidatorTest do
+ use Pleroma.DataCase, async: true
+
+ alias Pleroma.Web.ActivityPub.ObjectValidators.UserValidator
+
+ # all standard actor types are listed here:
+ # https://www.w3.org/TR/activitystreams-vocabulary/#actor-types
+ describe "accepts standard type" do
+ test "Application" do
+ validates_file!("test/fixtures/mastodon/application_actor.json")
+ end
+
+ test "Group" do
+ validates_file!("test/fixtures/peertube/actor-videochannel.json")
+ end
+
+ test "Organization" do
+ validates_file!("test/fixtures/tesla_mock/wedistribute-user.json")
+ end
+
+ test "Person" do
+ validates_file!("test/fixtures/bridgy/actor.json")
+ end
+
+ test "Service" do
+ validates_file!("test/fixtures/mastodon/service_actor.json")
+ end
+ end
+
+ defp validates_file!(path) do
+ user_data = Jason.decode!(File.read!(path))
+ {:ok, _validated_data, _meta} = UserValidator.validate(user_data, [])
+ end
+end
diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs
index e473ae659..dd7977593 100644
--- a/test/pleroma/web/activity_pub/transmogrifier_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs
@@ -124,6 +124,28 @@ test "it fixes both the Create and object contexts in a reply" do
assert activity.data["context"] == object.data["context"]
end
+
+ test "it accepts quote posts" do
+ insert(:user, ap_id: "https://misskey.io/users/7rkrarq81i")
+
+ object = File.read!("test/fixtures/quote_post/misskey_quote_post.json") |> Jason.decode!()
+
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "type" => "Create",
+ "actor" => "https://misskey.io/users/7rkrarq81i",
+ "object" => object
+ }
+
+ assert {:ok, activity} = Transmogrifier.handle_incoming(message)
+
+ # Object was created in the database
+ object = Object.normalize(activity)
+ assert object.data["quoteUri"] == "https://misskey.io/notes/8vs6wxufd0"
+
+ # It fetched the quoted post
+ assert Object.normalize("https://misskey.io/notes/8vs6wxufd0")
+ end
end
describe "prepare outgoing" do
@@ -413,7 +435,7 @@ test "it rejects activities which reference objects with bogus origins" do
assert capture_log(fn ->
{:error, _} = Transmogrifier.handle_incoming(data)
- end) =~ "Object containment failed"
+ end) =~ "Object rejected while fetching"
end
test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
@@ -428,7 +450,7 @@ test "it rejects activities which reference objects that have an incorrect attri
assert capture_log(fn ->
{:error, _} = Transmogrifier.handle_incoming(data)
- end) =~ "Object containment failed"
+ end) =~ "Object rejected while fetching"
end
test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
@@ -443,7 +465,7 @@ test "it rejects activities which reference objects that have an incorrect attri
assert capture_log(fn ->
{:error, _} = Transmogrifier.handle_incoming(data)
- end) =~ "Object containment failed"
+ end) =~ "Object rejected while fetching"
end
end
@@ -536,7 +558,7 @@ test "returns non-modified object" do
test "returns nil when cannot normalize object" do
assert capture_log(fn ->
refute Transmogrifier.get_obj_helper("test-obj-id")
- end) =~ "URI Scheme Invalid"
+ end) =~ ":valid_uri_scheme"
end
@tag capture_log: true
diff --git a/test/pleroma/web/common_api/utils_test.exs b/test/pleroma/web/common_api/utils_test.exs
index f56d21c70..3d639d389 100644
--- a/test/pleroma/web/common_api/utils_test.exs
+++ b/test/pleroma/web/common_api/utils_test.exs
@@ -590,41 +590,22 @@ test "returns recipients when object not found" do
end
end
- describe "attachments_from_ids_descs/2" do
- test "returns [] when attachment ids is empty" do
- assert Utils.attachments_from_ids_descs([], "{}") == []
- end
-
- test "returns list attachments with desc" do
- object = insert(:note)
- desc = Jason.encode!(%{object.id => "test-desc"})
-
- assert Utils.attachments_from_ids_descs(["#{object.id}", "34"], desc) == [
- Map.merge(object.data, %{"name" => "test-desc"})
- ]
- end
- end
-
describe "attachments_from_ids/1" do
- test "returns attachments with descs" do
- object = insert(:note)
- desc = Jason.encode!(%{object.id => "test-desc"})
-
- assert Utils.attachments_from_ids(%{
- media_ids: ["#{object.id}"],
- descriptions: desc
- }) == [
- Map.merge(object.data, %{"name" => "test-desc"})
- ]
+ test "returns attachments without descs" do
+ user = insert(:user)
+ object = insert(:attachment, user: user)
+ assert Utils.attachments_from_ids(user, %{media_ids: ["#{object.id}"]}) == [object.data]
end
- test "returns attachments without descs" do
- object = insert(:note)
- assert Utils.attachments_from_ids(%{media_ids: ["#{object.id}"]}) == [object.data]
+ test "returns [] when passed non-media object ids" do
+ user = insert(:user)
+ object = insert(:note, user: user)
+ assert Utils.attachments_from_ids(user, %{media_ids: ["#{object.id}"]}) == []
end
test "returns [] when not pass media_ids" do
- assert Utils.attachments_from_ids(%{}) == []
+ user = insert(:user)
+ assert Utils.attachments_from_ids(user, %{}) == []
end
end
diff --git a/test/pleroma/web/federator_test.exs b/test/pleroma/web/federator_test.exs
index 76a7a6d37..d3cc239cf 100644
--- a/test/pleroma/web/federator_test.exs
+++ b/test/pleroma/web/federator_test.exs
@@ -137,6 +137,37 @@ test "successfully processes incoming AP docs with correct origin" do
assert {:error, :already_present} = ObanHelpers.perform(job)
end
+ test "successfully normalises public scope descriptors" do
+ params = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "actor" => "http://mastodon.example.org/users/admin",
+ "type" => "Create",
+ "id" => "http://mastodon.example.org/users/admin/activities/1",
+ "object" => %{
+ "type" => "Note",
+ "content" => "hi world!",
+ "id" => "http://mastodon.example.org/users/admin/objects/1",
+ "attributedTo" => "http://mastodon.example.org/users/admin",
+ "to" => ["Public"]
+ },
+ "to" => ["as:Public"]
+ }
+
+ assert {:ok, job} = Federator.incoming_ap_doc(params)
+ assert {:ok, activity} = ObanHelpers.perform(job)
+ assert activity.data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
+
+ object =
+ from(
+ object in Pleroma.Object,
+ where: fragment("(?)->>'id' = ?", object.data, ^activity.data["object"]),
+ limit: 1
+ )
+ |> Repo.one()
+
+ assert object.data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
+ end
+
test "rejects incoming AP docs with incorrect origin" do
params = %{
"@context" => "https://www.w3.org/ns/activitystreams",
diff --git a/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs
index 7b5f5850d..a224f063b 100644
--- a/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.MediaControllerTest do
use Pleroma.Web.ConnCase, async: false
import ExUnit.CaptureLog
+ import Pleroma.Factory
alias Pleroma.Object
alias Pleroma.User
@@ -174,6 +175,18 @@ test "/api/v1/media/:id good request", %{conn: conn, object: object} do
assert media["description"] == "test-media"
assert refresh_record(object).data["name"] == "test-media"
end
+
+ test "won't update non-media", %{conn: conn, user: user} do
+ object = insert(:note, user: user)
+
+ response =
+ conn
+ |> put_req_header("content-type", "multipart/form-data")
+ |> put("/api/v1/media/#{object.id}", %{"description" => "test-media"})
+ |> json_response(404)
+
+ assert response == %{"error" => "Record not found"}
+ end
end
describe "Get media by id (/api/v1/media/:id)" do
@@ -207,6 +220,17 @@ test "it returns media object when requested by owner", %{conn: conn, object: ob
assert media["id"]
end
+ test "it returns 404 when requesting non-media object", %{conn: conn, user: user} do
+ object = insert(:note, user: user)
+
+ response =
+ conn
+ |> get("/api/v1/media/#{object.id}")
+ |> json_response(404)
+
+ assert response == %{"error" => "Record not found"}
+ end
+
test "it returns 403 if media object requested by non-owner", %{object: object, user: user} do
%{conn: conn, user: other_user} = oauth_access(["read:media"])
diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
index 25a18e145..499596a53 100644
--- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
@@ -111,7 +111,7 @@ test "posting a status", %{conn: conn} do
# 2 hours
expires_in = 2 * 60 * 60
- expires_at = DateTime.add(DateTime.utc_now(), expires_in)
+ expires_at1 = DateTime.add(DateTime.utc_now(), expires_in)
conn_four =
conn
@@ -123,12 +123,16 @@ test "posting a status", %{conn: conn} do
assert %{"id" => fourth_id} = json_response_and_validate_schema(conn_four, 200)
- assert Activity.get_by_id(fourth_id)
+ activity = Activity.get_by_id(fourth_id)
+ assert activity
+
+ {:ok, expires_at2, _} = DateTime.from_iso8601(activity.data["expires_at"])
+ assert Timex.compare(expires_at1, expires_at2, :minutes) == 0
assert_enqueued(
worker: Pleroma.Workers.PurgeExpiredActivity,
args: %{activity_id: fourth_id},
- scheduled_at: expires_at
+ scheduled_at: expires_at2
)
end
@@ -148,16 +152,13 @@ test "automatically setting a post expiry if status_ttl_days is set" do
activity = Activity.get_by_id_with_object(id)
{:ok, expires_at, _} = DateTime.from_iso8601(activity.data["expires_at"])
- assert Timex.diff(
- expires_at,
- DateTime.utc_now(),
- :hours
- ) == 23
+ expiry_delay = Timex.diff(expires_at, DateTime.utc_now(), :hours)
+ assert(expiry_delay in [23, 24])
assert_enqueued(
worker: Pleroma.Workers.PurgeExpiredActivity,
args: %{activity_id: id},
- scheduled_at: DateTime.add(DateTime.utc_now(), 1 * 60 * 60 * 24)
+ scheduled_at: expires_at
)
end
@@ -219,6 +220,28 @@ test "posting an undefined status with an attachment", %{user: user, conn: conn}
assert json_response_and_validate_schema(conn, 200)
end
+ test "refuses to post non-owned media", %{conn: conn} do
+ other_user = insert(:user)
+
+ file = %Plug.Upload{
+ content_type: "image/jpeg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ {:ok, upload} = ActivityPub.upload(file, actor: other_user.ap_id)
+
+ conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/statuses", %{
+ "status" => "mew",
+ "media_ids" => [to_string(upload.id)]
+ })
+
+ assert json_response(conn, 422) == %{"error" => "forbidden"}
+ end
+
test "posting a status with an invalid language", %{conn: conn} do
conn =
conn
@@ -341,59 +364,6 @@ test "posting a fake status", %{conn: conn} do
assert real_status == fake_status
end
- test "fake statuses' preview card is not cached", %{conn: conn} do
- clear_config([:rich_media, :enabled], true)
-
- Tesla.Mock.mock_global(fn
- %{
- method: :get,
- url: "https://example.com/twitter-card"
- } ->
- %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/twitter_card.html")}
-
- env ->
- apply(HttpRequestMock, :request, [env])
- end)
-
- conn1 =
- conn
- |> put_req_header("content-type", "application/json")
- |> post("/api/v1/statuses", %{
- "status" => "https://example.com/ogp",
- "preview" => true
- })
-
- conn2 =
- conn
- |> put_req_header("content-type", "application/json")
- |> post("/api/v1/statuses", %{
- "status" => "https://example.com/twitter-card",
- "preview" => true
- })
-
- assert %{"card" => %{"title" => "The Rock"}} = json_response_and_validate_schema(conn1, 200)
-
- assert %{"card" => %{"title" => "Small Island Developing States Photo Submission"}} =
- json_response_and_validate_schema(conn2, 200)
- end
-
- test "posting a status with OGP link preview", %{conn: conn} do
- Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
- clear_config([:rich_media, :enabled], true)
-
- conn =
- conn
- |> put_req_header("content-type", "application/json")
- |> post("/api/v1/statuses", %{
- "status" => "https://example.com/ogp"
- })
-
- assert %{"id" => id, "card" => %{"title" => "The Rock"}} =
- json_response_and_validate_schema(conn, 200)
-
- assert Activity.get_by_id(id)
- end
-
test "posting a direct status", %{conn: conn} do
user2 = insert(:user)
content = "direct cofe @#{user2.nickname}"
@@ -568,6 +538,29 @@ test "creates a scheduled activity with a media attachment", %{user: user, conn:
assert %{"type" => "image"} = media_attachment
end
+ test "refuses to schedule post with non-owned media", %{conn: conn} do
+ other_user = insert(:user)
+
+ file = %Plug.Upload{
+ content_type: "image/jpeg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ {:ok, upload} = ActivityPub.upload(file, actor: other_user.ap_id)
+
+ conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/statuses", %{
+ "status" => "mew",
+ "scheduled_at" => DateTime.add(DateTime.utc_now(), 6, :minute),
+ "media_ids" => [to_string(upload.id)]
+ })
+
+ assert json_response(conn, 403) == %{"error" => "Access denied"}
+ end
+
test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
%{conn: conn} do
scheduled_at =
@@ -1405,7 +1398,7 @@ test "on pin removes deletion job, on unpin reschedule deletion" do
%{conn: conn} = oauth_access(["write:accounts", "write:statuses"])
expires_in = 2 * 60 * 60
- expires_at = DateTime.add(DateTime.utc_now(), expires_in)
+ expires_at1 = DateTime.add(DateTime.utc_now(), expires_in)
assert %{"id" => id} =
conn
@@ -1416,10 +1409,15 @@ test "on pin removes deletion job, on unpin reschedule deletion" do
})
|> json_response_and_validate_schema(200)
+ activity = Activity.get_by_id(id)
+ {:ok, expires_at2, _} = DateTime.from_iso8601(activity.data["expires_at"])
+
+ assert Timex.compare(expires_at1, expires_at2, :minutes) == 0
+
assert_enqueued(
worker: Pleroma.Workers.PurgeExpiredActivity,
args: %{activity_id: id},
- scheduled_at: expires_at
+ scheduled_at: expires_at2
)
assert %{"id" => ^id, "pinned" => true} =
@@ -1431,7 +1429,7 @@ test "on pin removes deletion job, on unpin reschedule deletion" do
refute_enqueued(
worker: Pleroma.Workers.PurgeExpiredActivity,
args: %{activity_id: id},
- scheduled_at: expires_at
+ scheduled_at: expires_at2
)
assert %{"id" => ^id, "pinned" => false} =
@@ -1443,7 +1441,7 @@ test "on pin removes deletion job, on unpin reschedule deletion" do
assert_enqueued(
worker: Pleroma.Workers.PurgeExpiredActivity,
args: %{activity_id: id},
- scheduled_at: expires_at
+ scheduled_at: expires_at2
)
end
end
@@ -1944,7 +1942,7 @@ test "expires_at is nil for another user" do
|> json_response_and_validate_schema(:ok)
{:ok, a_expires_at, 0} = DateTime.from_iso8601(a_expires_at)
- assert DateTime.diff(expires_at, a_expires_at) == 0
+ assert Timex.compare(expires_at, a_expires_at, :minutes) == 0
%{conn: conn} = oauth_access(["read:statuses"])
@@ -2400,6 +2398,25 @@ test "it updates the attachments", %{conn: conn, user: user} do
assert [%{"id" => ^attachment_id}] = response["media_attachments"]
end
+ test "it does not update to non-owned attachments", %{conn: conn, user: user} do
+ other_user = insert(:user)
+ attachment = insert(:attachment, user: other_user)
+ attachment_id = to_string(attachment.id)
+
+ {:ok, activity} = CommonAPI.post(user, %{status: "mew mew #abc", spoiler_text: "#def"})
+
+ conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> put("/api/v1/statuses/#{activity.id}", %{
+ "status" => "mew mew #abc",
+ "spoiler_text" => "#def",
+ "media_ids" => [attachment_id]
+ })
+
+ assert json_response(conn, 400) == %{"error" => "internal_server_error"}
+ end
+
test "it does not update visibility", %{conn: conn, user: user} do
{:ok, activity} =
CommonAPI.post(user, %{
diff --git a/test/pleroma/web/mastodon_api/views/scheduled_activity_view_test.exs b/test/pleroma/web/mastodon_api/views/scheduled_activity_view_test.exs
index e323f3a1f..cc893f94e 100644
--- a/test/pleroma/web/mastodon_api/views/scheduled_activity_view_test.exs
+++ b/test/pleroma/web/mastodon_api/views/scheduled_activity_view_test.exs
@@ -47,8 +47,7 @@ test "A scheduled activity with a media attachment" do
expected = %{
id: to_string(scheduled_activity.id),
media_attachments:
- %{media_ids: [upload.id]}
- |> Utils.attachments_from_ids()
+ Utils.attachments_from_ids(user, %{media_ids: [upload.id]})
|> Enum.map(&StatusView.render("attachment.json", %{attachment: &1})),
params: %{
in_reply_to_id: to_string(activity.id),
diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs
index 22f65a0d1..7db3e3e61 100644
--- a/test/pleroma/web/mastodon_api/views/status_view_test.exs
+++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs
@@ -16,10 +16,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.StatusView
+ alias Pleroma.Web.RichMedia.Card
+ alias Pleroma.UnstubbedConfigMock, as: ConfigMock
import Pleroma.Factory
import Tesla.Mock
import OpenApiSpex.TestAssertions
+ import Mox
setup do
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
@@ -677,56 +680,88 @@ test "it returns a a dictionary tags" do
describe "rich media cards" do
test "a rich media card without a site name renders correctly" do
- page_url = "http://example.com"
+ page_url = "https://example.com"
- card = %{
- url: page_url,
- image: page_url <> "/example.jpg",
- title: "Example website"
- }
+ {:ok, card} =
+ Card.create(page_url, %{image: page_url <> "/example.jpg", title: "Example website"})
- %{provider_name: "example.com"} =
- StatusView.render("card.json", %{page_url: page_url, rich_media: card})
+ %{provider_name: "example.com"} = StatusView.render("card.json", card)
end
test "a rich media card without a site name or image renders correctly" do
- page_url = "http://example.com"
+ page_url = "https://example.com"
- card = %{
- url: page_url,
- title: "Example website"
+ fields = %{
+ "url" => page_url,
+ "title" => "Example website"
}
- %{provider_name: "example.com"} =
- StatusView.render("card.json", %{page_url: page_url, rich_media: card})
+ {:ok, card} = Card.create(page_url, fields)
+
+ %{provider_name: "example.com"} = StatusView.render("card.json", card)
end
test "a rich media card without an image renders correctly" do
- page_url = "http://example.com"
+ page_url = "https://example.com"
- card = %{
- url: page_url,
- site_name: "Example site name",
- title: "Example website"
+ fields = %{
+ "url" => page_url,
+ "site_name" => "Example site name",
+ "title" => "Example website"
}
- %{provider_name: "example.com"} =
- StatusView.render("card.json", %{page_url: page_url, rich_media: card})
+ {:ok, card} = Card.create(page_url, fields)
+
+ %{provider_name: "example.com"} = StatusView.render("card.json", card)
end
test "a rich media card with all relevant data renders correctly" do
- page_url = "http://example.com"
+ page_url = "https://example.com"
- card = %{
- url: page_url,
- site_name: "Example site name",
- title: "Example website",
- image: page_url <> "/example.jpg",
- description: "Example description"
+ fields = %{
+ "url" => page_url,
+ "site_name" => "Example site name",
+ "title" => "Example website",
+ "image" => page_url <> "/example.jpg",
+ "description" => "Example description"
}
- %{provider_name: "example.com"} =
- StatusView.render("card.json", %{page_url: page_url, rich_media: card})
+ {:ok, card} = Card.create(page_url, fields)
+
+ %{provider_name: "example.com"} = StatusView.render("card.json", card)
+ end
+
+ test "a rich media card has all media proxied" do
+ clear_config([:media_proxy, :enabled], true)
+ clear_config([:media_preview_proxy, :enabled])
+
+ ConfigMock
+ |> stub_with(Pleroma.Test.StaticConfig)
+
+ page_url = "https://example.com"
+
+ fields = %{
+ "url" => page_url,
+ "site_name" => "Example site name",
+ "title" => "Example website",
+ "image" => page_url <> "/example.jpg",
+ "audio" => page_url <> "/example.ogg",
+ "video" => page_url <> "/example.mp4",
+ "description" => "Example description"
+ }
+
+ {:ok, card} = Card.create(page_url, fields)
+
+ %{
+ provider_name: "example.com",
+ image: image,
+ pleroma: %{opengraph: og}
+ } = StatusView.render("card.json", card)
+
+ assert String.match?(image, ~r/\/proxy\//)
+ assert String.match?(og["image"], ~r/\/proxy\//)
+ assert String.match?(og["audio"], ~r/\/proxy\//)
+ assert String.match?(og["video"], ~r/\/proxy\//)
end
end
diff --git a/test/pleroma/web/rich_media/card_test.exs b/test/pleroma/web/rich_media/card_test.exs
new file mode 100644
index 000000000..570e4034d
--- /dev/null
+++ b/test/pleroma/web/rich_media/card_test.exs
@@ -0,0 +1,78 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2024 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.RichMedia.CardTest do
+ use Pleroma.DataCase, async: true
+
+ alias Pleroma.UnstubbedConfigMock, as: ConfigMock
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.RichMedia.Card
+
+ import Mox
+ import Pleroma.Factory
+ import Tesla.Mock
+
+ setup do
+ mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+
+ ConfigMock
+ |> stub_with(Pleroma.Test.StaticConfig)
+
+ :ok
+ end
+
+ setup do: clear_config([:rich_media, :enabled], true)
+
+ test "crawls URL in activity" do
+ user = insert(:user)
+
+ url = "https://example.com/ogp"
+ url_hash = Card.url_to_hash(url)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ status: "[test](#{url})",
+ content_type: "text/markdown"
+ })
+
+ # wait for oban
+ Pleroma.Tests.ObanHelpers.perform_all()
+
+ assert %Card{url_hash: ^url_hash, fields: _} = Card.get_by_activity(activity)
+ end
+
+ test "recrawls URLs on status edits/updates" do
+ original_url = "https://google.com/"
+ original_url_hash = Card.url_to_hash(original_url)
+ updated_url = "https://yahoo.com/"
+ updated_url_hash = Card.url_to_hash(updated_url)
+
+ user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{status: "I like this site #{original_url}"})
+
+ # Force a backfill
+ Card.get_by_activity(activity)
+ # wait for oban
+ Pleroma.Tests.ObanHelpers.perform_all()
+
+ assert match?(
+ %Card{url_hash: ^original_url_hash, fields: _},
+ Card.get_by_activity(activity)
+ )
+
+ {:ok, _} = CommonAPI.update(user, activity, %{status: "I like this site #{updated_url}"})
+
+ activity = Pleroma.Activity.get_by_id(activity.id)
+
+ # Force a backfill
+ Card.get_by_activity(activity)
+ # wait for oban
+ Pleroma.Tests.ObanHelpers.perform_all()
+
+ assert match?(
+ %Card{url_hash: ^updated_url_hash, fields: _},
+ Card.get_by_activity(activity)
+ )
+ end
+end
diff --git a/test/pleroma/web/rich_media/helpers_test.exs b/test/pleroma/web/rich_media/helpers_test.exs
deleted file mode 100644
index c6c3ffd6c..000000000
--- a/test/pleroma/web/rich_media/helpers_test.exs
+++ /dev/null
@@ -1,84 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2021 Pleroma Authors
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.RichMedia.HelpersTest do
- use Pleroma.DataCase
-
- alias Pleroma.Web.CommonAPI
- alias Pleroma.Web.RichMedia.Helpers
-
- import Pleroma.Factory
- import Tesla.Mock
-
- setup do
- mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
-
- :ok
- end
-
- setup do: clear_config([:rich_media, :enabled])
-
- test "refuses to crawl incomplete URLs" do
- user = insert(:user)
-
- {:ok, activity} =
- CommonAPI.post(user, %{
- status: "[test](example.com/ogp)",
- content_type: "text/markdown"
- })
-
- clear_config([:rich_media, :enabled], true)
-
- assert %{} == Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
- end
-
- test "refuses to crawl malformed URLs" do
- user = insert(:user)
-
- {:ok, activity} =
- CommonAPI.post(user, %{
- status: "[test](example.com[]/ogp)",
- content_type: "text/markdown"
- })
-
- clear_config([:rich_media, :enabled], true)
-
- assert %{} == Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
- end
-
- test "crawls valid, complete URLs" do
- user = insert(:user)
-
- {:ok, activity} =
- CommonAPI.post(user, %{
- status: "[test](https://example.com/ogp)",
- content_type: "text/markdown"
- })
-
- clear_config([:rich_media, :enabled], true)
-
- assert %{page_url: "https://example.com/ogp", rich_media: _} =
- Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
- end
-
- test "refuses to crawl URLs of private network from posts" do
- user = insert(:user)
-
- {:ok, activity} =
- CommonAPI.post(user, %{status: "http://127.0.0.1:4000/notice/9kCP7VNyPJXFOXDrgO"})
-
- {:ok, activity2} = CommonAPI.post(user, %{status: "https://10.111.10.1/notice/9kCP7V"})
- {:ok, activity3} = CommonAPI.post(user, %{status: "https://172.16.32.40/notice/9kCP7V"})
- {:ok, activity4} = CommonAPI.post(user, %{status: "https://192.168.10.40/notice/9kCP7V"})
- {:ok, activity5} = CommonAPI.post(user, %{status: "https://pleroma.local/notice/9kCP7V"})
-
- clear_config([:rich_media, :enabled], true)
-
- assert %{} = Helpers.fetch_data_for_activity(activity)
- assert %{} = Helpers.fetch_data_for_activity(activity2)
- assert %{} = Helpers.fetch_data_for_activity(activity3)
- assert %{} = Helpers.fetch_data_for_activity(activity4)
- assert %{} = Helpers.fetch_data_for_activity(activity5)
- end
-end
diff --git a/test/pleroma/web/rich_media/parser/ttl/aws_signed_url_test.exs b/test/pleroma/web/rich_media/parser/ttl/aws_signed_url_test.exs
index 0c8203417..136e43775 100644
--- a/test/pleroma/web/rich_media/parser/ttl/aws_signed_url_test.exs
+++ b/test/pleroma/web/rich_media/parser/ttl/aws_signed_url_test.exs
@@ -1,10 +1,25 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2021 Pleroma Authors
+# Copyright © 2017-2022 Pleroma Authors
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrlTest do
- # Relies on Cachex, needs to be synchronous
- use Pleroma.DataCase
+ use Pleroma.DataCase, async: false
+ use Oban.Testing, repo: Pleroma.Repo
+
+ import Mox
+
+ alias Pleroma.UnstubbedConfigMock, as: ConfigMock
+ alias Pleroma.Web.RichMedia.Card
+ alias Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl
+
+ setup do
+ ConfigMock
+ |> stub_with(Pleroma.Test.StaticConfig)
+
+ clear_config([:rich_media, :enabled], true)
+
+ :ok
+ end
test "s3 signed url is parsed correct for expiration time" do
url = "https://pleroma.social/amz"
@@ -22,7 +37,7 @@ test "s3 signed url is parsed correct for expiration time" do
expire_time =
Timex.parse!(timestamp, "{ISO:Basic:Z}") |> Timex.to_unix() |> Kernel.+(valid_till)
- assert {:ok, expire_time} == Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl.ttl(metadata, url)
+ assert expire_time == Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl.ttl(metadata, url)
end
test "s3 signed url is parsed and correct ttl is set for rich media" do
@@ -43,26 +58,37 @@ test "s3 signed url is parsed and correct ttl is set for rich media" do
-
+
"""
Tesla.Mock.mock(fn
%{
method: :get,
- url: "https://pleroma.social/amz"
+ url: ^url
} ->
%Tesla.Env{status: 200, body: body}
+
+ %{method: :head} ->
+ %Tesla.Env{status: 200}
end)
- Cachex.put(:rich_media_cache, url, metadata)
+ Card.get_or_backfill_by_url(url)
+ # wait for oban
+ Pleroma.Tests.ObanHelpers.perform_all()
- Pleroma.Web.RichMedia.Parser.set_ttl_based_on_image(metadata, url)
+ assert_enqueued(worker: Pleroma.Workers.RichMediaExpirationWorker, args: %{"url" => url})
- {:ok, cache_ttl} = Cachex.ttl(:rich_media_cache, url)
+ [%Oban.Job{scheduled_at: scheduled_at}] = all_enqueued()
- # as there is delay in setting and pulling the data from cache we ignore 1 second
- # make it 2 seconds for flakyness
- assert_in_delta(valid_till * 1000, cache_ttl, 2000)
+ timestamp_dt = Timex.parse!(timestamp, "{ISO:Basic:Z}")
+
+ assert DateTime.diff(scheduled_at, timestamp_dt) == valid_till
+ end
+
+ test "AWS URL for an image without expiration works" do
+ og_data = %{"image" => "https://amazonaws.com/image.png"}
+
+ assert is_nil(AwsSignedUrl.ttl(og_data, ""))
end
defp construct_s3_url(timestamp, valid_till) do
@@ -71,11 +97,11 @@ defp construct_s3_url(timestamp, valid_till) do
defp construct_metadata(timestamp, valid_till, url) do
%{
- image: construct_s3_url(timestamp, valid_till),
- site: "Pleroma",
- title: "Pleroma",
- description: "Pleroma",
- url: url
+ "image" => construct_s3_url(timestamp, valid_till),
+ "site" => "Pleroma",
+ "title" => "Pleroma",
+ "description" => "Pleroma",
+ "url" => url
}
end
end
diff --git a/test/pleroma/web/rich_media/parser/ttl/opengraph_test.exs b/test/pleroma/web/rich_media/parser/ttl/opengraph_test.exs
new file mode 100644
index 000000000..3360e8357
--- /dev/null
+++ b/test/pleroma/web/rich_media/parser/ttl/opengraph_test.exs
@@ -0,0 +1,43 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2024 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.RichMedia.Parser.TTL.OpengraphTest do
+ use Pleroma.DataCase
+ use Oban.Testing, repo: Pleroma.Repo
+
+ import Mox
+
+ alias Pleroma.UnstubbedConfigMock, as: ConfigMock
+ alias Pleroma.Web.RichMedia.Card
+
+ setup do
+ ConfigMock
+ |> stub_with(Pleroma.Test.StaticConfig)
+
+ clear_config([:rich_media, :enabled], true)
+
+ :ok
+ end
+
+ test "OpenGraph TTL value is honored" do
+ url = "https://reddit.com/r/somepost"
+
+ Tesla.Mock.mock(fn
+ %{
+ method: :get,
+ url: ^url
+ } ->
+ %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/reddit.html")}
+
+ %{method: :head} ->
+ %Tesla.Env{status: 200}
+ end)
+
+ Card.get_or_backfill_by_url(url)
+ # wait for oban
+ Pleroma.Tests.ObanHelpers.perform_all()
+
+ assert_enqueued(worker: Pleroma.Workers.RichMediaExpirationWorker, args: %{"url" => url})
+ end
+end
diff --git a/test/pleroma/web/rich_media/parser_test.exs b/test/pleroma/web/rich_media/parser_test.exs
index 426cc72e6..a5f2563a2 100644
--- a/test/pleroma/web/rich_media/parser_test.exs
+++ b/test/pleroma/web/rich_media/parser_test.exs
@@ -1,97 +1,30 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2021 Pleroma Authors
+# Copyright © 2017-2022 Pleroma Authors
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.ParserTest do
- use ExUnit.Case
+ use Pleroma.DataCase
alias Pleroma.Web.RichMedia.Parser
+ import Tesla.Mock
+
setup do
- Tesla.Mock.mock_global(fn
- %{
- method: :get,
- url: "http://example.com/ogp"
- } ->
- %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/ogp.html")}
-
- %{
- method: :get,
- url: "http://example.com/non-ogp"
- } ->
- %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/non_ogp_embed.html")}
-
- %{
- method: :get,
- url: "http://example.com/ogp-missing-title"
- } ->
- %Tesla.Env{
- status: 200,
- body: File.read!("test/fixtures/rich_media/ogp-missing-title.html")
- }
-
- %{
- method: :get,
- url: "http://example.com/twitter-card"
- } ->
- %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/twitter_card.html")}
-
- %{
- method: :get,
- url: "http://example.com/oembed"
- } ->
- %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/oembed.html")}
-
- %{
- method: :get,
- url: "http://example.com/oembed.json"
- } ->
- %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/oembed.json")}
-
- %{method: :get, url: "http://example.com/empty"} ->
- %Tesla.Env{status: 200, body: "hello"}
-
- %{method: :get, url: "http://example.com/malformed"} ->
- %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/malformed-data.html")}
-
- %{method: :get, url: "http://example.com/error"} ->
- {:error, :overload}
-
- %{
- method: :head,
- url: "http://example.com/huge-page"
- } ->
- %Tesla.Env{
- status: 200,
- headers: [{"content-length", "2000001"}, {"content-type", "text/html"}]
- }
-
- %{
- method: :head,
- url: "http://example.com/pdf-file"
- } ->
- %Tesla.Env{
- status: 200,
- headers: [{"content-length", "1000000"}, {"content-type", "application/pdf"}]
- }
-
- %{method: :head} ->
- %Tesla.Env{status: 404, body: "", headers: []}
- end)
-
- :ok
+ mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
end
+ setup_all do: clear_config([:rich_media, :enabled], true)
+
test "returns error when no metadata present" do
- assert {:error, _} = Parser.parse("http://example.com/empty")
+ assert {:error, _} = Parser.parse("https://example.com/empty")
end
test "doesn't just add a title" do
- assert {:error, {:invalid_metadata, _}} = Parser.parse("http://example.com/non-ogp")
+ assert {:error, {:invalid_metadata, _}} = Parser.parse("https://example.com/non-ogp")
end
test "parses ogp" do
- assert Parser.parse("http://example.com/ogp") ==
+ assert Parser.parse("https://example.com/ogp") ==
{:ok,
%{
"image" => "http://ia.media-imdb.com/images/rock.jpg",
@@ -99,12 +32,12 @@ test "parses ogp" do
"description" =>
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
"type" => "video.movie",
- "url" => "http://example.com/ogp"
+ "url" => "https://example.com/ogp"
}}
end
test "falls back to when ogp:title is missing" do
- assert Parser.parse("http://example.com/ogp-missing-title") ==
+ assert Parser.parse("https://example.com/ogp-missing-title") ==
{:ok,
%{
"image" => "http://ia.media-imdb.com/images/rock.jpg",
@@ -112,12 +45,12 @@ test "falls back to when ogp:title is missing" do
"description" =>
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
"type" => "video.movie",
- "url" => "http://example.com/ogp-missing-title"
+ "url" => "https://example.com/ogp-missing-title"
}}
end
test "parses twitter card" do
- assert Parser.parse("http://example.com/twitter-card") ==
+ assert Parser.parse("https://example.com/twitter-card") ==
{:ok,
%{
"card" => "summary",
@@ -125,12 +58,12 @@ test "parses twitter card" do
"image" => "https://farm6.staticflickr.com/5510/14338202952_93595258ff_z.jpg",
"title" => "Small Island Developing States Photo Submission",
"description" => "View the album on Flickr.",
- "url" => "http://example.com/twitter-card"
+ "url" => "https://example.com/twitter-card"
}}
end
test "parses OEmbed and filters HTML tags" do
- assert Parser.parse("http://example.com/oembed") ==
+ assert Parser.parse("https://example.com/oembed") ==
{:ok,
%{
"author_name" => "\u202E\u202D\u202Cbees\u202C",
@@ -150,7 +83,7 @@ test "parses OEmbed and filters HTML tags" do
"thumbnail_width" => 150,
"title" => "Bacon Lollys",
"type" => "photo",
- "url" => "http://example.com/oembed",
+ "url" => "https://example.com/oembed",
"version" => "1.0",
"web_page" => "https://www.flickr.com/photos/bees/2362225867/",
"web_page_short_url" => "https://flic.kr/p/4AK2sc",
@@ -159,18 +92,47 @@ test "parses OEmbed and filters HTML tags" do
end
test "rejects invalid OGP data" do
- assert {:error, _} = Parser.parse("http://example.com/malformed")
+ assert {:error, _} = Parser.parse("https://example.com/malformed")
end
test "returns error if getting page was not successful" do
- assert {:error, :overload} = Parser.parse("http://example.com/error")
+ assert {:error, :overload} = Parser.parse("https://example.com/error")
end
test "does a HEAD request to check if the body is too large" do
- assert {:error, :body_too_large} = Parser.parse("http://example.com/huge-page")
+ assert {:error, :body_too_large} = Parser.parse("https://example.com/huge-page")
end
test "does a HEAD request to check if the body is html" do
- assert {:error, {:content_type, _}} = Parser.parse("http://example.com/pdf-file")
+ assert {:error, {:content_type, _}} = Parser.parse("https://example.com/pdf-file")
+ end
+
+ test "refuses to crawl incomplete URLs" do
+ url = "example.com/ogp"
+ assert :error == Parser.parse(url)
+ end
+
+ test "refuses to crawl malformed URLs" do
+ url = "example.com[]/ogp"
+ assert :error == Parser.parse(url)
+ end
+
+ test "refuses to crawl URLs of private network from posts" do
+ [
+ "http://127.0.0.1:4000/notice/9kCP7VNyPJXFOXDrgO",
+ "https://10.111.10.1/notice/9kCP7V",
+ "https://172.16.32.40/notice/9kCP7V",
+ "https://192.168.10.40/notice/9kCP7V",
+ "https://pleroma.local/notice/9kCP7V"
+ ]
+ |> Enum.each(fn url ->
+ assert :error == Parser.parse(url)
+ end)
+ end
+
+ test "returns error when disabled" do
+ clear_config([:rich_media, :enabled], false)
+
+ assert match?({:error, :rich_media_disabled}, Parser.parse("https://example.com/ogp"))
end
end
diff --git a/test/pleroma/web/twitter_api/remote_follow_controller_test.exs b/test/pleroma/web/twitter_api/remote_follow_controller_test.exs
index d2bc7840f..5a94e4396 100644
--- a/test/pleroma/web/twitter_api/remote_follow_controller_test.exs
+++ b/test/pleroma/web/twitter_api/remote_follow_controller_test.exs
@@ -132,7 +132,7 @@ test "show follow page with error when user can not be fetched by `acct` link",
|> html_response(200)
assert response =~ "Error fetching user"
- end) =~ "Object has been deleted"
+ end) =~ ":not_found"
end
end
diff --git a/test/pleroma/web/web_finger/web_finger_controller_test.exs b/test/pleroma/web/web_finger/web_finger_controller_test.exs
index f792f20e6..afb4ce92d 100644
--- a/test/pleroma/web/web_finger/web_finger_controller_test.exs
+++ b/test/pleroma/web/web_finger/web_finger_controller_test.exs
@@ -50,12 +50,7 @@ test "Webfinger JRD" do
]
end
- test "reach user on tld, while pleroma is runned on subdomain" do
- Pleroma.Web.Endpoint.config_change(
- [{Pleroma.Web.Endpoint, url: [host: "sub.example.com"]}],
- []
- )
-
+ test "reach user on tld, while pleroma is running on subdomain" do
clear_config([Pleroma.Web.Endpoint, :url, :host], "sub.example.com")
clear_config([Pleroma.Web.WebFinger, :domain], "example.com")
diff --git a/test/pleroma/web/web_finger_test.exs b/test/pleroma/web/web_finger_test.exs
index b47eabe80..2af084090 100644
--- a/test/pleroma/web/web_finger_test.exs
+++ b/test/pleroma/web/web_finger_test.exs
@@ -9,7 +9,7 @@ defmodule Pleroma.Web.WebFingerTest do
import Tesla.Mock
setup do
- mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
:ok
end
@@ -76,15 +76,6 @@ test "returns the ActivityPub actor URI for an ActivityPub user" do
{:ok, _data} = WebFinger.finger(user)
end
- test "returns the ActivityPub actor URI and subscribe address for an ActivityPub user with the ld+json mimetype" do
- user = "kaniini@gerzilla.de"
-
- {:ok, data} = WebFinger.finger(user)
-
- assert data["ap_id"] == "https://gerzilla.de/channel/kaniini"
- assert data["subscribe_address"] == "https://gerzilla.de/follow?f=&url={uri}"
- end
-
test "it work for AP-only user" do
user = "kpherox@mstdn.jp"
@@ -99,12 +90,6 @@ test "it work for AP-only user" do
assert data["subscribe_address"] == "https://mstdn.jp/authorize_interaction?acct={uri}"
end
- test "it works for friendica" do
- user = "lain@squeet.me"
-
- {:ok, _data} = WebFinger.finger(user)
- end
-
test "it gets the xrd endpoint" do
{:ok, template} = WebFinger.find_lrdd_template("social.heldscal.la")
@@ -180,5 +165,44 @@ test "respects xml content-type" do
{:ok, _data} = WebFinger.finger("pekorino@pawoo.net")
end
+
+ test "prevents spoofing" do
+ Tesla.Mock.mock(fn
+ %{
+ url: "https://bad.com/.well-known/webfinger?resource=acct:meanie@bad.com"
+ } ->
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/webfinger_spoof.json"),
+ headers: [{"content-type", "application/jrd+json"}]
+ }}
+
+ %{url: "https://bad.com/.well-known/host-meta"} ->
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/bad.com_host_meta")
+ }}
+ end)
+
+ {:error, _data} = WebFinger.finger("meanie@bad.com")
+ end
+ end
+
+ @tag capture_log: true
+ test "prevents forgeries" do
+ Tesla.Mock.mock(fn
+ %{url: "https://bad.com/.well-known/webfinger?resource=acct:meanie@bad.com"} ->
+ fake_webfinger =
+ File.read!("test/fixtures/webfinger/imposter-webfinger.json") |> Jason.decode!()
+
+ Tesla.Mock.json(fake_webfinger)
+
+ %{url: "https://bad.com/.well-known/host-meta"} ->
+ {:ok, %Tesla.Env{status: 404}}
+ end)
+
+ assert {:error, {:webfinger_invalid, _, _}} = WebFinger.finger("meanie@bad.com")
end
end
diff --git a/test/pleroma/workers/remote_fetcher_worker_test.exs b/test/pleroma/workers/remote_fetcher_worker_test.exs
new file mode 100644
index 000000000..c30e773d4
--- /dev/null
+++ b/test/pleroma/workers/remote_fetcher_worker_test.exs
@@ -0,0 +1,69 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2023 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Workers.RemoteFetcherWorkerTest do
+ use Pleroma.DataCase
+ use Oban.Testing, repo: Pleroma.Repo
+
+ alias Pleroma.Workers.RemoteFetcherWorker
+
+ @deleted_object_one "https://deleted-404.example.com/"
+ @deleted_object_two "https://deleted-410.example.com/"
+ @unauthorized_object "https://unauthorized.example.com/"
+ @depth_object "https://depth.example.com/"
+
+ describe "RemoteFetcherWorker" do
+ setup do
+ Tesla.Mock.mock(fn
+ %{method: :get, url: @deleted_object_one} ->
+ %Tesla.Env{
+ status: 404
+ }
+
+ %{method: :get, url: @deleted_object_two} ->
+ %Tesla.Env{
+ status: 410
+ }
+
+ %{method: :get, url: @unauthorized_object} ->
+ %Tesla.Env{
+ status: 403
+ }
+
+ %{method: :get, url: @depth_object} ->
+ %Tesla.Env{
+ status: 200
+ }
+ end)
+ end
+
+ test "does not requeue a deleted object" do
+ assert {:discard, _} =
+ RemoteFetcherWorker.perform(%Oban.Job{
+ args: %{"op" => "fetch_remote", "id" => @deleted_object_one}
+ })
+
+ assert {:discard, _} =
+ RemoteFetcherWorker.perform(%Oban.Job{
+ args: %{"op" => "fetch_remote", "id" => @deleted_object_two}
+ })
+ end
+
+ test "does not requeue an unauthorized object" do
+ assert {:discard, _} =
+ RemoteFetcherWorker.perform(%Oban.Job{
+ args: %{"op" => "fetch_remote", "id" => @unauthorized_object}
+ })
+ end
+
+ test "does not requeue an object that exceeded depth" do
+ clear_config([:instance, :federation_incoming_replies_max_depth], 0)
+
+ assert {:discard, _} =
+ RemoteFetcherWorker.perform(%Oban.Job{
+ args: %{"op" => "fetch_remote", "id" => @depth_object, "depth" => 1}
+ })
+ end
+ end
+end
diff --git a/test/support/factory.ex b/test/support/factory.ex
index e21b8fc1e..2a73a4ae6 100644
--- a/test/support/factory.ex
+++ b/test/support/factory.ex
@@ -559,16 +559,15 @@ def undo_activity_factory(attrs \\ %{}) do
like_activity = attrs[:like_activity] || insert(:like_activity)
attrs = Map.drop(attrs, [:like_activity])
- data =
- %{
- "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
- "type" => "Undo",
- "actor" => like_activity.data["actor"],
- "to" => like_activity.data["to"],
- "object" => like_activity.data["id"],
- "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
- "context" => like_activity.data["context"]
- }
+ data = %{
+ "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
+ "type" => "Undo",
+ "actor" => like_activity.data["actor"],
+ "to" => like_activity.data["to"],
+ "object" => like_activity.data["id"],
+ "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
+ "context" => like_activity.data["context"]
+ }
%Pleroma.Activity{
data: data,
diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex
index e487d2e6b..6a01393e3 100644
--- a/test/support/http_request_mock.ex
+++ b/test/support/http_request_mock.ex
@@ -1321,6 +1321,25 @@ def get("https://skippers-bin.com/notes/7x9tmrp97i", _, _, _) do
}}
end
+ # A misskey quote
+ def get("https://misskey.io/notes/8vs6wxufd0", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/misskey.io_8vs6wxufd0.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://misskey.io/users/83ssedkv53", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/aimu@misskey.io.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
def get("https://example.org/emoji/firedfox.png", _, _, _) do
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.jpg")}}
end
@@ -1445,6 +1464,177 @@ def get("https://p.helene.moe/objects/fd5910ac-d9dc-412e-8d1d-914b203296c4", _,
}}
end
+ def get("https://google.com/", _, _, _) do
+ {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/google.html")}}
+ end
+
+ def get("https://yahoo.com/", _, _, _) do
+ {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/yahoo.html")}}
+ end
+
+ def get("https://example.com/error", _, _, _), do: {:error, :overload}
+
+ def get("https://example.com/ogp-missing-title", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/rich_media/ogp-missing-title.html")
+ }}
+ end
+
+ def get("https://example.com/oembed", _, _, _) do
+ {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/oembed.html")}}
+ end
+
+ def get("https://example.com/oembed.json", _, _, _) do
+ {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/oembed.json")}}
+ end
+
+ def get("https://example.com/twitter-card", _, _, _) do
+ {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/twitter_card.html")}}
+ end
+
+ def get("https://example.com/non-ogp", _, _, _) do
+ {:ok,
+ %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/non_ogp_embed.html")}}
+ end
+
+ def get("https://example.com/empty", _, _, _) do
+ {:ok, %Tesla.Env{status: 200, body: "hello"}}
+ end
+
+ def get("https://friends.grishka.me/posts/54642", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/smithereen_non_anonymous_poll.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://friends.grishka.me/users/1", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/smithereen_user.json"),
+ headers: activitypub_object_headers()
+ }}
+ end
+
+ def get("https://mastodon.example/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 302,
+ headers: [{"location", "https://sub.mastodon.example/.well-known/host-meta"}]
+ }}
+ end
+
+ def get("https://sub.mastodon.example/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body:
+ "test/fixtures/webfinger/masto-host-meta.xml"
+ |> File.read!()
+ |> String.replace("{{domain}}", "sub.mastodon.example")
+ }}
+ end
+
+ def get(
+ "https://sub.mastodon.example/.well-known/webfinger?resource=acct:a@mastodon.example",
+ _,
+ _,
+ _
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body:
+ "test/fixtures/webfinger/masto-webfinger.json"
+ |> File.read!()
+ |> String.replace("{{nickname}}", "a")
+ |> String.replace("{{domain}}", "mastodon.example")
+ |> String.replace("{{subdomain}}", "sub.mastodon.example"),
+ headers: [{"content-type", "application/jrd+json"}]
+ }}
+ end
+
+ def get("https://sub.mastodon.example/users/a", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body:
+ "test/fixtures/webfinger/masto-user.json"
+ |> File.read!()
+ |> String.replace("{{nickname}}", "a")
+ |> String.replace("{{domain}}", "sub.mastodon.example"),
+ headers: [{"content-type", "application/activity+json"}]
+ }}
+ end
+
+ def get("https://sub.mastodon.example/users/a/collections/featured", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body:
+ File.read!("test/fixtures/users_mock/masto_featured.json")
+ |> String.replace("{{domain}}", "sub.mastodon.example")
+ |> String.replace("{{nickname}}", "a"),
+ headers: [{"content-type", "application/activity+json"}]
+ }}
+ end
+
+ def get("https://pleroma.example/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 302,
+ headers: [{"location", "https://sub.pleroma.example/.well-known/host-meta"}]
+ }}
+ end
+
+ def get("https://sub.pleroma.example/.well-known/host-meta", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body:
+ "test/fixtures/webfinger/pleroma-host-meta.xml"
+ |> File.read!()
+ |> String.replace("{{domain}}", "sub.pleroma.example")
+ }}
+ end
+
+ def get(
+ "https://sub.pleroma.example/.well-known/webfinger?resource=acct:a@pleroma.example",
+ _,
+ _,
+ _
+ ) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body:
+ "test/fixtures/webfinger/pleroma-webfinger.json"
+ |> File.read!()
+ |> String.replace("{{nickname}}", "a")
+ |> String.replace("{{domain}}", "pleroma.example")
+ |> String.replace("{{subdomain}}", "sub.pleroma.example"),
+ headers: [{"content-type", "application/jrd+json"}]
+ }}
+ end
+
+ def get("https://sub.pleroma.example/users/a", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body:
+ "test/fixtures/webfinger/pleroma-user.json"
+ |> File.read!()
+ |> String.replace("{{nickname}}", "a")
+ |> String.replace("{{domain}}", "sub.pleroma.example"),
+ headers: [{"content-type", "application/activity+json"}]
+ }}
+ end
+
def get(url, query, body, headers) do
{:error,
"Mock response not implemented for GET #{inspect(url)}, #{query}, #{inspect(body)}, #{inspect(headers)}"}
@@ -1518,14 +1708,41 @@ def post(url, query, body, headers) do
# Most of the rich media mocks are missing HEAD requests, so we just return 404.
@rich_media_mocks [
+ "https://example.com/empty",
+ "https://example.com/error",
+ "https://example.com/malformed",
+ "https://example.com/non-ogp",
+ "https://example.com/oembed",
+ "https://example.com/oembed.json",
"https://example.com/ogp",
"https://example.com/ogp-missing-data",
- "https://example.com/twitter-card"
+ "https://example.com/ogp-missing-title",
+ "https://example.com/twitter-card",
+ "https://google.com/",
+ "https://pleroma.local/notice/9kCP7V",
+ "https://yahoo.com/"
]
+
def head(url, _query, _body, _headers) when url in @rich_media_mocks do
{:ok, %Tesla.Env{status: 404, body: ""}}
end
+ def head("https://example.com/pdf-file", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ headers: [{"content-length", "1000000"}, {"content-type", "application/pdf"}]
+ }}
+ end
+
+ def head("https://example.com/huge-page", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ headers: [{"content-length", "2000001"}, {"content-type", "text/html"}]
+ }}
+ end
+
def head(url, query, body, headers) do
{:error,
"Mock response not implemented for HEAD #{inspect(url)}, #{query}, #{inspect(body)}, #{inspect(headers)}"}
diff --git a/test/support/mocks.ex b/test/support/mocks.ex
index fd8f825b3..cc34b21a5 100644
--- a/test/support/mocks.ex
+++ b/test/support/mocks.ex
@@ -26,5 +26,6 @@
Mox.defmock(Pleroma.Web.FederatorMock, for: Pleroma.Web.Federator.Publishing)
Mox.defmock(Pleroma.ConfigMock, for: Pleroma.Config.Getting)
+Mox.defmock(Pleroma.UnstubbedConfigMock, for: Pleroma.Config.Getting)
Mox.defmock(Pleroma.LoggerMock, for: Pleroma.Logging)
diff --git a/test/test_helper.exs b/test/test_helper.exs
index 22a0f33ee..dafa45099 100644
--- a/test/test_helper.exs
+++ b/test/test_helper.exs
@@ -17,3 +17,16 @@
uploads = Pleroma.Config.get([Pleroma.Uploaders.Local, :uploads], "test/uploads")
File.rm_rf!(uploads)
end)
+
+defmodule Pleroma.Test.StaticConfig do
+ @moduledoc """
+ This module provides a Config that is completely static, built at startup time from the environment. It's safe to use in testing as it will not modify any state.
+ """
+
+ @behaviour Pleroma.Config.Getting
+ @config Application.get_all_env(:pleroma)
+
+ def get(path, default \\ nil) do
+ get_in(@config, path) || default
+ end
+end
Want to add to the discussion?
Post a comment!