diff --git a/.woodpecker/build-amd64.yml b/.woodpecker/build-amd64.yml deleted file mode 100644 index 0a41fafef..000000000 --- a/.woodpecker/build-amd64.yml +++ /dev/null @@ -1,121 +0,0 @@ -labels: - platform: linux/amd64 - -depends_on: - - test - -variables: - - &scw-secrets - SCW_ACCESS_KEY: - from_secret: SCW_ACCESS_KEY - SCW_SECRET_KEY: - from_secret: SCW_SECRET_KEY - SCW_DEFAULT_ORGANIZATION_ID: - from_secret: SCW_DEFAULT_ORGANIZATION_ID - - &setup-hex "mix local.hex --force && mix local.rebar --force" - - &on-release - when: - event: - - push - - tag - branch: - - develop - - stable - - &on-stable - when: - event: - - push - - tag - branch: - - stable - - &on-pr-open - when: - event: - - pull_request - - - &tag-build "export BUILD_TAG=$${CI_COMMIT_TAG:-\"$CI_COMMIT_BRANCH\"} && export PLEROMA_BUILD_BRANCH=$BUILD_TAG" - - - &clean "(rm -rf release || true) && (rm -rf _build || true) && (rm -rf /root/.mix)" - - &mix-clean "mix deps.clean --all && mix clean" - -steps: - # Canonical amd64 - debian-bookworm: - image: hexpm/elixir:1.15.4-erlang-26.0.2-debian-bookworm-20230612 - <<: *on-release - environment: - MIX_ENV: prod - DEBIAN_FRONTEND: noninteractive - commands: - - apt-get update && apt-get install -y cmake libmagic-dev rclone zip imagemagick libmagic-dev git build-essential g++ wget - - *clean - - echo "import Config" > config/prod.secret.exs - - *setup-hex - - *tag-build - - mix deps.get --only prod - - mix release --path release - - zip akkoma-amd64.zip -r release - - release-debian-bookworm: - image: akkoma/releaser - <<: *on-release - environment: *scw-secrets - commands: - - export SOURCE=akkoma-amd64.zip - # AMD64 - - export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-amd64.zip - - /bin/sh /entrypoint.sh - # Ubuntu jammy (currently compatible) - - export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-amd64-ubuntu-jammy.zip - - /bin/sh /entrypoint.sh - - debian-bullseye: - image: hexpm/elixir:1.15.4-erlang-26.0.2-debian-bullseye-20230612 - <<: *on-release - environment: - MIX_ENV: prod - DEBIAN_FRONTEND: noninteractive - commands: - - apt-get update && apt-get install -y cmake libmagic-dev rclone zip imagemagick libmagic-dev git build-essential g++ wget - - *clean - - echo "import Config" > config/prod.secret.exs - - *setup-hex - - *tag-build - - mix deps.get --only prod - - mix release --path release - - zip akkoma-amd64-debian-bullseye.zip -r release - - release-debian-bullseye: - image: akkoma/releaser - <<: *on-release - environment: *scw-secrets - commands: - - export SOURCE=akkoma-amd64-debian-bullseye.zip - # AMD64 - - export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-amd64-debian-bullseye.zip - - /bin/sh /entrypoint.sh - - # Canonical amd64-musl - musl: - image: hexpm/elixir:1.15.4-erlang-26.0.2-alpine-3.18.2 - <<: *on-stable - environment: - MIX_ENV: prod - commands: - - apk add git gcc g++ musl-dev make cmake file-dev rclone wget zip imagemagick - - *clean - - *setup-hex - - *mix-clean - - *tag-build - - mix deps.get --only prod - - mix release --path release - - zip akkoma-amd64-musl.zip -r release - - release-musl: - image: akkoma/releaser - <<: *on-stable - environment: *scw-secrets - commands: - - export SOURCE=akkoma-amd64-musl.zip - - export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-amd64-musl.zip - - /bin/sh /entrypoint.sh diff --git a/.woodpecker/build-arm64.yml b/.woodpecker/build-arm64.yml deleted file mode 100644 index b1e495153..000000000 --- a/.woodpecker/build-arm64.yml +++ /dev/null @@ -1,93 +0,0 @@ -labels: - platform: linux/arm64 - -depends_on: - - test - -variables: - - &scw-secrets - SCW_ACCESS_KEY: - from_secret: SCW_ACCESS_KEY - SCW_SECRET_KEY: - from_secret: SCW_SECRET_KEY - SCW_DEFAULT_ORGANIZATION_ID: - from_secret: SCW_DEFAULT_ORGANIZATION_ID - - &setup-hex "mix local.hex --force && mix local.rebar --force" - - &on-release - when: - event: - - push - - tag - branch: - - stable - - develop - - &on-stable - when: - event: - - push - - tag - branch: - - stable - - &on-pr-open - when: - event: - - pull_request - - - &tag-build "export BUILD_TAG=$${CI_COMMIT_TAG:-\"$CI_COMMIT_BRANCH\"} && export PLEROMA_BUILD_BRANCH=$BUILD_TAG" - - - &clean "(rm -rf release || true) && (rm -rf _build || true) && (rm -rf /root/.mix)" - - &mix-clean "mix deps.clean --all && mix clean" - -steps: - # Canonical arm64 - debian-bookworm: - image: hexpm/elixir:1.15.4-erlang-26.0.2-debian-bookworm-20230612 - <<: *on-release - environment: - MIX_ENV: prod - DEBIAN_FRONTEND: noninteractive - commands: - - apt-get update && apt-get install -y cmake libmagic-dev rclone zip imagemagick libmagic-dev git build-essential g++ wget - - *clean - - echo "import Config" > config/prod.secret.exs - - *setup-hex - - *tag-build - - mix deps.get --only prod - - mix release --path release - - zip akkoma-arm64.zip -r release - - release-debian-bookworm: - image: akkoma/releaser:arm64 - <<: *on-release - environment: *scw-secrets - commands: - - export SOURCE=akkoma-arm64.zip - - export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-arm64-ubuntu-jammy.zip - - /bin/sh /entrypoint.sh - - export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-arm64.zip - - /bin/sh /entrypoint.sh - - # Canonical arm64-musl - musl: - image: hexpm/elixir:1.15.4-erlang-26.0.2-alpine-3.18.2 - <<: *on-stable - environment: - MIX_ENV: prod - commands: - - apk add git gcc g++ musl-dev make cmake file-dev rclone wget zip imagemagick - - *clean - - *setup-hex - - *mix-clean - - *tag-build - - mix deps.get --only prod - - mix release --path release - - zip akkoma-arm64-musl.zip -r release - - release-musl: - image: akkoma/releaser:arm64 - <<: *on-stable - environment: *scw-secrets - commands: - - export SOURCE=akkoma-arm64-musl.zip - - export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-arm64-musl.zip - - /bin/sh /entrypoint.sh diff --git a/.woodpecker/docs.yml b/.woodpecker/docs.yml index f8cd12145..f4a9f42ee 100644 --- a/.woodpecker/docs.yml +++ b/.woodpecker/docs.yml @@ -1,50 +1,15 @@ labels: platform: linux/amd64 -depends_on: - - test - - build-amd64 - -variables: - - &setup-hex "mix local.hex --force && mix local.rebar --force" - - &on-release - when: - event: - - push - - tag - branch: - - develop - - stable - - refs/tags/v* - - refs/tags/stable-* - - &on-stable - when: - event: - - push - - tag - branch: - - stable - - refs/tags/stable-* - - &on-point-release - when: - event: - - push - branch: - - develop - - stable - - &on-pr-open - when: - event: - - pull_request - - - &tag-build "export BUILD_TAG=$${CI_COMMIT_TAG:-\"$CI_COMMIT_BRANCH\"} && export PLEROMA_BUILD_BRANCH=$BUILD_TAG" - - - &clean "(rm -rf release || true) && (rm -rf _build || true) && (rm -rf /root/.mix)" - - &mix-clean "mix deps.clean --all && mix clean" +when: + event: + - push + branch: + - develop + - stable steps: docs: - <<: *on-point-release environment: CI: "true" SCW_ACCESS_KEY: diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml deleted file mode 100644 index 632465fa4..000000000 --- a/.woodpecker/lint.yml +++ /dev/null @@ -1,52 +0,0 @@ -labels: - platform: linux/amd64 - -variables: - - &setup-hex "mix local.hex --force && mix local.rebar --force" - - &on-release - when: - event: - - push - - tag - branch: - - develop - - stable - - refs/tags/v* - - refs/tags/stable-* - - &on-stable - when: - event: - - push - - tag - branch: - - stable - - refs/tags/stable-* - - &on-point-release - when: - event: - - push - branch: - - develop - - stable - - &on-pr-open - when: - event: - - pull_request - - - &tag-build "export BUILD_TAG=$${CI_COMMIT_TAG:-\"$CI_COMMIT_BRANCH\"} && export PLEROMA_BUILD_BRANCH=$BUILD_TAG" - - - &clean "(rm -rf release || true) && (rm -rf _build || true) && (rm -rf /root/.mix)" - - &mix-clean "mix deps.clean --all && mix clean" - -steps: - lint: - image: akkoma/ci-base:1.18-otp27 - <<: *on-pr-open - environment: - MIX_ENV: test - commands: - - mix local.hex --force - - mix local.rebar --force - - mix deps.get - - mix compile - - mix format --check-formatted diff --git a/.woodpecker/publish.yml b/.woodpecker/publish.yml new file mode 100644 index 000000000..73b913346 --- /dev/null +++ b/.woodpecker/publish.yml @@ -0,0 +1,104 @@ +when: + event: + - push + - tag + branch: + - develop + - stable + evaluate: 'SKIP_DEVELOP != "YES" || CI_COMMIT_BRANCH != "develop"' + +matrix: + include: + # Canonical amd64 + - ARCH: amd64 + SUFFIX: + IMG_VAR: debian-bookworm-20230612 + UBUNTU_EXPORT: YES + + # old debian variant of amd64 + - ARCH: amd64 + SUFFIX: -debian-bullseye + IMG_VAR: debian-bullseye-20230612 + + # Canonical amd64-musl + - ARCH: amd64 + SUFFIX: -musl + IMG_VAR: alpine-3.18.2 + SKIP_DEVELOP: YES + + # Canonical arm64 + - ARCH: arm64 + SUFFIX: + RELEASER_TAG: :arm64 + IMG_VAR: debian-bookworm-20230612 + UBUNTU_EXPORT: YES + + # Canonical arm64-musl + - ARCH: arm64 + SUFFIX: -musl + RELEASER_TAG: :arm64 + IMG_VAR: alpine-3.18.2 + SKIP_DEVELOP: YES + +labels: + platform: linux/${ARCH} + +steps: + # Canonical amd64 + build: + image: hexpm/elixir:1.15.4-erlang-26.0.2-${IMG_VAR} + environment: + MIX_ENV: prod + DEBIAN_FRONTEND: noninteractive + commands: | + # install deps + case "${IMG_VAR}" in + debian*) + apt-get update && apt-get install -y \ + cmake libmagic-dev rclone zip git wget \ + build-essential g++ imagemagick libmagic-dev + ;; + alpine*) + apk add git gcc g++ musl-dev make cmake file-dev rclone wget zip imagemagick + ;; + *) + echo "No package manager defined for ${BASE_IMG}!" + exit 1 + esac + + # clean leftovers + rm -rf release + rm -rf _build + rm -rf /root/.mix + + # setup + echo "import Config" > config/prod.secret.exs + mix local.hex --force + mix local.rebar --force + export BUILD_TAG=$${CI_COMMIT_TAG:-\"$CI_COMMIT_BRANCH\"} + export PLEROMA_BUILD_BRANCH=$BUILD_TAG + + # actually build and zip up + mix deps.get --only prod + mix release --path release + zip akkoma-${ARCH}${SUFFIX}.zip -r release + + release: + image: akkoma/releaser${RELEASER_TAG} + environment: + SCW_ACCESS_KEY: + from_secret: SCW_ACCESS_KEY + SCW_SECRET_KEY: + from_secret: SCW_SECRET_KEY + SCW_DEFAULT_ORGANIZATION_ID: + from_secret: SCW_DEFAULT_ORGANIZATION_ID + commands: | + export SOURCE=akkoma-${ARCH}${SUFFIX}.zip + export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/$${SOURCE} + /bin/sh /entrypoint.sh + + if [ "${UBUNTU_EXPORT}" = "YES" ] ; then + # Ubuntu jammy (currently compatible with our default debian builds) + export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-${ARCH}-ubuntu-jammy.zip + /bin/sh /entrypoint.sh + fi diff --git a/.woodpecker/test.yml b/.woodpecker/test.yml index f4044000c..dbbfb6cb6 100644 --- a/.woodpecker/test.yml +++ b/.woodpecker/test.yml @@ -1,66 +1,24 @@ -labels: - platform: linux/amd64 - -depends_on: - - lint +when: + - event: pull_request matrix: # test the lowest and highest versions - ELIXIR_VERSION: - - 1.14 - - 1.18 - OTP_VERSION: - - 25 - - 27 include: - ELIXIR_VERSION: 1.14 OTP_VERSION: 25 + LINT: NO + PLATFORM: linux/amd64 - ELIXIR_VERSION: 1.18 OTP_VERSION: 27 + LINT: YES + PLATFORM: linux/arm64 -variables: - - &setup-hex "mix local.hex --force && mix local.rebar --force" - - &on-release - when: - event: - - push - - tag - branch: - - develop - - stable - - refs/tags/v* - - refs/tags/stable-* - - &on-stable - when: - event: - - push - - tag - branch: - - stable - - refs/tags/stable-* - - &on-point-release - when: - event: - - push - branch: - - develop - - stable - - &on-pr-open - when: - event: - - pull_request - - - &tag-build "export BUILD_TAG=$${CI_COMMIT_TAG:-\"$CI_COMMIT_BRANCH\"} && export PLEROMA_BUILD_BRANCH=$BUILD_TAG" - - - &clean "(rm -rf release || true) && (rm -rf _build || true) && (rm -rf /root/.mix)" - - &mix-clean "mix deps.clean --all && mix clean" +labels: + platform: ${PLATFORM} services: postgres: image: postgres:15 - when: - event: - - pull_request environment: POSTGRES_DB: pleroma_test_${ELIXIR_VERSION}_${OTP_VERSION} POSTGRES_USER: postgres @@ -69,18 +27,20 @@ services: steps: test: image: akkoma/ci-base:${ELIXIR_VERSION}-otp${OTP_VERSION} - <<: *on-pr-open environment: MIX_ENV: test POSTGRES_DB: pleroma_test_${ELIXIR_VERSION}_${OTP_VERSION} POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres DB_HOST: postgres + LINT: ${LINT} commands: + - sh -c 'uname -a && cat /etc/os-release || :' - mix local.hex --force - mix local.rebar --force - mix deps.get - mix compile + - test "${LINT}" = "NO" || mix format --check-formatted - mix ecto.drop -f -q - mix ecto.create - mix ecto.migrate diff --git a/CHANGELOG.md b/CHANGELOG.md index aa24791c1..f2a1423bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,48 @@ 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 +### REMOVED ### Added +- status responses include two new fields for ActivityPub cross-referencing: `akkoma.quote_apid` and `akkoma.in_reply_to_apid` + +### Fixed +- replies and quotes to unresolvable posts now fill out IDs for replied to + status, user or quoted status with a 404-ing ID to make them recognisable as + replies/quotes instead of pretending they’re root posts + +### Changed + + +## 2025.10 + +### REMOVED +- Dropped `accepts_chat_messages` column from users table in database; + it has been unused for almost 3 years +- Healthcheck responses no longer contain job queue data; + it was useless anyway due to lacking any temporal information about failures + and more complete data can be obtained from Prometheus metrics. + +### Added +- We mark our MFM posts as FEP-c16b compliant, and retain remote HTML representations for incoming posts marked as FEP-c16b-compliant. (Safety scrubbers are still applied) - Prometheus stats now exposes failed ActivityPub deliveries which failed all attempts and the failure reason - status and user HTML pages now provide ActivityPub alternate links - the `prune_objects` mix task no longer deletes pinned posts by default - added `--prune-pinned` and `--keep-followed {posts,full,none}` options to the `prune_objects` mix task +- timestamps of incoming HTTP signatures are now verified. + By default up to two hour old signatures and a maximal clock skew + of 40 min for future timestamps or explicit expiry deadlines are accepted +- Added `short_description` field to `api/v1/instance` for Mastodon compatibility; the corresponding + new setting `:pleroma, :instance, :short_description` is also preferred for nodeinfo use +- Note AP objects now expose full `replies` collections and those collections can be accessed on their own; + previously only self-replies were inlined as an anonymous collection into the Note object +- Added a reference Grafana dashboard and improved documentation for Prometheus metrics +- New mix task `clean_inlined_replies` to delete some unused data from objects +- New mix task `resync_inlined_caches` to retroactively fix various issues with e.g. boosts, emoji reacts and likes +- It is now possible to allow outgoing requests to use HTTP2 via config option, + but due to bugs in the relevant backend this is not the default nor recommended. +- Prometheus metrics now expose count of scheduled and pending jobs per queue ### Fixed - Internal actors no longer pretend to have unresolvable follow(er|ing) collections @@ -21,13 +56,43 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). this lead e.g. to unlisted replies from Pleroma instances being partially treated as private posts - fixed our fetch actor advertising bogus follower and following collection ActivityPub IDs - fix network-path references not being handled by media proxy +- federation with bridgy now works +- remote signing keys are no longer refreshed multiple times per incoming request +- fix digest emails never being sent and clogging the job queue even if not enabled +- `api/v1/instance`’s `uri` field now correctly shows the bare WebFinger domain +- fixed bug leading to `content` and the `contentMap` entry of the primary language to sometimes diverge +- reloading emoji with a broken `pack.json` file being on disk no longer crashes the whole server +- fixed blocked servers being able to access local objects when authorized fetch isn’t enabled + even when the remote server identifies itselfs +- fixed handling of inlined "featured" collections +- fixed user endpoint serving invalid ActivityPub for minimal, authfetch-fallback responses +- remote emoji reacts from IceShrimp.NET instances are now handled consistently and always merged with identical other emoji reactions +- ActivityPub requests signatures are now renewed when following redirects making sure path and host actually match the final URL +- private replies no longer increase the publicly visible reply counter +- unblock activities are no longer federated when block federation is disabled (the default) +- fix like activity database IDs rendering as misattributed posts ### Changed - Internal and relay actors are now again represented with type "Application" - `cleanup_attachments` is now enabled by default - shared inboxes are now generally preferred over personal inboxes, cutting down on duplicate publishing churn - instance actors are now really of type `Service` -- ActivityPub delivery attempts are spaced out more giving up after 3h instead of ~20min before +- ActivityPub delivery attempts are spaced out more and increased by one + now giving up after 24h instead of ~20min by default before +- inboxes now fake a succcess reply on incoming Delete documents whose signing key is unknown but gone; + this prevents older Mastodon from repeatedly trying to deliver Deletes of actors we never knew anyway +- The config option `config :pleroma, :http, :pool_max_idle_time` was removed; it never actually + did anything and was redundant with `config :pleroma, :http, :pool_timeout` which actually works. +- repeated attempt to process incoming ActivityPub objects are spaced out more, allowing unreachable remotes + more time to come back up when e.g. processing repeats of a post not yet locally known +- `/api/v1/statuses/:id/reblog` now honours all possible visibilities except `list` and `conversation` + instead of mapping them down to a boolean private/public +- we no longer repeatedly try to deliver to explicitly deleted inboxes +- Config option `Pleroma.Web.MediaProxy.Invalidation.Http, :options` and + the `:http` subkey of `:media_proxy, :proxy_opts` now only accept + adapter-related settings inside the `:adapter` subkey, no longer on the top-level +- follow requests are now ordered reverse chronologically + ## 2025.03 diff --git a/FEDERATION.md b/FEDERATION.md index fda5d1891..e18e510c4 100644 --- a/FEDERATION.md +++ b/FEDERATION.md @@ -34,7 +34,8 @@ Depending on instance configuration the same may be true for GET requests. ### FEP-c16b: Formatting MFM functions -The optional extension term `htmlMfm` is currently not used. +We set the optional extension term `htmlMfm: true` when using content type "text/x.misskeymarkdown". +Incoming messages containing `htmlMfm: true` will not have their content re-parsed. ## Nodeinfo diff --git a/benchmarks/load_testing/fetcher.ex b/benchmarks/load_testing/fetcher.ex index 607b7d4cb..445463dea 100644 --- a/benchmarks/load_testing/fetcher.ex +++ b/benchmarks/load_testing/fetcher.ex @@ -53,7 +53,7 @@ defp fetch_timelines(user) do fetch_public_timeline(user, :local) fetch_public_timeline(user, :tag) fetch_notifications(user) - fetch_favourites(user) + fetch_favourited_with_fav_id(user) fetch_long_thread(user) fetch_timelines_with_reply_filtering(user) end @@ -378,21 +378,21 @@ defp fetch_notifications(user) do end defp fetch_favourites(user) do - first_page_last = ActivityPub.fetch_favourites(user) |> List.last() + first_page_last = ActivityPub.fetch_favourited_with_fav_id(user) |> List.last() second_page_last = - ActivityPub.fetch_favourites(user, %{:max_id => first_page_last.id}) |> List.last() + ActivityPub.fetch_favourited_with_fav_id(user, %{:max_id => first_page_last.id}) |> List.last() third_page_last = - ActivityPub.fetch_favourites(user, %{:max_id => second_page_last.id}) |> List.last() + ActivityPub.fetch_favourited_with_fav_id(user, %{:max_id => second_page_last.id}) |> List.last() forth_page_last = - ActivityPub.fetch_favourites(user, %{:max_id => third_page_last.id}) |> List.last() + ActivityPub.fetch_favourited_with_fav_id(user, %{:max_id => third_page_last.id}) |> List.last() Benchee.run( %{ "Favourites" => fn opts -> - ActivityPub.fetch_favourites(user, opts) + ActivityPub.fetch_favourited_with_fav_id(user, opts) end }, inputs: %{ @@ -465,7 +465,8 @@ defp render_timelines(user) do notifications = MastodonAPI.get_notifications(user, opts) - favourites = ActivityPub.fetch_favourites(user) + favourites_keyed = ActivityPub.fetch_favourited_with_fav_id(user) + favourites = Pagiation.unwrap(favourites_keyed) Benchee.run( %{ diff --git a/config/benchmark.exs b/config/benchmark.exs index fda0a25f9..ab8a71fcc 100644 --- a/config/benchmark.exs +++ b/config/benchmark.exs @@ -80,8 +80,6 @@ IO.puts("RUM enabled: #{rum_enabled}") IO.puts("PGroonga enabled: #{pgroonga_enabled}") -config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.ClientMock - if File.exists?("./config/benchmark.secret.exs") do import_config "benchmark.secret.exs" else diff --git a/config/config.exs b/config/config.exs index d0f4e7033..c8c3af879 100644 --- a/config/config.exs +++ b/config/config.exs @@ -51,6 +51,11 @@ queue_target: 20_000, migration_lock: nil +# password hash strength +config :argon2_elixir, + t_cost: 8, + parallelism: 2 + config :pleroma, Pleroma.Captcha, enabled: true, seconds_valid: 300, @@ -182,20 +187,22 @@ # Configures http settings, upstream proxy etc. config :pleroma, :http, - pool_timeout: :timer.seconds(5), + pool_timeout: :timer.seconds(60), receive_timeout: :timer.seconds(15), proxy_url: nil, + protocols: [:http1], user_agent: :default, pool_size: 10, - adapter: [], - # see: https://hexdocs.pm/finch/Finch.html#start_link/1 - pool_max_idle_time: :timer.seconds(30) + adapter: [] config :pleroma, :instance, name: "Akkoma", email: "example@example.com", notify_email: "noreply@example.com", + # allowed to use HTML (if short_description is set) description: "Akkoma: The cooler fediverse server", + # only plain text (defaults to description) + short_description: nil, background_image: "/images/city.jpg", instance_thumbnail: "/instance/thumbnail.jpeg", limit: 5_000, @@ -599,6 +606,7 @@ remote_fetcher: 2, attachments_cleanup: 1, new_users_digest: 1, + digest_emails: 1, mute_expire: 5, search_indexing: 10, nodeinfo_fetcher: 1, @@ -619,7 +627,7 @@ config :pleroma, :workers, retries: [ federator_incoming: 5, - federator_outgoing: 5, + federator_outgoing: 6, search_indexing: 2, rich_media_backfill: 1 ], diff --git a/config/description.exs b/config/description.exs index 2a7f3344d..d961273c5 100644 --- a/config/description.exs +++ b/config/description.exs @@ -298,7 +298,7 @@ key: :ssl, label: "Use SSL", type: :boolean, - description: "Use Implicit SSL/TLS. e.g. port 465" + description: "Use Implicit SSL/TLS. e.g. port 465; default: true" }, %{ group: {:subgroup, Swoosh.Adapters.SMTP}, @@ -566,7 +566,16 @@ key: :description, type: :string, description: - "The instance's description. It can be seen in nodeinfo and `/api/v1/instance`", + "The instance's description. It may use HTML and can be seen in `/api/v1/instance` and nodeifno if no short description is set", + suggestions: [ + "Very cool instance" + ] + }, + %{ + key: :short_description, + type: :string, + description: + "A brief instance description. It must be plain text and can be seen in `/api/v1/instance` and nodeinfo", suggestions: [ "Very cool instance" ] @@ -3241,8 +3250,7 @@ suggestions: [ Pleroma.Web.Preload.Providers.Instance, Pleroma.Web.Preload.Providers.User, - Pleroma.Web.Preload.Providers.Timelines, - Pleroma.Web.Preload.Providers.StatusNet + Pleroma.Web.Preload.Providers.Timelines ] } ] diff --git a/config/test.exs b/config/test.exs index e8dbe2b0a..09ed2bc60 100644 --- a/config/test.exs +++ b/config/test.exs @@ -135,9 +135,7 @@ config :pleroma, Pleroma.Web.WebFinger, update_nickname_on_user_fetch: false -config :pleroma, :side_effects, - ap_streamer: Pleroma.Web.ActivityPub.ActivityPubMock, - logger: Pleroma.LoggerMock +config :pleroma, :side_effects, ap_streamer: Pleroma.Web.ActivityPub.ActivityPubMock config :pleroma, Pleroma.Search, module: Pleroma.Search.DatabaseSearch diff --git a/docs/docs/administration/CLI_tasks/database.md b/docs/docs/administration/CLI_tasks/database.md index 68ff0afb4..f21ad2d40 100644 --- a/docs/docs/administration/CLI_tasks/database.md +++ b/docs/docs/administration/CLI_tasks/database.md @@ -26,13 +26,19 @@ Replaces embedded objects with references to them in the `objects` table. Only n ## Prune old remote posts from the database -This will prune remote posts older than 90 days (configurable with [`config :pleroma, :instance, remote_post_retention_days`](../../configuration/cheatsheet.md#instance)) from the database. Pruned posts may be refetched in some cases. +This will selectively prune remote posts older than 90 days (configurable with [`config :pleroma, :instance, remote_post_retention_days`](../../configuration/cheatsheet.md#instance)) from the database. Pruned posts may be refetched in some cases. !!! note - The disk space will only be reclaimed after a proper vacuum. By default, Postgresql does this for you on a regular basis, but if your instance has been running for a long time and there are many rows deleted, it may be advantageous to use `VACUUM FULL` (e.g. by using the `--vacuum` option). + The disk space used up by deleted rows only becomes usable for new data after a vaccum. + By default, Postgresql does this for you on a regular basis, but if you delete a lot at once + it might be advantageous to also manually kick off a vacuum and statistics update using `VACUUM ANALYZE`. + + **However**, the freed up space is never returned to the operating system unless you run + the much more heavy `VACUUM FULL` operation. This epensive but comprehensive vacuum mode + can be schedlued using the `--vacuum` option. !!! danger - You may run out of disk space during the execution of the task or vacuuming if you don't have about 1/3rds of the database size free. Vacuum causes a substantial increase in I/O traffic, and may lead to a degraded experience while it is running. + You may run out of disk space during the execution of the task or full vacuuming if you don't have about 1/3rds of the database size free. `VACUUM FULL` causes a substantial increase in I/O traffic, needs full table locks and thus renders the instance basically unusable while its running. === "OTP" @@ -48,15 +54,41 @@ This will prune remote posts older than 90 days (configurable with [`config :ple ### Options +The recommended starting point and configuration for small and medium-sized instances is: +```sh +prune_objects --keep-followed posts --keep-threads --keep-non-public +# followed by +prune_orphaned_activities --no-singles +prune_orphaned_activities --no-arrays +# and finally, using psql to manually run: +# VACUUM ANALYZE; +# REINDEX TABLE objects; +# REINDEX TABLE activities; +``` +This almost certainly won’t delete stuff your interested in and +makes sure the database is immediately utilising the newly freed up space. +If you need more aggressive database size reductions or if this proves too costly to run for you +you can drop restrictions and/or use the `--limit` option. +In the opposite case if everything goes through quickly, +you can combine the three CLI tasks into one for future runs using `--prune-orphaned-activities` +and perhaps even using a full vacuum (which implies a reindex) using `--vacuum` too. + +Full details below: + +- `--no-fix-replies-count` - Skip recalculating replies count for posts. + When using multiple batches of prunes with `--limit`, all but the last batch + should set this option to avoid unnecessary overhead. - `--keep-followed ` - If set to `posts` all posts and boosts of users with local follows will be kept. If set to `full` it will additionally keep any posts such users interacted with; this requires `--keep-threads`. By default this is set to `none` and followed users are not treated special. - `--keep-threads` - Don't prune posts when they are part of a thread where at least one post has seen local interaction (e.g. one of the posts is a local post, or is favourited by a local user, or has been repeated by a local user...). It also won’t delete posts when at least one of the posts in the thread has seen recent activity or is kept due to `--keep-followed`. - `--keep-non-public` - Keep non-public posts like DM's and followers-only, even if they are remote. - `--limit` - limits how many remote posts get pruned. This limit does **not** apply to any of the follow up jobs. If wanting to keep the database load in check it is thus advisable to run the standalone `prune_orphaned_activities` task with a limit afterwards instead of passing `--prune-orphaned-activities` to this task. + See documentation of other options for futher hints. - `--prune-orphaned-activities` - Also prune orphaned activities afterwards. Activities are things like Like, Create, Announce, Flag (aka reports)... They can significantly help reduce the database size. - `--prune-pinned` - Also prune pinned posts; keeping pinned posts does not suffice to protect their threads from pruning, even when using `--keep-threads`. - Note, if using this option and pinned posts are pruned, they and their threads will just be refetched on the next user update. Therefore it usually doesn't bring much gain while incurring a heavy fetch load after pruning. + Note, if using this option and pinned posts are pruned, they and their threads will just be refetched on the next user update. Therefore it usually doesn't bring much gain while incurring a heavy fetch load after pruning. + One exception to this is if you already need to use a relatively small `--limit` to keep downtime mangeable or even being able to run it without downtime. Retaining pinned posts adds a mostly constant overhead which will impact repeated runs with small limit much more than one full prune run. - `--vacuum` - Run `VACUUM FULL` after the objects are pruned. This should not be used on a regular basis, but is useful if your instance has been running for a long time before pruning. ## Prune orphaned activities from the database @@ -223,3 +255,48 @@ to the current day. ```sh mix pleroma.database prune_task ``` + +## Clean inlined replies lists + +Those inlined arrays of replies AP IDs are not used (anymore). +Delete them to free up a little bit of space. + +=== "OTP" + + ```sh + ./bin/pleroma_ctl database clean_inlined_replies + ``` + +=== "From Source" + + ```sh + mix pleroma.database clean_inlined_replies + ``` + +## Resync data inlined into posts + +For legacy and performance reasons some data, e.g. relating to likes and boosts, +is currently copied and inline references post objects. Occasionally this data +may desync from the actual authorative activity and object data stored in the +database which may lead to cosmetic but also functional issues. +For example a particular user may appear unable to like a post. +Run this task to detect and fix such desyncs. + +=== "OTP" + + ```sh + ./bin/pleroma_ctl database resync_inlined_caches + ``` + +=== "From Source" + + ```sh + mix pleroma.database resync_inlined_caches + ``` + +### Options + +- `--no-announcements` - Skip fixing announcement counters and lists +- `--no-likes` - Skip fixing like counters and lists +- `--no-reactions` - Skip fixing inlined emoji reaction data +- `--no-replies-count` - Skip fixing replies counters (purely cosmetic) diff --git a/docs/docs/administration/img/grafana_dashboard.webp b/docs/docs/administration/img/grafana_dashboard.webp new file mode 100644 index 000000000..adad362dc Binary files /dev/null and b/docs/docs/administration/img/grafana_dashboard.webp differ diff --git a/docs/docs/administration/monitoring.md b/docs/docs/administration/monitoring.md index 7f3fa05c3..6f3145a74 100644 --- a/docs/docs/administration/monitoring.md +++ b/docs/docs/administration/monitoring.md @@ -1,45 +1,275 @@ # Monitoring Akkoma -If you run akkoma, you may be inclined to collect metrics to ensure your instance is running smoothly, -and that there's nothing quietly failing in the background. +If you run Akkoma it’s a good idea to collect metrics to ensure your instance is running smoothly +without anything silently failing and to aid troubleshooting if something actually goes wrong. -To facilitate this, akkoma exposes a dashboard and prometheus metrics to be scraped. +To facilitate this, Akkoma exposes Prometheus metrics to be scraped for long-term 24/7 monitoring +as well as as two built-in dashboards with ephemeral info about just the current status. +Setting up Prometheus scraping is highly recommended. ## Prometheus -See: [export\_prometheus\_metrics](../../configuration/cheatsheet#instance) +This method gives a more or less complete overview and allows for 24/7 long-term monitoring. -To scrape prometheus metrics, we need an oauth2 token with the `admin:metrics` scope. +Prometheus metric export can be globally disabled if you really want to, +but it doesn’t cause much overhead and is enabled by default: see the +[export\_prometheus\_metrics](../../configuration/cheatsheet#instance) config option. -Consider using [constanze](https://akkoma.dev/AkkomaGang/constanze) to make this easier - +Akkoma only exposes the current state of all metrics; to make it actually useful +an external scraper needs to regularly fetch and store those values. +An overview of the necessary steps follows. + +### Step 1: generate a token + +Accessing prometheus metrics, requires an OAuth2 token with the `admin:metrics` (sub)scope. +An access token with only this subscope will be unable to do anything at all _except_ looking at the exported metrics. + +Assuming your account has access to the `admin` scope category, +a suitable metrics-only token can be conveniently generated using +[constanze](https://akkoma.dev/AkkomaGang/constanze). +If you didn’t already do so before, set up `constanze` by running `constanze configure`. +Now getting the token is as simple as running the below command and following its instructions: ```bash constanze token --client-app --scopes "admin:metrics" --client-name "Prometheus" ``` -or see `scripts/create_metrics_app.sh` in the source tree for the process to get this token. +Alternatively you may manually call into the token and app APIs; +check `scripts/create_metrics_app.sh` in the source tree for the process for this. -Once you have your token of the form `Bearer $ACCESS_TOKEN`, you can use that in your prometheus config: +The resulting token will have the form `Bearer $ACCESS_TOKEN`; +in the following replace occurrences of `$ACCESS_TOKEN` with the actual token string everywhere. +If you wish, you can now check the token works by manually using it to query the current metrics with `curl`: -```yaml -- job_name: akkoma - scheme: https - authorization: - credentials: $ACCESS_TOKEN # this should have the bearer prefix removed - metrics_path: /api/v1/akkoma/metrics - static_configs: - - targets: - - example.com +!!! note + After restarting the instance it may take a couple minutes for content to show up in the metric endpoint + +```sh +curl -i -H 'Authorization: Bearer $ACCESS_TOKEN' https://myinstance.example/api/v1/akkoma/metrics | head -n 100 ``` -## Dashboard +### Step 2: set up a scraper + +You may use the eponymous [Prometheus](https://prometheus.io/) +or anything compatible with it like e.g. [VictoriaMetrics](https://victoriametrics.com/). +The latter claims better performance and storage efficiency. + +Both of them can usually be easily installed via distro-packages or docker. +Depending on your distro or installation method the preferred way to change the CLI arguments and the location of config files may differ; consult the documentation of your chosen method to find out. +Of special interest is the location of the prometheus scraping config file +and perhaps the maximal data retention period setting, +to manage used disk space and make sure you keep records long enough for your purposes. +It might also be a good idea to set up a minimal buffer of free disk space if you’re tight on that; +with VictoriaMetrics this can be done via the `-storage.minFreeDiskSpaceBytes 1GiB` CLI flag. + +Ideally the scraper runs on a different machine than Akkoma to be able to +distinguish Akkoma downtime from scraper downtime, but this is not strictly necessary. + +Once you’ve installed one of them, it’s time to add a job for scraping Akkoma. +For Prometheus the `scrape_configs` section will usually be added to the main config file, +for VictoriaMetrics this will be in the file passed via `-promscrape.config file_path`. +In either case a `scrape_configs` with just one job for a single Akkoma instance will look like this: + +```yaml +scrape_configs: + - job_name: 'akkoma_scrape_job' + scheme: https + metrics_path: /api/v1/akkoma/metrics + static_configs: + - targets: ['myinstance.example'] + # reminder: no Bearer prefix here! + bearer_token: '$ACCESS_TOKEN' + # One minute should be frequent enough, but you can choose any value, or rely on the global default. + # However, for later use in Grafana you want to match this exactly, thus make note. + scrape_interval: '1m' +``` + +Now (re)start the scraper service, wait for a multiple of the scrape interval and check logs +to make sure no errors occur. + +### Step 3: visualise the collected data + +At last it’s time to actually get a look at the collected data. +There are many options working with Prometheus-compatible backends +and even software which can act as both the scraper _and_ visualiser in one service. +Here we’ll just deal with Grafana, since we ship a reference Grafana dashboard you can just import. + +There are again multiple options for [installing Grafana](https://grafana.com/docs/grafana/latest/setup-grafana/) +and detailing all of them is out of scope here, but it’s nothing too complicated if you already set up Akkoma. + +Once you’ve got it running and are logged into Grafana, +you first need to tell it about the scraper which acts a the “data source”. +For this go to the “Connections” category and select “Data Sources”. +Here click the shiny button for adding a new data source, +select the “Prometheus” type and fill in the details +matching how you set up the scraper itself. +In particular, **use the same `Scrape Interval` value!** + +Now you’re ready to go to the “Dashboards” page. +Click the “New” button, select “Import” and upload or copy the contents of +the reference dashboard `installation/grafana_dashboard.json` from Akkoma’s source tree. +It will now ask you to select the data source you just configured, +as well as for the name of the job in your scraper config +and your instance domain+port identifier. +For the example settings from step 2 above +the latter two are `akkoma_scrape_job` and `myinstance.example:443`. +*(`443` is the default port for HTTPS)* + +That’s it, you’ve got a fancy dashboard with long-term, 24/7 metrics now! +Updating the dashboard can be done by just repeating the import process. + +Here’s an example taken from a healthy, small instance where +nobody was logged in except for a few minutes +resulting in an (expected) spike in incoming requests: +![Full view of the reference dashboard as it looked at the time of writing](img/grafana_dashboard.webp) + +!!! note + By default the dashboard does not count downtime of the data source, e.g. the scraper, + towards instance downtime, but a commented out alternative query is provided in the + panel edit menu. If you host the scraper on the same machine as Akkoma you likely want to swap this out. + +### Remarks on interpreting the data + +What’s kind of load and even error ratio is normal or irregular can depend on +the instance size, chosen configuration and federation partners. +*(E.g. when following relays, much more activities will be received and the received activities will in turn kick off more internal work and also external fetches raising the overall load)* + +Here the 24/7 nature of the metric history helps out, since we can just +look at "known-good" time spans to get a feeling for what’s normal and good. +If issues without an immediately clear origin crop up, +we can look for deviations from this known-good pattern. + +Still there are some things to be aware of and some common guidelines. + +#### Panel-specific time ranges + +A select few panels, are set to use a custom time range +independent from what you chose for the dashboard as a whole. +This is indicated with blue text next to the panel title. +Those custom times only take precedence over _relative_ global time ranges. +If you choose fixed start and end dates in the global setting +*(for example to look at a long-term trend after a specific change)* +this will take precedence over custom panel times and everything follows the date range. + +In the image above e.g. the uptime percent gauge thus considers the entire last week +while most other panels only display data for the last 6 hours. + +#### Long-term trends + +The lower section of the dashboard with 24h and 48h averages is particularly useful for observing long-term trends. +E.g. how a patch, version upgrade or database `VACCUUM`/`REINDEX` affects performance. + +For small time ranges you can still look at them to make sure the values are at a reasonable level, +but the upper part is probably more interesting. + +#### Processing times total + +The actions counted by various “time per second” or “time per minute” stats are partially overlapping. +E.g. the time to conclude a HTTP response includes the time it took to run whatever +query was needed to fetch the necessary information from the database. +However not all database queries originate from HTTP requests. + +But also, not all of the recorded time might have actually consumed CPU cycles. +Some jobs, e.g. `RemoteFetcherWorker`, will need to fetch data over the network +and often most of the time from job start to completion is just spent waiting +for a reply from the remote server to arrive. +Even a few HTTP endpoints will need to fetch remote data before completing; +e.g. `/inbox` needs to verify the signature of the submission, but if the signing key +wasn’t encountered before it first needs to be fetched. +Getting deliveries from such unknown users happens more often than you might initially assume +due to e.g. Mastodon federating actor deletions to _every server it knows about_ +regardless of whether there was ever any contact with the deleted user. +*(Meaning in the end the key lookup will just result in a `410 Gone` response and us dropping the superfluous `Delete`)* + +Thus if you just add up all timing stats you’ll count some actions multiple times +and may end up consistently with more processing time being done than time elapsing on the wall clock +even though your server is neither overloaded nor subject to relative time dilation. + +For keeping track of CPU and elixir-side(!) IO bottlenecks, +the corresponding BEAM VM gauges are much better indicators. +They should be zero most of the time and never exceed zero by much. + +!!! note + The BEAM VM (running our elixir code) cannot know about + the database’s IO activity or CPU cycle consumption, + thus this gauge is no indicator for database bottlenecks. + +#### Job failures and federation + +Most jobs are automatically retried and may fail (“exception”) due to no fault of your own instance +e.g. network issues or a remote server temporarily being overloaded. +Thus seeing some failures here is normal and nothing to be concerned about; +usually it will just resolve itself on the next retry. +Consistent and/or a relatively high success-to-failure ratio though +is worth looking into using logs. + +Of particular importance are Publisher jobs; +they handle delivering your local content to its intended remote recipients. +Again some PublisherWorker exceptions are no cause for concern, +but if all retries for a delivery fail, this means a remote recipient never +received something they should’ve seen. +Due to its particular importance, such final delivery failures are +recorded again in a separate metric. +The reference dashboard shows it in the “AP delivery failures” panel. +Everything listed there exhausted all retries without success. +Ideally this will always be empty and for small instances this should be the +case most of the time. +However, whenever a remote instance which once interacted with +your local instance in the past is decommissioned, delivery failures will likely +eventually show up in your metrics. For example: + + - a local user might be followed by an user from the dead instance + - a local posts was in the past fetched by the dead instance and this post is now deleted; + Akkoma will attempt to deliver the `Delete` to the dead instance even if there’s no follow relationship + +Delivery failures for such dead instances will typically list a reason like +`http_410`, `http_502`, `http_520`-`http_530` (cloudflare’d instances), `econnrefused`, `nxdomain` or just `timeout`. + +If all deliveries to a given remote instance consistently fail for a longer time, +Akkoma will mark it as permanently unreachable and stop even attempting to deliver +to it meaning the errors should go away after a while. +*(If Akkoma sees activity from the presumed dead instance again it will resume deliveries for future content, but anything in the past will remain lost)* + +Large instances with many users are more likely to have (had) some relationship to +such a recently decommissioned instances and thus might see failures here more often +even if nothing is wrong with the local Akkoma instance. +If this makes too much noise, consider filtering out telltale delivery failures. + +On the opposite side of things, a `http_401` error for example is always worth looking into! + +## Built-in Dashboard Administrators can access a live dashboard under `/phoenix/live_dashboard` giving an overview of uptime, software versions, database stats and more. -The dashboard also includes a variation of the prometheus metrics, however -they do not exactly match due to respective limitations of the dashboard -and the prometheus exporter. -Even more important, the dashboard collects metrics locally in the browser -only while the page is open and cannot give a view on their past history. -For proper monitoring it is recommended to set up prometheus. +This dashboard can also show a limited subset of Prometheus metrics, +however besides being limited it only starts collecting data when opening +the corresponding page in the browser and the history only exists in ephemeral browser memory. +When navigating away from the page, all history is gone. +However, this is not this dashboards main purpose anyway. + +The usefulness of this built-in dashboard are the insights into the current state of +the BEAM VM running Akkoma’s code and statistics about the database and its performance +as well as database diagnostics. +BEAM VM stats include detailed memory consumption breakdowns +and a full list of running processes for example. + +## Oban Web + +This too requires administrator rights to access and can be found under `/akkoma/oban` if enabled. +The exposed aggregate info is mostly redundant with job statistics already tracked in Prometheus, +but it additionally also: + + - shows full argument and meta details for each job + - allows interactively deleting or manually retrying jobs + *(keep this in mind when granting people administrator rights!)* + +However, there are two caveats: +1. Just as with the other built-in dashboard, data is not kept around + (although here a **short** backlog actually exists); + when you notice an issue during use and go here to check it likely is already too late. + Job details and history only exists while the jobs are still in the database; + by default failed and succeeded jobs will disappear after about a minute. +2. This dashboard comes with some seemingly constant-ish overhead. + For large instances this appears to be negligible, but small instances on weaker hardware might suffer. + Thus this dashboard can be disabled in the [config](../cheatsheet.md#oban-web). diff --git a/docs/docs/clients.md b/docs/docs/clients.md index b5ec500c9..d7e43cf63 100644 --- a/docs/docs/clients.md +++ b/docs/docs/clients.md @@ -41,12 +41,24 @@ This is a list of clients that are known to work with Akkoma. - Platforms: Android - Features: MastoAPI, No Streaming, Emoji Reactions, Text Formatting, FE Stickers +### Pachli +- Homepage: +- Source Code: +- Contact: [@pachli@mastodon.social](https://mastodon.social/users/pachli) +- Platforms: Android +- Features: MastoAPI, No Streaming + ### Tusky + +!!! warning + Versions after v30.0 do not support Akkoma-compatible filters. + Consider using another client if you use any filters. + - Homepage: - Source Code: - Contact: [@ConnyDuck@mastodon.social](https://mastodon.social/users/ConnyDuck) - Platforms: Android -- Features: MastoAPI, No Streaming +- Features: MastoAPI, No Streaming, **No Filters (beyond v30.0)** ### Subway Tooter - Source Code: diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md index f193cadf1..a91765396 100644 --- a/docs/docs/configuration/cheatsheet.md +++ b/docs/docs/configuration/cheatsheet.md @@ -12,7 +12,12 @@ To add configuration to your config file, you can copy it from the base config. * `name`: The instance’s name. * `email`: Email used to reach an Administrator/Moderator of the instance. * `notify_email`: Email used for notifications. -* `description`: The instance’s description, can be seen in nodeinfo and `/api/v1/instance`. +* `short_description`: A brief, plain-text-only instance description. + Can be seen in nodeinfo and `/api/v1/instance`. + Defaults to `description` if unset +* `description`: The instance’s more detailed description. + This is allowed to use HTML if `short_description` is set. + Can be seen in `api/v1/instance`. * `limit`: Posts character limit (CW/Subject included in the counter). * `description_limit`: The character limit for image descriptions. * `remote_limit`: Hard character limit beyond which remote posts will be dropped. @@ -569,6 +574,8 @@ Available caches: * `receive_timeout`: the amount of time, in ms, to wait for a remote server to respond to a request. (default: `15000`) * `pool_timeout`: the amount of time, in ms, to wait to check out an HTTP connection from the pool. This likely does not need changing unless your instance is _very_ busy with outbound requests. (default `5000`) +* `protocols`: array of acceptable protocols for outgoing requests; by default both HTTP1 and HTTP2 are supported. + Due to Finch limitations multiplexing cam only be used when this is set to exclusively HTTP2, but this will break federation with HTTP1-only instances. * `proxy_url`: an upstream proxy to fetch posts and/or media with, (default: `nil`); for example `http://127.0.0.1:3192`. Does not support SOCKS5 proxy, only http(s). * `send_user_agent`: should we include a user agent with HTTP requests? (default: `true`) * `user_agent`: what user agent should we use? (default: `:default`), must be string or `:default` @@ -1186,7 +1193,7 @@ Each job has these settings: * `:max_running` - max concurrently running jobs * `:max_waiting` - max waiting jobs -### Translation Settings +## Translation Settings Settings to automatically translate statuses for end users. Currently supported translation services are DeepL and LibreTranslate. The supported command line tool is [Argos Translate](https://github.com/argosopentech/argos-translate). @@ -1216,3 +1223,12 @@ Translations are available at `/api/v1/statuses/:id/translations/:language`, whe - `:command_argos_translate` - command for `argos-translate`. Can be the command if it's in your PATH, or the full path to the file (default: `argos-translate`). - `:command_argospm` - command for `argospm`. Can be the command if it's in your PATH, or the full path to the file (default: `argospm`). - `:strip_html` - Strip html from the post before translating it (default: `true`). + +## Oban Web + +The built-in Oban Web dashboard grants all administrators access to look at and modify the instance’s job queue. +To enable or disable it the following setting can be set to `true` or `false` respectively: + +``` +config :oban_met, autostart: false +``` diff --git a/docs/docs/configuration/howto_database_config.md b/docs/docs/configuration/howto_database_config.md index a597dae5c..0c1e4a2e6 100644 --- a/docs/docs/configuration/howto_database_config.md +++ b/docs/docs/configuration/howto_database_config.md @@ -7,73 +7,73 @@ The configuration of Akkoma (and Pleroma) has traditionally been managed with a 1. Run the mix task to migrate to the database. - **Source:** + **Source:** - ``` - $ mix pleroma.config migrate_to_db - ``` + ``` + $ mix pleroma.config migrate_to_db + ``` - or + or - **OTP:** + **OTP:** - *Note: OTP users need Akkoma to be running for `pleroma_ctl` commands to work* + *Note: OTP users need Akkoma to be running for `pleroma_ctl` commands to work* - ``` - $ ./bin/pleroma_ctl config migrate_to_db - ``` + ``` + $ ./bin/pleroma_ctl config migrate_to_db + ``` - ``` - Migrating settings from file: /home/pleroma/config/dev.secret.exs + ``` + Migrating settings from file: /home/pleroma/config/dev.secret.exs - Settings for key instance migrated. - Settings for group :pleroma migrated. - ``` + Settings for key instance migrated. + Settings for group :pleroma migrated. + ``` 2. It is recommended to backup your config file now. - ``` - cp config/dev.secret.exs config/dev.secret.exs.orig - ``` + ``` + cp config/dev.secret.exs config/dev.secret.exs.orig + ``` 3. Edit your Akkoma config to enable database configuration: - ``` - config :pleroma, configurable_from_database: true - ``` + ``` + config :pleroma, configurable_from_database: true + ``` 4. ⚠️ **THIS IS NOT REQUIRED** ⚠️ - Now you can edit your config file and strip it down to the only settings which are not possible to control in the database. e.g., the Postgres (Repo) and webserver (Endpoint) settings cannot be controlled in the database because the application needs the settings to start up and access the database. + Now you can edit your config file and strip it down to the only settings which are not possible to control in the database. e.g., the Postgres (Repo) and webserver (Endpoint) settings cannot be controlled in the database because the application needs the settings to start up and access the database. - Any settings in the database will override those in the config file, but you may find it less confusing if the setting is only declared in one place. + Any settings in the database will override those in the config file, but you may find it less confusing if the setting is only declared in one place. - A non-exhaustive list of settings that are only possible in the config file include the following: + A non-exhaustive list of settings that are only possible in the config file include the following: - * config :pleroma, Pleroma.Web.Endpoint - * config :pleroma, Pleroma.Repo - * config :pleroma, configurable\_from\_database - * config :pleroma, :database, rum_enabled - * config :pleroma, :database, pgroonga_enabled - * config :pleroma, :connections_pool + * config :pleroma, Pleroma.Web.Endpoint + * config :pleroma, Pleroma.Repo + * config :pleroma, configurable\_from\_database + * config :pleroma, :database, rum_enabled + * config :pleroma, :database, pgroonga_enabled + * config :pleroma, :connections_pool - Here is an example of a server config stripped down after migration: + Here is an example of a server config stripped down after migration: - ``` - use Mix.Config + ``` + use Mix.Config - config :pleroma, Pleroma.Web.Endpoint, - url: [host: "cool.pleroma.site", scheme: "https", port: 443] + config :pleroma, Pleroma.Web.Endpoint, + url: [host: "cool.pleroma.site", scheme: "https", port: 443] - config :pleroma, Pleroma.Repo, - adapter: Ecto.Adapters.Postgres, - username: "akkoma", - password: "MySecretPassword", - database: "akkoma_prod", - hostname: "localhost" + config :pleroma, Pleroma.Repo, + adapter: Ecto.Adapters.Postgres, + username: "akkoma", + password: "MySecretPassword", + database: "akkoma_prod", + hostname: "localhost" - config :pleroma, configurable_from_database: true - ``` + config :pleroma, configurable_from_database: true + ``` 5. Restart your instance and you can now access the Settings tab in admin-fe. @@ -82,28 +82,28 @@ The configuration of Akkoma (and Pleroma) has traditionally been managed with a 1. Run the mix task to migrate back from the database. You'll receive some debugging output and a few messages informing you of what happened. - **Source:** + **Source:** - ``` - $ mix pleroma.config migrate_from_db - ``` + ``` + $ mix pleroma.config migrate_from_db + ``` - or + or - **OTP:** + **OTP:** - ``` - $ ./bin/pleroma_ctl config migrate_from_db - ``` + ``` + $ ./bin/pleroma_ctl config migrate_from_db + ``` - ``` - 10:26:30.593 [debug] QUERY OK source="config" db=9.8ms decode=1.2ms queue=26.0ms idle=0.0ms - SELECT c0."id", c0."key", c0."group", c0."value", c0."inserted_at", c0."updated_at" FROM "config" AS c0 [] + ``` + 10:26:30.593 [debug] QUERY OK source="config" db=9.8ms decode=1.2ms queue=26.0ms idle=0.0ms + SELECT c0."id", c0."key", c0."group", c0."value", c0."inserted_at", c0."updated_at" FROM "config" AS c0 [] - 10:26:30.659 [debug] QUERY OK source="config" db=1.1ms idle=80.7ms - SELECT c0."id", c0."key", c0."group", c0."value", c0."inserted_at", c0."updated_at" FROM "config" AS c0 [] - Database configuration settings have been saved to config/dev.exported_from_db.secret.exs - ``` + 10:26:30.659 [debug] QUERY OK source="config" db=1.1ms idle=80.7ms + SELECT c0."id", c0."key", c0."group", c0."value", c0."inserted_at", c0."updated_at" FROM "config" AS c0 [] + Database configuration settings have been saved to config/dev.exported_from_db.secret.exs + ``` 2. Remove `config :pleroma, configurable_from_database: true` from your config. The in-database configuration still exists, but it will not be used. Future migrations will erase the database config before importing your config file again. diff --git a/docs/docs/configuration/optimisation/general.md b/docs/docs/configuration/optimisation/general.md new file mode 100644 index 000000000..bcf35ba26 --- /dev/null +++ b/docs/docs/configuration/optimisation/general.md @@ -0,0 +1,48 @@ +# General Performance and Optimisation Notes + +# Oban Web + +The built-in Oban Web dashboard has a seemingly constant'ish overhead +irrelevant to large instances but potentially +noticeable for small instances on low power systems. +Thus if the latter applies to your case, you might want to disable it; +see [the cheatsheet](../cheatsheet.md#oban-web). + +# Relays + +Subscribing to relays exposes your instance to a high volume flood of incoming activities. +This does not just incur the cost of processing those activities themselves, but typically +each activity may trigger additional work, like fetching ancestors and child posts to +complete the thread, refreshing user profiles, etc. +Furthermore the larger the count of activities and objects in your database the costlier +all database operations on these (highly important) tables get. + +Carefully consider whether this is worth the cost +and if you experience performance issues unsubscribe from relays. + +Regularly pruning old remote posts and orphaned activities is also especially important +when following relays or just having unfollowed relays for performance reasons. + +# Pruning old remote data + +Over time your instance accumulates more and more remote data, mainly in form of posts and activities. +Chances are you and your local users do not actually care for the vast majority of those. +Consider regularly *(frequency highly dependent on your individual setup)* pruning such old and irrelevant remote data; see +[the corresponding `mix` tasks](../../../administration/CLI_tasks/database#prune-old-remote-posts-from-the-database). + +# Database Maintenance + +Akkoma’s performance is highly dependent on and often bottle-necked by the database. +Taking good care of it pays off! +See the dedicated [PostgreSQL page](../postgresql.md). + +# HTTP Request Cache + +If your instance is frequently getting _many_ `GET` requests from external +actors *(i.e. everyone except logged-in local users)* an additional +*(Akkoma already has some caching built-in and so might your reverse proxy)* +caching layer as described in the [Varnish Cache guide](varnish_cache.md) +might help alleviate the impact. + +If this condition does **not** hold though, +setting up such a cache likely only worsens latency and wastes memory. diff --git a/docs/docs/development/API/admin_api.md b/docs/docs/development/API/admin_api.md index ff1e30f4e..70a2e3835 100644 --- a/docs/docs/development/API/admin_api.md +++ b/docs/docs/development/API/admin_api.md @@ -433,7 +433,7 @@ Response: * On success: URL of the unfollowed relay ```json -{"https://example.com/relay"} +"https://example.com/relay" ``` ## `POST /api/v1/pleroma/admin/users/invite_token` @@ -1173,20 +1173,23 @@ Loads JSON generated from `config/descriptions.exs`. - Response: ```json -[ - { - "id": 1234, - "data": { - "actor": { - "id": 1, - "nickname": "lain" +{ + "items": [ + { + "id": 1234, + "data": { + "actor": { + "id": 1, + "nickname": "lain" + }, + "action": "relay_follow" }, - "action": "relay_follow" - }, - "time": 1502812026, // timestamp - "message": "[2017-08-15 15:47:06] @nick0 followed relay: https://example.org/relay" // log message - } -] + "time": 1502812026, // timestamp + "message": "[2017-08-15 15:47:06] @nick0 followed relay: https://example.org/relay" // log message + } + ], + "total": 1 +} ``` ## `POST /api/v1/pleroma/admin/reload_emoji` @@ -1215,24 +1218,10 @@ Loads JSON generated from `config/descriptions.exs`. ## `GET /api/v1/pleroma/admin/stats` -### Stats +**DEPRECATED; DO NOT USE**!! -- Query Params: - - *optional* `instance`: **string** instance hostname (without protocol) to get stats for -- Example: `https://mypleroma.org/api/v1/pleroma/admin/stats?instance=lain.com` - -- Response: - -```json -{ - "status_visibility": { - "direct": 739, - "private": 9, - "public": 17, - "unlisted": 14 - } -} -``` +Returned information is only stubbed out. +The endpoint will be removed entirely in an upcoming release. ## `GET /api/v1/pleroma/admin/oauth_app` diff --git a/docs/docs/development/API/differences_in_mastoapi_responses.md b/docs/docs/development/API/differences_in_mastoapi_responses.md index 9183468d2..138353e9d 100644 --- a/docs/docs/development/API/differences_in_mastoapi_responses.md +++ b/docs/docs/development/API/differences_in_mastoapi_responses.md @@ -32,7 +32,7 @@ Home, public, hashtag & list timelines further accept: ## Statuses -- `visibility`: has additional possible values `list` and `local` (for local-only statuses) +- `visibility`: has additional possible value `local` (for local-only statuses) - `emoji_reactions`: additional field since Akkoma 3.2.0; identical to `pleroma/emoji_reactions` Has these additional fields under the `pleroma` object: @@ -193,6 +193,7 @@ Accepts additional parameters: - `exclude_visibilities`: will exclude the notifications for activities with the given visibilities. The parameter accepts an array of visibility types (`public`, `unlisted`, `private`, `direct`). Usage example: `GET /api/v1/notifications?exclude_visibilities[]=direct&exclude_visibilities[]=private`. - `include_types`: will include the notifications for activities with the given types. The parameter accepts an array of types (`mention`, `follow`, `reblog`, `favourite`, `move`, `pleroma:emoji_reaction`, `pleroma:report`). Usage example: `GET /api/v1/notifications?include_types[]=mention&include_types[]=reblog`. + **Deprecated:** replaced by `types` which is equivalent but (by now) also supported by vanilla Mastodon. ## DELETE `/api/v1/notifications/destroy_multiple` @@ -215,7 +216,6 @@ Additional parameters can be added to the JSON body/Form data: - `to`: A list of nicknames (like `admin@otp.akkoma.dev` or `admin` on the local server) that will be used to determine who is going to be addressed by this post. Using this will disable the implicit addressing by mentioned names in the `status` body, only the people in the `to` list will be addressed. The normal rules for post visibility are not affected by this and will still apply. - `visibility`: string, besides standard MastoAPI values (`direct`, `private`, `unlisted`, `local` or `public`) it can be used to address a List by setting it to `list:LIST_ID`. - `expires_in`: The number of seconds the posted activity should expire in. When a posted activity expires it will be deleted from the server, and a delete request for it will be federated. This needs to be longer than an hour. -- `in_reply_to_conversation_id`: Will reply to a given conversation, addressing only the people who are part of the recipient set of that conversation. Sets the visibility to `direct`. ## GET `/api/v1/statuses` diff --git a/docs/docs/development/API/pleroma_api.md b/docs/docs/development/API/pleroma_api.md index a34948878..8002b233b 100644 --- a/docs/docs/development/API/pleroma_api.md +++ b/docs/docs/development/API/pleroma_api.md @@ -376,13 +376,8 @@ See [Admin-API](admin_api.md) Pleroma Conversations have the same general structure that Mastodon Conversations have. The behavior differs in the following ways when using these endpoints: -1. Pleroma Conversations never add or remove recipients, unless explicitly changed by the user. +1. Pleroma Conversations never add or remove recipients (`accounts` key), unless explicitly changed by the user. 2. Pleroma Conversations statuses can be requested by Conversation id. -3. Pleroma Conversations can be replied to. - -Conversations have the additional field `recipients` under the `pleroma` key. This holds a list of all the accounts that will receive a message in this conversation. - -The status posting endpoint takes an additional parameter, `in_reply_to_conversation_id`, which, when set, will set the visiblity to direct and address only the people who are the recipients of that Conversation. ⚠ Conversation IDs can be found in direct messages with the `pleroma.direct_conversation_id` key, do not confuse it with `pleroma.conversation_id`. diff --git a/docs/docs/development/ap_extensions.md b/docs/docs/development/ap_extensions.md index a5e3d0186..fd3794f70 100644 --- a/docs/docs/development/ap_extensions.md +++ b/docs/docs/development/ap_extensions.md @@ -267,17 +267,33 @@ special meaning to the potential local-scope identifier. 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. +## List post scope + +Messages originally addressed to a custom list will contain +a `listMessage` field with an unresolvable pseudo ActivityPub id. + +!!! note + The concept did not work out too well in practice with even remote servers + recognising the `listMessage` extension being unaware of the state of the + list and resulting weird desyncs in thread display and handling between + servers. + As it was it also never found its way in any known clients or frontends. + + A more consistent superset of what this was able to actually do + can be achieved without ActivityPub extensions by explicitly addressing + all intended participants without inline mentions. + While true federated and moderated "lists" or "groups" + will need more work and a different approach. + + Thus suport for it was removed and it is recommended + to not create any new implementation of it. + ## Actor endpoints The following endpoints used to be present: diff --git a/docs/requirements.txt b/docs/requirements.txt index d67bbf65f..f8aff2121 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,26 +1,26 @@ -certifi==2022.9.24 -charset-normalizer==2.1.1 -click==8.1.3 -ghp-import==2.1.0 -idna==3.4 -importlib-metadata==4.12.0 -Jinja2==3.1.2 -Markdown==3.3.7 -markdown-include==0.7.0 -MarkupSafe==2.1.1 -mergedeep==1.3.4 -mkdocs==1.4.2 -mkdocs-material==8.5.9 -mkdocs-material-extensions==1.1 -packaging==21.3 -Pygments==2.13.0 -pymdown-extensions==9.8 -pyparsing==3.0.9 -python-dateutil==2.8.2 -PyYAML==6.0 -pyyaml_env_tag==0.1 -requests==2.28.1 -six==1.16.0 -urllib3==1.26.12 -watchdog==2.1.9 -zipp==3.8.0 +certifi +charset-normalizer +click +ghp-import +idna +importlib-metadata +Jinja2 +Markdown +markdown-include +MarkupSafe +mergedeep +mkdocs +mkdocs-material +mkdocs-material-extensions +packaging +Pygments +pymdown-extensions +pyparsing +python-dateutil +PyYAML +pyyaml_env_tag +requests +six +urllib3 +watchdog +zipp diff --git a/installation/grafana_dashboard.json b/installation/grafana_dashboard.json new file mode 100644 index 000000000..052df5de6 --- /dev/null +++ b/installation/grafana_dashboard.json @@ -0,0 +1,3603 @@ +{ + "__inputs": [ + { + "name": "DATASOURCE", + "label": "datasource", + "description": "", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + }, + { + "name": "DS_EXPRESSION", + "label": "Expression", + "description": "", + "type": "datasource", + "pluginId": "__expr__" + }, + { + "name": "INSTANCE", + "label": "instance", + "description": "Domain and port of the monitored Akkoma instance in the format domain:port", + "type": "constant", + "value": "" + }, + { + "name": "SCRAPE_JOB", + "label": "scrape_job", + "description": "Name of the scrape job as configured in victoriametrics’ or prometheus’ config", + "type": "constant", + "value": "" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "datasource", + "id": "__expr__", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "bargauge", + "name": "Bar gauge", + "version": "" + }, + { + "type": "panel", + "id": "gauge", + "name": "Gauge", + "version": "" + }, + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "12.1.0" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "state-timeline", + "name": "State timeline", + "version": "" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "halp", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "displayName": "${__field.displayName}", + "fieldMinMax": false, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "light-yellow", + "value": 0 + }, + { + "color": "orange", + "value": 5 + }, + { + "color": "red", + "value": 25 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 9, + "x": 0, + "y": 0 + }, + "id": 29, + "options": { + "displayMode": "gradient", + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "maxVizHeight": 300, + "minVizHeight": 16, + "minVizWidth": 8, + "namePlacement": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "text": {}, + "valueMode": "color" + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "increase(akkoma_ap_delivery_fail_final{job=\"${SCRAPE_JOB}\", instance=\"${INSTANCE}\"}[$__interval]) != 0", + "format": "heatmap", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "interval": "", + "legendFormat": "{{target}} - {{reason}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "timeFrom": "7d", + "title": "Failed AP deliveries", + "transformations": [ + { + "id": "joinByField", + "options": { + "byField": "Time", + "mode": "outer" + } + }, + { + "id": "organize", + "options": { + "excludeByName": {}, + "includeByName": {}, + "indexByName": {}, + "orderBy": [ + { + "desc": false, + "name": "target", + "type": "label" + }, + { + "desc": false, + "name": "reason", + "type": "label" + } + ], + "orderByMode": "auto", + "renameByName": {} + } + } + ], + "type": "bargauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "dark-red", + "value": 0 + }, + { + "color": "orange", + "value": 0.5 + }, + { + "color": "semi-dark-yellow", + "value": 0.8 + }, + { + "color": "dark-green", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 9, + "y": 0 + }, + "id": 7, + "options": { + "alignValue": "left", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "never", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "avg_over_time(up{instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}[5m])\n# or if you want to count downtime of the Prometheus scraper as Akkoma downtime too,\n# e.g. because both services are on the same host, you can use the following with VictoriaMetrics:\n #avg_over_time((up{job=\"${SCRAPE_JOB}\", instance=\"${INSTANCE}\"} default 0.0)[5m])\n# or this more complex query for plain Prometheus:\n# avg_over_time(sum(up{job=\"${SCRAPE_JOB}\", instance=\"${INSTANCE}\"} or 0.0)[5m])", + "format": "heatmap", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Up", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "type": "state-timeline" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "displayName": "Uptime", + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 2, + "x": 13, + "y": 0 + }, + "id": 13, + "maxDataPoints": 700, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "editorMode": "code", + "expr": "up{job=\"${SCRAPE_JOB}\", instance=\"${INSTANCE}\"}\n# or if you want to count downtime of the Prometheus scraper as Akkoma downtime too,\n# e.g. because both services are on the same host, you can use:\n# sum(up{job=\"${SCRAPE_JOB}\", instance=\"${INSTANCE}\"} or 0.0)\n# When using VictoriaMetrics-specific this simplifies to:\n# up{job=\"${SCRAPE_JOB}\", instance=\"${INSTANCE}\"} default 0.0", + "hide": true, + "instant": false, + "legendFormat": "raw_up", + "range": true, + "refId": "raw" + }, + { + "datasource": { + "type": "__expr__", + "uid": "${DS_EXPRESSION}" + }, + "expression": "raw", + "hide": false, + "reducer": "mean", + "refId": "mean", + "settings": { + "mode": "replaceNN", + "replaceWithValue": 0 + }, + "type": "reduce" + } + ], + "timeFrom": "now-7d", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "pleroma_local_users_total" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "left" + }, + { + "id": "custom.axisLabel", + "value": "Local Users" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "pleroma_local_statuses_total" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "custom.axisLabel", + "value": "Local Posts (excl. priv.)" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 15, + "y": 0 + }, + "id": 6, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "pleroma_local_users_total{instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Users", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "exemplar": false, + "expr": "pleroma_local_statuses_total{instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}", + "format": "time_series", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Posts", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "Local Resources", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Users" + }, + "properties": [ + { + "id": "custom.axisLabel" + }, + { + "id": "custom.axisPlacement", + "value": "left" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Domains" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "custom.axisLabel" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 4, + "x": 20, + "y": 0 + }, + "id": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "pleroma_remote_users_total{instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Users", + "range": true, + "refId": "remote_user_count", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "exemplar": false, + "expr": "pleroma_domains_total{instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}", + "format": "time_series", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Domains", + "range": true, + "refId": "remote_domain_count", + "useBackend": false + } + ], + "title": "Remote Resrc. Estimates", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "displayName": "Run Queue", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "yellow", + "value": 15 + }, + { + "color": "red", + "value": 25 + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byFrameRefID", + "options": "cpuqueue" + }, + "properties": [ + { + "id": "displayName", + "value": "VM CPU Queue" + } + ] + }, + { + "matcher": { + "id": "byFrameRefID", + "options": "mem" + }, + "properties": [ + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 500000000 + } + ] + } + }, + { + "id": "unit", + "value": "bytes" + }, + { + "id": "displayName", + "value": "Memory" + } + ] + }, + { + "matcher": { + "id": "byFrameRefID", + "options": "ioqueue" + }, + "properties": [ + { + "id": "displayName", + "value": "VM IO Queue" + } + ] + } + ] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 9, + "y": 4 + }, + "id": 8, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto", + "text": { + "titleSize": 10 + } + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "exemplar": false, + "expr": "rate(vm_memory_total_psum{instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}[$__interval])", + "fullMetaSearch": false, + "hide": true, + "includeNullMetadata": true, + "instant": true, + "legendFormat": "__auto", + "range": false, + "refId": "memsum", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "exemplar": false, + "expr": "rate(vm_memory_total_pcount{instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}[$__interval])", + "fullMetaSearch": false, + "hide": true, + "includeNullMetadata": true, + "instant": true, + "legendFormat": "__auto", + "range": false, + "refId": "memcnt", + "useBackend": false + }, + { + "datasource": { + "type": "__expr__", + "uid": "${DS_EXPRESSION}" + }, + "expression": "${memsum} / ${memcnt}", + "hide": false, + "refId": "mem", + "type": "math" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "exemplar": false, + "expr": "rate(vm_total_run_queue_lengths_cpu_psum{instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}[$__rate_interval])", + "fullMetaSearch": false, + "hide": true, + "includeNullMetadata": true, + "instant": true, + "legendFormat": "__auto", + "range": false, + "refId": "cpusum", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "exemplar": false, + "expr": "rate(vm_total_run_queue_lengths_cpu_pcount{instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}[$__rate_interval])", + "fullMetaSearch": false, + "hide": true, + "includeNullMetadata": true, + "instant": true, + "legendFormat": "__auto", + "range": false, + "refId": "cpucnt", + "useBackend": false + }, + { + "datasource": { + "type": "__expr__", + "uid": "${DS_EXPRESSION}" + }, + "expression": "${cpusum} / ${cpucnt}", + "hide": false, + "refId": "cpuqueue", + "type": "math" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "exemplar": false, + "expr": "rate(vm_total_run_queue_lengths_io_fsum_psum{instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}[$__rate_interval])", + "fullMetaSearch": false, + "hide": true, + "includeNullMetadata": true, + "instant": true, + "legendFormat": "__auto", + "range": false, + "refId": "iosum", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "exemplar": false, + "expr": "rate(vm_total_run_queue_lengths_io_fsum_pcount{instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}[$__rate_interval])", + "fullMetaSearch": false, + "hide": true, + "includeNullMetadata": true, + "instant": true, + "legendFormat": "__auto", + "range": false, + "refId": "iocnt", + "useBackend": false + }, + { + "datasource": { + "type": "__expr__", + "uid": "${DS_EXPRESSION}" + }, + "expression": "${iosum} / ${iocnt}", + "hide": false, + "refId": "ioqueue", + "type": "math" + } + ], + "title": "BEAM System Load", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.9, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "stepBefore", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Completions: PublisherWorker" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Completions: ReceiverWorker" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Completions: RemoteFetcherWorker" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Exceptions: PublisherWorker" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Exceptions: ReceiverWorker" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Exceptions: RemoteFetcherWorker" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 10, + "x": 0, + "y": 8 + }, + "id": 4, + "interval": "1m", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "increase(oban_job_completion_count{worker=~\"Pleroma.Workers.RemoteFetcherWorker|Pleroma.Workers.ReceiverWorker|Pleroma.Workers.PublisherWorker\", instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}[$__interval])", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Completions: {{worker}}", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "increase(oban_job_exception_count{worker=~\"Pleroma.Workers.RemoteFetcherWorker|Pleroma.Workers.ReceiverWorker|Pleroma.Workers.PublisherWorker\", instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}[$__interval])", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Exceptions: {{worker}}", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "Receive, Fetch, Publish Events", + "transformations": [ + { + "id": "renameByRegex", + "options": { + "regex": "([^:]+: )Pleroma\\.Workers\\.(.*)", + "renamePattern": "$1$2" + } + } + ], + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 70, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [ + { + "matcher": { + "id": "byFrameRefID", + "options": "totalavg" + }, + "properties": [ + { + "id": "custom.stacking", + "value": { + "group": "A", + "mode": "none" + } + }, + { + "id": "custom.fillOpacity", + "value": 0 + }, + { + "id": "displayName", + "value": "Average Total Time per Query" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 10, + "y": 8 + }, + "id": 9, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "histogram_quantile(0.95, rate(pleroma_repo_query_queue_time_bucket{instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}[$__rate_interval]))", + "fullMetaSearch": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "Queue", + "range": true, + "refId": "queue", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "histogram_quantile(0.95, rate(pleroma_repo_query_decode_time_fdist_bucket{instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}[$__rate_interval]))", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "Decode", + "range": true, + "refId": "decode", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "histogram_quantile(0.95, rate(pleroma_repo_query_query_time_fdist_bucket{instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}[$__rate_interval]))", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "Query", + "range": true, + "refId": "query", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(pleroma_repo_query_total_time_sum{instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}[$__rate_interval])", + "fullMetaSearch": false, + "hide": true, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "totalsum", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(pleroma_repo_query_total_time_count{instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}[$__rate_interval])", + "fullMetaSearch": false, + "hide": true, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "totalcnt", + "useBackend": false + }, + { + "datasource": { + "type": "__expr__", + "uid": "${DS_EXPRESSION}" + }, + "expression": "${totalsum} / ${totalcnt}", + "hide": false, + "refId": "totalavg", + "type": "math" + } + ], + "title": "Single DB Query (95% quantiles)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 70, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [ + { + "matcher": { + "id": "byFrameRefID", + "options": "total" + }, + "properties": [ + { + "id": "custom.stacking", + "value": { + "group": "A", + "mode": "none" + } + }, + { + "id": "custom.fillOpacity", + "value": 0 + }, + { + "id": "displayName", + "value": "Total Time" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 18, + "y": 8 + }, + "id": 10, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(pleroma_repo_query_queue_time_sum{instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}[$__rate_interval])", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "Queue", + "range": true, + "refId": "queue", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(pleroma_repo_query_decode_time_fdist_sum{instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}[$__rate_interval])", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "Decode", + "range": true, + "refId": "decode", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(pleroma_repo_query_query_time_fdist_sum{instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}[$__rate_interval])", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "Query", + "range": true, + "refId": "query", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(pleroma_repo_query_total_time_sum{instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}[$__rate_interval])", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "total", + "useBackend": false + } + ], + "title": "Total DB Query Times per Second", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 0, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "200" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "401" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "super-light-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "410" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 10, + "x": 0, + "y": 16 + }, + "id": 3, + "interval": "1m", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "increase(tesla_request_completed_count{job=\"${SCRAPE_JOB}\", instance=\"${INSTANCE}\"}[$__interval])", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{response_code}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Outgoing Requests", + "transformations": [ + { + "id": "renameByRegex", + "options": { + "regex": "\\{.*", + "renamePattern": "unknown" + } + } + ], + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "green", + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.8, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 0, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 4, + "x": 10, + "y": 16 + }, + "id": 17, + "interval": "1m", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "increase(oban_job_completion_count{worker!~\"Pleroma.Workers.ReceiverWorker|Pleroma.Workers.PublisherWorker|Pleroma.Workers.RemoteFetcherWorker\", instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}[$__interval])", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Completion: {{worker}}", + "range": true, + "refId": "completion", + "useBackend": false + } + ], + "title": "Job Completions", + "transformations": [ + { + "id": "renameByRegex", + "options": { + "regex": "([^:]+: )[^.]+\\.[^.]+\\.(.*)", + "renamePattern": "$1$2" + } + } + ], + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "green", + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 1, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 0, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 4, + "x": 14, + "y": 16 + }, + "id": 12, + "interval": "1m", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "increase(oban_job_exception_count{worker!~\"Pleroma.Workers.ReceiverWorker|Pleroma.Workers.RemoteFetcherWorker|Pleroma.Workers.PublisherWorker\", instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}[$__interval])", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Exception: {{worker}}", + "range": true, + "refId": "exception", + "useBackend": false + } + ], + "title": "Job Exceptions", + "transformations": [ + { + "id": "renameByRegex", + "options": { + "regex": "([^:]+: )[^.]+\\.[^.]+\\.(.*)", + "renamePattern": "$1$2" + } + } + ], + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 66, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 0, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 18, + "y": 16 + }, + "id": 14, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(oban_job_completion_sum{instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}[$__rate_interval])", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{worker}} (c)", + "range": true, + "refId": "completionsum", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(oban_job_exception_sum{instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}[$__rate_interval])", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{worker}} (e)", + "range": true, + "refId": "exceptionsum", + "useBackend": false + } + ], + "title": "Total Job Proc. Time per Second", + "transformations": [ + { + "id": "renameByRegex", + "options": { + "regex": "[^.]+\\.[^.]+\\.(.+)", + "renamePattern": "$1" + } + } + ], + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 0, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [ + { + "matcher": { + "id": "byFrameRefID", + "options": "sum" + }, + "properties": [ + { + "id": "custom.stacking", + "value": { + "group": "A", + "mode": "none" + } + }, + { + "id": "custom.fillOpacity", + "value": 0 + }, + { + "id": "custom.lineWidth", + "value": 1 + } + ] + } + ] + }, + "gridPos": { + "h": 10, + "w": 10, + "x": 0, + "y": 23 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "timezone": [ + "utc" + ], + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(phoenix_router_dispatch_stop_duration_sum{job=\"${SCRAPE_JOB}\", instance=\"${INSTANCE}\"}[$__rate_interval])", + "fullMetaSearch": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "{{route}}", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "sum(rate(phoenix_router_dispatch_stop_duration_sum{job=\"${SCRAPE_JOB}\", instance=\"${INSTANCE}\"}[$__rate_interval]))", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "TOTAL", + "range": true, + "refId": "sum", + "useBackend": false + } + ], + "title": "Inbound HTTP Proc. Time per Second", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "description": "Jobs eligible for immediate execution, but not yet started. Most likely they’re held back because the queue is already saturated and there’s no free slot.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic-by-name" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.9, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 0, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 7, + "x": 10, + "y": 25 + }, + "id": 31, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "disableTextWrap": false, + "editorMode": "builder", + "expr": "akkoma_job_queue_available", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "{{queue_name}}", + "range": true, + "refId": "A", + "useBackend": false, + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + } + } + ], + "title": "Pending Jobs", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "description": "Jobs intentionally held back until a later start data", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic-by-name" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.9, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 0, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 7, + "x": 17, + "y": 25 + }, + "id": 30, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "disableTextWrap": false, + "editorMode": "builder", + "expr": "akkoma_job_queue_scheduled", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "{{queue_name}}", + "range": true, + "refId": "A", + "useBackend": false, + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + } + } + ], + "title": "Scheduled Jobs", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 0, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "200" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 10, + "w": 10, + "x": 0, + "y": 33 + }, + "id": 16, + "interval": "1m", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "increase(phoenix_router_dispatch_stop_duration_count{job=\"${SCRAPE_JOB}\", instance=\"${INSTANCE}\"}[$__interval])", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{route}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Inbound Requests", + "transformations": [ + { + "id": "renameByRegex", + "options": { + "regex": "\\{.*", + "renamePattern": "unknown" + } + } + ], + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 66, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "BEAM VM memory consumption", + "fieldMinMax": false, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "yellow", + "value": 50000000 + }, + { + "color": "red", + "value": 70000000 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 10, + "x": 10, + "y": 33 + }, + "id": 15, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(vm_memory_total_psum{instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}[$__interval])", + "fullMetaSearch": false, + "hide": true, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "memsum", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(vm_memory_total_pcount{instance=\"${INSTANCE}\", job=\"${SCRAPE_JOB}\"}[$__interval])", + "fullMetaSearch": false, + "hide": true, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "memcnt", + "useBackend": false + }, + { + "datasource": { + "type": "__expr__", + "uid": "${DS_EXPRESSION}" + }, + "expression": "${memsum} / ${memcnt}", + "hide": false, + "refId": "mem", + "type": "math" + } + ], + "title": "BEAM VM Memory Usage Over Time", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "description": "Includes network traffic, but also communication to local non-elixir components like fast_html workers", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.8, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 0, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 20, + "y": 33 + }, + "id": 26, + "interval": "1m", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "increase(vm_portio_in_total{job=\"${SCRAPE_JOB}\", instance=\"${INSTANCE}\"}[$__interval])", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "in", + "range": true, + "refId": "portio_in", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "editorMode": "code", + "expr": "increase(vm_portio_out_total{job=\"${SCRAPE_JOB}\", instance=\"${INSTANCE}\"}[$__interval])", + "hide": false, + "instant": false, + "legendFormat": "out", + "range": true, + "refId": "portio_out" + } + ], + "title": "VM Port IO Traffic", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "description": "For improved insight this is tracks federation-related jobs, i.e. RFP (Receive, Fetch, Publish) jobs, separately from everything else", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "green", + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.8, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Completions" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Exceptions" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "RFP Exceptions" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "semi-dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "RFP Completions" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 10, + "x": 10, + "y": 39 + }, + "id": 19, + "interval": "1m", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "avg_over_time((rate(sum(oban_job_completion_count{worker!~\"Pleroma.Workers.ReceiverWorker|Pleroma.Workers.PublisherWorker|Pleroma.Workers.RemoteFetcherWorker\", job=\"${SCRAPE_JOB}\", instance=\"${INSTANCE}\"})[1m]) or 0)[24h]) * 60", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Completions", + "range": true, + "refId": "completion", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "avg_over_time((rate(sum(oban_job_exception_count{worker!~\"Pleroma.Workers.ReceiverWorker|Pleroma.Workers.PublisherWorker|Pleroma.Workers.RemoteFetcherWorker\", job=\"${SCRAPE_JOB}\", instance=\"${INSTANCE}\"})[1m]) or 0)[24h]) * 60", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Exceptions", + "range": true, + "refId": "exceptions", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "avg_over_time((rate(sum(oban_job_completion_count{worker=~\"Pleroma.Workers.ReceiverWorker|Pleroma.Workers.PublisherWorker|Pleroma.Workers.RemoteFetcherWorker\", job=\"${SCRAPE_JOB}\", instance=\"${INSTANCE}\"})[1m]) or 0)[24h]) * 60 ", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "RFP Completions", + "range": true, + "refId": "completion_ap", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "avg_over_time((rate(sum(oban_job_exception_count{worker=~\"Pleroma.Workers.ReceiverWorker|Pleroma.Workers.PublisherWorker|Pleroma.Workers.RemoteFetcherWorker\", job=\"${SCRAPE_JOB}\", instance=\"${INSTANCE}\"})[1m]) or 0)[24h]) * 60 ", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "RFP Exceptions", + "range": true, + "refId": "exceptions_ap", + "useBackend": false + } + ], + "title": "Jobs Per Minute - 24h Average", + "transformations": [ + { + "id": "renameByRegex", + "options": { + "regex": "([^:]+: )[^.]+\\.[^.]+\\.(.*)", + "renamePattern": "$1$2" + } + } + ], + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 4, + "x": 20, + "y": 39 + }, + "id": 23, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "avg_over_time(sum(rate(phoenix_router_dispatch_stop_duration_count{job=\"${SCRAPE_JOB}\", instance=\"${INSTANCE}\"}[$__rate_interval]))[24h]) * 60", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": false, + "instant": false, + "interval": "", + "legendFormat": "Inbound Requests per Minute", + "range": true, + "refId": "outreq", + "useBackend": false + } + ], + "title": "Inbound Requests per Min. - 24h Avg", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "DB Total" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "DB Query" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byFrameRefID", + "options": "job time" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "semi-dark-purple", + "mode": "fixed" + } + }, + { + "id": "unit", + "value": "s" + } + ] + } + ] + }, + "gridPos": { + "h": 10, + "w": 10, + "x": 0, + "y": 43 + }, + "id": 21, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "avg_over_time(rate(pleroma_repo_query_query_time_fdist_sum{job=\"${SCRAPE_JOB}\", instance=\"${INSTANCE}\"}[$__rate_interval])[48h]) * 60\n# By default downtime is excluded from average; use below if it should count as zeroes instead (only on VictoriaMetrics)\n# avg_over_time(rate(pleroma_repo_query_query_time_fdist_sum{job=\"${SCRAPE_JOB}\", instance=\"${INSTANCE}\"}[$__rate_interval] default 0)[48h]) * 60", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "DB Query", + "range": true, + "refId": "db query", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "avg_over_time(rate(pleroma_repo_query_total_time_sum{job=\"${SCRAPE_JOB}\", instance=\"${INSTANCE}\"}[$__rate_interval])[48h]) * 60\n# By default downtime is excluded from average; use below if it should count as zeroes instead (only on VictoriaMetrics):\n# avg_over_time(rate(pleroma_repo_query_total_time_sum{job=\"${SCRAPE_JOB}\", instance=\"${INSTANCE}\"}[$__rate_interval] default 0)[48h]) * 60", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "DB Total", + "range": true, + "refId": "db total", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "avg_over_time((rate((sum(oban_job_completion_sum{job=\"${SCRAPE_JOB}\", instance=\"${INSTANCE}\"}))[$__rate_interval]))[48h]) * 60\n+ avg_over_time((rate((sum(oban_job_exception_sum{job=\"${SCRAPE_JOB}\", instance=\"${INSTANCE}\"}))[$__rate_interval]))[48h]) * 60\n# By default downtime is excluded from average; use below if it should count as zeroes instead (both VictoriaMetrics and Prometheus)\n#avg_over_time((rate((sum(oban_job_completion_sum{job=\"${SCRAPE_JOB}\", instance=\"${INSTANCE}\"}))[$__rate_interval]) or 0)[48h]) * 60\n#+ avg_over_time((rate((sum(oban_job_exception_sum{job=\"${SCRAPE_JOB}\", instance=\"${INSTANCE}\"}))[$__rate_interval]) or 0)[48h]) * 60", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Job Total", + "range": true, + "refId": "job time", + "useBackend": false + } + ], + "title": "DB and Job Time Per Min - 48h Avg", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 4, + "x": 20, + "y": 46 + }, + "id": 25, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "avg_over_time(sum(rate(tesla_request_completed_count{job=\"${SCRAPE_JOB}\", instance=\"${INSTANCE}\"}[$__rate_interval]))[24h]) * 60", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "Outgoing Request Count per Minute", + "range": true, + "refId": "outreq", + "useBackend": false + } + ], + "title": "Outgoing Requests per Min. - 24h Avg", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 10, + "x": 10, + "y": 48 + }, + "id": 22, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DATASOURCE}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "avg_over_time(sum(rate(phoenix_router_dispatch_stop_duration_sum{job=\"${SCRAPE_JOB}\", instance=\"${INSTANCE}\"}[$__rate_interval]))[24h]) * 60", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "HTTP Proc Time per Min", + "range": true, + "refId": "http_proc_time_avg", + "useBackend": false + } + ], + "title": "HTTP Proc Time per Minute - 24h Avg", + "type": "timeseries" + } + ], + "refresh": "5m", + "schemaVersion": 41, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-3h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "utc", + "title": "Akkoma Dashboard", + "uid": "edzowz85niznkc", + "version": 29, + "weekStart": "" +} diff --git a/installation/nginx/akkoma.nginx b/installation/nginx/akkoma.nginx index bdd5d0f8b..d931e8555 100644 --- a/installation/nginx/akkoma.nginx +++ b/installation/nginx/akkoma.nginx @@ -37,6 +37,15 @@ server { listen 80; listen [::]:80; + # for nginx versions < 1.25.1, you need to use a listen paramter instead + http2 on; + + # Uncomment to enable HTTP/3 suppport (only do this after getting a valid certificate) + # Requires nginx 1.25.0+ and ngx_http_v3_module enabled at build time + #listen 443 quic reuseport; + #listen [::]:443 quic reuseport; + #add_header Alt-Svc 'h3=":443"; ma=86400'; + # If you are not using Certbot, comment out the above and uncomment/edit the following # listen 443 ssl http2; # listen [::]:443 ssl http2; @@ -70,7 +79,7 @@ server { proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; - proxy_set_header Host $http_host; + proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; location ~ ^/(media|proxy) { @@ -91,6 +100,17 @@ server { listen 80; listen [::]:80; + # for nginx versions < 1.25.1, you need to use a listen paramter instead + http2 on; + + # Uncomment to enable HTTP/3 suppport (only do this after getting a valid certificate) + # Note: reuseport is not specified here as it's already used in the first server block, + # and can only be used once per ip/port pair. + # Requires nginx 1.25.0+ and ngx_http_v3_module enabled at build time + #listen 443 quic; + #listen [::]:443 quic; + #add_header Alt-Svc 'h3=":443"; ma=86400'; + # If you are not using certbot, comment the above and copy all the ssl # stuff from above into here. @@ -108,7 +128,7 @@ server { proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; - proxy_set_header Host $http_host; + proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; location ~ ^/(media|proxy) { diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex index c6d7f5521..105fce968 100644 --- a/lib/mix/pleroma.ex +++ b/lib/mix/pleroma.ex @@ -24,14 +24,9 @@ def start_pleroma do Pleroma.Application.limiters_setup() Application.put_env(:phoenix, :serve_endpoints, false, persistent: true) - proxy_url = Pleroma.Config.get([:http, :proxy_url]) - proxy = Pleroma.HTTP.AdapterHelper.format_proxy(proxy_url) - finch_config = - [:http, :adapter] - |> Pleroma.Config.get([]) - |> Pleroma.HTTP.AdapterHelper.maybe_add_proxy_pool(proxy) - |> Keyword.put(:name, MyFinch) + Pleroma.Config.get([:http, :adapter]) + |> Pleroma.HTTP.AdapterHelper.options() unless System.get_env("DEBUG") do Logger.remove_backend(:console) diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex index 589027a1e..1671333f8 100644 --- a/lib/mix/tasks/pleroma/database.ex +++ b/lib/mix/tasks/pleroma/database.ex @@ -340,6 +340,7 @@ def run(["prune_objects" | args]) do keep_non_public: :boolean, prune_orphaned_activities: :boolean, prune_pinned: :boolean, + fix_replies_count: :boolean, limit: :integer ] ) @@ -427,10 +428,15 @@ def run(["prune_objects" | args]) do AND hto.hashtag_id is NULL AND ufht.hashtag_id is NULL """ - |> Repo.query!() + |> Repo.query!([], timeout: :infinity) Logger.info("Deleted #{del_hashtags} no longer used hashtags...") + if Keyword.get(options, :fix_replies_count, true) do + Logger.info("Fixing reply counters...") + resync_replies_count() + end + if Keyword.get(options, :vacuum) do Logger.info("Starting vacuum...") Maintenance.vacuum("full") @@ -446,6 +452,7 @@ def run(["prune_task"]) do |> Pleroma.Workers.Cron.PruneDatabaseWorker.perform() end + # fixes wrong type of inlined like references for objects predating the inlined array def run(["fix_likes_collections"]) do start_pleroma() @@ -516,6 +523,60 @@ def run(["ensure_expiration"]) do |> Stream.run() end + def run(["resync_inlined_caches" | args]) do + {options, [], []} = + OptionParser.parse( + args, + strict: [ + replies_count: :boolean, + announcements: :boolean, + likes: :boolean, + reactions: :boolean + ] + ) + + start_pleroma() + + if Keyword.get(options, :replies_count, true) do + resync_replies_count() + end + + if Keyword.get(options, :announcements, true) do + resync_inlined_array("Announce", "announcement") + end + + if Keyword.get(options, :likes, true) do + resync_inlined_array("Like", "like") + end + + if Keyword.get(options, :reactions, true) do + resync_inlined_reactions() + end + end + + def run(["clean_inlined_replies"]) do + # The inlined replies array is not used after the initial processing + # when first receiving the object and only wastes space + start_pleroma() + + # We cannot check jsonb_typeof(array) and jsonb_array_length() in the same query + # since the checks do not short-circuit and NULL values will raise an error for the latter + has_replies = + Pleroma.Object + |> select([o], %{id: o.id}) + |> where([o], fragment("jsonb_typeof(?->'replies') = 'array'", o.data)) + + {update_cnt, _} = + Pleroma.Object + |> with_cte("arrays", as: ^has_replies) + |> join(:inner, [o], a in "arrays", on: o.id == a.id) + |> where([o, _a], fragment("jsonb_array_length(?->'replies') > 0", o.data)) + |> update(set: [data: fragment("jsonb_set(data, '{replies}', '[]'::jsonb)")]) + |> Pleroma.Repo.update_all([], timeout: :infinity) + + Logger.info("Emptied inlined replies lists from #{update_cnt} rows.") + end + def run(["set_text_search_config", tsconfig]) do start_pleroma() %{rows: [[tsc]]} = Ecto.Adapters.SQL.query!(Pleroma.Repo, "SHOW default_text_search_config;") @@ -623,4 +684,125 @@ def run(["rollback", version]) do shell_info(inspect(result)) end end + + defp resync_replies_count() do + public_str = Pleroma.Constants.as_public() + + ref = + Pleroma.Object + |> select([o], %{apid: fragment("?->>'inReplyTo'", o.data), cnt: count()}) + |> where( + [o], + fragment("?->>'type' <> 'Answer'", o.data) and + fragment("?->>'inReplyTo' IS NOT NULL", o.data) and + (fragment("?->'to' @> ?::jsonb", o.data, ^public_str) or + fragment("?->'cc' @> ?::jsonb", o.data, ^public_str)) + ) + |> group_by([o], fragment("?->>'inReplyTo'", o.data)) + + {update_cnt, _} = + Pleroma.Object + |> with_cte("ref", as: ^ref) + |> join(:inner, [o], r in "ref", on: fragment("?->>'id'", o.data) == r.apid) + |> where([o, r], fragment("(?->>'repliesCount')::bigint <> ?", o.data, r.cnt)) + |> update([o, r], + set: [data: fragment("jsonb_set(?, '{repliesCount}', to_jsonb(?))", o.data, r.cnt)] + ) + |> Pleroma.Repo.update_all([], timeout: :infinity) + + Logger.info("Fixed reply counter for #{update_cnt} objects.") + end + + defp resync_inlined_array(activity_type, basename) do + array_name = basename <> "s" + counter_name = basename <> "_count" + + ref = + Pleroma.Activity + |> select([a], %{ + apid: fragment("?->>'object'", a.data), + correct: fragment("to_jsonb(ARRAY_AGG(?->>'actor'))", a.data) + }) + |> where( + [a], + fragment("?->>'type' = ?", a.data, ^activity_type) and + fragment("?->>'id' IS NOT NULL", a.data) and + fragment("?->>'actor' IS NOT NULL", a.data) + ) + |> group_by([a], fragment("?->>'object'", a.data)) + + {update_cnt, _} = + Pleroma.Object + |> with_cte("ref", as: ^ref) + |> join(:inner, [o], r in "ref", on: fragment("?->>'id'", o.data) == r.apid) + |> where( + [o, r], + fragment("?->>'id' = ?", o.data, r.apid) and + not (fragment("? @> (?->?)", r.correct, o.data, ^array_name) and + fragment("? <@ (?->?)", r.correct, o.data, ^array_name)) + ) + |> update([o, r], + set: [ + data: + fragment( + "? || jsonb_build_object(?::text, jsonb_array_length(?::jsonb), ?::text, ?::jsonb)", + o.data, + ^counter_name, + r.correct, + ^array_name, + r.correct + ) + ] + ) + |> Pleroma.Repo.update_all([], timeout: :infinity) + + Logger.info("Fixed inlined #{basename} array and counter for #{update_cnt} objects.") + end + + defp resync_inlined_reactions() do + expanded_ref = + Pleroma.Activity + |> select([a], %{ + apid: selected_as(fragment("?->>'object'", a.data), :apid), + emoji_name: selected_as(fragment("TRIM(?->>'content', ':')", a.data), :emoji_name), + actors: fragment("ARRAY_AGG(DISTINCT ?->>'actor')", a.data), + url: selected_as(fragment("?#>>'{tag,0,icon,url}'", a.data), :url) + }) + |> where( + [a], + fragment("?->>'type' = 'EmojiReact'", a.data) and + fragment("?->>'actor' IS NOT NULL", a.data) and + fragment("TRIM(?->>'content', ':') IS NOT NULL", a.data) + ) + |> group_by([_], [selected_as(:apid), selected_as(:emoji_name), selected_as(:url)]) + + ref = + from(e in subquery(expanded_ref)) + |> select([e], %{ + apid: e.apid, + correct: + fragment( + "jsonb_agg(DISTINCT ARRAY[to_jsonb(?), to_jsonb(?), to_jsonb(?)])", + e.emoji_name, + e.actors, + e.url + ) + }) + |> group_by([e], e.apid) + + {update_cnt, _} = + Pleroma.Object + |> join(:inner, [o], r in subquery(ref), on: r.apid == fragment("?->>'id'", o.data)) + |> where( + [o, r], + not (fragment("? @> (?->'reactions')", r.correct, o.data) and + fragment("? <@ (?->'reactions')", r.correct, o.data)) + ) + |> update([o, r], + set: [data: fragment("jsonb_set(?, '{reactions}', ?)", o.data, r.correct)] + ) + |> Pleroma.Repo.update_all([], timeout: :infinity) + + Logger.info("Fixed inlined emoji reactions for #{update_cnt} objects.") + end end diff --git a/lib/mix/tasks/pleroma/refresh_counter_cache.ex b/lib/mix/tasks/pleroma/refresh_counter_cache.ex deleted file mode 100644 index 58384cf63..000000000 --- a/lib/mix/tasks/pleroma/refresh_counter_cache.ex +++ /dev/null @@ -1,68 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2021 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Mix.Tasks.Pleroma.RefreshCounterCache do - @shortdoc "Refreshes counter cache" - - use Mix.Task - - alias Pleroma.Activity - alias Pleroma.CounterCache - alias Pleroma.Repo - - import Ecto.Query - - def run([]) do - Mix.Pleroma.start_pleroma() - - instances = - Activity - |> distinct([a], true) - |> select([a], fragment("split_part(?, '/', 3)", a.actor)) - |> Repo.all() - - instances - |> Enum.with_index(1) - |> Enum.each(fn {instance, i} -> - counters = instance_counters(instance) - CounterCache.set(instance, counters) - - Mix.Pleroma.shell_info( - "[#{i}/#{length(instances)}] Setting #{instance} counters: #{inspect(counters)}" - ) - end) - - Mix.Pleroma.shell_info("Done") - end - - defp instance_counters(instance) do - counters = %{"public" => 0, "unlisted" => 0, "private" => 0, "direct" => 0} - - Activity - |> where([a], fragment("(? ->> 'type'::text) = 'Create'", a.data)) - |> where([a], fragment("split_part(?, '/', 3) = ?", a.actor, ^instance)) - |> select( - [a], - {fragment( - "activity_visibility(?, ?, ?)", - a.actor, - a.recipients, - a.data - ), count(a.id)} - ) - |> group_by( - [a], - fragment( - "activity_visibility(?, ?, ?)", - a.actor, - a.recipients, - a.data - ) - ) - |> Repo.all(timeout: :timer.minutes(30)) - |> Enum.reduce(counters, fn {visibility, count}, acc -> - Map.put(acc, visibility, count) - end) - end -end diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index f820cbdae..449c9beda 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -33,10 +33,6 @@ defmodule Pleroma.Activity do field(:recipients, {:array, :string}, default: []) field(:thread_muted?, :boolean, virtual: true) - # A field that can be used if you need to join some kind of other - # id to order / paginate this field by - field(:pagination_id, :string, virtual: true) - # This is a fake relation, # do not use outside of with_preloaded_user_actor/with_joined_user_actor has_one(:user_actor, User, on_delete: :nothing, foreign_key: :id) diff --git a/lib/pleroma/activity/html.ex b/lib/pleroma/activity/html.ex index 92840b1c0..187459388 100644 --- a/lib/pleroma/activity/html.ex +++ b/lib/pleroma/activity/html.ex @@ -59,6 +59,8 @@ def get_cached_scrubbed_html_for_activity( object = Object.normalize(activity, fetch: false) add_cache_key_for(activity.id, key) + + # callback already produces :commit or :ignore tuples HTML.ensure_scrubbed_html(content, scrubbers, object.data["fake"] || false, callback) end) end diff --git a/lib/pleroma/activity/queries.ex b/lib/pleroma/activity/queries.ex index 6c0274315..346168938 100644 --- a/lib/pleroma/activity/queries.ex +++ b/lib/pleroma/activity/queries.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Activity.Queries do Contains queries for Activity. """ - import Ecto.Query, only: [from: 2, where: 3] + import Ecto.Query, only: [from: 2] @type query :: Ecto.Queryable.t() | Activity.t() @@ -76,22 +76,6 @@ def by_object_id(query, object_id) when is_binary(object_id) do ) end - @spec by_object_in_reply_to_id(query, String.t(), keyword()) :: query - def by_object_in_reply_to_id(query, in_reply_to_id, opts \\ []) do - query = - if opts[:skip_preloading] do - Activity.with_joined_object(query) - else - Activity.with_preloaded_object(query) - end - - where( - query, - [activity, object: o], - fragment("(?)->>'inReplyTo' = ?", o.data, ^to_string(in_reply_to_id)) - ) - end - @spec by_type(query, String.t()) :: query def by_type(query \\ Activity, activity_type) do from( diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 506fc3daf..72899940f 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -44,9 +44,6 @@ def start(_type, _args) do # every time the application is restarted, so we disable module # conflicts at runtime Code.compiler_options(ignore_module_conflict: true) - # Disable warnings_as_errors at runtime, it breaks Phoenix live reload - # due to protocol consolidation warnings - Code.compiler_options(warnings_as_errors: false) Config.Holder.save_default() Pleroma.HTML.compile_scrubbers() Pleroma.Config.Oban.warn() @@ -71,14 +68,13 @@ def start(_type, _args) do http_children() ++ [ Pleroma.Stats, - Pleroma.JobQueueMonitor, {Majic.Pool, [name: Pleroma.MajicPool, pool_size: Config.get([:majic_pool, :size], 2)]}, {Oban, Config.get(Oban)}, Pleroma.Web.Endpoint, Pleroma.Web.Telemetry ] ++ elasticsearch_children() ++ - task_children(@mix_env) ++ + task_children() ++ dont_run_in_test(@mix_env) # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html @@ -148,35 +144,90 @@ def load_all_pleroma_modules do defp cachex_children do [ - build_cachex("used_captcha", ttl_interval: seconds_valid_interval()), - build_cachex("user", default_ttl: 25_000, ttl_interval: 1000, limit: 2500), - build_cachex("object", default_ttl: 25_000, ttl_interval: 1000, limit: 2500), - build_cachex("rich_media", default_ttl: :timer.minutes(120), limit: 5000), - build_cachex("scrubber", limit: 2500), - build_cachex("scrubber_management", limit: 2500), - build_cachex("idempotency", expiration: idempotency_expiration(), limit: 2500), - build_cachex("web_resp", limit: 2500), - build_cachex("emoji_packs", expiration: emoji_packs_expiration(), limit: 10), - build_cachex("failed_proxy_url", limit: 2500), - build_cachex("banned_urls", default_ttl: :timer.hours(24 * 30), limit: 5_000), - 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("host_meta", default_ttl: :timer.minutes(120), limit: 5000), - build_cachex("http_backoff", default_ttl: :timer.hours(24 * 30), limit: 10000) + build_cachex( + "used_captcha", + expiration: expiration(interval: seconds_valid_interval()) + ), + build_cachex( + "user", + expiration: expiration(default: 3_000, interval: 1_000), + hooks: [cachex_sched_limit(2500)] + ), + build_cachex( + "object", + expiration: expiration(default: 3_000, interval: 1_000), + hooks: [cachex_sched_limit(2500)] + ), + build_cachex( + "rich_media", + expiration: expiration(default: :timer.hours(2)), + hooks: [cachex_sched_limit(5000)] + ), + build_cachex( + "scrubber", + hooks: [cachex_sched_limit(2500)] + ), + build_cachex( + "scrubber_management", + hooks: [cachex_sched_limit(2500)] + ), + build_cachex( + "idempotency", + expiration: expiration(default: :timer.hours(6), interval: :timer.minutes(1)), + hooks: [cachex_sched_limit(2500, [], frequency: :timer.minutes(1))] + ), + build_cachex( + "web_resp", + hooks: [cachex_sched_limit(2500)] + ), + build_cachex( + "emoji_packs", + expiration: expiration(default: :timer.minutes(5), interval: :timer.minutes(1)), + hooks: [cachex_sched_limit(10)] + ), + build_cachex( + "failed_proxy_url", + hooks: [cachex_sched_limit(2500)] + ), + build_cachex( + "banned_urls", + expiration: expiration(default: :timer.hours(24 * 30)), + hooks: [cachex_sched_limit(5_000, [], frequency: :timer.minutes(5))] + ), + build_cachex( + "translations", + expiration: expiration(default: :timer.hours(24 * 30)), + hooks: [cachex_sched_limit(2500)] + ), + build_cachex( + "instances", + expiration: expiration(default: :timer.hours(24), interval: 1000), + hooks: [cachex_sched_limit(2500)] + ), + build_cachex( + "rel_me", + expiration: expiration(default: :timer.hours(24 * 30)), + hooks: [cachex_sched_limit(300, [], frequency: :timer.minutes(1))] + ), + build_cachex( + "host_meta", + expiration: expiration(default: :timer.minutes(120)), + hooks: [cachex_sched_limit(5000, [], frequency: :timer.minutes(1))] + ), + build_cachex( + "http_backoff", + expiration: expiration(default: :timer.hours(24 * 30)), + hooks: [cachex_sched_limit(10_000, [], frequency: :timer.minutes(5))] + ) ] end - defp emoji_packs_expiration, - do: expiration(default: :timer.seconds(5 * 60), interval: :timer.seconds(60)) - - defp idempotency_expiration, - do: expiration(default: :timer.seconds(6 * 60 * 60), interval: :timer.seconds(60)) - defp seconds_valid_interval, do: :timer.seconds(Config.get!([Pleroma.Captcha, :seconds_valid])) + defp cachex_sched_limit(limit, prune_opts \\ [], sched_opts \\ []), + do: hook(module: Cachex.Limit.Scheduled, args: {limit, prune_opts, sched_opts}) + @spec build_cachex(String.t(), keyword()) :: map() def build_cachex(type, opts), do: %{ @@ -204,31 +255,29 @@ defp background_migrators do ] end - @spec task_children(atom()) :: [map()] + @spec task_children() :: [map()] + defp task_children() do + always = + [ + %{ + id: :web_push_init, + start: {Task, :start_link, [&Pleroma.Web.Push.init/0]}, + restart: :temporary + } + ] - defp task_children(:test) do - [ - %{ - id: :web_push_init, - start: {Task, :start_link, [&Pleroma.Web.Push.init/0]}, - restart: :temporary - } - ] - end - - defp task_children(_) do - [ - %{ - id: :web_push_init, - start: {Task, :start_link, [&Pleroma.Web.Push.init/0]}, - restart: :temporary - }, - %{ - id: :internal_fetch_init, - start: {Task, :start_link, [&Pleroma.Web.ActivityPub.InternalFetchActor.init/0]}, - restart: :temporary - } - ] + if @mix_env == :test do + always + else + [ + %{ + id: :internal_fetch_init, + start: {Task, :start_link, [&Pleroma.Web.ActivityPub.InternalFetchActor.init/0]}, + restart: :temporary + } + | always + ] + end end @spec elasticsearch_children :: [Pleroma.Search.Elasticsearch.Cluster] @@ -262,23 +311,11 @@ def limiters_setup do end 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], 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() config = - [:http, :adapter] - |> Config.get([]) - |> 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) + Config.get([:http, :adapter]) + |> Pleroma.HTTP.AdapterHelper.options() [{Finch, config}] end diff --git a/lib/pleroma/captcha.ex b/lib/pleroma/captcha.ex index bad7b3a66..7a37a6246 100644 --- a/lib/pleroma/captcha.ex +++ b/lib/pleroma/captcha.ex @@ -97,7 +97,7 @@ defp validate_usage(token) do defp mark_captcha_as_used(token) do ttl = seconds_valid() |> :timer.seconds() - @cachex.put(:used_captcha_cache, token, true, ttl: ttl) + @cachex.put(:used_captcha_cache, token, true, expire: ttl) end defp method, do: Pleroma.Config.get!([__MODULE__, :method]) diff --git a/lib/pleroma/collections/fetcher.ex b/lib/pleroma/collections/fetcher.ex index a4c3463a2..e5a050359 100644 --- a/lib/pleroma/collections/fetcher.ex +++ b/lib/pleroma/collections/fetcher.ex @@ -27,6 +27,10 @@ def fetch_collection(%{"type" => type} = page) partial_as_success(objects_from_collection(page)) end + def fetch_collection(_) do + {:error, :invalid_type} + end + defp partial_as_success({:partial, items}), do: {:ok, items} defp partial_as_success(res), do: res diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex index b62532595..57a264f54 100644 --- a/lib/pleroma/config/deprecation_warnings.ex +++ b/lib/pleroma/config/deprecation_warnings.ex @@ -22,6 +22,27 @@ defmodule Pleroma.Config.DeprecationWarnings do "\n* `config :pleroma, :instance, :quarantined_instances` is now covered by `:pleroma, :mrf_simple, :reject`"} ] + def check_skip_thread_containment do + # The default in config/config.exs is "true" since 593b8b1e6a8502cca9bf5559b8bec86f172bbecb + # but when the default is retrieved in code the fallback is still "false" + uses_thread_visibility_filtering = !Config.get([:instance, :skip_thread_containment], false) + + if uses_thread_visibility_filtering do + Logger.warning(""" + !!!DEPRECATION WARNING!!! + Your config is explicitly enabling thread-based visibility containment by setting the below: + ``` + config :pleroma, :instance, skip_thread_containment: false + ``` + + This feature comes with a very high performance overhead and is considered for removal. + If you actually need or strongly prefer keeping it, speak up NOW(!) by filing a ticket at + https://akkoma.dev/AkkomaGang/akkoma/issues + Complaints only after the removal happened are much less likely to have any effect. + """) + end + end + def check_exiftool_filter do filters = Config.get([Pleroma.Upload]) |> Keyword.get(:filters, []) @@ -222,7 +243,8 @@ def warn do check_http_adapter(), check_uploader_base_url_set(), check_uploader_base_url_is_not_base_domain(), - check_exiftool_filter() + check_exiftool_filter(), + check_skip_thread_containment() ] |> Enum.reduce(:ok, fn :ok, :ok -> :ok diff --git a/lib/pleroma/config_db.ex b/lib/pleroma/config_db.ex index 9e4e6f3ea..d5d19cb44 100644 --- a/lib/pleroma/config_db.ex +++ b/lib/pleroma/config_db.ex @@ -7,7 +7,9 @@ defmodule Pleroma.ConfigDB do import Ecto.Changeset import Ecto.Query, only: [select: 3, from: 2] - import Pleroma.Web.Gettext + + use Gettext, + backend: Pleroma.Web.Gettext alias __MODULE__ alias Pleroma.Repo @@ -303,7 +305,9 @@ def to_elixir_types(%{"tuple" => [":partial_chain", entity]}) do end def to_elixir_types(%{"tuple" => entity}) do - Enum.reduce(entity, {}, &Tuple.append(&2, to_elixir_types(&1))) + entity + |> Enum.map(&to_elixir_types(&1)) + |> List.to_tuple() end def to_elixir_types(entity) when is_map(entity) do diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex index 37d6e4ea6..3f11cb8bf 100644 --- a/lib/pleroma/constants.ex +++ b/lib/pleroma/constants.ex @@ -19,7 +19,8 @@ defmodule Pleroma.Constants do "context_id", "deleted_activity_id", "pleroma_internal", - "generator" + "generator", + "voters" ] ) diff --git a/lib/pleroma/counter_cache.ex b/lib/pleroma/counter_cache.ex deleted file mode 100644 index 1e75d19ae..000000000 --- a/lib/pleroma/counter_cache.ex +++ /dev/null @@ -1,79 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2021 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.CounterCache do - alias Pleroma.CounterCache - alias Pleroma.Repo - use Ecto.Schema - import Ecto.Changeset - import Ecto.Query - - schema "counter_cache" do - field(:instance, :string) - field(:public, :integer) - field(:unlisted, :integer) - field(:private, :integer) - field(:direct, :integer) - end - - def changeset(struct, params) do - struct - |> cast(params, [:instance, :public, :unlisted, :private, :direct]) - |> validate_required([:instance]) - |> unique_constraint(:instance) - end - - def get_by_instance(instance) do - CounterCache - |> select([c], %{ - "public" => c.public, - "unlisted" => c.unlisted, - "private" => c.private, - "direct" => c.direct - }) - |> where([c], c.instance == ^instance) - |> Repo.one() - |> case do - nil -> %{"public" => 0, "unlisted" => 0, "private" => 0, "direct" => 0} - val -> val - end - end - - def get_sum do - CounterCache - |> select([c], %{ - "public" => type(sum(c.public), :integer), - "unlisted" => type(sum(c.unlisted), :integer), - "private" => type(sum(c.private), :integer), - "direct" => type(sum(c.direct), :integer) - }) - |> Repo.one() - end - - def set(instance, values) do - params = - Enum.reduce( - ["public", "private", "unlisted", "direct"], - %{"instance" => instance}, - fn param, acc -> - Map.put_new(acc, param, Map.get(values, param, 0)) - end - ) - - %CounterCache{} - |> changeset(params) - |> Repo.insert( - on_conflict: [ - set: [ - public: params["public"], - private: params["private"], - unlisted: params["unlisted"], - direct: params["direct"] - ] - ], - returning: true, - conflict_target: :instance - ) - end -end diff --git a/lib/pleroma/docs/translator.ex b/lib/pleroma/docs/translator.ex index 13e33c87e..9f8b5b2ca 100644 --- a/lib/pleroma/docs/translator.ex +++ b/lib/pleroma/docs/translator.ex @@ -4,7 +4,7 @@ defmodule Pleroma.Docs.Translator do require Pleroma.Docs.Translator.Compiler - require Pleroma.Web.Gettext + use Gettext, backend: Pleroma.Web.Gettext @before_compile Pleroma.Docs.Translator.Compiler end diff --git a/lib/pleroma/docs/translator/compiler.ex b/lib/pleroma/docs/translator/compiler.ex index 5d27d9fa2..8265724e3 100644 --- a/lib/pleroma/docs/translator/compiler.ex +++ b/lib/pleroma/docs/translator/compiler.ex @@ -7,6 +7,8 @@ defmodule Pleroma.Docs.Translator.Compiler do @raw_config Pleroma.Config.Loader.read("config/description.exs") @raw_descriptions @raw_config[:pleroma][:config_description] + require Gettext.Macros + defmacro __before_compile__(_env) do strings = __MODULE__.descriptions() @@ -21,7 +23,8 @@ def placeholder do ctxt = msgctxt_for(path, type) quote do - Pleroma.Web.Gettext.dpgettext_noop( + Gettext.Macros.dpgettext_noop_with_backend( + Pleroma.Web.Gettext, "config_descriptions", unquote(ctxt), unquote(string) diff --git a/lib/pleroma/emails/user_email.ex b/lib/pleroma/emails/user_email.ex index a7bcc03b6..573fceebb 100644 --- a/lib/pleroma/emails/user_email.ex +++ b/lib/pleroma/emails/user_email.ex @@ -5,12 +5,13 @@ defmodule Pleroma.Emails.UserEmail do @moduledoc "User emails" - require Pleroma.Web.Gettext + require Pleroma.Web.GettextCompanion + use Gettext, backend: Pleroma.Web.Gettext use Pleroma.Web, :mailer alias Pleroma.Config alias Pleroma.User - alias Pleroma.Web.Gettext + alias Pleroma.Web.GettextCompanion import Swoosh.Email import Phoenix.Swoosh, except: [render_body: 3] @@ -29,7 +30,7 @@ defp recipient(%User{} = user), do: recipient(user.email, user.name) @spec welcome(User.t(), map()) :: Swoosh.Email.t() def welcome(user, opts \\ %{}) do - Gettext.with_locale_or_default user.language do + GettextCompanion.with_locale_or_default user.language do new() |> to(recipient(user)) |> from(Map.get(opts, :sender, sender())) @@ -37,7 +38,7 @@ def welcome(user, opts \\ %{}) do Map.get( opts, :subject, - Gettext.dpgettext( + dpgettext( "static_pages", "welcome email subject", "Welcome to %{instance_name}!", @@ -49,7 +50,7 @@ def welcome(user, opts \\ %{}) do Map.get( opts, :html, - Gettext.dpgettext( + dpgettext( "static_pages", "welcome email html body", "Welcome to %{instance_name}!", @@ -61,7 +62,7 @@ def welcome(user, opts \\ %{}) do Map.get( opts, :text, - Gettext.dpgettext( + dpgettext( "static_pages", "welcome email text body", "Welcome to %{instance_name}!", @@ -73,11 +74,11 @@ def welcome(user, opts \\ %{}) do end def password_reset_email(user, token) when is_binary(token) do - Gettext.with_locale_or_default user.language do + GettextCompanion.with_locale_or_default user.language do password_reset_url = url(~p[/api/v1/pleroma/password_reset/#{token}]) html_body = - Gettext.dpgettext( + dpgettext( "static_pages", "password reset email body", """ @@ -93,9 +94,7 @@ def password_reset_email(user, token) when is_binary(token) do new() |> to(recipient(user)) |> from(sender()) - |> subject( - Gettext.dpgettext("static_pages", "password reset email subject", "Password reset") - ) + |> subject(dpgettext("static_pages", "password reset email subject", "Password reset")) |> html_body(html_body) end end @@ -106,11 +105,11 @@ def user_invitation_email( to_email, to_name \\ nil ) do - Gettext.with_locale_or_default user.language do + GettextCompanion.with_locale_or_default user.language do registration_url = url(~p[/registration/#{user_invite_token.token}]) html_body = - Gettext.dpgettext( + dpgettext( "static_pages", "user invitation email body", """ @@ -127,7 +126,7 @@ def user_invitation_email( |> to(recipient(to_email, to_name)) |> from(sender()) |> subject( - Gettext.dpgettext( + dpgettext( "static_pages", "user invitation email subject", "Invitation to %{instance_name}", @@ -139,11 +138,11 @@ def user_invitation_email( end def account_confirmation_email(user) do - Gettext.with_locale_or_default user.language do + GettextCompanion.with_locale_or_default user.language do confirmation_url = url(~p[/api/account/confirm_email/#{user.id}/#{user.confirmation_token}]) html_body = - Gettext.dpgettext( + dpgettext( "static_pages", "confirmation email body", """ @@ -159,7 +158,7 @@ def account_confirmation_email(user) do |> to(recipient(user)) |> from(sender()) |> subject( - Gettext.dpgettext( + dpgettext( "static_pages", "confirmation email subject", "%{instance_name} account confirmation", @@ -171,9 +170,9 @@ def account_confirmation_email(user) do end def approval_pending_email(user) do - Gettext.with_locale_or_default user.language do + GettextCompanion.with_locale_or_default user.language do html_body = - Gettext.dpgettext( + dpgettext( "static_pages", "approval pending email body", """ @@ -187,7 +186,7 @@ def approval_pending_email(user) do |> to(recipient(user)) |> from(sender()) |> subject( - Gettext.dpgettext( + dpgettext( "static_pages", "approval pending email subject", "Your account is awaiting approval" @@ -198,9 +197,9 @@ def approval_pending_email(user) do end def successful_registration_email(user) do - Gettext.with_locale_or_default user.language do + GettextCompanion.with_locale_or_default user.language do html_body = - Gettext.dpgettext( + dpgettext( "static_pages", "successful registration email body", """ @@ -216,7 +215,7 @@ def successful_registration_email(user) do |> to(recipient(user)) |> from(sender()) |> subject( - Gettext.dpgettext( + dpgettext( "static_pages", "successful registration email subject", "Account registered on %{instance_name}", @@ -234,7 +233,7 @@ def successful_registration_email(user) do """ @spec digest_email(User.t()) :: Swoosh.Email.t() | nil def digest_email(user) do - Gettext.with_locale_or_default user.language do + GettextCompanion.with_locale_or_default user.language do notifications = Pleroma.Notification.for_user_since(user, user.last_digest_emailed_at) mentions = @@ -295,7 +294,7 @@ def digest_email(user) do |> to(recipient(user)) |> from(sender()) |> subject( - Gettext.dpgettext( + dpgettext( "static_pages", "digest email subject", "Your digest from %{instance_name}", @@ -336,12 +335,12 @@ def unsubscribe_url(user, notifications_type) do def backup_is_ready_email(backup, admin_user_id \\ nil) do %{user: user} = Pleroma.Repo.preload(backup, :user) - Gettext.with_locale_or_default user.language do + GettextCompanion.with_locale_or_default user.language do download_url = Pleroma.Web.PleromaAPI.BackupView.download_url(backup) html_body = if is_nil(admin_user_id) do - Gettext.dpgettext( + dpgettext( "static_pages", "account archive email body - self-requested", """ @@ -353,7 +352,7 @@ def backup_is_ready_email(backup, admin_user_id \\ nil) do else admin = Pleroma.Repo.get(User, admin_user_id) - Gettext.dpgettext( + dpgettext( "static_pages", "account archive email body - admin requested", """ @@ -369,7 +368,7 @@ def backup_is_ready_email(backup, admin_user_id \\ nil) do |> to(recipient(user)) |> from(sender()) |> subject( - Gettext.dpgettext( + dpgettext( "static_pages", "account archive email subject", "Your account archive is ready" diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index d320b9a71..6ba68b4ba 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -16,7 +16,7 @@ defmodule Pleroma.Emoji do @ets __MODULE__.Ets @ets_options [ - :ordered_set, + :set, :protected, :named_table, {:read_concurrency, true} @@ -25,6 +25,8 @@ defmodule Pleroma.Emoji do defstruct [:code, :file, :tags, :safe_code, :safe_file] + @type t :: %__MODULE__{} + @doc "Build emoji struct" def build({code, file, tags}) do %__MODULE__{ @@ -43,14 +45,14 @@ def start_link(_) do GenServer.start_link(__MODULE__, [], name: __MODULE__) end - @doc "Reloads the emojis from disk." + @doc "Reloads the emojis from disk (asynchronous)" @spec reload() :: :ok def reload do - GenServer.call(__MODULE__, :reload) + GenServer.cast(__MODULE__, :reload) end - @doc "Returns the path of the emoji `name`." - @spec get(String.t()) :: String.t() | nil + @doc "Returns the emoji struct of the given `name` if it exists." + @spec get(String.t()) :: t() | nil def get(name) do name = if String.starts_with?(name, ":") do @@ -62,11 +64,23 @@ def get(name) do end case :ets.lookup(@ets, name) do - [{_, path}] -> path + [{_, emoji}] -> emoji _ -> nil end end + @doc "Updates or inserts new emoji (asynchronous)" + @spec add_or_update(t()) :: :ok + def add_or_update(%__MODULE__{} = emoji) do + GenServer.cast(__MODULE__, {:add, emoji}) + end + + @doc "Delete emoji with given shortcode if it exists (asynchronous)" + @spec delete(String.t()) :: :ok + def delete(code) do + GenServer.cast(__MODULE__, {:delete, code}) + end + @spec exist?(String.t()) :: boolean() def exist?(name), do: not is_nil(get(name)) @@ -76,9 +90,6 @@ def get_all do :ets.tab2list(@ets) end - @doc "Clear out old emojis" - def clear_all, do: :ets.delete_all_objects(@ets) - @doc false def init(_) do @ets = :ets.new(@ets, @ets_options) @@ -92,10 +103,14 @@ def handle_cast(:reload, state) do {:noreply, state} end - @doc false - def handle_call(:reload, _from, state) do - update_emojis(Loader.load()) - {:reply, :ok, state} + def handle_cast({:add, %__MODULE__{} = emoji}, state) do + :ets.insert(@ets, {emoji.code, emoji}) + {:noreply, state} + end + + def handle_cast({:delete, code}, state) do + :ets.delete(@ets, code) + {:noreply, state} end @doc false @@ -109,7 +124,10 @@ def code_change(_old_vsn, state, _extra) do {:ok, state} end + defp update_emojis([]), do: true + defp update_emojis(emojis) do + :ets.delete_all_objects(@ets) :ets.insert(@ets, emojis) end diff --git a/lib/pleroma/emoji/loader.ex b/lib/pleroma/emoji/loader.ex index e09279dd0..96a8747ff 100644 --- a/lib/pleroma/emoji/loader.ex +++ b/lib/pleroma/emoji/loader.ex @@ -69,7 +69,6 @@ def load do load_pack(Path.join(emoji_dir_path, pack), emoji_groups) end) - Emoji.clear_all() emojis end @@ -103,26 +102,22 @@ defp load_pack(pack_dir, emoji_groups) do pack_file = Path.join(pack_dir, "pack.json") if File.exists?(pack_file) do - Logger.info("Loading emoji pack from JSON: #{pack_file}") - contents = Jason.decode!(File.read!(pack_file)) + Logger.debug("Loading emoji pack from JSON: #{pack_file}") - contents["files"] - |> Enum.map(fn {name, rel_file} -> - filename = Path.join("/emoji/#{pack_name}", rel_file) - {name, filename, ["pack:#{pack_name}"]} - end) + Jason.decode(File.read!(pack_file)) + |> load_from_pack(pack_name) else # Load from emoji.txt / all files emoji_txt = Path.join(pack_dir, "emoji.txt") if File.exists?(emoji_txt) do - Logger.info("Loading emoji pack from emoji.txt: #{emoji_txt}") + Logger.debug("Loading emoji pack from emoji.txt: #{emoji_txt}") load_from_file(emoji_txt, emoji_groups) else extensions = Config.get([:emoji, :pack_extensions]) Logger.info( - "No emoji.txt found for pack \"#{pack_name}\", assuming all #{Enum.join(extensions, ", ")} files are emoji" + "No pack.json or emoji.txt found for pack \"#{pack_name}\", assuming all #{Enum.join(extensions, ", ")} files are emoji" ) make_shortcode_to_file_map(pack_dir, extensions) @@ -135,6 +130,21 @@ defp load_pack(pack_dir, emoji_groups) do end end + defp load_from_pack({:ok, %{"files" => files}}, pack_name) when is_map(files) do + Enum.map(files, fn {name, rel_file} -> + filename = Path.join("/emoji/#{pack_name}", rel_file) + {name, filename, ["pack:#{pack_name}"]} + end) + end + + defp load_from_pack(pack_json_result, pack_name) do + Logger.error( + "Failed to load emoji pack #{pack_name} from pack.json:\n#{inspect(pack_json_result)}" + ) + + [] + end + def make_shortcode_to_file_map(pack_dir, exts) do find_all_emoji(pack_dir, exts) |> Enum.map(&Path.relative_to(&1, pack_dir)) diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex index a841f5ac6..b485860cc 100644 --- a/lib/pleroma/emoji/pack.ex +++ b/lib/pleroma/emoji/pack.ex @@ -49,12 +49,15 @@ defp path_join_safe(dir, path) do Path.join(dir, safe_path) end + defp tags(%__MODULE__{} = pack), do: ["pack:" <> pack.name] + @spec create(String.t()) :: {:ok, t()} | {:error, File.posix()} | {:error, :empty_values} def create(name) do with :ok <- validate_not_empty([name]), dir <- path_join_name_safe(emoji_path(), name), :ok <- File.mkdir(dir) do save_pack(%__MODULE__{ + name: name, path: dir, pack_file: Path.join(dir, "pack.json") }) @@ -142,8 +145,6 @@ def add_file(%Pack{} = pack, _, _, %Plug.Upload{content_type: "application/zip"} {item, updated_pack} end) - Emoji.reload() - {:ok, updated_pack} after File.rm_rf(tmp_dir) @@ -169,16 +170,19 @@ defp try_add_file(%Pack{} = pack, shortcode, filename, file) do with :ok <- validate_not_empty([shortcode, filename]), :ok <- validate_emoji_not_exists(shortcode), {:ok, updated_pack} <- do_add_file(pack, shortcode, filename, file) do - Emoji.reload() {:ok, updated_pack} end end defp do_add_file(pack, shortcode, filename, file) do - with :ok <- save_file(file, pack, filename) do - pack - |> put_emoji(shortcode, filename) - |> save_pack() + with :ok <- save_file(file, pack, filename), + pack <- put_emoji(pack, shortcode, filename), + {:ok, pack} <- save_pack(pack) do + {shortcode, filename, tags(pack)} + |> Emoji.build() + |> Emoji.add_or_update() + + {:ok, pack} end end @@ -188,7 +192,7 @@ def delete_file(%Pack{} = pack, shortcode) do with :ok <- validate_not_empty([shortcode]), :ok <- remove_file(pack, shortcode), {:ok, updated_pack} <- pack |> delete_emoji(shortcode) |> save_pack() do - Emoji.reload() + Emoji.delete(shortcode) {:ok, updated_pack} end end @@ -205,7 +209,12 @@ def update_file(%Pack{} = pack, shortcode, new_shortcode, new_filename, force) d |> delete_emoji(shortcode) |> put_emoji(new_shortcode, new_filename) |> save_pack() do - Emoji.reload() + Emoji.delete(shortcode) + + {new_shortcode, new_filename, tags(pack)} + |> Emoji.build() + |> Emoji.add_or_update() + {:ok, updated_pack} end end @@ -455,7 +464,7 @@ defp create_archive_and_cache(pack, hash) do # if pack.json MD5 changes, the cache is not valid anymore %{hash: hash, pack_data: result}, # Add a minute to cache time for every file in the pack - ttl: overall_ttl + expire: overall_ttl ) result @@ -580,7 +589,7 @@ defp get_filename(pack, shortcode) do defp http_get(%URI{} = url), do: url |> to_string() |> http_get() defp http_get(url) do - with {:ok, %{body: body}} <- Pleroma.HTTP.get(url, [], []) do + with {:ok, %{body: body}} <- Pleroma.HTTP.get(url) do Jason.decode(body) end end diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex index 9e75458e5..885704f95 100644 --- a/lib/pleroma/following_relationship.ex +++ b/lib/pleroma/following_relationship.ex @@ -161,7 +161,11 @@ def get_follow_requests_query(%User{id: id}) do |> where([r], r.state == ^:follow_pending) |> where([r], r.following_id == ^id) |> where([r, follower: f], f.is_active == true) - |> select([r, follower: f], f) + end + + def get_follow_requesting_users_with_request_id(%User{} = user) do + get_follow_requests_query(user) + |> select([r, follower: f], %{id: r.id, entry: f}) end def following?(%User{id: follower_id}, %User{id: followed_id}) do diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index fc841a550..8ca5d5857 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -3,6 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Formatter do + alias PhoenixHTMLHelpers.Tag alias Pleroma.HTML alias Pleroma.User @@ -37,10 +38,10 @@ def mention_tag(%User{id: id} = user, nickname, opts \\ []) do nickname_text = get_nickname_text(nickname, opts) :span - |> Phoenix.HTML.Tag.content_tag( - Phoenix.HTML.Tag.content_tag( + |> Tag.content_tag( + Tag.content_tag( :a, - ["@", Phoenix.HTML.Tag.content_tag(:span, nickname_text)], + ["@", Tag.content_tag(:span, nickname_text)], "data-user": id, class: "u-url mention", href: user_url, @@ -68,7 +69,7 @@ def hashtag_handler("#" <> tag = tag_text, _buffer, _opts, acc) do url = "#{Pleroma.Web.Endpoint.url()}/tag/#{tag}" link = - Phoenix.HTML.Tag.content_tag(:a, tag_text, + Tag.content_tag(:a, tag_text, class: "hashtag", "data-tag": tag, href: url, diff --git a/lib/pleroma/frontend.ex b/lib/pleroma/frontend.ex index 3ce7910cf..4ad13dc45 100644 --- a/lib/pleroma/frontend.ex +++ b/lib/pleroma/frontend.ex @@ -84,7 +84,7 @@ defp download_build(frontend_info, dest) do url = String.replace(frontend_info["build_url"], "${ref}", frontend_info["ref"]) with {:ok, %{status: 200, body: zip_body}} <- - Pleroma.HTTP.get(url, [], receive_timeout: 120_000) do + Pleroma.HTTP.get(url, [], adapter: [receive_timeout: 120_000]) do unzip(zip_body, dest) else {:error, e} -> {:error, e} diff --git a/lib/pleroma/healthcheck.ex b/lib/pleroma/healthcheck.ex index c905bba3f..4aa8fecfd 100644 --- a/lib/pleroma/healthcheck.ex +++ b/lib/pleroma/healthcheck.ex @@ -14,7 +14,6 @@ defmodule Pleroma.Healthcheck do active: 0, idle: 0, memory_used: 0, - job_queue_stats: nil, healthy: true @type t :: %__MODULE__{ @@ -22,7 +21,6 @@ defmodule Pleroma.Healthcheck do active: non_neg_integer(), idle: non_neg_integer(), memory_used: number(), - job_queue_stats: map(), healthy: boolean() } @@ -32,7 +30,6 @@ def system_info do memory_used: Float.round(:recon_alloc.memory(:allocated) / 1024 / 1024, 2) } |> assign_db_info() - |> assign_job_queue_stats() |> check_health() end @@ -58,11 +55,6 @@ defp assign_db_info(healthcheck) do Map.merge(healthcheck, db_info) end - defp assign_job_queue_stats(healthcheck) do - stats = Pleroma.JobQueueMonitor.stats() - Map.put(healthcheck, :job_queue_stats, stats) - end - @spec check_health(Healthcheck.t()) :: Healthcheck.t() def check_health(%{pool_size: pool_size, active: active} = check) when active >= pool_size do diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index cb95d0e68..ecd01d363 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -24,7 +24,7 @@ def missing_dependencies do def image_resize(url, options) do with executable when is_binary(executable) <- System.find_executable("convert"), {:ok, args} <- prepare_image_resize_args(options), - {:ok, env} <- HTTP.get(url, [], []), + {:ok, env} <- HTTP.get(url), {:ok, fifo_path} <- mkfifo() do args = List.flatten([fifo_path, args]) run_fifo(fifo_path, env, executable, args) @@ -73,7 +73,7 @@ defp prepare_image_resize_args(_), do: {:error, :missing_options} # Note: video thumbnail is intentionally not resized (always has original dimensions) def video_framegrab(url) do with executable when is_binary(executable) <- System.find_executable("ffmpeg"), - {:ok, env} <- HTTP.get(url, [], []), + {:ok, env} <- HTTP.get(url), {:ok, fifo_path} <- mkfifo(), args = [ "-y", diff --git a/lib/pleroma/http.ex b/lib/pleroma/http.ex index 3ff27915c..1b81a1236 100644 --- a/lib/pleroma/http.ex +++ b/lib/pleroma/http.ex @@ -7,10 +7,6 @@ defmodule Pleroma.HTTP do Wrapper for `Tesla.request/2`. """ - alias Pleroma.HTTP.AdapterHelper - alias Pleroma.HTTP.Request - alias Pleroma.HTTP.RequestBuilder, as: Builder - alias Tesla.Client alias Tesla.Env require Logger @@ -18,6 +14,8 @@ defmodule Pleroma.HTTP do @type t :: __MODULE__ @type method() :: :get | :post | :put | :delete | :head + @mix_env Mix.env() + @doc """ Performs GET request. @@ -59,40 +57,44 @@ def post(url, body, headers \\ [], options \\ []), @spec request(method(), Request.url(), String.t(), Request.headers(), keyword()) :: {:ok, Env.t()} | {:error, any()} def request(method, url, body, headers, options) when is_binary(url) do - uri = URI.parse(url) - adapter_opts = AdapterHelper.options(uri, options || []) - - adapter_opts = - if uri.scheme == :https do - AdapterHelper.maybe_add_cacerts(adapter_opts, :public_key.cacerts_get()) - else - adapter_opts - end - - options = put_in(options[:adapter], adapter_opts) params = options[:params] || [] - request = build_request(method, headers, options, url, body, params) - client = Tesla.client([Tesla.Middleware.FollowRedirects, Tesla.Middleware.Telemetry]) + options = options |> Keyword.delete(:params) + headers = maybe_add_user_agent(headers) + + client = + Tesla.client([ + Tesla.Middleware.FollowRedirects, + Pleroma.HTTP.Middleware.HTTPSignature, + Tesla.Middleware.Telemetry + ]) Logger.debug("Outbound: #{method} #{url}") - request(client, request) + + Tesla.request(client, + method: method, + url: url, + query: params, + headers: headers, + body: body, + opts: options + ) rescue e -> - Logger.error("Failed to fetch #{url}: #{inspect(e)}") + Logger.error("Failed to fetch #{url}: #{Exception.format(:error, e, __STACKTRACE__)}") {:error, :fetch_error} end - @spec request(Client.t(), keyword()) :: {:ok, Env.t()} | {:error, any()} - def request(client, request), do: Tesla.request(client, request) - - defp build_request(method, headers, options, url, body, params) do - Builder.new() - |> Builder.method(method) - |> Builder.headers(headers) - |> Builder.opts(options) - |> Builder.url(url) - |> Builder.add_param(:body, :body, body) - |> Builder.add_param(:query, :query, params) - |> Builder.convert_to_keyword() + if @mix_env == :test do + defp maybe_add_user_agent(headers) do + with true <- Pleroma.Config.get([:http, :send_user_agent]) do + [{"user-agent", Pleroma.Application.user_agent()} | headers] + else + _ -> + headers + end + end + else + defp maybe_add_user_agent(headers), + do: [{"user-agent", Pleroma.Application.user_agent()} | headers] end end diff --git a/lib/pleroma/http/adapter_helper.ex b/lib/pleroma/http/adapter_helper.ex index 74b0b88f1..aad1dbef3 100644 --- a/lib/pleroma/http/adapter_helper.ex +++ b/lib/pleroma/http/adapter_helper.ex @@ -6,141 +6,84 @@ defmodule Pleroma.HTTP.AdapterHelper do @moduledoc """ Configure Tesla.Client with default and customized adapter options. """ - @defaults [name: MyFinch, pool_timeout: 5_000, receive_timeout: 5_000] @type proxy_type() :: :socks4 | :socks5 @type host() :: charlist() | :inet.ip_address() - alias Pleroma.HTTP.AdapterHelper + alias Pleroma.Config require Logger @type proxy :: {Connection.proxy_type(), Connection.host(), pos_integer(), list()} - @callback options(keyword(), URI.t()) :: keyword() + @doc """ + Merge default connection & adapter options with received ones. + """ + @spec options(Keyword.t()) :: Keyword.t() + def options(opts \\ []) do + [ + name: MyFinch, + pools: %{ + default: [ + size: Config.get!([:http, :pool_size]), + pool_max_idle_time: Config.get!([:http, :pool_timeout]), + conn_max_idle_time: Config.get!([:http, :receive_timeout]), + protocols: Config.get!([:http, :protocols]), + conn_opts: [ + # Do NOT add cacerts here as this will cause issues for plain HTTP connections! + # (when we upgrade our deps to Mint >= 1.6.0 we can also explicitly enable "inet4: true") + transport_opts: [inet6: true], + # up to at least version 0.20.0, Finch leaves server_push enabled by default for HTTP2, + # but will actually raise an exception when receiving such a response. Tell servers we don't want it. + # see: https://github.com/sneako/finch/issues/325 + client_settings: [enable_push: false] + ] + ] + } + ] + |> maybe_add_proxy_pool(Config.get([:http, :proxy_url])) + |> nested_merge(opts) + # Ensure name is not overwritten + |> Keyword.put(:name, MyFinch) + end + + @spec nested_merge(Keyword.t(), Keyword.t()) :: Keyword.t() + defp nested_merge(k1, k2) do + Keyword.merge(k1, k2, &nested_merge/3) + end + + defp nested_merge(_key, v1, v2) when is_list(v1) and is_list(v2) do + if Keyword.keyword?(v1) and Keyword.keyword?(v2) do + nested_merge(v1, v2) + else + v2 + end + end + + defp nested_merge(_key, v1, v2) when is_map(v1) and is_map(v2) do + Map.merge(v1, v2, &nested_merge/3) + end + + defp nested_merge(_key, _v1, v2), do: v2 + + defp maybe_add_proxy_pool(opts, proxy_config) do + case format_proxy(proxy_config) do + nil -> + opts + + proxy -> + Logger.info("Using HTTP Proxy: #{inspect(proxy)}") + put_in(opts, [:pools, :default, :conn_opts, :proxy], proxy) + end + end @spec format_proxy(String.t() | tuple() | nil) :: proxy() | nil - def format_proxy(nil), do: nil - - def format_proxy(proxy_url) do + defp format_proxy(proxy_url) do case parse_proxy(proxy_url) do {:ok, type, host, port} -> {type, host, port, []} _ -> nil end end - @spec maybe_add_proxy(keyword(), proxy() | nil) :: keyword() - def maybe_add_proxy(opts, nil), do: opts - - def maybe_add_proxy(opts, proxy) do - Keyword.put(opts, :proxy, proxy) - end - - def maybe_add_proxy_pool(opts, nil), do: opts - - def maybe_add_proxy_pool(opts, proxy) do - Logger.info("Using HTTP Proxy: #{inspect(proxy)}") - - opts - |> maybe_add_pools() - |> maybe_add_default_pool() - |> maybe_add_conn_opts() - |> put_in([:pools, :default, :conn_opts, :proxy], proxy) - end - - def maybe_add_cacerts(opts, nil), do: opts - - def maybe_add_cacerts(opts, cacerts) do - opts - |> maybe_add_pools() - |> maybe_add_default_pool() - |> maybe_add_conn_opts() - |> maybe_add_transport_opts() - |> put_in([:pools, :default, :conn_opts, :transport_opts, :cacerts], cacerts) - end - - def add_pool_size(opts, pool_size) do - opts - |> maybe_add_pools() - |> maybe_add_default_pool() - |> put_in([:pools, :default, :size], pool_size) - end - - def ensure_ipv6(opts) do - # Default transport opts already enable IPv6, so just ensure they're loaded - opts - |> maybe_add_pools() - |> maybe_add_default_pool() - |> maybe_add_conn_opts() - |> maybe_add_transport_opts() - end - - defp maybe_add_pools(opts) do - if Keyword.has_key?(opts, :pools) do - opts - else - Keyword.put(opts, :pools, %{}) - end - end - - defp maybe_add_default_pool(opts) do - pools = Keyword.get(opts, :pools) - - if Map.has_key?(pools, :default) do - opts - else - put_in(opts, [:pools, :default], []) - end - end - - defp maybe_add_conn_opts(opts) do - conn_opts = get_in(opts, [:pools, :default, :conn_opts]) - - unless is_nil(conn_opts) do - opts - else - put_in(opts, [:pools, :default, :conn_opts], []) - end - end - - defp maybe_add_transport_opts(opts) do - transport_opts = get_in(opts, [:pools, :default, :conn_opts, :transport_opts]) - - opts = - unless is_nil(transport_opts) do - opts - else - put_in(opts, [:pools, :default, :conn_opts, :transport_opts], []) - end - - # IPv6 is disabled and IPv4 enabled by default; ensure we can use both - 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. - """ - - @spec options(URI.t(), keyword()) :: keyword() - def options(%URI{} = uri, opts \\ []) do - @defaults - |> Keyword.merge(opts) - |> AdapterHelper.Default.options(uri) - end - defp proxy_type("http"), do: {:ok, :http} defp proxy_type("https"), do: {:ok, :https} defp proxy_type(_), do: {:error, :unknown} @@ -149,10 +92,10 @@ defp proxy_type(_), do: {:error, :unknown} {:ok, proxy_type(), host(), pos_integer()} | {:error, atom()} | nil - def parse_proxy(nil), do: nil - def parse_proxy(""), do: nil + defp parse_proxy(nil), do: nil + defp parse_proxy(""), do: nil - def parse_proxy(proxy) when is_binary(proxy) do + defp parse_proxy(proxy) when is_binary(proxy) do with %URI{} = uri <- URI.parse(proxy), {:ok, type} <- proxy_type(uri.scheme) do {:ok, type, uri.host, uri.port} @@ -163,7 +106,7 @@ def parse_proxy(proxy) when is_binary(proxy) do end end - def parse_proxy(proxy) when is_tuple(proxy) do + defp parse_proxy(proxy) when is_tuple(proxy) do with {type, host, port} <- proxy do {:ok, type, host, port} else @@ -172,30 +115,4 @@ def parse_proxy(proxy) when is_tuple(proxy) do {:error, :invalid_proxy} end end - - @spec parse_host(String.t() | atom() | charlist()) :: charlist() | :inet.ip_address() - def parse_host(host) when is_list(host), do: host - def parse_host(host) when is_atom(host), do: to_charlist(host) - - def parse_host(host) when is_binary(host) do - host = to_charlist(host) - - case :inet.parse_address(host) do - {:error, :einval} -> host - {:ok, ip} -> ip - end - end - - @spec format_host(String.t()) :: charlist() - def format_host(host) do - host_charlist = to_charlist(host) - - case :inet.parse_address(host_charlist) do - {:error, :einval} -> - :idna.encode(host_charlist) - - {:ok, _ip} -> - host_charlist - end - end end diff --git a/lib/pleroma/http/adapter_helper/default.ex b/lib/pleroma/http/adapter_helper/default.ex deleted file mode 100644 index fc377b376..000000000 --- a/lib/pleroma/http/adapter_helper/default.ex +++ /dev/null @@ -1,24 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2021 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.HTTP.AdapterHelper.Default do - alias Pleroma.HTTP.AdapterHelper - - @behaviour Pleroma.HTTP.AdapterHelper - - @spec options(keyword(), URI.t()) :: keyword() - def options(opts, _uri) do - proxy = Pleroma.Config.get([:http, :proxy_url]) - pool_timeout = Pleroma.Config.get([:http, :pool_timeout], 5000) - receive_timeout = Pleroma.Config.get([:http, :receive_timeout], 15_000) - - opts - |> AdapterHelper.maybe_add_proxy(AdapterHelper.format_proxy(proxy)) - |> Keyword.put(:pool_timeout, pool_timeout) - |> Keyword.put(:receive_timeout, receive_timeout) - end - - @spec get_conn(URI.t(), keyword()) :: {:ok, keyword()} - def get_conn(_uri, opts), do: {:ok, opts} -end diff --git a/lib/pleroma/http/backoff.ex b/lib/pleroma/http/backoff.ex index b3f734a92..a897b9f92 100644 --- a/lib/pleroma/http/backoff.ex +++ b/lib/pleroma/http/backoff.ex @@ -77,6 +77,12 @@ defp next_backoff_timestamp(%{headers: headers}) when is_list(headers) do defp next_backoff_timestamp(_), do: DateTime.utc_now() |> Timex.shift(seconds: 5 * 60) + defp log_ratelimit(429, host, time), + do: Logger.error("Rate limited on #{host}! Backing off until #{time}...") + + defp log_ratelimit(503, host, time), + do: Logger.warning("#{host} temporarily unavailable! Backing off until #{time}...") + # 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()) :: @@ -84,11 +90,11 @@ defp next_backoff_timestamp(_), do: DateTime.utc_now() |> Timex.shift(seconds: 5 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) + log_ratelimit(status, host, timestamp) ttl = Timex.diff(timestamp, DateTime.utc_now(), :seconds) # we will cache the host for 5 minutes - @cachex.put(@backoff_cache, host, true, ttl: ttl) + @cachex.put(@backoff_cache, host, true, expire: ttl) {:error, :ratelimit} _ -> diff --git a/lib/pleroma/http/middleware/httpsignature.ex b/lib/pleroma/http/middleware/httpsignature.ex new file mode 100644 index 000000000..a26ac1855 --- /dev/null +++ b/lib/pleroma/http/middleware/httpsignature.ex @@ -0,0 +1,121 @@ +# Akkoma: Magically expressive social media +# Copyright © 2025 Akkoma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.HTTP.Middleware.HTTPSignature do + alias Pleroma.User.SigningKey + alias Pleroma.Signature + + require Logger + + @behaviour Tesla.Middleware + + @moduledoc """ + Adds a HTTP signature and related headers to requests, if a signing key is set in the request env. + If any other middleware can update the target location (e.g. redirects) this MUST be placed after all of them! + + (Note: the third argument holds static middleware options from client creation) + """ + @impl true + def call(env, next, _options) do + env = maybe_sign(env) + Tesla.run(env, next) + end + + defp maybe_sign(env) do + case Keyword.get(env.opts, :httpsig) do + %{signing_key: %SigningKey{} = key} -> + set_signature_headers(env, key) + + _ -> + env + end + end + + defp set_signature_headers(env, key) do + Logger.debug("Signing request to: #{env.url}") + {http_headers, signing_headers} = collect_headers_for_signature(env) + signature = Signature.sign(key, signing_headers, has_body: has_body(env)) + set_headers(env, [{"signature", signature} | http_headers]) + end + + defp has_body(%{body: body}) when body in [nil, ""], do: false + defp has_body(_), do: true + + defp set_headers(env, []), do: env + + defp set_headers(env, [{key, val} | rest]) do + headers = :proplists.delete(key, env.headers) + headers = [{key, val} | headers] + set_headers(%{env | headers: headers}, rest) + end + + # Returns tuple. + # First element is headers+values which need to be added to the HTTP request. + # Second element are all headers to be used for signing, including already existing and pseudo headers. + defp collect_headers_for_signature(env) do + {request_target, host} = get_request_target_and_host(env) + date = http_date() + + # content-length is always automatically set later on + # since they are needed to establish working connection. + # Similarly host will always be set for HTTP/1, and technically may be omitted for HTTP/2+ + # but Tesla doesn’t handle it well if we preset it ourselves (and seems to set it even for HTTP/2 anyway) + http_headers = [{"date", date}] + + signing_headers = %{ + "(request-target)" => request_target, + "host" => host, + "date" => date + } + + if has_body(env) do + append_body_headers(env, http_headers, signing_headers) + else + {http_headers, signing_headers} + end + end + + defp append_body_headers(env, http_headers, signing_headers) do + content_length = byte_size(env.body) + digest = digest_value(env) + + http_headers = [{"digest", digest} | http_headers] + + signing_headers = + Map.merge(signing_headers, %{ + "digest" => digest, + "content-length" => content_length + }) + + {http_headers, signing_headers} + end + + defp get_request_target_and_host(env) do + uri = URI.parse(env.url) + rt = "#{env.method} #{uri.path}" + host = host_from_uri(uri) + {rt, host} + end + + defp digest_value(env) do + # case Tesla.get_header(env, "digest") + encoded_hash = :crypto.hash(:sha256, env.body) |> Base.encode64() + "SHA-256=" <> encoded_hash + end + + defp host_from_uri(%URI{port: port, scheme: scheme, host: host}) do + # https://httpwg.org/specs/rfc9110.html#field.host + # https://www.rfc-editor.org/rfc/rfc3986.html#section-3.2.3 + if port == URI.default_port(scheme) do + host + else + "#{host}:#{port}" + end + end + + defp http_date() do + now = NaiveDateTime.utc_now() + Timex.lformat!(now, "{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT", "en") + end +end diff --git a/lib/pleroma/http/request.ex b/lib/pleroma/http/request.ex deleted file mode 100644 index d906024de..000000000 --- a/lib/pleroma/http/request.ex +++ /dev/null @@ -1,23 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2021 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.HTTP.Request do - @moduledoc """ - Request struct. - """ - defstruct method: :get, url: "", query: [], headers: [], body: "", opts: [] - - @type method :: :head | :get | :delete | :trace | :options | :post | :put | :patch - @type url :: String.t() - @type headers :: [{String.t(), String.t()}] - - @type t :: %__MODULE__{ - method: method(), - url: url(), - query: keyword(), - headers: headers(), - body: String.t(), - opts: keyword() - } -end diff --git a/lib/pleroma/http/request_builder.ex b/lib/pleroma/http/request_builder.ex deleted file mode 100644 index 4cd75d3a0..000000000 --- a/lib/pleroma/http/request_builder.ex +++ /dev/null @@ -1,102 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2021 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.HTTP.RequestBuilder do - @moduledoc """ - Helper functions for building Tesla requests - """ - - alias Pleroma.HTTP.Request - alias Tesla.Multipart - - @mix_env Mix.env() - - @doc """ - Creates new request - """ - @spec new(Request.t()) :: Request.t() - def new(%Request{} = request \\ %Request{}), do: request - - @doc """ - Specify the request method when building a request - """ - @spec method(Request.t(), Request.method()) :: Request.t() - def method(request, m), do: %{request | method: m} - - @doc """ - Specify the request method when building a request - """ - @spec url(Request.t(), Request.url()) :: Request.t() - def url(request, u), do: %{request | url: u} - - @doc """ - Add headers to the request - """ - @spec headers(Request.t(), Request.headers()) :: Request.t() - def headers(request, headers) do - headers_list = maybe_add_user_agent(headers, @mix_env) - - %{request | headers: headers_list} - end - - @doc """ - Add custom, per-request middleware or adapter options to the request - """ - @spec opts(Request.t(), keyword()) :: Request.t() - def opts(request, options), do: %{request | opts: options} - - @doc """ - Add optional parameters to the request - """ - @spec add_param(Request.t(), atom(), atom(), any()) :: Request.t() - def add_param(request, :query, :query, values), do: %{request | query: values} - - def add_param(request, :body, :body, value), do: %{request | body: value} - - def add_param(request, :body, key, value) do - request - |> Map.put(:body, Multipart.new()) - |> Map.update!( - :body, - &Multipart.add_field( - &1, - key, - Jason.encode!(value), - headers: [{"content-type", "application/json"}] - ) - ) - end - - def add_param(request, :file, name, path) do - request - |> Map.put(:body, Multipart.new()) - |> Map.update!(:body, &Multipart.add_file(&1, path, name: name)) - end - - def add_param(request, :form, name, value) do - Map.update(request, :body, %{name => value}, &Map.put(&1, name, value)) - end - - def add_param(request, location, key, value) do - Map.update(request, location, [{key, value}], &(&1 ++ [{key, value}])) - end - - def convert_to_keyword(request) do - request - |> Map.from_struct() - |> Enum.into([]) - end - - defp maybe_add_user_agent(headers, :test) do - with true <- Pleroma.Config.get([:http, :send_user_agent]) do - [{"user-agent", Pleroma.Application.user_agent()} | headers] - else - _ -> - headers - end - end - - defp maybe_add_user_agent(headers, _), - do: [{"user-agent", Pleroma.Application.user_agent()} | headers] -end diff --git a/lib/pleroma/http/tzdata.ex b/lib/pleroma/http/tzdata.ex index a208b9be3..691636c7f 100644 --- a/lib/pleroma/http/tzdata.ex +++ b/lib/pleroma/http/tzdata.ex @@ -10,15 +10,15 @@ defmodule Pleroma.HTTP.Tzdata do alias Pleroma.HTTP @impl true - def get(url, headers, options) do - with {:ok, %Tesla.Env{} = env} <- HTTP.get(url, headers, options) do + def get(url, headers, _options) do + with {:ok, %Tesla.Env{} = env} <- HTTP.get(url, headers) do {:ok, {env.status, env.headers, env.body}} end end @impl true - def head(url, headers, options) do - with {:ok, %Tesla.Env{} = env} <- HTTP.head(url, headers, options) do + def head(url, headers, _options) do + with {:ok, %Tesla.Env{} = env} <- HTTP.head(url, headers) do {:ok, {env.status, env.headers}} end end diff --git a/lib/pleroma/http/web_push.ex b/lib/pleroma/http/web_push.ex index 16bbe6e8c..f2b0fb093 100644 --- a/lib/pleroma/http/web_push.ex +++ b/lib/pleroma/http/web_push.ex @@ -5,8 +5,8 @@ defmodule Pleroma.HTTP.WebPush do @moduledoc false - def post(url, payload, headers, options \\ []) do + def post(url, payload, headers, _options) do list_headers = Map.to_list(headers) - Pleroma.HTTP.post(url, payload, list_headers, options) + Pleroma.HTTP.post(url, payload, list_headers) end end diff --git a/lib/pleroma/instances.ex b/lib/pleroma/instances.ex index daea102db..6b57e56da 100644 --- a/lib/pleroma/instances.ex +++ b/lib/pleroma/instances.ex @@ -43,6 +43,4 @@ def host(url_or_host) when is_binary(url_or_host) do url_or_host end end - - defdelegate set_request_signatures(url_or_host), to: Instance end diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex index 105556900..6113d53bd 100644 --- a/lib/pleroma/instances/instance.ex +++ b/lib/pleroma/instances/instance.ex @@ -26,7 +26,6 @@ defmodule Pleroma.Instances.Instance do field(:favicon, :string) field(:metadata_updated_at, :naive_datetime) field(:nodeinfo, :map, default: %{}) - field(:has_request_signatures, :boolean) timestamps() end @@ -40,8 +39,7 @@ def changeset(struct, params \\ %{}) do :unreachable_since, :favicon, :nodeinfo, - :metadata_updated_at, - :has_request_signatures + :metadata_updated_at ]) |> validate_required([:host]) |> unique_constraint(:host) @@ -244,7 +242,7 @@ defp scrape_nodeinfo(%URI{} = instance_uri) do {:ok, Enum.find(links, &(&1["rel"] == "http://nodeinfo.diaspora.software/ns/schema/2.0"))}, {:ok, %Tesla.Env{body: data}} <- - Pleroma.HTTP.get(href, [{"accept", "application/json"}], []), + Pleroma.HTTP.get(href, [{"accept", "application/json"}]), {:length, true} <- {:length, String.length(data) < 50_000}, {:ok, nodeinfo} <- Jason.decode(data) do nodeinfo @@ -272,7 +270,7 @@ defp scrape_favicon(%URI{} = instance_uri) do with true <- Pleroma.Config.get([:instances_favicons, :enabled]), {_, true} <- {:reachable, reachable?(instance_uri.host)}, {:ok, %Tesla.Env{body: html}} <- - Pleroma.HTTP.get(to_string(instance_uri), [{"accept", "text/html"}], []), + Pleroma.HTTP.get(to_string(instance_uri), [{"accept", "text/html"}]), {_, [favicon_rel | _]} when is_binary(favicon_rel) <- {:parse, html |> Floki.parse_document!() |> Floki.attribute("link[rel=icon]", "href")}, {_, favicon} when is_binary(favicon) <- @@ -332,24 +330,4 @@ def get_cached_by_url(url_or_host) do end) end end - - def set_request_signatures(url_or_host) when is_binary(url_or_host) do - host = host(url_or_host) - existing_record = Repo.get_by(Instance, %{host: host}) - changes = %{has_request_signatures: true} - - cond do - is_nil(existing_record) -> - %Instance{} - |> changeset(Map.put(changes, :host, host)) - |> Repo.insert() - - true -> - existing_record - |> changeset(changes) - |> Repo.update() - end - end - - def set_request_signatures(_), do: {:error, :invalid_input} end diff --git a/lib/pleroma/job_queue_monitor.ex b/lib/pleroma/job_queue_monitor.ex deleted file mode 100644 index 8d81ffcac..000000000 --- a/lib/pleroma/job_queue_monitor.ex +++ /dev/null @@ -1,95 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2021 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.JobQueueMonitor do - use GenServer - - @initial_state %{workers: %{}, queues: %{}, processed_jobs: 0} - @queue %{processed_jobs: 0, success: 0, failure: 0} - @operation %{processed_jobs: 0, success: 0, failure: 0} - - def start_link(_) do - GenServer.start_link(__MODULE__, @initial_state, name: __MODULE__) - end - - @impl true - def init(state) do - :telemetry.attach( - "oban-monitor-failure", - [:oban, :job, :exception], - &Pleroma.JobQueueMonitor.handle_event/4, - nil - ) - - :telemetry.attach( - "oban-monitor-success", - [:oban, :job, :stop], - &Pleroma.JobQueueMonitor.handle_event/4, - nil - ) - - {:ok, state} - end - - def stats do - GenServer.call(__MODULE__, :stats) - end - - def handle_event([:oban, :job, event], %{duration: duration}, meta, _) do - GenServer.cast( - __MODULE__, - {:process_event, mapping_status(event), duration, meta} - ) - end - - @impl true - def handle_call(:stats, _from, state) do - {:reply, state, state} - end - - @impl true - def handle_cast({:process_event, status, duration, meta}, state) do - state = - state - |> Map.update!(:workers, fn workers -> - workers - |> Map.put_new(meta.worker, %{}) - |> Map.update!(meta.worker, &update_worker(&1, status, meta, duration)) - end) - |> Map.update!(:queues, fn workers -> - workers - |> Map.put_new(meta.queue, @queue) - |> Map.update!(meta.queue, &update_queue(&1, status, meta, duration)) - end) - |> Map.update!(:processed_jobs, &(&1 + 1)) - - {:noreply, state} - end - - defp update_worker(worker, status, meta, duration) do - worker - |> Map.put_new(meta.args["op"], @operation) - |> Map.update!(meta.args["op"], &update_op(&1, status, meta, duration)) - end - - defp update_op(op, :enqueue, _meta, _duration) do - op - |> Map.update!(:enqueued, &(&1 + 1)) - end - - defp update_op(op, status, _meta, _duration) do - op - |> Map.update!(:processed_jobs, &(&1 + 1)) - |> Map.update!(status, &(&1 + 1)) - end - - defp update_queue(queue, status, _meta, _duration) do - queue - |> Map.update!(:processed_jobs, &(&1 + 1)) - |> Map.update!(status, &(&1 + 1)) - end - - defp mapping_status(:stop), do: :success - defp mapping_status(:exception), do: :failure -end diff --git a/lib/pleroma/logging.ex b/lib/pleroma/logging.ex deleted file mode 100644 index 11e1c3bed..000000000 --- a/lib/pleroma/logging.ex +++ /dev/null @@ -1,7 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2021 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Logging do - @callback error(String.t()) :: any() -end diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index d496cb23f..4a0a66a6e 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -144,7 +144,7 @@ defp warn_on_no_object_preloaded(ap_id) do Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}") end - def normalize(_, options \\ [fetch: false, id_only: false]) + def normalize(_, options \\ [fetch: false]) # If we pass an Activity to Object.normalize(), we can try to use the preloaded object. # Use this whenever possible, especially when walking graphs in an O(N) loop! @@ -173,9 +173,6 @@ def normalize(%{"id" => ap_id}, options), do: normalize(ap_id, options) def normalize(ap_id, options) when is_binary(ap_id) do cond do - Keyword.get(options, :id_only) -> - ap_id - Keyword.get(options, :fetch) -> case Fetcher.fetch_object_from_id(ap_id, options) do {:ok, object} -> object @@ -365,28 +362,6 @@ def local?(%Object{data: %{"id" => id}}) do String.starts_with?(id, Pleroma.Web.Endpoint.url() <> "/") end - def replies(object, opts \\ []) do - object = Object.normalize(object, fetch: false) - - query = - Object - |> where( - [o], - fragment("(?)->>'inReplyTo' = ?", o.data, ^object.data["id"]) - ) - |> order_by([o], asc: o.id) - - if opts[:self_only] do - actor = object.data["actor"] - where(query, [o], fragment("(?)->>'actor' = ?", o.data, ^actor)) - else - query - end - end - - def self_replies(object, opts \\ []), - do: replies(object, Keyword.put(opts, :self_only, true)) - def tags(%Object{data: %{"tag" => tags}}) when is_list(tags), do: tags def tags(_), do: [] diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index a9e6e508c..8810135fb 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -9,7 +9,6 @@ defmodule Pleroma.Object.Fetcher do alias Pleroma.Object alias Pleroma.Object.Containment alias Pleroma.Repo - alias Pleroma.Signature alias Pleroma.Web.ActivityPub.InternalFetchActor alias Pleroma.Web.ActivityPub.ObjectValidator alias Pleroma.Web.ActivityPub.Transmogrifier @@ -227,36 +226,6 @@ defp prepare_activity_params(data) do |> Maps.put_if_present("bcc", data["bcc"]) end - defp make_signature(id, date) do - uri = URI.parse(id) - - signature = - InternalFetchActor.get_actor() - |> Signature.sign(%{ - "(request-target)": "get #{uri.path}", - host: uri.host, - date: date - }) - - {"signature", signature} - end - - defp sign_fetch(headers, id, date) do - if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do - [make_signature(id, date) | headers] - else - headers - end - end - - defp maybe_date_fetch(headers, date) do - if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do - [{"date", date} | headers] - else - headers - end - end - @doc """ Fetches arbitrary remote object and performs basic safety and authenticity checks. When the fetch URL is known to already be a canonical AP id, checks are stricter. @@ -402,20 +371,25 @@ defp get_final_id(final_url, _intial_url) do @doc "Do NOT use; only public for use in tests" def get_object(id) do - date = Pleroma.Signature.signed_date() - headers = [ # The first is required by spec, the second provided as a fallback for buggy implementations {"accept", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""}, {"accept", "application/activity+json"} ] - |> maybe_date_fetch(date) - |> sign_fetch(id, date) + + http_opts = + if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do + signing_actor = InternalFetchActor.get_actor() |> Pleroma.User.SigningKey.load_key() + signing_key = signing_actor.signing_key + [httpsig: %{signing_key: signing_key}] + else + [] + end with {:ok, %{body: body, status: code, headers: headers, url: final_url}} when code in 200..299 <- - HTTP.Backoff.get(id, headers), + HTTP.Backoff.get(id, headers, http_opts), {:has_content_type, {_, content_type}} <- {:has_content_type, List.keyfind(headers, "content-type", 0)}, {:parse_content_type, {:ok, "application", subtype, type_params}} <- @@ -443,6 +417,13 @@ def get_object(id) do {:ok, %{status: code}} when code in [404, 410] -> {:error, :not_found} + {:ok, %{status: code, headers: headers}} -> + {:error, {:http_error, code, headers}} + + # connection/protocol-related error + {:ok, %Tesla.Env{} = env} -> + {:error, {:http_error, :connect, env}} + {:error, e} -> {:error, e} diff --git a/lib/pleroma/pagination.ex b/lib/pleroma/pagination.ex index 28e37933e..f70e281a9 100644 --- a/lib/pleroma/pagination.ex +++ b/lib/pleroma/pagination.ex @@ -86,6 +86,16 @@ def paginate(query, options, :offset, table_binding) do |> restrict(:limit, options, table_binding) end + @doc """ + Unwraps a result list for a query paginated by a foreign id. + Usually you want to keep those foreign ids around until after pagination Link headers got generated. + """ + @spec unwrap([%{id: any(), entry: any()}]) :: [any()] + def unwrap(list) when is_list(list), do: do_unwrap(list, []) + + defp do_unwrap([%{entry: entry} | rest], acc), do: do_unwrap(rest, [entry | acc]) + defp do_unwrap([], acc), do: Enum.reverse(acc) + defp cast_params(params) do param_types = %{ min_id: params[:id_type] || :string, @@ -94,13 +104,31 @@ defp cast_params(params) do offset: :integer, limit: :integer, skip_extra_order: :boolean, - skip_order: :boolean + skip_order: :boolean, + order_asc: :boolean } + params = Map.delete(params, :id_type) changeset = cast({%{}, param_types}, params, Map.keys(param_types)) changeset.changes end + defp order_statement(query, table_binding, :asc) do + order_by( + query, + [{u, table_position(query, table_binding)}], + fragment("? asc nulls last", u.id) + ) + end + + defp order_statement(query, table_binding, :desc) do + order_by( + query, + [{u, table_position(query, table_binding)}], + fragment("? desc nulls last", u.id) + ) + end + defp restrict(query, :min_id, %{min_id: min_id}, table_binding) do where(query, [{q, table_position(query, table_binding)}], q.id > ^min_id) end @@ -118,19 +146,16 @@ defp restrict(query, :order, %{skip_order: true}, _), do: query defp restrict(%{order_bys: [_ | _]} = query, :order, %{skip_extra_order: true}, _), do: query defp restrict(query, :order, %{min_id: _}, table_binding) do - order_by( - query, - [{u, table_position(query, table_binding)}], - fragment("? asc nulls last", u.id) - ) + order_statement(query, table_binding, :asc) end - defp restrict(query, :order, _options, table_binding) do - order_by( - query, - [{u, table_position(query, table_binding)}], - fragment("? desc nulls last", u.id) - ) + defp restrict(query, :order, %{max_id: _}, table_binding) do + order_statement(query, table_binding, :desc) + end + + defp restrict(query, :order, options, table_binding) do + dir = if options[:order_asc], do: :asc, else: :desc + order_statement(query, table_binding, dir) end defp restrict(query, :offset, %{offset: offset}, _table_binding) do @@ -150,11 +175,9 @@ defp restrict(query, :limit, options, _table_binding) do defp restrict(query, _, _, _), do: query - defp enforce_order(result, %{min_id: _}) do - result - |> Enum.reverse() - end - + defp enforce_order(result, %{min_id: _, order_asc: true}), do: result + defp enforce_order(result, %{min_id: _}), do: Enum.reverse(result) + defp enforce_order(result, %{max_id: _, order_asc: true}), do: Enum.reverse(result) defp enforce_order(result, _), do: result defp table_position(%Ecto.Query{} = query, binding_name) do diff --git a/lib/pleroma/reverse_proxy.ex b/lib/pleroma/reverse_proxy.ex index f017bf51b..0a9f7a1f9 100644 --- a/lib/pleroma/reverse_proxy.ex +++ b/lib/pleroma/reverse_proxy.ex @@ -109,7 +109,9 @@ def call(conn = %{method: method}, url, opts) when method in @methods do with {:ok, nil} <- @cachex.get(:failed_proxy_url_cache, url), {:ok, status, headers, body} <- request(method, url, req_headers, client_opts), :ok <- - header_length_constraint( + check_length_constraint( + method, + body, headers, Keyword.get(opts, :max_body_length, @max_body_length) ) do @@ -342,7 +344,9 @@ defp build_csp_headers(headers) do List.keystore(headers, "content-security-policy", 0, {"content-security-policy", "sandbox"}) end - defp header_length_constraint(headers, limit) when is_integer(limit) and limit > 0 do + defp check_length_constraint(_, _, _, limit) when not is_integer(limit) or limit <= 0, do: :ok + + defp check_length_constraint(:head, _, headers, limit) do with {_, size} <- List.keyfind(headers, "content-length", 0), {size, _} <- Integer.parse(size), true <- size <= limit do @@ -356,7 +360,15 @@ defp header_length_constraint(headers, limit) when is_integer(limit) and limit > end end - defp header_length_constraint(_, _), do: :ok + defp check_length_constraint(_, body, _, limit) when is_binary(body) do + if byte_size(body) <= limit do + :ok + else + {:error, :body_too_large} + end + end + + defp check_length_constraint(_, _, _, _), do: :ok defp track_failed_url(url, error, opts) do ttl = @@ -366,6 +378,6 @@ defp track_failed_url(url, error, opts) do nil end - @cachex.put(:failed_proxy_url_cache, url, true, ttl: ttl) + @cachex.put(:failed_proxy_url_cache, url, true, expire: ttl) end end diff --git a/lib/pleroma/reverse_proxy/client.ex b/lib/pleroma/reverse_proxy/client.ex deleted file mode 100644 index 75243d2dc..000000000 --- a/lib/pleroma/reverse_proxy/client.ex +++ /dev/null @@ -1,20 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2021 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.ReverseProxy.Client do - @type status :: pos_integer() - @type header_name :: String.t() - @type header_value :: String.t() - @type headers :: [{header_name(), header_value()}] - - @callback request(atom(), String.t(), headers(), String.t(), list()) :: - {:ok, status(), headers(), reference() | map()} - | {:ok, status(), headers()} - | {:ok, reference()} - | {:error, term()} - - @callback stream_body(map()) :: {:ok, binary(), map()} | :done | {:error, atom() | String.t()} - - @callback close(reference() | pid() | map()) :: :ok -end diff --git a/lib/pleroma/reverse_proxy/client/tesla.ex b/lib/pleroma/reverse_proxy/client/tesla.ex deleted file mode 100644 index a4fc1ebc2..000000000 --- a/lib/pleroma/reverse_proxy/client/tesla.ex +++ /dev/null @@ -1,77 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2021 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.ReverseProxy.Client.Tesla do - @behaviour Pleroma.ReverseProxy.Client - - @type headers() :: [{String.t(), String.t()}] - @type status() :: pos_integer() - - @spec request(atom(), String.t(), headers(), String.t(), keyword()) :: - {:ok, status(), headers} - | {:ok, status(), headers, map()} - | {:error, atom() | String.t()} - | no_return() - - @impl true - def request(method, url, headers, body, opts \\ []) do - check_adapter() - - opts = Keyword.put(opts, :body_as, :chunks) - - with {:ok, response} <- - Pleroma.HTTP.request( - method, - url, - body, - headers, - opts - ) do - if is_map(response.body) and method != :head do - {:ok, response.status, response.headers, response.body} - else - {:ok, response.status, response.headers} - end - else - {:error, error} -> {:error, error} - end - end - - @impl true - @spec stream_body(map()) :: - {:ok, binary(), map()} | {:error, atom() | String.t()} | :done | no_return() - def stream_body(%{pid: _pid, fin: true}) do - :done - end - - def stream_body(client) do - case read_chunk!(client) do - {:fin, body} -> - {:ok, body, Map.put(client, :fin, true)} - - {:nofin, part} -> - {:ok, part, client} - - {:error, error} -> - {:error, error} - end - end - - defp read_chunk!(%{pid: pid, stream: stream, opts: opts}) do - adapter = check_adapter() - adapter.read_chunk(pid, stream, opts) - end - - @impl true - @spec close(map) :: :ok | no_return() - def close(%{pid: _pid}) do - :ok - end - - defp check_adapter do - adapter = Application.get_env(:tesla, :adapter) - - adapter - end -end diff --git a/lib/pleroma/reverse_proxy/client/wrapper.ex b/lib/pleroma/reverse_proxy/client/wrapper.ex deleted file mode 100644 index b9a05ce11..000000000 --- a/lib/pleroma/reverse_proxy/client/wrapper.ex +++ /dev/null @@ -1,28 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2021 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.ReverseProxy.Client.Wrapper do - @moduledoc "Meta-client that calls the appropriate client from the config." - @behaviour Pleroma.ReverseProxy.Client - - @impl true - def request(method, url, headers, body \\ "", opts \\ []) do - client().request(method, url, headers, body, opts) - end - - @impl true - def stream_body(ref), do: client().stream_body(ref) - - @impl true - def close(ref), do: client().close(ref) - - defp client do - :tesla - |> Application.get_env(:adapter) - |> client() - end - - defp client({Tesla.Adapter.Finch, _}), do: Pleroma.ReverseProxy.Client.Tesla - defp client(_), do: Pleroma.Config.get!(Pleroma.ReverseProxy.Client) -end diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex index f926f1aa6..dfcb82f9e 100644 --- a/lib/pleroma/signature.ex +++ b/lib/pleroma/signature.ex @@ -5,58 +5,73 @@ defmodule Pleroma.Signature do @behaviour HTTPSignatures.Adapter + alias HTTPSignatures.HTTPKey alias Pleroma.User alias Pleroma.User.SigningKey require Logger - def fetch_public_key(conn) do - with {_, %{"keyId" => kid}} <- {:keyid, HTTPSignatures.signature_for_conn(conn)}, - {_, {:ok, %SigningKey{} = sk}, _} <- - {:fetch, SigningKey.get_or_fetch_by_key_id(kid), kid}, + def fetch_public_key(kid, _) do + with {_, {:ok, %SigningKey{} = sk}} <- {:fetch, SigningKey.get_or_fetch_by_key_id(kid)}, + {_, {%User{} = key_user, _}} <- {:user, {User.get_by_id(sk.user_id), sk.user_id}}, {_, {:ok, decoded_key}} <- {:decode, SigningKey.public_key_decoded(sk)} do - {:ok, decoded_key} + {:ok, %HTTPKey{key: decoded_key, user_data: %{"key_user" => key_user}}} else - {:fetch, error, kid} -> - Logger.error("Failed to acquire key from signature: #{kid} #{inspect(error)}") - {:error, {:fetch, error}} - e -> - {:error, e} + handle_common_errors(e, kid, "acquire") end end - def refetch_public_key(conn) do - with {_, %{"keyId" => kid}} <- {:keyid, HTTPSignatures.signature_for_conn(conn)}, - {_, {:ok, %SigningKey{} = sk}, _} <- {:fetch, SigningKey.refresh_by_key_id(kid), kid}, + def refetch_public_key(kid, _) do + with {_, {:ok, %SigningKey{} = sk}} <- {:fetch, SigningKey.refresh_by_key_id(kid)}, + {_, {%User{} = key_user, _}} <- {:user, {User.get_by_id(sk.user_id), sk.user_id}}, {_, {:ok, decoded_key}} <- {:decode, SigningKey.public_key_decoded(sk)} do - {:ok, decoded_key} + {:ok, %HTTPKey{key: decoded_key, user_data: %{"key_user" => key_user}}} else - {:fetch, {:error, :too_young}, kid} -> + {:fetch, {:error, :too_young}} -> Logger.debug("Refusing to refetch recently updated key: #{kid}") - {:error, {:fetch, :too_young}} + {:error, {:too_young, kid}} - {:fetch, {:error, :unknown}, kid} -> + {:fetch, {:error, :unknown}} -> Logger.warning("Attempted to refresh unknown key; this should not happen: #{kid}") - {:error, {:fetch, :unknown}} + {:error, {:unknown, kid}} - {:fetch, error, kid} -> - Logger.error("Failed to refresh stale key from signature: #{kid} #{inspect(error)}") + e -> + handle_common_errors(e, kid, "refresh stale") + end + end + + defp handle_common_errors(error, kid, action_name) do + case error do + {:fetch, {:error, :not_found}} -> + {:halt, {:error, :gone}} + + {:fetch, {:reject, reason}} -> + {:halt, {:error, {:reject, reason}}} + + {:fetch, error} -> + Logger.error("Failed to #{action_name} key from signature: #{kid} #{inspect(error)}") {:error, {:fetch, error}} + {:user, {_, uid}} -> + Logger.warning( + "Failed to resolve user (id=#{uid}) for retrieved signing key. Race condition?" + ) + e -> {:error, e} end end - def sign(%User{} = user, headers) do - with {:ok, private_key} <- SigningKey.private_key(user) do - HTTPSignatures.sign(private_key, SigningKey.local_key_id(user.ap_id), headers) + def sign(%SigningKey{} = key, headers, opts \\ []) do + with {:ok, private_key_binary} <- SigningKey.private_key_binary(key) do + HTTPSignatures.sign( + %HTTPKey{key: private_key_binary}, + key.key_id, + headers, + opts + ) + else + _ -> raise "Tried to sign with #{key.key_id} but it has no private key!" end end - - def signed_date, do: signed_date(NaiveDateTime.utc_now()) - - def signed_date(%NaiveDateTime{} = date) do - Timex.lformat!(date, "{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT", "en") - end end diff --git a/lib/pleroma/stats.ex b/lib/pleroma/stats.ex index f33c378dd..8fd021133 100644 --- a/lib/pleroma/stats.ex +++ b/lib/pleroma/stats.ex @@ -7,7 +7,6 @@ defmodule Pleroma.Stats do import Ecto.Query - alias Pleroma.CounterCache alias Pleroma.Repo alias Pleroma.User alias Pleroma.Instances.Instance @@ -107,15 +106,6 @@ def calculate_stat_data do } end - @spec get_status_visibility_count(String.t() | nil) :: map() - def get_status_visibility_count(instance \\ nil) do - if is_nil(instance) do - CounterCache.get_sum() - else - CounterCache.get_by_instance(instance) - end - end - @impl true def handle_continue(:calculate_stats, _) do stats = calculate_stat_data() diff --git a/lib/pleroma/uploaders/uploader.ex b/lib/pleroma/uploaders/uploader.ex index deba548b7..9e3aeafac 100644 --- a/lib/pleroma/uploaders/uploader.ex +++ b/lib/pleroma/uploaders/uploader.ex @@ -3,7 +3,8 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Uploaders.Uploader do - import Pleroma.Web.Gettext + use Gettext, + backend: Pleroma.Web.Gettext @mix_env Mix.env() diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index f7a9c82f8..3998f6226 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -91,6 +91,9 @@ defmodule Pleroma.User do @cachex Pleroma.Config.get([:cachex, :provider], Cachex) + # hide sensitive data from logs + @derive {Inspect, except: [:password, :password_hash, :email]} + schema "users" do field(:bio, :string, default: "") field(:raw_bio, :string) @@ -270,13 +273,13 @@ def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \ def cached_blocked_users_ap_ids(user) do @cachex.fetch!(:user_cache, "blocked_users_ap_ids:#{user.ap_id}", fn _ -> - blocked_users_ap_ids(user) + {:commit, blocked_users_ap_ids(user)} end) end def cached_muted_users_ap_ids(user) do @cachex.fetch!(:user_cache, "muted_users_ap_ids:#{user.ap_id}", fn _ -> - muted_users_ap_ids(user) + {:commit, muted_users_ap_ids(user)} end) end @@ -286,11 +289,6 @@ def cached_muted_users_ap_ids(user) do defdelegate following_ap_ids(user), to: FollowingRelationship defdelegate get_follow_requests_query(user), to: FollowingRelationship - def get_follow_requests(user) do - get_follow_requests_query(user) - |> Repo.all() - end - defdelegate search(query, opts \\ []), to: User.Search @doc """ @@ -720,13 +718,6 @@ def force_password_reset(user), do: update_password_reset_pending(user, true) # Used to auto-register LDAP accounts which won't have a password hash stored locally def register_changeset_ldap(struct, params = %{password: password}) when is_nil(password) do - params = - if Map.has_key?(params, :email) do - Map.put_new(params, :email, params[:email]) - else - params - end - struct |> cast(params, [ :name, @@ -1174,7 +1165,7 @@ def get_user_friends_ap_ids(user) do @spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()] def get_cached_user_friends_ap_ids(user) do @cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ -> - get_user_friends_ap_ids(user) + {:commit, get_user_friends_ap_ids(user)} end) end @@ -1484,17 +1475,17 @@ def get_recipients_from_activity(%Activity{recipients: to, actor: actor}) do {:ok, list(UserRelationship.t())} | {:error, String.t()} def mute(%User{} = muter, %User{} = mutee, params \\ %{}) do notifications? = Map.get(params, :notifications, true) - expires_in = Map.get(params, :expires_in, 0) + duration = Map.get(params, :duration, 0) with {:ok, user_mute} <- UserRelationship.create_mute(muter, mutee), {:ok, user_notification_mute} <- (notifications? && UserRelationship.create_notification_mute(muter, mutee)) || {:ok, nil} do - if expires_in > 0 do + if duration > 0 do Pleroma.Workers.MuteExpireWorker.enqueue( "unmute_user", %{"muter_id" => muter.id, "mutee_id" => mutee.id}, - schedule_in: expires_in + schedule_in: duration ) end diff --git a/lib/pleroma/user/backup.ex b/lib/pleroma/user/backup.ex index 21c2948a0..1af4bb2ef 100644 --- a/lib/pleroma/user/backup.ex +++ b/lib/pleroma/user/backup.ex @@ -7,7 +7,9 @@ defmodule Pleroma.User.Backup do import Ecto.Changeset import Ecto.Query - import Pleroma.Web.Gettext + + use Gettext, + backend: Pleroma.Web.Gettext require Pleroma.Constants diff --git a/lib/pleroma/user/signing_key.ex b/lib/pleroma/user/signing_key.ex index 9d052b6bf..dd7ef1813 100644 --- a/lib/pleroma/user/signing_key.ex +++ b/lib/pleroma/user/signing_key.ex @@ -8,6 +8,7 @@ defmodule Pleroma.User.SigningKey do require Logger + @derive {Inspect, only: [:user_id, :key_id]} @primary_key false schema "signing_keys" do belongs_to(:user, Pleroma.User, type: FlakeId.Ecto.CompatType) @@ -136,24 +137,22 @@ def public_key_pem(_e) do {:error, "key not found"} end - @spec private_key(User.t()) :: {:ok, binary()} | {:error, String.t()} + @spec private_key_binary(__MODULE__) :: {:ok, binary()} | {:error, String.t()} @doc """ - Given a user, return the private key for that user in binary format. + Given a key, return the corresponding private key in binary format. """ - def private_key(%User{} = user) do - case Repo.preload(user, :signing_key) do - %{signing_key: %__MODULE__{private_key: private_key_pem}} -> - key = - private_key_pem - |> :public_key.pem_decode() - |> hd() - |> :public_key.pem_entry_decode() + def private_key_binary(%__MODULE__{private_key: private_key_pem}) do + key = + private_key_pem + |> :public_key.pem_decode() + |> hd() + |> :public_key.pem_entry_decode() - {:ok, key} + {:ok, key} + end - _ -> - {:error, "key not found"} - end + def private_key_binary(%__MODULE__{} = key) do + {:error, "key #{key.key_id} has no private key"} end @spec get_or_fetch_by_key_id(String.t()) :: {:ok, __MODULE__} | {:error, String.t()} @@ -208,7 +207,12 @@ def fetch_remote_key(key_id) do else e -> Logger.debug("Failed to fetch remote key: #{inspect(e)}") - {:error, "Could not fetch key"} + + case e do + {:error, e} -> {:error, e} + {:reject, reason} -> {:reject, reason} + _ -> {:error, {"Could not fetch key", e}} + end end end diff --git a/lib/pleroma/user_relationship.ex b/lib/pleroma/user_relationship.ex index 2f77697d4..07042a617 100644 --- a/lib/pleroma/user_relationship.ex +++ b/lib/pleroma/user_relationship.ex @@ -67,7 +67,7 @@ def create(relationship_type, %User{} = source, %User{} = target) do target_id: target.id }) |> Repo.insert( - on_conflict: {:replace_all_except, [:id, :inserted_at]}, + on_conflict: {:replace, [:relationship_type, :source_id, :target_id]}, conflict_target: [:source_id, :relationship_type, :target_id], returning: true ) diff --git a/lib/pleroma/web.ex b/lib/pleroma/web.ex index 5422e7896..e58b8a017 100644 --- a/lib/pleroma/web.ex +++ b/lib/pleroma/web.ex @@ -31,21 +31,19 @@ defmodule Pleroma.Web do def controller do quote do - use Phoenix.Controller, namespace: Pleroma.Web + use Phoenix.Controller, + formats: [html: "View", json: "View"], + layouts: [html: Pleroma.Web.LayoutView] import Plug.Conn - import Pleroma.Web.Gettext + use Gettext, + backend: Pleroma.Web.Gettext + import Pleroma.Web.TranslationHelpers unquote(verified_routes()) - plug(:set_put_layout) - - defp set_put_layout(conn, _) do - put_layout(conn, Pleroma.Config.get(:app_layout, "app.html")) - end - # Marks plugs intentionally skipped and blocks their execution if present in plugs chain defp skip_plug(conn, plug_modules) do plug_modules @@ -233,14 +231,18 @@ def router do def channel do quote do use Phoenix.Channel - import Pleroma.Web.Gettext + + use Gettext, + backend: Pleroma.Web.Gettext end end defp view_helpers do quote do # Use all HTML functionality (forms, tags, etc) - use Phoenix.HTML + import Phoenix.HTML + import Phoenix.HTML.Form + use PhoenixHTMLHelpers # Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc) import Phoenix.LiveView.Helpers @@ -249,7 +251,10 @@ defp view_helpers do import Phoenix.View import Pleroma.Web.ErrorHelpers - import Pleroma.Web.Gettext + + use Gettext, + backend: Pleroma.Web.Gettext + unquote(verified_routes()) end end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 746d18639..f3077b242 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -509,6 +509,28 @@ def fetch_activities_for_context(context, opts \\ %{}) do |> Repo.all() end + def fetch_objects_for_replies_collection(parent_ap_id, opts \\ %{}) do + opts = + opts + |> Map.put(:order_asc, true) + |> Map.put(:id_type, :integer) + + from(o in Object, + where: + fragment("?->>'inReplyTo' = ?", o.data, ^parent_ap_id) and + fragment( + "(?->'to' \\? ?::text OR ?->'cc' \\? ?::text)", + o.data, + ^Pleroma.Constants.as_public(), + o.data, + ^Pleroma.Constants.as_public() + ) and + fragment("?->>'type' <> 'Answer'", o.data), + select: %{id: o.id, ap_id: fragment("?->>'id'", o.data)} + ) + |> Pagination.fetch_paginated(opts, :keyset) + end + @spec fetch_latest_direct_activity_id_for_context(String.t(), keyword() | map()) :: FlakeId.Ecto.CompatType.t() | nil def fetch_latest_direct_activity_id_for_context(context, opts \\ %{}) do @@ -1445,21 +1467,18 @@ def fetch_activities_query(recipients, opts \\ %{}) do end @doc """ - Fetch favorites activities of user with order by sort adds to favorites + Fetch posts liked by the given user wrapped in a paginated list with IDs taken from the like activity """ - @spec fetch_favourites(User.t(), map(), Pagination.type()) :: list(Activity.t()) - def fetch_favourites(user, params \\ %{}, pagination \\ :keyset) do + @spec fetch_favourited_with_fav_id(User.t(), map()) :: + list(%{id: binary(), entry: Activity.t()}) + def fetch_favourited_with_fav_id(user, params \\ %{}) do user.ap_id |> Activity.Queries.by_actor() |> Activity.Queries.by_type("Like") |> Activity.with_joined_object() |> Object.with_joined_activity() - |> select([like, object, activity], %{activity | object: object, pagination_id: like.id}) - |> order_by([like, _, _], desc_nulls_last: like.id) - |> Pagination.fetch_paginated( - Map.merge(params, %{skip_order: true}), - pagination - ) + |> select([like, object, create], %{id: like.id, entry: %{create | object: object}}) + |> Pagination.fetch_paginated(params, :keyset) end defp maybe_update_cc(activities, [_ | _] = list_memberships, %User{ap_id: user_ap_id}) do @@ -1586,8 +1605,11 @@ defp object_to_user_data(data, additional) do invisible = data["invisible"] || false actor_type = data["type"] || "Person" - featured_address = data["featured"] - {:ok, pinned_objects} = fetch_and_prepare_featured_from_ap_id(featured_address) + {featured_address, pinned_objects} = + case process_featured_collection(data["featured"]) do + {:ok, featured_address, pinned_objects} -> {featured_address, pinned_objects} + _ -> {nil, %{}} + end # first, check that the owner is correct signing_key = @@ -1786,57 +1808,35 @@ def maybe_handle_clashing_nickname(data) do end end - def pin_data_from_featured_collection(%{ - "type" => "OrderedCollection", - "first" => first - }) do - with {:ok, page} <- Fetcher.fetch_and_contain_remote_object_from_id(first) do - page - |> Map.get("orderedItems") - |> Map.new(fn %{"id" => object_ap_id} -> {object_ap_id, NaiveDateTime.utc_now()} end) - else - e -> - Logger.error("Could not decode featured collection at fetch #{first}, #{inspect(e)}") - %{} - end - end + def process_featured_collection(nil), do: {:ok, nil, %{}} + def process_featured_collection(""), do: {:ok, nil, %{}} - def pin_data_from_featured_collection( - %{ - "type" => type - } = collection - ) - when type in ["OrderedCollection", "Collection"] do - with {:ok, objects} <- Collections.Fetcher.fetch_collection(collection) do - # Items can either be a map _or_ a string - objects - |> Map.new(fn - ap_id when is_binary(ap_id) -> {ap_id, NaiveDateTime.utc_now()} - %{"id" => object_ap_id} -> {object_ap_id, NaiveDateTime.utc_now()} - end) - else - e -> - Logger.warning("Failed to fetch featured collection #{collection}, #{inspect(e)}") - %{} - end - end + def process_featured_collection(featured_collection) do + featured_address = + case get_ap_id(featured_collection) do + id when is_binary(id) -> id + _ -> nil + end - def pin_data_from_featured_collection(obj) do - Logger.error("Could not parse featured collection #{inspect(obj)}") - %{} - end + # TODO: allow passing item/page limit as function opt and use here + case Collections.Fetcher.fetch_collection(featured_collection) do + {:ok, items} -> + now = NaiveDateTime.utc_now() + dated_obj_ids = Map.new(items, fn obj -> {get_ap_id(obj), now} end) + {:ok, featured_address, dated_obj_ids} - def fetch_and_prepare_featured_from_ap_id(nil) do - {:ok, %{}} - end + error -> + Logger.error( + "Could not decode featured collection at fetch #{inspect(featured_collection)}: #{inspect(error)}" + ) - def fetch_and_prepare_featured_from_ap_id(ap_id) do - with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id) do - {:ok, pin_data_from_featured_collection(data)} - else - e -> - Logger.error("Could not decode featured collection at fetch #{ap_id}, #{inspect(e)}") - {:ok, %{}} + error = + case error do + {:error, e} -> e + e -> e + end + + {:error, error} 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 9c6b3655d..9a347e2ea 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -22,8 +22,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do alias Pleroma.Web.Plugs.EnsureAuthenticatedPlug alias Pleroma.Web.Plugs.FederatingPlug - require Logger - action_fallback(:errors) @federating_only_actions [:internal_fetch, :relay, :relay_following, :relay_followers] @@ -64,7 +62,7 @@ defp relay_active?(conn, _) do Render the user's AP data WARNING: we cannot actually check if the request has a fragment! so let's play defensively - IF we have a valid signature, serve full user - - IF we do not, and authorized_fetch_mode is enabled, serve the key only + - IF we do not, and authorized_fetch_mode is enabled, serve only the key and bare minimum info - OTHERWISE, serve the full actor (since we don't need to worry about the signature) """ def user(%{assigns: %{valid_signature: true}} = conn, params) do @@ -96,7 +94,7 @@ def render_key_only_user(conn, %{"nickname" => nickname}) do conn |> put_resp_content_type("application/activity+json") |> put_view(UserView) - |> render("keys.json", %{user: user}) + |> render("stripped_user.json", %{user: user}) else nil -> {:error, :not_found} %{local: false} -> {:error, :not_found} @@ -121,6 +119,35 @@ def object(%{assigns: assigns} = conn, _) do end end + def object_replies(%{assigns: assigns, query_params: params} = conn, _all_params) do + object_ap_id = conn.path_info |> Enum.reverse() |> tl() |> Enum.reverse() + object_ap_id = Endpoint.url() <> "/" <> Enum.join(object_ap_id, "/") + + # Most other API params are converted to atoms by OpenAPISpex 3.x + # and therefore helper functions assume atoms. For consistency, + # also convert our params to atoms here. + params = + params + |> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end) + |> Map.put(:object_ap_id, object_ap_id) + |> Map.put(:order_asc, true) + |> Map.put(:conn, conn) + + with %Object{} = object <- Object.get_cached_by_ap_id(object_ap_id), + user <- Map.get(assigns, :user, nil), + {_, true} <- {:visible?, Visibility.visible_for_user?(object, user)} do + conn + |> maybe_skip_cache(user) + |> set_cache_ttl_for(object) + |> put_resp_content_type("application/activity+json") + |> put_view(ObjectView) + |> render("object_replies.json", render_params: params) + else + {:visible?, false} -> {:error, :not_found} + nil -> {:error, :not_found} + end + end + def track_object_fetch(conn, nil), do: conn def track_object_fetch(conn, object_id) do @@ -287,8 +314,7 @@ def outbox( |> put_view(UserView) |> render("activity_collection_page.json", %{ activities: activities, - pagination: ControllerHelper.get_pagination_fields(conn, activities), - iri: "#{user.ap_id}/outbox" + pagination: ControllerHelper.get_pagination_fields(conn, activities) }) end end @@ -304,8 +330,6 @@ def outbox(conn, %{"nickname" => nickname}) do def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do with %User{} = recipient <- User.get_cached_by_nickname(nickname), - {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]), - true <- Utils.recipient_in_message(recipient, actor, params), params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do Federator.incoming_ap_doc(params) json(conn, "ok") @@ -370,8 +394,7 @@ def read_inbox( |> put_view(UserView) |> render("activity_collection_page.json", %{ activities: activities, - pagination: ControllerHelper.get_pagination_fields(conn, activities), - iri: "#{user.ap_id}/inbox" + pagination: ControllerHelper.get_pagination_fields(conn, activities) }) end diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index e67a14b58..de8a58cee 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -316,21 +316,18 @@ def block(blocker, blocked) do @spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()} def announce(actor, object, options \\ []) do - public? = Keyword.get(options, :public, false) + visibility = Keyword.get(options, :visibility, "public") - to = - cond do - actor.ap_id == Relay.ap_id() -> - [actor.follower_address] - - public? and Visibility.is_local_public?(object) -> - [actor.follower_address, object.data["actor"], Utils.as_local_public()] - - public? -> - [actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()] - - true -> - [actor.follower_address, object.data["actor"]] + {to, cc} = + if actor.ap_id == Relay.ap_id() do + {[actor.follower_address], []} + else + Pleroma.Web.CommonAPI.Utils.get_to_and_cc_for_visibility( + visibility, + actor.follower_address, + nil, + [object.data["actor"]] + ) end {:ok, @@ -339,6 +336,7 @@ def announce(actor, object, options \\ []) do "actor" => actor.ap_id, "object" => object.data["id"], "to" => to, + "cc" => cc, "context" => object.data["context"], "type" => "Announce", "published" => Utils.make_date() diff --git a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex index e5449b576..f91c39862 100644 --- a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex @@ -11,8 +11,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do require Logger - @adapter_options [ - receive_timeout: 10_000 + @http_options [ + adapter: [receive_timeout: 10_000] ] @impl true @@ -36,7 +36,7 @@ defp prefetch(url) do end end - defp fetch(url), do: HTTP.get(url, [], @adapter_options) + defp fetch(url), do: HTTP.get(url, [], @http_options) defp preload(%{"object" => %{"attachment" => attachments}} = _message) do Enum.each(attachments, fn diff --git a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex index 151c6ed20..82d233740 100644 --- a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex +++ b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex @@ -11,17 +11,39 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do @impl true def history_awareness, do: :auto + def scrub_if_present(obj, field, scrubber) do + case obj[field] do + text when is_binary(text) -> + update_in(obj[field], &HTML.filter_tags(&1, scrubber)) + + map when is_map(map) -> + map = + Enum.into(map, %{}, fn + {k, v} when is_binary(v) -> + {k, HTML.filter_tags(v, scrubber)} + + {k, v} -> + {k, v} + end) + + put_in(obj[field], map) + + _ -> + obj + end + end + @impl true def filter(%{"type" => type, "object" => child_object} = object) when type in ["Create", "Update"] do scrub_policy = Pleroma.Config.get([:mrf_normalize_markup, :scrub_policy]) - content = - child_object["content"] - |> HTML.filter_tags(scrub_policy) - - object = put_in(object, ["object", "content"], content) + child_object = + child_object + |> scrub_if_present("content", scrub_policy) + |> scrub_if_present("contentMap", scrub_policy) + object = put_in(object["object"], child_object) {:ok, object} end 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 4649db2a1..8ea4f884d 100644 --- a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex @@ -112,7 +112,7 @@ defp get_int_header(headers, header_name, default \\ nil) do 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 + Pleroma.HTTP.head(url) do content_length = get_int_header(headers, "content-length") size_limit = Config.get([:mrf_steal_emoji, :size_limit], @size_limit) @@ -165,7 +165,6 @@ def filter(%{"object" => %{"emoji" => foreign_emojis, "actor" => actor}} = messa if !Enum.empty?(new_emojis) do Logger.info("Stole new emojis: #{inspect(new_emojis)}") - Pleroma.Emoji.reload() end end diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index 93980f35b..98f86a2b5 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -63,10 +63,16 @@ def validate(%{"type" => "Undo"} = object, meta) do |> Ecto.Changeset.apply_action(:insert) do object = stringify_keys(object) undone_object = Activity.get_by_ap_id(object["object"]) + outgoing_blocks = Pleroma.Config.get([:activitypub, :outgoing_blocks]) + # if we're undoing a block, and do not permit federating that: + do_not_federate = + Keyword.get(meta, :do_not_federate) || + (Map.get(undone_object.data, "type") == "Block" && !outgoing_blocks) meta = meta |> Keyword.put(:object_data, undone_object.data) + |> Keyword.put(:do_not_federate, do_not_federate) {:ok, object, meta} end diff --git a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex index e52986502..8bc9bc98f 100644 --- a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex @@ -69,6 +69,7 @@ defp fix_tag(%{"tag" => tag} = data) when is_list(tag) do defp fix_tag(%{"tag" => tag} = data) when is_map(tag), do: Map.put(data, "tag", [tag]) defp fix_tag(data), do: Map.drop(data, ["tag"]) + # legacy internal *oma format defp fix_replies(%{"replies" => replies} = data) when is_list(replies), do: data defp fix_replies(%{"replies" => %{"first" => first}} = data) when is_binary(first) do @@ -85,9 +86,16 @@ defp fix_replies(%{"replies" => %{"first" => %{"items" => replies}}} = data) when is_list(replies), do: Map.put(data, "replies", replies) + defp fix_replies(%{"replies" => %{"first" => %{"orderedItems" => replies}}} = data) + when is_list(replies), + do: Map.put(data, "replies", replies) + defp fix_replies(%{"replies" => %{"items" => replies}} = data) when is_list(replies), do: Map.put(data, "replies", replies) + defp fix_replies(%{"replies" => %{"orderedItems" => replies}} = data) when is_list(replies), + do: Map.put(data, "replies", replies) + defp fix_replies(data), do: Map.delete(data, "replies") defp remote_mention_resolver( @@ -116,6 +124,8 @@ defp remote_mention_resolver( end end + defp fix_misskey_content(object = %{"htmlMfm" => true}), do: object + # See https://akkoma.dev/FoundKeyGang/FoundKey/issues/343 # Misskey/Foundkey drops some of the custom formatting when it sends remotely # So this basically reprocesses the MFM source @@ -136,20 +146,13 @@ defp fix_misskey_content( # See https://github.com/misskey-dev/misskey/pull/8787 # This is for compatibility with older Misskey instances defp fix_misskey_content(%{"_misskey_content" => content} = object) when is_binary(content) do - mention_handler = fn nick, buffer, opts, acc -> - remote_mention_resolver(object, nick, buffer, opts, acc) - end - - {linked, _, _} = - Utils.format_input(content, "text/x.misskeymarkdown", mention_handler: mention_handler) - object |> Map.put("source", %{ "content" => content, "mediaType" => "text/x.misskeymarkdown" }) - |> Map.put("content", linked) |> Map.delete("_misskey_content") + |> fix_misskey_content() end defp fix_misskey_content(data), do: data diff --git a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex index dba18a3d0..949ede2b7 100644 --- a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex @@ -15,6 +15,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do field(:type, :string) field(:mediaType, :string, default: "application/octet-stream") field(:name, :string) + field(:summary, :string) field(:blurhash, :string) embeds_many :url, UrlObjectValidator, primary_key: false do @@ -44,7 +45,7 @@ def changeset(struct, data) do |> fix_url() struct - |> cast(data, [:id, :type, :mediaType, :name, :blurhash]) + |> cast(data, [:id, :type, :mediaType, :name, :summary, :blurhash]) |> cast_embed(:url, with: &url_changeset/2, required: true) |> validate_inclusion(:type, ~w[Link Document Audio Image Video]) |> validate_required([:type, :mediaType]) diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex index db28c38ef..d8e27e67a 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex @@ -31,6 +31,7 @@ defmacro activity_fields do defmacro object_fields do quote bind_quoted: binding() do field(:content, :string) + field(:htmlMfm, :boolean) field(:published, ObjectValidators.DateTime) field(:updated, ObjectValidators.DateTime) diff --git a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex index 9cafeeb14..e17dbb825 100644 --- a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex @@ -57,6 +57,8 @@ defp fix(data) do |> fix_emoji_qualification() |> CommonFixes.fix_actor() |> CommonFixes.fix_activity_addressing() + |> prune_tags() + |> drop_remote_indicator() data = if Map.has_key?(data, "tag") do @@ -133,4 +135,54 @@ defp validate_data(data_cng) do |> validate_emoji() |> maybe_validate_tag_presence() end + + # All tags but the single emoji tag corresponding to the used custom emoji (if any) + # are ignored anyway. Having a known single-element array makes further processing easier. + # Also ensures the Emoji tag uses a pre-stripped name + defp prune_tags(%{"content" => emoji, "tag" => tags} = data) do + clean_emoji = Emoji.stripped_name(emoji) + + pruned_tags = + Enum.reduce_while(tags, [], fn + %{"type" => "Emoji", "name" => name} = tag, res -> + clean_name = Emoji.stripped_name(name) + + if clean_name == clean_emoji do + {:halt, [%{tag | "name" => clean_name}]} + else + {:cont, res} + end + + _, res -> + {:cont, res} + end) + + %{data | "tag" => pruned_tags} + end + + defp prune_tags(data), do: data + + # some software, like Iceshrimp.NET, federates emoji reaction with (from its POV) remote emoji + # with the source instance added to the name in AP as an @ postfix, similar to how it’s handled + # in Akkoma’s REST API. + # However, this leads to duplicated remote indicators being presented to our clients an can cause + # issues when trying to split the values we receive from REST API. Thus just drop them here. + defp drop_remote_indicator(%{"content" => emoji, "tag" => tag} = data) when is_list(tag) do + if String.contains?(emoji, "@") do + stripped_emoji = Emoji.stripped_name(emoji) + [clean_emoji | _] = String.split(stripped_emoji, "@", parts: 2) + + clean_tag = + Enum.map(tag, fn + %{"name" => ^stripped_emoji} = t -> %{t | "name" => clean_emoji} + t -> t + end) + + %{data | "content" => ":" <> clean_emoji <> ":", "tag" => clean_tag} + else + data + end + end + + defp drop_remote_indicator(data), do: data end 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 5612ded1b..08069b96e 100644 --- a/lib/pleroma/web/activity_pub/object_validators/user_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/user_validator.ex @@ -14,6 +14,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UserValidator do @behaviour Pleroma.Web.ActivityPub.ObjectValidator.Validating alias Pleroma.Object.Containment + alias Pleroma.Web.ActivityPub.Utils require Pleroma.Constants @@ -62,6 +63,7 @@ defp validate_inbox(%{"id" => id, "inbox" => inbox}) do defp validate_inbox(_), do: {:error, "No inbox"} defp check_field_value(%{"id" => id} = _data, value) do + value = Utils.get_ap_id(value) Containment.same_origin(id, value) end diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index 8d9d07874..482c89b6c 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -51,30 +51,15 @@ def publish_one( %{"inbox" => inbox, "json" => json, "actor" => %User{} = actor, "id" => id} = params ) do Logger.debug("Federating #{id} to #{inbox}") - uri = %{path: path} = URI.parse(inbox) - digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64()) - date = Pleroma.Signature.signed_date() - - signature = - Pleroma.Signature.sign(actor, %{ - "(request-target)": "post #{path}", - host: signature_host(uri), - "content-length": byte_size(json), - digest: digest, - date: date - }) + signing_key = Pleroma.User.SigningKey.load_key(actor).signing_key with {:ok, %{status: code}} = result when code in 200..299 <- HTTP.post( inbox, json, - [ - {"Content-Type", "application/activity+json"}, - {"Date", date}, - {"signature", signature}, - {"digest", digest} - ] + [{"content-type", "application/activity+json"}], + httpsig: %{signing_key: signing_key} ) do if not Map.has_key?(params, "unreachable_since") || params["unreachable_since"] do Instances.set_reachable(inbox) @@ -84,7 +69,7 @@ def publish_one( else {_post_result, response} -> unless params["unreachable_since"], do: Instances.set_unreachable(inbox) - {:error, response} + {:error, format_error_response(response)} end end @@ -97,13 +82,13 @@ def publish_one(%{"actor_id" => actor_id} = params) do |> publish_one() end - defp signature_host(%URI{port: port, scheme: scheme, host: host}) do - if port == URI.default_port(scheme) do - host - else - "#{host}:#{port}" - end - end + defp format_error_response(%Tesla.Env{status: code, headers: headers}), + do: {:http_error, code, headers} + + defp format_error_response(%Tesla.Env{} = env), + do: {:http_error, :connect, env} + + defp format_error_response(response), do: response defp blocked_instances do Config.get([:instance, :quarantined_instances], []) ++ diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 0e85e47be..99b030d80 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -19,6 +19,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.Pipeline alias Pleroma.Web.ActivityPub.Utils + alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.Push alias Pleroma.Web.Streamer alias Pleroma.Workers.PollWorker @@ -26,8 +27,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do require Pleroma.Constants require Logger - @logger Pleroma.Config.get([:side_effects, :logger], Logger) - @behaviour Pleroma.Web.ActivityPub.SideEffects.Handling defp ap_streamer, do: Pleroma.Config.get([:side_effects, :ap_streamer], ActivityPub) @@ -204,7 +203,9 @@ def handle(%{data: %{"type" => "Create"}} = activity, meta) do {:ok, _user} = ActivityPub.increase_note_count_if_public(user, object) {:ok, _user} = ActivityPub.update_last_status_at_if_public(user, object) - if in_reply_to = object.data["type"] != "Answer" && object.data["inReplyTo"] do + if in_reply_to = + object.data["type"] != "Answer" && Visibility.is_public?(object.data) && + object.data["inReplyTo"] do Object.increase_replies_count(in_reply_to) end @@ -307,7 +308,8 @@ def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, {:ok, user} = ActivityPub.decrease_note_count_if_public(user, deleted_object) - if in_reply_to = deleted_object.data["inReplyTo"] do + if in_reply_to = + Visibility.is_public?(deleted_object.data) && deleted_object.data["inReplyTo"] do Object.decrease_replies_count(in_reply_to) end @@ -316,7 +318,7 @@ def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, :ok else {:actor, _} -> - @logger.error("The object doesn't have an actor: #{inspect(deleted_object)}") + Logger.error("The object doesn't have an actor: #{inspect(deleted_object)}") :no_object_actor end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index f6d2ea652..63a56fbd0 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -22,8 +22,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes alias Pleroma.Web.Federator - import Ecto.Query - require Pleroma.Constants require Logger @@ -341,6 +339,7 @@ def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachm } |> Maps.put_if_present("mediaType", media_type) |> Maps.put_if_present("name", data["name"]) + |> Maps.put_if_present("summary", data["summary"]) |> Maps.put_if_present("blurhash", data["blurhash"]) else nil @@ -790,49 +789,24 @@ def set_quote_url(%{"quoteUri" => quote} = object) when is_binary(quote) do def set_quote_url(obj), do: obj @doc """ - Serialized Mastodon-compatible `replies` collection containing _self-replies_. - Based on Mastodon's ActivityPub::NoteSerializer#replies. + Inline first page of the `replies` collection, + containing any replies in chronological order. """ def set_replies(obj_data) do - replies_uris = - with limit when limit > 0 <- - Pleroma.Config.get([:activitypub, :note_replies_output_limit], 0), - %Object{} = object <- Object.get_cached_by_ap_id(obj_data["id"]) do - object - |> Object.self_replies() - |> select([o], fragment("?->>'id'", o.data)) - |> limit(^limit) - |> Repo.all() - else - _ -> [] - end - - set_replies(obj_data, replies_uris) + with obj_ap_id when obj_ap_id != nil <- obj_data["id"], + limit when limit > 0 <- + Pleroma.Config.get([:activitypub, :note_replies_output_limit], 0), + collection <- + Pleroma.Web.ActivityPub.ObjectView.render("object_replies.json", %{ + render_params: %{object_ap_id: obj_data["id"], limit: limit, skip_ap_ctx: true} + }) do + Map.put(obj_data, "replies", collection) + else + 0 -> Map.put(obj_data, "replies", obj_data["id"] <> "/replies") + _ -> obj_data + end end - defp set_replies(obj, []) do - obj - end - - defp set_replies(obj, replies_uris) do - replies_collection = %{ - "type" => "Collection", - "items" => replies_uris - } - - Map.merge(obj, %{"replies" => replies_collection}) - end - - def replies(%{"replies" => %{"first" => %{"items" => items}}}) when not is_nil(items) do - items - end - - def replies(%{"replies" => %{"items" => items}}) when not is_nil(items) do - items - end - - def replies(_), do: [] - # Prepares the object of an outgoing create activity. def prepare_object(object) do object @@ -905,6 +879,10 @@ def prepare_outgoing(%{"type" => "Update", "object" => %{"type" => objtype} = ob {:ok, data} end + def prepare_outgoing(%{"type" => "Update", "object" => %{}} = data) do + raise "Requested to serve an Update for non-updateable object type: #{inspect(data)}" + end + def prepare_outgoing(%{"type" => "Announce", "actor" => ap_id, "object" => object_id} = data) do object = object_id diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index f731b5286..3b389cba9 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -76,18 +76,6 @@ def label_in_message?(label, params), [params["to"], params["cc"], params["bto"], params["bcc"]] |> Enum.any?(&label_in_collection?(label, &1)) - @spec unaddressed_message?(map()) :: boolean() - def unaddressed_message?(params), - do: - [params["to"], params["cc"], params["bto"], params["bcc"]] - |> Enum.all?(&is_nil(&1)) - - @spec recipient_in_message(User.t(), User.t(), map()) :: boolean() - def recipient_in_message(%User{ap_id: ap_id} = recipient, %User{} = actor, params), - do: - label_in_message?(ap_id, params) || unaddressed_message?(params) || - User.following?(recipient, actor) - defp extract_list(target) when is_binary(target), do: [target] defp extract_list(lst) when is_list(lst), do: lst defp extract_list(_), do: [] @@ -114,7 +102,8 @@ def make_json_ld_header do "https://www.w3.org/ns/activitystreams", "#{Endpoint.url()}/schemas/litepub-0.1.jsonld", %{ - "@language" => "und" + "@language" => "und", + "htmlMfm" => "https://w3id.org/fep/c16b#htmlMfm" } ] } diff --git a/lib/pleroma/web/activity_pub/views/collection_view_helper.ex b/lib/pleroma/web/activity_pub/views/collection_view_helper.ex new file mode 100644 index 000000000..7913f30f6 --- /dev/null +++ b/lib/pleroma/web/activity_pub/views/collection_view_helper.ex @@ -0,0 +1,59 @@ +# Akkoma: Magically expressive social media +# Copyright © 2017-2021 Pleroma Authors +# Copyright © 2025 Akkoma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.CollectionViewHelper do + alias Pleroma.Web.ActivityPub.Utils + + def collection_page_offset(collection, iri, page, show_items \\ true, total \\ nil) do + offset = (page - 1) * 10 + items = Enum.slice(collection, offset, 10) + items = Enum.map(items, fn user -> user.ap_id end) + total = total || length(collection) + + map = %{ + "id" => "#{iri}?page=#{page}", + "type" => "OrderedCollectionPage", + "partOf" => iri, + "totalItems" => total, + "orderedItems" => if(show_items, do: items, else: []) + } + + if offset + 10 < total do + Map.put(map, "next", "#{iri}?page=#{page + 1}") + else + map + end + end + + defp maybe_omit_next(pagination, _items, nil), do: pagination + + defp maybe_omit_next(pagination, items, limit) when is_binary(limit) do + case Integer.parse(limit) do + {limit, ""} -> maybe_omit_next(pagination, items, limit) + _ -> maybe_omit_next(pagination, items, nil) + end + end + + defp maybe_omit_next(pagination, items, limit) when is_number(limit) do + if Enum.count(items) < limit, do: Map.delete(pagination, "next"), else: pagination + end + + def collection_page_keyset( + display_items, + pagination, + limit \\ nil, + skip_ap_context \\ false + ) do + %{ + "type" => "OrderedCollectionPage", + "orderedItems" => display_items + } + |> Map.merge(pagination) + |> maybe_omit_next(display_items, limit) + |> then(fn m -> + if skip_ap_context, do: m, else: Map.merge(m, Utils.make_json_ld_header()) + end) + end +end diff --git a/lib/pleroma/web/activity_pub/views/object_view.ex b/lib/pleroma/web/activity_pub/views/object_view.ex index 29e2bbc81..201ca218c 100644 --- a/lib/pleroma/web/activity_pub/views/object_view.ex +++ b/lib/pleroma/web/activity_pub/views/object_view.ex @@ -6,7 +6,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do use Pleroma.Web, :view alias Pleroma.Activity alias Pleroma.Object + alias Pleroma.Web.ActivityPub.CollectionViewHelper + alias Pleroma.Web.ControllerHelper alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.ActivityPub.ActivityPub def render("object.json", %{object: %Object{} = object}) do base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header() @@ -15,26 +18,93 @@ def render("object.json", %{object: %Object{} = object}) do Map.merge(base, additional) end - def render("object.json", %{object: %Activity{data: %{"type" => activity_type}} = activity}) - when activity_type in ["Create"] do - base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header() - object = Object.normalize(activity, fetch: false) - - additional = - Transmogrifier.prepare_object(activity.data) - |> Map.put("object", Transmogrifier.prepare_object(object.data)) - - Map.merge(base, additional) + def render("object.json", %{object: %Activity{} = activity}) do + {:ok, ap_data} = Transmogrifier.prepare_outgoing(activity.data) + ap_data end - def render("object.json", %{object: %Activity{} = activity}) do - base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header() - object_id = Object.normalize(activity, id_only: true) + def render("object_replies.json", %{ + conn: conn, + render_params: %{object_ap_id: object_ap_id, page: "true"} = params + }) do + params = Map.put_new(params, :limit, 40) - additional = - Transmogrifier.prepare_object(activity.data) - |> Map.put("object", object_id) + items = ActivityPub.fetch_objects_for_replies_collection(object_ap_id, params) + display_items = map_reply_collection_items(items) - Map.merge(base, additional) + pagination = ControllerHelper.get_pagination_fields(conn, items, %{}, :asc) + + CollectionViewHelper.collection_page_keyset(display_items, pagination, params[:limit]) + end + + def render( + "object_replies.json", + %{ + render_params: %{object_ap_id: object_ap_id} = params + } = opts + ) do + params = + params + |> Map.drop([:max_id, :min_id, :since_id, :object_ap_id]) + |> Map.put_new(:limit, 40) + |> Map.put(:total, true) + + %{total: total, items: items} = + ActivityPub.fetch_objects_for_replies_collection(object_ap_id, params) + + display_items = map_reply_collection_items(items) + + first_pagination = reply_collection_first_pagination(items, opts) + + col_ap = + %{ + "id" => object_ap_id <> "/replies", + "type" => "OrderedCollection", + "totalItems" => total + } + + col_ap = + if total > 0 do + first_page = + CollectionViewHelper.collection_page_keyset( + display_items, + first_pagination, + params[:limit], + true + ) + + Map.put(col_ap, "first", first_page) + else + col_ap + end + + if params[:skip_ap_ctx] do + col_ap + else + Map.merge(col_ap, Pleroma.Web.ActivityPub.Utils.make_json_ld_header()) + end + end + + defp map_reply_collection_items(items), do: Enum.map(items, fn %{ap_id: ap_id} -> ap_id end) + + defp reply_collection_first_pagination(items, %{conn: %Plug.Conn{} = conn}) do + ControllerHelper.get_pagination_fields(conn, items, %{"page" => true}, :asc) + end + + defp reply_collection_first_pagination(items, %{render_params: %{object_ap_id: object_ap_id}}) do + %{ + "id" => object_ap_id <> "/replies?page=true", + "partOf" => object_ap_id <> "/replies" + } + |> then(fn m -> + case items do + [] -> + m + + i -> + next_id = object_ap_id <> "/replies?page=true&min_id=#{List.last(i)[:id]}" + Map.put(m, "next", next_id) + end + end) end end diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 2a8c6df21..ac72182ff 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do alias Pleroma.Object alias Pleroma.Repo alias Pleroma.User + alias Pleroma.Web.ActivityPub.CollectionViewHelper alias Pleroma.Web.ActivityPub.ObjectView alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Utils @@ -112,7 +113,10 @@ def render("user.json", %{user: user}) do |> Map.merge(Utils.make_json_ld_header()) end - def render("keys.json", %{user: user}) do + # For unauthenticated requests when authfetch is enabled. + # Still serve the key and the bare minimum of required fields + # to avoid being stuck in an infinite "cannot verify" loop with remotes. + def render("stripped_user.json", %{user: user}) do {:ok, public_key} = User.SigningKey.public_key_pem(user) %{ @@ -121,7 +125,14 @@ def render("keys.json", %{user: user}) do "id" => User.SigningKey.key_id_of_local_user(user), "owner" => user.ap_id, "publicKeyPem" => public_key - } + }, + # REQUIRED fields per AP spec + "inbox" => "#{user.ap_id}/inbox", + "outbox" => "#{user.ap_id}/outbox", + # allow type-based processing + "type" => user.actor_type, + # since Mastodon requires a WebFinger address for all users, this seems like a good idea + "preferredUsername" => user.nickname } |> Map.merge(Utils.make_json_ld_header()) end @@ -141,7 +152,13 @@ def render("following.json", %{user: user, page: page} = opts) do 0 end - collection(following, "#{user.ap_id}/following", page, showing_items, total) + CollectionViewHelper.collection_page_offset( + following, + "#{user.ap_id}/following", + page, + showing_items, + total + ) |> Map.merge(Utils.make_json_ld_header()) end @@ -166,7 +183,12 @@ def render("following.json", %{user: user} = opts) do "totalItems" => total, "first" => if showing_items do - collection(following, "#{user.ap_id}/following", 1, !user.hide_follows) + CollectionViewHelper.collection_page_offset( + following, + "#{user.ap_id}/following", + 1, + !user.hide_follows + ) else "#{user.ap_id}/following?page=1" end @@ -189,7 +211,13 @@ def render("followers.json", %{user: user, page: page} = opts) do 0 end - collection(followers, "#{user.ap_id}/followers", page, showing_items, total) + CollectionViewHelper.collection_page_offset( + followers, + "#{user.ap_id}/followers", + page, + showing_items, + total + ) |> Map.merge(Utils.make_json_ld_header()) end @@ -213,7 +241,13 @@ def render("followers.json", %{user: user} = opts) do "type" => "OrderedCollection", "first" => if showing_items do - collection(followers, "#{user.ap_id}/followers", 1, showing_items, total) + CollectionViewHelper.collection_page_offset( + followers, + "#{user.ap_id}/followers", + 1, + showing_items, + total + ) else "#{user.ap_id}/followers?page=1" end @@ -233,22 +267,15 @@ def render("activity_collection.json", %{iri: iri}) do def render("activity_collection_page.json", %{ activities: activities, - iri: iri, pagination: pagination }) do - collection = + display_items = Enum.map(activities, fn activity -> {:ok, data} = Transmogrifier.prepare_outgoing(activity.data) data end) - %{ - "type" => "OrderedCollectionPage", - "partOf" => iri, - "orderedItems" => collection - } - |> Map.merge(Utils.make_json_ld_header()) - |> Map.merge(pagination) + CollectionViewHelper.collection_page_keyset(display_items, pagination) end def render("featured.json", %{ @@ -276,27 +303,6 @@ defp maybe_put_total_items(map, true, total) do Map.put(map, "totalItems", total) end - def collection(collection, iri, page, show_items \\ true, total \\ nil) do - offset = (page - 1) * 10 - items = Enum.slice(collection, offset, 10) - items = Enum.map(items, fn user -> user.ap_id end) - total = total || length(collection) - - map = %{ - "id" => "#{iri}?page=#{page}", - "type" => "OrderedCollectionPage", - "partOf" => iri, - "totalItems" => total, - "orderedItems" => if(show_items, do: items, else: []) - } - - if offset < total do - Map.put(map, "next", "#{iri}?page=#{page + 1}") - else - map - end - end - defp maybe_make_image(func, key, user) do image = func.(user, no_default: true) maybe_insert_image(key, image) diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex index 02d4e195a..d3d1de8ea 100644 --- a/lib/pleroma/web/activity_pub/visibility.ex +++ b/lib/pleroma/web/activity_pub/visibility.ex @@ -53,25 +53,11 @@ def is_direct?(activity) do !is_public?(activity) && !is_private?(activity) end - def is_list?(%{data: %{"listMessage" => _}}), do: true - def is_list?(_), do: false - @spec visible_for_user?(Object.t() | Activity.t() | nil, User.t() | nil) :: boolean() def visible_for_user?(%Object{data: %{"type" => "Tombstone"}}, _), do: false def visible_for_user?(%Activity{actor: ap_id}, %User{ap_id: ap_id}), do: true def visible_for_user?(%Object{data: %{"actor" => ap_id}}, %User{ap_id: ap_id}), do: true def visible_for_user?(nil, _), do: false - def visible_for_user?(%Activity{data: %{"listMessage" => _}}, nil), do: false - - def visible_for_user?( - %Activity{data: %{"listMessage" => list_ap_id}} = activity, - %User{} = user - ) do - user.ap_id in activity.data["to"] || - list_ap_id - |> Pleroma.List.get_by_ap_id() - |> Pleroma.List.member?(user) - end def visible_for_user?(%{__struct__: module} = message, nil) when module in [Activity, Object] do @@ -141,9 +127,6 @@ def get_visibility(object) do object.data["directMessage"] == true -> "direct" - is_binary(object.data["listMessage"]) -> - "list" - length(cc) > 0 -> "private" diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex index 7344e1f77..1972ff9aa 100644 --- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex @@ -11,7 +11,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do alias Pleroma.Config alias Pleroma.MFA alias Pleroma.ModerationLog - alias Pleroma.Stats alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.AdminAPI @@ -399,10 +398,17 @@ def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" = json(conn, "") end - def stats(conn, params) do - counters = Stats.get_status_visibility_count(params["instance"]) - - json(conn, %{"status_visibility" => counters}) + # Legacy endpoint, stubbed out for a transition period before removal + # (atm only used by admin-fe) + def stats(conn, _params) do + json(conn, %{ + "status_visibility" => %{ + "direct" => 0, + "private" => 0, + "public" => 0, + "unlisted" => 0 + } + }) end def create_backup(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do diff --git a/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex b/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex index a6d7aaf54..7df0da4a1 100644 --- a/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex @@ -40,7 +40,7 @@ def index(%{assigns: %{user: _}} = conn, params) do defp fetch_entries(params) do MediaProxy.cache_table() - |> @cachex.stream!(Cachex.Query.create(true, :key)) + |> @cachex.stream!(Cachex.Query.build(output: :key)) |> filter_entries(params[:query]) end diff --git a/lib/pleroma/web/akkoma_api/controllers/translation_controller.ex b/lib/pleroma/web/akkoma_api/controllers/translation_controller.ex index 022da3198..23b53a80b 100644 --- a/lib/pleroma/web/akkoma_api/controllers/translation_controller.ex +++ b/lib/pleroma/web/akkoma_api/controllers/translation_controller.ex @@ -42,7 +42,7 @@ defp get_languages do @cachex.fetch!(:translations_cache, "languages:#{module}}", fn _ -> with {:ok, source_languages, dest_languages} <- module.languages() do - {:ok, source_languages, dest_languages} + {:commit, {:ok, source_languages, dest_languages}} else {:error, err} -> {:ignore, {:error, err}} end diff --git a/lib/pleroma/web/api_spec/cast_and_validate.ex b/lib/pleroma/web/api_spec/cast_and_validate.ex index d23a7dcb6..c317bbbf5 100644 --- a/lib/pleroma/web/api_spec/cast_and_validate.ex +++ b/lib/pleroma/web/api_spec/cast_and_validate.ex @@ -15,9 +15,12 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do @behaviour Plug + alias OpenApiSpex.Cast alias OpenApiSpex.Plug.PutApiSpec alias Plug.Conn + use Gettext, backend: Pleroma.Web.Gettext + @impl Plug def init(opts) do opts @@ -27,6 +30,9 @@ def init(opts) do @impl Plug + # How often to attempt removing errors and retrying validation in permissive mode + @max_retries 10 + def call(conn, %{operation_id: operation_id, render_error: render_error}) do {spec, operation_lookup} = PutApiSpec.get_spec_and_operation_lookup(conn) operation = operation_lookup[operation_id] @@ -99,31 +105,62 @@ defp cast_and_validate(spec, operation, conn, content_type, true = _strict) do end defp cast_and_validate(spec, operation, conn, content_type, false = _strict) do - case OpenApiSpex.cast_and_validate(spec, operation, conn, content_type) do + with {:error, errors} <- OpenApiSpex.cast_and_validate(spec, operation, conn, content_type), + state <- conn.private[:akkoma_api_spec] || %{}, + {_, _, attempt} when attempt < @max_retries <- {:attempt, errors, state[:attempt] || 0} do + case strip_errors(errors, conn.query_params) do + {:error, _} = err -> + err + + {:ok, query_params} -> + state = Map.put(state, :attempt, attempt + 1) + private = Map.put(conn.private, :akkoma_api_spec, state) + + conn = + conn + |> Map.put(:query_params, query_params) + |> Map.put(:private, private) + + cast_and_validate(spec, operation, conn, content_type, false) + end + else {:ok, conn} -> {:ok, conn} - # Remove unexpected query params and cast/validate again - {:error, errors} -> - query_params = - Enum.reduce(errors, conn.query_params, fn - %{reason: :unexpected_field, name: name, path: [name]}, params -> - Map.delete(params, name) + {:attempt, errors, _} -> + Cast.error( + %{path: ["/"], value: @max_retries, errors: errors}, + {:custom, dgettext("errors", "too many bad parameters.")} + ) + end + end - # Filter out empty params - %{reason: :invalid_type, path: [name_atom], value: ""}, params -> - Map.delete(params, to_string(name_atom)) + @spec strip_errors([OpenApiSpex.Cast.Error.t()], Plug.Conn.query_params()) :: + {:error, [OpenApiSpex.Cast.Error.t()]} | {:ok, Plug.Conn.query_params()} + defp strip_errors(errors, query_params) do + res = + Enum.reduce_while(errors, query_params, fn + %{reason: :unexpected_field, name: name, path: [name]}, params -> + res = Map.delete(params, name) + {:cont, res} - %{reason: :invalid_enum, name: nil, path: path, value: value}, params -> - path = path |> Enum.reverse() |> tl() |> Enum.reverse() |> list_items_to_string() - update_in(params, path, &List.delete(&1, value)) + # Filter out empty params + %{reason: :invalid_type, path: [name_atom], value: ""}, params -> + res = Map.delete(params, to_string(name_atom)) + {:cont, res} - _, params -> - params - end) + %{reason: :invalid_enum, name: nil, path: path, value: value}, params -> + path = path |> Enum.reverse() |> tl() |> Enum.reverse() |> list_items_to_string() + res = update_in(params, path, &List.delete(&1, value)) + {:cont, res} - conn = %Conn{conn | query_params: query_params} - OpenApiSpex.cast_and_validate(spec, operation, conn, content_type) + _err, _params -> + {:halt, {:error, errors}} + end) + + case res do + {:error, _} = err -> err + res -> {:ok, res} end end diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index c15a246f2..11dce35c9 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -279,11 +279,17 @@ def mute_operation do %Schema{allOf: [BooleanLike], default: true}, "Mute notifications in addition to statuses? Defaults to `true`." ), + Operation.parameter( + :duration, + :query, + %Schema{type: :integer}, + "Expire the mute in `duration` seconds. Default 0 for infinity" + ), Operation.parameter( :expires_in, :query, %Schema{type: :integer, default: 0}, - "Expire the mute in `expires_in` seconds. Default 0 for infinity" + "Deprecated, use `duration` instead" ) ], responses: %{ @@ -858,10 +864,15 @@ defp mute_request do description: "Mute notifications in addition to statuses? Defaults to true.", default: true }, + duration: %Schema{ + type: :integer, + nullable: true, + description: "Expire the mute in `duration` seconds. Default 0 for infinity" + }, expires_in: %Schema{ type: :integer, nullable: true, - description: "Expire the mute in `expires_in` seconds. Default 0 for infinity", + description: "Deprecated, use `duration` instead", default: 0 } }, diff --git a/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex b/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex index b254fc57b..4974f806e 100644 --- a/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex +++ b/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex @@ -35,7 +35,8 @@ def index_operation do security: [%{"oAuth" => ["read:statuses"]}], operationId: "EmojiReactionController.index", responses: %{ - 200 => array_of_reactions_response() + 200 => array_of_reactions_response(), + 403 => Operation.response("Access denied", "application/json", ApiError) } } end diff --git a/lib/pleroma/web/api_spec/operations/notification_operation.ex b/lib/pleroma/web/api_spec/operations/notification_operation.ex index b4a20e5e5..f96ad70ef 100644 --- a/lib/pleroma/web/api_spec/operations/notification_operation.ex +++ b/lib/pleroma/web/api_spec/operations/notification_operation.ex @@ -51,6 +51,13 @@ def index_operation do :include_types, :query, %Schema{type: :array, items: notification_type()}, + "Deprecated, use `types` instead", + deprecated: true + ), + Operation.parameter( + :types, + :query, + %Schema{type: :array, items: notification_type()}, "Include the notifications for activities with the given types" ), Operation.parameter( diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex index 65877cc64..7e2efe789 100644 --- a/lib/pleroma/web/api_spec/operations/status_operation.ex +++ b/lib/pleroma/web/api_spec/operations/status_operation.ex @@ -156,6 +156,7 @@ def favourite_operation do parameters: [id_param()], responses: %{ 200 => status_response(), + 403 => Operation.response("Access denied", "application/json", ApiError), 404 => Operation.response("Not Found", "application/json", ApiError) } } @@ -362,6 +363,7 @@ def reblogged_by_operation do "application/json", AccountOperation.array_of_accounts() ), + 403 => Operation.response("Access denied", "application/json", ApiError), 404 => Operation.response("Not Found", "application/json", ApiError) } } @@ -553,10 +555,9 @@ defp create_request do nullable: true, anyOf: [ VisibilityScope, - %Schema{type: :string, description: "`list:LIST_ID`", example: "LIST:123"} + %Schema{type: :string, description: "scope name", example: "unlisted"} ], - description: - "Visibility of the posted status. Besides standard MastoAPI values (`direct`, `private`, `unlisted` or `public`) it can be used to address a List by setting it to `list:LIST_ID`" + description: "Visibility of the posted status." }, expires_in: %Schema{ nullable: true, @@ -564,12 +565,6 @@ defp create_request do description: "The number of seconds the posted activity should expire in. When a posted activity expires it will be deleted from the server, and a delete request for it will be federated. This needs to be longer than an hour." }, - in_reply_to_conversation_id: %Schema{ - nullable: true, - type: :string, - description: - "Will reply to a given conversation, addressing only the people who are part of the recipient set of that conversation. Sets the visibility to `direct`." - }, quote_id: %Schema{ nullable: true, type: :string, diff --git a/lib/pleroma/web/api_spec/render_error.ex b/lib/pleroma/web/api_spec/render_error.ex index 7feec4fd3..916c73113 100644 --- a/lib/pleroma/web/api_spec/render_error.ex +++ b/lib/pleroma/web/api_spec/render_error.ex @@ -7,7 +7,9 @@ defmodule Pleroma.Web.ApiSpec.RenderError do import Plug.Conn, only: [put_status: 2] import Phoenix.Controller, only: [json: 2] - import Pleroma.Web.Gettext + + use Gettext, + backend: Pleroma.Web.Gettext @impl Plug def init(opts), do: opts @@ -228,6 +230,10 @@ defp message(%{reason: :min_properties, meta: meta}) do ) end + defp message(%{reason: :custom, meta: meta}) do + meta.message + end + defp safe_string(string) do to_string(string) |> String.slice(0..39) end diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex index 28386b096..0a9b682c9 100644 --- a/lib/pleroma/web/api_spec/schemas/status.ex +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -250,6 +250,16 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do } } ] + }, + in_reply_to_apid: %Schema{ + type: :string, + nullable: true, + description: "The AcitivityPub ID this post is replying to, if it is a reply." + }, + quote_apid: %Schema{ + type: :string, + nullable: true, + description: "The AcitivityPub ID this post is quoting, if it is a quote." } } }, diff --git a/lib/pleroma/web/api_spec/schemas/visibility_scope.ex b/lib/pleroma/web/api_spec/schemas/visibility_scope.ex index 25a08a0b2..a6c675bb1 100644 --- a/lib/pleroma/web/api_spec/schemas/visibility_scope.ex +++ b/lib/pleroma/web/api_spec/schemas/visibility_scope.ex @@ -9,6 +9,6 @@ defmodule Pleroma.Web.ApiSpec.Schemas.VisibilityScope do title: "VisibilityScope", description: "Status visibility", type: :string, - enum: ["public", "unlisted", "local", "private", "direct", "list"] + enum: ["public", "unlisted", "local", "private", "direct"] }) end diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex index 6b62ef2d1..9fab754b4 100644 --- a/lib/pleroma/web/common_api.ex +++ b/lib/pleroma/web/common_api.ex @@ -4,7 +4,6 @@ defmodule Pleroma.Web.CommonAPI do alias Pleroma.Activity - alias Pleroma.Conversation.Participation alias Pleroma.Object alias Pleroma.ThreadMute alias Pleroma.User @@ -16,7 +15,9 @@ defmodule Pleroma.Web.CommonAPI do alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.CommonAPI.ActivityDraft - import Pleroma.Web.Gettext + use Gettext, + backend: Pleroma.Web.Gettext + import Pleroma.Web.CommonAPI.Utils require Pleroma.Constants @@ -123,8 +124,8 @@ def repeat(id, user, params \\ %{}) do with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id), object = %Object{} <- Object.normalize(activity, fetch: false), {_, nil} <- {:existing_announce, Utils.get_existing_announce(user.ap_id, object)}, - public = public_announce?(object, params), - {:ok, announce, _} <- Builder.announce(user, object, public: public), + visibility = announce_visibility(object, params), + {:ok, announce, _} <- Builder.announce(user, object, visibility: visibility), {:ok, activity, _} <- Pipeline.common_pipeline(announce, local: true) do {:ok, activity} else @@ -156,7 +157,7 @@ def favorite(%User{} = user, id) do {:ok, _} = res -> res - {:error, :not_found} = res -> + {:error, reason} = res when reason in [:not_found, :forbidden] -> res {:error, e} -> @@ -167,6 +168,7 @@ def favorite(%User{} = user, id) do def favorite_helper(user, id) do with {_, %Activity{object: object}} <- {:find_object, Activity.get_by_id_with_object(id)}, + {_, true} <- {:visible, Visibility.visible_for_user?(object, user)}, {_, {:ok, like_object, meta}} <- {:build_object, Builder.like(user, object)}, {_, {:ok, %Activity{} = activity, _meta}} <- {:common_pipeline, @@ -176,6 +178,9 @@ def favorite_helper(user, id) do {:find_object, _} -> {:error, :not_found} + {:visible, _} -> + {:error, :forbidden} + {:common_pipeline, {:error, {:validate, {:error, changeset}}}} = e -> if {:object, {"already liked by this actor", []}} in changeset.errors do {:ok, :already_liked} @@ -204,11 +209,15 @@ def unfavorite(id, user) do def react_with_emoji(id, user, emoji) do with %Activity{} = activity <- Activity.get_by_id(id), + {_, true} <- {:visible, Visibility.visible_for_user?(activity, user)}, object <- Object.normalize(activity, fetch: false), {:ok, emoji_react, _} <- Builder.emoji_react(user, object, emoji), {:ok, activity, _} <- Pipeline.common_pipeline(emoji_react, local: true) do {:ok, activity} else + {:visible, _} -> + {:error, dgettext("errors", "Must be able to access post to interact with it")} + _ -> {:error, dgettext("errors", "Could not add reaction emoji")} end @@ -286,31 +295,22 @@ defp normalize_and_validate_choices(choices, object) do end end - def public_announce?(_, %{visibility: visibility}) - when visibility in ~w{public unlisted private direct}, - do: visibility in ~w(public unlisted) + def announce_visibility(_, %{visibility: visibility}) + when visibility in ~w{public unlisted private direct local}, + do: visibility - def public_announce?(object, _) do - Visibility.is_public?(object) - end + def announce_visibility(object, _), do: Visibility.get_visibility(object) - def get_visibility(_, _, %Participation{}), do: {"direct", "direct"} - - def get_visibility(%{visibility: visibility}, in_reply_to, _) + def get_visibility(%{visibility: visibility}, in_reply_to) when visibility in ~w{public local unlisted private direct}, do: {visibility, get_replied_to_visibility(in_reply_to)} - def get_visibility(%{visibility: "list:" <> list_id}, in_reply_to, _) do - visibility = {:list, String.to_integer(list_id)} - {visibility, get_replied_to_visibility(in_reply_to)} - end - - def get_visibility(_, in_reply_to, _) when not is_nil(in_reply_to) do + def get_visibility(_, in_reply_to) when not is_nil(in_reply_to) do visibility = get_replied_to_visibility(in_reply_to) {visibility, visibility} end - def get_visibility(_, in_reply_to, _), do: {"public", get_replied_to_visibility(in_reply_to)} + def get_visibility(_, nil), do: {"public", nil} def get_replied_to_visibility(nil), do: nil diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index 2d24edec4..8406fc0d0 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -4,13 +4,14 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do alias Pleroma.Activity - alias Pleroma.Conversation.Participation alias Pleroma.Object alias Pleroma.Web.ActivityPub.Builder + alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI.Utils - import Pleroma.Web.Gettext + use Gettext, + backend: Pleroma.Web.Gettext defstruct valid?: true, errors: [], @@ -21,7 +22,6 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do full_payload: nil, attachments: [], in_reply_to: nil, - in_reply_to_conversation: nil, language: nil, content_map: %{}, quote_id: nil, @@ -47,8 +47,7 @@ defp new(user, params) do end def create(user, params) do - user - |> new(params) + new(user, params) |> status() |> summary() |> with_valid(&attachments/1) @@ -56,7 +55,6 @@ def create(user, params) do |> expires_at() |> poll() |> with_valid(&in_reply_to/1) - |> with_valid(&in_reply_to_conversation/1) |> with_valid(&visibility/1) |> with_valid("e_id/1) |> content() @@ -105,7 +103,30 @@ defp attachments(%{params: params, user: user} = draft) do defp in_reply_to(%{params: %{in_reply_to_status_id: ""}} = draft), do: draft defp in_reply_to(%{params: %{in_reply_to_status_id: id}} = draft) when is_binary(id) do - %__MODULE__{draft | in_reply_to: Activity.get_by_id(id)} + # If a post was deleted all its activities (except the newly added Delete) are purged too, + # thus lookup by Create db ID will yield nil just as if it never existed in the first place. + # We allow replying to Announce here, due to an akkomafe quirk where if presented with a Announce id + # it will render it as if it was just the normal referenced post, but use the announce ID for all interaction. + # (XXX: fix this in akkoma-fe, then drop such workarounds here and in all other affected places) + with %Activity{} = activity <- Activity.get_by_id(id), + true <- Visibility.visible_for_user?(activity, draft.user), + {_, type} when type in ["Create", "Announce"] <- {:type, activity.data["type"]} do + %__MODULE__{draft | in_reply_to: activity} + else + nil -> + add_error(draft, dgettext("errors", "Parent post does not exist or was deleted")) + + false -> + add_error(draft, dgettext("errors", "Must be able to access post to interact with it")) + + {:type, type} -> + add_error( + draft, + dgettext("errors", "Can only reply to posts, not %{type} activities", + type: inspect(type) + ) + ) + end end defp in_reply_to(%{params: %{in_reply_to_status_id: %Activity{} = in_reply_to}} = draft) do @@ -114,11 +135,6 @@ defp in_reply_to(%{params: %{in_reply_to_status_id: %Activity{} = in_reply_to}} defp in_reply_to(draft), do: draft - defp in_reply_to_conversation(draft) do - in_reply_to_conversation = Participation.get(draft.params[:in_reply_to_conversation_id]) - %__MODULE__{draft | in_reply_to_conversation: in_reply_to_conversation} - end - defp quote_id(%{params: %{quote_id: ""}} = draft), do: draft defp quote_id(%{params: %{quote_id: id}} = draft) when is_binary(id) do @@ -156,7 +172,7 @@ defp language(%{content_html: content} = draft) do end defp visibility(%{params: params} = draft) do - case CommonAPI.get_visibility(params, draft.in_reply_to, draft.in_reply_to_conversation) do + case CommonAPI.get_visibility(params, draft.in_reply_to) do {visibility, "direct"} when visibility != "direct" -> add_error(draft, dgettext("errors", "The message visibility must be direct")) @@ -236,6 +252,7 @@ defp object(draft) do end emoji = Map.merge(emoji, summary_emoji) + media_type = Utils.get_content_type(draft.params[:content_type]) {:ok, note_data, _meta} = Builder.note(draft) object = @@ -243,14 +260,18 @@ defp object(draft) do |> Map.put("emoji", emoji) |> Map.put("source", %{ "content" => draft.status, - "mediaType" => Utils.get_content_type(draft.params[:content_type]) + "mediaType" => media_type }) + |> maybe_put("htmlMfm", true, media_type == "text/x.misskeymarkdown") |> Map.put("generator", draft.params[:generator]) |> Map.put("contentMap", draft.content_map) %__MODULE__{draft | object: object} end + defp maybe_put(map, key, value, true), do: map |> Map.put(key, value) + defp maybe_put(map, _, _, _), do: map + defp preview?(draft) do preview? = Pleroma.Web.Utils.Params.truthy_param?(draft.params[:preview]) %__MODULE__{draft | preview?: preview?} @@ -274,7 +295,6 @@ defp changes(draft) do object: draft.object, additional: additional } - |> Utils.maybe_add_list_data(draft.user, draft.visibility) %__MODULE__{draft | changes: changes} end diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 635143621..fa86882c0 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -3,12 +3,12 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.CommonAPI.Utils do - import Pleroma.Web.Gettext + use Gettext, + backend: Pleroma.Web.Gettext alias Calendar.Strftime alias Pleroma.Activity alias Pleroma.Config - alias Pleroma.Conversation.Participation alias Pleroma.Formatter alias Pleroma.Object alias Pleroma.Repo @@ -51,53 +51,66 @@ def get_attachment(media_id) do @spec get_to_and_cc(ActivityDraft.t()) :: {list(String.t()), list(String.t())} - def get_to_and_cc(%{in_reply_to_conversation: %Participation{} = participation}) do - participation = Repo.preload(participation, :recipients) - {Enum.map(participation.recipients, & &1.ap_id), []} - end - - def get_to_and_cc(%{visibility: visibility} = draft) when visibility in ["public", "local"] do - to = - case visibility do - "public" -> [Pleroma.Constants.as_public() | draft.mentions] - "local" -> [Utils.as_local_public() | draft.mentions] + def get_to_and_cc(%{visibility: visibility} = draft) do + # If the OP is a DM already, add the implicit actor + mentions = + if visibility == "direct" && draft.in_reply_to && Visibility.is_direct?(draft.in_reply_to) do + Enum.uniq([draft.in_reply_to.data["actor"] | draft.mentions]) + else + draft.mentions end - cc = [draft.user.follower_address] - - if draft.in_reply_to do - {Enum.uniq([draft.in_reply_to.data["actor"] | to]), cc} - else - {to, cc} - end + get_to_and_cc_for_visibility( + visibility, + draft.user.follower_address, + draft.in_reply_to && draft.in_reply_to.data["actor"], + mentions + ) end - def get_to_and_cc(%{visibility: "unlisted"} = draft) do - to = [draft.user.follower_address | draft.mentions] - cc = [Pleroma.Constants.as_public()] + def get_to_and_cc_for_visibility("public", follower_collection, parent_actor, mentions) do + scope_addr = Pleroma.Constants.as_public() - if draft.in_reply_to do - {Enum.uniq([draft.in_reply_to.data["actor"] | to]), cc} - else - {to, cc} - end + to = + if parent_actor, + do: Enum.uniq([parent_actor, scope_addr | mentions]), + else: [scope_addr | mentions] + + {to, [follower_collection]} end - def get_to_and_cc(%{visibility: "private"} = draft) do - {to, cc} = get_to_and_cc(struct(draft, visibility: "direct")) - {[draft.user.follower_address | to], cc} + def get_to_and_cc_for_visibility("local", follower_collection, parent_actor, mentions) do + recipients = + if parent_actor, + do: Enum.uniq([parent_actor | mentions]), + else: mentions + + to = [ + Utils.as_local_public() + | Enum.filter(recipients, fn addr -> + String.starts_with?(addr, Pleroma.Web.Endpoint.url() <> "/") + end) + ] + + {to, [follower_collection]} end - def get_to_and_cc(%{visibility: "direct"} = draft) do - # If the OP is a DM already, add the implicit actor. - if draft.in_reply_to && Visibility.is_direct?(draft.in_reply_to) do - {Enum.uniq([draft.in_reply_to.data["actor"] | draft.mentions]), []} - else - {draft.mentions, []} - end + def get_to_and_cc_for_visibility("unlisted", follower_collection, parent_actor, mentions) do + to = + if parent_actor, + do: Enum.uniq([parent_actor, follower_collection | mentions]), + else: [follower_collection | mentions] + + {to, [Pleroma.Constants.as_public()]} end - def get_to_and_cc(%{visibility: {:list, _}, mentions: mentions}), do: {mentions, []} + def get_to_and_cc_for_visibility("private", follower_collection, _, mentions) do + {[follower_collection | mentions], []} + end + + def get_to_and_cc_for_visibility("direct", _, _, mentions) do + {mentions, []} + end def get_addressed_users(_, to) when is_list(to) do User.get_ap_ids_by_nicknames(to) @@ -105,21 +118,6 @@ def get_addressed_users(_, to) when is_list(to) do def get_addressed_users(mentioned_users, _), do: mentioned_users - def maybe_add_list_data(activity_params, user, {:list, list_id}) do - case Pleroma.List.get(list_id, user) do - %Pleroma.List{} = list -> - activity_params - |> put_in([:additional, "bcc"], [list.ap_id]) - |> put_in([:additional, "listMessage"], list.ap_id) - |> put_in([:object, "listMessage"], list.ap_id) - - _ -> - activity_params - end - end - - def maybe_add_list_data(activity_params, _, _), do: activity_params - def make_poll_data(%{"poll" => %{"expires_in" => expires_in}} = data) when is_binary(expires_in) do # In some cases mastofe sends out strings instead of integers @@ -225,10 +223,6 @@ def get_content_type(content_type) do end end - def make_context(%{in_reply_to_conversation: %Participation{} = participation}) do - Repo.preload(participation, :conversation).conversation.ap_id - end - def make_context(%{in_reply_to: %Activity{data: %{"context" => context}}}), do: context def make_context(%{quote: %Activity{data: %{"context" => context}}}), do: context def make_context(_), do: Utils.generate_context_id() @@ -242,13 +236,13 @@ def maybe_add_attachments({text, mentions, tags}, attachments, _no_links) do def add_attachments(text, attachments) do attachment_text = Enum.map(attachments, &build_attachment_link/1) - Enum.join([text | attachment_text], "
") + Enum.join([text | attachment_text], "
") end defp build_attachment_link(%{"url" => [%{"href" => href} | _]} = attachment) do name = attachment["name"] || URI.decode(Path.basename(href)) href = MediaProxy.url(href) - "#{shortname(name)}" + "#{shortname(name)}" end defp build_attachment_link(_), do: "" diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex index 6acc8f078..a13d32eac 100644 --- a/lib/pleroma/web/controller_helper.ex +++ b/lib/pleroma/web/controller_helper.ex @@ -54,34 +54,53 @@ def add_link_headers(conn, entries, extra_params) do end @id_keys Pagination.page_keys() -- ["limit", "order"] - defp build_pagination_fields(conn, min_id, max_id, extra_params) do + defp build_pagination_fields(conn, min_id, max_id, extra_params, order) do params = - conn.params - |> Map.drop(Map.keys(conn.path_params) |> Enum.map(&String.to_existing_atom/1)) + conn.body_params + |> Map.merge(conn.query_params) |> Map.merge(extra_params) |> Map.drop(@id_keys) + {{next_id, nid}, {prev_id, pid}} = + if order == :desc, + do: {{:max_id, max_id}, {:min_id, min_id}}, + else: {{:min_id, min_id}, {:max_id, max_id}} + + id = Phoenix.Controller.current_url(conn) + base_id = %{URI.parse(id) | query: nil} |> URI.to_string() + %{ - "next" => current_url(conn, Map.put(params, :max_id, max_id)), - "prev" => current_url(conn, Map.put(params, :min_id, min_id)), - "id" => current_url(conn) + "next" => current_url(conn, Map.put(params, next_id, nid)), + "prev" => current_url(conn, Map.put(params, prev_id, pid)), + "id" => id, + "partOf" => base_id } end - def get_pagination_fields(conn, entries, extra_params \\ %{}) do + defp get_first_last_pagination_id(entries) do case List.last(entries) do - %{pagination_id: max_id} when not is_nil(max_id) -> - %{pagination_id: min_id} = List.first(entries) - - build_pagination_fields(conn, min_id, max_id, extra_params) - - %{id: max_id} -> - %{id: min_id} = List.first(entries) - - build_pagination_fields(conn, min_id, max_id, extra_params) + %{id: last_id} -> + %{id: first_id} = List.first(entries) + {first_id, last_id} _ -> - %{} + nil + end + end + + def get_pagination_fields(conn, entries, extra_params \\ %{}, order \\ :desc) + + def get_pagination_fields(conn, entries, extra_params, :desc) do + case get_first_last_pagination_id(entries) do + nil -> %{} + {min_id, max_id} -> build_pagination_fields(conn, min_id, max_id, extra_params, :desc) + end + end + + def get_pagination_fields(conn, entries, extra_params, :asc) do + case get_first_last_pagination_id(entries) do + nil -> %{} + {max_id, min_id} -> build_pagination_fields(conn, min_id, max_id, extra_params, :asc) end end diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index 3c9fcb48c..719171035 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -168,18 +168,6 @@ defmodule Pleroma.Web.Endpoint do plug(Pleroma.Web.Router) - @doc """ - Dynamically loads configuration from the system environment - on startup. - - It receives the endpoint configuration from the config files - and must return the updated configuration. - """ - def load_from_system_env(config) do - port = System.get_env("PORT") || raise "expected the PORT environment variable to be set" - {:ok, Keyword.put(config, :http, [:inet6, port: port])} - end - def websocket_url do String.replace_leading(url(), "http", "ws") end diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex index 8b0a42dc8..f05cb7f28 100644 --- a/lib/pleroma/web/feed/feed_view.ex +++ b/lib/pleroma/web/feed/feed_view.ex @@ -3,13 +3,12 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Feed.FeedView do - use Phoenix.HTML use Pleroma.Web, :view alias Pleroma.Formatter alias Pleroma.Object alias Pleroma.User - alias Pleroma.Web.Gettext + use Gettext, backend: Pleroma.Web.Gettext alias Pleroma.Web.MediaProxy require Pleroma.Constants diff --git a/lib/pleroma/web/feed/tag_controller.ex b/lib/pleroma/web/feed/tag_controller.ex index 1e0f7223c..82326d861 100644 --- a/lib/pleroma/web/feed/tag_controller.ex +++ b/lib/pleroma/web/feed/tag_controller.ex @@ -10,7 +10,7 @@ defmodule Pleroma.Web.Feed.TagController do alias Pleroma.Web.Feed.FeedView def feed(conn, params) do - if Config.get!([:instance, :public]) do + if not Config.restrict_unauthenticated_access?(:timelines, :local) do render_feed(conn, params) else render_error(conn, :not_found, "Not found") @@ -18,10 +18,12 @@ def feed(conn, params) do end defp render_feed(conn, %{"tag" => raw_tag} = params) do + local_only = Config.restrict_unauthenticated_access?(:timelines, :federated) + {format, tag} = parse_tag(raw_tag) activities = - %{type: ["Create"], tag: tag} + %{type: ["Create"], tag: tag, local_only: local_only} |> Pleroma.Maps.put_if_present(:max_id, params["max_id"]) |> ActivityPub.fetch_public_activities() diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex index b320c9224..c6fe08a80 100644 --- a/lib/pleroma/web/feed/user_controller.ex +++ b/lib/pleroma/web/feed/user_controller.ex @@ -28,9 +28,12 @@ def feed_redirect(%{assigns: %{format: format}} = conn, _params) ActivityPubController.call(conn, :user) end - def feed_redirect(conn, %{"nickname" => nickname}) do + def feed_redirect(%{assigns: assigns} = conn, %{"nickname" => nickname}) do + format = Map.get(assigns, :format, "atom") + format = if format in ["atom", "rss"], do: format, else: "atom" + with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do - redirect(conn, external: "#{url(~p"/users/#{user.nickname}/feed")}.atom") + redirect(conn, external: "#{url(~p"/users/#{user.nickname}/feed")}.#{format}") end end diff --git a/lib/pleroma/web/gettext.ex b/lib/pleroma/web/gettext.ex index 7afcd38f0..44b608b93 100644 --- a/lib/pleroma/web/gettext.ex +++ b/lib/pleroma/web/gettext.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Web.Gettext do By using [Gettext](https://hexdocs.pm/gettext), your module gains a set of macros for translations, for example: - import Pleroma.Web.Gettext + use Gettext, backend: Pleroma.Web.Gettext # Simple translation gettext "Here is the string to translate" @@ -24,132 +24,7 @@ defmodule Pleroma.Web.Gettext do See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. """ - use Gettext, otp_app: :pleroma - - def language_tag do - # Naive implementation: HTML lang attribute uses BCP 47, which - # uses - as a separator. - # https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang - - Gettext.get_locale() - |> String.replace("_", "-", global: true) - end - - def normalize_locale(locale) do - if is_binary(locale) do - String.replace(locale, "-", "_", global: true) - else - nil - end - end - - def supports_locale?(locale) do - Pleroma.Web.Gettext - |> Gettext.known_locales() - |> Enum.member?(locale) - end - - def variant?(locale), do: String.contains?(locale, "_") - - def language_for_variant(locale) do - Enum.at(String.split(locale, "_"), 0) - end - - def ensure_fallbacks(locales) do - locales - |> Enum.flat_map(fn locale -> - others = - other_supported_variants_of_locale(locale) - |> Enum.filter(fn l -> not Enum.member?(locales, l) end) - - [locale] ++ others - end) - end - - def other_supported_variants_of_locale(locale) do - cond do - supports_locale?(locale) -> - [] - - variant?(locale) -> - lang = language_for_variant(locale) - if supports_locale?(lang), do: [lang], else: [] - - true -> - Gettext.known_locales(Pleroma.Web.Gettext) - |> Enum.filter(fn l -> String.starts_with?(l, locale <> "_") end) - end - end - - def get_locales do - Process.get({Pleroma.Web.Gettext, :locales}, []) - end - - def is_locale_list(locales) do - Enum.all?(locales, &is_binary/1) - end - - def put_locales(locales) do - if is_locale_list(locales) do - Process.put({Pleroma.Web.Gettext, :locales}, Enum.uniq(locales)) - Gettext.put_locale(Enum.at(locales, 0, Gettext.get_locale())) - :ok - else - {:error, :not_locale_list} - end - end - - def locale_or_default(locale) do - if supports_locale?(locale) do - locale - else - Gettext.get_locale() - end - end - - def with_locales_func(locales, fun) do - prev_locales = Process.get({Pleroma.Web.Gettext, :locales}) - put_locales(locales) - - try do - fun.() - after - if prev_locales do - put_locales(prev_locales) - else - Process.delete({Pleroma.Web.Gettext, :locales}) - Process.delete(Gettext) - end - end - end - - defmacro with_locales(locales, do: fun) do - quote do - Pleroma.Web.Gettext.with_locales_func(unquote(locales), fn -> - unquote(fun) - end) - end - end - - def to_locale_list(locale) when is_binary(locale) do - locale - |> String.split(",") - |> Enum.filter(&supports_locale?/1) - end - - def to_locale_list(_), do: [] - - defmacro with_locale_or_default(locale, do: fun) do - quote do - Pleroma.Web.Gettext.with_locales_func( - Pleroma.Web.Gettext.to_locale_list(unquote(locale)) - |> Enum.concat(Pleroma.Web.Gettext.get_locales()), - fn -> - unquote(fun) - end - ) - end - end + use Gettext.Backend, otp_app: :pleroma defp next_locale(locale, list) do index = Enum.find_index(list, fn item -> item == locale end) @@ -176,8 +51,9 @@ defp should_fallback?(locale) do locale != "en" end + @impl true def handle_missing_translation(locale, domain, msgctxt, msgid, bindings) do - next = next_locale(locale, get_locales()) + next = next_locale(locale, Pleroma.Web.GettextCompanion.get_locales()) if is_nil(next) or not should_fallback?(locale) do super(locale, domain, msgctxt, msgid, bindings) @@ -189,6 +65,7 @@ def handle_missing_translation(locale, domain, msgctxt, msgid, bindings) do end end + @impl true def handle_missing_plural_translation( locale, domain, @@ -198,7 +75,7 @@ def handle_missing_plural_translation( n, bindings ) do - next = next_locale(locale, get_locales()) + next = next_locale(locale, Pleroma.Web.GettextCompanion.get_locales()) if is_nil(next) or not should_fallback?(locale) do super(locale, domain, msgctxt, msgid, msgid_plural, n, bindings) diff --git a/lib/pleroma/web/gettext_companion.ex b/lib/pleroma/web/gettext_companion.ex new file mode 100644 index 000000000..98248dd5a --- /dev/null +++ b/lib/pleroma/web/gettext_companion.ex @@ -0,0 +1,139 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.GettextCompanion do + @moduledoc """ + Akkoma-specific functions controlling the Gettext backend or general utility for interfacing with it + """ + + @doc "Sets the passed locales as possible translation languages for strings; ordered by preference." + def put_locales(locales) do + if is_locale_list(locales) do + Process.put({Pleroma.Web.Gettext, :locales}, Enum.uniq(locales)) + Gettext.put_locale(Enum.at(locales, 0, Gettext.get_locale())) + :ok + else + {:error, :not_locale_list} + end + end + + # Public for usage in tests + def get_locales do + Process.get({Pleroma.Web.Gettext, :locales}, []) + end + + @doc """ + Ensures all entries can fallback to other locales without or with a different variant tag + by appending additional entries where needed and alternatives are supported by the backend. + """ + def ensure_fallbacks(locales) do + locales + |> Enum.flat_map(fn locale -> + others = + other_supported_variants_of_locale(locale) + |> Enum.filter(fn l -> not Enum.member?(locales, l) end) + + [locale] ++ others + end) + end + + @doc "Converts simple BCP-47 format langauge specifiers into Gettext locale specifier format" + def normalize_locale(locale) do + if is_binary(locale) do + String.replace(locale, "-", "_", global: true) + else + nil + end + end + + @doc "Converts a Gettext locale specifier into a BCP-47 language tag as used in HTML" + def language_tag do + # Naive implementation: HTML lang attribute uses BCP 47, which + # uses - as a separator. + # https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang + + Gettext.get_locale() + |> String.replace("_", "-", global: true) + end + + @doc "Checks whether the given locale has translations in Akkoma" + def supports_locale?(locale) do + Pleroma.Web.Gettext + |> Gettext.known_locales() + |> Enum.member?(locale) + end + + # used by macro + def with_locales_func(locales, fun) do + prev_locales = Process.get({Pleroma.Web.Gettext, :locales}) + put_locales(locales) + + try do + fun.() + after + if prev_locales do + put_locales(prev_locales) + else + Process.delete({Pleroma.Web.Gettext, :locales}) + Process.delete(Gettext) + end + end + end + + @doc "Temporarily changes the translation locale of the current process" + defmacro with_locales(locales, do: fun) do + quote do + Pleroma.Web.GettextCompanion.with_locales_func(unquote(locales), fn -> + unquote(fun) + end) + end + end + + # used by macro + def to_locale_list(locale) when is_binary(locale) do + locale + |> String.split(",") + |> Enum.filter(&supports_locale?/1) + end + + def to_locale_list(_), do: [] + + @doc "Temporarily changes the translation locale of the current process, allowing fallback to the default locale." + defmacro with_locale_or_default(locale, do: fun) do + quote do + Pleroma.Web.GettextCompanion.with_locales_func( + Pleroma.Web.GettextCompanion.to_locale_list(unquote(locale)) + |> Enum.concat(Pleroma.Web.GettextCompanion.get_locales()), + fn -> + unquote(fun) + end + ) + end + end + + defp is_locale_list(locales) do + Enum.all?(locales, &is_binary/1) + end + + defp variant?(locale), do: String.contains?(locale, "_") + + defp language_for_variant(locale) do + Enum.at(String.split(locale, "_"), 0) + end + + defp other_supported_variants_of_locale(locale) do + cond do + supports_locale?(locale) -> + [] + + variant?(locale) -> + lang = language_for_variant(locale) + if supports_locale?(lang), do: [lang], else: [] + + true -> + Gettext.known_locales(Pleroma.Web.Gettext) + |> Enum.filter(fn l -> String.starts_with?(l, locale <> "_") end) + end + end +end diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 2c7d6a893..63484de49 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -219,7 +219,10 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p |> Maps.put_if_present(:is_locked, params[:locked]) # Note: param name is indeed :discoverable (not an error) |> Maps.put_if_present(:is_discoverable, params[:discoverable]) - |> Maps.put_if_present(:language, Pleroma.Web.Gettext.normalize_locale(params[:language])) + |> Maps.put_if_present( + :language, + Pleroma.Web.GettextCompanion.normalize_locale(params[:language]) + ) |> Maps.put_if_present(:status_ttl_days, params[:status_ttl_days], status_ttl_days_value) |> Maps.put_if_present(:accepts_direct_messages_from, params[:accepts_direct_messages_from]) |> Maps.put_if_present(:permit_followback, params[:permit_followback]) @@ -422,6 +425,10 @@ def unfollow(%{assigns: %{user: follower, account: followed}} = conn, _params) d @doc "POST /api/v1/accounts/:id/mute" def mute(%{assigns: %{user: muter, account: muted}, body_params: params} = conn, _params) do + params = + params + |> Map.put_new(:duration, Map.get(params, :expires_in, 0)) + with {:ok, _user_relationships} <- User.mute(muter, muted, params) do render(conn, "relationship.json", user: muter, target: muted) else diff --git a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex index e534d0388..53d69a451 100644 --- a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] + alias Pleroma.FollowingRelationship alias Pleroma.User alias Pleroma.Web.CommonAPI alias Pleroma.Web.Plugs.OAuthScopesPlug @@ -31,12 +32,14 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do def index(%{assigns: %{user: followed}} = conn, params) do follow_requests = followed - |> User.get_follow_requests_query() - |> Pagination.fetch_paginated(params, :keyset, :follower) + |> FollowingRelationship.get_follow_requesting_users_with_request_id() + |> Pagination.fetch_paginated(params, :keyset) + + requesting_users = Pagination.unwrap(follow_requests) conn |> add_link_headers(follow_requests) - |> render("index.json", for: followed, users: follow_requests, as: :user) + |> render("index.json", for: followed, users: requesting_users, as: :user) end @doc "POST /api/v1/follow_requests/:id/authorize" diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex index 8e6cf2a6a..e98f2b5e0 100644 --- a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex @@ -56,7 +56,7 @@ def index(conn, %{account_id: account_id} = params) do def index(%{assigns: %{user: user}} = conn, params) do params = Map.new(params, fn {k, v} -> {to_string(k), v} end) - |> Map.put_new("include_types", @default_notification_types) + |> Map.put_new("types", Map.get(params, :include_types, @default_notification_types)) notifications = MastodonAPI.get_notifications(user, params) diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 79d590a7b..0b2efadef 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -262,7 +262,12 @@ def update(%{assigns: %{user: user}, body_params: body_params} = conn, %{id: id} @doc "GET /api/v1/statuses/:id" def show(%{assigns: %{user: user}} = conn, %{id: id} = params) do + # Announces are handled as normal statuses in MastoAPI, they just put the reblogged post + # in a "reblog" subproperty which clients the use for display. Creates are trivially ok. + # Everything else isn’t a status as far as MastoAPI is concerned and + # would show up buggy since it’s not expected by our JSON render, thus reject. with %Activity{} = activity <- Activity.get_by_id_with_object(id), + type when type in ["Create", "Announce"] <- activity.data["type"], true <- Visibility.visible_for_user?(activity, user) do try_render(conn, "show.json", activity: activity, @@ -278,6 +283,7 @@ def show(%{assigns: %{user: user}} = conn, %{id: id} = params) do @doc "DELETE /api/v1/statuses/:id" def delete(%{assigns: %{user: user}} = conn, %{id: id}) do with %Activity{} = activity <- Activity.get_by_id_with_object(id), + # CommonAPI already chcks whether user is allowed to delete {:ok, %Activity{}} <- CommonAPI.delete(id, user) do try_render(conn, "show.json", activity: activity, @@ -292,6 +298,7 @@ def delete(%{assigns: %{user: user}} = conn, %{id: id}) do @doc "POST /api/v1/statuses/:id/reblog" def reblog(%{assigns: %{user: user}, body_params: params} = conn, %{id: ap_id_or_id}) do + # CommonAPI checks if allowed to reblog with {:ok, announce} <- CommonAPI.repeat(ap_id_or_id, user, params), %Activity{} = announce <- Activity.normalize(announce.data) do try_render(conn, "show.json", %{activity: announce, for: user, as: :activity}) @@ -308,6 +315,7 @@ def unreblog(%{assigns: %{user: user}} = conn, %{id: activity_id}) do @doc "POST /api/v1/statuses/:id/favourite" def favourite(%{assigns: %{user: user}} = conn, %{id: activity_id}) do + # CommonAPI checks if allowed to fav with {:ok, _fav} <- CommonAPI.favorite(user, activity_id), %Activity{} = activity <- Activity.get_by_id(activity_id) do try_render(conn, "show.json", activity: activity, for: user, as: :activity) @@ -455,10 +463,11 @@ def context(%{assigns: %{user: user}} = conn, %{id: id}) do @doc "GET /api/v1/favourites" def favourites(%{assigns: %{user: %User{} = user}} = conn, params) do - activities = ActivityPub.fetch_favourites(user, params) + activities_keyed = ActivityPub.fetch_favourited_with_fav_id(user, params) + activities = Pleroma.Pagination.unwrap(activities_keyed) conn - |> add_link_headers(activities) + |> add_link_headers(activities_keyed) |> render("index.json", activities: activities, for: user, @@ -525,7 +534,7 @@ defp fetch_or_translate(status_id, text, source_language, target_language, trans value = translation_module.translate(text, source_language, target_language) with {:ok, _, _} <- value do - value + {:commit, value} else _ -> {:ignore, value} end diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex index 23846b36a..700474137 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex @@ -65,7 +65,7 @@ def get_notifications(user, params \\ %{}) do user |> Notification.for_user_query(options) - |> restrict(:include_types, options) + |> restrict(:types, options) |> restrict(:exclude_types, options) |> restrict(:account_ap_id, options) |> Pagination.fetch_paginated(params) @@ -80,7 +80,7 @@ def get_scheduled_activities(user, params \\ %{}) do defp cast_params(params) do param_types = %{ exclude_types: {:array, :string}, - include_types: {:array, :string}, + types: {:array, :string}, exclude_visibilities: {:array, :string}, reblogs: :boolean, with_muted: :boolean, @@ -92,7 +92,7 @@ defp cast_params(params) do changeset.changes end - defp restrict(query, :include_types, %{include_types: mastodon_types = [_ | _]}) do + defp restrict(query, :types, %{types: mastodon_types = [_ | _]}) do where(query, [n], n.type in ^mastodon_types) end diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index e9c04e927..0a13cf9f9 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -343,8 +343,8 @@ defp maybe_put_follow_requests_count( ) do count = user - |> User.get_follow_requests() - |> length() + |> User.get_follow_requests_query() + |> Pleroma.Repo.aggregate(:count) data |> Kernel.put_in([:follow_requests_count], count) diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index d5c484561..85be1f0a9 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -14,9 +14,11 @@ def render("show.json", _) do instance = Config.get(:instance) %{ - uri: Pleroma.Web.Endpoint.url(), + uri: Pleroma.Web.WebFinger.domain(), title: Keyword.get(instance, :name), description: Keyword.get(instance, :description), + short_description: + Keyword.get(instance, :short_description) || Keyword.get(instance, :description), version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})", email: Keyword.get(instance, :email), urls: %{ diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 65aeecfcf..222821847 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -26,6 +26,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1, visible_for_user?: 2] + # Used as a placeholder to represent known-existing relatives we do cannot resolve locally + # will always 404 when supplied to API endpoints + @ghost_flake_id "_" + + @valid_attach_types ["image", "audio", "video"] + defp fetch_rich_media_for_activities(activities) do Enum.each(activities, fn activity -> Card.get_by_activity(activity) @@ -277,9 +283,12 @@ def render("show.json", %{activity: %{id: id, data: %{"object" => _object}} = ac nil end - reply_to = get_reply_to(activity, opts) + reply_to_apid = get_single_apid(object.data, "inReplyTo") + reply_to = reply_to_apid && get_reply_to(activity, opts) + reply_to_id = reply_to_apid && get_id_or_ghost(reply_to) reply_to_user = reply_to && CommonAPI.get_user(reply_to.data["actor"]) + reply_to_user_id = reply_to_apid && get_id_or_ghost(reply_to_user) history_len = 1 + @@ -363,7 +372,10 @@ def render("show.json", %{activity: %{id: id, data: %{"object" => _object}} = ac {pinned?, pinned_at} = pin_data(object, user) - quote = Activity.get_quoted_activity_from_object(object) + quote_apid = get_single_apid(object.data, "quoteUri") + quote = quote_apid && Activity.get_quoted_activity_from_object(object) + quote_id = quote_apid && get_id_or_ghost(quote) + lang = language(object) %{ @@ -375,8 +387,8 @@ def render("show.json", %{activity: %{id: id, data: %{"object" => _object}} = ac user: user, for: opts[:for] }), - in_reply_to_id: reply_to && to_string(reply_to.id), - in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id), + in_reply_to_id: reply_to_id, + in_reply_to_account_id: reply_to_user_id, reblog: nil, card: card, content: content_html, @@ -401,7 +413,7 @@ def render("show.json", %{activity: %{id: id, data: %{"object" => _object}} = ac application: build_application(object.data["generator"]), language: lang, emojis: build_emojis(object.data["emoji"]), - quote_id: if(quote, do: quote.id, else: nil), + quote_id: quote_id, quote: maybe_render_quote(quote, opts), emoji_reactions: emoji_reactions, pleroma: %{ @@ -419,7 +431,12 @@ def render("show.json", %{activity: %{id: id, data: %{"object" => _object}} = ac pinned_at: pinned_at }, akkoma: %{ - source: object.data["source"] + source: object.data["source"], + # Note: these AP IDs will also be filled out if we cannot resolve the actual object + # (e.g. because it’s a private post we aren't allowed to access, or just federation woes) + # allowing users to potentially discover the full context from other accounts/servers. + in_reply_to_apid: reply_to_apid, + quote_apid: quote_apid } } else @@ -558,18 +575,22 @@ def render("card.json", _), do: nil def render("attachment.json", %{attachment: attachment}) do [attachment_url | _] = attachment["url"] - media_type = attachment_url["mediaType"] || attachment_url["mimeType"] || "image" + + media_type = + attachment_url["mediaType"] || attachment_url["mimeType"] || "application/octet-stream" + href = attachment_url["href"] |> MediaProxy.url() href_preview = attachment_url["href"] |> MediaProxy.preview_url() meta = render("attachment_meta.json", %{attachment: attachment}) + # try to deduce type from full MIME, but if inconclusive (and since full type not set + # by all remote servers) try to fallback to generic type + generic_type = String.downcase(attachment["type"] || "") + type = - cond do - String.contains?(media_type, "image") -> "image" - String.contains?(media_type, "video") -> "video" - String.contains?(media_type, "audio") -> "audio" - true -> "unknown" - end + Enum.find(@valid_attach_types, fn type -> String.contains?(media_type, type) end) || + (generic_type in @valid_attach_types && generic_type) || + "unknown" attachment_id = with {_, ap_id} when is_binary(ap_id) <- {:ap_id, attachment["id"]}, @@ -589,7 +610,7 @@ def render("attachment.json", %{attachment: attachment}) do preview_url: href_preview, text_url: href, type: type, - description: attachment["name"], + description: attachment["summary"] || attachment["name"], pleroma: %{mime_type: media_type}, blurhash: attachment["blurhash"] } @@ -637,6 +658,26 @@ defp proxied_url(url, page_url_data) do end end + defp get_id_or_ghost(object) do + (object && to_string(object.id)) || @ghost_flake_id + end + + defp get_single_apid(object, key) do + apid = object[key] + + apid = + case apid do + [head | _] -> head + _ -> apid + end + + if apid != "" and is_binary(apid) do + apid + else + nil + end + end + def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do object = Object.normalize(activity, fetch: false) diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 84e07132a..14ab2b5dd 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -54,7 +54,7 @@ defp handle_preview(conn, url) do media_proxy_url = MediaProxy.url(url) with {:ok, %{status: status} = head_response} when status in 200..299 <- - Pleroma.HTTP.request("HEAD", media_proxy_url, [], [], name: MyFinch) do + Pleroma.HTTP.head(media_proxy_url) do content_type = Tesla.get_header(head_response, "content-type") content_length = Tesla.get_header(head_response, "content-length") content_length = content_length && String.to_integer(content_length) diff --git a/lib/pleroma/web/metadata.ex b/lib/pleroma/web/metadata.ex index 2256b2bae..de8820f82 100644 --- a/lib/pleroma/web/metadata.ex +++ b/lib/pleroma/web/metadata.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.Metadata do alias Phoenix.HTML + alias PhoenixHTMLHelpers.Tag def build_static_tags(params) do providers = [ @@ -42,10 +43,10 @@ def build_tags(params) do def to_tag(data) do with {name, attrs, _content = []} <- data do - HTML.Tag.tag(name, attrs) + Tag.tag(name, attrs) else {name, attrs, content} -> - HTML.Tag.content_tag(name, content, attrs) + Tag.content_tag(name, content, attrs) _ -> raise ArgumentError, message: "make_tag invalid args" diff --git a/lib/pleroma/web/metadata/player_view.ex b/lib/pleroma/web/metadata/player_view.ex index 9be5e433d..4768817ec 100644 --- a/lib/pleroma/web/metadata/player_view.ex +++ b/lib/pleroma/web/metadata/player_view.ex @@ -4,7 +4,7 @@ defmodule Pleroma.Web.Metadata.PlayerView do use Pleroma.Web, :view - import Phoenix.HTML.Tag, only: [content_tag: 3, tag: 2] + import PhoenixHTMLHelpers.Tag, only: [content_tag: 3, tag: 2] def render("player.html", %{"mediaType" => type, "href" => href}) do {tag_type, tag_attrs} = diff --git a/lib/pleroma/web/nodeinfo/nodeinfo.ex b/lib/pleroma/web/nodeinfo/nodeinfo.ex index 532ae53a7..25b1bfe64 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo.ex @@ -9,6 +9,12 @@ defmodule Pleroma.Web.Nodeinfo.Nodeinfo do alias Pleroma.Web.Federator.Publisher alias Pleroma.Web.MastodonAPI.InstanceView + defp description() do + # The text in nodeinfo should be plain text and preferably not too long + Config.get([:instance, :short_description]) || + Config.get([:instance, :description]) + end + # returns a nodeinfo 2.0 map, since 2.1 just adds a repository field # under software. def get_nodeinfo("2.0") do @@ -43,7 +49,7 @@ def get_nodeinfo("2.0") do }, metadata: %{ nodeName: Config.get([:instance, :name]), - nodeDescription: Config.get([:instance, :description]), + nodeDescription: description(), private: !Config.get([:instance, :public], true), suggestions: %{ enabled: false diff --git a/lib/pleroma/web/o_auth/mfa_view.ex b/lib/pleroma/web/o_auth/mfa_view.ex index 952c90efe..7c96d5f47 100644 --- a/lib/pleroma/web/o_auth/mfa_view.ex +++ b/lib/pleroma/web/o_auth/mfa_view.ex @@ -4,9 +4,8 @@ defmodule Pleroma.Web.OAuth.MFAView do use Pleroma.Web, :view - import Phoenix.HTML.Form alias Pleroma.MFA - alias Pleroma.Web.Gettext + use Gettext, backend: Pleroma.Web.Gettext def render("mfa_response.json", %{token: token, user: user}) do %{ diff --git a/lib/pleroma/web/o_auth/o_auth_view.ex b/lib/pleroma/web/o_auth/o_auth_view.ex index 57a315705..f318e0f93 100644 --- a/lib/pleroma/web/o_auth/o_auth_view.ex +++ b/lib/pleroma/web/o_auth/o_auth_view.ex @@ -4,9 +4,8 @@ defmodule Pleroma.Web.OAuth.OAuthView do use Pleroma.Web, :view - import Phoenix.HTML.Form import Phoenix.HTML - alias Pleroma.Web.Gettext + use Gettext, backend: Pleroma.Web.Gettext alias Pleroma.Web.OAuth.Token.Utils diff --git a/lib/pleroma/web/o_status/o_status_controller.ex b/lib/pleroma/web/o_status/o_status_controller.ex index 2b2872c9a..19752f0f5 100644 --- a/lib/pleroma/web/o_status/o_status_controller.ex +++ b/lib/pleroma/web/o_status/o_status_controller.ex @@ -56,6 +56,7 @@ def activity(%{assigns: %{format: format}} = conn, _params) def activity(conn, _params) do with id <- Endpoint.url() <> conn.request_path, {_, %Activity{} = activity} <- {:activity, Activity.normalize(id)}, + {_, "Create"} <- {:type, activity.data["type"]}, {_, true} <- {:public?, Visibility.is_public?(activity)}, {_, false} <- {:local_public?, Visibility.is_local_public?(activity)} do redirect(conn, to: "/notice/#{activity.id}") @@ -63,6 +64,9 @@ def activity(conn, _params) do reason when reason in [{:public?, false}, {:activity, nil}] -> {:error, :not_found} + {:type, _} -> + {:error, :not_found} + e -> e end @@ -114,7 +118,7 @@ def notice_player(conn, %{"id" => id}) do %{data: %{"attachment" => [%{"url" => [url | _]} | _]}} <- object, true <- String.starts_with?(url["mediaType"], ["audio", "video"]) do conn - |> put_layout(:metadata_player) + |> put_layout(html: {Pleroma.Web.LayoutView, :metadata_player}) |> put_resp_header("x-frame-options", "ALLOW") |> put_resp_header( "content-security-policy", diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex index 66cff7aaa..6a97bf365 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do alias Pleroma.Activity alias Pleroma.Object alias Pleroma.User + alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.CommonAPI alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.Plugs.OAuthScopesPlug @@ -28,6 +29,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do def index(%{assigns: %{user: user}} = conn, %{id: activity_id} = params) do with true <- Pleroma.Config.get([:instance, :show_reactions]), %Activity{} = activity <- Activity.get_by_id_with_object(activity_id), + {_, true} <- {:visible, Visibility.visible_for_user?(activity, user)}, %Object{data: %{"reactions" => reactions}} when is_list(reactions) <- Object.normalize(activity, fetch: false) do reactions = @@ -37,6 +39,7 @@ def index(%{assigns: %{user: user}} = conn, %{id: activity_id} = params) do render(conn, "index.json", emoji_reactions: reactions, user: user) else + {:visible, _} -> {:error, :forbidden} _e -> json(conn, []) end end @@ -101,6 +104,7 @@ def create(%{assigns: %{user: user}} = conn, %{id: activity_id, emoji: emoji}) d |> Pleroma.Emoji.fully_qualify_emoji() |> Pleroma.Emoji.maybe_quote() + # CommonAPI checks if allowed to react with {:ok, _activity} <- CommonAPI.react_with_emoji(activity_id, user, emoji) do activity = Activity.get_by_id(activity_id) @@ -116,6 +120,7 @@ def delete(%{assigns: %{user: user}} = conn, %{id: activity_id, emoji: emoji}) d |> Pleroma.Emoji.fully_qualify_emoji() |> Pleroma.Emoji.maybe_quote() + # CommonAPI checks only author can revoke reactions with {:ok, _activity} <- CommonAPI.unreact_with_emoji(activity_id, user, emoji) do activity = Activity.get_by_id(activity_id) diff --git a/lib/pleroma/web/plugs/cache.ex b/lib/pleroma/web/plugs/cache.ex index 935b2d834..17ed46833 100644 --- a/lib/pleroma/web/plugs/cache.ex +++ b/lib/pleroma/web/plugs/cache.ex @@ -102,7 +102,7 @@ defp cache_resp(conn, opts) do conn = unless opts[:tracking_fun] do if should_cache do - @cachex.put(:web_resp_cache, key, {content_type, body}, ttl: ttl) + @cachex.put(:web_resp_cache, key, {content_type, body}, expire: ttl) end conn @@ -110,7 +110,9 @@ defp cache_resp(conn, opts) do tracking_fun_data = Map.get(conn.assigns, :tracking_fun_data, nil) if should_cache do - @cachex.put(:web_resp_cache, key, {content_type, body, tracking_fun_data}, ttl: ttl) + @cachex.put(:web_resp_cache, key, {content_type, body, tracking_fun_data}, + expire: ttl + ) end opts.tracking_fun.(conn, tracking_fun_data) diff --git a/lib/pleroma/web/plugs/ensure_user_public_key_plug.ex b/lib/pleroma/web/plugs/ensure_user_public_key_plug.ex deleted file mode 100644 index 5b090f289..000000000 --- a/lib/pleroma/web/plugs/ensure_user_public_key_plug.ex +++ /dev/null @@ -1,32 +0,0 @@ -defmodule Pleroma.Web.Plugs.EnsureUserPublicKeyPlug do - @moduledoc """ - This plug will attempt to pull in a user's public key if we do not have it. - We _should_ be able to request the URL from the key URL... - """ - - alias Pleroma.User - - def init(options), do: options - - def call(conn, _opts) do - key_id = key_id_from_conn(conn) - - unless is_nil(key_id) do - User.SigningKey.get_or_fetch_by_key_id(key_id) - # now we SHOULD have the user that owns the key locally. maybe. - # if we don't, we'll error out when we try to validate. - end - - conn - end - - defp key_id_from_conn(conn) do - case HTTPSignatures.signature_for_conn(conn) do - %{"keyId" => key_id} when is_binary(key_id) -> - key_id - - _ -> - nil - end - end -end diff --git a/lib/pleroma/web/plugs/http_signature_plug.ex b/lib/pleroma/web/plugs/http_signature_plug.ex index 195a9dc1c..1b72a1d3a 100644 --- a/lib/pleroma/web/plugs/http_signature_plug.ex +++ b/lib/pleroma/web/plugs/http_signature_plug.ex @@ -6,14 +6,12 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do import Plug.Conn import Phoenix.Controller, only: [get_format: 1] + alias HTTPSignatures.HTTPKey + use Pleroma.Web, :verified_routes alias Pleroma.Activity - alias Pleroma.Instances - alias Pleroma.User.SigningKey require Logger - @cachex Pleroma.Config.get([:cachex, :provider], Cachex) - def init(options) do options end @@ -26,70 +24,123 @@ def call(conn, _opts) do if get_format(conn) in ["json", "activity+json"] do conn |> maybe_assign_valid_signature() - |> maybe_require_signature() else conn end end - def route_aliases(%{path_info: ["objects", id], query_string: query_string}) do + def route_aliases(%{path_info: ["objects", id], query_string: query_string, method: method}) do ap_id = url(~p[/objects/#{id}]) + method = String.downcase(method) - with %Activity{} = activity <- Activity.get_by_object_ap_id_with_object(ap_id) do - [~p"/notice/#{activity.id}", "/notice/#{activity.id}?#{query_string}"] - else - _ -> [] - end + [ + fn -> + with %Activity{} = activity <- Activity.get_by_object_ap_id_with_object(ap_id) do + ["#{method} /notice/#{activity.id}", "#{method} /notice/#{activity.id}?#{query_string}"] + else + _ -> [] + end + end + ] end def route_aliases(_), do: [] - def maybe_put_created_psudoheader(conn) do - case HTTPSignatures.signature_for_conn(conn) do - %{"created" => created} -> - put_req_header(conn, "(created)", created) + defp maybe_log_error(conn, verification_error) do + siginfo_str = + "<#{conn.method} #{conn.request_path}> #{inspect(get_req_header(conn, "signature"))}" - _ -> - conn + case verification_error do + :gone -> + # We can't verify the data since the actor was deleted and not previously known. + # Likely we just received the actor’s Delete activity, so just silently drop. + Logger.debug( + "Unable to verify request signature of deleted actor; dropping (#{siginfo_str})" + ) + + {:reject, reason} -> + Logger.debug( + "Refusing to validate signature from rejected key due to #{inspect(reason)} #{siginfo_str}" + ) + + :wrong_signature -> + Logger.warning("Received request with invalid signature!\n#{inspect(conn)}") + + {:fetch_key, e} -> + Logger.info( + "Unable to verify request since key cannot be retrieved: #{inspect(e)} #{siginfo_str}" + ) + + error -> + Logger.error( + "Failed to verify request signature due to fatal error: #{inspect(error)} #{inspect(conn)}" + ) end - end - - def maybe_put_expires_psudoheader(conn) do - case HTTPSignatures.signature_for_conn(conn) do - %{"expires" => expires} -> - put_req_header(conn, "(expires)", expires) - - _ -> - conn - end - end - - defp assign_valid_signature_on_route_aliases(conn, []), do: conn - - defp assign_valid_signature_on_route_aliases(%{assigns: %{valid_signature: true}} = conn, _), - do: conn - - defp assign_valid_signature_on_route_aliases(conn, [path | rest]) do - request_target = String.downcase("#{conn.method}") <> " #{path}" - - conn = - conn - |> put_req_header("(request-target)", request_target) - |> maybe_put_created_psudoheader() - |> maybe_put_expires_psudoheader() conn - |> assign(:valid_signature, HTTPSignatures.validate_conn(conn)) - |> assign(:signature_actor_id, signature_host(conn)) - |> assign_valid_signature_on_route_aliases(rest) + end + + defp maybe_halt(conn, :gone) do + # If the key was deleted the error is basically unrecoverable. + # Most likely it was the Delete activity for the key actor and we never knew about this actor before. + # Older Mastodon is very insistent about resending those Deletes until it receives a success. + # see: https://github.com/mastodon/mastodon/pull/33617 + with "POST" <- conn.method, + %{"type" => "Delete"} <- conn.body_params do + conn + |> resp(202, "Accepted") + |> halt() + else + _ -> conn + end + end + + defp maybe_halt(conn, {:reject, _}) do + cond do + conn.method == "POST" -> + conn + |> resp(202, "Accepted") + |> halt() + + true -> + conn + |> resp(404, "Not found") + |> halt() + end + end + + defp maybe_halt(conn, _), do: conn + + defp assign_valid_signature(%{assigns: %{valid_signature: true}} = conn, _), + do: conn + + defp assign_valid_signature(conn, request_targets) do + case HTTPSignatures.validate_conn(conn, request_targets) do + {:ok, %HTTPKey{user_data: ud}} -> + conn + |> assign(:valid_signature, true) + |> assign(:signature_user, ud["key_user"]) + + {:error, e} -> + conn + |> assign(:valid_signature, false) + |> assign(:signature_user, nil) + |> maybe_log_error(e) + |> maybe_halt(e) + end end defp maybe_assign_valid_signature(conn) do if has_signature_header?(conn) do # set (request-target) header to the appropriate value # we also replace the digest header with the one we computed - possible_paths = - [conn.request_path, conn.request_path <> "?#{conn.query_string}" | route_aliases(conn)] + method = String.downcase(conn.method) + + request_targets = + [ + "#{method} " <> conn.request_path, + "#{method} " <> conn.request_path <> "?#{conn.query_string}" | route_aliases(conn) + ] conn = case conn do @@ -97,7 +148,7 @@ defp maybe_assign_valid_signature(conn) do conn -> conn end - assign_valid_signature_on_route_aliases(conn, possible_paths) + assign_valid_signature(conn, request_targets) else Logger.debug("No signature header!") conn @@ -107,53 +158,4 @@ defp maybe_assign_valid_signature(conn) do defp has_signature_header?(conn) do conn |> get_req_header("signature") |> Enum.at(0, false) end - - defp maybe_require_signature( - %{assigns: %{valid_signature: true, signature_actor_id: actor_id}} = conn - ) do - # inboxes implicitly need http signatures for authentication - # so we don't really know if the instance will have broken federation after - # we turn on authorized_fetch_mode. - # - # to "check" this is a signed fetch, verify if method is GET - if conn.method == "GET" do - actor_host = URI.parse(actor_id).host - - case @cachex.get(:request_signatures_cache, actor_host) do - {:ok, nil} -> - Logger.debug("Successful signature from #{actor_host}") - Instances.set_request_signatures(actor_host) - @cachex.put(:request_signatures_cache, actor_host, true) - - {:ok, true} -> - :noop - - any -> - Logger.warning( - "expected request signature cache to return a boolean, instead got #{inspect(any)}" - ) - end - end - - conn - end - - defp maybe_require_signature(conn), do: conn - - defp signature_host(conn) do - with {:key_id, %{"keyId" => kid}} <- {:key_id, HTTPSignatures.signature_for_conn(conn)}, - {:actor_id, actor_id, _} when actor_id != nil <- - {:actor_id, SigningKey.key_id_to_ap_id(kid), kid} do - actor_id - else - {:key_id, e} -> - Logger.error("Failed to extract key_id from signature: #{inspect(e)}") - nil - - {:actor_id, _, kid} -> - # SigningKeys SHOULD have been fetched before this gets called! - Logger.error("Failed to extract actor_id from signature: signing key #{kid} not known") - nil - end - end end diff --git a/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex b/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex index 0d5cb9eab..4b0b988e6 100644 --- a/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex +++ b/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex @@ -15,101 +15,60 @@ def init(options), do: options def call(%{assigns: %{user: %User{}}} = conn, _opts), do: conn # if this has payload make sure it is signed by the same actor that made it - def call(%{assigns: %{valid_signature: true}, params: %{"actor" => actor}} = conn, _opts) do + def call( + %{ + assigns: %{valid_signature: true, signature_user: signature_user}, + params: %{"actor" => actor} + } = conn, + _opts + ) do with actor_id <- Utils.get_ap_id(actor), - {:user, %User{} = user} <- {:user, user_from_key_id(conn)}, - {:federate, true} <- {:federate, should_federate?(user)}, - {:user_match, true} <- {:user_match, user.ap_id == actor_id} do + {:federate, true} <- {:federate, should_federate?(signature_user)}, + {:user_match, true} <- {:user_match, signature_user.ap_id == actor_id} do conn - |> assign(:user, user) + |> assign(:user, signature_user) |> AuthHelper.skip_oauth() else {:user_match, false} -> Logger.debug("Failed to map identity from signature (payload actor mismatch)") - Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{inspect(actor)}") - conn - |> assign(:valid_signature, false) + Logger.debug( + "key_user=#{signature_user.id}(#{signature_user.ap_id}), actor=#{inspect(actor)}" + ) - # remove me once testsuite uses mapped capabilities instead of what we do now - {:user, _} -> - Logger.debug("Failed to map identity from signature (lookup failure)") - Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{actor}") + assign(conn, :valid_signature, false) - conn - |> assign(:valid_signature, false) - - {:federate, false} -> - Logger.debug("Identity from signature is instance blocked") - Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{actor}") - - conn - |> assign(:valid_signature, false) + error -> + handle_common_errors(conn, actor, signature_user, error) end end # no payload, probably a signed fetch - def call(%{assigns: %{valid_signature: true}} = conn, _opts) do - with %User{} = user <- user_from_key_id(conn), - {:federate, true} <- {:federate, should_federate?(user)} do + def call(%{assigns: %{valid_signature: true, signature_user: signature_user}} = conn, _opts) do + with {:federate, true} <- {:federate, should_federate?(signature_user)} do conn - |> assign(:user, user) + |> assign(:user, signature_user) |> AuthHelper.skip_oauth() else - {:federate, false} -> - Logger.debug("Identity from signature is instance blocked") - Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}") - - conn - |> assign(:valid_signature, false) - - nil -> - Logger.debug("Failed to map identity from signature (lookup failure)") - Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}") - - conn - |> assign(:valid_signature, false) - - _ -> - Logger.debug("Failed to map identity from signature (no payload actor mismatch)") - Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}") - - conn - |> assign(:valid_signature, false) + error -> handle_common_errors(conn, nil, signature_user, error) end end + # supposedly valid signature but no user (this isn’t supposed to happen) + def call(%{assigns: %{valid_signature: true}} = conn, _opts), + do: assign(conn, :valid_signature, false) + # no signature at all def call(conn, _opts), do: conn - defp key_id_from_conn(conn) do - case HTTPSignatures.signature_for_conn(conn) do - %{"keyId" => key_id} when is_binary(key_id) -> - key_id + def handle_common_errors(conn, actor, signature_user, error) do + actor_str = if actor == nil, do: "", else: " actor=#{inspect(actor)}" - _ -> - nil - end - end - - defp user_from_key_id(conn) do - with {:key_id, key_id} when is_binary(key_id) <- {:key_id, key_id_from_conn(conn)}, - {:mapped_ap_id, ap_id} when is_binary(ap_id) <- - {:mapped_ap_id, User.SigningKey.key_id_to_ap_id(key_id)}, - {:user_fetch, {:ok, %User{} = user}} <- {:user_fetch, User.get_or_fetch_by_ap_id(ap_id)} do - user - else - {:key_id, nil} -> - Logger.debug("Failed to map identity from signature (no key ID)") - {:key_id, nil} - - {:mapped_ap_id, nil} -> - Logger.debug("Failed to map identity from signature (could not map key ID to AP ID)") - {:mapped_ap_id, nil} - - {:user_fetch, {:error, _}} -> - Logger.debug("Failed to map identity from signature (lookup failure)") - {:user_fetch, nil} + case error do + {:federate, false} -> + Logger.debug("Identity from signature is instance blocked") + Logger.debug("key_user=#{signature_user.nickname}(#{signature_user.id})#{actor_str}") + assign(conn, :valid_signature, false) end end diff --git a/lib/pleroma/web/plugs/o_auth_scopes_plug.ex b/lib/pleroma/web/plugs/o_auth_scopes_plug.ex index f017c8bc7..3a9d0194e 100644 --- a/lib/pleroma/web/plugs/o_auth_scopes_plug.ex +++ b/lib/pleroma/web/plugs/o_auth_scopes_plug.ex @@ -4,7 +4,9 @@ defmodule Pleroma.Web.Plugs.OAuthScopesPlug do import Plug.Conn - import Pleroma.Web.Gettext + + use Gettext, + backend: Pleroma.Web.Gettext alias Pleroma.Helpers.AuthHelper diff --git a/lib/pleroma/web/plugs/set_locale_plug.ex b/lib/pleroma/web/plugs/set_locale_plug.ex index e78917199..8e7d9db71 100644 --- a/lib/pleroma/web/plugs/set_locale_plug.ex +++ b/lib/pleroma/web/plugs/set_locale_plug.ex @@ -14,7 +14,7 @@ def call(conn, _) do locales = get_locales_from_header(conn) first_locale = Enum.at(locales, 0, Gettext.get_locale()) - Pleroma.Web.Gettext.put_locales(locales) + Pleroma.Web.GettextCompanion.put_locales(locales) conn |> assign(:locale, first_locale) @@ -31,13 +31,13 @@ defp get_locales_from_header(conn) do defp all_supported(locales) do locales - |> Pleroma.Web.Gettext.ensure_fallbacks() + |> Pleroma.Web.GettextCompanion.ensure_fallbacks() |> Enum.filter(&supported_locale?/1) end defp normalize_language_codes(codes) do codes - |> Enum.map(fn code -> Pleroma.Web.Gettext.normalize_locale(code) end) + |> Enum.map(fn code -> Pleroma.Web.GettextCompanion.normalize_locale(code) end) end defp extract_preferred_language(conn) do @@ -74,7 +74,7 @@ defp extract_accept_language(conn) do end defp supported_locale?(locale) do - Pleroma.Web.Gettext.supports_locale?(locale) + Pleroma.Web.GettextCompanion.supports_locale?(locale) end defp parse_language_option(string) do diff --git a/lib/pleroma/web/plugs/uploaded_media.ex b/lib/pleroma/web/plugs/uploaded_media.ex index 15d6190df..e7ab59d2f 100644 --- a/lib/pleroma/web/plugs/uploaded_media.ex +++ b/lib/pleroma/web/plugs/uploaded_media.ex @@ -7,7 +7,10 @@ defmodule Pleroma.Web.Plugs.UploadedMedia do """ import Plug.Conn - import Pleroma.Web.Gettext + + use Gettext, + backend: Pleroma.Web.Gettext + require Logger alias Pleroma.Web.MediaProxy diff --git a/lib/pleroma/web/preload.ex b/lib/pleroma/web/preload.ex index 57705d2de..bd250bdb1 100644 --- a/lib/pleroma/web/preload.ex +++ b/lib/pleroma/web/preload.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.Preload do alias Phoenix.HTML + alias PhoenixHTMLHelpers.Tag def build_tags(%{assigns: %{csp_nonce: nonce}}, params) do preload_data = @@ -27,7 +28,7 @@ def build_tags(%{assigns: %{csp_nonce: nonce}}, params) do end def build_script_tag(content, nonce) do - HTML.Tag.content_tag(:script, HTML.raw(content), + Tag.content_tag(:script, HTML.raw(content), id: "initial-results", type: "application/json", nonce: nonce diff --git a/lib/pleroma/web/rel_me.ex b/lib/pleroma/web/rel_me.ex index afb525dbe..c1a432c5d 100644 --- a/lib/pleroma/web/rel_me.ex +++ b/lib/pleroma/web/rel_me.ex @@ -4,8 +4,7 @@ defmodule Pleroma.Web.RelMe do @options [ - max_body: 2_000_000, - receive_timeout: 2_000 + adapter: [receive_timeout: 2_000] ] if Pleroma.Config.get(:env) == :test do diff --git a/lib/pleroma/web/rich_media/backfill.ex b/lib/pleroma/web/rich_media/backfill.ex index 16730ec5a..874ce64da 100644 --- a/lib/pleroma/web/rich_media/backfill.ex +++ b/lib/pleroma/web/rich_media/backfill.ex @@ -96,7 +96,7 @@ defp stream_update(%{"activity_id" => activity_id}) do 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) + @cachex.put(:rich_media_cache, key, nil, expire: ttl) {:discard, :error} end end diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index 79ad660a2..46342f00d 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -9,7 +9,7 @@ def rich_media_get(url) do headers = [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}] head_check = - case Pleroma.HTTP.head(url, headers, http_options()) do + case Pleroma.HTTP.head(url, headers) 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 -> @@ -24,7 +24,7 @@ def rich_media_get(url) do :ok end - with :ok <- head_check, do: Pleroma.HTTP.get(url, headers, http_options()) + with :ok <- head_check, do: Pleroma.HTTP.get(url, headers) end defp check_content_type(headers) do @@ -41,7 +41,7 @@ defp check_content_type(headers) do end defp check_content_length(headers) do - max_body = Keyword.get(http_options(), :max_body) + max_body = Config.get([:rich_media, :max_body], 5_000_000) case List.keyfind(headers, "content-length", 0) do {_, maybe_content_length} -> @@ -55,11 +55,4 @@ 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/router.ex b/lib/pleroma/web/router.ex index 67c1919d0..31047daf5 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -146,13 +146,11 @@ defmodule Pleroma.Web.Router do end pipeline :optional_http_signature do - plug(Pleroma.Web.Plugs.EnsureUserPublicKeyPlug) plug(Pleroma.Web.Plugs.HTTPSignaturePlug) plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug) end pipeline :http_signature do - plug(Pleroma.Web.Plugs.EnsureUserPublicKeyPlug) plug(Pleroma.Web.Plugs.HTTPSignaturePlug) plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug) plug(Pleroma.Web.Plugs.EnsureHTTPSignaturePlug) @@ -830,6 +828,7 @@ defmodule Pleroma.Web.Router do get("/users/:nickname/outbox", ActivityPubController, :outbox) post("/users/:nickname/inbox", ActivityPubController, :inbox) get("/users/:nickname/collections/featured", ActivityPubController, :pinned) + get("/objects/:uuid/replies", ActivityPubController, :object_replies) end scope "/relay", Pleroma.Web.ActivityPub do diff --git a/lib/pleroma/web/static_fe/static_fe_controller.ex b/lib/pleroma/web/static_fe/static_fe_controller.ex index b1ea3178d..8d57d171e 100644 --- a/lib/pleroma/web/static_fe/static_fe_controller.ex +++ b/lib/pleroma/web/static_fe/static_fe_controller.ex @@ -12,7 +12,7 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.Metadata - plug(:put_layout, :static_fe) + plug(:put_layout, html: {Pleroma.Web.LayoutView, :static_fe}) plug(:assign_id) @page_keys ["max_id", "min_id", "limit", "since_id", "order"] diff --git a/lib/pleroma/web/static_fe/static_fe_view.ex b/lib/pleroma/web/static_fe/static_fe_view.ex index c1d83c5a0..5b0f1be73 100644 --- a/lib/pleroma/web/static_fe/static_fe_view.ex +++ b/lib/pleroma/web/static_fe/static_fe_view.ex @@ -8,12 +8,10 @@ defmodule Pleroma.Web.StaticFE.StaticFEView do alias Calendar.Strftime alias Pleroma.Emoji.Formatter alias Pleroma.User - alias Pleroma.Web.Gettext + use Gettext, backend: Pleroma.Web.Gettext alias Pleroma.Web.MediaProxy alias Pleroma.Web.Metadata.Utils - use Phoenix.HTML - @media_types ["image", "audio", "video"] def fetch_media_type(%{"mediaType" => mediaType}) do diff --git a/lib/pleroma/web/telemetry.ex b/lib/pleroma/web/telemetry.ex index 1251a23c3..a993d95bc 100644 --- a/lib/pleroma/web/telemetry.ex +++ b/lib/pleroma/web/telemetry.ex @@ -1,5 +1,6 @@ defmodule Pleroma.Web.Telemetry do use Supervisor + import Ecto.Query import Telemetry.Metrics alias Pleroma.Stats alias Pleroma.Config @@ -15,7 +16,7 @@ def init(_arg) do children = [ - {:telemetry_poller, measurements: periodic_measurements(), period: 10_000} + {:telemetry_poller, measurements: periodic_measurements(), period: 60_000} ] ++ prometheus_children() @@ -98,16 +99,25 @@ defp collect_apdelivery_error(:stop, %{result: {_, reason}}) do process_fail_reason(reason) end - defp collect_apdelivery_error(_, _) do + defp collect_apdelivery_error(state, res) do + Logger.info("Unknown AP delivery result: #{inspect(state)}, #{inspect(res)}") "cause_unknown" end defp process_fail_reason(error) do case error do - error when is_binary(error) -> error - error when is_atom(error) -> "#{error}" - %{status: code} when is_number(code) -> "http_#{code}" - _ -> "error_unknown" + error when is_binary(error) -> + error + + error when is_atom(error) -> + "#{error}" + + %{status: code} when is_number(code) -> + "http_#{code}" + + _ -> + Logger.notice("Unusual AP delivery error mapped to 'unknown': #{inspect(error)}") + "error_unknown" end end @@ -121,7 +131,7 @@ defp distribution_metrics do unit: {:native, :second}, tags: [:route], reporter_options: [ - buckets: [0.1, 0.2, 0.5, 1, 2.5, 5, 10, 25, 50, 100, 250, 500, 1000] + buckets: [0.0005, 0.001, 0.005, 0.01, 0.025, 0.05, 0.10, 0.25, 0.5, 0.75, 1, 2, 5, 15] ] ), @@ -223,8 +233,7 @@ defp summary_fallback_metrics(byte_unit \\ :byte) do # and we can use sum + counter to get the average between polls from their change # But for repo query times we need to use a full distribution - simple_buckets = [0, 1, 2, 4, 8, 16] - simple_buckets_quick = for t <- simple_buckets, do: t / 100.0 + simple_buckets = [1, 2, 4, 8, 16, 32] # Already included in distribution metrics anyway: # phoenix.router_dispatch.stop.duration @@ -244,7 +253,7 @@ defp summary_fallback_metrics(byte_unit \\ :byte) do measurement: :decode_time, unit: {:native, :millisecond}, reporter_options: [ - buckets: simple_buckets_quick + buckets: [0.001, 0.0025, 0.005, 0.01, 0.02, 0.05, 0.1, 0.5] ] ), distribution("pleroma.repo.query.query_time.fdist", @@ -252,7 +261,7 @@ defp summary_fallback_metrics(byte_unit \\ :byte) do measurement: :query_time, unit: {:native, :millisecond}, reporter_options: [ - buckets: simple_buckets + buckets: [0.1, 0.2, 0.5, 1, 1.5, 3, 5, 10, 25, 50] ] ), distribution("pleroma.repo.query.idle_time.fdist", @@ -295,7 +304,9 @@ defp common_metrics(byte_unit \\ :byte) do last_value("pleroma.domains.total"), last_value("pleroma.local_statuses.total"), last_value("pleroma.remote_users.total"), - counter("akkoma.ap.delivery.fail.final", tags: [:target, :reason]) + counter("akkoma.ap.delivery.fail.final", tags: [:target, :reason]), + last_value("akkoma.job.queue.scheduled", tags: [:queue_name]), + last_value("akkoma.job.queue.available", tags: [:queue_name]) ] end @@ -307,7 +318,8 @@ def live_dashboard_metrics, do: common_metrics(:megabyte) ++ summary_metrics(:me defp periodic_measurements do [ {__MODULE__, :io_stats, []}, - {__MODULE__, :instance_stats, []} + {__MODULE__, :instance_stats, []}, + {__MODULE__, :oban_pending, []} ] end @@ -325,4 +337,26 @@ def instance_stats do :telemetry.execute([:pleroma, :local_statuses], %{total: stats.status_count}, %{}) :telemetry.execute([:pleroma, :remote_users], %{total: stats.remote_user_count}, %{}) end + + def oban_pending() do + query = + from(j in Oban.Job, + select: %{queue: j.queue, state: j.state, count: count()}, + where: j.state in ["scheduled", "available"], + group_by: [j.queue, j.state] + ) + + conf = Oban.Config.new(Config.get!(Oban)) + qres = Oban.Repo.all(conf, query) + acc = Enum.into(conf.queues, %{}, fn {x, _} -> {x, %{available: 0, scheduled: 0}} end) + + acc = + Enum.reduce(qres, acc, fn %{queue: q, state: state, count: count}, acc -> + put_in(acc, [String.to_existing_atom(q), String.to_existing_atom(state)], count) + end) + + for {queue, info} <- acc do + :telemetry.execute([:akkoma, :job, :queue], info, %{queue_name: queue}) + end + end end diff --git a/lib/pleroma/web/templates/email/digest.html.eex b/lib/pleroma/web/templates/email/digest.html.eex index 1efc76e1a..7dcc9d01d 100644 --- a/lib/pleroma/web/templates/email/digest.html.eex +++ b/lib/pleroma/web/templates/email/digest.html.eex @@ -160,7 +160,7 @@

<%= Gettext.dpgettext("static_pages", "digest email header line", "Hey %{nickname}, here is what you've missed!", nickname: @user.nickname) %>

+ style="font-size: 30px; color: <%= @styling.header_color %>;"><%= dpgettext("static_pages", "digest email header line", "Hey %{nickname}, here is what you've missed!", nickname: @user.nickname) %>

@@ -231,8 +231,8 @@ <%= for %{data: mention, object: object, from: from} <- @mentions do %> - <%# mention START %> - <%# user card START %> + <%!-- mention START --%> + <%!-- user card START --%>
@@ -291,7 +291,7 @@
- <%# user card END %> + <%!-- user card END --%>
- <%# mention END %> + <%!-- mention END --%> <% end %> <%= if @followers != [] do %> - <%# new followers header START %> + <%!-- new followers header START --%>
@@ -382,7 +382,7 @@

<%= Gettext.dpngettext("static_pages", "new followers count header", "%{count} New Follower", "%{count} New Followers", length(@followers), count: length(@followers)) %><%= dpngettext("static_pages", "new followers count header", "%{count} New Follower", "%{count} New Followers", length(@followers), count: length(@followers)) %>

@@ -397,10 +397,10 @@
- <%# new followers header END %> + <%!-- new followers header END --%> <%= for %{data: follow, from: from} <- @followers do %> - <%# user card START %> + <%!-- user card START --%>
@@ -459,13 +459,13 @@
- <%# user card END %> + <%!-- user card END --%> <% end %> <% end %> - <%# divider start %> + <%!-- divider start --%>
@@ -514,7 +514,7 @@
- <%# divider end %> + <%!-- divider end --%>
@@ -535,16 +535,16 @@ style="color:<%= @styling.text_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;">

- <%= raw Gettext.dpgettext("static_pages", "digest email sending reason", "You have received this email because you have signed up to receive digest emails from %{instance} Pleroma instance.", instance: safe_to_string(html_escape(@instance))) %>

+ <%= raw dpgettext("static_pages", "digest email sending reason", "You have received this email because you have signed up to receive digest emails from %{instance} Pleroma instance.", instance: safe_to_string(html_escape(@instance))) %>

 

- <%= raw Gettext.dpgettext("static_pages", "digest email receiver address", "The email address you are subscribed as is %{email}. ", color: safe_to_string(html_escape(@styling.link_color)), email: safe_to_string(html_escape(@user.email))) %>

+ <%= raw dpgettext("static_pages", "digest email receiver address", "The email address you are subscribed as is %{email}. ", color: safe_to_string(html_escape(@styling.link_color)), email: safe_to_string(html_escape(@user.email))) %>

- <%= raw Gettext.dpgettext("static_pages", "digest email unsubscribe action", "To unsubscribe, please go %{here}.", here: safe_to_string link(Gettext.dpgettext("static_pages", "digest email unsubscribe action link text", "here"), style: "color: #{@styling.link_color};text-decoration: none;", to: @unsubscribe_link)) %>

+ <%= raw dpgettext("static_pages", "digest email unsubscribe action", "To unsubscribe, please go %{here}.", here: safe_to_string link(dpgettext("static_pages", "digest email unsubscribe action link text", "here"), style: "color: #{@styling.link_color};text-decoration: none;", to: @unsubscribe_link)) %>

diff --git a/lib/pleroma/web/templates/email/new_users_digest.html.eex b/lib/pleroma/web/templates/email/new_users_digest.html.eex index 40d9b8381..74c47c652 100644 --- a/lib/pleroma/web/templates/email/new_users_digest.html.eex +++ b/lib/pleroma/web/templates/email/new_users_digest.html.eex @@ -1,5 +1,5 @@ <%= for {user, total_statuses, latest_status} <- @users_and_statuses do %> - <%# user card START %> + <%!-- user card START --%>
@@ -60,7 +60,7 @@
- <%# user card END %> + <%!-- user card END --%> <%= if latest_status do %>
@@ -104,7 +104,7 @@
<% end %> - <%# divider start %> + <%!-- divider start --%>
@@ -153,6 +153,6 @@
- <%# divider end %> - <%# user card END %> + <%!-- divider end --%> + <%!-- user card END --%> <% end %> diff --git a/lib/pleroma/web/templates/embed/show.html.eex b/lib/pleroma/web/templates/embed/show.html.eex index 092b52b70..ff25750d2 100644 --- a/lib/pleroma/web/templates/embed/show.html.eex +++ b/lib/pleroma/web/templates/embed/show.html.eex @@ -24,7 +24,7 @@
<%= if sensitive?(@activity) do %>
- <%= Gettext.gettext("sensitive media") %> + <%= gettext("sensitive media") %>
<%= render("_attachment.html", %{name: name, url: url["href"], mediaType: fetch_media_type(url)}) %> @@ -39,9 +39,9 @@
-
<%= Gettext.gettext("replies") %>
<%= @counts.replies %>
-
<%= Gettext.gettext("announces") %>
<%= @counts.announces %>
-
<%= Gettext.gettext("likes") %>
<%= @counts.likes %>
+
<%= gettext("replies") %>
<%= @counts.replies %>
+
<%= gettext("announces") %>
<%= @counts.announces %>
+
<%= gettext("likes") %>
<%= @counts.likes %>

diff --git a/lib/pleroma/web/templates/feed/feed/tag.atom.eex b/lib/pleroma/web/templates/feed/feed/tag.atom.eex index 359f2cc11..1b78d7175 100644 --- a/lib/pleroma/web/templates/feed/feed/tag.atom.eex +++ b/lib/pleroma/web/templates/feed/feed/tag.atom.eex @@ -1,6 +1,6 @@ -<%= "#{url(~p"/tags/#{@tag}")}.rss" %> #<%= @tag %> - <%= Gettext.dpgettext("static_pages", "tag feed description", "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse.", tag: @tag) %> + <%= dpgettext("static_pages", "tag feed description", "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse.", tag: @tag) %> <%= feed_logo() %> <%= most_recent_update(@activities) %> " type="application/atom+xml"/> diff --git a/lib/pleroma/web/templates/feed/feed/tag.rss.eex b/lib/pleroma/web/templates/feed/feed/tag.rss.eex index 494e966ec..78cdcd030 100644 --- a/lib/pleroma/web/templates/feed/feed/tag.rss.eex +++ b/lib/pleroma/web/templates/feed/feed/tag.rss.eex @@ -4,7 +4,7 @@ #<%= @tag %> - <%= Gettext.dpgettext("static_pages", "tag feed description", "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse.", tag: @tag) %> + <%= dpgettext("static_pages", "tag feed description", "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse.", tag: @tag) %> <%= "#{url(~p"/tags/#{@tag}")}.rss" %> <%= feed_logo() %> 2b90d9 diff --git a/lib/pleroma/web/templates/layout/app.html.eex b/lib/pleroma/web/templates/layout/app.html.eex index 31e6ec52b..7ae14b3d0 100644 --- a/lib/pleroma/web/templates/layout/app.html.eex +++ b/lib/pleroma/web/templates/layout/app.html.eex @@ -1,5 +1,5 @@ - + diff --git a/lib/pleroma/web/templates/layout/email.html.eex b/lib/pleroma/web/templates/layout/email.html.eex index 087aa4fc0..e06f94d7e 100644 --- a/lib/pleroma/web/templates/layout/email.html.eex +++ b/lib/pleroma/web/templates/layout/email.html.eex @@ -1,5 +1,5 @@ - + <%= @email.subject %> diff --git a/lib/pleroma/web/templates/layout/email_styled.html.eex b/lib/pleroma/web/templates/layout/email_styled.html.eex index 82cabd889..3eeb52a39 100644 --- a/lib/pleroma/web/templates/layout/email_styled.html.eex +++ b/lib/pleroma/web/templates/layout/email_styled.html.eex @@ -111,7 +111,7 @@ - <%# header %> + <%!-- header --%>

@@ -145,7 +145,7 @@
- <%# title %> + <%!-- title --%> <%= if @title do %>
<%= Gettext.dpgettext("static_pages", "mailer unsubscribe failed message", "UNSUBSCRIBE FAILURE") %> +

<%= dpgettext("static_pages", "mailer unsubscribe failed message", "UNSUBSCRIBE FAILURE") %>

diff --git a/lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex b/lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex index cbce495d4..c5b07ae24 100644 --- a/lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex +++ b/lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex @@ -1 +1 @@ -

<%= Gettext.dpgettext("static_pages", "mailer unsubscribe successful message", "UNSUBSCRIBE SUCCESSFUL") %>

+

<%= dpgettext("static_pages", "mailer unsubscribe successful message", "UNSUBSCRIBE SUCCESSFUL") %>

diff --git a/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex b/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex index b9b08c45d..23020339d 100644 --- a/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex +++ b/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex @@ -6,12 +6,12 @@ <% end %>
- <%= Gettext.dpgettext("static_pages", "mfa recover page title", "Two-factor recovery") %> + <%= dpgettext("static_pages", "mfa recover page title", "Two-factor recovery") %>
<%= form_for @conn, ~p"/oauth/mfa/verify", [as: "mfa"], fn f -> %>
- <%= label f, :code, Gettext.dpgettext("static_pages", "mfa recover recovery code prompt", "Recovery code") %> + <%= label f, :code, dpgettext("static_pages", "mfa recover recovery code prompt", "Recovery code") %> <%= text_input f, :code, [autocomplete: false, autocorrect: "off", autocapitalize: "off", autofocus: true, spellcheck: false] %> <%= hidden_input f, :mfa_token, value: @mfa_token %> <%= hidden_input f, :state, value: @state %> @@ -19,10 +19,10 @@ <%= hidden_input f, :challenge_type, value: "recovery" %>
- <%= submit Gettext.dpgettext("static_pages", "mfa recover verify recovery code button", "Verify") %> + <%= submit dpgettext("static_pages", "mfa recover verify recovery code button", "Verify") %> <% end %> "> - <%= Gettext.dpgettext("static_pages", "mfa recover use 2fa code link", "Enter a two-factor code") %> + <%= dpgettext("static_pages", "mfa recover use 2fa code link", "Enter a two-factor code") %>
diff --git a/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex index 59827780b..45c0182b1 100644 --- a/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex +++ b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex @@ -6,12 +6,12 @@ <% end %>
- <%= Gettext.dpgettext("static_pages", "mfa auth page title", "Two-factor authentication") %> + <%= dpgettext("static_pages", "mfa auth page title", "Two-factor authentication") %>
<%= form_for @conn, ~p"/oauth/mfa/verify", [as: "mfa"], fn f -> %>
- <%= label f, :code, Gettext.dpgettext("static_pages", "mfa auth code prompt", "Authentication code") %> + <%= label f, :code, dpgettext("static_pages", "mfa auth code prompt", "Authentication code") %> <%= text_input f, :code, [autocomplete: "one-time-code", autocorrect: "off", autocapitalize: "off", autofocus: true, pattern: "[0-9]*", spellcheck: false] %> <%= hidden_input f, :mfa_token, value: @mfa_token %> <%= hidden_input f, :state, value: @state %> @@ -19,10 +19,10 @@ <%= hidden_input f, :challenge_type, value: "totp" %>
- <%= submit Gettext.dpgettext("static_pages", "mfa auth verify code button", "Verify") %> + <%= submit dpgettext("static_pages", "mfa auth verify code button", "Verify") %> <% end %> "> - <%= Gettext.dpgettext("static_pages", "mfa auth page use recovery code link", "Enter a two-factor recovery code") %> + <%= dpgettext("static_pages", "mfa auth page use recovery code link", "Enter a two-factor recovery code") %>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex index 73115e92a..7bac2eefa 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex @@ -1,8 +1,8 @@
- <%= label @form, :scope, Gettext.dpgettext("static_pages", "oauth scopes message", "The following permissions will be granted") %> + <%= label @form, :scope, dpgettext("static_pages", "oauth scopes message", "The following permissions will be granted") %>
<%= for scope <- @available_scopes do %> - <%# Note: using hidden input with `unchecked_value` in order to distinguish user's empty selection from `scope` param being omitted %> + <%!-- Note: using hidden input with `unchecked_value` in order to distinguish user's empty selection from `scope` param being omitted --%> <%= if scope in @scopes do %>
<%= checkbox @form, :"scope_#{scope}", value: scope in @scopes && scope, checked_value: scope, unchecked_value: "", name: "authorization[scope][]" %> diff --git a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex index 97f2b770f..ae60ab60f 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex @@ -1,4 +1,4 @@ -

<%= Gettext.dpgettext("static_pages", "oauth external provider page title", "Sign in with external provider") %>

+

<%= dpgettext("static_pages", "oauth external provider page title", "Sign in with external provider") %>

<%= form_for @conn, ~p"/oauth/prepare_request", [as: "authorization", method: "get"], fn f -> %>
@@ -10,6 +10,6 @@ <%= hidden_input f, :state, value: @state %> <%= for strategy <- Pleroma.Config.oauth_consumer_strategies() do %> - <%= submit Gettext.dpgettext("static_pages", "oauth external provider sign in button", "Sign in with %{strategy}", strategy: String.capitalize(strategy)), name: "provider", value: strategy %> + <%= submit dpgettext("static_pages", "oauth external provider sign in button", "Sign in with %{strategy}", strategy: String.capitalize(strategy)), name: "provider", value: strategy %> <% end %> <% end %> diff --git a/lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex index 17e54fb42..10f345ac3 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex @@ -1,8 +1,8 @@
- <%= Gettext.dpgettext("static_pages", "oauth authorized page title", "Successfully authorized") %> + <%= dpgettext("static_pages", "oauth authorized page title", "Successfully authorized") %>
- <%= raw Gettext.dpgettext("static_pages", "oauth token code message", "Token code is
%{token}", token: safe_to_string(html_escape(@auth.token))) %> + <%= raw dpgettext("static_pages", "oauth token code message", "Token code is
%{token}", token: safe_to_string(html_escape(@auth.token))) %>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex index 11671fa1c..ca0526150 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex @@ -1,8 +1,8 @@
- <%= Gettext.dpgettext("static_pages", "oauth authorization exists page title", "Authorization exists") %> + <%= dpgettext("static_pages", "oauth authorization exists page title", "Authorization exists") %>
- <%= raw Gettext.dpgettext("static_pages", "oauth token code message", "Token code is
%{token}", token: safe_to_string(html_escape(@token.token))) %> + <%= raw dpgettext("static_pages", "oauth token code message", "Token code is
%{token}", token: safe_to_string(html_escape(@token.token))) %>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex index 601b16b98..9ea4d7eef 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex @@ -5,34 +5,34 @@ <% end %> -

<%= Gettext.dpgettext("static_pages", "oauth register page title", "Registration Details") %>

+

<%= dpgettext("static_pages", "oauth register page title", "Registration Details") %>

-

<%= Gettext.dpgettext("static_pages", "oauth register page fill form prompt", "If you'd like to register a new account, please provide the details below.") %>

+

<%= dpgettext("static_pages", "oauth register page fill form prompt", "If you'd like to register a new account, please provide the details below.") %>

<%= form_for @conn, ~p"/oauth/register", [as: "authorization"], fn f -> %>
- <%= label f, :nickname, Gettext.dpgettext("static_pages", "oauth register page nickname prompt", "Nickname") %> + <%= label f, :nickname, dpgettext("static_pages", "oauth register page nickname prompt", "Nickname") %> <%= text_input f, :nickname, value: @nickname, autocomplete: "username" %>
- <%= label f, :email, Gettext.dpgettext("static_pages", "oauth register page email prompt", "Email") %> + <%= label f, :email, dpgettext("static_pages", "oauth register page email prompt", "Email") %> <%= text_input f, :email, value: @email, autocomplete: "email" %>
-<%= submit Gettext.dpgettext("static_pages", "oauth register page register button", "Proceed as new user"), name: "op", value: "register" %> +<%= submit dpgettext("static_pages", "oauth register page register button", "Proceed as new user"), name: "op", value: "register" %> -

<%= Gettext.dpgettext("static_pages", "oauth register page login prompt", "Alternatively, sign in to connect to existing account.") %>

+

<%= dpgettext("static_pages", "oauth register page login prompt", "Alternatively, sign in to connect to existing account.") %>

- <%= label f, :name, Gettext.dpgettext("static_pages", "oauth register page login username prompt", "Name or email") %> + <%= label f, :name, dpgettext("static_pages", "oauth register page login username prompt", "Name or email") %> <%= text_input f, :name, autocomplete: "username" %>
- <%= label f, :password, Gettext.dpgettext("static_pages", "oauth register page login password prompt", "Password") %> + <%= label f, :password, dpgettext("static_pages", "oauth register page login password prompt", "Password") %> <%= password_input f, :password, autocomplete: "password" %>
-<%= submit Gettext.dpgettext("static_pages", "oauth register page login button", "Proceed as existing user"), name: "op", value: "connect" %> +<%= submit dpgettext("static_pages", "oauth register page login button", "Proceed as existing user"), name: "op", value: "connect" %> <%= hidden_input f, :client_id, value: @client_id %> <%= hidden_input f, :redirect_uri, value: @redirect_uri %> diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex index 420a17562..b8fb9ecf3 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex @@ -23,7 +23,7 @@
<%= if @app do %>
-

<%= raw Gettext.dpgettext("static_pages", "oauth authorize message", "Application %{client_name} is requesting access to your account.", client_name: safe_to_string(html_escape(@app.client_name))) %>

+

<%= raw dpgettext("static_pages", "oauth authorize message", "Application %{client_name} is requesting access to your account.", client_name: safe_to_string(html_escape(@app.client_name))) %>

<% end %> @@ -32,16 +32,16 @@ <%= if @user do %>
- <%= Gettext.dpgettext("static_pages", "oauth authorize cancel button", "Cancel") %> + <%= dpgettext("static_pages", "oauth authorize cancel button", "Cancel") %> - <%= submit Gettext.dpgettext("static_pages", "oauth authorize approve button", "Approve"), class: "button--approve" %> + <%= submit dpgettext("static_pages", "oauth authorize approve button", "Approve"), class: "button--approve" %>
<% else %> <%= if @params["registration"] in ["true", true] do %> -

<%= Gettext.dpgettext("static_pages", "oauth register page title", "This is your first visit! Please enter your Akkoma handle.") %>

-

<%= Gettext.dpgettext("static_pages", "oauth register nickname unchangeable warning", "Choose carefully! You won't be able to change this later. You will be able to change your display name, though.") %>

+

<%= dpgettext("static_pages", "oauth register page title", "This is your first visit! Please enter your Akkoma handle.") %>

+

<%= dpgettext("static_pages", "oauth register nickname unchangeable warning", "Choose carefully! You won't be able to change this later. You will be able to change your display name, though.") %>

- <%= label f, :nickname, Gettext.dpgettext("static_pages", "oauth register nickname prompt", "Pleroma Handle") %> + <%= label f, :nickname, dpgettext("static_pages", "oauth register nickname prompt", "Pleroma Handle") %> <%= text_input f, :nickname, placeholder: "lain", autocomplete: "username" %>
<%= hidden_input f, :name, value: @params["name"] %> @@ -49,14 +49,14 @@
<% else %>
- <%= label f, :name, Gettext.dpgettext("static_pages", "oauth login username prompt", "Username") %> + <%= label f, :name, dpgettext("static_pages", "oauth login username prompt", "Username") %> <%= text_input f, :name %>
- <%= label f, :password, Gettext.dpgettext("static_pages", "oauth login password prompt", "Password") %> + <%= label f, :password, dpgettext("static_pages", "oauth login password prompt", "Password") %> <%= password_input f, :password %>
- <%= submit Gettext.dpgettext("static_pages", "oauth login button", "Log In") %> + <%= submit dpgettext("static_pages", "oauth login button", "Log In") %> <% end %> <% end %>
diff --git a/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex b/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex index 0bc44738b..14e5641a4 100644 --- a/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex +++ b/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex @@ -39,7 +39,7 @@
"> - +
diff --git a/lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex b/lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex index 5ac0aa4e0..8c74bec13 100644 --- a/lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex +++ b/lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex @@ -1 +1 @@ -

<%= Gettext.dpgettext("static_pages", "password reset invalid token message", "Invalid Token") %>

+

<%= dpgettext("static_pages", "password reset invalid token message", "Invalid Token") %>

diff --git a/lib/pleroma/web/templates/twitter_api/password/reset.html.eex b/lib/pleroma/web/templates/twitter_api/password/reset.html.eex index 390a8371f..1b0675c05 100644 --- a/lib/pleroma/web/templates/twitter_api/password/reset.html.eex +++ b/lib/pleroma/web/templates/twitter_api/password/reset.html.eex @@ -1,13 +1,13 @@

Password Reset for <%= @user.nickname %>

<%= form_for @conn, ~p"/api/v1/pleroma/password_reset", [as: "data"], fn f -> %>
- <%= label f, :password, Gettext.dpgettext("static_pages", "password reset form password prompt", "Password") %> + <%= label f, :password, dpgettext("static_pages", "password reset form password prompt", "Password") %> <%= password_input f, :password %>
- <%= label f, :password_confirmation, Gettext.dpgettext("static_pages", "password reset form confirm password prompt", "Confirmation") %> + <%= label f, :password_confirmation, dpgettext("static_pages", "password reset form confirm password prompt", "Confirmation") %> <%= password_input f, :password_confirmation %>
<%= hidden_input f, :token, value: @token.token %> - <%= submit Gettext.dpgettext("static_pages", "password reset button", "Reset") %> + <%= submit dpgettext("static_pages", "password reset button", "Reset") %> <% end %> diff --git a/lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex b/lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex index 774e3462a..d3ce6015f 100644 --- a/lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex +++ b/lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex @@ -1,6 +1,6 @@ -

<%= Gettext.dpgettext("static_pages", "password reset failed message", "Password reset failed") %>

+

<%= dpgettext("static_pages", "password reset failed message", "Password reset failed") %>

- <%= Gettext.dpgettext("static_pages", "password reset failed homepage link", "Homepage") %> + <%= dpgettext("static_pages", "password reset failed homepage link", "Homepage") %>

diff --git a/lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex b/lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex index 40f6bb3fc..894600f65 100644 --- a/lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex +++ b/lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex @@ -1,2 +1,2 @@ -

<%= Gettext.dpgettext("static_pages", "password reset successful message", "Password changed!") %>

-

<%= Gettext.dpgettext("static_pages", "password reset successful homepage link", "Homepage") %>

+

<%= dpgettext("static_pages", "password reset successful message", "Password changed!") %>

+

<%= dpgettext("static_pages", "password reset successful homepage link", "Homepage") %>

diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex index 894b5c6ee..1f7933e0a 100644 --- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex +++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex @@ -1,11 +1,11 @@ <%= if @error == :error do %> -

<%= Gettext.dpgettext("static_pages", "remote follow error", "Error fetching user") %>

+

<%= dpgettext("static_pages", "remote follow error", "Error fetching user") %>

<% else %> -

<%= Gettext.dpgettext("static_pages", "remote follow header", "Remote follow") %>

+

<%= dpgettext("static_pages", "remote follow header", "Remote follow") %>

<%= @followee.nickname %>

<%= form_for @conn, ~p"/ostatus_subscribe", [as: "user"], fn f -> %> <%= hidden_input f, :id, value: @followee.id %> - <%= submit Gettext.dpgettext("static_pages", "remote follow authorization button", "Authorize") %> + <%= submit dpgettext("static_pages", "remote follow authorization button", "Authorize") %> <% end %> <% end %> diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex index b0084aac4..1712581a7 100644 --- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex +++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex @@ -1,14 +1,14 @@ <%= if @error do %>

<%= @error %>

<% end %> -

<%= Gettext.dpgettext("static_pages", "remote follow header, need login", "Log in to follow") %>

+

<%= dpgettext("static_pages", "remote follow header, need login", "Log in to follow") %>

<%= @followee.nickname %>

<%= form_for @conn, ~p"/ostatus_subscribe", [as: "authorization"], fn f -> %> -<%= text_input f, :name, placeholder: Gettext.dpgettext("static_pages", "placeholder text for username entry", "Username"), required: true, autocomplete: "username" %> +<%= text_input f, :name, placeholder: dpgettext("static_pages", "placeholder text for username entry", "Username"), required: true, autocomplete: "username" %>
-<%= password_input f, :password, placeholder: Gettext.dpgettext("static_pages", "placeholder text for password entry", "Password"), required: true, autocomplete: "password" %> +<%= password_input f, :password, placeholder: dpgettext("static_pages", "placeholder text for password entry", "Password"), required: true, autocomplete: "password" %>
<%= hidden_input f, :id, value: @followee.id %> -<%= submit Gettext.dpgettext("static_pages", "remote follow authorization button for login", "Authorize") %> +<%= submit dpgettext("static_pages", "remote follow authorization button for login", "Authorize") %> <% end %> diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex index f34eba165..dc4e076b7 100644 --- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex +++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex @@ -1,13 +1,13 @@ <%= if @error do %>

<%= @error %>

<% end %> -

<%= Gettext.dpgettext("static_pages", "remote follow mfa header", "Two-factor authentication") %>

+

<%= dpgettext("static_pages", "remote follow mfa header", "Two-factor authentication") %>

<%= @followee.nickname %>

<%= form_for @conn, ~p"/ostatus_subscribe", [as: "mfa"], fn f -> %> -<%= text_input f, :code, placeholder: Gettext.dpgettext("static_pages", "placeholder text for auth code entry", "Authentication code"), required: true %> +<%= text_input f, :code, placeholder: dpgettext("static_pages", "placeholder text for auth code entry", "Authentication code"), required: true %>
<%= hidden_input f, :id, value: @followee.id %> <%= hidden_input f, :token, value: @mfa_token %> -<%= submit Gettext.dpgettext("static_pages", "remote follow authorization button for mfa", "Authorize") %> +<%= submit dpgettext("static_pages", "remote follow authorization button for mfa", "Authorize") %> <% end %> diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex index 2fb4cc5d3..29e1ffb81 100644 --- a/lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex +++ b/lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex @@ -1,5 +1,5 @@ <%= if @error do %> -

<%= Gettext.dpgettext("static_pages", "remote follow error", "Error following account") %>

+

<%= dpgettext("static_pages", "remote follow error", "Error following account") %>

<% else %> -

<%= Gettext.dpgettext("static_pages", "remote follow success", "Account followed!") %>

+

<%= dpgettext("static_pages", "remote follow success", "Account followed!") %>

<% end %> diff --git a/lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex b/lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex index 3105772e2..ccb8a9472 100644 --- a/lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex +++ b/lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex @@ -1,10 +1,10 @@ <%= if @error do %> -

<%= Gettext.dpgettext("static_pages", "status interact error", "Error: %{error}", error: @error) %>

+

<%= dpgettext("static_pages", "status interact error", "Error: %{error}", error: @error) %>

<% else %> -

<%= raw Gettext.dpgettext("static_pages", "status interact header", "Interacting with %{nickname}'s %{status_link}", nickname: safe_to_string(html_escape(@nickname)), status_link: safe_to_string(link(Gettext.dpgettext("static_pages", "status interact header - status link text", "status"), to: @status_link))) %>

+

<%= raw dpgettext("static_pages", "status interact header", "Interacting with %{nickname}'s %{status_link}", nickname: safe_to_string(html_escape(@nickname)), status_link: safe_to_string(link(dpgettext("static_pages", "status interact header - status link text", "status"), to: @status_link))) %>

<%= form_for @conn, ~p"/main/ostatus", [as: "status"], fn f -> %> <%= hidden_input f, :status_id, value: @status_id %> - <%= text_input f, :profile, placeholder: Gettext.dpgettext("static_pages", "placeholder text for account id", "Your account ID, e.g. lain@quitter.se") %> - <%= submit Gettext.dpgettext("static_pages", "status interact authorization button", "Interact") %> + <%= text_input f, :profile, placeholder: dpgettext("static_pages", "placeholder text for account id", "Your account ID, e.g. lain@quitter.se") %> + <%= submit dpgettext("static_pages", "status interact authorization button", "Interact") %> <% end %> <% end %> diff --git a/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex b/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex index 47b73f676..d1ab91c56 100644 --- a/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex +++ b/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex @@ -1,10 +1,10 @@ <%= if @error do %> -

<%= Gettext.dpgettext("static_pages", "remote follow error", "Error: %{error}", error: @error) %>

+

<%= dpgettext("static_pages", "remote follow error", "Error: %{error}", error: @error) %>

<% else %> -

<%= Gettext.dpgettext("static_pages", "remote follow header", "Remotely follow %{nickname}", nickname: @nickname) %>

+

<%= dpgettext("static_pages", "remote follow header", "Remotely follow %{nickname}", nickname: @nickname) %>

<%= form_for @conn, ~p"/main/ostatus", [as: "user"], fn f -> %> <%= hidden_input f, :nickname, value: @nickname %> - <%= text_input f, :profile, placeholder: Gettext.dpgettext("static_pages", "placeholder text for account id", "Your account ID, e.g. lain@quitter.se") %> - <%= submit Gettext.dpgettext("static_pages", "remote follow authorization button for following with a remote account", "Follow") %> + <%= text_input f, :profile, placeholder: dpgettext("static_pages", "placeholder text for account id", "Your account ID, e.g. lain@quitter.se") %> + <%= submit dpgettext("static_pages", "remote follow authorization button for following with a remote account", "Follow") %> <% end %> <% end %> diff --git a/lib/pleroma/web/translation_helpers.ex b/lib/pleroma/web/translation_helpers.ex index 0fe31d189..93af4252a 100644 --- a/lib/pleroma/web/translation_helpers.ex +++ b/lib/pleroma/web/translation_helpers.ex @@ -11,11 +11,17 @@ defmacro render_error( identifier \\ Macro.escape("") ) do quote do - require Pleroma.Web.Gettext + require Gettext.Macros error_map = %{ - error: Pleroma.Web.Gettext.dgettext("errors", unquote(msgid), unquote(bindings)), + error: + Gettext.Macros.dgettext_with_backend( + Pleroma.Web.Gettext, + "errors", + unquote(msgid), + unquote(bindings) + ), identifier: unquote(identifier) } |> Enum.reject(fn {_k, v} -> v == "" end) diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 5f1a3518d..8a6a5fdff 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do use Pleroma.Web, :controller + use Gettext, backend: Pleroma.Web.Gettext require Logger @@ -64,7 +65,7 @@ def show_subscribe_form(conn, %{"nickname" => nick}) do nickname: nick, avatar: nil, error: - Pleroma.Web.Gettext.dpgettext( + dpgettext( "static_pages", "remote follow error message - user not found", "Could not find user" @@ -92,7 +93,7 @@ def show_subscribe_form(conn, %{"status_id" => id}) do status_id: id, avatar: nil, error: - Pleroma.Web.Gettext.dpgettext( + dpgettext( "static_pages", "status interact error message - status not found", "Could not find status" @@ -120,7 +121,7 @@ def remote_subscribe(conn, %{"user" => %{"nickname" => nick, "profile" => profil nickname: nick, avatar: nil, error: - Pleroma.Web.Gettext.dpgettext( + dpgettext( "static_pages", "remote follow error message - unknown error", "Something went wrong." @@ -141,7 +142,7 @@ def remote_subscribe(conn, %{"status" => %{"status_id" => id, "profile" => profi status_id: id, avatar: nil, error: - Pleroma.Web.Gettext.dpgettext( + dpgettext( "static_pages", "status interact error message - unknown error", "Something went wrong." diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 7921653a8..a5e92e794 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -3,7 +3,8 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.TwitterAPI.TwitterAPI do - import Pleroma.Web.Gettext + use Gettext, + backend: Pleroma.Web.Gettext alias Pleroma.Emails.Mailer alias Pleroma.Emails.UserEmail @@ -24,7 +25,7 @@ def register_user(params, opts \\ []) do |> Map.put(:registration_reason, params[:reason]) |> Map.put( :language, - Pleroma.Web.Gettext.normalize_locale(params[:language]) || fallback_language + Pleroma.Web.GettextCompanion.normalize_locale(params[:language]) || fallback_language ) if Pleroma.Config.get([:instance, :registrations_open]) do diff --git a/lib/pleroma/web/twitter_api/views/password_view.ex b/lib/pleroma/web/twitter_api/views/password_view.ex index 40e7fca49..6da489f46 100644 --- a/lib/pleroma/web/twitter_api/views/password_view.ex +++ b/lib/pleroma/web/twitter_api/views/password_view.ex @@ -4,6 +4,5 @@ defmodule Pleroma.Web.TwitterAPI.PasswordView do use Pleroma.Web, :view - import Phoenix.HTML.Form - alias Pleroma.Web.Gettext + use Gettext, backend: Pleroma.Web.Gettext end diff --git a/lib/pleroma/web/twitter_api/views/remote_follow_view.ex b/lib/pleroma/web/twitter_api/views/remote_follow_view.ex index bb24902a4..b981ee476 100644 --- a/lib/pleroma/web/twitter_api/views/remote_follow_view.ex +++ b/lib/pleroma/web/twitter_api/views/remote_follow_view.ex @@ -4,8 +4,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowView do use Pleroma.Web, :view - import Phoenix.HTML.Form - alias Pleroma.Web.Gettext + use Gettext, backend: Pleroma.Web.Gettext def avatar_url(user) do user diff --git a/lib/pleroma/web/twitter_api/views/util_view.ex b/lib/pleroma/web/twitter_api/views/util_view.ex index 6ed74ee80..a0eaad745 100644 --- a/lib/pleroma/web/twitter_api/views/util_view.ex +++ b/lib/pleroma/web/twitter_api/views/util_view.ex @@ -5,11 +5,10 @@ defmodule Pleroma.Web.TwitterAPI.UtilView do use Pleroma.Web, :view import Phoenix.HTML - import Phoenix.HTML.Form - import Phoenix.HTML.Link + import PhoenixHTMLHelpers.Link alias Pleroma.Config alias Pleroma.Web.Endpoint - alias Pleroma.Web.Gettext + use Gettext, backend: Pleroma.Web.Gettext def status_net_config(instance) do """ diff --git a/lib/pleroma/web/views/email_view.ex b/lib/pleroma/web/views/email_view.ex index 2ef049d27..94765e47b 100644 --- a/lib/pleroma/web/views/email_view.ex +++ b/lib/pleroma/web/views/email_view.ex @@ -5,8 +5,9 @@ defmodule Pleroma.Web.EmailView do use Pleroma.Web, :view import Phoenix.HTML - import Phoenix.HTML.Link - alias Pleroma.Web.Gettext + import PhoenixHTMLHelpers.Link + + use Gettext, backend: Pleroma.Web.Gettext def avatar_url(user) do Pleroma.User.avatar_url(user) diff --git a/lib/pleroma/web/views/embed_view.ex b/lib/pleroma/web/views/embed_view.ex index fe1009e2f..70988bf04 100644 --- a/lib/pleroma/web/views/embed_view.ex +++ b/lib/pleroma/web/views/embed_view.ex @@ -10,7 +10,7 @@ defmodule Pleroma.Web.EmbedView do alias Pleroma.Emoji.Formatter alias Pleroma.Object alias Pleroma.User - alias Pleroma.Web.Gettext + use Gettext, backend: Pleroma.Web.Gettext alias Pleroma.Web.MediaProxy alias Pleroma.Web.Metadata.Utils diff --git a/lib/pleroma/web/views/mailer/subscription_view.ex b/lib/pleroma/web/views/mailer/subscription_view.ex index 01e96c61c..8fc78127c 100644 --- a/lib/pleroma/web/views/mailer/subscription_view.ex +++ b/lib/pleroma/web/views/mailer/subscription_view.ex @@ -4,5 +4,5 @@ defmodule Pleroma.Web.Mailer.SubscriptionView do use Pleroma.Web, :view - alias Pleroma.Web.Gettext + use Gettext, backend: Pleroma.Web.Gettext end diff --git a/lib/pleroma/workers/publisher_worker.ex b/lib/pleroma/workers/publisher_worker.ex index 9fef9b77b..860fc6326 100644 --- a/lib/pleroma/workers/publisher_worker.ex +++ b/lib/pleroma/workers/publisher_worker.ex @@ -34,6 +34,17 @@ def perform(%Job{ end def perform(%Job{args: %{"op" => "publish_one", "module" => module_name, "params" => params}}) do - Federator.perform(:publish_one, String.to_existing_atom(module_name), params) + res = Federator.perform(:publish_one, String.to_existing_atom(module_name), params) + + case res do + # instance / actor was explicitly deleted; there’s nothing to deliver to anymore + # since we don’t know whether the whole instance is gone or just this actor, + # do NOT immediately mark the instance as unreachable + {:error, {:http_error, 410, _}} -> + :ok + + res -> + res + end end end diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex index 9980a3546..b5a74dedc 100644 --- a/lib/pleroma/workers/receiver_worker.ex +++ b/lib/pleroma/workers/receiver_worker.ex @@ -80,4 +80,8 @@ def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do # reraise to let oban handle transaction conflicts without deductig an attempt reraise err, __STACKTRACE__ end + + def backoff(%Job{attempt: attempt}) when is_integer(attempt) do + Pleroma.Workers.WorkerHelper.sidekiq_backoff(attempt, 5) + end end diff --git a/mix.exs b/mix.exs index f08d755dc..214da9949 100644 --- a/mix.exs +++ b/mix.exs @@ -4,11 +4,10 @@ defmodule Pleroma.Mixfile do def project do [ app: :pleroma, - version: version("3.15.2"), + version: version("3.16.0"), elixir: "~> 1.14.1 or ~> 1.15", elixirc_paths: elixirc_paths(Mix.env()), compilers: Mix.compilers(), - elixirc_options: [warnings_as_errors: warnings_as_errors()], xref: [exclude: [:eldap]], start_permanent: Mix.env() == :prod, aliases: aliases(), @@ -42,8 +41,12 @@ def project do ] ] ] + |> add_listeners(Mix.env()) end + defp add_listeners(project, :dev), do: Keyword.put(project, :listeners, [Phoenix.CodeReloader]) + defp add_listeners(project, _), do: project + def put_otp_version(%{path: target_path} = release) do File.write!( Path.join([target_path, "OTP_VERSION"]), @@ -91,8 +94,6 @@ defp elixirc_paths(:benchmark), do: ["lib", "benchmarks", "priv/scrubbers"] defp elixirc_paths(:test), do: ["lib", "test/support"] defp elixirc_paths(_), do: ["lib"] - defp warnings_as_errors, do: System.get_env("CI") == "true" - # Specifies OAuth dependencies. defp oauth_deps do oauth_strategy_packages = @@ -116,7 +117,7 @@ defp oauth_deps do # Type `mix help deps` for examples and options. defp deps do [ - {:phoenix, "~> 1.7.0"}, + {:phoenix, "~> 1.8.0"}, {:phoenix_view, "~> 2.0"}, {:phoenix_live_dashboard, "~> 0.8.6"}, {:tzdata, "~> 1.1.1"}, @@ -125,21 +126,22 @@ defp deps do {:phoenix_ecto, "~> 4.6"}, {:inet_cidr, "~> 1.0.0"}, {:ecto_enum, "~> 1.4"}, - {:ecto_sql, "~> 3.12.0"}, - {:postgrex, "~> 0.20.0"}, - {:oban, "~> 2.19.0"}, + {:ecto_sql, "~> 3.13.2"}, + {:postgrex, "~> 0.21.1"}, + {:oban, "~> 2.20.1"}, {:oban_web, "~> 2.11.0"}, - {:gettext, "~> 0.22.3"}, - {:bcrypt_elixir, "~> 3.0.1"}, + {:gettext, "~> 0.26 or ~> 1.0"}, + {:bcrypt_elixir, "~> 3.3.2"}, {:fast_sanitize, "~> 0.2.3"}, {:html_entities, "~> 0.5"}, - {:phoenix_html, "~> 3.3"}, + {:phoenix_html, "~> 4.0"}, + {:phoenix_html_helpers, "~> 1.0"}, {:calendar, "~> 1.0"}, - {:cachex, "~> 3.6"}, + {:cachex, "~> 4.1"}, {:tesla, "~> 1.7"}, {:castore, "~> 1.0"}, {:cowlib, "~> 2.12"}, - {:finch, "~> 0.18.0"}, + {:finch, "~> 0.20.0"}, {:jason, "~> 1.4"}, {:trailing_format_plug, "~> 0.0.7"}, {:mogrify, "~> 0.9"}, @@ -148,26 +150,25 @@ defp deps do {:sweet_xml, "~> 0.7"}, {:earmark, "1.4.46"}, {:bbcode_pleroma, "~> 0.2.0"}, - {:argon2_elixir, "~> 3.1"}, + {:argon2_elixir, "~> 4.0"}, {:cors_plug, "~> 3.0"}, {:web_push_encryption, "~> 0.3.1"}, - {:swoosh, "~> 1.14.2"}, + {:swoosh, "~> 1.19.8"}, # for gmail adapter in swoosh {:mail, ">= 0.0.0"}, {:phoenix_swoosh, "~> 1.2"}, {:gen_smtp, "~> 1.2"}, - {:ex_syslogger, "~> 2.0.0"}, + {:ex_syslogger, "~> 2.1.0"}, {:floki, "~> 0.34"}, {:timex, "~> 3.7"}, - {:ueberauth, "== 0.10.5"}, - {:linkify, "~> 0.5.3"}, + {:ueberauth, "~> 0.10.7"}, + {:linkify, git: "https://akkoma.dev/AkkomaGang/linkify.git", branch: "main"}, {:http_signatures, - git: "https://akkoma.dev/AkkomaGang/http_signatures.git", - ref: "d44c43d66758c6a73eaa4da9cffdbee0c5da44ae"}, + git: "https://akkoma.dev/AkkomaGang/http_signatures.git", branch: "main"}, {:telemetry, "~> 1.2"}, {:telemetry_poller, "~> 1.0"}, - {:telemetry_metrics, "~> 0.6"}, - {:telemetry_metrics_prometheus_core, "~> 1.1.0"}, + {:telemetry_metrics, "~> 1.0"}, + {:telemetry_metrics_prometheus_core, "~> 1.2.1"}, {:poolboy, "~> 1.5"}, {:recon, "~> 2.5"}, {:joken, "~> 2.6"}, @@ -175,44 +176,36 @@ defp deps do {:pot, "~> 1.0"}, {:ex_const, "~> 0.2"}, {:plug_static_index_html, "~> 1.0.0"}, - {:flake_id, - git: "https://akkoma.dev/AkkomaGang/flake_id.git", - ref: "5a68513f7e7353706e788781eff6e56bf00bb41b"}, + {:flake_id, git: "https://akkoma.dev/AkkomaGang/flake_id.git", branch: "main"}, {:concurrent_limiter, - git: "https://akkoma.dev/AkkomaGang/concurrent-limiter.git", - ref: "a9e0b3d64574bdba761f429bb4fba0cf687b3338"}, - {:remote_ip, "~> 1.1.0"}, + git: "https://akkoma.dev/AkkomaGang/concurrent-limiter.git", branch: "main"}, + {:remote_ip, "~> 1.2.0"}, {:captcha, git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", - ref: "6630c42aaaab124e697b4e513190c89d8b64e410"}, + branch: "master"}, {:restarter, path: "./restarter"}, - {:majic, - git: "https://akkoma.dev/AkkomaGang/majic.git", - ref: "80540b36939ec83f48e76c61e5000e0fd67706f0"}, + {:majic, git: "https://akkoma.dev/AkkomaGang/majic.git", branch: "main"}, {:eblurhash, "~> 1.2.2"}, {:open_api_spex, "~> 3.17"}, {:search_parser, - git: "https://github.com/FloatingGhost/pleroma-contrib-search-parser.git", - ref: "08971a81e68686f9ac465cfb6661d51c5e4e1e7f"}, + git: "https://github.com/FloatingGhost/pleroma-contrib-search-parser.git", branch: "main"}, {:nimble_parsec, "~> 1.3", override: true}, {:ecto_psql_extras, "~> 0.8"}, {:elasticsearch, - git: "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", ref: "main"}, - {:mfm_parser, - git: "https://akkoma.dev/AkkomaGang/mfm-parser.git", - ref: "360a30267a847810a63ab48f606ba227b2ca05f0"}, + git: "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", branch: "main"}, + {:mfm_parser, git: "https://akkoma.dev/AkkomaGang/mfm-parser.git", branch: "akkoma"}, ## dev & test {:ex_doc, "~> 0.30", only: :dev, runtime: false}, {:ex_machina, "~> 2.8", only: :test}, {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, {:mock, "~> 0.3.8", only: :test}, - {:excoveralls, "0.16.1", only: :test}, + {:excoveralls, "0.18.5", only: :test}, {:mox, "~> 1.0", only: :test}, {:websockex, "~> 0.4.3", only: :test}, {:dialyxir, "~> 1.3", only: [:dev], runtime: false}, {:elixir_xml_to_map, "~> 3.0", only: :test}, - {:mint, "~> 1.5.1", override: true}, + {:mint, "~> 1.7.1", override: true}, {:nimble_pool, "~> 1.0", override: true}, {:mneme, "~> 0.10.2", only: [:dev, :test]} ] ++ oauth_deps() @@ -236,6 +229,13 @@ defp aliases do copyright: &add_copyright/1, "copyright.bump": &bump_copyright/1 ] + |> then(fn a -> + if System.get_env("CI") == "true" do + [{:compile, "compile --warnings-as-errors"} | a] + else + a + end + end) end # Builds a version string made of: diff --git a/mix.lock b/mix.lock index 9562b9912..158b2326b 100644 --- a/mix.lock +++ b/mix.lock @@ -1,148 +1,150 @@ %{ - "argon2_elixir": {:hex, :argon2_elixir, "3.2.1", "f47740bf9f2a39ffef79ba48eb25dea2ee37bcc7eadf91d49615591d1a6fce1a", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "a813b78217394530b5fcf4c8070feee43df03ffef938d044019169c766315690"}, + "argon2_elixir": {:hex, :argon2_elixir, "4.1.3", "4f28318286f89453364d7fbb53e03d4563fd7ed2438a60237eba5e426e97785f", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "7c295b8d8e0eaf6f43641698f962526cdf87c6feb7d14bd21e599271b510608c"}, "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.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"}, + "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.3.2", "d50091e3c9492d73e17fc1e1619a9b09d6a5ef99160eb4d736926fd475a16ca3", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "471be5151874ae7931911057d1467d908955f93554f7a6cd1b7d804cac8cef53"}, + "benchee": {:hex, :benchee, "1.5.0", "4d812c31d54b0ec0167e91278e7de3f596324a78a096fd3d0bea68bb0c513b10", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.1", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "5b075393aea81b8ae74eadd1c28b1d87e8a63696c649d8293db7c4df3eb67535"}, "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"}, + "cachex": {:hex, :cachex, "4.1.1", "574c5cd28473db313a0a76aac8c945fe44191659538ca6a1e8946ec300b1a19f", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:ex_hash_ring, "~> 6.0", [hex: :ex_hash_ring, 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", "d6b7449ff98d6bb92dda58bd4fc3189cae9f99e7042054d669596f56dc503cd8"}, "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", "6630c42aaaab124e697b4e513190c89d8b64e410", [ref: "6630c42aaaab124e697b4e513190c89d8b64e410"]}, - "castore": {:hex, :castore, "1.0.12", "053f0e32700cbec356280c0e835df425a3be4bc1e0627b714330ad9d0f05497f", [:mix], [], "hexpm", "3dca286b2186055ba0c9449b4e95b97bf1b57b47c1f2644555879e659960c224"}, - "certifi": {:hex, :certifi, "2.14.0", "ed3bef654e69cde5e6c022df8070a579a79e8ba2368a00acf3d75b82d9aceeed", [:rebar3], [], "hexpm", "ea59d87ef89da429b8e905264fdec3419f84f2215bb3d81e07a18aac919026c3"}, + "captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "784605815756bbc1d7e313ff552840afb62e2c41", [branch: "master"]}, + "castore": {:hex, :castore, "1.0.16", "8a4f9a7c8b81cda88231a08fe69e3254f16833053b23fa63274b05cbc61d2a1e", [:mix], [], "hexpm", "33689203a0eaaf02fcd0e86eadfbcf1bd636100455350592e7e2628564022aaf"}, + "certifi": {:hex, :certifi, "2.15.0", "0e6e882fcdaaa0a5a9f2b3db55b1394dba07e8d6d9bcad08318fb604c6839712", [:rebar3], [], "hexpm", "b147ed22ce71d72eafdad94f055165c1c182f61a2ff49df28bcc71d1d5b94a60"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "comeonin": {:hex, :comeonin, "5.5.1", "5113e5f3800799787de08a6e0db307133850e635d34e9fab23c70b6501669510", [:mix], [], "hexpm", "65aac8f19938145377cee73973f192c5645873dcf550a8a6b18187d17c13ccdb"}, - "concurrent_limiter": {:git, "https://akkoma.dev/AkkomaGang/concurrent-limiter.git", "a9e0b3d64574bdba761f429bb4fba0cf687b3338", [ref: "a9e0b3d64574bdba761f429bb4fba0cf687b3338"]}, + "concurrent_limiter": {:git, "https://akkoma.dev/AkkomaGang/concurrent-limiter.git", "a9e0b3d64574bdba761f429bb4fba0cf687b3338", [branch: "main"]}, "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, "cors_plug": {:hex, :cors_plug, "3.0.3", "7c3ac52b39624bc616db2e937c282f3f623f25f8d550068b6710e58d04a0e330", [:mix], [{:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3f2d759e8c272ed3835fab2ef11b46bddab8c1ab9528167bd463b6452edf830d"}, - "cowboy": {:hex, :cowboy, "2.13.0", "09d770dd5f6a22cc60c071f432cd7cb87776164527f205c5a6b0f24ff6b38990", [:make, :rebar3], [{:cowlib, ">= 2.14.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "e724d3a70995025d654c1992c7b11dbfea95205c047d86ff9bf1cda92ddc5614"}, + "cowboy": {:hex, :cowboy, "2.14.2", "4008be1df6ade45e4f2a4e9e2d22b36d0b5aba4e20b0a0d7049e28d124e34847", [:make, :rebar3], [{:cowlib, ">= 2.16.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "569081da046e7b41b5df36aa359be71a0c8874e5b9cff6f747073fc57baf1ab9"}, "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.14.0", "623791c56c1cc9df54a71a9c55147a401549917f00a2e48a6ae12b812c586ced", [:make, :rebar3], [], "hexpm", "0af652d1550c8411c3b58eed7a035a7fb088c0b86aff6bc504b0bc3b7f791aa2"}, - "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [: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", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, + "cowlib": {:hex, :cowlib, "2.16.0", "54592074ebbbb92ee4746c8a8846e5605052f29309d3a873468d76cdf932076f", [:make, :rebar3], [], "hexpm", "7f478d80d66b747344f0ea7708c187645cfcc08b11aa424632f78e25bf05db51"}, + "credo": {:hex, :credo, "1.7.13", "126a0697df6b7b71cd18c81bc92335297839a806b6f62b61d417500d1070ff4e", [: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", "47641e6d2bbff1e241e87695b29f617f1a8f912adea34296fb10ecc3d7e9e84f"}, "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"}, - "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, + "db_connection": {:hex, :db_connection, "2.8.1", "9abdc1e68c34c6163f6fb96a96532272d13ad7ca45262156ae8b7ec6d9dc4bec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a61a3d489b239d76f326e03b98794fb8e45168396c925ef25feb405ed09da8fd"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, - "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, + "dialyxir": {:hex, :dialyxir, "1.4.7", "dda948fcee52962e4b6c5b4b16b2d8fa7d50d8645bbae8b8685c3f9ecb7f5f4d", [:mix], [{:erlex, ">= 0.2.8", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b34527202e6eb8cee198efec110996c25c5898f43a4094df157f8d28f27d9efe"}, "earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.43", "34b2f401fe473080e39ff2b90feb8ddfeef7639f8ee0bbf71bb41911831d77c5", [:mix], [], "hexpm", "970a3cd19503f5e8e527a190662be2cee5d98eed1ff72ed9b3d1a3d466692de8"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, "eblurhash": {:hex, :eblurhash, "1.2.2", "7da4255aaea984b31bb71155f673257353b0e0554d0d30dcf859547e74602582", [:rebar3], [], "hexpm", "8c20ca00904de023a835a9dcb7b7762fed32264c85a80c3cafa85288e405044c"}, - "ecto": {:hex, :ecto, "3.12.5", "4a312960ce612e17337e7cefcf9be45b95a3be6b36b6f94dfb3d8c361d631866", [:mix], [{:decimal, "~> 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", "6eb18e80bef8bb57e17f5a7f068a1719fbda384d40fc37acb8eb8aeca493b6ea"}, + "ecto": {:hex, :ecto, "3.13.4", "27834b45d58075d4a414833d9581e8b7bb18a8d9f264a21e42f653d500dbeeb5", [:mix], [{:decimal, "~> 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", "5ad7d1505685dfa7aaf86b133d54f5ad6c42df0b4553741a1ff48796736e88b2"}, "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.8.7", "49943fe6bce07281fe3adfc2a23d3794e2acc644dfe98411cb5712ffecb6ad1a", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "> 0.16.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "ac0a0bce57ffe36b30fac2a2d0d427b04de016e6af5db6f4b41afa1241f39cda"}, - "ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 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", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"}, - "elasticsearch": {:git, "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", "6cd946f75f6ab9042521a009d1d32d29a90113ca", [ref: "main"]}, + "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.8.8", "aa02529c97f69aed5722899f5dc6360128735a92dd169f23c5d50b1f7fdede08", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "> 0.16.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "04c63d92b141723ad6fed2e60a4b461ca00b3594d16df47bbc48f1f4534f2c49"}, + "ecto_sql": {:hex, :ecto_sql, "3.13.2", "a07d2461d84107b3d037097c822ffdd36ed69d1cf7c0f70e12a3d1decf04e2e1", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 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", "539274ab0ecf1a0078a6a72ef3465629e4d6018a3028095dc90f60a19c371717"}, + "elasticsearch": {:git, "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", "6cd946f75f6ab9042521a009d1d32d29a90113ca", [branch: "main"]}, "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, "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.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, + "erlex": {:hex, :erlex, "0.2.8", "cd8116f20f3c0afe376d1e8d1f0ae2452337729f68be016ea544a72f767d9c12", [:mix], [], "hexpm", "9d66ff9fedf69e49dc3fd12831e12a8a37b76f8651dd21cd45fcf5561a8a7590"}, "erlsom": {:hex, :erlsom, "1.5.2", "3e47c53a199136fb4d20d5479edb7c9229f31624534c062633951c8d14dcd276", [:rebar3], [], "hexpm", "4e765cc677fb30509f7b628ff2914e124cf4dcc0fac1c0a62ee4dcee24215b5d"}, "eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"}, - "ex_aws": {:hex, :ex_aws, "2.5.8", "0393cfbc5e4a9e7017845451a015d836a670397100aa4c86901980e2a2c5f7d4", [: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]}, {:req, "~> 0.3", [hex: :req, repo: "hexpm", optional: true]}, {: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", "8f79777b7932168956c8cc3a6db41f5783aa816eb50de356aed3165a71e5f8c3"}, - "ex_aws_s3": {:hex, :ex_aws_s3, "2.5.6", "d135983bbd8b6df6350dfd83999437725527c1bea151e5055760bfc9b2d17c20", [: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", "9874e12847e469ca2f13a5689be04e546c16f63caf6380870b7f25bf7cb98875"}, + "ex_aws": {:hex, :ex_aws, "2.6.0", "346e87e35e5df0b3c016a96fb30adf6001de102981a71648dfc3ce3ad04765af", [:mix], [{:configparser_ex, "~> 5.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]}, {:req, "~> 0.5.10 or ~> 0.6 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {: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", "30729ee9cbaacda674a4e4260d74206fa89bcd712267c4eaf42a0fc34592c0b3"}, + "ex_aws_s3": {:hex, :ex_aws_s3, "2.5.8", "5ee7407bc8252121ad28fba936b3b293f4ecef93753962351feb95b8a66096fa", [: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", "84e512ca2e0ae6a6c497036dff06d4493ffb422cfe476acc811d7c337c16691c"}, "ex_const": {:hex, :ex_const, "0.3.0", "9d79516679991baf540ef445438eef1455ca91cf1a3c2680d8fb9e5bea2fe4de", [:mix], [], "hexpm", "76546322abb9e40ee4a2f454cf1c8a5b25c3672fa79bed1ea52c31e0d2428ca9"}, - "ex_doc": {:hex, :ex_doc, "0.37.2", "2a3aa7014094f0e4e286a82aa5194a34dd17057160988b8509b15aa6c292720c", [:mix], [{:earmark_parser, "~> 1.4.42", [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", "4dfa56075ce4887e4e8b1dcc121cd5fcb0f02b00391fd367ff5336d98fa49049"}, + "ex_doc": {:hex, :ex_doc, "0.39.1", "e19d356a1ba1e8f8cfc79ce1c3f83884b6abfcb79329d435d4bbb3e97ccc286e", [:mix], [{:earmark_parser, "~> 1.4.44", [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", "8abf0ed3e3ca87c0847dfc4168ceab5bedfe881692f1b7c45f4a11b232806865"}, + "ex_hash_ring": {:hex, :ex_hash_ring, "6.0.4", "bef9d2d796afbbe25ab5b5a7ed746e06b99c76604f558113c273466d52fa6d6b", [:mix], [], "hexpm", "89adabf31f7d3dfaa36802ce598ce918e9b5b33bae8909ac1a4d052e1e567d18"}, "ex_machina": {:hex, :ex_machina, "2.8.0", "a0e847b5712065055ec3255840e2c78ef9366634d62390839d4880483be38abe", [: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", "79fe1a9c64c0c1c1fab6c4fa5d871682cb90de5885320c187d117004627a7729"}, - "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"}, - "expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"}, - "fast_html": {:hex, :fast_html, "2.4.1", "73142526cee294b0ec8cf122483f32c861e4a9d988c60cd04f63ce7fd9e5a620", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "767a63ecc941d3fc0e0e9609ded1a5e798398e5b1bf4d2f47bcb5992a86b32cf"}, + "ex_syslogger": {:hex, :ex_syslogger, "2.1.0", "437dcf5cb4ba05116dd3a848555c683443ef90c4336fca4d336fd78e4614bcd3", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "a7c4ca96aacc8290dc6b338dd0080b9ba4ec3a5b93665a8a614f8c2d8d7dee8e"}, + "excoveralls": {:hex, :excoveralls, "0.18.5", "e229d0a65982613332ec30f07940038fe451a2e5b29bce2a5022165f0c9b157e", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "523fe8a15603f86d64852aab2abe8ddbd78e68579c8525ae765facc5eae01562"}, + "expo": {:hex, :expo, "1.1.1", "4202e1d2ca6e2b3b63e02f69cfe0a404f77702b041d02b58597c00992b601db5", [:mix], [], "hexpm", "5fb308b9cb359ae200b7e23d37c76978673aa1b06e2b3075d814ce12c5811640"}, + "fast_html": {:hex, :fast_html, "2.5.0", "23578c1c1fa03db6a3786d78218b2d944df34b0fc3729e72de912f390913c80f", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "69eb46ed98a5d9cca1ccd4a5ac94ce5dd626fc29513fbaa0a16cd8b2da67ae3e"}, "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.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, - "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": {:git, "https://akkoma.dev/AkkomaGang/flake_id.git", "5a68513f7e7353706e788781eff6e56bf00bb41b", [ref: "5a68513f7e7353706e788781eff6e56bf00bb41b"]}, - "floki": {:hex, :floki, "0.37.0", "b83e0280bbc6372f2a403b2848013650b16640cd2470aea6701f0632223d719e", [:mix], [], "hexpm", "516a0c15a69f78c47dc8e0b9b3724b29608aa6619379f91b1ffa47109b5d0dd3"}, - "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"}, + "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, + "finch": {:hex, :finch, "0.20.0", "5330aefb6b010f424dcbbc4615d914e9e3deae40095e73ab0c1bb0968933cadf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2658131a74d051aabfcba936093c903b8e89da9a1b63e430bee62045fa9b2ee2"}, + "flake_id": {:git, "https://akkoma.dev/AkkomaGang/flake_id.git", "5a68513f7e7353706e788781eff6e56bf00bb41b", [branch: "main"]}, + "floki": {:hex, :floki, "0.38.0", "62b642386fa3f2f90713f6e231da0fa3256e41ef1089f83b6ceac7a3fd3abf33", [:mix], [], "hexpm", "a5943ee91e93fb2d635b612caf5508e36d37548e84928463ef9dd986f0d1abd9"}, + "gen_smtp": {:hex, :gen_smtp, "1.3.0", "62c3d91f0dcf6ce9db71bcb6881d7ad0d1d834c7f38c13fa8e952f4104a8442e", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "0b73fbf069864ecbce02fe653b16d3f35fd889d0fdd4e14527675565c39d84e6"}, + "gettext": {:hex, :gettext, "0.26.2", "5978aa7b21fada6deabf1f6341ddba50bc69c999e812211903b169799208f2a8", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "aa978504bcf76511efdc22d580ba08e2279caab1066b76bb9aa81c4a1e0a32a5"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, - "hackney": {:hex, :hackney, "1.23.0", "55cc09077112bcb4a69e54be46ed9bc55537763a96cd4a80a221663a7eafd767", [:rebar3], [{:certifi, "~> 2.14.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", "6cd1c04cd15c81e5a493f167b226a15f0938a84fc8f0736ebe4ddcab65c0b44e"}, - "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, + "hackney": {:hex, :hackney, "1.25.0", "390e9b83f31e5b325b9f43b76e1a785cbdb69b5b6cd4e079aa67835ded046867", [:rebar3], [{:certifi, "~> 2.15.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.4", [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.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "7209bfd75fd1f42467211ff8f59ea74d6f2a9e81cbcee95a56711ee79fd6b1d4"}, + "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, - "http_signatures": {:git, "https://akkoma.dev/AkkomaGang/http_signatures.git", "d44c43d66758c6a73eaa4da9cffdbee0c5da44ae", [ref: "d44c43d66758c6a73eaa4da9cffdbee0c5da44ae"]}, + "http_signatures": {:git, "https://akkoma.dev/AkkomaGang/http_signatures.git", "fa3891074c33b5cb87964450f1226357f44549ae", [branch: "main"]}, "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"}, - "igniter": {:hex, :igniter, "0.5.29", "6bf7ddaf15e88ae75f6dad514329530ec8f4721ba14782f6386a7345c1be99fd", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "beb6e0f69fc6d4e3975ffa26c5459fc63fd96f85cfaeba984c2dfd3d7333b6ad"}, + "igniter": {:hex, :igniter, "0.5.52", "18777a36918e3bb91c70f07b69f6a6589d8fa5547a7d210b228d410a2453923f", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "8d75f0f2307e21b53ad96bd746f1806da91859ec0d4a68b203b763da4d5ae567"}, "inet_cidr": {:hex, :inet_cidr, "1.0.8", "d26bb7bdbdf21ae401ead2092bf2bb4bf57fe44a62f5eaa5025280720ace8a40", [:mix], [], "hexpm", "d5b26da66603bb56c933c65214c72152f0de9a6ea53618b56d63302a68f6a90e"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "joken": {:hex, :joken, "2.6.2", "5daaf82259ca603af4f0b065475099ada1b2b849ff140ccd37f4b6828ca6892a", [:mix], [{:jose, "~> 1.11.10", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5134b5b0a6e37494e46dbf9e4dad53808e5e787904b7c73972651b51cce3d72b"}, "jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"}, "jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"}, - "linkify": {:hex, :linkify, "0.5.3", "5f8143d8f61f5ff08d3aeeff47ef6509492b4948d8f08007fbf66e4d2246a7f2", [:mix], [], "hexpm", "3ef35a1377d47c25506e07c1c005ea9d38d700699d92ee92825f024434258177"}, - "mail": {:hex, :mail, "0.4.3", "16df84500780980826d9b52059d24c08fdd75c98f178a7a7ea809ea83fb70542", [:mix], [], "hexpm", "164975550b977e47cab431c403b0e90c8ce542036d32c7189b83839d8d7d391b"}, - "majic": {:git, "https://akkoma.dev/AkkomaGang/majic.git", "80540b36939ec83f48e76c61e5000e0fd67706f0", [ref: "80540b36939ec83f48e76c61e5000e0fd67706f0"]}, + "linkify": {:git, "https://akkoma.dev/AkkomaGang/linkify.git", "b8399ccd74849b70609fe92eea96654c1c36aa6d", [branch: "main"]}, + "mail": {:hex, :mail, "0.5.1", "6383a61620aea24675c96e34b9019dede1bfc9a37ee10ce5a5cafcc7c5e48743", [:mix], [], "hexpm", "595144340b74f23d651ea2b4a72a896819940478d7425dfa302ea3b5c9041ec9"}, + "majic": {:git, "https://akkoma.dev/AkkomaGang/majic.git", "80540b36939ec83f48e76c61e5000e0fd67706f0", [branch: "main"]}, "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [: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", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, "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", "360a30267a847810a63ab48f606ba227b2ca05f0", [ref: "360a30267a847810a63ab48f606ba227b2ca05f0"]}, - "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, - "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"}, + "mfm_parser": {:git, "https://akkoma.dev/AkkomaGang/mfm-parser.git", "360a30267a847810a63ab48f606ba227b2ca05f0", [branch: "akkoma"]}, + "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, + "mimerl": {:hex, :mimerl, "1.4.0", "3882a5ca67fbbe7117ba8947f27643557adec38fa2307490c4c4207624cb213b", [:rebar3], [], "hexpm", "13af15f9f68c65884ecca3a3891d50a7b57d82152792f3e19d88650aa126b144"}, + "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, "mneme": {:hex, :mneme, "0.10.2", "f263ff74e993ef9eb160e04b608a149881e601ed6f5508c9afcafb578a184b52", [:mix], [{:file_system, "~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:igniter, "~> 0.3.76 or ~> 0.4.0 or ~> 0.5.0", [hex: :igniter, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 1.0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "3b9493fc114c4bb0f6232e021620ffd7944819b9b9105a5b286b6dc907f7720a"}, "mock": {:hex, :mock, "0.3.9", "10e44ad1f5962480c5c9b9fa779c6c63de9bd31997c8e04a853ec990a9d841af", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "9e1b244c4ca2551bb17bb8415eed89e40ee1308e0fbaed0a4fdfe3ec8a4adbd3"}, "mogrify": {:hex, :mogrify, "0.9.3", "238c782f00271dace01369ad35ae2e9dd020feee3443b9299ea5ea6bed559841", [:mix], [], "hexpm", "0189b1e1de27455f2b9ae8cf88239cefd23d38de9276eb5add7159aea51731e6"}, "mox": {:hex, :mox, "1.2.0", "a2cd96b4b80a3883e3100a221e8adc1b98e4c3a332a8fc434c39526babafd5b3", [:mix], [{:nimble_ownership, "~> 1.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}], "hexpm", "c7b92b3cc69ee24a7eeeaf944cd7be22013c52fcb580c1f33f50845ec821089a"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, - "nimble_ownership": {:hex, :nimble_ownership, "1.0.1", "f69fae0cdd451b1614364013544e66e4f5d25f36a2056a9698b793305c5aa3a6", [:mix], [], "hexpm", "3825e461025464f519f3f3e4a1f9b68c47dc151369611629ad08b636b73bb22d"}, + "nimble_ownership": {:hex, :nimble_ownership, "1.0.2", "fa8a6f2d8c592ad4d79b2ca617473c6aefd5869abfa02563a77682038bf916cf", [:mix], [], "hexpm", "098af64e1f6f8609c6672127cfe9e9590a5d3fcdd82bc17a377b8692fd81a879"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, - "oban": {:hex, :oban, "2.19.2", "11e635c49b4f422814eb96a4d78974c9e67d62a20a969e5fd50db040d32c08ba", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:igniter, "~> 0.5", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "de8314b00b31d17f98fd2c76781f80c1cfc8621122b41830c0834486c44e1087"}, - "oban_met": {:hex, :oban_met, "1.0.1", "737db0064567b923d3f35efd1d3009dd1435d60ee6f98dbb55dbb83db8f4f4fa", [:mix], [{:oban, "~> 2.18", [hex: :oban, repo: "hexpm", optional: false]}], "hexpm", "0492d841f880b76c5b73081bc70ebea20ebacc08e871345f72c2270513f09957"}, - "oban_web": {:hex, :oban_web, "2.11.1", "646492d92177a43ff869535f0c92380e17148f45bc2c8dc8f74c69f8a2874bad", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:oban, "~> 2.19", [hex: :oban, repo: "hexpm", optional: false]}, {:oban_met, "~> 1.0", [hex: :oban_met, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}], "hexpm", "d853c6af3f7c20d03a2bf7b1baad71835d50fbb98af05004e9b51da558b90b01"}, - "open_api_spex": {:hex, :open_api_spex, "3.21.2", "6a704f3777761feeb5657340250d6d7332c545755116ca98f33d4b875777e1e5", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {: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 or ~> 6.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", "f42ae6ed668b895ebba3e02773cfb4b41050df26f803f2ef634c72a7687dc387"}, - "owl": {:hex, :owl, "0.12.2", "65906b525e5c3ef51bab6cba7687152be017aebe1da077bb719a5ee9f7e60762", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "6398efa9e1fea70a04d24231e10dcd66c1ac1aa2da418d20ef5357ec61de2880"}, + "oban": {:hex, :oban, "2.20.1", "39d0b68787e5cf251541c0d657a698f6142a24d8744e1e40b2cf045d4fa232a6", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:igniter, "~> 0.5", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.20", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.3", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "17a45277dbeb41a455040b41dd8c467163fad685d1366f2f59207def3bcdd1d8"}, + "oban_met": {:hex, :oban_met, "1.0.3", "ea8f7a4cef3c8a7aef3b900b4458df46e83508dcbba9374c75dd590efda7a32a", [:mix], [{:oban, "~> 2.19", [hex: :oban, repo: "hexpm", optional: false]}], "hexpm", "23db1a0ee58b93afe324b221530594bdf3647a9bd4e803af762c3e00ad74b9cf"}, + "oban_web": {:hex, :oban_web, "2.11.6", "53933cb4253c4d9f1098ee311c06f07935259f0e564dcf2d66bae4cc98e317fe", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:oban, "~> 2.19", [hex: :oban, repo: "hexpm", optional: false]}, {:oban_met, "~> 1.0", [hex: :oban_met, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}], "hexpm", "576d94b705688c313694c2c114ca21aa0f8f2ad1b9ca45c052c5ba316d3e8d10"}, + "open_api_spex": {:hex, :open_api_spex, "3.22.0", "fbf90dc82681dc042a4ee79853c8e989efbba73d9e87439085daf849bbf8bc20", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {: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 or ~> 6.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", "dd751ddbdd709bb4a5313e9a24530da6e66594773c7242a0c2592cbd9f589063"}, + "owl": {:hex, :owl, "0.13.0", "26010e066d5992774268f3163506972ddac0a7e77bfe57fa42a250f24d6b876e", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "59bf9d11ce37a4db98f57cb68fbfd61593bf419ec4ed302852b6683d3d2f7475"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, - "phoenix": {:hex, :phoenix, "1.7.20", "6bababaf27d59f5628f9b608de902a021be2cecefb8231e1dbdc0a2e2e480e9b", [: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", "6be2ab98302e8784a31829e0d50d8bdfa81a23cd912c395bafd8b8bfb5a086c2"}, - "phoenix_ecto": {:hex, :phoenix_ecto, "4.6.3", "f686701b0499a07f2e3b122d84d52ff8a31f5def386e03706c916f6feddf69ef", [: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.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "909502956916a657a197f94cc1206d9a65247538de8a5e186f7537c895d95764"}, - "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.8.6", "7b1f0327f54c9eb69845fd09a77accf922f488c549a7e7b8618775eb603a62c7", [: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]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "1681ab813ec26ca6915beb3414aa138f298e17721dc6a2bde9e6eb8a62360ff6"}, - "phoenix_live_view": {:hex, :phoenix_live_view, "1.0.5", "f072166f87c44ffaf2b47b65c5ced8c375797830e517bfcf0a006fe7eb113911", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {: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 or ~> 4.0", [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]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "94abbc84df8a93a64514fc41528695d7326b6f3095e906b32f264ec4280811f3"}, - "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, + "phoenix": {:hex, :phoenix, "1.8.1", "865473a60a979551a4879db79fbfb4503e41cd809e77c85af79716578b6a456d", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {: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", "84d77d2b2e77c3c7e7527099bd01ef5c8560cd149c036d6b3a40745f11cd2fb2"}, + "phoenix_ecto": {:hex, :phoenix_ecto, "4.7.0", "75c4b9dfb3efdc42aec2bd5f8bccd978aca0651dbcbc7a3f362ea5d9d43153c6", [: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.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "1d75011e4254cb4ddf823e81823a9629559a1be93b4321a6a5f11a5306fbf4cc"}, + "phoenix_html": {:hex, :phoenix_html, "4.3.0", "d3577a5df4b6954cd7890c84d955c470b5310bb49647f0a114a6eeecc850f7ad", [:mix], [], "hexpm", "3eaa290a78bab0f075f791a46a981bbe769d94bc776869f4f3063a14f30497ad"}, + "phoenix_html_helpers": {:hex, :phoenix_html_helpers, "1.0.1", "7eed85c52eff80a179391036931791ee5d2f713d76a81d0d2c6ebafe1e11e5ec", [:mix], [{:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cffd2385d1fa4f78b04432df69ab8da63dc5cf63e07b713a4dcf36a3740e3090"}, + "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.7", "405880012cb4b706f26dd1c6349125bfc903fb9e44d1ea668adaf4e04d4884b7", [: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]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "3a8625cab39ec261d48a13b7468dc619c0ede099601b084e343968309bd4d7d7"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "1.0.18", "943431edd0ef8295ffe4949f0897e2cb25c47d3d7ebba2b008d7c68598b887f1", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [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]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "724934fd0a68ecc57281cee863674454b06163fed7f5b8005b5e201ba4b23316"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.2.0", "ff3a5616e1bed6804de7773b92cbccfc0b0f473faf1f63d7daf1206c7aeaaa6f", [:mix], [], "hexpm", "adc313a5bf7136039f63cfd9668fde73bba0765e0614cba80c06ac9460ff3e96"}, "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.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [: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", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"}, - "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [: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", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.7.3", "1304d36752e8bdde213cea59ef424ca932910a91a07ef9f3874be709c4ddb94b", [: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", "77c95524b2aa5364b247fa17089029e73b951ebc1adeef429361eab0bb55819d"}, - "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, + "plug": {:hex, :plug, "1.18.1", "5067f26f7745b7e31bc3368bc1a2b818b9779faa959b49c934c17730efc911cf", [: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", "57a57db70df2b422b564437d2d33cf8d33cd16339c1edb190cd11b1a3a546cc2"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.7.4", "729c752d17cf364e2b8da5bdb34fb5804f56251e88bb602aff48ae0bd8673d11", [: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", "9b85632bd7012615bae0a5d70084deb1b25d2bcbb32cab82d1e9a1e023168aa3"}, + "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, "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"}, - "postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"}, + "postgrex": {:hex, :postgrex, "0.21.1", "2c5cc830ec11e7a0067dd4d623c049b3ef807e9507a424985b8dcf921224cd88", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "27d8d21c103c3cc68851b533ff99eef353e6a0ff98dc444ea751de43eb48bdac"}, "pot": {:hex, :pot, "1.0.2", "13abb849139fdc04ab8154986abbcb63bdee5de6ed2ba7e1713527e33df923dd", [:rebar3], [], "hexpm", "78fe127f5a4f5f919d6ea5a2a671827bd53eb9d37e5b4128c0ad3df99856c2e0"}, "ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"}, "recon": {:hex, :recon, "2.5.6", "9052588e83bfedfd9b72e1034532aee2a5369d9d9343b61aeb7fbce761010741", [:mix, :rebar3], [], "hexpm", "96c6799792d735cc0f0fd0f86267e9d351e63339cbe03df9d162010cefc26bb0"}, - "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"}, - "req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"}, - "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, - "search_parser": {:git, "https://github.com/FloatingGhost/pleroma-contrib-search-parser.git", "08971a81e68686f9ac465cfb6661d51c5e4e1e7f", [ref: "08971a81e68686f9ac465cfb6661d51c5e4e1e7f"]}, + "remote_ip": {:hex, :remote_ip, "1.2.0", "fb078e12a44414f4cef5a75963c33008fe169b806572ccd17257c208a7bc760f", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "2ff91de19c48149ce19ed230a81d377186e4412552a597d6a5137373e5877cb7"}, + "req": {:hex, :req, "0.5.15", "662020efb6ea60b9f0e0fac9be88cd7558b53fe51155a2d9899de594f9906ba9", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "a6513a35fad65467893ced9785457e91693352c70b58bbc045b47e5eb2ef0c53"}, + "rewrite": {:hex, :rewrite, "1.2.0", "80220eb14010e175b67c939397e1a8cdaa2c32db6e2e0a9d5e23e45c0414ce21", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "a1cd702bbb9d51613ab21091f04a386d750fc6f4516b81900df082d78b2d8c50"}, + "search_parser": {:git, "https://github.com/FloatingGhost/pleroma-contrib-search-parser.git", "08971a81e68686f9ac465cfb6661d51c5e4e1e7f", [branch: "main"]}, "sleeplocks": {:hex, :sleeplocks, "1.1.3", "96a86460cc33b435c7310dbd27ec82ca2c1f24ae38e34f8edde97f756503441a", [:rebar3], [], "hexpm", "d3b3958552e6eb16f463921e70ae7c767519ef8f5be46d7696cc1ed649421321"}, - "sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"}, - "spitfire": {:hex, :spitfire, "0.1.5", "10b041e781bff9544d2fdf00893e1a325758408c5366a9bfa4333072568659b1", [:mix], [], "hexpm", "866a55d21fe827934ff38200111335c9dd311df13cbf2580ed71d84b0a783150"}, + "sourceror": {:hex, :sourceror, "1.10.0", "38397dedbbc286966ec48c7af13e228b171332be1ad731974438c77791945ce9", [:mix], [], "hexpm", "29dbdfc92e04569c9d8e6efdc422fc1d815f4bd0055dc7c51b8800fb75c4b3f1"}, + "spitfire": {:hex, :spitfire, "0.2.1", "29e154873f05444669c7453d3d931820822cbca5170e88f0f8faa1de74a79b47", [:mix], [], "hexpm", "6eeed75054a38341b2e1814d41bb0a250564092358de2669fdb57ff88141d91b"}, "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"}, + "statistex": {:hex, :statistex, "1.1.0", "7fec1eb2f580a0d2c1a05ed27396a084ab064a40cfc84246dbfb0c72a5c761e5", [:mix], [], "hexpm", "f5950ea26ad43246ba2cce54324ac394a4e7408fdcf98b8e230f503a0cba9cf5"}, "sweet_xml": {:hex, :sweet_xml, "0.7.5", "803a563113981aaac202a1dbd39771562d0ad31004ddbfc9b5090bdcd5605277", [:mix], [], "hexpm", "193b28a9b12891cae351d81a0cead165ffe67df1b73fe5866d10629f4faefb12"}, - "swoosh": {:hex, :swoosh, "1.14.4", "94e9dba91f7695a10f49b0172c4a4cb658ef24abef7e8140394521b7f3bbb2d4", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.4 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "081c5a590e4ba85cc89baddf7b2beecf6c13f7f84a958f1cd969290815f0f026"}, + "swoosh": {:hex, :swoosh, "1.19.8", "0576f2ea96d1bb3a6e02cc9f79cbd7d497babc49a353eef8dce1a1f9f82d7915", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:idna, "~> 6.0", [hex: :idna, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5.10 or ~> 0.6 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d7503c2daf0f9899afd8eba9923eeddef4b62e70816e1d3b6766e4d6c60e94ad"}, "syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"}, "table_rex": {:hex, :table_rex, "4.1.0", "fbaa8b1ce154c9772012bf445bfb86b587430fb96f3b12022d3f35ee4a68c918", [:mix], [], "hexpm", "95932701df195d43bc2d1c6531178fc8338aa8f38c80f098504d529c43bc2601"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, - "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"}, + "telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"}, "telemetry_metrics_prometheus": {:hex, :telemetry_metrics_prometheus, "1.1.0", "1cc23e932c1ef9aa3b91db257ead31ea58d53229d407e059b29bb962c1505a13", [:mix], [{:plug_cowboy, "~> 2.1", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.0", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}], "hexpm", "d43b3659b3244da44fe0275b717701542365d4519b79d9ce895b9719c1ce4d26"}, - "telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.1.0", "4e15f6d7dbedb3a4e3aed2262b7e1407f166fcb9c30ca3f96635dfbbef99965c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0dd10e7fe8070095df063798f82709b0a1224c31b8baf6278b423898d591a069"}, - "telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"}, + "telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.2.1", "c9755987d7b959b557084e6990990cb96a50d6482c683fb9622a63837f3cd3d8", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "5e2c599da4983c4f88a33e9571f1458bf98b0cf6ba930f1dc3a6e8cf45d5afb6"}, + "telemetry_poller": {:hex, :telemetry_poller, "1.3.0", "d5c46420126b5ac2d72bc6580fb4f537d35e851cc0f8dbd571acf6d6e10f5ec7", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "51f18bed7128544a50f75897db9974436ea9bfba560420b646af27a9a9b35211"}, "temple": {:git, "https://akkoma.dev/AkkomaGang/temple.git", "066a699ade472d8fa42a9d730b29a61af9bc8b59", [ref: "066a699ade472d8fa42a9d730b29a61af9bc8b59"]}, - "tesla": {:hex, :tesla, "1.14.1", "71c5b031b4e089c0fbfb2b362e24b4478465773ae4ef569760a8c2899ad1e73c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.21", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:mox, "~> 1.0", [hex: :mox, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "c1dde8140a49a3bef5bb622356e77ac5a24ad0c8091f12c3b7fc1077ce797155"}, + "tesla": {:hex, :tesla, "1.15.3", "3a2b5c37f09629b8dcf5d028fbafc9143c0099753559d7fe567eaabfbd9b8663", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.21", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:mox, "~> 1.0", [hex: :mox, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "98bb3d4558abc67b92fb7be4cd31bb57ca8d80792de26870d362974b58caeda7"}, "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"}, - "timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"}, + "timex": {:hex, :timex, "3.7.13", "0688ce11950f5b65e154e42b47bf67b15d3bc0e0c3def62199991b8a8079a1e2", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.26", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "09588e0522669328e973b8b4fd8741246321b3f0d32735b589f78b136e6d4c54"}, "trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"}, - "tzdata": {:hex, :tzdata, "1.1.2", "45e5f1fcf8729525ec27c65e163be5b3d247ab1702581a94674e008413eef50b", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "cec7b286e608371602318c414f344941d5eb0375e14cfdab605cca2fe66cba8b"}, - "ueberauth": {:hex, :ueberauth, "0.10.5", "806adb703df87e55b5615cf365e809f84c20c68aa8c08ff8a416a5a6644c4b02", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3efd1f31d490a125c7ed453b926f7c31d78b97b8a854c755f5c40064bf3ac9e1"}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, + "tzdata": {:hex, :tzdata, "1.1.3", "b1cef7bb6de1de90d4ddc25d33892b32830f907e7fc2fccd1e7e22778ab7dfbc", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "d4ca85575a064d29d4e94253ee95912edfb165938743dbf002acdf0dcecb0c28"}, + "ueberauth": {:hex, :ueberauth, "0.10.8", "ba78fbcbb27d811a6cd06ad851793aaf7d27c3b30c9e95349c2c362b344cd8f0", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f2d3172e52821375bccb8460e5fa5cb91cfd60b19b636b6e57e9759b6f8c10c1"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"}, "unsafe": {:hex, :unsafe, "1.0.2", "23c6be12f6c1605364801f4b47007c0c159497d0446ad378b5cf05f1855c0581", [:mix], [], "hexpm", "b485231683c3ab01a9cd44cb4a79f152c6f3bb87358439c6f68791b85c2df675"}, "vex": {:hex, :vex, "0.9.2", "fe061acc9e0907d983d46b51bf35d58176f0fe6eb7ba3b33c9336401bf42b6d1", [:mix], [], "hexpm", "76e709a9762e98c6b462dfce92e9b5dfbf712839227f2da8add6dd11549b12cb"}, "web_push_encryption": {:hex, :web_push_encryption, "0.3.1", "76d0e7375142dfee67391e7690e89f92578889cbcf2879377900b5620ee4708d", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.11.1", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "4f82b2e57622fb9337559058e8797cb0df7e7c9790793bdc4e40bc895f70e2a2"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, - "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"}, + "websock_adapter": {:hex, :websock_adapter, "0.5.9", "43dc3ba6d89ef5dec5b1d0a39698436a1e856d000d84bf31a3149862b01a287f", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "5534d5c9adad3c18a0f58a9371220d75a803bf0b9a3d87e6fe072faaeed76a08"}, "websockex": {:hex, :websockex, "0.4.3", "92b7905769c79c6480c02daacaca2ddd49de936d912976a4d3c923723b647bf0", [:mix], [], "hexpm", "95f2e7072b85a3a4cc385602d42115b73ce0b74a9121d0d6dbbf557645ac53e4"}, } diff --git a/priv/gettext/tr/LC_MESSAGES/config_descriptions.po b/priv/gettext/tr/LC_MESSAGES/config_descriptions.po new file mode 100644 index 000000000..e192def8c --- /dev/null +++ b/priv/gettext/tr/LC_MESSAGES/config_descriptions.po @@ -0,0 +1,6433 @@ +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-03-23 21:04+0000\n" +"PO-Revision-Date: 2025-03-28 06:32+0000\n" +"Last-Translator: Hasan Yıldız \n" +"Language-Team: Turkish \n" +"Language: tr\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 "Logger ile ilgili ayarlar" + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format +msgctxt "config description at :mime" +msgid "Mime Types settings" +msgstr "Mim Türleri ayarları" + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format, fuzzy +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 "" +"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)" + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format, fuzzy +msgctxt "config description at :pleroma" +msgid "Authenticator" +msgstr "Authenticator" + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format, fuzzy +msgctxt "config label at :cors_plug" +msgid "CORS plug config" +msgstr "CORS plug config" + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format, fuzzy +msgctxt "config label at :logger" +msgid "Logger" +msgstr "Logger" + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format, fuzzy +msgctxt "config label at :mime" +msgid "Mime Types" +msgstr "Mime Types" + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format, fuzzy +msgctxt "config label at :pleroma" +msgid "Pleroma Admin Token" +msgstr "Pleroma Admin Token" + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format, fuzzy +msgctxt "config label at :pleroma" +msgid "Pleroma Authenticator" +msgstr "Pleroma Authenticator" + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format, fuzzy +msgctxt "config description at :logger-:console" +msgid "Console logger settings" +msgstr "Console logger settings" + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format, fuzzy +msgctxt "config description at :logger-:ex_syslogger" +msgid "ExSyslogger-related settings" +msgstr "ExSyslogger-related settings" + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format, fuzzy +msgctxt "config description at :pleroma-:activitypub" +msgid "ActivityPub-related settings" +msgstr "ActivityPub-related settings" + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format, fuzzy +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 "" +"This section configures assets to be used with various frontends. Currently " +"the only option relates to mascots on the mastodon frontend" + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format, fuzzy +msgctxt "config description at :pleroma-:auth" +msgid "Authentication / authorization settings" +msgstr "Authentication / authorization settings" + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format, fuzzy +msgctxt "config description at :pleroma-:email_notifications" +msgid "Email notifications settings" +msgstr "Email notifications settings" + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format, fuzzy +msgctxt "config description at :pleroma-:features" +msgid "Customizable features" +msgstr "Customizable features" + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format, fuzzy +msgctxt "config description at :pleroma-:feed" +msgid "Configure feed rendering" +msgstr "Configure feed rendering" + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format, fuzzy +msgctxt "config description at :pleroma-:frontends" +msgid "Installed frontends management" +msgstr "Installed frontends management" + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format, fuzzy +msgctxt "config description at :pleroma-:http" +msgid "HTTP settings" +msgstr "HTTP settings" + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format, fuzzy +msgctxt "config description at :pleroma-:http_security" +msgid "HTTP security settings" +msgstr "HTTP security settings" + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format, fuzzy +msgctxt "config description at :pleroma-:instance" +msgid "Instance-related settings" +msgstr "Instance-related settings" + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format, fuzzy +msgctxt "config description at :pleroma-:instances_favicons" +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-: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 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." + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format, fuzzy +msgctxt "config description at :pleroma-:majic_pool" +msgid "Majic/libmagic configuration" +msgstr "Majic/libmagic configuration" + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format, fuzzy +msgctxt "config description at :pleroma-:manifest" +msgid "This section describe PWA manifest instance-specific values. Currently this option relate only for MastoFE." +msgstr "" +"This section describe PWA manifest instance-specific values. Currently this " +"option relate only for MastoFE." + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format, fuzzy +msgctxt "config description 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 description at :pleroma-:media_proxy" +msgid "Media proxy" +msgstr "Media proxy" + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format, fuzzy +msgctxt "config description at :pleroma-:modules" +msgid "Custom Runtime Modules" +msgstr "Custom Runtime Modules" + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format, fuzzy +msgctxt "config description at :pleroma-:mrf" +msgid "General MRF settings" +msgstr "General MRF settings" + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format, fuzzy +msgctxt "config description at :pleroma-:mrf_activity_expiration" +msgid "Adds automatic expiration to all local activities" +msgstr "Adds automatic expiration to all local activities" + +#: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format, fuzzy +msgctxt "config description at :pleroma-:mrf_follow_bot" +msgid "Automatically follows newly discovered accounts." +msgstr "Automatically follows newly discovered accounts." + +#: 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, fuzzy +msgctxt "config description at :pleroma-Pleroma.Upload.Filter.AnonymizeFilename" +msgid "Filter replaces the filename of the upload" +msgstr "Filter replaces the filename of the 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 > :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 > :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/zh_Hans/LC_MESSAGES/config_descriptions.po b/priv/gettext/zh_Hans/LC_MESSAGES/config_descriptions.po index 2c196fb70..08983968d 100644 --- a/priv/gettext/zh_Hans/LC_MESSAGES/config_descriptions.po +++ b/priv/gettext/zh_Hans/LC_MESSAGES/config_descriptions.po @@ -8,30 +8,27 @@ # ## to merge POT files into PO files. msgid "" msgstr "" -"PO-Revision-Date: 2023-08-04 14:26+0000\n" -"Last-Translator: Anonymous \n" -"Language-Team: Chinese (Simplified) \n" +"PO-Revision-Date: 2025-09-01 06:55+0000\n" +"Last-Translator: Poesty Li \n" +"Language-Team: Chinese (Simplified Han script) \n" "Language: zh_Hans\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 4.18.2\n" +"X-Generator: Weblate 5.12.2\n" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :logger" msgid "Logger-related settings" -msgstr "Logger-related settings" +msgstr "日志相关设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :mime" msgid "Mime Types settings" -msgstr "Mime Types settings" +msgstr "MIME 类型设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma" msgid "" "Allows setting a token that can be used to authenticate requests with admin " @@ -39,131 +36,108 @@ msgid "" "parameter to requests to utilize it. (Please reconsider using HTTP Basic " "Auth or OAuth-based authentication if possible)" msgstr "" -"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)" +"允许设置一个令牌,该令牌可用于在没有普通用户账号令牌的情况下对具有管理员权限" +"的请求进行身份验证。在请求中附加 `admin_token` 参数以使用它。(如有可能," +"请考虑使用 HTTP 基本认证或基于 OAuth 的身份验证)" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma" msgid "Authenticator" -msgstr "Authenticator" +msgstr "认证器" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :cors_plug" msgid "CORS plug config" -msgstr "CORS plug config" +msgstr "CORS 插件配置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :logger" msgid "Logger" -msgstr "Logger" +msgstr "日志记录器" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :mime" msgid "Mime Types" -msgstr "Mime Types" +msgstr "MIME 类型" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma" msgid "Pleroma Admin Token" -msgstr "Pleroma Admin Token" +msgstr "Pleroma 管理员令牌" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma" msgid "Pleroma Authenticator" -msgstr "Pleroma Authenticator" +msgstr "Pleroma 认证器" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :logger-:console" msgid "Console logger settings" -msgstr "Console logger settings" +msgstr "控制台日志记录器设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :logger-:ex_syslogger" msgid "ExSyslogger-related settings" -msgstr "ExSyslogger-related settings" +msgstr "ExSyslogger 相关设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:activitypub" msgid "ActivityPub-related settings" -msgstr "ActivityPub-related settings" +msgstr "ActivityPub 相关设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy 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 "" -"This section configures assets to be used with various frontends. Currently " -"the only option relates to mascots on the mastodon frontend" +msgstr "此部分配置用于各种前端的资源。目前唯一选项涉及 Mastodon 前端的吉祥物" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:auth" msgid "Authentication / authorization settings" -msgstr "Authentication / authorization settings" +msgstr "认证/授权设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:email_notifications" msgid "Email notifications settings" -msgstr "Email notifications settings" +msgstr "邮件通知设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:features" msgid "Customizable features" -msgstr "Customizable features" +msgstr "可自定义功能" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:feed" msgid "Configure feed rendering" -msgstr "Configure feed rendering" +msgstr "配置信息流渲染" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:frontends" msgid "Installed frontends management" -msgstr "Installed frontends management" +msgstr "已安装前端管理" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:http" msgid "HTTP settings" -msgstr "HTTP settings" +msgstr "HTTP 设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:http_security" msgid "HTTP security settings" -msgstr "HTTP security settings" +msgstr "HTTP 安全设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:instance" msgid "Instance-related settings" -msgstr "Instance-related settings" +msgstr "实例相关设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:instances_favicons" msgid "Control favicons for instances" -msgstr "Control favicons for instances" +msgstr "控制实例的网站图标" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:ldap" msgid "" "Use LDAP for user authentication. When a user logs in to the Pleroma " @@ -172,66 +146,53 @@ msgid "" "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 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." +"使用 LDAP 进行用户认证。当用户登录 Pleroma 实例时,将通过尝试对 LDAP " +"服务器进行身份验证(绑定)来验证名称和密码。如果用户存在于 LDAP 目录中但 " +"Pleroma 实例上尚无同名账号,则将创建一个与 LDAP 用户名相同的新 Pleroma 账号。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:majic_pool" msgid "Majic/libmagic configuration" -msgstr "Majic/libmagic configuration" +msgstr "Majic/libmagic 配置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:manifest" msgid "" "This section describe PWA manifest instance-specific values. Currently this " "option relate only for MastoFE." -msgstr "" -"This section describe PWA manifest instance-specific values. Currently this " -"option relate only for MastoFE." +msgstr "此部分描述 PWA 清单的实例特定值。目前此选项仅适用于 MastoFE。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:media_preview_proxy" msgid "Media preview proxy" -msgstr "Media preview proxy" +msgstr "媒体预览代理" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:media_proxy" msgid "Media proxy" -msgstr "Media proxy" +msgstr "媒体代理" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:modules" msgid "Custom Runtime Modules" -msgstr "Custom Runtime Modules" +msgstr "自定义运行时模块" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:mrf" msgid "General MRF settings" -msgstr "General MRF settings" +msgstr "通用 MRF 设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:mrf_activity_expiration" msgid "Adds automatic expiration to all local activities" -msgstr "Adds automatic expiration to all local activities" +msgstr "为所有本地活动添加自动过期" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:mrf_follow_bot" msgid "Automatically follows newly discovered accounts." -msgstr "Automatically follows newly discovered accounts." +msgstr "自动关注新发现的账号。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:mrf_hashtag" msgid "" "Reject, TWKN-remove or Set-Sensitive messsages with specific hashtags " @@ -240,289 +201,231 @@ msgid "" "Note: 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" +"拒绝、TWKN 移除或设置包含特定标签(不带前导#号)的消息为敏感内容\n" "\n" -"Note: This MRF Policy is always enabled, if you want to disable it you have " -"to set empty lists.\n" +"注意:此 MRF 策略始终启用,如需禁用需设置空列表。\n" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:mrf_hellthread" msgid "Block messages with excessive user mentions" -msgstr "Block messages with excessive user mentions" +msgstr "阻止包含过多用户提及的消息" #: lib/pleroma/docs/translator.ex:5 -#, 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)." +msgstr "拒绝或替换匹配关键词或[正则表达式](https://hexdocs.pm/elixir/" +"Regex.html)的消息。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:mrf_mention" msgid "Block messages which mention a specific user" -msgstr "Block messages which mention a specific user" +msgstr "阻止提及特定用户的消息" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "MRF 标记标准化设置。清理配置的超文本标记。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "根据帖文时间戳与服务器时钟的偏差拒绝或取消列表帖文。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "RejectNonPublic 会丢弃具有非公开可见性设置的帖文。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:mrf_simple" msgid "Simple ingress policies" -msgstr "Simple ingress policies" +msgstr "简单的入口策略" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "当看到来自选定实例的表情符号时会进行盗用。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "当给定消息符合特定条件时,此策略通过备用管道处理消息。所有条件均配置为从正则" +"表达式到策略模块列表的映射。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "过滤属于特定活动词汇表的消息" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:oauth2" msgid "Configure OAuth 2 provider capabilities" -msgstr "Configure OAuth 2 provider capabilities" +msgstr "配置 OAuth 2 提供者能力" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:populate_hashtags_table" msgid "`populate_hashtags_table` background migration settings" -msgstr "`populate_hashtags_table` background migration settings" +msgstr "`populate_hashtags_table` 后台迁移设置" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "速率限制设置。此为高级功能,默认仅对 :authentication 启用。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "若启用,实例将解析附加链接的元数据以生成链接预览" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "使用服务器生成的 HTML 渲染配置文件和帖文,无需 JavaScript 即可查看" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:streamer" msgid "Settings for notifications streamer" -msgstr "Settings for notifications streamer" +msgstr "通知流设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:uri_schemes" msgid "URI schemes related settings" -msgstr "URI schemes related settings" +msgstr "URI 方案相关设置" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "Web 响应缓存的过期时间。值应为毫秒数或 `nil`(禁用过期)。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:welcome" msgid "Welcome messages settings" -msgstr "Welcome messages settings" +msgstr "欢迎消息设置" #: lib/pleroma/docs/translator.ex:5 -#, 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`" +msgstr "包含无法由 `Oban` 直接解释的自定义工作器选项" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-ConcurrentLimiter" msgid "Limits configuration for background tasks." -msgstr "Limits configuration for background tasks." +msgstr "后台任务的限制配置。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "[Oban](https://github.com/sorentwo/oban) 异步作业处理器配置。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Pleroma.Captcha" msgid "Captcha-related settings" -msgstr "Captcha-related settings" +msgstr "验证码相关设置" #: lib/pleroma/docs/translator.ex:5 -#, 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." +"Kocaptcha 是一个极简的验证码服务,仅有一个 API 端点,源代码在此:https://" +"github.com/koto-bank/kocaptcha。默认端点 (https://captcha.kotobank.ch) " +"由开发者托管。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Pleroma.Emails.Mailer" msgid "Mailer-related settings" -msgstr "Mailer-related settings" +msgstr "邮件发送器相关设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Pleroma.Emails.NewUsersDigestEmail" msgid "New users admin email digest" -msgstr "New users admin email digest" +msgstr "新用户管理员邮件摘要" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Pleroma.Emails.UserEmail" msgid "Email template settings" -msgstr "Email template settings" +msgstr "邮件模板设置" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "Pleroma 链接格式化器的配置,用于解析提及、话题标签和 URL。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Pleroma.ScheduledActivity" msgid "Scheduled activities settings" -msgstr "Scheduled activities settings" +msgstr "定时活动设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Pleroma.Upload" msgid "Upload general settings" -msgstr "Upload general settings" +msgstr "上传通用设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-Pleroma.Upload.Filter.AnonymizeFilename" msgid "Filter replaces the filename of the upload" -msgstr "Filter replaces the filename of the upload" +msgstr "过滤器替换上传文件的文件名" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Pleroma.Upload.Filter.Mogrify" msgid "Uploads mogrify filter settings" -msgstr "Uploads mogrify filter settings" +msgstr "上传 mogrify 过滤器设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Pleroma.Uploaders.Local" msgid "Local uploader-related settings" -msgstr "Local uploader-related settings" +msgstr "本地上传器相关设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Pleroma.Uploaders.S3" msgid "S3 uploader-related settings" -msgstr "S3 uploader-related settings" +msgstr "S3 上传器相关设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Pleroma.User.Backup" msgid "Account Backup" -msgstr "Account Backup" +msgstr "账号备份" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http" msgid "HTTP invalidate settings" -msgstr "HTTP invalidate settings" +msgstr "HTTP 失效设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Script" msgid "Invalidation script settings" -msgstr "Invalidation script settings" +msgstr "失效脚本设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Pleroma.Web.Metadata" msgid "Metadata-related settings" -msgstr "Metadata-related settings" +msgstr "元数据相关设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Pleroma.Web.Plugs.RemoteIp" msgid "" "`Pleroma.Web.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git." @@ -530,623 +433,516 @@ msgid "" "**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" +"`Pleroma.Web.Plugs.RemoteIp` 是一个垫片,用于调用 [`RemoteIp`](https://" +"git.pleroma.social/pleroma/remote_ip) 但支持运行时配置。\n" +"**如果您的实例未处于至少一个反向代理之后,则不应启用此插件。**\n" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Pleroma.Web.Preload" msgid "Preload-related settings" -msgstr "Preload-related settings" +msgstr "预加载相关设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Pleroma.Workers.PurgeExpiredActivity" msgid "Expired activities settings" -msgstr "Expired activities settings" +msgstr "过期活动设置" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "Web 推送通知配置。您可以使用 mix 任务 mix web_push.gen.keypair 来生成它。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :ex_aws-:s3" msgid "S3" msgstr "S3" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :logger-:console" msgid "Console Logger" -msgstr "Console Logger" +msgstr "控制台日志记录器" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :logger-:ex_syslogger" msgid "ExSyslogger" msgstr "ExSyslogger" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:activitypub" msgid "ActivityPub" msgstr "ActivityPub" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:assets" msgid "Assets" -msgstr "Assets" +msgstr "资源" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:auth" msgid "Auth" -msgstr "Auth" +msgstr "认证" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:email_notifications" msgid "Email notifications" -msgstr "Email notifications" +msgstr "邮件通知" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:emoji" msgid "Emoji" -msgstr "Emoji" +msgstr "表情符号" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:features" msgid "Features" -msgstr "Features" +msgstr "功能" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:feed" msgid "Feed" -msgstr "Feed" +msgstr "信息流" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:frontend_configurations" msgid "Frontend configurations" -msgstr "Frontend configurations" +msgstr "前端配置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:frontends" msgid "Frontends" -msgstr "Frontends" +msgstr "前端" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:http" msgid "HTTP" msgstr "HTTP" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:http_security" msgid "HTTP security" -msgstr "HTTP security" +msgstr "HTTP 安全" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance" msgid "Instance" -msgstr "Instance" +msgstr "实例" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instances_favicons" msgid "Instances favicons" -msgstr "Instances favicons" +msgstr "实例收藏图标" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:ldap" msgid "LDAP" msgstr "LDAP" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:majic_pool" msgid "Majic pool" -msgstr "Majic pool" +msgstr "Majic 池" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:manifest" msgid "Manifest" -msgstr "Manifest" +msgstr "清单" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:markup" msgid "Markup Settings" -msgstr "Markup Settings" +msgstr "标记设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:media_preview_proxy" msgid "Media preview proxy" -msgstr "Media preview proxy" +msgstr "媒体预览代理" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:media_proxy" msgid "Media proxy" -msgstr "Media proxy" +msgstr "媒体代理" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:modules" msgid "Modules" -msgstr "Modules" +msgstr "模块" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf" msgid "MRF" msgstr "MRF" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_activity_expiration" msgid "MRF Activity Expiration Policy" -msgstr "MRF Activity Expiration Policy" +msgstr "MRF 活动过期策略" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_follow_bot" msgid "MRF FollowBot Policy" -msgstr "MRF FollowBot Policy" +msgstr "MRF 关注机器人策略" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_hashtag" msgid "MRF Hashtag" -msgstr "MRF Hashtag" +msgstr "MRF 话题标签" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_hellthread" msgid "MRF Hellthread" -msgstr "MRF Hellthread" +msgstr "MRF 地狱线程" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_keyword" msgid "MRF Keyword" -msgstr "MRF Keyword" +msgstr "MRF 关键词" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_mention" msgid "MRF Mention" -msgstr "MRF Mention" +msgstr "MRF 提及" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_normalize_markup" msgid "MRF Normalize Markup" -msgstr "MRF Normalize Markup" +msgstr "MRF 标准化标记" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_object_age" msgid "MRF Object Age" -msgstr "MRF Object Age" +msgstr "MRF 对象年龄" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_rejectnonpublic" msgid "MRF Reject Non Public" -msgstr "MRF Reject Non Public" +msgstr "MRF 拒绝非公开" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_simple" msgid "MRF Simple" -msgstr "MRF Simple" +msgstr "MRF 简单策略" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_steal_emoji" msgid "MRF Emojis" -msgstr "MRF Emojis" +msgstr "MRF 表情符号" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_subchain" msgid "MRF Subchain" -msgstr "MRF Subchain" +msgstr "MRF 子链" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_vocabulary" msgid "MRF Vocabulary" -msgstr "MRF Vocabulary" +msgstr "MRF 词汇表" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:oauth2" msgid "OAuth2" msgstr "OAuth2" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:populate_hashtags_table" msgid "Populate hashtags table" -msgstr "Populate hashtags table" +msgstr "填充话题标签表" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:rate_limit" msgid "Rate limit" -msgstr "Rate limit" +msgstr "速率限制" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:restrict_unauthenticated" msgid "Restrict Unauthenticated" -msgstr "Restrict Unauthenticated" +msgstr "限制未认证用户" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:rich_media" msgid "Rich media" -msgstr "Rich media" +msgstr "富媒体" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:static_fe" msgid "Static FE" -msgstr "Static FE" +msgstr "静态前端" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:streamer" msgid "Streamer" -msgstr "Streamer" +msgstr "流媒体器" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:uri_schemes" msgid "URI Schemes" -msgstr "URI Schemes" +msgstr "URI 方案" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:user" msgid "User" -msgstr "User" +msgstr "用户" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:web_cache_ttl" msgid "Web cache TTL" -msgstr "Web cache TTL" +msgstr "Web 缓存 TTL" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:welcome" msgid "Welcome" -msgstr "Welcome" +msgstr "欢迎" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:workers" msgid "Workers" -msgstr "Workers" +msgstr "工作器" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-ConcurrentLimiter" msgid "ConcurrentLimiter" -msgstr "ConcurrentLimiter" +msgstr "并发限制器" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Oban" msgid "Oban" msgstr "Oban" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Captcha" msgid "Pleroma.Captcha" msgstr "Pleroma.Captcha" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Captcha.Kocaptcha" msgid "Pleroma.Captcha.Kocaptcha" msgstr "Pleroma.Captcha.Kocaptcha" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Emails.Mailer" msgid "Pleroma.Emails.Mailer" msgstr "Pleroma.Emails.Mailer" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Emails.NewUsersDigestEmail" msgid "Pleroma.Emails.NewUsersDigestEmail" msgstr "Pleroma.Emails.NewUsersDigestEmail" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Emails.UserEmail" msgid "Pleroma.Emails.UserEmail" msgstr "Pleroma.Emails.UserEmail" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Formatter" msgid "Linkify" -msgstr "Linkify" +msgstr "链接化" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.ScheduledActivity" msgid "Pleroma.ScheduledActivity" msgstr "Pleroma.ScheduledActivity" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Upload" msgid "Pleroma.Upload" msgstr "Pleroma.Upload" #: lib/pleroma/docs/translator.ex:5 -#, 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 -#, 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 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Uploaders.Local" msgid "Pleroma.Uploaders.Local" msgstr "Pleroma.Uploaders.Local" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Uploaders.S3" msgid "Pleroma.Uploaders.S3" msgstr "Pleroma.Uploaders.S3" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.User" msgid "Pleroma.User" msgstr "Pleroma.User" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.User.Backup" msgid "Pleroma.User.Backup" msgstr "Pleroma.User.Backup" #: lib/pleroma/docs/translator.ex:5 -#, 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 -#, 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 -#, 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 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Web.Metadata" msgid "Pleroma.Web.Metadata" msgstr "Pleroma.Web.Metadata" #: lib/pleroma/docs/translator.ex:5 -#, 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 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Web.Preload" msgid "Pleroma.Web.Preload" msgstr "Pleroma.Web.Preload" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Workers.PurgeExpiredActivity" msgid "Pleroma.Workers.PurgeExpiredActivity" msgstr "Pleroma.Workers.PurgeExpiredActivity" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :web_push_encryption-:vapid_details" msgid "Vapid Details" -msgstr "Vapid Details" +msgstr "Vapid 详情" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :ex_aws-:s3 > :access_key_id" msgid "S3 access key ID" -msgstr "S3 access key ID" +msgstr "S3 访问密钥 ID" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :ex_aws-:s3 > :host" msgid "S3 host" -msgstr "S3 host" +msgstr "S3 主机" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :ex_aws-:s3 > :region" msgid "S3 region (for AWS)" -msgstr "S3 region (for AWS)" +msgstr "S3 区域(用于 AWS)" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :ex_aws-:s3 > :secret_access_key" msgid "Secret access key" -msgstr "Secret access key" +msgstr "秘密访问密钥" #: lib/pleroma/docs/translator.ex:5 -#, 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." +"日志发送位置,:console - 发送日志到标准输出,{ ExSyslogger, :ex_syslogger } -" +" 发送到系统日志,Quack.Logger - 发送到 Slack。" #: lib/pleroma/docs/translator.ex:5 -#, 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\"" +msgstr "默认:\"$date $time [$level] $levelpad$node $metadata $message\"" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :logger-:console > :level" msgid "Log level" -msgstr "Log level" +msgstr "日志级别" #: lib/pleroma/docs/translator.ex:5 -#, 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\"" +msgstr "默认:\"$date $time [$level] $levelpad$node $metadata $message\"" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "预置到每条消息的字符串,通常设置为应用名称" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :logger-:ex_syslogger > :level" msgid "Log level" -msgstr "Log level" +msgstr "日志级别" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma > :admin_token" msgid "Admin token" -msgstr "Admin token" +msgstr "管理员令牌" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "用户是否可以看到已屏蔽他们的人" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:activitypub > :follow_handshake_timeout" msgid "Following handshake timeout" -msgstr "Following handshake timeout" +msgstr "关注握手超时" #: lib/pleroma/docs/translator.ex:5 -#, 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)" +msgstr "包含在出站联邦中的 Note 回复 URI 数量(`5` 以匹配 Mastodon 硬编码值,`0` " +"禁用输出)" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "是否将屏蔽信息联邦到其它实例" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:activitypub > :sign_object_fetches" msgid "Sign object fetches with HTTP signatures" -msgstr "Sign object fetches with HTTP signatures" +msgstr "使用 HTTP 签名签署对象获取请求" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "屏蔽是否会导致取消关注" #: lib/pleroma/docs/translator.ex:5 -#, 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`" +msgstr "这将用作 MastoFE 上的默认吉祥物。默认值:`:pleroma_fox_tan`" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:assets > :default_user_avatar" msgid "URL of the default user avatar" -msgstr "URL of the default user avatar" +msgstr "默认用户头像的 URL" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "吉祥物的关键字,每个元素必须同时包含 URL 和 mime_type 键" #: lib/pleroma/docs/translator.ex:5 -#, 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`." +"认证表单模板。默认为 `show.html`,对应 `lib/pleroma/web/templates/o_auth/" +"o_auth/show.html.ee`。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:auth > :enforce_oauth_admin_scope_usage" msgid "" @@ -1155,13 +951,11 @@ msgid "" "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." +"OAuth 管理员范围要求开关。启用时,管理操作明确要求 OAuth 令牌中存在管理员 " +"OAuth 范围(客户端应用必须支持管理员范围)。禁用时若令牌无管理员范围,则 " +"`is_admin` 用户标志授予管理操作访问权限。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:auth > :oauth_consumer_strategies" msgid "" "The list of enabled OAuth consumer strategies. By default it's set by " @@ -1170,105 +964,86 @@ msgid "" "\" (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_)." +"启用的 OAuth 消费者策略列表。默认由 OAUTH_CONSUMER_STRATEGIES 环境变量设置。" +"此空格分隔字符串中的每个条目应为\"strategy\"或\"strategy:dependency\"格式(" +"例如 twitter,或当依赖项命名与 " +"ueberauth_不同时使用keycloak:ueberauth_keycloak_strategy)。" #: lib/pleroma/docs/translator.ex:5 -#, 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`." +"OAuth 消费者模式认证表单模板。默认为 `consumer.html`,对应 `lib/pleroma/web/" +"templates/o_auth/o_auth/consumer.html.eex`。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "针对一段时间未活跃用户的\"您错过了什么\"摘要邮件" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:email_notifications > :digest > :active" msgid "Globally enable or disable digest emails" -msgstr "Globally enable or disable digest emails" +msgstr "全局启用或禁用摘要邮件" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:email_notifications > :digest > :" "inactivity_threshold" msgid "Minimum user inactivity threshold" -msgstr "Minimum user inactivity threshold" +msgstr "用户最低不活跃阈值" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "向同一用户发送摘要邮件的最小时间间隔" #: lib/pleroma/docs/translator.ex:5 -#, 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\"." +msgstr "摘要邮件发送时间(crontab 格式)。\"0 0 0\"为默认值," +"表示\"每周日凌晨午夜发送一次”。" #: lib/pleroma/docs/translator.ex:5 -#, 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)." +msgstr "JSON " +"清单的位置。该清单包含可下载的表情包信息。目前只能添加一个清单(不支持数组)" +"。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "表情按组(标签)排序。此为键值对数组,键为组名称,值为位置或位置数组。*可用作" +"通配符。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "当表情包不存在 emoji.txt 时,表情的文件扩展名列表" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "自定义表情文件的位置。*可用作通配符。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:features > :improved_hashtag_timeline" msgid "" "Setting to force toggle / force disable improved hashtags timeline. `:" @@ -1277,117 +1052,98 @@ msgid "" "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)." +"强制切换/禁用改进版话题时间线的设置。`:enabled` 强制从 `hashtags` " +"表获取话题时间线数据。`:disabled` 强制使用对象内嵌话题(速度较慢)。保持 " +"`:auto` 可自动行为(当 HashtagsTableMigrator 完成时[除非被覆盖]自动设置为 " +"`:enabled`)。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:feed > :post_title" msgid "Configure title rendering" -msgstr "Configure title rendering" +msgstr "配置标题渲染" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "标题截断前的最大字符数" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "截断字符串后使用的替换文本" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe" msgid "Settings for Pleroma FE" -msgstr "Settings for Pleroma FE" +msgstr "Pleroma FE 的设置" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "禁用时,如果主题字段为空则自动隐藏" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "背景图片的 URL,除非查看已设置背景的用户个人资料" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "当消息有主题(即内容警告)时,默认折叠" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "启用以 > 字符开头的行的绿色文本" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:frontend_configurations > :pleroma_fe > :" "hideFilteredStatuses" msgid "Hides filtered statuses from timelines" -msgstr "Hides filtered statuses from timelines" +msgstr "从时间线中隐藏已过滤的状态" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:frontend_configurations > :pleroma_fe > :" "hideMutedPosts" msgid "Hides muted statuses from timelines" -msgstr "Hides muted statuses from timelines" +msgstr "从时间线中隐藏已静音的状态" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:frontend_configurations > :pleroma_fe > :" "hidePostStats" msgid "Hide notices statistics (repeats, favorites, ...)" -msgstr "Hide notices statistics (repeats, favorites, ...)" +msgstr "隐藏通知统计(转推、收藏等)" #: lib/pleroma/docs/translator.ex:5 -#, 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, ...)" +msgstr "隐藏个人资料统计(帖文、每日帖文数、关注者、正在关注等)" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "徽标的 URL,默认为 Pleroma 的徽标" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:frontend_configurations > :pleroma_fe > :" "logoMargin" @@ -1395,13 +1151,10 @@ 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." +msgstr "允许调整徽标边界与导航栏边框之间的垂直边距。目的是让徽标图像没有任何额外的边" +"距,而是在布局中根据需要调整它们。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:frontend_configurations > :pleroma_fe > :" "logoMask" @@ -1409,85 +1162,69 @@ 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." +msgstr "默认情况下,它假设使用的徽标将是单色的,并带有 Alpha " +"通道,以兼容浅色和深色主题。如果您想要彩色徽标,必须禁用 logoMask。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "用于在时间线中隐藏 NSFW 媒体附件的图像 URL" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:frontend_configurations > :pleroma_fe > :" "postContentType" msgid "Default post formatting option" -msgstr "Default post formatting option" +msgstr "默认帖文格式化选项" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "指示用户登录后重定向到的相对 URL" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "指示用户未登录时重定向到的相对 URL" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "默认情况下在回复帖文时复制范围(私密/未列出/公开)" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "在关于页面上启用显示实例功能的面板" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "是否显示实例的自定义面板" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "将侧边栏和面板的对齐方式更改为右侧" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:frontend_configurations > :pleroma_fe > :" "subjectLineBehavior" @@ -1497,269 +1234,214 @@ msgid "" " `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." +"允许更改回复中主题行的默认行为。\n" +" `email`:复制并前置 re:,如电子邮件中,\n" +" `masto`:逐字复制,如 Mastodon 中,\n" +" `noop`:不复制主题。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "使用哪个主题。可用主题在 styles.json 中定义" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:frontends > :admin" msgid "Admin frontend" -msgstr "Admin frontend" +msgstr "管理前端" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "已安装前端的名称。有效配置必须包括“名称”和“引用”值。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "要使用的已安装前端的引用。有效配置必须包括“名称”和“引用”值。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "包含可用前端及其安装参数的映射表。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:frontends > :available > build_dir" msgid "The directory inside the zip file " -msgstr "The directory inside the zip file " +msgstr "zip 文件内的目录 " #: lib/pleroma/docs/translator.ex:5 -#, 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`." +msgstr "包含前端的 zip 文件的 URL,或是通过插入 `ref` 来构建前端的模板。字符串 " +"`${ref}` 将被配置的 `ref` 替换。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "前端的自定义 HTTP 头部" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "前端 git 仓库的 URL" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:frontends > :available > name" msgid "Name of the frontend." -msgstr "Name of the frontend." +msgstr "前端的名称。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "要使用的前端的引用。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "主要前端,默认情况下为所有页面提供服务的那个" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "已安装前端的名称。有效配置必须同时包含 `名称 `和` 引用` 值。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "要使用的已安装前端的引用。有效配置必须同时包含 `名称 `和` 引用` 值。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:http > :adapter" msgid "Adapter specific options" -msgstr "Adapter specific options" +msgstr "适配器特定选项" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:http > :adapter > :ssl_options" msgid "SSL options for HTTP adapter" -msgstr "SSL options for HTTP adapter" +msgstr "HTTP 适配器的 SSL 选项" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "要使用的 TLS 版本列表" #: lib/pleroma/docs/translator.ex:5 -#, 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`." +msgstr "要使用的用户代理。必须是一个字符串或原子 `:default`。默认值为 `:default`。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "托管内容安全策略是否启用" #: lib/pleroma/docs/translator.ex:5 -#, 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\"" +msgstr "要使用的引用策略,可以是\"same-origin\"或\"no-referrer\"" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "将指定的 URL 添加到 CSP 头中的 report-uri 和 report-to 组" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "是否额外发送 Strict-Transport-Security 头部" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "如果发送 Strict-Transport-Security 头部,其最大生存时间" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "要求用户在登录前确认其电子邮件" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "要求用户在登录前必须由管理员手动批准" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "账号字段名称的最大长度。默认值:512。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "账号字段值的最大长度。默认值:2048。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "允许远程实例订阅您实例的所有公开帖文。(重要!)这可能会增加您实例的可见性。" #: lib/pleroma/docs/translator.ex:5 -#, 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)" +msgstr "允许发布的格式的 MIME 类型列表(将转换为 HTML)" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "启用自动将附件链接文本添加到状态中" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "设置为每个新用户应自动关注的(本地)用户的昵称" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "设置为自动关注每个新注册用户的(本地)用户的昵称" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "用户个人资料头像的文件大小限制" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "用户个人资料背景的文件大小限制" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "用户个人资料横幅的文件大小限制" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:instance > :cleanup_attachments" msgid "" "Enable to remove associated attachments when status is removed.\n" @@ -1767,52 +1449,42 @@ msgid "" "Enabling 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" +"启用后在删除状态时移除关联的附件。\n" +"这不会影响重复项和没有状态的附件。\n" +"在较大的实例上删除状态时,启用此选项会增加数据库的负载。\n" #: lib/pleroma/docs/translator.ex:5 -#, 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`" +msgstr "实例的描述。可在节点信息和 `/api/v1/instance` 中查看" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "用于联系实例管理员/版主的电子邮件" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "启用扩展本地昵称格式(允许下划线/破折号)。这将中断与旧软件对这些昵称的联邦互" +"通。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "为外部用户启用关注者/被关注者计数器同步" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:instance > :federating" msgid "Enable federation with other instances" -msgstr "Enable federation with other instances" +msgstr "启用与其它实例的联邦互通" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:instance > :" "federation_incoming_replies_max_depth" @@ -1822,240 +1494,191 @@ msgid "" "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." +"传入联邦时获取回复活动和回复对象的最大深度,以防止获取超长线程时出现内存不足" +"情况。若设为 `nil`,将获取任意深度的线程。若遇到内存崩溃,请降低此值。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "每个外部联邦目标无法访问的超时天数(天),超过后将暂停向其联邦" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "实例缩略图可以是代表您实例的任何图像,某些应用或服务在显示您实例信息时会使用" +"它。" #: lib/pleroma/docs/translator.ex:5 -#, 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)" +msgstr "为管理员启用用户邀请功能(需禁用 `registrations_open`)" #: lib/pleroma/docs/translator.ex:5 -#, 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)" +msgstr "帖文字符限制(计数器包含 CW/主题)" #: lib/pleroma/docs/translator.ex:5 -#, 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`." +msgstr "限制未认证用户仅能搜索本地内容和用户。默认:`:unauthenticated`。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "用户个人资料中自定义字段的最大数量。默认:10。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "置顶状态的最大数量。0 将禁用此功能。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "远程用户个人资料中自定义字段的最大数量。默认:20。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "报告评论的最大长度。默认:1000。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:instance > :multi_factor_authentication" msgid "Multi-factor authentication settings" -msgstr "Multi-factor authentication settings" +msgstr "多因素认证设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:instance > :multi_factor_authentication > :" "backup_codes" msgid "MFA backup codes settings" -msgstr "MFA backup codes settings" +msgstr "MFA 备份代码设置" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "确定备份一次性验证码的长度(字符数)。默认为 16 个字符。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "要生成的备份代码数量。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:instance > :multi_factor_authentication > :" "totp" msgid "TOTP settings" -msgstr "TOTP settings" +msgstr "TOTP 设置" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "确定一次性验证码的长度(字符数)。默认为 6 个字符。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "TOTP 代码的有效期(秒)。默认为 30 秒。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:instance > :name" msgid "Name of the instance" -msgstr "Name of the instance" +msgstr "实例名称" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "通过 Pleroma 发送邮件的信封发件人地址" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "本地投票的投票限制映射" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:instance > :poll_limits > :max_expiration" msgid "Maximum expiration time (in seconds)" -msgstr "Maximum expiration time (in seconds)" +msgstr "最大过期时间(秒)" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "每个选项的最大字符数" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:instance > :poll_limits > :max_options" msgid "Maximum number of options" -msgstr "Maximum number of options" +msgstr "最大选项数量" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:instance > :poll_limits > :min_expiration" msgid "Minimum expiration time (in seconds)" -msgstr "Minimum expiration time (in seconds)" +msgstr "最小过期时间(秒)" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:instance > :profile_directory" msgid "Enable profile directory." -msgstr "Enable profile directory." +msgstr "启用用户目录。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:instance > :registration_reason_length" msgid "Maximum registration reason length. Default: 500." -msgstr "Maximum registration reason length. Default: 500." +msgstr "注册原因的最大长度。默认值:500。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "允许任何人注册。邀请功能需要禁用此设置。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "远程帖文将被丢弃的硬字符限制" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "清理数据库时保留远程帖文的默认天数" #: lib/pleroma/docs/translator.ex:5 -#, 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 " @@ -2063,263 +1686,211 @@ msgid "" "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" +"如果启用,只有帖文开头的提及才会用于在私信中称呼他人。这是为了防止在谈论他人" +"时意外提及(例如“@admin 请留意 @bad_actor”)。默认值:禁用" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "允许通过 API 查看收藏和表情回应。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "跳过过滤损坏的线程。默认值:启用。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:instance > :static_dir" msgid "Instance static directory" -msgstr "Instance static directory" +msgstr "实例静态目录" #: lib/pleroma/docs/translator.ex:5 -#, 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)" +msgstr "上传文件的大小限制(头像、背景、横幅除外)" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "用户简介的最大长度。默认值:5000。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "用户名的最大长度。默认值:100。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "允许/禁止显示和获取实例的网站图标" #: lib/pleroma/docs/translator.ex:5 -#, 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\"" +msgstr "LDAP 基础,例如“dc=example,dc=com”" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:ldap > :enabled" msgid "Enables LDAP authentication" -msgstr "Enables LDAP authentication" +msgstr "启用 LDAP 认证" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:ldap > :host" msgid "LDAP server hostname" -msgstr "LDAP server hostname" +msgstr "LDAP 服务器主机名" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:ldap > :port" msgid "LDAP port, e.g. 389 or 636" -msgstr "LDAP port, e.g. 389 or 636" +msgstr "LDAP 端口,例如 389 或 636" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "启用以使用 SSL,通常意味着端口 636" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:ldap > :sslopts" msgid "Additional SSL options" -msgstr "Additional SSL options" +msgstr "额外的 SSL 选项" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "包含 PEM 编码的 cacerts 的文件路径" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:ldap > :sslopts > :verify" msgid "Type of cert verification" -msgstr "Type of cert verification" +msgstr "证书验证类型" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "启用以使用 STARTTLS,通常意味着端口 389" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:ldap > :tlsopts" msgid "Additional TLS options" -msgstr "Additional TLS options" +msgstr "额外的 TLS 选项" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "包含 PEM 编码的 cacerts 的文件路径" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:ldap > :tlsopts > :verify" msgid "Type of cert verification" -msgstr "Type of cert verification" +msgstr "证书验证类型" #: lib/pleroma/docs/translator.ex:5 -#, 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\"" +msgstr "用于认证用户的 LDAP 属性名称,例如当为“cn”时,过滤器将为“cn=username,base”" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:majic_pool > :size" msgid "Number of majic workers to start." -msgstr "Number of majic workers to start." +msgstr "启动的 majic 工作进程数量。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:manifest > :icons" msgid "Describe the icons of the app" -msgstr "Describe the icons of the app" +msgstr "描述应用程序的图标" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "模块名称被缩短(移除前导的 `Pleroma.HTML.` " +"部分),但在添加自定义模块时需要使用全名。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "启用远程媒体预览代理到实例的代理。需要启用媒体代理。" #: lib/pleroma/docs/translator.ex:5 -#, 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)." +msgstr "输出的质量。范围从 0(最低质量)到 100(最高质量)。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "执行预览的最小内容长度(字节)。小于此大小的媒体将不生成缩略图直接提供。" #: lib/pleroma/docs/translator.ex:5 -#, 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)." +msgstr "图像预览缩略图的最大高度(视频预览始终具有原始尺寸)。" #: lib/pleroma/docs/translator.ex:5 -#, 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)." +msgstr "图像预览缩略图的最大宽度(视频预览始终具有原始尺寸)。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "访问用户上传文件的基础 URL。当您希望通过另一个主机/CDN " +"前端代理媒体文件时很有用。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "启用通过实例代理远程媒体的功能" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:media_proxy > :invalidation > :enabled" msgid "Enables media cache object invalidation." -msgstr "Enables media cache object invalidation." +msgstr "启用媒体缓存对象失效功能。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "用于从缓存中清除对象的模块。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:media_proxy > :proxy_opts" msgid "Internal Pleroma.ReverseProxy settings" -msgstr "Internal Pleroma.ReverseProxy settings" +msgstr "Pleroma.ReverseProxy 内部设置" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "允许通过 Pleroma 媒体代理缓存的最大文件大小(字节)。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "GET 请求到远程 URI 的超时时间(毫秒)。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:media_proxy > :proxy_opts > :" "redirect_on_failure" @@ -2335,122 +1906,95 @@ msgid "" "\n" "IP 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" +"遇到 HTTP 错误时将客户端重定向到源服务器。\n" "\n" -"Note that files larger than Max Body Length will trigger an error. (e.g., " -"Peertube videos)\n" +"注意:大于最大正文长度的文件将触发错误(例如 PeerTube 视频)\n" "\n" "\n" -"**WARNING:** This setting will allow larger files to be accessed, but " -"exposes the\n" +"**警告:** 此设置允许访问更大的文件,但会绕过媒体代理,将用户的 IP " +"地址暴露给其它服务器。\n" "\n" -"IP addresses of your users to the other servers, bypassing the MediaProxy.\n" +"用户的 IP 地址传递到其它服务器,绕过 MediaProxy。\n" #: lib/pleroma/docs/translator.ex:5 -#, 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)." +msgstr "自定义 Elixir 模块(如 MRF 策略)的路径。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +"启用的 MRF 策略列表。模块名称已缩短(移除前导的 " +"`Pleroma.Web.ActivityPub.MRF.` 部分),但添加自定义模块时需使用完整名称。" #: lib/pleroma/docs/translator.ex:5 -#, 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)" +msgstr "公开消息重写设施设置内容(通过节点信息)" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "从 MRF " +"透明度中排除特定实例名称。排除功能的使用将在节点信息中以布尔值披露。您还可以" +"提供排除这些实例名称的原因。实例和原因不会被公开披露。" #: lib/pleroma/docs/translator.ex:5 -#, 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)" +msgstr "所有本地活动的默认全局过期时间(天)" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "用于关注新发现用户的机器人账号名称。" #: lib/pleroma/docs/translator.ex:5 -#, 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)." +msgstr "导致消息从联合时间线(即未列出)中移除的主题标签列表。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "导致消息被拒绝的主题标签列表。" #: lib/pleroma/docs/translator.ex:5 -#, 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)" +msgstr "导致消息被标记为敏感内容(即 NSFW/R-18)的主题标签列表" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "提及用户数量达到此阈值后,消息将从时间线移除并禁用通知。设为 0 可禁用。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "提及用户数量达到此阈值后,消息将被拒绝。设为 0 可禁用。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:mrf_keyword > :federated_timeline_removal" msgid "" @@ -2460,14 +2004,12 @@ msgid "" " 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" "\n" -" Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex." -"html) in the format of `~r/PATTERN/`.\n" +" 每个模式可以是字符串或[Regex](https://hexdocs.pm/elixir/Regex.html)格式的 " +"`~r/模式/`。\n" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:mrf_keyword > :reject" msgid "" " A list of patterns which result in message being rejected.\n" @@ -2475,13 +2017,12 @@ msgid "" " 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" "\n" -" Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex." -"html) in the format of `~r/PATTERN/`.\n" +" 每个模式可以是一个字符串或格式为 `~r/模式/` 的[正则表达式](https://" +"hexdocs.pm/elixir/Regex.html)。\n" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:mrf_keyword > :replace" msgid "" " **Pattern**: a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in " @@ -2489,19 +2030,17 @@ msgid "" "\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" +" **模式**:一个字符串或格式为 `~r/模式/` 的[正则表达式](https://hexdocs.pm/" +"elixir/Regex.html)。\n" "\n" -" **Replacement**: a string. Leaving the field empty is permitted.\n" +" **替换**:一个字符串。允许将该字段留空。\n" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "一组提及它们任何帖文都将被丢弃的参与者列表" #: lib/pleroma/docs/translator.ex:5 -#, 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 " @@ -2509,121 +2048,95 @@ msgid "" "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" +"应用于帖文的操作列表。`:delist` 从公共时间线中移除帖文;`:strip_followers` " +"从 ActivityPub " +"收件人列表中移除关注者,确保它们不会被递送到主页时间线;`:reject` " +"完全拒绝消息" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "采取操作前帖文所需的存在时间(以秒为单位)。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:mrf_rejectnonpublic > :allow_direct" msgid "Whether to allow direct messages" -msgstr "Whether to allow direct messages" +msgstr "是否允许直接消息" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:mrf_rejectnonpublic > :allow_followersonly" msgid "Whether to allow followers-only posts" -msgstr "Whether to allow followers-only posts" +msgstr "是否允许仅关注者可见的帖文" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "仅接受来自这些实例的活动(删除除外)的实例列表及原因" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "从中移除头像的实例列表及原因" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "从中移除横幅的实例列表及原因" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "从联合时间线(即整个已知网络时间线)中移除的实例列表及原因" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "强制来自给定实例的帖文仅对关注者可见及原因" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "从中将所有媒体标记为 NSFW(敏感)的实例列表及原因" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "从中移除媒体附件的实例列表及原因" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "拒绝来自这些实例的活动(删除除外)的实例列表及原因" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "拒绝来自这些实例的删除操作的实例列表及原因" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "拒绝来自这些实例的举报的实例列表及原因" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "从中窃取表情符号的主机列表" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:mrf_steal_emoji > :rejected_shortcodes" msgid "" @@ -2632,71 +2145,55 @@ msgid "" " 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" "\n" -" Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex." -"html) in the format of `~r/PATTERN/`.\n" +" 每个模式可以是一个字符串或格式为 `~r/模式/` 的[正则表达式](https://" +"hexdocs.pm/elixir/Regex.html)。\n" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "文件大小限制(以字节为单位),在表情符号保存到磁盘前检查" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "对参与者字段匹配一系列正则表达式" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "接受的 ActivityStreams 术语列表。如果为空,则接受所有支持的消息。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "要拒绝的 ActivityStreams 术语列表。如果为空,则不拒绝任何消息。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "启用后台任务清理过期的 OAuth 令牌。默认:禁用。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "在获取访问令牌时保留旧刷新令牌或生成新刷新令牌" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "访问令牌的生命周期(秒)" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:populate_hashtags_table > :" "fault_rate_allowance" @@ -2704,630 +2201,513 @@ 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." +msgstr "迁移中允许的最大失败对象比率。取值范围从 0.0(不容忍任何错误)到 " +"1.0(即使所有记录的标签转移失败也会启用该功能)。" #: lib/pleroma/docs/translator.ex:5 -#, 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)." +msgstr "每批记录处理之间的休眠间隔(毫秒),以减轻系统负载(默认为 " +"0,大多数实例应保持默认值)。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "用于限制同一 IP 地址注册用户账号" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "用于认证创建/密码检查/用户存在检查请求" #: lib/pleroma/docs/translator.ex:5 -#, 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)" +msgstr "用于针对特定用户的关系操作(关注、取消关注)" #: lib/pleroma/docs/translator.ex:5 -#, 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)" +msgstr "用于所有用户关系操作(关注、取消关注)" #: lib/pleroma/docs/translator.ex:5 -#, 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.)" +msgstr "用于搜索请求(账号和状态搜索等)" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "用于同一用户对同一状态的收藏/取消收藏或转发/取消转发操作" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "用于任何状态的创建/删除/收藏/取消收藏/转发/取消转发操作" #: lib/pleroma/docs/translator.ex:5 -#, 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)" +msgstr "用于时间线请求(每个时间线有自己的限制器)" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:restrict_unauthenticated > :profiles" msgid "Settings for user profiles." -msgstr "Settings for user profiles." +msgstr "用户配置文件设置。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:restrict_unauthenticated > :timelines" msgid "Settings for public and federated timelines." -msgstr "Settings for public and federated timelines." +msgstr "公共和联合时间线设置。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:rich_media > :enabled" msgid "Enables RichMedia parsing of URLs" -msgstr "Enables RichMedia parsing of URLs" +msgstr "启用 URL 的富媒体解析" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "请求失败后的退避时间(毫秒),在此期间不会重试请求。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "元数据解析器将忽略的主机列表" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "元数据解析将忽略的 TLD(顶级域名)列表" #: lib/pleroma/docs/translator.ex:5 -#, 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." +"富媒体解析器列表。模块名称已缩短(移除前导 `Pleroma.Web.RichMedia.Parsers.` " +"部分),但添加自定义模块时需使用全名。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +"富媒体 TTL 设置器列表。模块名称已缩短(移除前导 " +"`Pleroma.Web.RichMedia.Parser.` 部分),但添加自定义模块时需使用全名。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "启用静态 HTML 渲染。默认:禁用。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "如果池为空时创建的最大工作进程数" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:streamer > :workers" msgid "Number of workers to send notifications" -msgstr "Number of workers to send notifications" +msgstr "发送通知的工作进程数量" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "被视为有效 URL 的方案部分列表" #: lib/pleroma/docs/translator.ex:5 -#, 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)." +msgstr "Activitypub 路由(问题活动除外)。默认值:`nil`(无过期时间)。" #: lib/pleroma/docs/translator.ex:5 -#, 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)." +msgstr "Activitypub 路由(问题活动)。默认值:`30_000`(30 秒)。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "启用向新注册用户发送私信" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "将发送给新注册用户的消息" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "发送欢迎消息的本地用户的昵称" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "启用向新注册用户发送电子邮件" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "欢迎邮件的 HTML 内容。可使用包含 user 和 instance_name 变量的 EEX 模板。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "用于发送欢迎邮件的电子邮件地址和/或昵称。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "欢迎邮件的主题。可使用包含 user 和 instance_name 变量的 EEX 模板。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "欢迎邮件的文本内容。可使用包含 user 和 instance_name 变量的 EEX 模板。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "每个 `Oban` 队列失败作业的最大重试次数" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-ConcurrentLimiter > Pleroma.Web.ActivityPub." "MRF.MediaProxyWarmingPolicy" msgid "Concurrent limits configuration for MediaProxyWarmingPolicy." -msgstr "Concurrent limits configuration for MediaProxyWarmingPolicy." +msgstr "MediaProxyWarmingPolicy 的并发限制配置。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-ConcurrentLimiter > Pleroma.Web.ActivityPub." "MRF.MediaProxyWarmingPolicy > :max_running" msgid "Max running concurrently jobs." -msgstr "Max running concurrently jobs." +msgstr "最大并发运行作业数。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-ConcurrentLimiter > Pleroma.Web.ActivityPub." "MRF.MediaProxyWarmingPolicy > :max_waiting" msgid "Max waiting jobs." -msgstr "Max waiting jobs." +msgstr "最大等待作业数。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "获取活动富媒体的并发限制配置。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-ConcurrentLimiter > Pleroma.Web.RichMedia." "Helpers > :max_running" msgid "Max running concurrently jobs." -msgstr "Max running concurrently jobs." +msgstr "最大并发运行作业数。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-ConcurrentLimiter > Pleroma.Web.RichMedia." "Helpers > :max_waiting" msgid "Max waiting jobs." -msgstr "Max waiting jobs." +msgstr "最大等待作业数。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Oban > :crontab" msgid "Settings for cron background jobs" -msgstr "Settings for cron background jobs" +msgstr "定时后台作业的设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Oban > :log" msgid "Logs verbose mode" -msgstr "Logs verbose mode" +msgstr "日志详细模式" #: lib/pleroma/docs/translator.ex:5 -#, 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)" +msgstr "后台作业队列(键:队列,值:最大并发作业数)" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Oban > :queues > :activity_expiration" msgid "Activity expiration queue" -msgstr "Activity expiration queue" +msgstr "活动过期队列" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Oban > :queues > :attachments_cleanup" msgid "Attachment deletion queue" -msgstr "Attachment deletion queue" +msgstr "附件清理队列" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Oban > :queues > :background" msgid "Background queue" -msgstr "Background queue" +msgstr "后台队列" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Oban > :queues > :backup" msgid "Backup queue" -msgstr "Backup queue" +msgstr "备份队列" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Oban > :queues > :federator_incoming" msgid "Incoming federation queue" -msgstr "Incoming federation queue" +msgstr "入站联邦队列" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Oban > :queues > :federator_outgoing" msgid "Outgoing federation queue" -msgstr "Outgoing federation queue" +msgstr "出站联邦队列" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "邮件发送队列,参见 Pleroma.Emails.Mailer" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Oban > :queues > :scheduled_activities" msgid "Scheduled activities queue, see Pleroma.ScheduledActivities" -msgstr "Scheduled activities queue, see Pleroma.ScheduledActivities" +msgstr "定时活动队列,参见 Pleroma.ScheduledActivities" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Oban > :queues > :transmogrifier" msgid "Transmogrifier queue" -msgstr "Transmogrifier queue" +msgstr "转换队列" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Oban > :queues > :web_push" msgid "Web push notifications queue" -msgstr "Web push notifications queue" +msgstr "Web 推送通知队列" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "是否应在注册时显示验证码" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "用于验证码的方法/服务" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "验证码有效的秒数" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Pleroma.Captcha.Kocaptcha > :endpoint" msgid "The kocaptcha endpoint to use" -msgstr "The kocaptcha endpoint to use" +msgstr "要使用的 kocaptcha 端点" #: lib/pleroma/docs/translator.ex:5 -#, 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)" +"[Swoosh 文档](https://hexdocs.pm/swoosh/Swoosh.html#module-" +"adapters)中列出的邮件适配器之一" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:" "auth" msgid "SMTP AUTH enforcement mode" -msgstr "SMTP AUTH enforcement mode" +msgstr "SMTP AUTH 强制模式" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:" "password" msgid "SMTP AUTH password" -msgstr "SMTP AUTH password" +msgstr "SMTP AUTH 密码" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:" "port" msgid "SMTP port" -msgstr "SMTP port" +msgstr "SMTP 端口" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:" "relay" msgid "Hostname or IP address" -msgstr "Hostname or IP address" +msgstr "主机名或 IP 地址" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "SMTP 临时(4xx)错误重试次数" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "使用隐式 SSL/TLS。例如端口 465" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "显式 TLS(STARTTLS)强制模式" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:" "username" msgid "SMTP AUTH username" -msgstr "SMTP AUTH username" +msgstr "SMTP AUTH 用户名" #: lib/pleroma/docs/translator.ex:5 -#, 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`" +msgstr "当为 `true` 时启用新用户管理员摘要邮件" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "自定义徽标路径。设置为 `nil` 以使用默认 Pleroma 徽标。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "用于邮件模板颜色设置的映射表。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "指定要添加到生成链接中的类。禁用可清除。" #: lib/pleroma/docs/translator.ex:5 -#, 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.)" +msgstr "链接使用较少见协议(magnet、ipfs、irc 等)的 URL" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "链接 URL 将在新窗口/标签页中打开。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "覆盖 rel 属性。禁用可清除。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Pleroma.Formatter > :strip_prefix" msgid "Strip the scheme prefix." -msgstr "Strip the scheme prefix." +msgstr "去除协议前缀。" #: lib/pleroma/docs/translator.ex:5 -#, 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 `...`" +msgstr "设置为数字以截断长于该数字的 URL。截断的 URL 将以 `...` 结尾" #: lib/pleroma/docs/translator.ex:5 -#, 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)" +"设置为 false 可禁用 URL/电子邮件的 TLD 验证。可设置为:no_scheme " +"以仅验证无协议 URL 的 TLD(例如 `example.com` " +"将被验证,但`http://example.loki` 不会)" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "用户单日允许创建的定时活动数量。默认值:25。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "是否将定时活动发送到作业队列执行" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "用户允许创建的定时活动总数。默认值:300。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "上传文件的基础 URL。如果使用 CDN 或将附件托管在不同域名下则需要设置。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "设置显示文件名的最大长度。0=无限制。默认值:30" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "上传过滤器模块列表。模块名称已缩短(去除前导 `Pleroma.Upload.Filter.` " +"部分),但添加自定义模块时需使用全名。" #: lib/pleroma/docs/translator.ex:5 -#, 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`." +"如果启用,将在上传 URL 中添加名称参数。例如 `https://instance.tld/media/" +"imagehash.png?name=realname.png`。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "用于上传的模块" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-Pleroma.Upload.Filter.AnonymizeFilename > :" "text" @@ -3336,56 +2716,46 @@ msgid "" "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}." +"替换链接中文件名的文本。如果未设置,将使用{random}.extension。您可以使用{exte" +"nsion}获取原始文件扩展名,例如 custom-file-name.{extension}。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +"mogrify 命令的操作列表。可以添加自定义字符串设置。例如`auto-orient, strip, {" +"\"resize\", \"3840x1080>\"}`值将被解析为有效的设置列表。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "用户上传文件的保存路径" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Pleroma.Uploaders.S3 > :bucket" msgid "S3 bucket" -msgstr "S3 bucket" +msgstr "S3 存储桶" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-Pleroma.Uploaders.S3 > :bucket_namespace" msgid "S3 bucket namespace" -msgstr "S3 bucket namespace" +msgstr "S3 存储桶命名空间" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "启用流式上传,启用时文件将在读取过程中分块发送到服务器。某些提供商可能不支持" +"此功能,如果遇到上传问题请尝试禁用。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-Pleroma.Uploaders.S3 > :truncated_namespace" msgid "" @@ -3393,1903 +2763,1597 @@ msgid "" "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." +"如果使用 S3 兼容服务(如 Digital Ocean Spaces 或 CDN),请设置文件夹名称或\"" +"\"等。例如使用 CDN 到 S3 虚拟主机格式时,设置\"\"。此时请在 Upload base_url " +"中写入 CDN 的 CNAME。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "用户禁止注册的邮箱域名列表。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "用户禁止注册的昵称列表。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "限制用户每 N 天最多导出一次" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "N 天后删除备份存档" #: lib/pleroma/docs/translator.ex:5 -#, 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)" +msgstr "启用严格输入验证(开发环境有用,生产环境不推荐)" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http > :" "headers" msgid "HTTP headers of request" -msgstr "HTTP headers of request" +msgstr "请求的 HTTP 头部" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "请求的 HTTP 方法。默认值::purge" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http > :" "options" msgid "Request options" -msgstr "Request options" +msgstr "请求选项" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "可执行脚本的路径,用于清除缓存项。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "可选的 URL 格式预处理。仅适用于 Apache 的 htcacheclean。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Pleroma.Web.Metadata > :providers" msgid "List of metadata providers to enable" -msgstr "List of metadata providers to enable" +msgstr "要启用的元数据提供程序列表" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "启用时,NSFW 附件将在预览中显示" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "启用/禁用该插件。默认值:禁用。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr " 用于获取真实客户端 IP 的 HTTP 头部字符串列表。默认值:`" +"[\"x-forwarded-for\"]`。\n" #: lib/pleroma/docs/translator.ex:5 -#, 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." +"上游代理 IP 子网的 CIDR 表示法列表,我们将从中解析 `headers` 的内容。默认为 " +"`[]`。没有位掩码的 IPv4 条目将被视为/32,IPv6 为/128。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +" 保留 IP 子网的 CIDR 表示法列表,如果在 `headers` 中找到则应忽略。默认为`[" +"\"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 -#, fuzzy msgctxt "config description at :pleroma-Pleroma.Web.Preload > :providers" msgid "List of preload providers to enable" -msgstr "List of preload providers to enable" +msgstr "要启用的预加载提供程序列表" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-Pleroma.Workers.PurgeExpiredActivity > :" "enabled" msgid "Enables expired activities addition & deletion" -msgstr "Enables expired activities addition & deletion" +msgstr "启用过期活动的添加和删除" #: lib/pleroma/docs/translator.ex:5 -#, 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)" +msgstr "临时活动的最短生命周期(秒)" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :web_push_encryption-:vapid_details > :private_key" msgid "VAPID private key" -msgstr "VAPID private key" +msgstr "VAPID 私钥" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :web_push_encryption-:vapid_details > :public_key" msgid "VAPID public key" -msgstr "VAPID public key" +msgstr "VAPID 公钥" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "管理联系人的 mailto 链接。最好使用实例管理团队的群组邮箱,而非个人邮箱地址。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :cors_plug > :credentials" msgid "Credentials" -msgstr "Credentials" +msgstr "凭据" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :cors_plug > :expose" msgid "Expose" -msgstr "Expose" +msgstr "公开" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :cors_plug > :headers" msgid "Headers" -msgstr "Headers" +msgstr "头部" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :cors_plug > :max_age" msgid "Max age" -msgstr "Max age" +msgstr "最大年龄" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :cors_plug > :methods" msgid "Methods" -msgstr "Methods" +msgstr "方法" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :ex_aws-:s3 > :access_key_id" msgid "Access key" -msgstr "Access key" +msgstr "访问密钥" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :ex_aws-:s3 > :host" msgid "Host" -msgstr "Host" +msgstr "主机" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :ex_aws-:s3 > :region" msgid "Region" -msgstr "Region" +msgstr "区域" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :ex_aws-:s3 > :secret_access_key" msgid "Secret access key" -msgstr "Secret access key" +msgstr "秘密访问密钥" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :logger > :backends" msgid "Backends" -msgstr "Backends" +msgstr "后端" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :logger-:console > :format" msgid "Format" -msgstr "Format" +msgstr "格式" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :logger-:console > :level" msgid "Level" -msgstr "Level" +msgstr "级别" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :logger-:console > :metadata" msgid "Metadata" -msgstr "Metadata" +msgstr "元数据" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :logger-:ex_syslogger > :format" msgid "Format" -msgstr "Format" +msgstr "格式" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :logger-:ex_syslogger > :ident" msgid "Ident" -msgstr "Ident" +msgstr "标识" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :logger-:ex_syslogger > :level" msgid "Level" -msgstr "Level" +msgstr "级别" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :logger-:ex_syslogger > :metadata" msgid "Metadata" -msgstr "Metadata" +msgstr "元数据" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :mime > :types" msgid "Types" -msgstr "Types" +msgstr "类型" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :mime > :types > application/activity+json" msgid "\"application/activity+json\"" msgstr "\"application/activity+json\"" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :mime > :types > application/jrd+json" msgid "\"application/jrd+json\"" msgstr "\"application/jrd+json\"" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :mime > :types > application/ld+json" msgid "\"application/ld+json\"" msgstr "\"application/ld+json\"" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :mime > :types > application/xml" msgid "\"application/xml\"" msgstr "\"application/xml\"" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :mime > :types > application/xrd+xml" msgid "\"application/xrd+xml\"" msgstr "\"application/xrd+xml\"" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma > :admin_token" msgid "Admin token" -msgstr "Admin token" +msgstr "管理员令牌" #: lib/pleroma/docs/translator.ex:5 -#, 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 -#, fuzzy msgctxt "config label at :pleroma-:activitypub > :blockers_visible" msgid "Blockers visible" -msgstr "Blockers visible" +msgstr "屏蔽者可见" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:activitypub > :follow_handshake_timeout" msgid "Follow handshake timeout" -msgstr "Follow handshake timeout" +msgstr "关注握手超时" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:activitypub > :note_replies_output_limit" msgid "Note replies output limit" -msgstr "Note replies output limit" +msgstr "回复输出限制" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:activitypub > :outgoing_blocks" msgid "Outgoing blocks" -msgstr "Outgoing blocks" +msgstr "外发屏蔽" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:activitypub > :sign_object_fetches" msgid "Sign object fetches" -msgstr "Sign object fetches" +msgstr "签名对象获取" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:activitypub > :unfollow_blocked" msgid "Unfollow blocked" -msgstr "Unfollow blocked" +msgstr "取消关注被屏蔽用户" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:assets > :default_mascot" msgid "Default mascot" -msgstr "Default mascot" +msgstr "默认吉祥物" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:assets > :default_user_avatar" msgid "Default user avatar" -msgstr "Default user avatar" +msgstr "默认用户头像" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:assets > :mascots" msgid "Mascots" -msgstr "Mascots" +msgstr "吉祥物" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:auth > :auth_template" msgid "Auth template" -msgstr "Auth template" +msgstr "认证模板" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:auth > :enforce_oauth_admin_scope_usage" msgid "Enforce OAuth admin scope usage" -msgstr "Enforce OAuth admin scope usage" +msgstr "强制使用 OAuth 管理员范围" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:auth > :oauth_consumer_strategies" msgid "OAuth consumer strategies" -msgstr "OAuth consumer strategies" +msgstr "OAuth 消费者策略" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:auth > :oauth_consumer_template" msgid "OAuth consumer template" -msgstr "OAuth consumer template" +msgstr "OAuth 消费者模板" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:email_notifications > :digest" msgid "Digest" -msgstr "Digest" +msgstr "摘要" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:email_notifications > :digest > :active" msgid "Enabled" -msgstr "Enabled" +msgstr "已启用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:email_notifications > :digest > :" "inactivity_threshold" msgid "Inactivity threshold" -msgstr "Inactivity threshold" +msgstr "不活跃阈值" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:email_notifications > :digest > :interval" msgid "Interval" -msgstr "Interval" +msgstr "间隔" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:email_notifications > :digest > :schedule" msgid "Schedule" -msgstr "Schedule" +msgstr "计划" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:emoji > :default_manifest" msgid "Default manifest" -msgstr "Default manifest" +msgstr "默认清单" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:emoji > :groups" msgid "Groups" -msgstr "Groups" +msgstr "分组" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:emoji > :pack_extensions" msgid "Pack extensions" -msgstr "Pack extensions" +msgstr "扩展包" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "共享包缓存秒数/文件" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:emoji > :shortcode_globs" msgid "Shortcode globs" -msgstr "Shortcode globs" +msgstr "短代码通配符" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:features > :improved_hashtag_timeline" msgid "Improved hashtag timeline" -msgstr "Improved hashtag timeline" +msgstr "改进的标签时间线" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:feed > :post_title" msgid "Post title" -msgstr "Post title" +msgstr "帖文标题" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:feed > :post_title > :max_length" msgid "Max length" -msgstr "Max length" +msgstr "最大长度" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:feed > :post_title > :omission" msgid "Omission" -msgstr "Omission" +msgstr "省略符" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe" msgid "Pleroma FE" msgstr "Pleroma FE" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :" "alwaysShowSubjectInput" msgid "Always show subject input" -msgstr "Always show subject input" +msgstr "始终显示主题输入框" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :background" msgid "Background" -msgstr "Background" +msgstr "背景" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :" "collapseMessageWithSubject" msgid "Collapse message with subject" -msgstr "Collapse message with subject" +msgstr "折叠带主题的消息" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :greentext" msgid "Greentext" -msgstr "Greentext" +msgstr "绿色文本" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :" "hideFilteredStatuses" msgid "Hide Filtered Statuses" -msgstr "Hide Filtered Statuses" +msgstr "隐藏已过滤状态" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :" "hideMutedPosts" msgid "Hide Muted Posts" -msgstr "Hide Muted Posts" +msgstr "隐藏已静音帖文" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :" "hidePostStats" msgid "Hide post stats" -msgstr "Hide post stats" +msgstr "隐藏帖文统计" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :" "hideUserStats" msgid "Hide user stats" -msgstr "Hide user stats" +msgstr "隐藏用户统计" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :logo" msgid "Logo" -msgstr "Logo" +msgstr "徽标" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :logoMargin" msgid "Logo margin" -msgstr "Logo margin" +msgstr "徽标边距" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :logoMask" msgid "Logo mask" -msgstr "Logo mask" +msgstr "徽标遮罩" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :" "nsfwCensorImage" msgid "NSFW Censor Image" -msgstr "NSFW Censor Image" +msgstr "NSFW 审查图像" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :" "postContentType" msgid "Post Content Type" -msgstr "Post Content Type" +msgstr "帖文内容类型" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :" "redirectRootLogin" msgid "Redirect root login" -msgstr "Redirect root login" +msgstr "重定向根登录" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :" "redirectRootNoLogin" msgid "Redirect root no login" -msgstr "Redirect root no login" +msgstr "重定向根未登录" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :scopeCopy" msgid "Scope copy" -msgstr "Scope copy" +msgstr "范围复制" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :" "showFeaturesPanel" msgid "Show instance features panel" -msgstr "Show instance features panel" +msgstr "显示实例功能面板" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :" "showInstanceSpecificPanel" msgid "Show instance specific panel" -msgstr "Show instance specific panel" +msgstr "显示实例特定面板" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :" "sidebarRight" msgid "Sidebar on Right" -msgstr "Sidebar on Right" +msgstr "右侧边栏" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :" "subjectLineBehavior" msgid "Subject line behavior" -msgstr "Subject line behavior" +msgstr "主题行行为" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :theme" msgid "Theme" -msgstr "Theme" +msgstr "主题" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:frontends > :admin" msgid "Admin" -msgstr "Admin" +msgstr "管理" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:frontends > :admin > name" msgid "Name" -msgstr "Name" +msgstr "名称" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:frontends > :admin > ref" msgid "Reference" -msgstr "Reference" +msgstr "引用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:frontends > :available" msgid "Available" -msgstr "Available" +msgstr "可用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:frontends > :available > build_dir" msgid "Build directory" -msgstr "Build directory" +msgstr "构建目录" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:frontends > :available > build_url" msgid "Build URL" -msgstr "Build URL" +msgstr "构建 URL" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontends > :available > custom-http-headers" msgid "Custom HTTP headers" -msgstr "Custom HTTP headers" +msgstr "自定义 HTTP 头" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:frontends > :available > git" msgid "Git Repository URL" -msgstr "Git Repository URL" +msgstr "Git 仓库 URL" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:frontends > :available > name" msgid "Name" -msgstr "Name" +msgstr "名称" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:frontends > :available > ref" msgid "Reference" -msgstr "Reference" +msgstr "引用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:frontends > :primary" msgid "Primary" -msgstr "Primary" +msgstr "主要" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:frontends > :primary > name" msgid "Name" -msgstr "Name" +msgstr "名称" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:frontends > :primary > ref" msgid "Reference" -msgstr "Reference" +msgstr "引用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:http > :adapter" msgid "Adapter" -msgstr "Adapter" +msgstr "适配器" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:http > :adapter > :ssl_options" msgid "SSL Options" -msgstr "SSL Options" +msgstr "SSL 选项" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:http > :adapter > :ssl_options > :versions" msgid "Versions" -msgstr "Versions" +msgstr "版本" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:http > :proxy_url" msgid "Proxy URL" -msgstr "Proxy URL" +msgstr "代理 URL" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:http > :user_agent" msgid "User agent" -msgstr "User agent" +msgstr "用户代理" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:http_security > :enabled" msgid "Enabled" -msgstr "Enabled" +msgstr "启用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:http_security > :referrer_policy" msgid "Referrer policy" -msgstr "Referrer policy" +msgstr "引用策略" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:http_security > :report_uri" msgid "Report URI" -msgstr "Report URI" +msgstr "报告 URI" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:http_security > :sts" msgid "STS" msgstr "STS" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:http_security > :sts_max_age" msgid "STS max age" -msgstr "STS max age" +msgstr "STS 最大年龄" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :account_activation_required" msgid "Account activation required" -msgstr "Account activation required" +msgstr "需要账号激活" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :account_approval_required" msgid "Account approval required" -msgstr "Account approval required" +msgstr "需要账号批准" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :account_field_name_length" msgid "Account field name length" -msgstr "Account field name length" +msgstr "账号字段名称长度" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :account_field_value_length" msgid "Account field value length" -msgstr "Account field value length" +msgstr "账号字段值长度" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :allow_relay" msgid "Allow relay" -msgstr "Allow relay" +msgstr "允许中继" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :allowed_post_formats" msgid "Allowed post formats" -msgstr "Allowed post formats" +msgstr "允许的帖文格式" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :attachment_links" msgid "Attachment links" -msgstr "Attachment links" +msgstr "附件链接" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :autofollowed_nicknames" msgid "Autofollowed nicknames" -msgstr "Autofollowed nicknames" +msgstr "自动关注昵称" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :autofollowing_nicknames" msgid "Autofollowing nicknames" -msgstr "Autofollowing nicknames" +msgstr "自动关注中昵称" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :avatar_upload_limit" msgid "Avatar upload limit" -msgstr "Avatar upload limit" +msgstr "头像上传限制" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :background_upload_limit" msgid "Background upload limit" -msgstr "Background upload limit" +msgstr "背景上传限制" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :banner_upload_limit" msgid "Banner upload limit" -msgstr "Banner upload limit" +msgstr "横幅上传限制" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :cleanup_attachments" msgid "Cleanup attachments" -msgstr "Cleanup attachments" +msgstr "清理附件" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :description" msgid "Description" -msgstr "Description" +msgstr "描述" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :email" msgid "Admin Email Address" -msgstr "Admin Email Address" +msgstr "管理员邮箱地址" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :extended_nickname_format" msgid "Extended nickname format" -msgstr "Extended nickname format" +msgstr "扩展昵称格式" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :external_user_synchronization" msgid "External user synchronization" -msgstr "External user synchronization" +msgstr "外部用户同步" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :federating" msgid "Federating" -msgstr "Federating" +msgstr "联邦" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:instance > :federation_incoming_replies_max_depth" msgid "Fed. incoming replies max depth" -msgstr "Fed. incoming replies max depth" +msgstr "联邦传入回复最大深度" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:instance > :federation_reachability_timeout_days" msgid "Fed. reachability timeout days" -msgstr "Fed. reachability timeout days" +msgstr "联邦可达性超时天数" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :healthcheck" msgid "Healthcheck" -msgstr "Healthcheck" +msgstr "健康检查" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :instance_thumbnail" msgid "Instance thumbnail" -msgstr "Instance thumbnail" +msgstr "实例缩略图" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :invites_enabled" msgid "Invites enabled" -msgstr "Invites enabled" +msgstr "启用邀请" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :limit" msgid "Limit" -msgstr "Limit" +msgstr "限制" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :limit_to_local_content" msgid "Limit to local content" -msgstr "Limit to local content" +msgstr "限制为本地内容" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :max_account_fields" msgid "Max account fields" -msgstr "Max account fields" +msgstr "最大账号字段数" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :max_pinned_statuses" msgid "Max pinned statuses" -msgstr "Max pinned statuses" +msgstr "最大置顶状态数" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :max_remote_account_fields" msgid "Max remote account fields" -msgstr "Max remote account fields" +msgstr "最大远程账号字段数" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :max_report_comment_size" msgid "Max report comment size" -msgstr "Max report comment size" +msgstr "最大报告评论大小" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :multi_factor_authentication" msgid "Multi factor authentication" -msgstr "Multi factor authentication" +msgstr "多因素认证" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:instance > :multi_factor_authentication > :" "backup_codes" msgid "Backup codes" -msgstr "Backup codes" +msgstr "备份代码" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:instance > :multi_factor_authentication > :" "backup_codes > :length" msgid "Length" -msgstr "Length" +msgstr "长度" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:instance > :multi_factor_authentication > :" "backup_codes > :number" msgid "Number" -msgstr "Number" +msgstr "数量" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:instance > :multi_factor_authentication > :totp" msgid "TOTP settings" -msgstr "TOTP settings" +msgstr "TOTP 设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:instance > :multi_factor_authentication > :totp > :" "digits" msgid "Digits" -msgstr "Digits" +msgstr "位数" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:instance > :multi_factor_authentication > :totp > :" "period" msgid "Period" -msgstr "Period" +msgstr "周期" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :name" msgid "Name" -msgstr "Name" +msgstr "名称" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :notify_email" msgid "Sender Email Address" -msgstr "Sender Email Address" +msgstr "发件人邮箱地址" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :poll_limits" msgid "Poll limits" -msgstr "Poll limits" +msgstr "投票限制" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :poll_limits > :max_expiration" msgid "Max expiration" -msgstr "Max expiration" +msgstr "最大过期时间" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :poll_limits > :max_option_chars" msgid "Max option chars" -msgstr "Max option chars" +msgstr "最大选项字符数" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :poll_limits > :max_options" msgid "Max options" -msgstr "Max options" +msgstr "最大选项数" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :poll_limits > :min_expiration" msgid "Min expiration" -msgstr "Min expiration" +msgstr "最小过期时间" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :privileged_staff" msgid "Privileged staff" -msgstr "Privileged staff" +msgstr "特权员工" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :profile_directory" msgid "Profile directory" -msgstr "Profile directory" +msgstr "个人资料目录" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :public" msgid "Public" -msgstr "Public" +msgstr "公开" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :quarantined_instances" msgid "Quarantined instances" -msgstr "Quarantined instances" +msgstr "隔离实例" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :registration_reason_length" msgid "Registration reason length" -msgstr "Registration reason length" +msgstr "注册原因长度" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :registrations_open" msgid "Registrations open" -msgstr "Registrations open" +msgstr "开放注册" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :remote_limit" msgid "Remote limit" -msgstr "Remote limit" +msgstr "远程限制" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :remote_post_retention_days" msgid "Remote post retention days" -msgstr "Remote post retention days" +msgstr "远程帖文保留天数" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :safe_dm_mentions" msgid "Safe DM mentions" -msgstr "Safe DM mentions" +msgstr "安全私信提及" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :show_reactions" msgid "Show reactions" -msgstr "Show reactions" +msgstr "显示反应" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :skip_thread_containment" msgid "Skip thread containment" -msgstr "Skip thread containment" +msgstr "跳过线程包含" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :static_dir" msgid "Static dir" -msgstr "Static dir" +msgstr "静态目录" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :upload_limit" msgid "Upload limit" -msgstr "Upload limit" +msgstr "上传限制" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :user_bio_length" msgid "User bio length" -msgstr "User bio length" +msgstr "用户简介长度" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :user_name_length" msgid "User name length" -msgstr "User name length" +msgstr "用户名称长度" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instances_favicons > :enabled" msgid "Enabled" -msgstr "Enabled" +msgstr "启用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:ldap > :base" msgid "Base" -msgstr "Base" +msgstr "基础" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:ldap > :enabled" msgid "Enabled" -msgstr "Enabled" +msgstr "启用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:ldap > :host" msgid "Host" -msgstr "Host" +msgstr "主机" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:ldap > :port" msgid "Port" -msgstr "Port" +msgstr "端口" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:ldap > :ssl" msgid "SSL" msgstr "SSL" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:ldap > :sslopts" msgid "SSL options" -msgstr "SSL options" +msgstr "SSL 选项" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:ldap > :sslopts > :cacertfile" msgid "Cacertfile" -msgstr "Cacertfile" +msgstr "CA 证书文件" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:ldap > :sslopts > :verify" msgid "Verify" -msgstr "Verify" +msgstr "验证" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:ldap > :tls" msgid "TLS" msgstr "TLS" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:ldap > :tlsopts" msgid "TLS options" -msgstr "TLS options" +msgstr "TLS 选项" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:ldap > :tlsopts > :cacertfile" msgid "Cacertfile" -msgstr "Cacertfile" +msgstr "CA 证书文件" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:ldap > :tlsopts > :verify" msgid "Verify" -msgstr "Verify" +msgstr "验证" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:ldap > :uid" msgid "UID" msgstr "UID" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:majic_pool > :size" msgid "Size" -msgstr "Size" +msgstr "大小" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:manifest > :background_color" msgid "Background color" -msgstr "Background color" +msgstr "背景颜色" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:manifest > :icons" msgid "Icons" -msgstr "Icons" +msgstr "图标" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:manifest > :theme_color" msgid "Theme color" -msgstr "Theme color" +msgstr "主题颜色" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:markup > :allow_fonts" msgid "Allow fonts" -msgstr "Allow fonts" +msgstr "允许字体" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:markup > :allow_headings" msgid "Allow headings" -msgstr "Allow headings" +msgstr "允许标题" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:markup > :allow_inline_images" msgid "Allow inline images" -msgstr "Allow inline images" +msgstr "允许内联图像" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:markup > :allow_tables" msgid "Allow tables" -msgstr "Allow tables" +msgstr "允许表格" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:markup > :scrub_policy" msgid "Scrub policy" -msgstr "Scrub policy" +msgstr "清理策略" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:media_preview_proxy > :enabled" msgid "Enabled" -msgstr "Enabled" +msgstr "启用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:media_preview_proxy > :image_quality" msgid "Image quality" -msgstr "Image quality" +msgstr "图像质量" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:media_preview_proxy > :min_content_length" msgid "Min content length" -msgstr "Min content length" +msgstr "最小内容长度" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:media_preview_proxy > :thumbnail_max_height" msgid "Thumbnail max height" -msgstr "Thumbnail max height" +msgstr "缩略图最大高度" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:media_preview_proxy > :thumbnail_max_width" msgid "Thumbnail max width" -msgstr "Thumbnail max width" +msgstr "缩略图最大宽度" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:media_proxy > :base_url" msgid "Base URL" -msgstr "Base URL" +msgstr "基础 URL" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:media_proxy > :enabled" msgid "Enabled" -msgstr "Enabled" +msgstr "启用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:media_proxy > :invalidation" msgid "Invalidation" -msgstr "Invalidation" +msgstr "失效" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:media_proxy > :invalidation > :enabled" msgid "Enabled" -msgstr "Enabled" +msgstr "已启用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:media_proxy > :invalidation > :provider" msgid "Provider" -msgstr "Provider" +msgstr "提供商" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:media_proxy > :proxy_opts" msgid "Advanced MediaProxy Options" -msgstr "Advanced MediaProxy Options" +msgstr "高级媒体代理选项" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:media_proxy > :proxy_opts > :max_body_length" msgid "Max body length" -msgstr "Max body length" +msgstr "最大正文长度" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:media_proxy > :proxy_opts > :max_read_duration" msgid "Max read duration" -msgstr "Max read duration" +msgstr "最大读取时长" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:media_proxy > :proxy_opts > :redirect_on_failure" msgid "Redirect on failure" -msgstr "Redirect on failure" +msgstr "失败时重定向" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:media_proxy > :whitelist" msgid "Whitelist" -msgstr "Whitelist" +msgstr "白名单" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:modules > :runtime_dir" msgid "Runtime dir" -msgstr "Runtime dir" +msgstr "运行时目录" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf > :policies" msgid "Policies" -msgstr "Policies" +msgstr "策略" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf > :transparency" msgid "MRF transparency" -msgstr "MRF transparency" +msgstr "MRF 透明度" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf > :transparency_exclusions" msgid "MRF transparency exclusions" -msgstr "MRF transparency exclusions" +msgstr "MRF 透明度排除项" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_activity_expiration > :days" msgid "Days" -msgstr "Days" +msgstr "天数" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_follow_bot > :follower_nickname" msgid "Follower nickname" -msgstr "Follower nickname" +msgstr "关注者昵称" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_hashtag > :federated_timeline_removal" msgid "Federated timeline removal" -msgstr "Federated timeline removal" +msgstr "联合时间线移除" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_hashtag > :reject" msgid "Reject" -msgstr "Reject" +msgstr "拒绝" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_hashtag > :sensitive" msgid "Sensitive" -msgstr "Sensitive" +msgstr "敏感" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_hellthread > :delist_threshold" msgid "Delist threshold" -msgstr "Delist threshold" +msgstr "取消列出阈值" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_hellthread > :reject_threshold" msgid "Reject threshold" -msgstr "Reject threshold" +msgstr "拒绝阈值" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_keyword > :federated_timeline_removal" msgid "Federated timeline removal" -msgstr "Federated timeline removal" +msgstr "联合时间线移除" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_keyword > :reject" msgid "Reject" -msgstr "Reject" +msgstr "拒绝" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_keyword > :replace" msgid "Replace" -msgstr "Replace" +msgstr "替换" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_mention > :actors" msgid "Actors" -msgstr "Actors" +msgstr "参与者" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_normalize_markup > :scrub_policy" msgid "Scrub policy" -msgstr "Scrub policy" +msgstr "清理策略" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_object_age > :actions" msgid "Actions" -msgstr "Actions" +msgstr "操作" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_object_age > :threshold" msgid "Threshold" -msgstr "Threshold" +msgstr "阈值" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_rejectnonpublic > :allow_direct" msgid "Allow direct" -msgstr "Allow direct" +msgstr "允许私密" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_rejectnonpublic > :allow_followersonly" msgid "Allow followers-only" -msgstr "Allow followers-only" +msgstr "允许仅关注者" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_simple > :accept" msgid "Accept" -msgstr "Accept" +msgstr "接受" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_simple > :avatar_removal" msgid "Avatar removal" -msgstr "Avatar removal" +msgstr "头像移除" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_simple > :banner_removal" msgid "Banner removal" -msgstr "Banner removal" +msgstr "横幅移除" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_simple > :federated_timeline_removal" msgid "Federated timeline removal" -msgstr "Federated timeline removal" +msgstr "联合时间线移除" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_simple > :followers_only" msgid "Followers only" -msgstr "Followers only" +msgstr "仅关注者" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_simple > :media_nsfw" msgid "Media NSFW" -msgstr "Media NSFW" +msgstr "媒体 NSFW" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_simple > :media_removal" msgid "Media removal" -msgstr "Media removal" +msgstr "媒体移除" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_simple > :reject" msgid "Reject" -msgstr "Reject" +msgstr "拒绝" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_simple > :reject_deletes" msgid "Reject deletes" -msgstr "Reject deletes" +msgstr "拒绝删除" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_simple > :report_removal" msgid "Report removal" -msgstr "Report removal" +msgstr "报告移除" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_steal_emoji > :hosts" msgid "Hosts" -msgstr "Hosts" +msgstr "主机" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_steal_emoji > :rejected_shortcodes" msgid "Rejected shortcodes" -msgstr "Rejected shortcodes" +msgstr "已拒绝短代码" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_steal_emoji > :size_limit" msgid "Size limit" -msgstr "Size limit" +msgstr "大小限制" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_subchain > :match_actor" msgid "Match actor" -msgstr "Match actor" +msgstr "匹配参与者" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_vocabulary > :accept" msgid "Accept" -msgstr "Accept" +msgstr "接受" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_vocabulary > :reject" msgid "Reject" -msgstr "Reject" +msgstr "拒绝" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:oauth2 > :clean_expired_tokens" msgid "Clean expired tokens" -msgstr "Clean expired tokens" +msgstr "清理过期令牌" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:oauth2 > :issue_new_refresh_token" msgid "Issue new refresh token" -msgstr "Issue new refresh token" +msgstr "签发新刷新令牌" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:oauth2 > :token_expires_in" msgid "Token expires in" -msgstr "Token expires in" +msgstr "令牌过期时间" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:populate_hashtags_table > :fault_rate_allowance" msgid "Fault rate allowance" -msgstr "Fault rate allowance" +msgstr "容错率允许值" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:populate_hashtags_table > :sleep_interval_ms" msgid "Sleep interval ms" -msgstr "Sleep interval ms" +msgstr "休眠间隔(毫秒)" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:rate_limit > :app_account_creation" msgid "App account creation" -msgstr "App account creation" +msgstr "应用账号创建" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:rate_limit > :authentication" msgid "Authentication" -msgstr "Authentication" +msgstr "认证" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:rate_limit > :relation_id_action" msgid "Relation ID action" -msgstr "Relation ID action" +msgstr "关系 ID 操作" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:rate_limit > :relations_actions" msgid "Relations actions" -msgstr "Relations actions" +msgstr "关系操作" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:rate_limit > :search" msgid "Search" -msgstr "Search" +msgstr "搜索" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:rate_limit > :status_id_action" msgid "Status ID action" -msgstr "Status ID action" +msgstr "状态 ID 操作" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:rate_limit > :statuses_actions" msgid "Statuses actions" -msgstr "Statuses actions" +msgstr "状态操作" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:rate_limit > :timeline" msgid "Timeline" -msgstr "Timeline" +msgstr "时间线" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:restrict_unauthenticated > :activities" msgid "Activities" -msgstr "Activities" +msgstr "活动" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:restrict_unauthenticated > :activities > :local" msgid "Local" -msgstr "Local" +msgstr "本地" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:restrict_unauthenticated > :activities > :remote" msgid "Remote" -msgstr "Remote" +msgstr "远程" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:restrict_unauthenticated > :profiles" msgid "Profiles" -msgstr "Profiles" +msgstr "资料" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:restrict_unauthenticated > :profiles > :local" msgid "Local" -msgstr "Local" +msgstr "本地" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:restrict_unauthenticated > :profiles > :remote" msgid "Remote" -msgstr "Remote" +msgstr "远程" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:restrict_unauthenticated > :timelines" msgid "Timelines" -msgstr "Timelines" +msgstr "时间线" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:restrict_unauthenticated > :timelines > :federated" msgid "Federated" -msgstr "Federated" +msgstr "联合" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:restrict_unauthenticated > :timelines > :local" msgid "Local" -msgstr "Local" +msgstr "本地" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:rich_media > :enabled" msgid "Enabled" -msgstr "Enabled" +msgstr "已启用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:rich_media > :failure_backoff" msgid "Failure backoff" -msgstr "Failure backoff" +msgstr "失败回退" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:rich_media > :ignore_hosts" msgid "Ignore hosts" -msgstr "Ignore hosts" +msgstr "忽略主机" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:rich_media > :ignore_tld" msgid "Ignore TLD" -msgstr "Ignore TLD" +msgstr "忽略 TLD" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:rich_media > :parsers" msgid "Parsers" -msgstr "Parsers" +msgstr "解析器" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:rich_media > :ttl_setters" msgid "TTL setters" -msgstr "TTL setters" +msgstr "TTL 设置器" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:static_fe > :enabled" msgid "Enabled" -msgstr "Enabled" +msgstr "已启用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:streamer > :overflow_workers" msgid "Overflow workers" -msgstr "Overflow workers" +msgstr "溢出工作进程" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:streamer > :workers" msgid "Workers" -msgstr "Workers" +msgstr "工作进程" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:uri_schemes > :valid_schemes" msgid "Valid schemes" -msgstr "Valid schemes" +msgstr "有效方案" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:user > :deny_follow_blocked" msgid "Deny follow blocked" -msgstr "Deny follow blocked" +msgstr "拒绝关注已屏蔽" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:web_cache_ttl > :activity_pub" msgid "Activity pub" -msgstr "Activity pub" +msgstr "Activitypub" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:web_cache_ttl > :activity_pub_question" msgid "Activity pub question" -msgstr "Activity pub question" +msgstr "Activitypub 问题" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:welcome > :direct_message" msgid "Direct message" -msgstr "Direct message" +msgstr "私信" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:welcome > :direct_message > :enabled" msgid "Enabled" -msgstr "Enabled" +msgstr "已启用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:welcome > :direct_message > :message" msgid "Message" -msgstr "Message" +msgstr "消息" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:welcome > :direct_message > :sender_nickname" msgid "Sender nickname" -msgstr "Sender nickname" +msgstr "发送者昵称" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:welcome > :email" msgid "Email" -msgstr "Email" +msgstr "邮件" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:welcome > :email > :enabled" msgid "Enabled" -msgstr "Enabled" +msgstr "已启用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:welcome > :email > :html" msgid "Html" -msgstr "Html" +msgstr "HTML" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:welcome > :email > :sender" msgid "Sender" -msgstr "Sender" +msgstr "发送者" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:welcome > :email > :subject" msgid "Subject" -msgstr "Subject" +msgstr "主题" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:welcome > :email > :text" msgid "Text" -msgstr "Text" +msgstr "文本" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:workers > :retries" msgid "Retries" -msgstr "Retries" +msgstr "重试次数" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-ConcurrentLimiter > Pleroma.Web.ActivityPub.MRF." "MediaProxyWarmingPolicy" @@ -5297,795 +4361,673 @@ msgid "Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy" msgstr "Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-ConcurrentLimiter > Pleroma.Web.ActivityPub.MRF." "MediaProxyWarmingPolicy > :max_running" msgid "Max running" -msgstr "Max running" +msgstr "最大运行数" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-ConcurrentLimiter > Pleroma.Web.ActivityPub.MRF." "MediaProxyWarmingPolicy > :max_waiting" msgid "Max waiting" -msgstr "Max waiting" +msgstr "最大等待数" #: lib/pleroma/docs/translator.ex:5 -#, 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 -#, fuzzy msgctxt "" "config label at :pleroma-ConcurrentLimiter > Pleroma.Web.RichMedia.Helpers " "> :max_running" msgid "Max running" -msgstr "Max running" +msgstr "最大运行数" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-ConcurrentLimiter > Pleroma.Web.RichMedia.Helpers " "> :max_waiting" msgid "Max waiting" -msgstr "Max waiting" +msgstr "最大等待数" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Oban > :crontab" msgid "Crontab" -msgstr "Crontab" +msgstr "定时任务" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Oban > :log" msgid "Log" -msgstr "Log" +msgstr "日志" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Oban > :queues" msgid "Queues" -msgstr "Queues" +msgstr "队列" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Oban > :queues > :activity_expiration" msgid "Activity expiration" -msgstr "Activity expiration" +msgstr "活动过期" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Oban > :queues > :attachments_cleanup" msgid "Attachments cleanup" -msgstr "Attachments cleanup" +msgstr "附件清理" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Oban > :queues > :background" msgid "Background" -msgstr "Background" +msgstr "后台" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Oban > :queues > :backup" msgid "Backup" -msgstr "Backup" +msgstr "备份" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Oban > :queues > :federator_incoming" msgid "Federator incoming" -msgstr "Federator incoming" +msgstr "联合器入站" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Oban > :queues > :federator_outgoing" msgid "Federator outgoing" -msgstr "Federator outgoing" +msgstr "联合器出站" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Oban > :queues > :mailer" msgid "Mailer" -msgstr "Mailer" +msgstr "邮件发送器" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Oban > :queues > :scheduled_activities" msgid "Scheduled activities" -msgstr "Scheduled activities" +msgstr "计划活动" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Oban > :queues > :transmogrifier" msgid "Transmogrifier" -msgstr "Transmogrifier" +msgstr "转换器" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Oban > :queues > :web_push" msgid "Web push" -msgstr "Web push" +msgstr "Web 推送" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Captcha > :enabled" msgid "Enabled" -msgstr "Enabled" +msgstr "已启用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Captcha > :method" msgid "Method" -msgstr "Method" +msgstr "方法" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Captcha > :seconds_valid" msgid "Seconds valid" -msgstr "Seconds valid" +msgstr "有效秒数" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Captcha.Kocaptcha > :endpoint" msgid "Endpoint" -msgstr "Endpoint" +msgstr "端点" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > :adapter" msgid "Adapter" -msgstr "Adapter" +msgstr "适配器" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > :enabled" msgid "Mailer Enabled" -msgstr "Mailer Enabled" +msgstr "邮件发送器已启用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.AmazonSES-:" "access_key" msgid "AWS Access Key" -msgstr "AWS Access Key" +msgstr "AWS 访问密钥" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.AmazonSES-:" "region" msgid "AWS Region" -msgstr "AWS Region" +msgstr "AWS 区域" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.AmazonSES-:" "secret" msgid "AWS Secret Key" -msgstr "AWS Secret Key" +msgstr "AWS 密钥" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Dyn-:api_key" msgid "Dyn API Key" -msgstr "Dyn API Key" +msgstr "Dyn API 密钥" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Gmail-:" "access_token" msgid "GMail API Access Token" -msgstr "GMail API Access Token" +msgstr "Gmail API 访问令牌" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Mailgun-:" "api_key" msgid "Mailgun API Key" -msgstr "Mailgun API Key" +msgstr "Mailgun API 密钥" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Mailgun-:" "domain" msgid "Domain" -msgstr "Domain" +msgstr "域名" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Mailjet-:" "api_key" msgid "MailJet Public API Key" -msgstr "MailJet Public API Key" +msgstr "MailJet 公共 API 密钥" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Mailjet-:" "secret" msgid "MailJet Private API Key" -msgstr "MailJet Private API Key" +msgstr "MailJet 私有 API 密钥" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Mandrill-:" "api_key" msgid "Mandrill API Key" -msgstr "Mandrill API Key" +msgstr "Mandrill API 密钥" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Postmark-:" "api_key" msgid "Postmark API Key" -msgstr "Postmark API Key" +msgstr "Postmark API 密钥" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:auth" msgid "AUTH Mode" -msgstr "AUTH Mode" +msgstr "认证模式" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:" "password" msgid "Password" -msgstr "Password" +msgstr "密码" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:port" msgid "Port" -msgstr "Port" +msgstr "端口" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:relay" msgid "Relay" -msgstr "Relay" +msgstr "中继" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:" "retries" msgid "Retries" -msgstr "Retries" +msgstr "重试次数" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:ssl" msgid "Use SSL" -msgstr "Use SSL" +msgstr "使用 SSL" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:tls" msgid "STARTTLS Mode" -msgstr "STARTTLS Mode" +msgstr "STARTTLS 模式" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:" "username" msgid "Username" -msgstr "Username" +msgstr "用户名" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Sendgrid-:" "api_key" msgid "SendGrid API Key" -msgstr "SendGrid API Key" +msgstr "SendGrid API 密钥" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Sendmail-:" "cmd_args" msgid "Cmd args" -msgstr "Cmd args" +msgstr "命令参数" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Sendmail-:" "cmd_path" msgid "Cmd path" -msgstr "Cmd path" +msgstr "命令路径" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Sendmail-:" "qmail" msgid "Qmail compat mode" -msgstr "Qmail compat mode" +msgstr "Qmail 兼容模式" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SocketLabs-:" "api_key" msgid "SocketLabs API Key" -msgstr "SocketLabs API Key" +msgstr "SocketLabs API 密钥" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SocketLabs-:" "server_id" msgid "Server ID" -msgstr "Server ID" +msgstr "服务器 ID" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SparkPost-:" "api_key" msgid "SparkPost API key" -msgstr "SparkPost API key" +msgstr "SparkPost API 密钥" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SparkPost-:" "endpoint" msgid "Endpoint" -msgstr "Endpoint" +msgstr "端点" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.NewUsersDigestEmail > :enabled" msgid "Enabled" -msgstr "Enabled" +msgstr "已启用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Emails.UserEmail > :logo" msgid "Logo" -msgstr "Logo" +msgstr "徽标" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Emails.UserEmail > :styling" msgid "Styling" -msgstr "Styling" +msgstr "样式" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.UserEmail > :styling > :" "background_color" msgid "Background color" -msgstr "Background color" +msgstr "背景颜色" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.UserEmail > :styling > :" "content_background_color" msgid "Content background color" -msgstr "Content background color" +msgstr "内容背景颜色" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.UserEmail > :styling > :header_color" msgid "Header color" -msgstr "Header color" +msgstr "标题颜色" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.UserEmail > :styling > :link_color" msgid "Link color" -msgstr "Link color" +msgstr "链接颜色" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.UserEmail > :styling > :text_color" msgid "Text color" -msgstr "Text color" +msgstr "文本颜色" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Emails.UserEmail > :styling > :" "text_muted_color" msgid "Text muted color" -msgstr "Text muted color" +msgstr "文本弱色" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Formatter > :class" msgid "Class" -msgstr "Class" +msgstr "类" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Formatter > :extra" msgid "Extra" -msgstr "Extra" +msgstr "额外" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Formatter > :new_window" msgid "New window" -msgstr "New window" +msgstr "新窗口" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Formatter > :rel" msgid "Rel" -msgstr "Rel" +msgstr "Rel 属性" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Formatter > :strip_prefix" msgid "Strip prefix" -msgstr "Strip prefix" +msgstr "去除前缀" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Formatter > :truncate" msgid "Truncate" -msgstr "Truncate" +msgstr "截断" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Formatter > :validate_tld" msgid "Validate tld" -msgstr "Validate tld" +msgstr "验证 TLD" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.ScheduledActivity > :daily_user_limit" msgid "Daily user limit" -msgstr "Daily user limit" +msgstr "每日用户限制" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.ScheduledActivity > :enabled" msgid "Enabled" -msgstr "Enabled" +msgstr "已启用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.ScheduledActivity > :total_user_limit" msgid "Total user limit" -msgstr "Total user limit" +msgstr "总用户限制" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Upload > :base_url" msgid "Base URL" -msgstr "Base URL" +msgstr "基础 URL" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Upload > :filename_display_max_length" msgid "Filename display max length" -msgstr "Filename display max length" +msgstr "文件名显示最大长度" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Upload > :filters" msgid "Filters" -msgstr "Filters" +msgstr "过滤器" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Upload > :link_name" msgid "Link name" -msgstr "Link name" +msgstr "链接名称" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Upload > :uploader" msgid "Uploader" -msgstr "Uploader" +msgstr "上传器" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Upload.Filter.AnonymizeFilename > :text" msgid "Text" -msgstr "Text" +msgstr "文本" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Upload.Filter.Mogrify > :args" msgid "Args" -msgstr "Args" +msgstr "参数" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Uploaders.Local > :uploads" msgid "Uploads" -msgstr "Uploads" +msgstr "上传" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Uploaders.S3 > :bucket" msgid "Bucket" -msgstr "Bucket" +msgstr "存储桶" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Uploaders.S3 > :bucket_namespace" msgid "Bucket namespace" -msgstr "Bucket namespace" +msgstr "存储桶命名空间" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Uploaders.S3 > :streaming_enabled" msgid "Streaming enabled" -msgstr "Streaming enabled" +msgstr "启用流式传输" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Uploaders.S3 > :truncated_namespace" msgid "Truncated namespace" -msgstr "Truncated namespace" +msgstr "截断的命名空间" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.User > :email_blacklist" msgid "Email blacklist" -msgstr "Email blacklist" +msgstr "邮箱黑名单" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.User > :restricted_nicknames" msgid "Restricted nicknames" -msgstr "Restricted nicknames" +msgstr "受限昵称" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.User.Backup > :limit_days" msgid "Limit days" -msgstr "Limit days" +msgstr "限制天数" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.User.Backup > :purge_after_days" msgid "Purge after days" -msgstr "Purge after days" +msgstr "过期后清理天数" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Web.ApiSpec.CastAndValidate > :strict" msgid "Strict" -msgstr "Strict" +msgstr "严格模式" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http > :headers" msgid "Headers" -msgstr "Headers" +msgstr "头部信息" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http > :method" msgid "Method" -msgstr "Method" +msgstr "方法" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http > :options" msgid "Options" -msgstr "Options" +msgstr "选项" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http > :options " "> :params" msgid "Params" -msgstr "Params" +msgstr "参数" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Script > :" "script_path" msgid "Script path" -msgstr "Script path" +msgstr "脚本路径" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Script > :" "url_format" msgid "URL Format" -msgstr "URL Format" +msgstr "URL 格式" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Web.Metadata > :providers" msgid "Providers" -msgstr "Providers" +msgstr "提供者" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Web.Metadata > :unfurl_nsfw" msgid "Unfurl NSFW" -msgstr "Unfurl NSFW" +msgstr "展开 NSFW 内容" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Web.Plugs.RemoteIp > :enabled" msgid "Enabled" -msgstr "Enabled" +msgstr "已启用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Web.Plugs.RemoteIp > :headers" msgid "Headers" -msgstr "Headers" +msgstr "头部信息" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Web.Plugs.RemoteIp > :proxies" msgid "Proxies" -msgstr "Proxies" +msgstr "代理" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Web.Plugs.RemoteIp > :reserved" msgid "Reserved" -msgstr "Reserved" +msgstr "保留" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Web.Preload > :providers" msgid "Providers" -msgstr "Providers" +msgstr "提供者" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Workers.PurgeExpiredActivity > :enabled" msgid "Enabled" -msgstr "Enabled" +msgstr "已启用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Workers.PurgeExpiredActivity > :min_lifetime" msgid "Min lifetime" -msgstr "Min lifetime" +msgstr "最短生存时间" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :web_push_encryption-:vapid_details > :private_key" msgid "Private key" -msgstr "Private key" +msgstr "私钥" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :web_push_encryption-:vapid_details > :public_key" msgid "Public key" -msgstr "Public key" +msgstr "公钥" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :web_push_encryption-:vapid_details > :subject" msgid "Subject" -msgstr "Subject" +msgstr "主题" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:activitypub > :authorized_fetch_mode" msgid "Authorized fetch mode" -msgstr "Authorized fetch mode" +msgstr "授权获取模式" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:activitypub > :authorized_fetch_mode" msgid "Require HTTP signatures on AP fetches" -msgstr "Require HTTP signatures on AP fetches" +msgstr "在 AP 获取时要求 HTTP 签名" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "从远程集合中获取的最大项目数。设置过低可能导致仅获取部分集合,但设置过高则可" +"能获取过多对象。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:argos_translate" msgid "ArgosTranslate Settings." -msgstr "ArgosTranslate Settings." +msgstr "ArgosTranslate 设置。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "`argos-translate` 的命令。可以是 PATH 中的命令,也可以是文件的完整路径。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "`argospm` 的命令。可以是 PATH 中的命令,也可以是文件的完整路径。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "在翻译前从帖文中去除 HTML。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:deepl" msgid "DeepL Settings." -msgstr "DeepL Settings." +msgstr "DeepL 设置。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:deepl > :api_key" msgid "API key for DeepL" -msgstr "API key for DeepL" +msgstr "DeepL 的 API 密钥" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:deepl > :tier" msgid "API Tier" -msgstr "API Tier" +msgstr "API 层级" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:frontend_configurations" msgid "" "This form can be used to configure a keyword list that keeps the " @@ -6093,213 +5035,171 @@ msgid "" "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." +"此表单可用于配置包含任何类型前端配置数据的关键字列表。默认情况下,配置了 " +"pleroma_fe 和 masto_fe 的设置。如果要添加自己的配置,所有字段必须完整填写。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:frontend_configurations > :masto_fe" msgid "Settings for Masto FE" -msgstr "Settings for Masto FE" +msgstr "Masto FE 的设置" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "是否显示实例特定面板" #: lib/pleroma/docs/translator.ex:5 -#, 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)" +msgstr "如何显示对话(线性或树状)" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "是否在导航栏隐藏实例网站图标" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "是否在导航栏隐藏站点名称" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "是否渲染 Misskey 风格的 Markdown" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "是否在导航栏放置额外导航选项" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "是否在面板顶部放置时间线导航标签" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "是否在导航栏图标之间添加额外空间" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "是否暂停动画图像直到鼠标悬停" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:frontends > :mastodon" msgid "Mastodon frontend" -msgstr "Mastodon frontend" +msgstr "Mastodon 前端" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "已安装前端的名称。有效配置必须同时包含“名称”和“引用”值。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "要使用的已安装前端的引用。有效配置必须同时包含“名称”和“引用”值。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:frontends > :swagger" msgid "Swagger API reference frontend" -msgstr "Swagger API reference frontend" +msgstr "Swagger API 参考前端" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "是否完全启用此项" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "已安装前端的名称。有效配置必须同时包含“名称”和“引用”值。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "要使用的已安装前端的引用。有效配置必须同时包含“名称”和“引用”值。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "允许的并发出站 HTTP 请求数量。默认 50。" #: lib/pleroma/docs/translator.ex:5 -#, 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)" +msgstr "发起 HTTP 请求的超时时间(单位:毫秒,默认 5000)" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "代理 URL - 格式为 http://host:port。建议在.exs " +"文件中设置而非管理界面,因为此设置在启动时生效。" #: lib/pleroma/docs/translator.ex:5 -#, 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)" +msgstr "等待远程服务器响应 HTTP 请求的超时时间(单位:毫秒,默认 15000)" #: lib/pleroma/docs/translator.ex:5 -#, 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)" +msgstr "启用 prometheus 指标(位于/api/v1/akkoma/metrics)" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:instance > :languages" msgid "Languages the instance uses" -msgstr "Languages the instance uses" +msgstr "实例使用的语言" #: lib/pleroma/docs/translator.ex:5 -#, 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)." +msgstr "构成您本地气泡(紧密相关实例)的实例列表。用于填充“气泡”时间线(仅域名)。" #: lib/pleroma/docs/translator.ex:5 -#, 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)" +msgstr "允许版主访问敏感数据(例如更新用户凭据、获取密码重置令牌、删除用户、索引和读" +"取私有状态)" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:instance > :public" msgid "" "Switching this on will allow unauthenticated users access to all public " @@ -6307,318 +5207,261 @@ msgid "" "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." +"开启此选项将允许未认证用户访问您实例上的所有公共资源。关闭此选项可用于禁用本" +"地时间线和整个已知网络。注意:设置为 `false` 时,请同时检查 " +"`:restrict_unauthenticated` 设置。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "(已弃用,将在下一版本移除)不会向其发送活动的 ActivityPub 实例列表及原因" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:instances_nodeinfo" msgid "Control favicons for instances" -msgstr "Control favicons for instances" +msgstr "控制实例的网站图标" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:instances_nodeinfo > :enabled" msgid "Allow/disallow getting instance nodeinfo" -msgstr "Allow/disallow getting instance nodeinfo" +msgstr "允许/禁止获取实例节点信息" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:libre_translate" msgid "LibreTranslate Settings." -msgstr "LibreTranslate Settings." +msgstr "LibreTranslate 设置。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:libre_translate > :api_key" msgid "API key for libretranslate" -msgstr "API key for libretranslate" +msgstr "libretranslate 的 API 密钥" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:libre_translate > :url" msgid "URL for libretranslate" -msgstr "URL for libretranslate" +msgstr "libretranslate 的 URL" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "描述应用的背景颜色 - 仅用于 mastodon-fe" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "描述应用的主题颜色 - 仅用于 mastodon-fe" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "在 MRF 透明度中混淆域名。当您屏蔽的域名包含不希望显示的词语但仍希望披露 MRF " +"设置时,此功能很有用。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:mrf_inline_quote" msgid "Force quote post URLs inline" -msgstr "Force quote post URLs inline" +msgstr "强制内联引用帖文 URL" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:mrf_inline_quote > :prefix" msgid "Prefix before the link" -msgstr "Prefix before the link" +msgstr "链接前的前缀" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "启用后可根据来源实例使用拒绝和接受规则过滤对线程的回复" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "禁止未认证用户查看时间线、用户资料和状态。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:restrict_unauthenticated > :activities" msgid "Settings for posts." -msgstr "Settings for posts." +msgstr "帖文相关设置。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:restrict_unauthenticated > :activities > :" "local" msgid "Disallow viewing local posts." -msgstr "Disallow viewing local posts." +msgstr "禁止查看本地帖文。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:restrict_unauthenticated > :activities > :" "remote" msgid "Disallow viewing remote posts." -msgstr "Disallow viewing remote posts." +msgstr "禁止查看远程帖文。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:restrict_unauthenticated > :profiles > :local" msgid "Disallow viewing local user profiles." -msgstr "Disallow viewing local user profiles." +msgstr "禁止查看本地用户资料。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:restrict_unauthenticated > :profiles > :" "remote" msgid "Disallow viewing remote user profiles." -msgstr "Disallow viewing remote user profiles." +msgstr "禁止查看远程用户资料。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "禁止查看整个已知网络时间线。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:restrict_unauthenticated > :timelines > :" "local" msgid "Disallow viewing the public timeline." -msgstr "Disallow viewing the public timeline." +msgstr "禁止查看公共时间线。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:translator" msgid "Translation Settings" -msgstr "Translation Settings" +msgstr "翻译设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:translator > :enabled" msgid "Is translation enabled?" -msgstr "Is translation enabled?" +msgstr "是否启用翻译?" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:translator > :module" msgid "Translation module." -msgstr "Translation module." +msgstr "翻译模块。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "作业超时时间,按 `Oban` 队列,单位毫秒" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Pleroma.Search" msgid "General search settings." -msgstr "General search settings." +msgstr "通用搜索设置。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Pleroma.Search > :module" msgid "Selected search module." -msgstr "Selected search module." +msgstr "选定的搜索模块。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Pleroma.Search.Elasticsearch.Cluster" msgid "Elasticsearch settings." -msgstr "Elasticsearch settings." +msgstr "Elasticsearch 设置。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "Elasticsearch 使用的 API 模块。应始终为 Elasticsearch.API.HTTP" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "要在 Elasticsearch 中设置的索引" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "用于活动的索引配置" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "批量放置请求的大小,主要用于构建索引" #: lib/pleroma/docs/translator.ex:5 -#, 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)" +msgstr "批量放置请求之间的等待时间(毫秒)" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "包含活动索引设置的文件路径。应包含映射。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "此索引使用的内部类型" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :" "indexes > :activities > :store" msgid "The internal store module" -msgstr "The internal store module" +msgstr "内部存储模块" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "与 Elasticsearch 通信时用于编码/解码的 JSON 模块" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "连接到 ES 的密码。如果集群未认证,请设置为 nil。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :url" msgid "Elasticsearch URL." -msgstr "Elasticsearch URL." +msgstr "Elasticsearch URL。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "连接到 ES 的用户名。如果集群未认证,请设置为 nil。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Pleroma.Search.Meilisearch" msgid "Meilisearch settings." -msgstr "Meilisearch settings." +msgstr "Meilisearch 设置。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-Pleroma.Search.Meilisearch > :" "initial_indexing_chunk_size" @@ -6626,503 +5469,420 @@ 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" +msgstr "运行初始索引操作时每批的帖文数量。可能不应超过 " +"100000,因为存在最大插入大小限制" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "用于 meilisearch 身份验证的私钥,或 `nil` 以禁用私钥身份验证。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-Pleroma.Search.Meilisearch > :url" msgid "Meilisearch URL." -msgstr "Meilisearch URL." +msgstr "Meilisearch URL。" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "特定提供程序,用于向抓取 index.html 的实例分发主题" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "实例的“强调色”,用于 misskey 的实例指示器等位置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:activitypub > :max_collection_objects" msgid "Max collection objects" -msgstr "Max collection objects" +msgstr "最大集合对象数" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:argos_translate" msgid "Argos translate" -msgstr "Argos translate" +msgstr "Argos 翻译" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:argos_translate > :command_argos_translate" msgid "Command argos translate" -msgstr "Command argos translate" +msgstr "命令 argos 翻译" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:argos_translate > :command_argospm" msgid "Command argospm" -msgstr "Command argospm" +msgstr "命令 argospm" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:argos_translate > :strip_html" msgid "Strip html" -msgstr "Strip html" +msgstr "去除 HTML" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:deepl" msgid "DeepL" msgstr "DeepL" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:deepl > :api_key" msgid "Api key" -msgstr "Api key" +msgstr "API 密钥" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:deepl > :tier" msgid "Tier" -msgstr "Tier" +msgstr "层级" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:frontend_configurations > :masto_fe" msgid "Masto FE" msgstr "Masto FE" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :masto_fe > :" "showInstanceSpecificPanel" msgid "Show instance specific panel" -msgstr "Show instance specific panel" +msgstr "显示实例特定面板" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :" "conversationDisplay" msgid "Conversation display style" -msgstr "Conversation display style" +msgstr "对话显示样式" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :" "hideSiteFavicon" msgid "Hide site favicon" -msgstr "Hide site favicon" +msgstr "隐藏站点图标" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :" "hideSiteName" msgid "Hide site name" -msgstr "Hide site name" +msgstr "隐藏站点名称" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :" "renderMisskeyMarkdown" msgid "Render misskey markdown" -msgstr "Render misskey markdown" +msgstr "渲染 Misskey Markdown" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :" "showNavShortcuts" msgid "Show navbar shortcuts" -msgstr "Show navbar shortcuts" +msgstr "显示导航栏快捷方式" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :" "showPanelNavShortcuts" msgid "Show timeline panel nav shortcuts" -msgstr "Show timeline panel nav shortcuts" +msgstr "显示时间线面板导航快捷方式" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :" "showWiderShortcuts" msgid "Increase navbar shortcut spacing" -msgstr "Increase navbar shortcut spacing" +msgstr "增加导航栏快捷方式间距" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:frontend_configurations > :pleroma_fe > :stopGifs" msgid "Stop Gifs" -msgstr "Stop Gifs" +msgstr "停止 GIF" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:frontends > :mastodon" msgid "Mastodon" msgstr "Mastodon" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:frontends > :mastodon > name" msgid "Name" -msgstr "Name" +msgstr "名称" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:frontends > :mastodon > ref" msgid "Reference" -msgstr "Reference" +msgstr "引用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:frontends > :swagger" msgid "Swagger" msgstr "Swagger" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:frontends > :swagger > enabled" msgid "Enabled" -msgstr "Enabled" +msgstr "已启用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:frontends > :swagger > name" msgid "Name" -msgstr "Name" +msgstr "名称" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:frontends > :swagger > ref" msgid "Reference" -msgstr "Reference" +msgstr "引用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:http > :pool_size" msgid "Pool size" -msgstr "Pool size" +msgstr "池大小" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:http > :pool_timeout" msgid "HTTP Pool Request Timeout" -msgstr "HTTP Pool Request Timeout" +msgstr "HTTP 池请求超时" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:http > :receive_timeout" msgid "HTTP Receive Timeout" -msgstr "HTTP Receive Timeout" +msgstr "HTTP 接收超时" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :export_prometheus_metrics" msgid "Export prometheus metrics" -msgstr "Export prometheus metrics" +msgstr "导出 Prometheus 指标" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :languages" msgid "Languages" -msgstr "Languages" +msgstr "语言" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :local_bubble" msgid "Local bubble" -msgstr "Local bubble" +msgstr "本地气泡" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instances_nodeinfo" msgid "Instances nodeinfo" -msgstr "Instances nodeinfo" +msgstr "实例节点信息" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instances_nodeinfo > :enabled" msgid "Enabled" -msgstr "Enabled" +msgstr "已启用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:libre_translate" msgid "Libre translate" -msgstr "Libre translate" +msgstr "Libre 翻译" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:libre_translate > :api_key" msgid "Api key" -msgstr "Api key" +msgstr "API 密钥" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:libre_translate > :url" msgid "Url" -msgstr "Url" +msgstr "URL" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf > :transparency_obfuscate_domains" msgid "MRF domain obfuscation" -msgstr "MRF domain obfuscation" +msgstr "MRF 域名混淆" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_inline_quote" msgid "MRF Inline Quote" -msgstr "MRF Inline Quote" +msgstr "MRF 内联引用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_inline_quote > :prefix" msgid "Prefix" -msgstr "Prefix" +msgstr "前缀" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_simple > :handle_threads" msgid "Apply to entire threads" -msgstr "Apply to entire threads" +msgstr "应用于整个线程" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:translator" msgid "Translator" -msgstr "Translator" +msgstr "翻译器" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:translator > :enabled" msgid "Enabled" -msgstr "Enabled" +msgstr "已启用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:translator > :module" msgid "Module" -msgstr "Module" +msgstr "模块" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:workers > :timeout" msgid "Timeout" -msgstr "Timeout" +msgstr "超时" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Search" msgid "Search" -msgstr "Search" +msgstr "搜索" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Search > :module" msgid "Module" -msgstr "Module" +msgstr "模块" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Search.Elasticsearch.Cluster" msgid "Elasticsearch" msgstr "Elasticsearch" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :api" msgid "Api" -msgstr "Api" +msgstr "API" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :indexes" msgid "Indexes" -msgstr "Indexes" +msgstr "索引" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :indexes > :" "activities" msgid "Activities" -msgstr "Activities" +msgstr "活动" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :indexes > :" "activities > :bulk_page_size" msgid "Bulk page size" -msgstr "Bulk page size" +msgstr "批量页面大小" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :indexes > :" "activities > :bulk_wait_interval" msgid "Bulk wait interval" -msgstr "Bulk wait interval" +msgstr "批量等待间隔" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :indexes > :" "activities > :settings" msgid "Settings" -msgstr "Settings" +msgstr "设置" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :indexes > :" "activities > :sources" msgid "Sources" -msgstr "Sources" +msgstr "来源" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :indexes > :" "activities > :store" msgid "Store" -msgstr "Store" +msgstr "存储" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :json_library" msgid "Json library" -msgstr "Json library" +msgstr "JSON 库" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :password" msgid "Password" -msgstr "Password" +msgstr "密码" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :url" msgid "Url" -msgstr "Url" +msgstr "URL" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Search.Elasticsearch.Cluster > :username" msgid "Username" -msgstr "Username" +msgstr "用户名" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Search.Meilisearch" msgid "Pleroma.Search.Meilisearch" msgstr "Pleroma.Search.Meilisearch" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Search.Meilisearch > :" "initial_indexing_chunk_size" msgid "Initial indexing chunk size" -msgstr "Initial indexing chunk size" +msgstr "初始索引分块大小" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Search.Meilisearch > :private_key" msgid "Private key" -msgstr "Private key" +msgstr "私钥" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-Pleroma.Search.Meilisearch > :url" msgid "Url" -msgstr "Url" +msgstr "网址" #: lib/pleroma/docs/translator.ex:5 -#, 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 -#, fuzzy msgctxt "" "config label at :pleroma-Pleroma.Web.Metadata.Providers.Theme > :theme_color" msgid "Theme color" -msgstr "Theme color" +msgstr "主题颜色" #: lib/pleroma/docs/translator.ex:5 -#, 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`" +msgstr "如果启用,系统数据将在 `/api/v1/pleroma/healthcheck` 显示" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "包含用户可选择作为偏好的所有前端的列表,格式为 :name/:ref,例如 pleroma-fe/" +"stable。" #: lib/pleroma/docs/translator.ex:5 -#, 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." +msgstr "允许人们查看来自所有实例的所有公开状态的“firehose”信息流。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:media_proxy > :blocklist" msgid "" "List of hosts with scheme which will not go through the MediaProxy, and will " @@ -7130,13 +5890,11 @@ msgid "" "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" 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" +"带有方案的主机列表,这些主机不会通过媒体代理,也不会被内容安全策略明确允许。" +"\n" +"这用于您不希望其媒体通过您的服务器或由客户端访问的实例。\n" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config description at :pleroma-:media_proxy > :whitelist" msgid "" "List of hosts with scheme to bypass the MediaProxy.\n" @@ -7149,71 +5907,60 @@ msgid "" "This 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" "\n" -"The media will be fetched by the client, directly from the remote server.\n" +"媒体将由客户端直接从远程服务器获取。\n" "\n" -"To allow this, it will Content-Security-Policy exceptions for each instance " -"listed.\n" +"为此,它将为列出的每个实例提供内容安全策略例外。\n" "\n" -"This is to be used for instances you trust and do not want to cache media " -"for.\n" +"这用于您信任且不希望缓存媒体的实例。\n" #: lib/pleroma/docs/translator.ex:5 -#, 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" +msgstr "拒绝来自最近创建的账号的帖文" #: lib/pleroma/docs/translator.ex:5 -#, 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)" +msgstr "拒绝时间阈值(以秒为单位)" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config description at :pleroma-:restrict_unauthenticated > :timelines > :" "bubble" msgid "Disallow viewing the bubble timeline." -msgstr "Disallow viewing the bubble timeline." +msgstr "禁止查看气泡时间线。" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:frontends > :pickable" msgid "Pickable" -msgstr "Pickable" +msgstr "可选择的" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:instance > :federated_timeline_available" msgid "Federated timeline available" -msgstr "Federated timeline available" +msgstr "联合时间线可用" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:media_proxy > :blocklist" msgid "Blocklist" -msgstr "Blocklist" +msgstr "阻止列表" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "config label at :pleroma-:mrf_reject_newly_created_account_notes" msgid "MRF Reject New Accounts" -msgstr "MRF Reject New Accounts" +msgstr "MRF 拒绝新账号" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:mrf_reject_newly_created_account_notes > :age" msgid "Age" -msgstr "Age" +msgstr "时间" #: lib/pleroma/docs/translator.ex:5 -#, fuzzy msgctxt "" "config label at :pleroma-:restrict_unauthenticated > :timelines > :bubble" msgid "Bubble" diff --git a/priv/gettext/zh_Hans/LC_MESSAGES/errors.po b/priv/gettext/zh_Hans/LC_MESSAGES/errors.po index f328d71ce..e1689a7be 100644 --- a/priv/gettext/zh_Hans/LC_MESSAGES/errors.po +++ b/priv/gettext/zh_Hans/LC_MESSAGES/errors.po @@ -3,16 +3,16 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-09-20 13:18+0000\n" -"PO-Revision-Date: 2023-02-26 08:57+0000\n" -"Last-Translator: SevicheCC \n" -"Language-Team: Chinese (Simplified) \n" +"PO-Revision-Date: 2025-09-01 06:55+0000\n" +"Last-Translator: Poesty Li \n" +"Language-Team: Chinese (Simplified Han script) \n" "Language: zh_Hans\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 4.14\n" +"X-Generator: Weblate 5.12.2\n" ## This file is a PO Template file. ## @@ -280,7 +280,7 @@ msgstr "该资源需要认证。" #: lib/pleroma/web/plugs/rate_limiter.ex:214 #, elixir-autogen, elixir-format msgid "Throttled" -msgstr "节流了" +msgstr "已限速" #: lib/pleroma/web/common_api.ex:285 #, elixir-autogen, elixir-format @@ -388,7 +388,7 @@ msgstr "无效的回答数据" #: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:40 #, elixir-autogen, elixir-format msgid "Nodeinfo schema version not handled" -msgstr "" +msgstr "Nodeinfo 模式版本未处理" #: lib/pleroma/web/o_auth/fallback_controller.ex:14 #, elixir-autogen, elixir-format @@ -399,7 +399,7 @@ msgstr "未知错误,请检查并重试。" #: lib/pleroma/web/o_auth/o_auth_controller.ex:204 #, elixir-autogen, elixir-format msgid "Unlisted redirect_uri." -msgstr "" +msgstr "未列出的 redirect_uri。" #: lib/pleroma/web/o_auth/o_auth_controller.ex:458 #, elixir-autogen, elixir-format @@ -535,17 +535,17 @@ msgstr "需要重置密码" #: 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 "" +msgstr "安全违规:OAuth 范围检查既未处理也未明确跳过。" #: lib/pleroma/web/plugs/ensure_authenticated_plug.ex:32 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgid "Two-factor authentication enabled, you must use a access token." -msgstr "已启用两因素验证,您需要使用访问令牌。" +msgstr "双因素认证已启用,您必须使用访问令牌。" #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgid "Web push subscription is disabled on this Pleroma instance" -msgstr "此 Pleroma 实例禁用了网页推送订阅" +msgstr "此 Pleroma 实例已禁用网页推送订阅功能" #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:214 #, elixir-autogen, elixir-format @@ -571,7 +571,7 @@ msgstr "此 API 需要已认证的用户" #: lib/pleroma/web/plugs/user_is_admin_plug.ex:21 #, elixir-autogen, elixir-format msgid "User is not an admin." -msgstr "该用户不是管理员。" +msgstr "用户不是管理员。" #: lib/pleroma/user/backup.ex:73 #, elixir-format @@ -582,64 +582,64 @@ msgstr[0] "" #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:399 #, elixir-autogen, elixir-format msgid "Character limit (%{limit} characters) exceeded, contains %{length} characters" -msgstr "" +msgstr "字符限制(%{limit}字符)已超出,包含%{length}字符" #: lib/pleroma/web/plugs/ensure_staff_privileged_plug.ex:33 #: lib/pleroma/web/plugs/user_is_staff_plug.ex:20 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgid "User is not a staff member." -msgstr "该用户不是管理员。" +msgstr "用户不是管理人员。" #: lib/pleroma/web/o_auth/o_auth_controller.ex:391 #, elixir-autogen, elixir-format msgid "Your account is awaiting approval." -msgstr "" +msgstr "您的账号正在等待批准。" #: 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 "" +msgstr "文件过大" #: 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 "未找到列表" +msgstr "话题标签未找到" #: lib/pleroma/web/common_api/activity_draft.ex:144 #, elixir-autogen, elixir-format msgid "Invalid language" -msgstr "" +msgstr "无效语言" #: 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 "此操作在许可范围以外" +msgstr "此操作超出授权范围" #: lib/pleroma/web/common_api/activity_draft.ex:129 #, elixir-autogen, elixir-format msgid "You can only quote public or unlisted statuses" -msgstr "" +msgstr "您只能引用公开或未列出的状态" #: 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 "您无法引用不存在的状态" #: lib/pleroma/web/embed_controller.ex:35 #, elixir-autogen, elixir-format msgid "Federated posts cannot be embedded" -msgstr "" +msgstr "联合帖文无法被嵌入" #: lib/pleroma/web/embed_controller.ex:38 #, elixir-autogen, elixir-format msgid "Not authorized to view this post" -msgstr "" +msgstr "未获授权查看此帖文" #: lib/pleroma/web/embed_controller.ex:32 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgid "Post not found" -msgstr "未找到列表" +msgstr "未找到帖文" diff --git a/priv/repo/migrations/20250202000000_drop_instance_has_request_signatures.exs b/priv/repo/migrations/20250202000000_drop_instance_has_request_signatures.exs new file mode 100644 index 000000000..f7b1d0bc2 --- /dev/null +++ b/priv/repo/migrations/20250202000000_drop_instance_has_request_signatures.exs @@ -0,0 +1,9 @@ +defmodule Pleroma.Repo.Migrations.DropInstanceHasRequestSignatures do + use Ecto.Migration + + def change do + alter table(:instances) do + remove(:has_request_signatures, :boolean, default: false, null: false) + end + end +end diff --git a/priv/repo/migrations/20250621000000_drop_accepts_chat_messages.exs b/priv/repo/migrations/20250621000000_drop_accepts_chat_messages.exs new file mode 100644 index 000000000..157d1fa98 --- /dev/null +++ b/priv/repo/migrations/20250621000000_drop_accepts_chat_messages.exs @@ -0,0 +1,17 @@ +defmodule Pleroma.Repo.Migrations.DropAcceptsChatMessages do + use Ecto.Migration + + def up do + alter table(:users) do + remove(:accepts_chat_messages) + end + end + + def down do + alter table(:users) do + add(:accepts_chat_messages, :boolean, nullable: true) + end + + execute("update users set accepts_chat_messages = true where local = true") + end +end diff --git a/priv/repo/migrations/20250806000000_emojireact_drop_remote_part_from_name.exs b/priv/repo/migrations/20250806000000_emojireact_drop_remote_part_from_name.exs new file mode 100644 index 000000000..b85ad287a --- /dev/null +++ b/priv/repo/migrations/20250806000000_emojireact_drop_remote_part_from_name.exs @@ -0,0 +1,115 @@ +defmodule Pleroma.Repo.Migrations.EmojiReactDropRemotePartFromName do + use Ecto.Migration + + import Ecto.Query + + defp drop_remote_indicator(%{"content" => emoji, "tag" => _tag} = data) do + if String.contains?(emoji, "@") do + do_drop_remote_indicator(data) + else + data + end + end + + defp do_drop_remote_indicator(%{"content" => emoji, "tag" => tag} = data) do + stripped_emoji = Pleroma.Emoji.stripped_name(emoji) + [clean_emoji | _] = String.split(stripped_emoji, "@", parts: 2) + + clean_tag = + Enum.map(tag, fn + %{"name" => ^stripped_emoji} = t -> %{t | "name" => clean_emoji} + t -> t + end) + + %{data | "content" => ":" <> clean_emoji <> ":", "tag" => clean_tag} + end + + defp prune_and_strip_tags(%{"content" => emoji, "tag" => tags} = data) do + clean_emoji = Pleroma.Emoji.stripped_name(emoji) + + pruned_tags = + Enum.reduce_while(tags, [], fn + %{"type" => "Emoji", "name" => name} = tag, res -> + clean_name = Pleroma.Emoji.stripped_name(name) + + if clean_name == clean_emoji do + {:halt, [%{tag | "name" => clean_name}]} + else + {:cont, res} + end + + _, res -> + {:cont, res} + end) + + %{data | "tag" => pruned_tags} + end + + def up() do + has_tag_array = + Pleroma.Activity + |> where( + [a], + fragment("?->>'type' = 'EmojiReact'", a.data) and + fragment("jsonb_typeof(?->'content') = 'string'", a.data) and + fragment("jsonb_typeof(?->'tag') = 'array'", a.data) + ) + + from(a in subquery(has_tag_array)) + |> join(:cross_lateral, [a], fragment("jsonb_array_elements(?->'tag')", a.data)) + |> where( + [a, t], + fragment("?->>'content' LIKE '%@%'", a.data) or + fragment("?->>'content' NOT LIKE ':%:'", a.data) or + fragment("jsonb_array_length(?->'tag') > 1", a.data) or + fragment("?->>'name' LIKE '%:%'", t) or + fragment("?->>'name' LIKE '%@%'", t) + ) + |> distinct(true) + |> Pleroma.Repo.chunk_stream(600, :batches, timeout: :infinity) + |> Stream.each(fn chunk -> + Enum.reduce(chunk, {[], []}, fn %{id: id, data: data}, {ids, newdat} -> + new_data = + data + |> prune_and_strip_tags() + |> drop_remote_indicator() + + if new_data == data do + {ids, newdat} + else + # not sure why we get a string back from the db here and need to explicit convert it back + {[FlakeId.from_string(id) | ids], [new_data | newdat]} + end + end) + |> then(fn + {[], []} -> + IO.puts("Nothing in current batch") + :ok + + {ids, newdat} -> + {upcnt, _} = + Pleroma.Activity + |> join( + :inner, + [a], + news in fragment( + "SELECT * FROM unnest(?::uuid[], ?::jsonb[]) AS news(id, new_data)", + ^ids, + ^newdat + ), + on: a.id == news.id + ) + |> update([_a, news], set: [data: news.new_data]) + |> Pleroma.Repo.update_all([], timeout: :infinity) + + IO.puts("Fixed #{upcnt} reacts in current batch") + end) + end) + |> Stream.run() + end + + def down() do + # not reversible, but also shouldn’t cause any problems + :ok + end +end diff --git a/priv/repo/migrations/20251109000000_purge_counter_cache.exs b/priv/repo/migrations/20251109000000_purge_counter_cache.exs new file mode 100644 index 000000000..f03a4aab3 --- /dev/null +++ b/priv/repo/migrations/20251109000000_purge_counter_cache.exs @@ -0,0 +1,93 @@ +defmodule Pleroma.Repo.Migrations.PurgeCounterCache do + use Ecto.Migration + + @function_name "update_status_visibility_counter_cache" + @trigger_name "status_visibility_counter_cache_trigger" + @table_name "counter_cache" + + def up() do + execute("DROP TRIGGER IF EXISTS " <> @trigger_name <> " ON activities;") + execute("DROP FUNCTION IF EXISTS " <> @function_name <> ";") + + # automatically drops indices + drop table(@table_name) + end + + def down() do + create_if_not_exists table(:counter_cache) do + add(:instance, :string, null: false) + add(:direct, :bigint, null: false, default: 0) + add(:private, :bigint, null: false, default: 0) + add(:unlisted, :bigint, null: false, default: 0) + add(:public, :bigint, null: false, default: 0) + end + + """ + CREATE OR REPLACE FUNCTION #{@function_name}() + RETURNS TRIGGER AS + $$ + DECLARE + hostname character varying(255); + visibility_new character varying(64); + visibility_old character varying(64); + actor character varying(255); + BEGIN + IF TG_OP = 'DELETE' THEN + actor := OLD.actor; + ELSE + actor := NEW.actor; + END IF; + hostname := split_part(actor, '/', 3); + IF TG_OP = 'INSERT' THEN + visibility_new := activity_visibility(NEW.actor, NEW.recipients, NEW.data); + IF NEW.data->>'type' = 'Create' + AND visibility_new IN ('public', 'unlisted', 'private', 'direct') THEN + EXECUTE format('INSERT INTO "counter_cache" ("instance", %1$I) VALUES ($1, 1) + ON CONFLICT ("instance") DO + UPDATE SET %1$I = "counter_cache".%1$I + 1', visibility_new) + USING hostname; + END IF; + RETURN NEW; + ELSIF TG_OP = 'UPDATE' THEN + visibility_new := activity_visibility(NEW.actor, NEW.recipients, NEW.data); + visibility_old := activity_visibility(OLD.actor, OLD.recipients, OLD.data); + IF (NEW.data->>'type' = 'Create') + AND (OLD.data->>'type' = 'Create') + AND visibility_new != visibility_old + AND visibility_new IN ('public', 'unlisted', 'private', 'direct') THEN + EXECUTE format('UPDATE "counter_cache" SET + %1$I = greatest("counter_cache".%1$I - 1, 0), + %2$I = "counter_cache".%2$I + 1 + WHERE "instance" = $1', visibility_old, visibility_new) + USING hostname; + END IF; + RETURN NEW; + ELSIF TG_OP = 'DELETE' THEN + IF OLD.data->>'type' = 'Create' THEN + visibility_old := activity_visibility(OLD.actor, OLD.recipients, OLD.data); + EXECUTE format('UPDATE "counter_cache" SET + %1$I = greatest("counter_cache".%1$I - 1, 0) + WHERE "instance" = $1', visibility_old) + USING hostname; + END IF; + RETURN OLD; + END IF; + END; + $$ + LANGUAGE 'plpgsql'; + """ + |> execute() + + """ + CREATE TRIGGER #{@trigger_name} + BEFORE + INSERT + OR UPDATE of recipients, data + OR DELETE + ON activities + FOR EACH ROW + EXECUTE PROCEDURE #{@function_name}(); + """ + |> execute() + end +end diff --git a/priv/scrubbers/default.ex b/priv/scrubbers/default.ex index c1a9e502e..53e469b64 100644 --- a/priv/scrubbers/default.ex +++ b/priv/scrubbers/default.ex @@ -58,8 +58,14 @@ defmodule Pleroma.HTML.Scrubber.Default do Meta.allow_tag_with_these_attributes(:u, []) Meta.allow_tag_with_these_attributes(:ul, []) + Meta.allow_tag_with_this_attribute_values(:p, "class", [ + # mastodon-style quote fallbacks + "quote-inline" + ]) + Meta.allow_tag_with_this_attribute_values(:span, "class", [ "h-card", + # *oma-style quote fallbacks "quote-inline", # "FEP-c16b: Formatting MFM functions" tags that Akkoma supports # NOTE: Maybe it would be better to have something like "allow `mfm-*`, diff --git a/priv/static/images/banner.png b/priv/static/images/banner.png new file mode 100644 index 000000000..aa76fdd8d Binary files /dev/null and b/priv/static/images/banner.png differ diff --git a/test/mix/tasks/pleroma/database_test.exs b/test/mix/tasks/pleroma/database_test.exs index b1de10c9b..67422b9e0 100644 --- a/test/mix/tasks/pleroma/database_test.exs +++ b/test/mix/tasks/pleroma/database_test.exs @@ -522,7 +522,6 @@ test "We don't have unexpected tables which may contain objects that are referen ["conversation_participation_recipient_ships"], ["conversation_participations"], ["conversations"], - ["counter_cache"], ["data_migration_failed_ids"], ["data_migrations"], ["deliveries"], diff --git a/test/mix/tasks/pleroma/emoji_test.exs b/test/mix/tasks/pleroma/emoji_test.exs index 6549fbb70..57105acaf 100644 --- a/test/mix/tasks/pleroma/emoji_test.exs +++ b/test/mix/tasks/pleroma/emoji_test.exs @@ -144,6 +144,12 @@ test "with default extensions", %{url: url} do name = "pack1" pack_json = "#{name}.json" files_json = "#{name}_file.json" + + on_exit(fn -> + File.rm(pack_json) + File.rm(files_json) + end) + refute File.exists?(pack_json) refute File.exists?(files_json) @@ -172,17 +178,18 @@ test "with default extensions", %{url: url} do assert File.exists?(pack_json) assert File.exists?(files_json) - - on_exit(fn -> - File.rm!(pack_json) - File.rm!(files_json) - end) end test "with custom extensions and update existing files", %{url: url} do name = "pack2" pack_json = "#{name}.json" files_json = "#{name}_file.json" + + on_exit(fn -> + File.rm(pack_json) + File.rm(files_json) + end) + refute File.exists?(pack_json) refute File.exists?(files_json) @@ -233,11 +240,6 @@ test "with custom extensions and update existing files", %{url: url} do end) assert captured =~ "#{pack_json} has been updated with the pack2 pack" - - on_exit(fn -> - File.rm!(pack_json) - File.rm!(files_json) - end) end end end diff --git a/test/mix/tasks/pleroma/refresh_counter_cache_test.exs b/test/mix/tasks/pleroma/refresh_counter_cache_test.exs deleted file mode 100644 index fe9e5cfeb..000000000 --- a/test/mix/tasks/pleroma/refresh_counter_cache_test.exs +++ /dev/null @@ -1,44 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2021 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Mix.Tasks.Pleroma.RefreshCounterCacheTest do - # Uses log capture, has to stay synchronous - use Pleroma.DataCase - alias Pleroma.Web.CommonAPI - import ExUnit.CaptureIO, only: [capture_io: 1] - import Pleroma.Factory - - test "counts statuses" do - user = insert(:user) - other_user = insert(:user) - - CommonAPI.post(user, %{visibility: "public", status: "hey"}) - - Enum.each(0..1, fn _ -> - CommonAPI.post(user, %{ - visibility: "unlisted", - status: "hey" - }) - end) - - Enum.each(0..2, fn _ -> - CommonAPI.post(user, %{ - visibility: "direct", - status: "hey @#{other_user.nickname}" - }) - end) - - Enum.each(0..3, fn _ -> - CommonAPI.post(user, %{ - visibility: "private", - status: "hey" - }) - end) - - assert capture_io(fn -> Mix.Tasks.Pleroma.RefreshCounterCache.run([]) end) =~ "Done\n" - - assert %{"direct" => 3, "private" => 4, "public" => 1, "unlisted" => 2} = - Pleroma.Stats.get_status_visibility_count() - end -end diff --git a/test/pleroma/conversation/participation_test.exs b/test/pleroma/conversation/participation_test.exs index 2bf57f539..5ef957455 100644 --- a/test/pleroma/conversation/participation_test.exs +++ b/test/pleroma/conversation/participation_test.exs @@ -29,14 +29,14 @@ test "for a new conversation or a reply, it doesn't mark the author's participat user = insert(:user) other_user = insert(:user) - {:ok, _} = + {:ok, op_activity} = CommonAPI.post(user, %{status: "Hey @#{other_user.nickname}.", visibility: "direct"}) user = User.get_cached_by_id(user.id) other_user = User.get_cached_by_id(other_user.id) [%{read: true}] = Participation.for_user(user) - [%{read: false} = participation] = Participation.for_user(other_user) + [%{read: false}] = Participation.for_user(other_user) assert Participation.unread_count(user) == 0 assert Participation.unread_count(other_user) == 1 @@ -44,7 +44,7 @@ test "for a new conversation or a reply, it doesn't mark the author's participat CommonAPI.post(other_user, %{ status: "Hey @#{user.nickname}.", visibility: "direct", - in_reply_to_conversation_id: participation.id + in_reply_to_id: op_activity.id }) user = User.get_cached_by_id(user.id) @@ -317,7 +317,7 @@ test "the conversation with the blocked user is not marked as unread on a reply" blocked = insert(:user) third_user = insert(:user) - {:ok, _direct1} = + {:ok, direct1} = CommonAPI.post(blocker, %{ status: "Hi @#{third_user.nickname}, @#{blocked.nickname}", visibility: "direct" @@ -327,27 +327,25 @@ test "the conversation with the blocked user is not marked as unread on a reply" assert [%{read: true}] = Participation.for_user(blocker) assert Participation.unread_count(blocker) == 0 - assert [blocked_participation] = Participation.for_user(blocked) # When it's a reply from the blocked user - {:ok, _direct2} = + {:ok, direct2} = CommonAPI.post(blocked, %{ - status: "reply", + status: "@#{third_user.nickname}, #{blocker.nickname} reply", visibility: "direct", - in_reply_to_conversation_id: blocked_participation.id + in_reply_to_id: direct1.id }) assert [%{read: true}] = Participation.for_user(blocker) assert Participation.unread_count(blocker) == 0 - assert [third_user_participation] = Participation.for_user(third_user) - # When it's a reply from the third user + # When it's a reply from the third user to the blocked user {:ok, _direct3} = CommonAPI.post(third_user, %{ status: "reply", visibility: "direct", - in_reply_to_conversation_id: third_user_participation.id + in_reply_to_id: direct2.id }) assert [%{read: true}] = Participation.for_user(blocker) diff --git a/test/pleroma/conversation_test.exs b/test/pleroma/conversation_test.exs index 1a947606d..4b19c2d33 100644 --- a/test/pleroma/conversation_test.exs +++ b/test/pleroma/conversation_test.exs @@ -61,8 +61,10 @@ test "it creates or updates a conversation and participations for a given DM" do jafnhar = insert(:user, local: false) tridi = insert(:user) + to = [har.nickname, jafnhar.nickname, tridi.nickname] + {:ok, activity} = - CommonAPI.post(har, %{status: "Hey @#{jafnhar.nickname}", visibility: "direct"}) + CommonAPI.post(har, %{status: "Hey @#{jafnhar.nickname}", visibility: "direct", to: to}) object = Pleroma.Object.normalize(activity, fetch: false) context = object.data["context"] @@ -83,7 +85,8 @@ test "it creates or updates a conversation and participations for a given DM" do CommonAPI.post(jafnhar, %{ status: "Hey @#{har.nickname}", visibility: "direct", - in_reply_to_status_id: activity.id + in_reply_to_status_id: activity.id, + to: to }) object = Pleroma.Object.normalize(activity, fetch: false) @@ -107,7 +110,8 @@ test "it creates or updates a conversation and participations for a given DM" do CommonAPI.post(tridi, %{ status: "Hey @#{har.nickname}", visibility: "direct", - in_reply_to_status_id: activity.id + in_reply_to_status_id: activity.id, + to: to }) object = Pleroma.Object.normalize(activity, fetch: false) diff --git a/test/pleroma/healthcheck_test.exs b/test/pleroma/healthcheck_test.exs index 9352840b5..d0fcf3b0b 100644 --- a/test/pleroma/healthcheck_test.exs +++ b/test/pleroma/healthcheck_test.exs @@ -15,7 +15,6 @@ test "system_info/0" do :active, :healthy, :idle, - :job_queue_stats, :memory_used, :pool_size ]) diff --git a/test/pleroma/http/adapter_helper_test.exs b/test/pleroma/http/adapter_helper_test.exs index 0951187aa..637df7f82 100644 --- a/test/pleroma/http/adapter_helper_test.exs +++ b/test/pleroma/http/adapter_helper_test.exs @@ -6,85 +6,49 @@ defmodule Pleroma.HTTP.AdapterHelperTest do use Pleroma.DataCase, async: true alias Pleroma.HTTP.AdapterHelper - describe "format_proxy/1" do + defp get_proxy_val(opts) do + opts + |> Keyword.get(:pools) + |> then(& &1[:default]) + |> Keyword.get(:conn_opts) + |> Keyword.get(:proxy, :undefined) + end + + defp assert_proxy_val(inp, ref) do + clear_config([:http, :proxy_url], inp) + opts = AdapterHelper.options() + assert get_proxy_val(opts) == ref + end + + describe "accepts proxy format" do test "with nil" do - assert AdapterHelper.format_proxy(nil) == nil + assert_proxy_val(nil, :undefined) end test "with string" do - assert AdapterHelper.format_proxy("http://127.0.0.1:8123") == {:http, "127.0.0.1", 8123, []} + assert_proxy_val("http://127.0.0.1:8123", {:http, "127.0.0.1", 8123, []}) end test "localhost with port" do - assert AdapterHelper.format_proxy("https://localhost:8123") == - {:https, "localhost", 8123, []} + assert_proxy_val("https://localhost:8123", {:https, "localhost", 8123, []}) end test "tuple" do - assert AdapterHelper.format_proxy({:http, "localhost", 9050}) == - {:http, "localhost", 9050, []} + assert_proxy_val({:http, "localhost", 9050}, {:http, "localhost", 9050, []}) end end - describe "maybe_add_proxy_pool/1" do - test "should do nothing with nil" do - assert AdapterHelper.maybe_add_proxy_pool([], nil) == [] - end + test "properly merges default with passed runtime config" do + clear_config([:http, :proxy_url], "http://127.0.0.1:8123") + opts = AdapterHelper.options(pools: %{default: [conn_opts: [already: "set"]]}) - test "should create pools" do - assert AdapterHelper.maybe_add_proxy_pool([], "proxy") == [ - pools: %{default: [conn_opts: [proxy: "proxy"]]} - ] - end + assert get_proxy_val(opts) == {:http, "127.0.0.1", 8123, []} - test "should not override conn_opts if set" do - assert AdapterHelper.maybe_add_proxy_pool( - [pools: %{default: [conn_opts: [already: "set"]]}], - "proxy" - ) == [ - pools: %{default: [conn_opts: [proxy: "proxy", already: "set"]]} - ] - end - end - - describe "timeout settings" do - test "should default to 5000/15000" do - options = AdapterHelper.options(%URI{host: ~c"somewhere"}) - assert options[:pool_timeout] == 5000 - assert options[:receive_timeout] == 15_000 - end - - test "pool_timeout should be overridden by :http, :pool_timeout" do - clear_config([:http, :pool_timeout], 10_000) - options = AdapterHelper.options(%URI{host: ~c"somewhere"}) - assert options[:pool_timeout] == 10_000 - end - - test "receive_timeout should be overridden by :http, :receive_timeout" do - clear_config([:http, :receive_timeout], 20_000) - options = AdapterHelper.options(%URI{host: ~c"somewhere"}) - assert options[:receive_timeout] == 20_000 - end - end - - describe "pool size settings" do - test "should get set" do - options = AdapterHelper.add_pool_size([], 50) - 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 + assert "set" == + opts + |> Keyword.get(:pools) + |> then(& &1[:default]) + |> Keyword.get(:conn_opts) + |> Keyword.get(:already) end end diff --git a/test/pleroma/http/request_builder_test.exs b/test/pleroma/http/request_builder_test.exs deleted file mode 100644 index 433beaac1..000000000 --- a/test/pleroma/http/request_builder_test.exs +++ /dev/null @@ -1,93 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2021 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.HTTP.RequestBuilderTest do - use ExUnit.Case - use Pleroma.Tests.Helpers - alias Pleroma.HTTP.Request - alias Pleroma.HTTP.RequestBuilder - - describe "headers/2" do - test "don't send pleroma user agent" do - assert RequestBuilder.headers(%Request{}, []) == %Request{headers: []} - end - - test "send pleroma user agent" do - clear_config([:http, :send_user_agent], true) - clear_config([:http, :user_agent], :default) - - assert RequestBuilder.headers(%Request{}, []) == %Request{ - headers: [{"user-agent", Pleroma.Application.user_agent()}] - } - end - - test "send custom user agent" do - clear_config([:http, :send_user_agent], true) - clear_config([:http, :user_agent], "totally-not-pleroma") - - assert RequestBuilder.headers(%Request{}, []) == %Request{ - headers: [{"user-agent", "totally-not-pleroma"}] - } - end - end - - describe "add_param/4" do - test "add file parameter" do - assert match?( - %Request{ - body: %Tesla.Multipart{ - boundary: _, - content_type_params: [], - parts: [ - %Tesla.Multipart.Part{ - body: %File.Stream{ - line_or_bytes: 2048, - modes: [:raw, :read_ahead, :binary], - path: "some-path/filename.png", - raw: true - }, - dispositions: [name: "filename.png", filename: "filename.png"], - headers: [] - } - ] - } - }, - RequestBuilder.add_param( - %Request{}, - :file, - "filename.png", - "some-path/filename.png" - ) - ) - end - - test "add key to body" do - %{ - body: %Tesla.Multipart{ - boundary: _, - content_type_params: [], - parts: [ - %Tesla.Multipart.Part{ - body: "\"someval\"", - dispositions: [name: "somekey"], - headers: [{"content-type", "application/json"}] - } - ] - } - } = RequestBuilder.add_param(%{}, :body, "somekey", "someval") - end - - test "add form parameter" do - assert RequestBuilder.add_param(%{}, :form, "somename", "someval") == %{ - body: %{"somename" => "someval"} - } - end - - test "add for location" do - assert RequestBuilder.add_param(%{}, :some_location, "somekey", "someval") == %{ - some_location: [{"somekey", "someval"}] - } - end - end -end diff --git a/test/pleroma/instances_test.exs b/test/pleroma/instances_test.exs index 744ee8df3..03f9e4e97 100644 --- a/test/pleroma/instances_test.exs +++ b/test/pleroma/instances_test.exs @@ -4,7 +4,6 @@ defmodule Pleroma.InstancesTest do alias Pleroma.Instances - alias Pleroma.Instances.Instance use Pleroma.DataCase @@ -122,21 +121,4 @@ test "keeps unreachable url or host unreachable" do refute Instances.reachable?(host) end end - - describe "set_request_signatures/1" do - test "sets instance has request signatures" do - host = "domain.com" - - {:ok, instance} = Instances.set_request_signatures(host) - assert instance.has_request_signatures - - {:ok, cached_instance} = Instance.get_cached_by_url(host) - assert cached_instance.has_request_signatures - end - - test "returns error status on non-binary input" do - assert {:error, _} = Instances.set_request_signatures(nil) - assert {:error, _} = Instances.set_request_signatures(1) - end - end end diff --git a/test/pleroma/job_queue_monitor_test.exs b/test/pleroma/job_queue_monitor_test.exs deleted file mode 100644 index ab81dcb1e..000000000 --- a/test/pleroma/job_queue_monitor_test.exs +++ /dev/null @@ -1,70 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2021 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.JobQueueMonitorTest do - use ExUnit.Case, async: true - - alias Pleroma.JobQueueMonitor - - @success {:process_event, :success, 1337, - %{ - args: %{"op" => "refresh_subscriptions"}, - attempt: 1, - id: 339, - max_attempts: 5, - queue: "federator_outgoing", - worker: "Pleroma.Workers.SubscriberWorker" - }} - - @failure {:process_event, :failure, 22_521_134, - %{ - args: %{"op" => "force_password_reset", "user_id" => "9nJG6n6Nbu7tj9GJX6"}, - attempt: 1, - error: %RuntimeError{message: "oops"}, - id: 345, - kind: :exception, - max_attempts: 1, - queue: "background", - stack: [ - {Pleroma.Workers.BackgroundWorker, :perform, 2, - [file: ~c"lib/pleroma/workers/background_worker.ex", line: 31]}, - {Oban.Queue.Executor, :safe_call, 1, - [file: ~c"lib/oban/queue/executor.ex", line: 42]}, - {:timer, :tc, 3, [file: ~c"timer.erl", line: 197]}, - {Oban.Queue.Executor, :call, 2, [file: ~c"lib/oban/queue/executor.ex", line: 23]}, - {Task.Supervised, :invoke_mfa, 2, [file: ~c"lib/task/supervised.ex", line: 90]}, - {:proc_lib, :init_p_do_apply, 3, [file: ~c"proc_lib.erl", line: 249]} - ], - worker: "Pleroma.Workers.BackgroundWorker" - }} - - test "stats/0" do - assert %{processed_jobs: _, queues: _, workers: _} = JobQueueMonitor.stats() - end - - test "handle_cast/2" do - state = %{workers: %{}, queues: %{}, processed_jobs: 0} - - assert {:noreply, state} = JobQueueMonitor.handle_cast(@success, state) - assert {:noreply, state} = JobQueueMonitor.handle_cast(@failure, state) - assert {:noreply, state} = JobQueueMonitor.handle_cast(@success, state) - assert {:noreply, state} = JobQueueMonitor.handle_cast(@failure, state) - - assert state == %{ - processed_jobs: 4, - queues: %{ - "background" => %{failure: 2, processed_jobs: 2, success: 0}, - "federator_outgoing" => %{failure: 0, processed_jobs: 2, success: 2} - }, - workers: %{ - "Pleroma.Workers.BackgroundWorker" => %{ - "force_password_reset" => %{failure: 2, processed_jobs: 2, success: 0} - }, - "Pleroma.Workers.SubscriberWorker" => %{ - "refresh_subscriptions" => %{failure: 0, processed_jobs: 2, success: 2} - } - } - } - end -end diff --git a/test/pleroma/notification_test.exs b/test/pleroma/notification_test.exs index c3ba399a6..add80e5dd 100644 --- a/test/pleroma/notification_test.exs +++ b/test/pleroma/notification_test.exs @@ -1019,22 +1019,6 @@ test "repeating an activity which is already deleted does not generate a notific assert Enum.empty?(Notification.for_user(user)) end - test "replying to a deleted post without tagging does not generate a notification" do - user = insert(:user) - other_user = insert(:user) - - {:ok, activity} = CommonAPI.post(user, %{status: "test post"}) - {:ok, _deletion_activity} = CommonAPI.delete(activity.id, user) - - {:ok, _reply_activity} = - CommonAPI.post(other_user, %{ - status: "test reply", - in_reply_to_status_id: activity.id - }) - - assert Enum.empty?(Notification.for_user(user)) - end - test "notifications are deleted if a local user is deleted" do user = insert(:user) other_user = insert(:user) diff --git a/test/pleroma/object/fetcher_test.exs b/test/pleroma/object/fetcher_test.exs index 6b87635eb..8472848a5 100644 --- a/test/pleroma/object/fetcher_test.exs +++ b/test/pleroma/object/fetcher_test.exs @@ -478,7 +478,7 @@ test "it can refetch pruned objects" do Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367") - assert called(Pleroma.Signature.sign(:_, :_)) + assert called(Pleroma.Signature.sign(:_, :_, :_)) end test_with_mock "it doesn't sign fetches when not configured to do so", @@ -489,7 +489,7 @@ test "it can refetch pruned objects" do Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367") - refute called(Pleroma.Signature.sign(:_, :_)) + refute called(Pleroma.Signature.sign(:_, :_, :_)) end end diff --git a/test/pleroma/search/meilisearch_test.exs b/test/pleroma/search/meilisearch_test.exs index 0a39b2004..32b01ce46 100644 --- a/test/pleroma/search/meilisearch_test.exs +++ b/test/pleroma/search/meilisearch_test.exs @@ -17,11 +17,6 @@ defmodule Pleroma.Search.MeilisearchTest do alias Pleroma.Web.CommonAPI alias Pleroma.Workers.SearchIndexingWorker - setup_all do - Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) - :ok - end - describe "meilisearch" do setup do: clear_config([Pleroma.Search, :module], Meilisearch) @@ -34,14 +29,16 @@ defmodule Pleroma.Search.MeilisearchTest do meili_put: fn u, a -> passthrough([u, a]) end ]} ], - context, - do: {:ok, context} - ) + context + ) do + Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end) + {:ok, context} + end test "indexes a local post on creation" do user = insert(:user) - mock_global(fn + mock(fn %{method: :put, url: "http://127.0.0.1:7700/indexes/objects/documents", body: body} -> assert match?( [%{"content" => "guys i just don't wanna leave the swamp"}], @@ -94,7 +91,7 @@ test "doesn't index posts that are not public" do test "deletes posts from index when deleted locally" do user = insert(:user) - mock_global(fn + mock(fn %{method: :put, url: "http://127.0.0.1:7700/indexes/objects/documents", body: body} -> assert match?( [%{"content" => "guys i just don't wanna leave the swamp"}], diff --git a/test/pleroma/signature_test.exs b/test/pleroma/signature_test.exs index 731605804..b3967ab12 100644 --- a/test/pleroma/signature_test.exs +++ b/test/pleroma/signature_test.exs @@ -8,9 +8,10 @@ defmodule Pleroma.SignatureTest do import Pleroma.Factory import Tesla.Mock - import Mock + alias HTTPSignatures.HTTPKey alias Pleroma.Signature + alias Pleroma.User.SigningKey setup do mock(fn env -> apply(HttpRequestMock, :request, [env]) end) @@ -28,20 +29,27 @@ defmodule Pleroma.SignatureTest do 65_537 } - defp make_fake_signature(key_id), do: "keyId=\"#{key_id}\"" + defp keyid(user = %Pleroma.User{}), do: keyid(user.ap_id) + defp keyid(user_ap_id), do: user_ap_id <> "#main-key" - defp make_fake_conn(key_id), - do: %Plug.Conn{req_headers: %{"signature" => make_fake_signature(key_id <> "#main-key")}} + defp assert_key(retval, refkey, refuser) do + assert match?( + {:ok, %HTTPKey{key: ^refkey, user_data: %{"key_user" => %Pleroma.User{}}}}, + retval + ) + + {:ok, key} = retval + # Avoid comparison failures from (not) loaded Ecto associations etc + assert refuser.id == key.user_data["key_user"].id + end describe "fetch_public_key/1" do test "it returns the key" do - expected_result = {:ok, @rsa_public_key} - user = insert(:user) |> with_signing_key(public_key: @public_key) - assert Signature.fetch_public_key(make_fake_conn(user.ap_id)) == expected_result + assert_key(Signature.fetch_public_key(keyid(user), nil), @rsa_public_key, user) end test "it returns error if public key is nil" do @@ -50,7 +58,7 @@ test "it returns error if public key is nil" do key_id = user.ap_id <> "#main-key" Tesla.Mock.mock(fn %{url: ^key_id} -> {:ok, %{status: 404}} end) - assert {:error, _} = Signature.fetch_public_key(make_fake_conn(user.ap_id)) + assert {:error, _} = Signature.fetch_public_key(keyid(user), nil) end end @@ -60,16 +68,17 @@ test "it returns key" do ap_id = "https://mastodon.social/users/lambadalambda" %Pleroma.User{signing_key: sk} = + user = Pleroma.User.get_or_fetch_by_ap_id(ap_id) |> then(fn {:ok, u} -> u end) - |> Pleroma.User.SigningKey.load_key() + |> SigningKey.load_key() {:ok, _} = %{sk | public_key: "-----BEGIN PUBLIC KEY-----\nasdfghjkl"} |> Ecto.Changeset.change() |> Pleroma.Repo.update() - assert Signature.refetch_public_key(make_fake_conn(ap_id)) == {:ok, @rsa_public_key} + assert_key(Signature.refetch_public_key(keyid(ap_id), nil), @rsa_public_key, user) end end @@ -111,30 +120,20 @@ test "it returns signature headers" do |> with_signing_key(private_key: @private_key) headers = %{ - host: "test.test", - "content-length": "100" + "host" => "test.test", + "content-length" => "100", + "date" => "Fri, 23 Aug 2019 18:11:24 GMT", + "digest" => "SHA-256=a29cdd711788c5118a2256c00d31519e0a5a0d4b144214e012f81e67b80b0ec1", + "(request-target)" => "post https://example.com/inbox" } assert_signature_equal( Signature.sign( - user, + user.signing_key, headers ), - "keyId=\"https://mastodon.social/users/lambadalambda#main-key\",algorithm=\"rsa-sha256\",headers=\"content-length host\",signature=\"sibUOoqsFfTDerquAkyprxzDjmJm6erYc42W5w1IyyxusWngSinq5ILTjaBxFvfarvc7ci1xAi+5gkBwtshRMWm7S+Uqix24Yg5EYafXRun9P25XVnYBEIH4XQ+wlnnzNIXQkU3PU9e6D8aajDZVp3hPJNeYt1gIPOA81bROI8/glzb1SAwQVGRbqUHHHKcwR8keiR/W2h7BwG3pVRy4JgnIZRSW7fQogKedDg02gzRXwUDFDk0pr2p3q6bUWHUXNV8cZIzlMK+v9NlyFbVYBTHctAR26GIAN6Hz0eV0mAQAePHDY1mXppbA8Gpp6hqaMuYfwifcXmcc+QFm4e+n3A==\"" + ~s|keyId="https://mastodon.social/users/lambadalambda#main-key",algorithm="rsa-sha256",headers="(request-target) content-length date digest host",signature="fhOT6IBThnCo6rv2Tv8BRXLV7LvVf/7wTX/bbPLtdq5A4GUqrmXUcY5p77jQ6NU9IRIVczeeStxQV6TrHqk/qPdqQOzDcB6cWsSfrB1gsTinBbAWdPzQYqUOTl+Minqn2RERAfPebKYr9QGa0sTODDHvze/UFPuL8a1lDO2VQE0lRCdg49Igr8pGl/CupUx8Fb874omqP0ba3M+siuKEwo02m9hHcbZUeLSN0ZVdvyTMttyqPM1BfwnFXkaQRAblLTyzt4Fv2+fTN+zPipSxJl1YIo1TsmwNq9klqImpjh8NHM3MJ5eZxTZ109S6Q910n1Lm46V/SqByDaYeg9g7Jw=="| ) end end - - describe "signed_date" do - test "it returns formatted current date" do - with_mock(NaiveDateTime, utc_now: fn -> ~N[2019-08-23 18:11:24.822233] end) do - assert Signature.signed_date() == "Fri, 23 Aug 2019 18:11:24 GMT" - end - end - - test "it returns formatted date" do - assert Signature.signed_date(~N[2019-08-23 08:11:24.822233]) == - "Fri, 23 Aug 2019 08:11:24 GMT" - end - end end diff --git a/test/pleroma/stats_test.exs b/test/pleroma/stats_test.exs index fd3195969..fb4e41c38 100644 --- a/test/pleroma/stats_test.exs +++ b/test/pleroma/stats_test.exs @@ -8,7 +8,6 @@ defmodule Pleroma.StatsTest do import Pleroma.Factory alias Pleroma.Stats - alias Pleroma.Web.CommonAPI describe "user count" do test "it ignores internal users" do @@ -19,104 +18,4 @@ test "it ignores internal users" do assert match?(%{stats: %{user_count: 1}}, Stats.calculate_stat_data()) end end - - describe "status visibility sum count" do - test "on new status" do - instance2 = "instance2.tld" - user = insert(:user) - other_user = insert(:user, %{ap_id: "https://#{instance2}/@actor"}) - - CommonAPI.post(user, %{visibility: "public", status: "hey"}) - - Enum.each(0..1, fn _ -> - CommonAPI.post(user, %{ - visibility: "unlisted", - status: "hey" - }) - end) - - Enum.each(0..2, fn _ -> - CommonAPI.post(user, %{ - visibility: "direct", - status: "hey @#{other_user.nickname}" - }) - end) - - Enum.each(0..3, fn _ -> - CommonAPI.post(user, %{ - visibility: "private", - status: "hey" - }) - end) - - assert %{"direct" => 3, "private" => 4, "public" => 1, "unlisted" => 2} = - Stats.get_status_visibility_count() - end - - test "on status delete" do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{visibility: "public", status: "hey"}) - assert %{"public" => 1} = Stats.get_status_visibility_count() - CommonAPI.delete(activity.id, user) - assert %{"public" => 0} = Stats.get_status_visibility_count() - end - - test "on status visibility update" do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{visibility: "public", status: "hey"}) - assert %{"public" => 1, "private" => 0} = Stats.get_status_visibility_count() - {:ok, _} = CommonAPI.update_activity_scope(activity.id, %{visibility: "private"}) - assert %{"public" => 0, "private" => 1} = Stats.get_status_visibility_count() - end - - test "doesn't count unrelated activities" do - user = insert(:user) - other_user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{visibility: "public", status: "hey"}) - _ = CommonAPI.follow(user, other_user) - CommonAPI.favorite(other_user, activity.id) - CommonAPI.repeat(activity.id, other_user) - - assert %{"direct" => 0, "private" => 0, "public" => 1, "unlisted" => 0} = - Stats.get_status_visibility_count() - end - end - - describe "status visibility by instance count" do - test "single instance" do - local_instance = Pleroma.Web.Endpoint.url() |> String.split("//") |> Enum.at(1) - instance2 = "instance2.tld" - user1 = insert(:user) - user2 = insert(:user, %{ap_id: "https://#{instance2}/@actor"}) - - CommonAPI.post(user1, %{visibility: "public", status: "hey"}) - - Enum.each(1..5, fn _ -> - CommonAPI.post(user1, %{ - visibility: "unlisted", - status: "hey" - }) - end) - - Enum.each(1..10, fn _ -> - CommonAPI.post(user1, %{ - visibility: "direct", - status: "hey @#{user2.nickname}" - }) - end) - - Enum.each(1..20, fn _ -> - CommonAPI.post(user2, %{ - visibility: "private", - status: "hey" - }) - end) - - assert %{"direct" => 10, "private" => 0, "public" => 1, "unlisted" => 5} = - Stats.get_status_visibility_count(local_instance) - - assert %{"direct" => 0, "private" => 20, "public" => 0, "unlisted" => 0} = - Stats.get_status_visibility_count(instance2) - end - end end diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index 890109a4a..eca60d90f 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -28,6 +28,12 @@ defmodule Pleroma.UserTest do setup do: clear_config([:instance, :account_activation_required]) + defp get_pending_follower_user_ids_for(%User{} = user) do + User.get_follow_requests_query(user) + |> select([r], r.follower_id) + |> Repo.all() + end + describe "service actors" do test "returns updated invisible actor" do uri = "#{Pleroma.Web.Endpoint.url()}/relay" @@ -181,14 +187,13 @@ test "returns all pending follow requests" do unlocked = insert(:user) locked = insert(:user, is_locked: true) follower = insert(:user) + follower_id = follower.id CommonAPI.follow(follower, unlocked) CommonAPI.follow(follower, locked) - assert [] = User.get_follow_requests(unlocked) - assert [activity] = User.get_follow_requests(locked) - - assert activity + assert [] = get_pending_follower_user_ids_for(unlocked) + assert [^follower_id] = get_pending_follower_user_ids_for(locked) end test "doesn't return already accepted or duplicate follow requests" do @@ -202,7 +207,8 @@ test "doesn't return already accepted or duplicate follow requests" do Pleroma.FollowingRelationship.update(accepted_follower, locked, :follow_accept) - assert [^pending_follower] = User.get_follow_requests(locked) + pending_follower_id = pending_follower.id + assert [^pending_follower_id] = get_pending_follower_user_ids_for(locked) end test "doesn't return follow requests for deactivated accounts" do @@ -212,7 +218,7 @@ test "doesn't return follow requests for deactivated accounts" do CommonAPI.follow(pending_follower, locked) refute pending_follower.is_active - assert [] = User.get_follow_requests(locked) + assert [] = get_pending_follower_user_ids_for(locked) end test "clears follow requests when requester is blocked" do @@ -220,10 +226,10 @@ test "clears follow requests when requester is blocked" do follower = insert(:user) CommonAPI.follow(follower, followed) - assert [_activity] = User.get_follow_requests(followed) + assert [_activity] = get_pending_follower_user_ids_for(followed) {:ok, _user_relationship} = User.block(followed, follower) - assert [] = User.get_follow_requests(followed) + assert [] = get_pending_follower_user_ids_for(followed) end test "follow_all follows mutliple users" do @@ -1126,7 +1132,7 @@ test "expiring" do user = insert(:user) muted_user = insert(:user) - {:ok, _user_relationships} = User.mute(user, muted_user, %{expires_in: 60}) + {:ok, _user_relationships} = User.mute(user, muted_user, %{duration: 60}) assert User.mutes?(user, muted_user) worker = Pleroma.Workers.MuteExpireWorker @@ -1662,7 +1668,7 @@ test "it deactivates a user, all follow relationships and all activities", %{use refute User.following?(follower, user) assert %{is_active: false} = User.get_by_id(user.id) - assert [] == User.get_follow_requests(locked_user) + assert [] == get_pending_follower_user_ids_for(locked_user) user_activities = user.ap_id 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 7665471ca..119f019e2 100644 --- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs @@ -133,6 +133,28 @@ test "it returns a json representation of the user with accept application/ld+js assert json_response(conn, 200) == UserView.render("user.json", %{user: user}) end + test "it returns a minimal json representation of the user when autfetch is enabled but no signature", + %{ + conn: conn + } do + clear_config([:activitypub, :authorized_fetch_mode], true) + + user = insert(:user) |> with_signing_key() + + conn = + conn + |> assign(:valid_signature, false) + |> put_req_header( + "accept", + "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"" + ) + |> get("/users/#{user.nickname}") + + user = User.get_cached_by_id(user.id) + + assert json_response(conn, 200) == UserView.render("stripped_user.json", %{user: user}) + end + test "it returns 404 for remote users", %{ conn: conn } do @@ -426,6 +448,75 @@ test "cached purged after object deletion", %{conn: conn} do end end + describe "/objects/:uuid/replies" do + test "it renders the top-level collection", %{ + conn: conn + } do + user = insert(:user) + note = insert(:note_activity) + note = Pleroma.Activity.get_by_id_with_object(note.id) + uuid = String.split(note.object.data["id"], "/") |> List.last() + + {:ok, _} = + CommonAPI.post(user, %{status: "reply1", in_reply_to_status_id: note.id}) + + conn = + conn + |> put_req_header("accept", "application/activity+json") + |> get("/objects/#{uuid}/replies") + + assert match?( + %{ + "id" => _, + "type" => "OrderedCollection", + "totalItems" => 1, + "first" => %{ + "id" => _, + "type" => "OrderedCollectionPage", + "orderedItems" => [_] + } + }, + json_response(conn, 200) + ) + end + + test "it renders a collection page", %{ + conn: conn + } do + user = insert(:user) + note = insert(:note_activity) + note = Pleroma.Activity.get_by_id_with_object(note.id) + uuid = String.split(note.object.data["id"], "/") |> List.last() + + {:ok, r1} = + CommonAPI.post(user, %{status: "reply1", in_reply_to_status_id: note.id}) + + {:ok, r2} = + CommonAPI.post(user, %{status: "reply2", in_reply_to_status_id: note.id}) + + {:ok, _} = + CommonAPI.post(user, %{status: "reply3", in_reply_to_status_id: note.id}) + + conn = + conn + |> put_req_header("accept", "application/activity+json") + |> get("/objects/#{uuid}/replies?page=true&min_id=#{r1.object.id}&limit=1") + + expected_uris = [r2.object.data["id"]] + + assert match?( + %{ + "id" => _, + "type" => "OrderedCollectionPage", + "prev" => _, + "next" => _, + "orderedItems" => ^expected_uris + }, + json_response(conn, 200) + ) + end + end + describe "/activities/:uuid" do test "it doesn't return a local-only activity", %{conn: conn} do user = insert(:user) @@ -555,14 +646,12 @@ test "cached purged after activity deletion", %{conn: conn} do describe "/inbox" do test "it inserts an incoming activity into the database", %{conn: conn} do data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!() + {:ok, actor} = User.get_or_fetch_by_ap_id("http://mastodon.example.org/users/admin") conn = conn |> assign(:valid_signature, true) - |> put_req_header( - "signature", - "keyId=\"http://mastodon.example.org/users/admin#main-key\"" - ) + |> assign(:signature_user, actor) |> put_req_header("content-type", "application/activity+json") |> post("/inbox", data) @@ -592,7 +681,7 @@ test "it inserts an incoming activity into the database" <> conn = conn |> assign(:valid_signature, true) - |> put_req_header("signature", "keyId=\"#{user.signing_key.key_id}\"") + |> assign(:signature_user, user) |> put_req_header("content-type", "application/activity+json") |> post("/inbox", data) @@ -617,7 +706,7 @@ test "it clears `unreachable` federation status of the sender", %{conn: conn} do conn = conn |> assign(:valid_signature, true) - |> put_req_header("signature", "keyId=\"#{sender.signing_key.key_id}\"") + |> assign(:signature_user, sender) |> put_req_header("content-type", "application/activity+json") |> post("/inbox", data) @@ -642,7 +731,7 @@ test "accept follow activity", %{conn: conn} do assert "ok" == conn |> assign(:valid_signature, true) - |> put_req_header("signature", "keyId=\"#{followed_relay.ap_id}#main-key\"") + |> assign(:signature_user, followed_relay) |> put_req_header("content-type", "application/activity+json") |> post("/inbox", accept) |> json_response(200) @@ -681,10 +770,11 @@ test "accepts Add/Remove activities", %{conn: conn} do actor = "https://example.com/users/lain" key_id = "#{actor}#main-key" - insert(:user, - ap_id: actor, - featured_address: "https://example.com/users/lain/collections/featured" - ) + sender = + insert(:user, + ap_id: actor, + featured_address: "https://example.com/users/lain/collections/featured" + ) Tesla.Mock.mock(fn %{ @@ -741,7 +831,7 @@ test "accepts Add/Remove activities", %{conn: conn} do assert "ok" == conn |> assign(:valid_signature, true) - |> put_req_header("signature", "keyId=\"#{actor}#main-key\"") + |> assign(:signature_user, sender) |> put_req_header("content-type", "application/activity+json") |> post("/inbox", data) |> json_response(200) @@ -764,7 +854,7 @@ test "accepts Add/Remove activities", %{conn: conn} do assert "ok" == conn |> assign(:valid_signature, true) - |> put_req_header("signature", "keyId=\"#{actor}#main-key\"") + |> assign(:signature_user, user) |> put_req_header("content-type", "application/activity+json") |> post("/inbox", data) |> json_response(200) @@ -863,7 +953,7 @@ test "mastodon pin/unpin", %{conn: conn} do assert "ok" == conn |> assign(:valid_signature, true) - |> put_req_header("signature", "keyId=\"#{sender.signing_key.key_id}\"") + |> assign(:signature_user, sender) |> put_req_header("content-type", "application/activity+json") |> post("/inbox", data) |> json_response(200) @@ -883,7 +973,7 @@ test "mastodon pin/unpin", %{conn: conn} do assert "ok" == conn |> assign(:valid_signature, true) - |> put_req_header("signature", "keyId=\"#{actor}#main-key\"") + |> assign(:signature_user, sender) |> put_req_header("content-type", "application/activity+json") |> post("/inbox", data) |> json_response(200) @@ -912,10 +1002,12 @@ test "it inserts an incoming activity into the database", %{conn: conn, data: da |> Map.put("bcc", [user.ap_id]) |> Kernel.put_in(["object", "bcc"], [user.ap_id]) + {:ok, sender} = User.get_or_fetch_by_ap_id(data["actor"]) + conn = conn |> assign(:valid_signature, true) - |> put_req_header("signature", "keyId=\"#{data["actor"]}#main-key\"") + |> assign(:signature_user, sender) |> put_req_header("content-type", "application/activity+json") |> post("/users/#{user.nickname}/inbox", data) @@ -936,10 +1028,12 @@ test "it accepts messages with to as string instead of array", %{conn: conn, dat |> Kernel.put_in(["object", "to"], user.ap_id) |> Kernel.put_in(["object", "cc"], []) + {:ok, sender} = User.get_or_fetch_by_ap_id(data["actor"]) + conn = conn |> assign(:valid_signature, true) - |> put_req_header("signature", "keyId=\"#{data["actor"]}#main-key\"") + |> assign(:signature_user, sender) |> put_req_header("content-type", "application/activity+json") |> post("/users/#{user.nickname}/inbox", data) @@ -958,10 +1052,12 @@ test "it accepts messages with cc as string instead of array", %{conn: conn, dat |> Kernel.put_in(["object", "to"], []) |> Kernel.put_in(["object", "cc"], user.ap_id) + {:ok, sender} = User.get_or_fetch_by_ap_id(data["actor"]) + conn = conn |> assign(:valid_signature, true) - |> put_req_header("signature", "keyId=\"#{data["actor"]}#main-key\"") + |> assign(:signature_user, sender) |> put_req_header("content-type", "application/activity+json") |> post("/users/#{user.nickname}/inbox", data) @@ -985,10 +1081,12 @@ test "it accepts messages with bcc as string instead of array", %{conn: conn, da |> Kernel.put_in(["object", "cc"], []) |> Kernel.put_in(["object", "bcc"], user.ap_id) + {:ok, sender} = User.get_or_fetch_by_ap_id(data["actor"]) + conn = conn |> assign(:valid_signature, true) - |> put_req_header("signature", "keyId=\"#{data["actor"]}#main-key\"") + |> assign(:signature_user, sender) |> put_req_header("content-type", "application/activity+json") |> post("/users/#{user.nickname}/inbox", data) @@ -1019,7 +1117,7 @@ test "it accepts announces with to as string instead of array", %{conn: conn} do conn = conn |> assign(:valid_signature, true) - |> put_req_header("signature", "keyId=\"#{announcer.signing_key.key_id}\"") + |> assign(:signature_user, announcer) |> put_req_header("content-type", "application/activity+json") |> post("/users/#{user.nickname}/inbox", data) @@ -1053,7 +1151,7 @@ test "it accepts messages from actors that are followed by the user", %{ conn = conn |> assign(:valid_signature, true) - |> put_req_header("signature", "keyId=\"#{actor.signing_key.key_id}\"") + |> assign(:signature_user, actor) |> put_req_header("content-type", "application/activity+json") |> post("/users/#{recipient.nickname}/inbox", data) @@ -1103,7 +1201,7 @@ test "it clears `unreachable` federation status of the sender", %{conn: conn, da conn = conn |> assign(:valid_signature, true) - |> put_req_header("signature", "keyId=\"#{user.signing_key.key_id}\"") + |> assign(:signature_user, user) |> put_req_header("content-type", "application/activity+json") |> post("/users/#{user.nickname}/inbox", data) @@ -1143,7 +1241,7 @@ test "it removes all follower collections but actor's", %{conn: conn} do conn |> assign(:valid_signature, true) - |> put_req_header("signature", "keyId=\"#{actor.signing_key.key_id}\"") + |> assign(:signature_user, actor) |> put_req_header("content-type", "application/activity+json") |> post("/users/#{recipient.nickname}/inbox", data) |> json_response(200) @@ -1239,7 +1337,7 @@ test "forwarded report", %{conn: conn} do conn |> assign(:valid_signature, true) - |> put_req_header("signature", "keyId=\"#{actor.signing_key.key_id}\"") + |> assign(:signature_user, actor) |> put_req_header("content-type", "application/activity+json") |> post("/users/#{reported_user.nickname}/inbox", data) |> json_response(200) @@ -1260,39 +1358,15 @@ test "forwarded report from mastodon", %{conn: conn} do admin = insert(:user, is_admin: true) actor = insert(:user, local: false) remote_domain = URI.parse(actor.ap_id).host - remote_actor = "https://#{remote_domain}/actor" [reported_user, another] = insert_list(2, :user) note = insert(:note_activity, user: reported_user) Pleroma.Web.CommonAPI.favorite(another, note.id) - mock_json_body = - "test/fixtures/mastodon/application_actor.json" - |> File.read!() - |> String.replace("{{DOMAIN}}", remote_domain) - - key_url = "#{remote_actor}#main-key" - - Tesla.Mock.mock(fn - %{url: ^remote_actor} -> - %Tesla.Env{ - status: 200, - body: mock_json_body, - headers: [{"content-type", "application/activity+json"}] - } - - %{url: ^key_url} -> - %Tesla.Env{ - status: 200, - body: mock_json_body, - headers: [{"content-type", "application/activity+json"}] - } - end) - data = %{ "@context" => "https://www.w3.org/ns/activitystreams", - "actor" => remote_actor, + "actor" => actor.ap_id, "content" => "test report", "id" => "https://#{remote_domain}/e3b12fd1-948c-446e-b93b-a5e67edbe1d8", "object" => [ @@ -1304,7 +1378,7 @@ test "forwarded report from mastodon", %{conn: conn} do conn |> assign(:valid_signature, true) - |> put_req_header("signature", "keyId=\"#{remote_actor}#main-key\"") + |> assign(:signature_user, actor) |> put_req_header("content-type", "application/activity+json") |> post("/users/#{reported_user.nickname}/inbox", data) |> json_response(200) diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs index 7990b7ef5..b8a02a16a 100644 --- a/test/pleroma/web/activity_pub/activity_pub_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_test.exs @@ -172,7 +172,7 @@ test "it excludes by the appropriate visibility" do end end - describe "building a user from his ap id" do + describe "building a user from AP id" do test "it returns a user" do user_id = "http://mastodon.example.org/users/admin" {:ok, user} = ActivityPub.make_user_from_ap_id(user_id) @@ -274,19 +274,11 @@ test "works for actors with malformed attachment fields" do assert [] = user.fields end - test "fetches user featured collection" do + defp test_featured(inlined) do ap_id = "https://example.com/users/lain" featured_url = "https://example.com/users/lain/collections/featured" - user_data = - "test/fixtures/users_mock/user.json" - |> File.read!() - |> String.replace("{{nickname}}", "lain") - |> Jason.decode!() - |> Map.put("featured", featured_url) - |> Jason.encode!() - object_id = Ecto.UUID.generate() featured_data = @@ -296,6 +288,16 @@ test "fetches user featured collection" do |> String.replace("{{nickname}}", "lain") |> String.replace("{{object_id}}", object_id) + featured_ref = if inlined, do: Jason.decode!(featured_data), else: featured_url + + user_data = + "test/fixtures/users_mock/user.json" + |> File.read!() + |> String.replace("{{nickname}}", "lain") + |> Jason.decode!() + |> Map.put("featured", featured_ref) + |> Jason.encode!() + object_url = "https://example.com/objects/#{object_id}" object_data = @@ -349,6 +351,14 @@ test "fetches user featured collection" do assert %{data: %{"id" => ^object_url}} = Object.get_by_ap_id(object_url) end + + test "fetches user featured collection by bare id" do + test_featured(false) + end + + test "fetches user featured collection when embedded" do + test_featured(true) + end end test "fetches user featured collection using the first property" do @@ -385,7 +395,7 @@ test "fetches user featured collection using the first property" do } end) - {:ok, data} = ActivityPub.fetch_and_prepare_featured_from_ap_id(featured_url) + {:ok, ^featured_url, data} = ActivityPub.process_featured_collection(featured_url) assert Map.has_key?(data, "http://inserted") end @@ -419,7 +429,7 @@ test "fetches user featured when it has string IDs" do } end) - {:ok, %{}} = ActivityPub.fetch_and_prepare_featured_from_ap_id(featured_url) + {:ok, ^featured_url, %{}} = ActivityPub.process_featured_collection(featured_url) end test "it fetches the appropriate tag-restricted posts" do @@ -1818,12 +1828,12 @@ test "returns a favourite activities sorted by adds to favorite" do {:ok, _} = CommonAPI.favorite(other_user, a4.id) {:ok, _} = CommonAPI.favorite(user, a1.id) {:ok, _} = CommonAPI.favorite(other_user, a1.id) - result = ActivityPub.fetch_favourites(user) + result = ActivityPub.fetch_favourited_with_fav_id(user) - assert Enum.map(result, & &1.id) == [a1.id, a5.id, a3.id, a4.id] + assert Enum.map(result, & &1.entry.id) == [a1.id, a5.id, a3.id, a4.id] - result = ActivityPub.fetch_favourites(user, %{limit: 2}) - assert Enum.map(result, & &1.id) == [a1.id, a5.id] + result = ActivityPub.fetch_favourited_with_fav_id(user, %{limit: 2}) + assert Enum.map(result, & &1.entry.id) == [a1.id, a5.id] end end @@ -2661,9 +2671,9 @@ test "allow fetching of accounts with an empty string name field" do assert user.name == " " end - test "pin_data_from_featured_collection will ignore unsupported values" do - assert %{} == - ActivityPub.pin_data_from_featured_collection(%{ + test "process_featured_collection will ignore unsupported values" do + assert {:error, :invalid_type} == + ActivityPub.process_featured_collection(%{ "type" => "CollectionThatIsNotRealAndCannotHurtMe", "first" => "https://social.example/users/alice/collections/featured?page=true" }) diff --git a/test/pleroma/web/activity_pub/mrf/normalize_markup_test.exs b/test/pleroma/web/activity_pub/mrf/normalize_markup_test.exs index 739d9b49c..5c79de983 100644 --- a/test/pleroma/web/activity_pub/mrf/normalize_markup_test.exs +++ b/test/pleroma/web/activity_pub/mrf/normalize_markup_test.exs @@ -32,10 +32,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkupTest do """ test "it filter html tags" do - message = %{"type" => "Create", "object" => %{"content" => @html_sample}} + message = %{ + "type" => "Create", + "object" => %{"content" => @html_sample, "contentMap" => %{"en" => @html_sample}} + } assert {:ok, res} = NormalizeMarkup.filter(message) assert res["object"]["content"] == @expected + assert res["object"]["contentMap"]["en"] == @expected end test "history-aware" do 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 45fe183a4..fc22bcb8c 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 @@ -17,13 +17,28 @@ defp has_pack?() do end end + defp sync_emoji_changes() do + # emoji updates happen asynchronously; + # force all pending change requests to be processed before checking condition + # (this is an (afaik undocumented) side-effect of get_state; + # presumably due to it doing a sync call to retrieve the state and message queue ordering) + :sys.get_state(Pleroma.Emoji) + end + defp has_emoji?(shortcode) do + sync_emoji_changes() + case Pack.load_pack("stolen") do {:ok, pack} -> Map.has_key?(pack.files, shortcode) {:error, :enoent} -> false end end + defp installed() do + sync_emoji_changes() + Emoji.get_all() |> Enum.map(fn {k, _} -> k end) + end + defmacro mock_tesla( url \\ "https://example.org/emoji/firedfox.png", status \\ 200, @@ -224,6 +239,4 @@ test "accepts content-size below limit", %{message: message} do assert "firedfox" in installed() end - - defp installed, do: Emoji.get_all() |> Enum.map(fn {k, _} -> k end) end diff --git a/test/pleroma/web/activity_pub/object_validators/announce_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/announce_validation_test.exs index 20964e855..b3d166ae9 100644 --- a/test/pleroma/web/activity_pub/object_validators/announce_validation_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/announce_validation_test.exs @@ -86,23 +86,32 @@ test "returns an error if the actor can't announce the object", %{ object = Object.normalize(post_activity, fetch: false) # Another user can't announce it - {:ok, announce, []} = Builder.announce(announcer, object, public: false) + {:ok, announce, []} = Builder.announce(announcer, object, visibility: "private") {:error, cng} = ObjectValidator.validate(announce, []) assert {:actor, {"can not announce this object", []}} in cng.errors - # The actor of the object can announce it - {:ok, announce, []} = Builder.announce(user, object, public: false) + # The actor of the object can announce it with a restrictive scope + {:ok, announce, []} = Builder.announce(user, object, visibility: "private") + assert {:ok, _, _} = ObjectValidator.validate(announce, []) + {:ok, announce, []} = Builder.announce(user, object, visibility: "direct") assert {:ok, _, _} = ObjectValidator.validate(announce, []) # The actor of the object can not announce it publicly - {:ok, announce, []} = Builder.announce(user, object, public: true) + {:ok, announce, []} = Builder.announce(user, object, visibility: "public") + {:error, cng1} = ObjectValidator.validate(announce, []) - {:error, cng} = ObjectValidator.validate(announce, []) + {:ok, announce, []} = Builder.announce(user, object, visibility: "unlisted") + {:error, cng2} = ObjectValidator.validate(announce, []) - assert {:actor, {"can not announce this object publicly", []}} in cng.errors + {:ok, announce, []} = Builder.announce(user, object, visibility: "local") + {:error, cng3} = ObjectValidator.validate(announce, []) + + for cng <- [cng1, cng2, cng3] do + assert {:actor, {"can not announce this object publicly", []}} in cng.errors + end end end end diff --git a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs index 64a322cd5..ebc2cf348 100644 --- a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs @@ -191,6 +191,31 @@ test "a misskey MFM status with a _misskey_content field should work and be link end end + test "an MFM status with htmlMfm should not have its content re-parsed" do + insert(:user, %{ap_id: "https://misskey.local.live/users/92hzkskwgy"}) + + note = + "test/fixtures/misskey/mfm_x_format.json" + |> File.read!() + |> Jason.decode!() + |> Map.put("htmlMfm", true) + |> Map.put("content", "do not re-parse") + + changes = ArticleNotePageValidator.cast_and_validate(note) + + %{ + valid?: true, + changes: %{ + content: content, + source: %{ + "mediaType" => "text/x.misskeymarkdown" + } + } + } = changes + + assert content == "do not re-parse" + end + test "a Note without replies/first/items validates" do insert(:user, ap_id: "https://mastodon.social/users/emelie") diff --git a/test/pleroma/web/activity_pub/side_effects/delete_test.exs b/test/pleroma/web/activity_pub/side_effects/delete_test.exs index 20f0d4b70..435ccc447 100644 --- a/test/pleroma/web/activity_pub/side_effects/delete_test.exs +++ b/test/pleroma/web/activity_pub/side_effects/delete_test.exs @@ -16,7 +16,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffects.DeleteTest do alias Pleroma.Web.ActivityPub.SideEffects alias Pleroma.Web.CommonAPI - alias Pleroma.LoggerMock alias Pleroma.Web.ActivityPub.ActivityPubMock import Mox @@ -133,13 +132,12 @@ test "it logs issues with objects deletion", %{ delete: delete, object: object } do - {:ok, _object} = - object - |> Object.change(%{data: Map.delete(object.data, "actor")}) - |> Repo.update() - - LoggerMock - |> expect(:error, fn str -> assert str =~ "The object doesn't have an actor" end) + ExUnit.CaptureLog.capture_log(fn -> + {:ok, _object} = + object + |> Object.change(%{data: Map.delete(object.data, "actor")}) + |> Repo.update() + end) =~ "The object doesn't have an actor" {:error, :no_object_actor} = SideEffects.handle(delete) end diff --git a/test/pleroma/web/activity_pub/side_effects_test.exs b/test/pleroma/web/activity_pub/side_effects_test.exs index 64a1fe6e6..b7d2ef263 100644 --- a/test/pleroma/web/activity_pub/side_effects_test.exs +++ b/test/pleroma/web/activity_pub/side_effects_test.exs @@ -684,13 +684,15 @@ test "creates a notification", %{like: like, poster: poster} do {:ok, post} = CommonAPI.post(poster, %{status: "hey"}) {:ok, private_post} = CommonAPI.post(poster, %{status: "hey", visibility: "private"}) - {:ok, announce_data, _meta} = Builder.announce(user, post.object, public: true) + {:ok, announce_data, _meta} = Builder.announce(user, post.object, visibility: "public") {:ok, private_announce_data, _meta} = - Builder.announce(user, private_post.object, public: false) + Builder.announce(user, private_post.object, visibility: "private") {:ok, relay_announce_data, _meta} = - Builder.announce(Pleroma.Web.ActivityPub.Relay.get_actor(), post.object, public: true) + Builder.announce(Pleroma.Web.ActivityPub.Relay.get_actor(), post.object, + visibility: "public" + ) {:ok, announce, _meta} = ActivityPub.persist(announce_data, local: true) {:ok, private_announce, _meta} = ActivityPub.persist(private_announce_data, local: true) diff --git a/test/pleroma/web/activity_pub/transmogrifier/emoji_react_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/emoji_react_handling_test.exs index b40e7f4da..4236348f4 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/emoji_react_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/emoji_react_handling_test.exs @@ -212,6 +212,66 @@ test "it reject invalid emoji reactions" do assert {:error, _} = Transmogrifier.handle_incoming(data) end + test "it strips colons from emoji object in tag" do + shortcode = ":blobcatgoogly:" + imgurl = "https://example.org/emoji/a.png" + {data, _, _} = prepare_react(shortcode, imgurl) + coloned_tag = Enum.map(data["tag"], fn tag -> %{tag | "name" => shortcode} end) + data = %{data | "tag" => coloned_tag} + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["type"] == "EmojiReact" + assert data["content"] == shortcode + [%{"name" => tag_name}] = data["tag"] + assert ":" <> tag_name <> ":" == shortcode + end + + test "it prunes tag to only the relevant emoji object" do + shortcode = "blobcatgoogly" + imgurl = "https://example.org/emoji/a.png" + {data, _, _} = prepare_react(shortcode, imgurl) + + ext_tag = [ + %{ + "type" => "Hashtag", + "name" => "#cat", + "href" => "https://example.org/hastags/cat" + }, + emoji_object("evilcat", "https://example.org/evilcat.avif") + | data["tag"] + ] + + data = %{data | "tag" => ext_tag} + refute match?([%{"type" => "Emoji"}], data["tag"]) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert match?([%{"type" => "Emoji", "name" => ^shortcode}], data["tag"]) + end + + test "it strips pre-existing remote indicators" do + shortcode = "blobcatgoogly@fedi.example.org" + imgurl = "https://example.org/emoji/a.png" + {data, _, _} = prepare_react(shortcode, imgurl) + [clean_shortcode, _] = String.split(shortcode, "@", parts: 2) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["content"] == ":" <> clean_shortcode <> ":" + assert match?([%{"name" => ^clean_shortcode}], data["tag"]) + end + + defp prepare_react(shortcode, imgurl, emoji_id \\ nil) do + user = insert(:user) + other_user = insert(:user, local: false) + {:ok, activity} = CommonAPI.post(user, %{status: "hello"}) + + emoji = emoji_object(shortcode, emoji_id, imgurl) + data = react_with_custom(activity.data["object"], other_user.ap_id, emoji) + {data, activity, other_user} + end + defp emoji_object(shortcode, id \\ nil, url \\ "https://example.org/emoji.png") do %{ "type" => "Emoji", diff --git a/test/pleroma/web/activity_pub/transmogrifier/follow_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/follow_handling_test.exs index 33b9657d5..10455df2a 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/follow_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/follow_handling_test.exs @@ -6,6 +6,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do use Pleroma.DataCase, async: false @moduletag :mocked alias Pleroma.Activity + alias Pleroma.FollowingRelationship alias Pleroma.Notification alias Pleroma.Repo alias Pleroma.User @@ -203,7 +204,11 @@ test "it works for incoming follows to locked account" do assert data["state"] == "pending" assert data["actor"] == "http://mastodon.example.org/users/admin" - assert [^pending_follower] = User.get_follow_requests(user) + pending_follows = + FollowingRelationship.get_follow_requesting_users_with_request_id(user) + |> Repo.all() + + assert [%{entry: ^pending_follower}] = pending_follows end end end diff --git a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs index e9271ce9a..92e976cd6 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs @@ -383,6 +383,32 @@ test "it correctly processes messages with weirdness in address fields" do assert ["http://mastodon.example.org/users/admin/followers"] == activity.data["cc"] assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"] end + + test "preserves both name and summary of attachments until the end" do + name = "marvellous.png" + summary = "The most wondrous thing you’ve ever seen." + + data = + Jason.decode!(File.read!("test/fixtures/mastodon-post-activity.json")) + |> put_in(["object", "attachment"], [ + %{ + "type" => "Image", + "mediaType" => "image/png", + "blurhash" => "LIN1M;~p~W%gt-RPjENI-=RiM_WE", + "name" => name, + "summary" => summary, + "url" => "https://example.org/marvellous.png" + } + ]) + + {:ok, activity} = Transmogrifier.handle_incoming(data) + %Object{} = obj = Object.normalize(activity) + + [attach] = obj.data["attachment"] + + assert attach["name"] == name + assert attach["summary"] == summary + end end describe "`handle_incoming/2`, Mastodon format `replies` handling" do @@ -681,12 +707,18 @@ test "returns object with emoji when object contains map tag" do describe "set_replies/1" do setup do: clear_config([:activitypub, :note_replies_output_limit], 2) - test "returns unmodified object if activity doesn't have self-replies" do + test "still provides reply collection id even if activity doesn't have replies yet" do data = Jason.decode!(File.read!("test/fixtures/mastodon-post-activity.json")) - assert Transmogrifier.set_replies(data) == data + modified = Transmogrifier.set_replies(data) + + refute data["replies"] + assert modified["replies"] + assert match?(%{"id" => "http" <> _, "totalItems" => 0}, modified["replies"]) + # first page should be omitted if there are no entries anyway + refute modified["replies"]["first"] end - test "sets `replies` collection with a limited number of self-replies" do + test "sets `replies` collection with a limited number of replies, preferring oldest" do [user, another_user] = insert_list(2, :user) {:ok, %{id: id1} = activity} = CommonAPI.post(user, %{status: "1"}) @@ -715,7 +747,7 @@ test "sets `replies` collection with a limited number of self-replies" do object = Object.normalize(activity, fetch: false) replies_uris = Enum.map([self_reply1, self_reply2], fn a -> a.object.data["id"] end) - assert %{"type" => "Collection", "items" => ^replies_uris} = + assert %{"type" => "OrderedCollection", "first" => %{"orderedItems" => ^replies_uris}} = Transmogrifier.set_replies(object.data)["replies"] end end diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs index b90692370..d72f5494b 100644 --- a/test/pleroma/web/activity_pub/transmogrifier_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs @@ -541,6 +541,70 @@ test "Updates of Notes are handled" do } } = prepared["object"] end + + test "Correctly handles Undo activities" do + blocked = insert(:user) + blocker = insert(:user, local: true) + + blocked_ap_id = blocked.ap_id + blocker_ap_id = blocker.ap_id + + {:ok, %Activity{} = block_activity} = CommonAPI.block(blocker, blocked) + {:ok, %Activity{} = undo_activity} = CommonAPI.unblock(blocker, blocked) + {:ok, data} = Transmogrifier.prepare_outgoing(undo_activity.data) + + block_ap_id = block_activity.data["id"] + assert is_binary(block_ap_id) + + assert match?( + %{ + "@context" => [_ | _], + "type" => "Undo", + "id" => "http://localhost" <> _, + "actor" => ^blocker_ap_id, + "object" => ^block_ap_id, + "to" => [^blocked_ap_id], + "cc" => [], + "bto" => [], + "bcc" => [] + }, + data + ) + end + + test "Correctly handles EmojiReact activities" do + user = insert(:user, local: true) + note_activity = insert(:note_activity) + + user_ap_id = user.ap_id + user_followers = user.follower_address + note_author = note_activity.data["actor"] + note_ap_id = note_activity.data["object"] + + assert is_binary(note_author) + assert is_binary(note_ap_id) + + {:ok, react_activity} = CommonAPI.react_with_emoji(note_activity.id, user, "🐈") + {:ok, data} = Transmogrifier.prepare_outgoing(react_activity.data) + + assert match?( + %{ + "@context" => [_ | _], + "type" => "EmojiReact", + "actor" => ^user_ap_id, + "to" => [^user_followers, ^note_author], + "cc" => ["https://www.w3.org/ns/activitystreams#Public"], + "bto" => [], + "bcc" => [], + "content" => "🐈", + "context" => "2hu", + "id" => "http://localhost" <> _, + "object" => ^note_ap_id, + "tag" => [] + }, + data + ) + end end describe "actor rewriting" do diff --git a/test/pleroma/web/activity_pub/utils_test.exs b/test/pleroma/web/activity_pub/utils_test.exs index e45af3aec..968503e83 100644 --- a/test/pleroma/web/activity_pub/utils_test.exs +++ b/test/pleroma/web/activity_pub/utils_test.exs @@ -144,7 +144,8 @@ test "make_json_ld_header/0" do "https://www.w3.org/ns/activitystreams", "http://localhost:4001/schemas/litepub-0.1.jsonld", %{ - "@language" => "und" + "@language" => "und", + "htmlMfm" => "https://w3id.org/fep/c16b#htmlMfm" } ] } @@ -332,74 +333,6 @@ test "fetches last block activities" do end end - describe "recipient_in_message/3" do - test "returns true when recipient in `to`" do - recipient = insert(:user) - actor = insert(:user) - assert Utils.recipient_in_message(recipient, actor, %{"to" => recipient.ap_id}) - - assert Utils.recipient_in_message( - recipient, - actor, - %{"to" => [recipient.ap_id], "cc" => ""} - ) - end - - test "returns true when recipient in `cc`" do - recipient = insert(:user) - actor = insert(:user) - assert Utils.recipient_in_message(recipient, actor, %{"cc" => recipient.ap_id}) - - assert Utils.recipient_in_message( - recipient, - actor, - %{"cc" => [recipient.ap_id], "to" => ""} - ) - end - - test "returns true when recipient in `bto`" do - recipient = insert(:user) - actor = insert(:user) - assert Utils.recipient_in_message(recipient, actor, %{"bto" => recipient.ap_id}) - - assert Utils.recipient_in_message( - recipient, - actor, - %{"bcc" => "", "bto" => [recipient.ap_id]} - ) - end - - test "returns true when recipient in `bcc`" do - recipient = insert(:user) - actor = insert(:user) - assert Utils.recipient_in_message(recipient, actor, %{"bcc" => recipient.ap_id}) - - assert Utils.recipient_in_message( - recipient, - actor, - %{"bto" => "", "bcc" => [recipient.ap_id]} - ) - end - - test "returns true when message without addresses fields" do - recipient = insert(:user) - actor = insert(:user) - assert Utils.recipient_in_message(recipient, actor, %{"bccc" => recipient.ap_id}) - - assert Utils.recipient_in_message( - recipient, - actor, - %{"btod" => "", "bccc" => [recipient.ap_id]} - ) - end - - test "returns false" do - recipient = insert(:user) - actor = insert(:user) - refute Utils.recipient_in_message(recipient, actor, %{"to" => "ap_id"}) - end - end - describe "lazy_put_activity_defaults/2" do test "returns map with id and published data" do note_activity = insert(:note_activity) diff --git a/test/pleroma/web/activity_pub/views/object_view_test.exs b/test/pleroma/web/activity_pub/views/object_view_test.exs index 9348c09be..9ae4e4c16 100644 --- a/test/pleroma/web/activity_pub/views/object_view_test.exs +++ b/test/pleroma/web/activity_pub/views/object_view_test.exs @@ -49,9 +49,39 @@ test "renders `replies` collection for a note activity" do replies_uris = [self_reply1.object.data["id"]] result = ObjectView.render("object.json", %{object: refresh_record(activity)}) - assert %{"type" => "Collection", "items" => ^replies_uris} = + assert %{ + "type" => "OrderedCollection", + "id" => _, + "first" => %{"orderedItems" => ^replies_uris} + } = get_in(result, ["object", "replies"]) end + + test "renders a replies collection on its own" do + user = insert(:user) + activity = insert(:note_activity, user: user) + activity = Pleroma.Activity.get_by_id_with_object(activity.id) + + {:ok, r1} = + CommonAPI.post(user, %{status: "reply1", in_reply_to_status_id: activity.id}) + + {:ok, r2} = + CommonAPI.post(user, %{status: "reply1", in_reply_to_status_id: activity.id}) + + replies_uris = [r1.object.data["id"], r2.object.data["id"]] + + result = + ObjectView.render("object_replies.json", %{ + render_params: %{object_ap_id: activity.object.data["id"]} + }) + + %{ + "type" => "OrderedCollection", + "id" => _, + "totalItems" => 2, + "first" => %{"orderedItems" => ^replies_uris} + } = result + end end test "renders a like activity" do diff --git a/test/pleroma/web/activity_pub/views/user_view_test.exs b/test/pleroma/web/activity_pub/views/user_view_test.exs index e2c8f5865..3f45f62f7 100644 --- a/test/pleroma/web/activity_pub/views/user_view_test.exs +++ b/test/pleroma/web/activity_pub/views/user_view_test.exs @@ -7,9 +7,32 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do import Pleroma.Factory alias Pleroma.User + alias Pleroma.Web.ActivityPub.ObjectValidators.UserValidator alias Pleroma.Web.ActivityPub.UserView alias Pleroma.Web.CommonAPI + test "Renders a user such that we accept it ourselves" do + user = + insert(:user) + |> with_signing_key() + + representation = UserView.render("user.json", %{user: user}) + validation_res = UserValidator.validate(representation, []) + + assert match?({:ok, _user, _meta}, validation_res) + end + + test "Renders a minimal user such that we accept it ourselves" do + user = + insert(:user) + |> with_signing_key() + + representation = UserView.render("stripped_user.json", %{user: user}) + validation_res = UserValidator.validate(representation, []) + + assert match?({:ok, _user, _meta}, validation_res) + end + test "Renders a user, including the public key" do user = insert(:user) diff --git a/test/pleroma/web/activity_pub/visibility_test.exs b/test/pleroma/web/activity_pub/visibility_test.exs index 1595f9085..42cb3c710 100644 --- a/test/pleroma/web/activity_pub/visibility_test.exs +++ b/test/pleroma/web/activity_pub/visibility_test.exs @@ -36,12 +36,18 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do {:ok, local} = CommonAPI.post(user, %{status: "@#{mentioned.nickname}", visibility: "local"}) - {:ok, list} = + # list visibility is no longer supported, but we want to check any + # leftover entreis are handled sensibly, so we nned to manually fix up the data + {:ok, list_activity} = CommonAPI.post(user, %{ status: "@#{mentioned.nickname}", - visibility: "list:#{list.id}" + visibility: "direct" }) + list_object = Object.normalize(list_activity) + {:ok, list_object} = Object.update_data(list_object, %{"listMessage" => list.ap_id}) + list_activity = %Activity{list_activity | object: list_object} + %{ public: public, private: private, @@ -51,7 +57,7 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do mentioned: mentioned, following: following, unrelated: unrelated, - list: list, + list: list_activity, local: local, remote: remote } @@ -69,8 +75,8 @@ test "is_direct?", %{ refute Visibility.is_direct?(public) refute Visibility.is_direct?(private) refute Visibility.is_direct?(unlisted) - assert Visibility.is_direct?(list) refute Visibility.is_direct?(local) + assert Visibility.is_direct?(list) end test "is_public?", %{ @@ -105,22 +111,6 @@ test "is_private?", %{ refute Visibility.is_private?(local) end - test "is_list?", %{ - public: public, - private: private, - direct: direct, - unlisted: unlisted, - list: list, - local: local - } do - refute Visibility.is_list?(direct) - refute Visibility.is_list?(public) - refute Visibility.is_list?(private) - refute Visibility.is_list?(unlisted) - assert Visibility.is_list?(list) - refute Visibility.is_list?(local) - end - test "visible_for_user? Activity", %{ public: public, private: private, @@ -177,9 +167,6 @@ test "visible_for_user? Activity", %{ refute Visibility.visible_for_user?(direct, nil) refute Visibility.visible_for_user?(local, nil) - # Visible for a list member - assert Visibility.visible_for_user?(list, unrelated) - # Local not visible to remote user refute Visibility.visible_for_user?(local, remote) end @@ -270,15 +257,16 @@ test "get_visibility", %{ assert Visibility.get_visibility(private) == "private" assert Visibility.get_visibility(direct) == "direct" assert Visibility.get_visibility(unlisted) == "unlisted" - assert Visibility.get_visibility(list) == "list" + # legacy, no longer supported visibility type doesn't leak out now + assert Visibility.get_visibility(list) == "direct" end test "get_visibility with directMessage flag" do assert Visibility.get_visibility(%{data: %{"directMessage" => true}}) == "direct" end - test "get_visibility with listMessage flag" do - assert Visibility.get_visibility(%{data: %{"listMessage" => ""}}) == "list" + test "get_visibility treats legacy list messages as direct" do + assert Visibility.get_visibility(%{data: %{"listMessage" => ""}}) == "direct" end describe "entire_thread_visible_for_user?/2" do diff --git a/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs index 8bdbe34ff..7b0b03469 100644 --- a/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs @@ -806,41 +806,6 @@ test "it resend emails for two users", %{conn: conn, admin: admin} do end end - describe "/api/v1/pleroma/admin/stats" do - test "status visibility count", %{conn: conn} do - user = insert(:user) - CommonAPI.post(user, %{visibility: "public", status: "hey"}) - CommonAPI.post(user, %{visibility: "unlisted", status: "hey"}) - CommonAPI.post(user, %{visibility: "unlisted", status: "hey"}) - - response = - conn - |> get("/api/v1/pleroma/admin/stats") - |> json_response(200) - - assert %{"direct" => 0, "private" => 0, "public" => 1, "unlisted" => 2} = - response["status_visibility"] - end - - test "by instance", %{conn: conn} do - user1 = insert(:user) - instance2 = "instance2.tld" - user2 = insert(:user, %{ap_id: "https://#{instance2}/@actor"}) - - CommonAPI.post(user1, %{visibility: "public", status: "hey"}) - CommonAPI.post(user2, %{visibility: "unlisted", status: "hey"}) - CommonAPI.post(user2, %{visibility: "private", status: "hey"}) - - response = - conn - |> get("/api/v1/pleroma/admin/stats", instance: instance2) - |> json_response(200) - - assert %{"direct" => 0, "private" => 1, "public" => 0, "unlisted" => 1} = - response["status_visibility"] - end - end - describe "/api/v1/pleroma/backups" do test "it creates a backup", %{conn: conn} do admin = %{id: admin_id, nickname: admin_nickname} = insert(:user, is_admin: true) diff --git a/test/pleroma/web/common_api/activity_draft_test.exs b/test/pleroma/web/common_api/activity_draft_test.exs new file mode 100644 index 000000000..c9bb05c77 --- /dev/null +++ b/test/pleroma/web/common_api/activity_draft_test.exs @@ -0,0 +1,39 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.CommonAPI.ActivityDraftTest do + alias Pleroma.Web.CommonAPI.ActivityDraft + + use Pleroma.DataCase + + import Pleroma.Factory + + setup do + user = insert(:user, local: true) + + %{user: user} + end + + defp test_dm_addressing(from, to) do + {:ok, draft} = + ActivityDraft.create(from, %{ + status: "@#{to.nickname} hi", + visibility: "direct" + }) + + assert to.ap_id in draft.mentions + end + + describe "addresses mentioned user" do + test "when no dot in name", %{user: user} do + addr = insert(:user, local: false, nickname: "nix@example.org") + test_dm_addressing(user, addr) + end + + test "when dot in name", %{user: user} do + addr = insert(:user, local: false, nickname: "ly.nx@example.org") + test_dm_addressing(user, addr) + end + end +end diff --git a/test/pleroma/web/common_api/utils_test.exs b/test/pleroma/web/common_api/utils_test.exs index 3d639d389..c0f2afcf2 100644 --- a/test/pleroma/web/common_api/utils_test.exs +++ b/test/pleroma/web/common_api/utils_test.exs @@ -34,7 +34,7 @@ test "it adds attachment links to a given text and attachment set", %{ clear_config([Pleroma.Upload, :filename_display_max_length], len) expected = - "
#{String.slice(name, 0..len)}…" + "
#{String.slice(name, 0..len)}…" assert Utils.add_attachments("", [attachment]) == expected end @@ -45,7 +45,7 @@ test "doesn't truncate file name if config for truncate is set to 0", %{ } do clear_config([Pleroma.Upload, :filename_display_max_length], 0) - expected = "
#{name}" + expected = "
#{name}" assert Utils.add_attachments("", [attachment]) == expected end @@ -609,27 +609,6 @@ test "returns [] when not pass media_ids" do end end - describe "maybe_add_list_data/3" do - test "adds list params when found user list" do - user = insert(:user) - {:ok, %Pleroma.List{} = list} = Pleroma.List.create("title", user) - - assert Utils.maybe_add_list_data(%{additional: %{}, object: %{}}, user, {:list, list.id}) == - %{ - additional: %{"bcc" => [list.ap_id], "listMessage" => list.ap_id}, - object: %{"listMessage" => list.ap_id} - } - end - - test "returns original params when list not found" do - user = insert(:user) - {:ok, %Pleroma.List{} = list} = Pleroma.List.create("title", insert(:user)) - - assert Utils.maybe_add_list_data(%{additional: %{}, object: %{}}, user, {:list, list.id}) == - %{additional: %{}, object: %{}} - end - end - describe "maybe_add_attachments/3" do test "returns parsed results when attachment_links is false" do assert Utils.maybe_add_attachments( @@ -647,7 +626,7 @@ test "adds attachments to parsed results" do [attachment], true ) == { - "test
SakuraPM.png", + "test
SakuraPM.png", [], ["tags"] } diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs index b32334389..ea1b78890 100644 --- a/test/pleroma/web/common_api_test.exs +++ b/test/pleroma/web/common_api_test.exs @@ -128,6 +128,26 @@ test "it works even without an existing block activity" do assert {:ok, :no_activity} == CommonAPI.unblock(blocker, blocked) refute User.blocks?(blocker, blocked) end + + test "it unblocks and does not federate if outgoing blocks are disabled" do + clear_config([:instance, :federating], true) + clear_config([:activitypub, :outgoing_blocks], false) + + blocked = insert(:user) + blocker = insert(:user) + + with_mock Pleroma.Web.Federator, + publish: fn _ -> nil end do + assert {:ok, block} = CommonAPI.block(blocker, blocked) + assert block.local + assert User.blocks?(blocker, blocked) + + assert {:ok, unblock} = CommonAPI.unblock(blocker, blocked) + assert unblock.local + refute User.blocks?(blocker, blocked) + assert_not_called(Pleroma.Web.Federator.publish(:_)) + end + end end describe "deletion" do @@ -298,49 +318,6 @@ test "repeating race condition" do assert object.data["announcement_count"] == 20 end - test "when replying to a conversation / participation, it will set the correct context id even if no explicit reply_to is given" do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"}) - - [participation] = Participation.for_user(user) - - {:ok, convo_reply} = - CommonAPI.post(user, %{status: ".", in_reply_to_conversation_id: participation.id}) - - assert Visibility.is_direct?(convo_reply) - - assert activity.data["context"] == convo_reply.data["context"] - end - - test "when replying to a conversation / participation, it only mentions the recipients explicitly declared in the participation" do - har = insert(:user) - jafnhar = insert(:user) - tridi = insert(:user) - - {:ok, activity} = - CommonAPI.post(har, %{ - status: "@#{jafnhar.nickname} hey", - visibility: "direct" - }) - - assert har.ap_id in activity.recipients - assert jafnhar.ap_id in activity.recipients - - [participation] = Participation.for_user(har) - - {:ok, activity} = - CommonAPI.post(har, %{ - status: "I don't really like @#{tridi.nickname}", - visibility: "direct", - in_reply_to_status_id: activity.id, - in_reply_to_conversation_id: participation.id - }) - - assert har.ap_id in activity.recipients - assert jafnhar.ap_id in activity.recipients - refute tridi.ap_id in activity.recipients - end - test "with the safe_dm_mention option set, it does not mention people beyond the initial tags" do har = insert(:user) jafnhar = insert(:user) @@ -523,15 +500,32 @@ test "replying with a direct message will NOT auto-add the author of the reply t refute user.ap_id in secret_answer.recipients end - test "it allows to address a list" do + test "it adds the htmlMFM term to MFM posts and properly processes it" do user = insert(:user) - {:ok, list} = Pleroma.List.create("foo", user) - {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"}) + assert {:ok, + %Pleroma.Activity{ + object: %Pleroma.Object{ + data: %{ + "content" => content, + "source" => %{ + "content" => source_content, + "mediaType" => "text/x.misskeymarkdown" + }, + "htmlMfm" => html_mfm + } + } + }} = + CommonAPI.post(user, %{ + status: "

$[spin 13:37]

", + content_type: "text/x.misskeymarkdown" + }) - assert activity.data["bcc"] == [list.ap_id] - assert activity.recipients == [list.ap_id, user.ap_id] - assert activity.data["listMessage"] == list.ap_id + assert html_mfm == true + assert content =~ "mfm-spin" + assert content =~ "13:37" + refute content =~ "scrub-this" + assert source_content == "

$[spin 13:37]

" end test "it returns error when status is empty and no attachments" do diff --git a/test/pleroma/web/federator_test.exs b/test/pleroma/web/federator_test.exs index 7a23d4117..56891dd7f 100644 --- a/test/pleroma/web/federator_test.exs +++ b/test/pleroma/web/federator_test.exs @@ -135,6 +135,73 @@ test "successfully processes incoming AP docs with correct origin" do assert {:cancel, :already_present} = ObanHelpers.perform(job) end + defp mfm_activity(object_overrides \\ %{}) do + %{ + "@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" => "

this is the original content

", + "source" => %{ + "content" => "this is the source content", + "mediaType" => "text/x.misskeymarkdown" + }, + "id" => "http://mastodon.example.org/users/admin/objects/1", + "attributedTo" => "http://mastodon.example.org/users/admin", + "to" => ["https://www.w3.org/ns/activitystreams#Public"] + }, + "to" => ["https://www.w3.org/ns/activitystreams#Public"] + } + |> Map.update!("object", fn obj -> Map.merge(obj, object_overrides) end) + end + + test "properly processes objects with the htmlMfm attribute true" do + params = mfm_activity(%{"htmlMfm" => true}) + + {:ok, job} = Federator.incoming_ap_doc(params) + {:ok, %Pleroma.Activity{data: %{"object" => object_ap_id}}} = ObanHelpers.perform(job) + + %Pleroma.Object{data: %{"content" => content, "htmlMfm" => html_mfm}} = + Pleroma.Object.get_by_ap_id(object_ap_id) + + assert html_mfm == true + refute content =~ "this-should-be-scrubbed-away" + refute content =~ "this is the source content" + assert content =~ "this is the original content" + end + + test "properly processes objects with the htmlMfm attribute false" do + params = mfm_activity(%{"htmlMfm" => false}) + + {:ok, job} = Federator.incoming_ap_doc(params) + {:ok, %Pleroma.Activity{data: %{"object" => object_ap_id}}} = ObanHelpers.perform(job) + + %Pleroma.Object{data: %{"content" => content, "htmlMfm" => html_mfm}} = + Pleroma.Object.get_by_ap_id(object_ap_id) + + assert html_mfm == false + refute content =~ "this-should-be-scrubbed-away" + assert content =~ "this is the source content" + refute content =~ "this is the original content" + end + + test "properly processes objects with the htmlMfm attribute not set" do + params = mfm_activity() + + {:ok, job} = Federator.incoming_ap_doc(params) + {:ok, %Pleroma.Activity{data: %{"object" => object_ap_id}}} = ObanHelpers.perform(job) + + %Pleroma.Object{data: %{"content" => content} = data} = + Pleroma.Object.get_by_ap_id(object_ap_id) + + refute Map.has_key?(data, "htmlMfm") + refute content =~ "this-should-be-scrubbed-away" + assert content =~ "this is the source content" + refute content =~ "this is the original content" + end + test "successfully normalises public scope descriptors" do params = %{ "@context" => "https://www.w3.org/ns/activitystreams", diff --git a/test/pleroma/web/feed/tag_controller_test.exs b/test/pleroma/web/feed/tag_controller_test.exs index f67d5485d..797fb6259 100644 --- a/test/pleroma/web/feed/tag_controller_test.exs +++ b/test/pleroma/web/feed/tag_controller_test.exs @@ -192,4 +192,60 @@ test "returns 404 for tags feed", %{conn: conn} do |> response(404) end end + + describe "restricted for unauthenticated" do + test "returns 404 when local timeline is disabled", %{conn: conn} do + clear_config([:restrict_unauthenticated, :timelines], %{local: true, federated: false}) + + conn + |> put_req_header("accept", "application/rss+xml") + |> get(~p"/tags/pleromaart.rss") + |> response(404) + end + + test "returns local posts only when federated timeline is disabled", %{conn: conn} do + clear_config([:restrict_unauthenticated, :timelines], %{local: false, federated: true}) + + local_user = insert(:user) + remote_user = insert(:user, local: false) + + local_note = + insert(:note, + user: local_user, + data: %{ + "content" => "local post #PleromaArt", + "summary" => "", + "tag" => ["pleromaart"] + } + ) + + remote_note = + insert(:note, + user: remote_user, + data: %{ + "content" => "remote post #PleromaArt", + "summary" => "", + "tag" => ["pleromaart"] + }, + local: false + ) + + insert(:note_activity, user: local_user, note: local_note) + insert(:note_activity, user: remote_user, note: remote_note, local: false) + + response = + conn + |> put_req_header("accept", "application/rss+xml") + |> get(~p"/tags/pleromaart.rss") + |> response(200) + + xml = parse(response) + + assert xpath(xml, ~x"//channel/title/text()") == ~c"#pleromaart" + + assert xpath(xml, ~x"//channel/item/title/text()"l) == [ + ~c"local post #PleromaArt" + ] + end + end end diff --git a/test/pleroma/web/feed/user_controller_test.exs b/test/pleroma/web/feed/user_controller_test.exs index d5d9faf06..daa3358cf 100644 --- a/test/pleroma/web/feed/user_controller_test.exs +++ b/test/pleroma/web/feed/user_controller_test.exs @@ -236,6 +236,21 @@ test "with non-html / non-json format, it redirects to user feed in atom format" "#{Pleroma.Web.Endpoint.url()}/users/#{user.nickname}/feed.atom" end + test "redirects to rss feed when explicitly requested", %{conn: conn} do + note_activity = insert(:note_activity) + user = User.get_cached_by_ap_id(note_activity.data["actor"]) + + conn = + conn + |> put_req_header("accept", "application/xml") + |> get("/users/#{user.nickname}.rss") + + assert conn.status == 302 + + assert redirected_to(conn) == + "#{Pleroma.Web.Endpoint.url()}/users/#{user.nickname}/feed.rss" + end + test "with non-html / non-json format, it returns error when user is not found", %{conn: conn} do response = conn diff --git a/test/pleroma/web/gettext_companion_test.exs b/test/pleroma/web/gettext_companion_test.exs new file mode 100644 index 000000000..fb4005ede --- /dev/null +++ b/test/pleroma/web/gettext_companion_test.exs @@ -0,0 +1,28 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.GettextTest do + use ExUnit.Case + + require Pleroma.Web.GettextCompanion + + test "put_locales/1: set the first in the list to Gettext's locale" do + Pleroma.Web.GettextCompanion.put_locales(["zh_Hans", "en_test"]) + + assert "zh_Hans" == Gettext.get_locale(Pleroma.Web.Gettext) + end + + test "with_locales/2: reset locale on exit" do + old_first_locale = Gettext.get_locale(Pleroma.Web.Gettext) + old_locales = Pleroma.Web.GettextCompanion.get_locales() + + Pleroma.Web.GettextCompanion.with_locales ["zh_Hans", "en_test"] do + assert "zh_Hans" == Gettext.get_locale(Pleroma.Web.Gettext) + assert ["zh_Hans", "en_test"] == Pleroma.Web.GettextCompanion.get_locales() + end + + assert old_first_locale == Gettext.get_locale(Pleroma.Web.Gettext) + assert old_locales == Pleroma.Web.GettextCompanion.get_locales() + end +end diff --git a/test/pleroma/web/gettext_test.exs b/test/pleroma/web/gettext_test.exs index e186f1ab3..b0d046aa0 100644 --- a/test/pleroma/web/gettext_test.exs +++ b/test/pleroma/web/gettext_test.exs @@ -5,32 +5,14 @@ defmodule Pleroma.Web.GettextTest do use ExUnit.Case - require Pleroma.Web.Gettext - - test "put_locales/1: set the first in the list to Gettext's locale" do - Pleroma.Web.Gettext.put_locales(["zh_Hans", "en_test"]) - - assert "zh_Hans" == Gettext.get_locale(Pleroma.Web.Gettext) - end - - test "with_locales/2: reset locale on exit" do - old_first_locale = Gettext.get_locale(Pleroma.Web.Gettext) - old_locales = Pleroma.Web.Gettext.get_locales() - - Pleroma.Web.Gettext.with_locales ["zh_Hans", "en_test"] do - assert "zh_Hans" == Gettext.get_locale(Pleroma.Web.Gettext) - assert ["zh_Hans", "en_test"] == Pleroma.Web.Gettext.get_locales() - end - - assert old_first_locale == Gettext.get_locale(Pleroma.Web.Gettext) - assert old_locales == Pleroma.Web.Gettext.get_locales() - end + use Gettext, backend: Pleroma.Web.Gettext + require Pleroma.Web.GettextCompanion describe "handle_missing_translation/5" do test "fallback to next locale if some translation is not available" do - Pleroma.Web.Gettext.with_locales ["x_unsupported", "en_test"] do + Pleroma.Web.GettextCompanion.with_locales ["x_unsupported", "en_test"] do assert "xxYour account is awaiting approvalxx" == - Pleroma.Web.Gettext.dpgettext( + dpgettext( "static_pages", "approval pending email subject", "Your account is awaiting approval" @@ -39,9 +21,9 @@ test "fallback to next locale if some translation is not available" do end test "putting en locale at the front should not make gettext fallback unexpectedly" do - Pleroma.Web.Gettext.with_locales ["en", "en_test"] do + Pleroma.Web.GettextCompanion.with_locales ["en", "en_test"] do assert "Your account is awaiting approval" == - Pleroma.Web.Gettext.dpgettext( + dpgettext( "static_pages", "approval pending email subject", "Your account is awaiting approval" @@ -50,9 +32,9 @@ test "putting en locale at the front should not make gettext fallback unexpected end test "duplicated locale in list should not result in infinite loops" do - Pleroma.Web.Gettext.with_locales ["x_unsupported", "x_unsupported", "en_test"] do + Pleroma.Web.GettextCompanion.with_locales ["x_unsupported", "x_unsupported", "en_test"] do assert "xxYour account is awaiting approvalxx" == - Pleroma.Web.Gettext.dpgettext( + dpgettext( "static_pages", "approval pending email subject", "Your account is awaiting approval" @@ -61,9 +43,9 @@ test "duplicated locale in list should not result in infinite loops" do end test "direct interpolation" do - Pleroma.Web.Gettext.with_locales ["en_test"] do + Pleroma.Web.GettextCompanion.with_locales ["en_test"] do assert "xxYour digest from some instancexx" == - Pleroma.Web.Gettext.dpgettext( + dpgettext( "static_pages", "digest email subject", "Your digest from %{instance_name}", @@ -73,9 +55,9 @@ test "direct interpolation" do end test "fallback with interpolation" do - Pleroma.Web.Gettext.with_locales ["x_unsupported", "en_test"] do + Pleroma.Web.GettextCompanion.with_locales ["x_unsupported", "en_test"] do assert "xxYour digest from some instancexx" == - Pleroma.Web.Gettext.dpgettext( + dpgettext( "static_pages", "digest email subject", "Your digest from %{instance_name}", @@ -85,9 +67,9 @@ test "fallback with interpolation" do end test "fallback to msgid" do - Pleroma.Web.Gettext.with_locales ["x_unsupported"] do + Pleroma.Web.GettextCompanion.with_locales ["x_unsupported"] do assert "Your digest from some instance" == - Pleroma.Web.Gettext.dpgettext( + dpgettext( "static_pages", "digest email subject", "Your digest from %{instance_name}", @@ -99,9 +81,9 @@ test "fallback to msgid" do describe "handle_missing_plural_translation/7" do test "direct interpolation" do - Pleroma.Web.Gettext.with_locales ["en_test"] do + Pleroma.Web.GettextCompanion.with_locales ["en_test"] do assert "xx1 New Followerxx" == - Pleroma.Web.Gettext.dpngettext( + dpngettext( "static_pages", "new followers count header", "%{count} New Follower", @@ -111,7 +93,7 @@ test "direct interpolation" do ) assert "xx5 New Followersxx" == - Pleroma.Web.Gettext.dpngettext( + dpngettext( "static_pages", "new followers count header", "%{count} New Follower", @@ -123,9 +105,9 @@ test "direct interpolation" do end test "fallback with interpolation" do - Pleroma.Web.Gettext.with_locales ["x_unsupported", "en_test"] do + Pleroma.Web.GettextCompanion.with_locales ["x_unsupported", "en_test"] do assert "xx1 New Followerxx" == - Pleroma.Web.Gettext.dpngettext( + dpngettext( "static_pages", "new followers count header", "%{count} New Follower", @@ -135,7 +117,7 @@ test "fallback with interpolation" do ) assert "xx5 New Followersxx" == - Pleroma.Web.Gettext.dpngettext( + dpngettext( "static_pages", "new followers count header", "%{count} New Follower", @@ -147,9 +129,9 @@ test "fallback with interpolation" do end test "fallback to msgid" do - Pleroma.Web.Gettext.with_locales ["x_unsupported"] do + Pleroma.Web.GettextCompanion.with_locales ["x_unsupported"] do assert "1 New Follower" == - Pleroma.Web.Gettext.dpngettext( + dpngettext( "static_pages", "new followers count header", "%{count} New Follower", @@ -159,7 +141,7 @@ test "fallback to msgid" do ) assert "5 New Followers" == - Pleroma.Web.Gettext.dpngettext( + dpngettext( "static_pages", "new followers count header", "%{count} New Follower", diff --git a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs index 39e9629eb..966ff86e3 100644 --- a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs @@ -18,11 +18,15 @@ test "get instance information", %{conn: conn} do thumbnail = Pleroma.Web.Endpoint.url() <> Pleroma.Config.get([:instance, :instance_thumbnail]) background = Pleroma.Web.Endpoint.url() <> Pleroma.Config.get([:instance, :background_image]) + # if no WebFinger domain is configured (without protocol) + uri = Pleroma.Web.Endpoint.host() + # Note: not checking for "max_toot_chars" since it's optional assert %{ - "uri" => _, + "uri" => ^uri, "title" => _, "description" => _, + "short_description" => _, "version" => _, "email" => from_config_email, "urls" => %{ @@ -54,6 +58,15 @@ test "get instance information", %{conn: conn} do assert background == from_config_background end + test "get instance information prefers WebFinger domain for uri", %{conn: conn} do + webfinger_domain = "webfinger.example" + clear_config([Pleroma.Web.WebFinger, :domain], webfinger_domain) + conn = get(conn, "/api/v1/instance") + + assert result = json_response_and_validate_schema(conn, 200) + assert match?(%{"uri" => ^webfinger_domain}, result) + end + test "get instance stats", %{conn: conn} do user = insert(:user, %{local: true}) diff --git a/test/pleroma/web/mastodon_api/controllers/notification_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/notification_controller_test.exs index 1c9c71803..b761bff3f 100644 --- a/test/pleroma/web/mastodon_api/controllers/notification_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/notification_controller_test.exs @@ -74,7 +74,7 @@ test "by default, does not contain pleroma:report" do result = conn - |> get("/api/v1/notifications?include_types[]=pleroma:report") + |> get("/api/v1/notifications?types[]=pleroma:report") |> json_response_and_validate_schema(200) assert [_] = result @@ -237,6 +237,9 @@ test "filters notifications for Like activities" do user = insert(:user) %{user: other_user, conn: conn} = oauth_access(["read:notifications"]) + {:ok, _, _, %{data: %{"state" => "accept"}}} = CommonAPI.follow(other_user, user) + {:ok, _, _, %{data: %{"state" => "accept"}}} = CommonAPI.follow(user, other_user) + {:ok, public_activity} = CommonAPI.post(other_user, %{status: ".", visibility: "public"}) {:ok, direct_activity} = @@ -385,7 +388,7 @@ test "filters notifications using exclude_types" do assert [%{"id" => ^reblog_notification_id}] = json_response_and_validate_schema(conn_res, 200) end - test "filters notifications using include_types" do + test "filters notifications using types" do %{user: user, conn: conn} = oauth_access(["read:notifications"]) other_user = insert(:user) @@ -400,21 +403,21 @@ test "filters notifications using include_types" do reblog_notification_id = get_notification_id_by_activity(reblog_activity) follow_notification_id = get_notification_id_by_activity(follow_activity) - conn_res = get(conn, "/api/v1/notifications?include_types[]=follow") + conn_res = get(conn, "/api/v1/notifications?types[]=follow") assert [%{"id" => ^follow_notification_id}] = json_response_and_validate_schema(conn_res, 200) - conn_res = get(conn, "/api/v1/notifications?include_types[]=mention") + conn_res = get(conn, "/api/v1/notifications?types[]=mention") assert [%{"id" => ^mention_notification_id}] = json_response_and_validate_schema(conn_res, 200) - conn_res = get(conn, "/api/v1/notifications?include_types[]=favourite") + conn_res = get(conn, "/api/v1/notifications?types[]=favourite") assert [%{"id" => ^favorite_notification_id}] = json_response_and_validate_schema(conn_res, 200) - conn_res = get(conn, "/api/v1/notifications?include_types[]=reblog") + conn_res = get(conn, "/api/v1/notifications?types[]=reblog") assert [%{"id" => ^reblog_notification_id}] = json_response_and_validate_schema(conn_res, 200) @@ -422,7 +425,7 @@ test "filters notifications using include_types" do assert length(result) == 4 - query = params_to_query(%{include_types: ["follow", "mention", "favourite", "reblog"]}) + query = params_to_query(%{types: ["follow", "mention", "favourite", "reblog"]}) result = conn @@ -432,6 +435,23 @@ test "filters notifications using include_types" do assert length(result) == 4 end + test "filtering falls back to include_types" do + %{user: user, conn: conn} = oauth_access(["read:notifications"]) + other_user = insert(:user) + + {:ok, _activity} = CommonAPI.post(other_user, %{status: "hey @#{user.nickname}"}) + {:ok, create_activity} = CommonAPI.post(user, %{status: "hey"}) + {:ok, _activity} = CommonAPI.favorite(other_user, create_activity.id) + {:ok, _activity} = CommonAPI.repeat(create_activity.id, other_user) + {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user) + + follow_notification_id = get_notification_id_by_activity(follow_activity) + + conn_res = get(conn, "/api/v1/notifications?include_types[]=follow") + + assert [%{"id" => ^follow_notification_id}] = json_response_and_validate_schema(conn_res, 200) + end + test "destroy multiple" do %{user: user, conn: conn} = oauth_access(["read:notifications", "write:notifications"]) other_user = insert(:user) 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 1022aa5c6..2ed6ef9bc 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -15,6 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Utils + alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.CommonAPI alias Pleroma.Workers.ScheduledActivityWorker @@ -343,6 +344,90 @@ test "replying to a direct message with visibility other than direct", %{ end) end + test "replying to a post the current user can't access fails", %{user: user, conn: conn} do + stranger = insert(:user) + + {:ok, priv_post_act} = + CommonAPI.post(stranger, %{status: "forbidden knowledge", visibility: "private"}) + + assert Visibility.visible_for_user?(priv_post_act, stranger) + refute Visibility.visible_for_user?(priv_post_act, user) + + resp = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses", %{ + "status" => "@#{stranger.nickname} :peek:", + "in_reply_to_id" => priv_post_act.id, + "visibility" => "private" + }) + |> json_response_and_validate_schema(422) + + assert match?(%{"error" => _}, resp) + end + + test "replying to own DM succeeds", %{user: user, conn: conn} do + # this is an "edge" case for visibility: replying user is not part of addressed users (but is the author) + stranger = insert(:user) + + {:ok, %{id: dm_id} = dm_post_act} = + CommonAPI.post(user, %{ + status: "@#{stranger.nickname} wanna lose your mind to forbidden knowledge?", + visibility: "direct" + }) + + assert Visibility.visible_for_user?(dm_post_act, stranger) + assert Visibility.visible_for_user?(dm_post_act, user) + + resp = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses", %{ + "status" => "@#{stranger.nickname} :peek:", + "in_reply_to_id" => dm_id, + "visibility" => "direct" + }) + |> json_response_and_validate_schema(200) + + assert match?(%{"in_reply_to_id" => ^dm_id}, resp) + end + + test "replying to a deleted post fails", %{conn: conn} do + user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{status: "test post"}) + {:ok, _deletion_activity} = CommonAPI.delete(activity.id, user) + + conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses", %{ + "status" => "test reply", + "in_reply_to_id" => to_string(activity.id) + }) + + assert %{"error" => "Parent post does not exist or was deleted"} = + json_response_and_validate_schema(conn, 422) + end + + test "replying to a non-post activity fails", %{conn: conn, user: user} do + other_user = insert(:user) + + {:ok, _, _, follow_activity} = CommonAPI.follow(user, other_user) + assert Visibility.visible_for_user?(follow_activity, user) + + conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses", %{ + "status" => "hiiii!", + "in_reply_to_id" => to_string(follow_activity.id) + }) + + assert %{"error" => "Can only reply to posts, not \"Follow\" activities"} = + json_response_and_validate_schema(conn, 422) + end + test "posting a status with an invalid in_reply_to_id", %{conn: conn} do conn = conn @@ -857,6 +942,18 @@ test "get a status" do assert id == to_string(activity.id) end + test "rejects non-Create, non-Announce activity id" do + %{conn: conn} = oauth_access(["read:statuses"]) + activity = insert(:note_activity) + like_user = insert(:user) + + {:ok, like_activity} = CommonAPI.favorite(like_user, activity.id) + + conn = get(conn, "/api/v1/statuses/#{like_activity.id}") + + assert %{"error" => _} = json_response_and_validate_schema(conn, 404) + end + defp local_and_remote_activities do local = insert(:note_activity) remote = insert(:note_activity, local: false) @@ -1245,6 +1342,24 @@ test "author can reblog own private status", %{conn: conn, user: user} do assert to_string(activity.id) == id end + + test "cannot reblog private status of others (even if visible)", %{conn: conn, user: user} do + followed = insert(:user, local: true) + + {:ok, _, _, %{data: %{"state" => "accept"}}} = CommonAPI.follow(user, followed) + + {:ok, activity} = CommonAPI.post(followed, %{status: "cofe", visibility: "private"}) + + assert Visibility.visible_for_user?(activity, user) + + resp = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses/#{activity.id}/reblog") + |> json_response_and_validate_schema(404) + + assert match?(%{"error" => _}, resp) + end end describe "unreblogging" do @@ -1274,6 +1389,33 @@ test "returns 404 error when activity does not exist", %{conn: conn} do assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"} end + + test "can’t unreblog someone else’s reblog", %{user: user, conn: conn} do + activity = insert(:note_activity) + other_user = insert(:user) + + {:ok, %{id: reblog_id}} = CommonAPI.repeat(activity.id, other_user) + + # unreblog by base post + resp1 = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses/#{activity.id}/unreblog") + |> json_response(400) + + assert match?(%{"error" => _}, resp1) + + # unreblog by reblog ID (reblog IDs are accepted by some APIs; ensure it fails here one way or another) + resp2 = + build_conn() + |> assign(:user, user) + |> assign(:token, insert(:oauth_token, user: user, scopes: ["write", "read"])) + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses/#{reblog_id}/unreblog") + |> json_response_and_validate_schema(404) + + assert match?(%{"error" => _}, resp2) + end end describe "favoriting" do @@ -1306,6 +1448,21 @@ test "favoriting twice will just return 200", %{conn: conn} do |> json_response_and_validate_schema(200) end + test "a status you cannot see fails", %{conn: conn} do + stranger = insert(:user) + + {:ok, activity} = + CommonAPI.post(stranger, %{status: "it can eternal lie", visibility: "private"}) + + resp = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses/#{activity.id}/favourite") + |> json_response_and_validate_schema(403) + + assert match?(%{"error" => _}, resp) + end + test "returns 404 error for a wrong id", %{conn: conn} do conn = conn @@ -1335,6 +1492,31 @@ test "unfavorites a status and returns it", %{user: user, conn: conn} do assert to_string(activity.id) == id end + test "doesn't do funny things to other users favs", %{conn: conn} do + activity = insert(:note_activity) + + other = insert(:user) + {:ok, fav_activity} = CommonAPI.favorite(other, activity.id) + + # using base post ID + resp1 = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses/#{activity.id}/unfavourite") + |> json_response(400) + + assert match?(%{"error" => _}, resp1) + + # some APIs (used to) take IDs of any activity type, make sure this fails one way or another + resp2 = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses/#{fav_activity.id}/unfavourite") + |> json_response_and_validate_schema(404) + + assert match?(%{"error" => _}, resp2) + end + test "returns 404 error for a wrong id", %{conn: conn} do conn = conn @@ -1739,6 +1921,25 @@ test "requires authentication for private posts", %{user: user} do assert id == other_user.id end + test "fails when base post not visible to current user", %{user: user} do + other_user = insert(:user, local: true) + + {:ok, activity} = + CommonAPI.post(user, %{ + status: "craving tea and mochi rn", + visibility: "private" + }) + + resp = + build_conn() + |> assign(:user, other_user) + |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"])) + |> get("/api/v1/statuses/#{activity.id}/favourited_by") + |> json_response_and_validate_schema(404) + + assert match?(%{"error" => _}, resp) + end + test "returns empty array when :show_reactions is disabled", %{conn: conn, activity: activity} do clear_config([:instance, :show_reactions], false) @@ -1857,6 +2058,25 @@ test "requires authentication for private posts", %{user: user} do assert [] == response end + + test "does fail when requesting for a non-visible status", %{user: user} do + other_user = insert(:user, local: true) + + {:ok, activity} = + CommonAPI.post(user, %{ + status: "deep below it sleeps and mustn't wake", + visibility: "private" + }) + + response = + build_conn() + |> assign(:user, other_user) + |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read"])) + |> get("/api/v1/statuses/#{activity.id}/reblogged_by") + |> json_response_and_validate_schema(404) + + assert match?(%{"error" => _}, response) + end end test "context" do @@ -1879,6 +2099,34 @@ test "context" do } = response end + test "context doesn't leak priv posts" do + %{user: user, conn: conn} = oauth_access(["read:statuses"]) + stranger = insert(:user) + + {:ok, %{id: id1}} = CommonAPI.post(stranger, %{status: "1", visibility: "public"}) + + {:ok, %{id: id2}} = + CommonAPI.post(stranger, %{status: "2", visibility: "unlisted", in_reply_to_status_id: id1}) + + {:ok, %{id: _id_boo} = act_boo} = + CommonAPI.post(stranger, %{status: "boo", visibility: "private", in_reply_to_status_id: id1}) + + refute Visibility.visible_for_user?(act_boo, user) + + response = + conn + |> get("/api/v1/statuses/#{id1}/context") + |> json_response_and_validate_schema(:ok) + + assert match?( + %{ + "ancestors" => [], + "descendants" => [%{"id" => ^id2}] + }, + response + ) + end + test "context when restrict_unauthenticated is on" do user = insert(:user) remote_user = insert(:user, local: false) 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 be2f4cfd9..14acdf471 100644 --- a/test/pleroma/web/mastodon_api/views/status_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs @@ -326,7 +326,9 @@ test "a note activity" do pinned_at: nil }, akkoma: %{ - source: HTML.filter_tags(object_data["content"]) + source: HTML.filter_tags(object_data["content"]), + in_reply_to_apid: nil, + quote_apid: nil }, quote_id: nil, quote: nil @@ -417,6 +419,17 @@ test "a reply" do assert status.in_reply_to_id == to_string(note.id) end + test "a reply to an unavailable post" do + note = insert(:note, data: %{"inReplyTo" => "https://example.org/404"}) + activity = insert(:note_activity, note: note) + + status = StatusView.render("show.json", %{activity: activity}) + + assert status.in_reply_to_id == "_" + assert status.in_reply_to_account_id == "_" + assert status.akkoma.in_reply_to_apid == "https://example.org/404" + end + test "a quote" do note = insert(:note_activity) user = insert(:user) @@ -433,12 +446,14 @@ test "a quote" do end test "a quote that we can't resolve" do - note = insert(:note_activity, quoteUri: "oopsie") + note = insert(:note, data: %{"quoteUri" => "oopsie"}) + activity = insert(:note_activity, note: note) - status = StatusView.render("show.json", %{activity: note}) + status = StatusView.render("show.json", %{activity: activity}) - assert is_nil(status.quote_id) assert is_nil(status.quote) + assert status.quote_id == "_" + assert status.akkoma.quote_apid == "oopsie" end test "a quote from a user we block" do @@ -625,6 +640,87 @@ test "attachments" do assert_schema(result, "Attachment", api_spec) end + test "attachment type will fallback to generic type if inconclusive full media type" do + # e.g. used by Bridgy + attachment_ap = %{ + "name" => "very cool image", + "type" => "Image", + # transformed into array of objects with duped mediaType by transmogrifier + "url" => [ + %{ + "type" => "Link", + "href" => + "https://bsky.network.example.org/xrpc/com.atproto.sync.getBlob?did=did:plc:xx2w6vfvn7AAAAAAA5tlosyx&cid=bafanala", + "mediaType" => "application/octet-stream" + } + ], + # inserted by transmogrifier: + "mediaType" => "application/octet-stream" + } + + resp = StatusView.render("attachment.json", %{attachment: attachment_ap}) + + api_spec = Pleroma.Web.ApiSpec.spec() + assert_schema(resp, "Attachment", api_spec) + + assert resp[:type] == "image" + assert resp[:pleroma][:mime_type] == "application/octet-stream" + end + + test "attachment alt text can use the summary attribute" do + # federated like this by e.g. GtS + alt_text = "Two sloths hanging from the same branch. It’s sunny." + + attachment_ap = %{ + "blurhash" => "L38}3{XS9EInNZtSxvxbH=ngocWT", + "mediaType" => "image/png", + "summary" => alt_text, + "type" => "Image", + "url" => [ + %{ + "type" => "Link", + "href" => + "https://gts.exampleorg/fileserver/016VVVVV/attachment/original/01JSXXYZZ.png", + "mediaType" => "image/png" + } + ] + } + + resp = StatusView.render("attachment.json", %{attachment: attachment_ap}) + + api_spec = Pleroma.Web.ApiSpec.spec() + assert_schema(resp, "Attachment", api_spec) + + assert resp[:description] == alt_text + end + + test "attachment alt text prefers the summary attribute when name is also present" do + alt_text = "Two sloths hanging from the same branch. It’s sunny." + + attachment_ap = %{ + "blurhash" => "L38}3{XS9EInNZtSxvxbH=ngocWT", + "mediaType" => "image/png", + "name" => "two_sloths.png", + "summary" => alt_text, + "type" => "Image", + "url" => [ + %{ + "type" => "Link", + "href" => + "https://gts.exampleorg/fileserver/016VVVVV/attachment/original/01JSXXYZZ.png", + "mediaType" => "image/png" + } + ] + } + + resp = StatusView.render("attachment.json", %{attachment: attachment_ap}) + + api_spec = Pleroma.Web.ApiSpec.spec() + assert_schema(resp, "Attachment", api_spec) + + assert resp[:description] == alt_text + end + test "put the url advertised in the Activity in to the url attribute" do Pleroma.Config.put([:instance, :limit_to_local_content], false) id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810" @@ -846,18 +942,6 @@ test "does not embed a relationship in the account in reposts" do assert_schema(result, "Status", Pleroma.Web.ApiSpec.spec()) end - test "visibility/list" do - user = insert(:user) - - {:ok, list} = Pleroma.List.create("foo", user) - - {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"}) - - status = StatusView.render("show.json", activity: activity) - - assert status.visibility == "list" - end - test "has a field for parent visibility" do user = insert(:user) poster = insert(:user) diff --git a/test/pleroma/web/media_proxy/media_proxy_controller_test.exs b/test/pleroma/web/media_proxy/media_proxy_controller_test.exs index 30c144917..54c498094 100644 --- a/test/pleroma/web/media_proxy/media_proxy_controller_test.exs +++ b/test/pleroma/web/media_proxy/media_proxy_controller_test.exs @@ -163,7 +163,7 @@ test "responds with 424 Failed Dependency if HEAD request to media proxy fails", media_proxy_url: media_proxy_url } do Tesla.Mock.mock(fn - %{method: "HEAD", url: ^media_proxy_url} -> + %{method: :head, url: ^media_proxy_url} -> %Tesla.Env{status: 500, body: ""} end) @@ -178,7 +178,7 @@ test "redirects to media proxy URI on unsupported content type", %{ media_proxy_url: media_proxy_url } do Tesla.Mock.mock(fn - %{method: "HEAD", url: ^media_proxy_url} -> + %{method: :head, url: ^media_proxy_url} -> %Tesla.Env{status: 200, body: "", headers: [{"content-type", "application/pdf"}]} end) @@ -198,7 +198,7 @@ test "with `static=true` and GIF image preview requested, responds with JPEG ima clear_config([:media_preview_proxy, :min_content_length], 1_000_000_000) Tesla.Mock.mock(fn - %{method: "HEAD", url: ^media_proxy_url} -> + %{method: :head, url: ^media_proxy_url} -> %Tesla.Env{ status: 200, body: "", @@ -223,7 +223,7 @@ test "with GIF image preview requested and no `static` param, redirects to media media_proxy_url: media_proxy_url } do Tesla.Mock.mock(fn - %{method: "HEAD", url: ^media_proxy_url} -> + %{method: :head, url: ^media_proxy_url} -> %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/gif"}]} end) @@ -241,7 +241,7 @@ test "with `static` param and non-GIF image preview requested, " <> media_proxy_url: media_proxy_url } do Tesla.Mock.mock(fn - %{method: "HEAD", url: ^media_proxy_url} -> + %{method: :head, url: ^media_proxy_url} -> %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/jpeg"}]} end) @@ -261,7 +261,7 @@ test "with :min_content_length setting not matched by Content-Length header, " < clear_config([:media_preview_proxy, :min_content_length], 100_000) Tesla.Mock.mock(fn - %{method: "HEAD", url: ^media_proxy_url} -> + %{method: :head, url: ^media_proxy_url} -> %Tesla.Env{ status: 200, body: "", @@ -283,7 +283,7 @@ test "thumbnails PNG images into PNG", %{ assert_dependencies_installed() Tesla.Mock.mock(fn - %{method: "HEAD", url: ^media_proxy_url} -> + %{method: :head, url: ^media_proxy_url} -> %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/png"}]} %{method: :get, url: ^media_proxy_url} -> @@ -305,7 +305,7 @@ test "thumbnails JPEG images into JPEG", %{ assert_dependencies_installed() Tesla.Mock.mock(fn - %{method: "HEAD", url: ^media_proxy_url} -> + %{method: :head, url: ^media_proxy_url} -> %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/jpeg"}]} %{method: :get, url: ^media_proxy_url} -> @@ -325,7 +325,7 @@ test "redirects to media proxy URI in case of thumbnailing error", %{ media_proxy_url: media_proxy_url } do Tesla.Mock.mock(fn - %{method: "HEAD", url: ^media_proxy_url} -> + %{method: :head, url: ^media_proxy_url} -> %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/jpeg"}]} %{method: :get, url: ^media_proxy_url} -> diff --git a/test/pleroma/web/o_status/o_status_controller_test.exs b/test/pleroma/web/o_status/o_status_controller_test.exs index c9d26b5b0..a168d3786 100644 --- a/test/pleroma/web/o_status/o_status_controller_test.exs +++ b/test/pleroma/web/o_status/o_status_controller_test.exs @@ -126,6 +126,24 @@ test "404s on nonexistent activities", %{conn: conn} do |> get("/activities/123") |> response(404) end + + test "404s on non-Create activities", %{conn: conn} do + activity = insert(:note_activity) + like_user = insert(:user) + + {:ok, like_activity} = CommonAPI.favorite(like_user, activity.id) + + like_url_path = + like_activity.data["id"] + |> String.trim_leading(Pleroma.Web.Endpoint.url()) + + assert String.starts_with?(like_url_path, "/activities/") + assert Pleroma.Web.Endpoint.url() <> like_url_path == like_activity.data["id"] + + conn + |> get(like_url_path) + |> response(404) + end end describe "GET notice/2" do diff --git a/test/pleroma/web/pleroma_api/controllers/conversation_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/conversation_controller_test.exs index 4cd6fe641..98c7835e8 100644 --- a/test/pleroma/web/pleroma_api/controllers/conversation_controller_test.exs +++ b/test/pleroma/web/pleroma_api/controllers/conversation_controller_test.exs @@ -45,8 +45,7 @@ test "/api/v1/pleroma/conversations/:id/statuses" do {:ok, activity_two} = CommonAPI.post(other_user, %{ status: "Hi!", - in_reply_to_status_id: activity.id, - in_reply_to_conversation_id: participation.id + in_reply_to_status_id: activity.id }) result = @@ -63,8 +62,7 @@ test "/api/v1/pleroma/conversations/:id/statuses" do {:ok, %{id: id_three}} = CommonAPI.post(other_user, %{ status: "Bye!", - in_reply_to_status_id: activity.id, - in_reply_to_conversation_id: participation.id + in_reply_to_status_id: activity.id }) assert [%{"id" => ^id_two}, %{"id" => ^id_three}] = diff --git a/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs index f16d163e8..179416b2a 100644 --- a/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs +++ b/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs @@ -9,10 +9,38 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionControllerTest do alias Pleroma.Object alias Pleroma.Tests.ObanHelpers alias Pleroma.User + alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.CommonAPI import Pleroma.Factory + defp prepare_reacted_post(visibility \\ "private") do + unrelated_user = insert(:user, local: true) + poster = insert(:user, local: true) + follower = insert(:user, local: true) + {:ok, _, _, %{data: %{"state" => "accept"}}} = CommonAPI.follow(follower, poster) + + {:ok, post_activity} = CommonAPI.post(poster, %{status: "miaow!", visibility: visibility}) + + if visibility != "direct" do + assert Visibility.visible_for_user?(post_activity, follower) + end + + if visibility in ["direct", "private"] do + refute Visibility.visible_for_user?(post_activity, unrelated_user) + end + + {:ok, _react_activity} = CommonAPI.react_with_emoji(post_activity.id, follower, "🐾") + + {post_activity, poster, follower, unrelated_user} + end + + defp prepare_conn_of_user(conn, user) do + conn + |> assign(:user, user) + |> assign(:token, insert(:oauth_token, user: user, scopes: ["write", "read"])) + end + test "PUT /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do user = insert(:user) other_user = insert(:user) @@ -120,6 +148,28 @@ test "PUT /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do |> json_response_and_validate_schema(400) end + test "PUT /api/v1/pleroma/statuses/:id/reactions/:emoji not allowed for non-visible posts", %{ + conn: conn + } do + {%{id: activity_id} = _activity, _author, follower, stranger} = prepare_reacted_post() + + # Works for follower + resp = + prepare_conn_of_user(conn, follower) + |> put("/api/v1/pleroma/statuses/#{activity_id}/reactions/🐈") + |> json_response_and_validate_schema(200) + + assert match?(%{"id" => ^activity_id}, resp) + + # Fails for stranger + resp = + prepare_conn_of_user(conn, stranger) + |> put("/api/v1/pleroma/statuses/#{activity_id}/reactions/🐈") + |> json_response_and_validate_schema(400) + + assert match?(%{"error" => _}, resp) + end + test "DELETE /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do user = insert(:user) other_user = insert(:user) @@ -194,6 +244,26 @@ test "DELETE /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do |> json_response(400) end + test "DELETE /api/v1/pleroma/statuses/:id/reactions/:emoji only allows original reacter to revoke", + %{conn: conn} do + {%{id: activity_id} = _activity, author, follower, unrelated} = prepare_reacted_post("public") + + # Works for original reacter + prepare_conn_of_user(conn, follower) + |> delete("/api/v1/pleroma/statuses/#{activity_id}/reactions/🐾") + |> json_response_and_validate_schema(200) + + # Fails for anyone else + for u <- [author, unrelated] do + resp = + prepare_conn_of_user(conn, u) + |> delete("/api/v1/pleroma/statuses/#{activity_id}/reactions/🐾") + |> json_response(400) + + assert match?(%{"error" => _}, resp) + end + end + test "GET /api/v1/pleroma/statuses/:id/reactions", %{conn: conn} do user = insert(:user) other_user = insert(:user) @@ -275,6 +345,28 @@ test "GET /api/v1/pleroma/statuses/:id/reactions?with_muted=true", %{conn: conn} assert [%{"name" => "🎅", "count" => 2}] = result end + test "GET /api/v1/pleroma/statuses/:id/reactions not allowed for non-visible posts", %{ + conn: conn + } do + {%{id: activity_id} = _activity, _author, follower, stranger} = prepare_reacted_post() + + # Works for follower + resp = + prepare_conn_of_user(conn, follower) + |> get("/api/v1/pleroma/statuses/#{activity_id}/reactions") + |> json_response_and_validate_schema(200) + + assert match?([%{"name" => _, "count" => _} | _], resp) + + # Fails for stranger + resp = + prepare_conn_of_user(conn, stranger) + |> get("/api/v1/pleroma/statuses/#{activity_id}/reactions") + |> json_response_and_validate_schema(403) + + assert match?(%{"error" => _}, resp) + end + test "GET /api/v1/pleroma/statuses/:id/reactions with :show_reactions disabled", %{conn: conn} do clear_config([:instance, :show_reactions], false) @@ -323,4 +415,20 @@ test "GET /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do assert represented_user["id"] == other_user.id end + + test "GET /api/v1/pleroma/statuses/:id/reactions/:emoji not allowed for non-visible posts", %{ + conn: conn + } do + {%{id: activity_id} = _activity, _author, follower, stranger} = prepare_reacted_post() + + # Works for follower + prepare_conn_of_user(conn, follower) + |> get("/api/v1/pleroma/statuses/#{activity_id}/reactions/🐈") + |> json_response_and_validate_schema(200) + + # Fails for stranger + prepare_conn_of_user(conn, stranger) + |> get("/api/v1/pleroma/statuses/#{activity_id}/reactions/🐈") + |> json_response_and_validate_schema(403) + end end diff --git a/test/pleroma/web/plugs/cache_test.exs b/test/pleroma/web/plugs/cache_test.exs index 4e729cafb..f489adf74 100644 --- a/test/pleroma/web/plugs/cache_test.exs +++ b/test/pleroma/web/plugs/cache_test.exs @@ -5,7 +5,8 @@ defmodule Pleroma.Web.Plugs.CacheTest do # Relies on Cachex, has to stay synchronous use Pleroma.DataCase - use Plug.Test + import Plug.Test + import Plug.Conn alias Pleroma.Web.Plugs.Cache diff --git a/test/pleroma/web/plugs/digest_plug_test.exs b/test/pleroma/web/plugs/digest_plug_test.exs index 629c28c93..484974981 100644 --- a/test/pleroma/web/plugs/digest_plug_test.exs +++ b/test/pleroma/web/plugs/digest_plug_test.exs @@ -1,6 +1,7 @@ defmodule Pleroma.Web.Plugs.DigestPlugTest do use ExUnit.Case, async: true - use Plug.Test + import Plug.Test + import Plug.Conn test "digest algorithm is taken from digest header" do body = "{\"hello\": \"world\"}" diff --git a/test/pleroma/web/plugs/http_signature_plug_test.exs b/test/pleroma/web/plugs/http_signature_plug_test.exs index 5236b519e..681bea10b 100644 --- a/test/pleroma/web/plugs/http_signature_plug_test.exs +++ b/test/pleroma/web/plugs/http_signature_plug_test.exs @@ -7,13 +7,13 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do @moduletag :mocked import Pleroma.Factory alias Pleroma.Web.Plugs.HTTPSignaturePlug - alias Pleroma.Instances.Instance - alias Pleroma.Repo import Plug.Conn import Phoenix.Controller, only: [put_format: 2] import Mock + @user_ap_id "http://mastodon.example.org/users/admin" + setup do user = :user @@ -26,35 +26,27 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do setup_with_mocks([ {HTTPSignatures, [], [ - signature_for_conn: fn _ -> - %{ - "keyId" => "http://mastodon.example.org/users/admin#main-key", - "created" => "1234567890", - "expires" => "1234567890" - } - end, - validate_conn: fn conn -> - Map.get(conn.assigns, :valid_signature, true) + validate_conn: fn conn, _ -> + cond do + Map.get(conn.assigns, :gone_signature_key, false) -> + {:error, :gone} + + Map.get(conn.assigns, :rejected_key_id, false) -> + {:error, {:reject, :mrf}} + + Map.get(conn.assigns, :valid_signature, true) -> + {:ok, user} = Pleroma.User.get_or_fetch_by_ap_id(@user_ap_id) + {:ok, %HTTPSignatures.HTTPKey{key: "aaa", user_data: %{"key_user" => user}}} + + true -> + {:error, :wrong_signature} + end end ]} ]) do :ok end - defp submit_to_plug(host), do: submit_to_plug(host, :get, "/doesntmattter") - - defp submit_to_plug(host, method, path) do - params = %{"actor" => "http://#{host}/users/admin"} - - build_conn(method, path, params) - |> put_req_header( - "signature", - "keyId=\"http://#{host}/users/admin#main-key" - ) - |> put_format("activity+json") - |> HTTPSignaturePlug.call(%{}) - end - test "it call HTTPSignatures to check validity if the actor signed it", %{user: user} do params = %{"actor" => user.ap_id} conn = build_conn(:get, "/doesntmattter", params) @@ -69,36 +61,9 @@ test "it call HTTPSignatures to check validity if the actor signed it", %{user: |> HTTPSignaturePlug.call(%{}) assert conn.assigns.valid_signature == true - assert conn.assigns.signature_actor_id == params["actor"] + assert conn.assigns.signature_user.ap_id == params["actor"] assert conn.halted == false - assert called(HTTPSignatures.validate_conn(:_)) - end - - test "it sets request signatures property on the instance" do - host = "mastodon.example.org" - conn = submit_to_plug(host) - assert conn.assigns.valid_signature == true - instance = Repo.get_by(Instance, %{host: host}) - assert instance.has_request_signatures - end - - test "it does not set request signatures property on the instance when using inbox" do - host = "mastodon.example.org" - conn = submit_to_plug(host, :post, "/inbox") - assert conn.assigns.valid_signature == true - - # we don't even create the instance entry if its just POST /inbox - refute Repo.get_by(Instance, %{host: host}) - end - - test "it does not set request signatures property on the instance when its cached" do - host = "mastodon.example.org" - Cachex.put(:request_signatures_cache, host, true) - conn = submit_to_plug(host) - assert conn.assigns.valid_signature == true - - # we don't even create the instance entry if it was already done - refute Repo.get_by(Instance, %{host: host}) + assert called(HTTPSignatures.validate_conn(:_, :_)) end describe "requires a signature when `authorized_fetch_mode` is enabled" do @@ -122,7 +87,7 @@ test "and signature is present and incorrect", %{conn: conn} do |> HTTPSignaturePlug.call(%{}) assert conn.assigns.valid_signature == false - assert called(HTTPSignatures.validate_conn(:_)) + assert called(HTTPSignatures.validate_conn(:_, :_)) end test "and signature is correct", %{conn: conn} do @@ -135,7 +100,7 @@ test "and signature is correct", %{conn: conn} do |> HTTPSignaturePlug.call(%{}) assert conn.assigns.valid_signature == true - assert called(HTTPSignatures.validate_conn(:_)) + assert called(HTTPSignatures.validate_conn(:_, :_)) end test "and halts the connection when `signature` header is not present", %{conn: conn} do @@ -151,21 +116,74 @@ test "aliases redirected /object endpoints", _ do path = URI.parse(obj.data["id"]).path conn = build_conn(:get, path, params) - assert ["/notice/#{act.id}", "/notice/#{act.id}?actor=someparam"] == - HTTPSignaturePlug.route_aliases(conn) + aliases = + HTTPSignaturePlug.route_aliases(conn) + |> Enum.reduce([], fn + x, acc when is_binary(x) -> + acc ++ [x] + + f, acc when is_function(f) -> + add = + case f.() do + a when is_binary(a) -> [a] + a -> a + end + + acc ++ add + end) + + assert ["get /notice/#{act.id}", "get /notice/#{act.id}?actor=someparam"] == aliases end - test "(created) psudoheader", _ do - conn = build_conn(:get, "/doesntmattter") - conn = HTTPSignaturePlug.maybe_put_created_psudoheader(conn) - created_header = List.keyfind(conn.req_headers, "(created)", 0) - assert {_, "1234567890"} = created_header + test "fakes success on gone key when receiving Delete" do + build_conn(:post, "/inbox", %{"type" => "Delete"}) + |> put_format("activity+json") + |> assign(:gone_signature_key, true) + |> put_req_header( + "signature", + "keyId=\"http://somewhere.example.org/users/deleted#main-key\"" + ) + |> HTTPSignaturePlug.call(%{}) + |> response(202) end - test "(expires) psudoheader", _ do - conn = build_conn(:get, "/doesntmattter") - conn = HTTPSignaturePlug.maybe_put_expires_psudoheader(conn) - expires_header = List.keyfind(conn.req_headers, "(expires)", 0) - assert {_, "1234567890"} = expires_header + test "fails on gone key for non-Delete" do + conn = + build_conn(:post, "/inbox", %{"type" => "Note"}) + |> put_format("activity+json") + |> assign(:gone_signature_key, true) + |> put_req_header( + "signature", + "keyId=\"http://somewhere.example.org/users/deleted#main-key\"" + ) + |> HTTPSignaturePlug.call(%{}) + + refute conn.halted + assert conn.assigns.valid_signature == false + assert conn.assigns.signature_user == nil + end + + test "fakes accept for POST on rejected keys", %{user: user} do + build_conn(:post, "/inbox", %{"type" => "Note"}) + |> put_format("activity+json") + |> assign(:rejected_key_id, true) + |> put_req_header( + "signature", + "keyId=\"#{user.signing_key.key_id}\"" + ) + |> HTTPSignaturePlug.call(%{}) + |> response(202) + end + + test "fakes not found for GET on rejected keys", %{user: user} do + build_conn(:get, "/doesntmattter", %{"user" => user.ap_id}) + |> put_format("activity+json") + |> assign(:rejected_key_id, true) + |> put_req_header( + "signature", + "keyId=\"#{user.signing_key.key_id}\"" + ) + |> HTTPSignaturePlug.call(%{}) + |> response(404) end end diff --git a/test/pleroma/web/plugs/idempotency_plug_test.exs b/test/pleroma/web/plugs/idempotency_plug_test.exs index dd8cda664..08c99066a 100644 --- a/test/pleroma/web/plugs/idempotency_plug_test.exs +++ b/test/pleroma/web/plugs/idempotency_plug_test.exs @@ -5,7 +5,8 @@ defmodule Pleroma.Web.Plugs.IdempotencyPlugTest do # Relies on Cachex, has to stay synchronous use Pleroma.DataCase - use Plug.Test + import Plug.Test + import Plug.Conn alias Pleroma.Web.Plugs.IdempotencyPlug alias Plug.Conn diff --git a/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs b/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs index 5789cd756..f68607f22 100644 --- a/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs +++ b/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs @@ -22,16 +22,21 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlugTest do {:ok, %{user: user}} end - defp set_signature(conn, ap_id) do + defp set_signature(conn, %Pleroma.User{} = user) do conn - |> put_req_header("signature", "keyId=\"#{ap_id}#main-key\"") |> assign(:valid_signature, true) + |> assign(:signature_user, user) + end + + defp set_signature(conn, ap_id) when is_binary(ap_id) do + {:ok, user} = Pleroma.User.get_or_fetch_by_ap_id(ap_id) + set_signature(conn, user) end test "it successfully maps a valid identity with a valid signature", %{user: user} do conn = build_conn(:get, "/doesntmattter") - |> set_signature(user.ap_id) + |> set_signature(user) |> MappedSignatureToIdentityPlug.call(%{}) refute is_nil(conn.assigns.user) @@ -40,7 +45,7 @@ test "it successfully maps a valid identity with a valid signature", %{user: use test "it successfully maps a valid identity with a valid signature with payload", %{user: user} do conn = build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id}) - |> set_signature(user.ap_id) + |> set_signature(user) |> MappedSignatureToIdentityPlug.call(%{}) refute is_nil(conn.assigns.user) @@ -52,7 +57,9 @@ test "it considers a mapped identity to be invalid when it mismatches a payload" |> set_signature("https://niu.moe/users/rye") |> MappedSignatureToIdentityPlug.call(%{}) - assert %{valid_signature: false} == conn.assigns + assert conn.assigns.valid_signature == false + refute is_nil(conn.assigns.signature_user) + refute match?(%{user: _}, conn.assigns) end test "it considers a mapped identity to be invalid when the associated instance is blocked", %{ @@ -74,10 +81,12 @@ test "it considers a mapped identity to be invalid when the associated instance conn = build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id}) - |> set_signature(user.ap_id) + |> set_signature(user) |> MappedSignatureToIdentityPlug.call(%{}) - assert %{valid_signature: false} == conn.assigns + assert conn.assigns.valid_signature == false + refute is_nil(conn.assigns.signature_user) + refute match?(%{user: _}, conn.assigns) end test "allowlist federation: it considers a mapped identity to be valid when the associated instance is allowed", @@ -97,7 +106,7 @@ test "allowlist federation: it considers a mapped identity to be valid when the conn = build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id}) - |> set_signature(user.ap_id) + |> set_signature(user) |> MappedSignatureToIdentityPlug.call(%{}) assert conn.assigns[:valid_signature] @@ -119,19 +128,11 @@ test "allowlist federation: it considers a mapped identity to be invalid when th conn = build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id}) - |> set_signature(user.ap_id) + |> set_signature(user) |> MappedSignatureToIdentityPlug.call(%{}) - assert %{valid_signature: false} == conn.assigns - end - - @tag skip: "known breakage; the testsuite presently depends on it" - test "it considers a mapped identity to be invalid when the identity cannot be found" do - conn = - build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"}) - |> set_signature("http://niu.moe/users/rye") - |> MappedSignatureToIdentityPlug.call(%{}) - - assert %{valid_signature: false} == conn.assigns + assert conn.assigns.valid_signature == false + refute is_nil(conn.assigns.signature_user) + refute match?(%{user: _}, conn.assigns) end end diff --git a/test/pleroma/web/plugs/rate_limiter_test.exs b/test/pleroma/web/plugs/rate_limiter_test.exs index a1f6cc5fe..4848cdc3d 100644 --- a/test/pleroma/web/plugs/rate_limiter_test.exs +++ b/test/pleroma/web/plugs/rate_limiter_test.exs @@ -273,6 +273,6 @@ def expire_ttl(%{remote_ip: remote_ip} = _conn, bucket_name_root) do key_name = "ip::#{remote_ip |> Tuple.to_list() |> Enum.join(".")}" {:ok, bucket_value} = Cachex.get(bucket_name, key_name) - Cachex.put(bucket_name, key_name, bucket_value, ttl: -1) + Cachex.put(bucket_name, key_name, bucket_value, expire: -1) end end diff --git a/test/pleroma/web/plugs/remote_ip_test.exs b/test/pleroma/web/plugs/remote_ip_test.exs index b77fa25f2..43c0d2c92 100644 --- a/test/pleroma/web/plugs/remote_ip_test.exs +++ b/test/pleroma/web/plugs/remote_ip_test.exs @@ -4,7 +4,8 @@ defmodule Pleroma.Web.Plugs.RemoteIpTest do use ExUnit.Case - use Plug.Test + import Plug.Test + import Plug.Conn alias Pleroma.Web.Plugs.RemoteIp diff --git a/test/pleroma/web/plugs/set_format_plug_test.exs b/test/pleroma/web/plugs/set_format_plug_test.exs index 21043f698..7246ea7cc 100644 --- a/test/pleroma/web/plugs/set_format_plug_test.exs +++ b/test/pleroma/web/plugs/set_format_plug_test.exs @@ -4,7 +4,8 @@ defmodule Pleroma.Web.Plugs.SetFormatPlugTest do use ExUnit.Case, async: true - use Plug.Test + import Plug.Test + import Plug.Conn alias Pleroma.Web.Plugs.SetFormatPlug diff --git a/test/pleroma/web/plugs/set_locale_plug_test.exs b/test/pleroma/web/plugs/set_locale_plug_test.exs index f9d34bbe4..c6736369c 100644 --- a/test/pleroma/web/plugs/set_locale_plug_test.exs +++ b/test/pleroma/web/plugs/set_locale_plug_test.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Web.Plugs.SetLocalePlugTest do use ExUnit.Case, async: true - use Plug.Test + import Plug.Test alias Pleroma.Web.Plugs.SetLocalePlug alias Plug.Conn diff --git a/test/pleroma/web/rich_media/parsers/twitter_card_test.exs b/test/pleroma/web/rich_media/parsers/twitter_card_test.exs index 90ccc7868..469f39cfd 100644 --- a/test/pleroma/web/rich_media/parsers/twitter_card_test.exs +++ b/test/pleroma/web/rich_media/parsers/twitter_card_test.exs @@ -102,7 +102,7 @@ test "respect only first title tag on the page" do } end - test "takes first founded title in html head if there is html markup error" do + test "takes first found title in html head if there is html markup error" do html = File.read!("test/fixtures/nypd-facial-recognition-children-teenagers4.html") |> Floki.parse_document!() diff --git a/test/support/mocks.ex b/test/support/mocks.ex index cc34b21a5..8ade26a78 100644 --- a/test/support/mocks.ex +++ b/test/support/mocks.ex @@ -27,5 +27,3 @@ 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 909b10365..211529805 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -6,6 +6,7 @@ ExUnit.start( capture_log: true, + assert_receive_timeout: 1_000, exclude: [:federated, :erratic] ++ os_exclude )