forked from AkkomaGang/akkoma
Merge pull request 'develop' (#6) from AkkomaGang/akkoma:develop into develop
Reviewed-on: #6
This commit is contained in:
commit
b366b9cd6b
108 changed files with 14730 additions and 3598 deletions
|
@ -1,3 +1,8 @@
|
||||||
|
platform: linux/amd64
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- test
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
- &scw-secrets
|
- &scw-secrets
|
||||||
- SCW_ACCESS_KEY
|
- SCW_ACCESS_KEY
|
||||||
|
@ -12,8 +17,6 @@ variables:
|
||||||
branch:
|
branch:
|
||||||
- develop
|
- develop
|
||||||
- stable
|
- stable
|
||||||
- refs/tags/v*
|
|
||||||
- refs/tags/stable-*
|
|
||||||
- &on-stable
|
- &on-stable
|
||||||
when:
|
when:
|
||||||
event:
|
event:
|
||||||
|
@ -21,14 +24,6 @@ variables:
|
||||||
- tag
|
- tag
|
||||||
branch:
|
branch:
|
||||||
- stable
|
- stable
|
||||||
- refs/tags/stable-*
|
|
||||||
- &on-point-release
|
|
||||||
when:
|
|
||||||
event:
|
|
||||||
- push
|
|
||||||
branch:
|
|
||||||
- develop
|
|
||||||
- stable
|
|
||||||
- &on-pr-open
|
- &on-pr-open
|
||||||
when:
|
when:
|
||||||
event:
|
event:
|
||||||
|
@ -39,60 +34,7 @@ variables:
|
||||||
- &clean "(rm -rf release || true) && (rm -rf _build || true) && (rm -rf /root/.mix)"
|
- &clean "(rm -rf release || true) && (rm -rf _build || true) && (rm -rf /root/.mix)"
|
||||||
- &mix-clean "mix deps.clean --all && mix clean"
|
- &mix-clean "mix deps.clean --all && mix clean"
|
||||||
|
|
||||||
services:
|
|
||||||
postgres:
|
|
||||||
image: postgres:15
|
|
||||||
when:
|
|
||||||
event:
|
|
||||||
- pull_request
|
|
||||||
environment:
|
|
||||||
POSTGRES_DB: pleroma_test
|
|
||||||
POSTGRES_USER: postgres
|
|
||||||
POSTGRES_PASSWORD: postgres
|
|
||||||
|
|
||||||
pipeline:
|
pipeline:
|
||||||
lint:
|
|
||||||
<<: *on-pr-open
|
|
||||||
image: akkoma/ci-base:1.14
|
|
||||||
commands:
|
|
||||||
- mix local.hex --force
|
|
||||||
- mix local.rebar --force
|
|
||||||
- mix format --check-formatted
|
|
||||||
|
|
||||||
build:
|
|
||||||
image: akkoma/ci-base:1.14
|
|
||||||
<<: *on-pr-open
|
|
||||||
environment:
|
|
||||||
MIX_ENV: test
|
|
||||||
POSTGRES_DB: pleroma_test
|
|
||||||
POSTGRES_USER: postgres
|
|
||||||
POSTGRES_PASSWORD: postgres
|
|
||||||
DB_HOST: postgres
|
|
||||||
commands:
|
|
||||||
- mix local.hex --force
|
|
||||||
- mix local.rebar --force
|
|
||||||
- mix deps.get
|
|
||||||
- mix compile
|
|
||||||
|
|
||||||
test:
|
|
||||||
image: akkoma/ci-base:1.14
|
|
||||||
<<: *on-pr-open
|
|
||||||
environment:
|
|
||||||
MIX_ENV: test
|
|
||||||
POSTGRES_DB: pleroma_test
|
|
||||||
POSTGRES_USER: postgres
|
|
||||||
POSTGRES_PASSWORD: postgres
|
|
||||||
DB_HOST: postgres
|
|
||||||
commands:
|
|
||||||
- mix local.hex --force
|
|
||||||
- mix local.rebar --force
|
|
||||||
- mix deps.get
|
|
||||||
- mix compile
|
|
||||||
- mix ecto.drop -f -q
|
|
||||||
- mix ecto.create
|
|
||||||
- mix ecto.migrate
|
|
||||||
- mix test --preload-modules --exclude erratic --exclude federated --max-cases 4
|
|
||||||
|
|
||||||
# Canonical amd64
|
# Canonical amd64
|
||||||
ubuntu22:
|
ubuntu22:
|
||||||
image: hexpm/elixir:1.14.3-erlang-25.2.2-ubuntu-jammy-20221130
|
image: hexpm/elixir:1.14.3-erlang-25.2.2-ubuntu-jammy-20221130
|
||||||
|
@ -151,8 +93,8 @@ pipeline:
|
||||||
|
|
||||||
# Canonical amd64-musl
|
# Canonical amd64-musl
|
||||||
musl:
|
musl:
|
||||||
image: hexpm/elixir:1.14.3-erlang-25.2.2-alpine-3.15.6
|
image: hexpm/elixir:1.14.3-erlang-25.2.2-alpine-3.18.0
|
||||||
<<: *on-release
|
<<: *on-stable
|
||||||
environment:
|
environment:
|
||||||
MIX_ENV: prod
|
MIX_ENV: prod
|
||||||
commands:
|
commands:
|
||||||
|
@ -167,31 +109,9 @@ pipeline:
|
||||||
|
|
||||||
release-musl:
|
release-musl:
|
||||||
image: akkoma/releaser
|
image: akkoma/releaser
|
||||||
<<: *on-release
|
<<: *on-stable
|
||||||
secrets: *scw-secrets
|
secrets: *scw-secrets
|
||||||
commands:
|
commands:
|
||||||
- export SOURCE=akkoma-amd64-musl.zip
|
- export SOURCE=akkoma-amd64-musl.zip
|
||||||
- export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-amd64-musl.zip
|
- export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-amd64-musl.zip
|
||||||
- /bin/sh /entrypoint.sh
|
- /bin/sh /entrypoint.sh
|
||||||
|
|
||||||
docs:
|
|
||||||
<<: *on-point-release
|
|
||||||
secrets:
|
|
||||||
- SCW_ACCESS_KEY
|
|
||||||
- SCW_SECRET_KEY
|
|
||||||
- SCW_DEFAULT_ORGANIZATION_ID
|
|
||||||
environment:
|
|
||||||
CI: "true"
|
|
||||||
image: python:3.10-slim
|
|
||||||
commands:
|
|
||||||
- apt-get update && apt-get install -y rclone wget git zip
|
|
||||||
- wget https://github.com/scaleway/scaleway-cli/releases/download/v2.5.1/scaleway-cli_2.5.1_linux_amd64
|
|
||||||
- mv scaleway-cli_2.5.1_linux_amd64 scaleway-cli
|
|
||||||
- chmod +x scaleway-cli
|
|
||||||
- ./scaleway-cli object config install type=rclone
|
|
||||||
- cd docs
|
|
||||||
- pip install -r requirements.txt
|
|
||||||
- mkdocs build
|
|
||||||
- zip -r docs.zip site/*
|
|
||||||
- cd site
|
|
||||||
- rclone copy . scaleway:akkoma-docs/$CI_COMMIT_BRANCH/
|
|
115
.woodpecker/build-arm64.yml
Normal file
115
.woodpecker/build-arm64.yml
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
platform: linux/arm64
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- test
|
||||||
|
|
||||||
|
variables:
|
||||||
|
- &scw-secrets
|
||||||
|
- SCW_ACCESS_KEY
|
||||||
|
- SCW_SECRET_KEY
|
||||||
|
- 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"
|
||||||
|
|
||||||
|
pipeline:
|
||||||
|
# Canonical arm64
|
||||||
|
ubuntu22:
|
||||||
|
image: hexpm/elixir:1.14.3-erlang-25.2.2-ubuntu-jammy-20221130
|
||||||
|
<<: *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-ubuntu-jammy.zip -r release
|
||||||
|
|
||||||
|
release-ubuntu22:
|
||||||
|
image: akkoma/releaser:arm64
|
||||||
|
<<: *on-release
|
||||||
|
secrets: *scw-secrets
|
||||||
|
commands:
|
||||||
|
- export SOURCE=akkoma-ubuntu-jammy.zip
|
||||||
|
- export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-arm64-ubuntu-jammy.zip
|
||||||
|
- /bin/sh /entrypoint.sh
|
||||||
|
|
||||||
|
debian-bullseye:
|
||||||
|
image: hexpm/elixir:1.14.3-erlang-25.2.2-debian-bullseye-20230109
|
||||||
|
<<: *on-stable
|
||||||
|
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 gcc make g++ wget
|
||||||
|
- *clean
|
||||||
|
- echo "import Config" > config/prod.secret.exs
|
||||||
|
- *setup-hex
|
||||||
|
- *tag-build
|
||||||
|
- *mix-clean
|
||||||
|
- mix deps.get --only prod
|
||||||
|
- mix release --path release
|
||||||
|
- zip akkoma-arm64.zip -r release
|
||||||
|
|
||||||
|
release-debian:
|
||||||
|
image: akkoma/releaser:arm64
|
||||||
|
<<: *on-stable
|
||||||
|
secrets: *scw-secrets
|
||||||
|
commands:
|
||||||
|
- export SOURCE=akkoma-arm64.zip
|
||||||
|
- export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-arm64.zip
|
||||||
|
- /bin/sh /entrypoint.sh
|
||||||
|
- export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-debian-stable.zip
|
||||||
|
- /bin/sh /entrypoint.sh
|
||||||
|
|
||||||
|
# Canonical arm64-musl
|
||||||
|
musl:
|
||||||
|
image: hexpm/elixir:1.14.3-erlang-25.2.2-alpine-3.18.0
|
||||||
|
<<: *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
|
||||||
|
secrets: *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
|
69
.woodpecker/docs.yml
Normal file
69
.woodpecker/docs.yml
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
platform: linux/amd64
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- test
|
||||||
|
- build-amd64
|
||||||
|
|
||||||
|
variables:
|
||||||
|
- &scw-secrets
|
||||||
|
- SCW_ACCESS_KEY
|
||||||
|
- SCW_SECRET_KEY
|
||||||
|
- SCW_DEFAULT_ORGANIZATION_ID
|
||||||
|
- &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"
|
||||||
|
|
||||||
|
pipeline:
|
||||||
|
docs:
|
||||||
|
<<: *on-point-release
|
||||||
|
secrets:
|
||||||
|
- SCW_ACCESS_KEY
|
||||||
|
- SCW_SECRET_KEY
|
||||||
|
- SCW_DEFAULT_ORGANIZATION_ID
|
||||||
|
environment:
|
||||||
|
CI: "true"
|
||||||
|
image: python:3.10-slim
|
||||||
|
commands:
|
||||||
|
- apt-get update && apt-get install -y rclone wget git zip
|
||||||
|
- wget https://github.com/scaleway/scaleway-cli/releases/download/v2.5.1/scaleway-cli_2.5.1_linux_amd64
|
||||||
|
- mv scaleway-cli_2.5.1_linux_amd64 scaleway-cli
|
||||||
|
- chmod +x scaleway-cli
|
||||||
|
- ./scaleway-cli object config install type=rclone
|
||||||
|
- cd docs
|
||||||
|
- pip install -r requirements.txt
|
||||||
|
- mkdocs build
|
||||||
|
- zip -r docs.zip site/*
|
||||||
|
- cd site
|
||||||
|
- rclone copy . scaleway:akkoma-docs/$CI_COMMIT_BRANCH/
|
96
.woodpecker/test.yml
Normal file
96
.woodpecker/test.yml
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
platform: linux/amd64
|
||||||
|
|
||||||
|
variables:
|
||||||
|
- &scw-secrets
|
||||||
|
- SCW_ACCESS_KEY
|
||||||
|
- SCW_SECRET_KEY
|
||||||
|
- SCW_DEFAULT_ORGANIZATION_ID
|
||||||
|
- &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"
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:15
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- pull_request
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: pleroma_test
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
|
||||||
|
pipeline:
|
||||||
|
lint:
|
||||||
|
<<: *on-pr-open
|
||||||
|
image: akkoma/ci-base:1.14
|
||||||
|
commands:
|
||||||
|
- mix local.hex --force
|
||||||
|
- mix local.rebar --force
|
||||||
|
- mix format --check-formatted
|
||||||
|
|
||||||
|
build:
|
||||||
|
image: akkoma/ci-base:1.14
|
||||||
|
<<: *on-pr-open
|
||||||
|
environment:
|
||||||
|
MIX_ENV: test
|
||||||
|
POSTGRES_DB: pleroma_test
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
DB_HOST: postgres
|
||||||
|
commands:
|
||||||
|
- mix local.hex --force
|
||||||
|
- mix local.rebar --force
|
||||||
|
- mix deps.get
|
||||||
|
- mix compile
|
||||||
|
|
||||||
|
test:
|
||||||
|
image: akkoma/ci-base:1.14
|
||||||
|
<<: *on-pr-open
|
||||||
|
environment:
|
||||||
|
MIX_ENV: test
|
||||||
|
POSTGRES_DB: pleroma_test
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
DB_HOST: postgres
|
||||||
|
commands:
|
||||||
|
- mix local.hex --force
|
||||||
|
- mix local.rebar --force
|
||||||
|
- mix deps.get
|
||||||
|
- mix compile
|
||||||
|
- mix ecto.drop -f -q
|
||||||
|
- mix ecto.create
|
||||||
|
- mix ecto.migrate
|
||||||
|
- mix test --preload-modules --exclude erratic --exclude federated --max-cases 4
|
58
CHANGELOG.md
58
CHANGELOG.md
|
@ -6,6 +6,63 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
## Added
|
||||||
|
|
||||||
|
- Added a new configuration option to the MediaProxy feature that allows the blocking of specific domains from using the media proxy or being explicitly allowed by the Content-Security-Policy.
|
||||||
|
- Please make sure instances you wanted to block media from are not in the MediaProxy `whitelist`, and instead use `blocklist`.
|
||||||
|
- `OnlyMedia` Upload Filter to simplify restricting uploads to audio, image, and video types
|
||||||
|
- ARM64 OTP builds
|
||||||
|
- Ubuntu22 builds are available for develop and stable
|
||||||
|
- other distributions are stable only
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
|
||||||
|
- Alpine OTP builds are now from alpine 3.18, which is SSLv3 compatible.
|
||||||
|
If you use alpine OTP builds you will have to update your local system.
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
- Deactivated users can no longer show up in the emoji reaction list
|
||||||
|
- Embedded posts can no longer bypass `:restrict\_unauthenticated`
|
||||||
|
- GET/HEAD requests will now work when requesting AWS-based instances.
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
- Add `no_new_privs` hardening to OpenRC and systemd service files
|
||||||
|
|
||||||
|
## 2023.05
|
||||||
|
|
||||||
|
## Added
|
||||||
|
- Custom options for users to accept/reject private messages
|
||||||
|
- options: everybody, nobody, people\_i\_follow
|
||||||
|
- MRF to reject notes from accounts newer than a given age
|
||||||
|
- this will have the side-effect of rejecting legitimate messages if your
|
||||||
|
post gets boosted outside of your local bubble and people your instance
|
||||||
|
does not know about reply to it.
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
- Support for `streams` public key URIs
|
||||||
|
- Bookmarks are cleaned up on DB prune now
|
||||||
|
|
||||||
|
## Security
|
||||||
|
- Fixed mediaproxy being a bit of a silly billy
|
||||||
|
|
||||||
|
## 2023.04
|
||||||
|
|
||||||
|
## Added
|
||||||
|
- Nodeinfo keys for unauthenticated timeline visibility
|
||||||
|
- Option to disable federated timeline
|
||||||
|
- Option to make the bubble timeline publicly accessible
|
||||||
|
- Ability to swap between installed standard frontends
|
||||||
|
- *mastodon frontends are still not counted as standard frontends due to the complexity in serving them correctly*.
|
||||||
|
|
||||||
|
### Upgrade Notes
|
||||||
|
- Elixir 1.14 is now required. If your distribution does not package this, you can
|
||||||
|
use [asdf](https://asdf-vm.com/). At time of writing, elixir 1.14.3 / erlang 25.3
|
||||||
|
is confirmed to work.
|
||||||
|
|
||||||
|
## 2023.03
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
- Support for `streams` public key URIs
|
- Support for `streams` public key URIs
|
||||||
|
|
||||||
|
@ -74,7 +131,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Rich media will now hard-exit after 5 seconds, to prevent timeline hangs
|
- Rich media will now hard-exit after 5 seconds, to prevent timeline hangs
|
||||||
- HTTP Content Security Policy is now far more strict to prevent any potential XSS/CSS leakages
|
- HTTP Content Security Policy is now far more strict to prevent any potential XSS/CSS leakages
|
||||||
- Follow requests are now paginated, matches mastodon API spec, so use the Link header to paginate.
|
- Follow requests are now paginated, matches mastodon API spec, so use the Link header to paginate.
|
||||||
- `internal.fetch` and `relay` actors are now represented with the actor type `Application`
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- /api/v1/accounts/lookup will now respect restrict\_unauthenticated
|
- /api/v1/accounts/lookup will now respect restrict\_unauthenticated
|
||||||
|
|
|
@ -418,6 +418,8 @@
|
||||||
|
|
||||||
config :pleroma, :mrf_follow_bot, follower_nickname: nil
|
config :pleroma, :mrf_follow_bot, follower_nickname: nil
|
||||||
|
|
||||||
|
config :pleroma, :mrf_reject_newly_created_account_notes, age: 86_400
|
||||||
|
|
||||||
config :pleroma, :rich_media,
|
config :pleroma, :rich_media,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
ignore_hosts: [],
|
ignore_hosts: [],
|
||||||
|
@ -441,7 +443,8 @@
|
||||||
# Note: max_read_duration defaults to Pleroma.ReverseProxy.max_read_duration_default/1
|
# Note: max_read_duration defaults to Pleroma.ReverseProxy.max_read_duration_default/1
|
||||||
max_read_duration: 30_000
|
max_read_duration: 30_000
|
||||||
],
|
],
|
||||||
whitelist: []
|
whitelist: [],
|
||||||
|
blocklist: []
|
||||||
|
|
||||||
config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Http,
|
config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Http,
|
||||||
method: :purge,
|
method: :purge,
|
||||||
|
|
|
@ -1558,7 +1558,21 @@
|
||||||
%{
|
%{
|
||||||
key: :whitelist,
|
key: :whitelist,
|
||||||
type: {:list, :string},
|
type: {:list, :string},
|
||||||
description: "List of hosts with scheme to bypass the MediaProxy",
|
description: """
|
||||||
|
List of hosts with scheme to bypass the MediaProxy.\n
|
||||||
|
The media will be fetched by the client, directly from the remote server.\n
|
||||||
|
To allow this, it will Content-Security-Policy exceptions for each instance listed.\n
|
||||||
|
This is to be used for instances you trust and do not want to cache media for.
|
||||||
|
""",
|
||||||
|
suggestions: ["http://example.com"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :blocklist,
|
||||||
|
type: {:list, :string},
|
||||||
|
description: """
|
||||||
|
List of hosts with scheme which will not go through the MediaProxy, and will not be explicitly allowed by the Content-Security-Policy.
|
||||||
|
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.
|
||||||
|
""",
|
||||||
suggestions: ["http://example.com"]
|
suggestions: ["http://example.com"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
docker-compose build --build-arg UID=$(id -u) --build-arg GID=$(id -g) akkoma
|
docker compose build --build-arg UID=$(id -u) --build-arg GID=$(id -g) akkoma
|
||||||
docker-compose build --build-arg UID=$(id -u) --build-arg GID=$(id -g) db
|
docker compose build --build-arg UID=$(id -u) --build-arg GID=$(id -g) db
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
docker-compose run --rm akkoma $@
|
docker compose run --rm akkoma $@
|
||||||
|
|
|
@ -42,7 +42,7 @@ For a frontend configured under the `available` key, it's enough to install it b
|
||||||
|
|
||||||
This will download the latest build for the pre-configured `ref` and install it. It can then be configured as the one of the served frontends in the config file (see `primary` or `admin`).
|
This will download the latest build for the pre-configured `ref` and install it. It can then be configured as the one of the served frontends in the config file (see `primary` or `admin`).
|
||||||
|
|
||||||
You can override any of the details. To install a Pleroma-FE build from a different URL, you could do this:
|
You can override any of the details. To install an Akkoma-FE build from a different URL, you could do this:
|
||||||
|
|
||||||
=== "OTP"
|
=== "OTP"
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,6 @@ mix ecto.migrate
|
||||||
# Start akkoma (replace with your system service manager's equivalent if different)
|
# Start akkoma (replace with your system service manager's equivalent if different)
|
||||||
sudo systemctl start akkoma
|
sudo systemctl start akkoma
|
||||||
|
|
||||||
# Update Pleroma-FE frontend to latest stable. For other Frontends see Frontend Configuration doc for more information.
|
# Update Akkoma-FE frontend to latest stable. For other Frontends see Frontend Configuration doc for more information.
|
||||||
mix pleroma.frontend install pleroma-fe --ref stable
|
mix pleroma.frontend install pleroma-fe --ref stable
|
||||||
```
|
```
|
||||||
|
|
|
@ -2,6 +2,14 @@
|
||||||
Note: Additional clients may work, but these are known to work with Akkoma.
|
Note: Additional clients may work, but these are known to work with Akkoma.
|
||||||
Apps listed here might not support all of Akkoma's features.
|
Apps listed here might not support all of Akkoma's features.
|
||||||
|
|
||||||
|
## Multiplatform
|
||||||
|
### Kaiteki
|
||||||
|
- Homepage: <https://kaiteki.app/>
|
||||||
|
- Source Code: <https://github.com/Kaiteki-Fedi/Kaiteki>
|
||||||
|
- Contact: [@kaiteki@fedi.software](https://fedi.software/@Kaiteki)
|
||||||
|
- Platforms: Web, Windows, Linux, Android
|
||||||
|
- Features: MastoAPI, Supports multiple backends
|
||||||
|
|
||||||
## Desktop
|
## Desktop
|
||||||
### Whalebird
|
### Whalebird
|
||||||
- Homepage: <https://whalebird.social/>
|
- Homepage: <https://whalebird.social/>
|
||||||
|
|
|
@ -246,11 +246,11 @@ Notes:
|
||||||
|
|
||||||
### :frontend_configurations
|
### :frontend_configurations
|
||||||
|
|
||||||
This 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. You can find the documentation for `pleroma_fe` configuration into [Pleroma-FE configuration and customization for instance administrators](https://docs-fe.akkoma.dev/stable/CONFIGURATION/#options).
|
This 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. You can find the documentation for `pleroma_fe` configuration into [Akkoma-FE configuration and customization for instance administrators](https://docs-fe.akkoma.dev/stable/CONFIGURATION/#options).
|
||||||
|
|
||||||
Frontends can access these settings at `/api/v1/pleroma/frontend_configurations`
|
Frontends can access these settings at `/api/v1/pleroma/frontend_configurations`
|
||||||
|
|
||||||
To add your own configuration for Pleroma-FE, use it like this:
|
To add your own configuration for Akkoma-FE, use it like this:
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
config :pleroma, :frontend_configurations,
|
config :pleroma, :frontend_configurations,
|
||||||
|
@ -615,6 +615,12 @@ This filter only strips the GPS and location metadata with Exiftool leaving colo
|
||||||
|
|
||||||
No specific configuration.
|
No specific configuration.
|
||||||
|
|
||||||
|
#### Pleroma.Upload.Filter.OnlyMedia
|
||||||
|
|
||||||
|
This filter rejects uploads that are not identified with Content-Type matching audio/\*, image/\*, or video/\*
|
||||||
|
|
||||||
|
No specific configuration.
|
||||||
|
|
||||||
#### Pleroma.Upload.Filter.Mogrify
|
#### Pleroma.Upload.Filter.Mogrify
|
||||||
|
|
||||||
* `args`: List of actions for the `mogrify` command like `"strip"` or `["strip", "auto-orient", {"implode", "1"}]`.
|
* `args`: List of actions for the `mogrify` command like `"strip"` or `["strip", "auto-orient", {"implode", "1"}]`.
|
||||||
|
|
|
@ -6,7 +6,7 @@ To add a custom theme to your instance, you'll first need to get a custom theme,
|
||||||
|
|
||||||
### Create your own theme
|
### Create your own theme
|
||||||
|
|
||||||
* You can create your own theme using the Pleroma FE by going to settings (gear on the top right) and choose the Theme tab. Here you have the options to create a personal theme.
|
* You can create your own theme using the Akkoma FE by going to settings (gear on the top right) and choose the Theme tab. Here you have the options to create a personal theme.
|
||||||
* To download your theme, you can do Save preset
|
* To download your theme, you can do Save preset
|
||||||
* If you want to upload a theme to customise it further, you can upload it using Load preset
|
* If you want to upload a theme to customise it further, you can upload it using Load preset
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ Home, public, hashtag & list timelines accept these parameters:
|
||||||
## Statuses
|
## Statuses
|
||||||
|
|
||||||
- `visibility`: has additional possible values `list` and `local` (for local-only statuses)
|
- `visibility`: has additional possible values `list` and `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:
|
Has these additional fields under the `pleroma` object:
|
||||||
|
|
||||||
|
@ -36,7 +37,9 @@ Has these additional fields under the `pleroma` object:
|
||||||
- `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being its mimetype. Currently, the only alternate representation supported is `text/plain`
|
- `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being its mimetype. Currently, the only alternate representation supported is `text/plain`
|
||||||
- `expires_at`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire
|
- `expires_at`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire
|
||||||
- `thread_muted`: true if the thread the post belongs to is muted
|
- `thread_muted`: true if the thread the post belongs to is muted
|
||||||
- `emoji_reactions`: A list with emoji / reaction maps. The format is `{name: "☕", count: 1, me: true}`. Contains no information about the reacting users, for that use the `/statuses/:id/reactions` endpoint.
|
- `emoji_reactions`: A list with emoji / reaction maps. The format is `{name: "☕", count: 2, me: true, account_ids: ["UserID1", "UserID2"]}`.
|
||||||
|
The `account_ids` property was added in Akkoma 3.2.0.
|
||||||
|
Further info about all reacting users at once, can be found using the `/statuses/:id/reactions` endpoint.
|
||||||
- `parent_visible`: If the parent of this post is visible to the user or not.
|
- `parent_visible`: If the parent of this post is visible to the user or not.
|
||||||
- `pinned_at`: a datetime (iso8601) when status was pinned, `null` otherwise.
|
- `pinned_at`: a datetime (iso8601) when status was pinned, `null` otherwise.
|
||||||
|
|
||||||
|
@ -214,6 +217,11 @@ Returns: array of Status.
|
||||||
|
|
||||||
The maximum number of statuses is limited to 100 per request.
|
The maximum number of statuses is limited to 100 per request.
|
||||||
|
|
||||||
|
## PUT `/api/v1/statuses/:id/emoji_reactions/:emoji`
|
||||||
|
|
||||||
|
This endpoint is an extension of the Fedibird Mastodon fork.
|
||||||
|
It behaves identical to PUT `/api/v1/pleroma/statuses/:id/reactions/:emoji`.
|
||||||
|
|
||||||
## PATCH `/api/v1/accounts/update_credentials`
|
## PATCH `/api/v1/accounts/update_credentials`
|
||||||
|
|
||||||
Additional parameters can be added to the JSON body/Form data:
|
Additional parameters can be added to the JSON body/Form data:
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
# Introduction to Akkoma
|
# Introduction to Akkoma
|
||||||
## What is Akkoma?
|
## What is Akkoma?
|
||||||
Akkoma is a federated social networking platform, compatible with Mastodon and other ActivityPub implementations. It is free software licensed under the AGPLv3.
|
Akkoma is a federated social networking platform, compatible with Mastodon and other ActivityPub implementations. It is free software licensed under the AGPLv3.
|
||||||
It actually consists of two components: a backend, named simply Akkoma, and a user-facing frontend, named Pleroma-FE. It also includes the Mastodon frontend, if that's your thing.
|
It actually consists of two components: a backend, named simply Akkoma, and a user-facing frontend, named Akkoma-FE. It also includes the Mastodon frontend, if that's your thing.
|
||||||
It's part of what we call the fediverse, a federated network of instances which speak common protocols and can communicate with each other.
|
It's part of what we call the fediverse, a federated network of instances which speak common protocols and can communicate with each other.
|
||||||
One account on an instance is enough to talk to the entire fediverse!
|
One account on an instance is enough to talk to the entire fediverse!
|
||||||
|
|
||||||
|
@ -31,11 +31,11 @@ Installation instructions can be found in the installation section of these docs
|
||||||
## I got an account, now what?
|
## I got an account, now what?
|
||||||
Great! Now you can explore the fediverse! Open the login page for your Akkoma instance (e.g. <https://otp.akkoma.dev>) and login with your username and password. (If you don't have an account yet, click on Register)
|
Great! Now you can explore the fediverse! Open the login page for your Akkoma instance (e.g. <https://otp.akkoma.dev>) and login with your username and password. (If you don't have an account yet, click on Register)
|
||||||
|
|
||||||
### Pleroma-FE
|
### Akkoma-FE
|
||||||
The default front-end used by Akkoma is Pleroma-FE. You can find more information on what it is and how to use it in the [Introduction to Pleroma-FE](https://docs-fe.akkoma.dev/stable/).
|
The default front-end used by Akkoma is Akkoma-FE. You can find more information on what it is and how to use it in the [Introduction to Akkoma-FE](https://docs-fe.akkoma.dev/stable/).
|
||||||
|
|
||||||
### Mastodon interface
|
### Mastodon interface
|
||||||
If the Pleroma-FE interface isn't your thing, or you're just trying something new but you want to keep using the familiar Mastodon interface, we got that too!
|
If the Akkoma-FE interface isn't your thing, or you're just trying something new but you want to keep using the familiar Mastodon interface, we got that too!
|
||||||
Just add a "/web" after your instance url (e.g. <https://otp.akkoma.dev/web>) and you'll end on the Mastodon web interface, but with a Akkoma backend! MAGIC!
|
Just add a "/web" after your instance url (e.g. <https://otp.akkoma.dev/web>) and you'll end on the Mastodon web interface, but with a Akkoma backend! MAGIC!
|
||||||
The Mastodon interface is from the Glitch-soc fork. For more information on the Mastodon interface you can check the [Mastodon](https://docs.joinmastodon.org/) and [Glitch-soc](https://glitch-soc.github.io/docs/) documentation.
|
The Mastodon interface is from the Glitch-soc fork. For more information on the Mastodon interface you can check the [Mastodon](https://docs.joinmastodon.org/) and [Glitch-soc](https://glitch-soc.github.io/docs/) documentation.
|
||||||
|
|
||||||
|
|
|
@ -23,23 +23,7 @@ sudo apt full-upgrade
|
||||||
sudo apt install git build-essential postgresql postgresql-contrib cmake libmagic-dev
|
sudo apt install git build-essential postgresql postgresql-contrib cmake libmagic-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
### Install Elixir and Erlang
|
### Create the akkoma user
|
||||||
|
|
||||||
* Install Elixir and Erlang (you might need to use backports or [asdf](https://github.com/asdf-vm/asdf) on old systems):
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install elixir erlang-dev erlang-nox
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Optional packages: [`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md)
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo apt install imagemagick ffmpeg libimage-exiftool-perl
|
|
||||||
```
|
|
||||||
|
|
||||||
### Install AkkomaBE
|
|
||||||
|
|
||||||
* Add a new system user for the Akkoma service:
|
* Add a new system user for the Akkoma service:
|
||||||
|
|
||||||
|
@ -49,7 +33,67 @@ sudo useradd -r -s /bin/false -m -d /var/lib/akkoma -U akkoma
|
||||||
|
|
||||||
**Note**: To execute a single command as the Akkoma system user, use `sudo -Hu akkoma command`. You can also switch to a shell by using `sudo -Hu akkoma $SHELL`. If you don’t have and want `sudo` on your system, you can use `su` as root user (UID 0) for a single command by using `su -l akkoma -s $SHELL -c 'command'` and `su -l akkoma -s $SHELL` for starting a shell.
|
**Note**: To execute a single command as the Akkoma system user, use `sudo -Hu akkoma command`. You can also switch to a shell by using `sudo -Hu akkoma $SHELL`. If you don’t have and want `sudo` on your system, you can use `su` as root user (UID 0) for a single command by using `su -l akkoma -s $SHELL -c 'command'` and `su -l akkoma -s $SHELL` for starting a shell.
|
||||||
|
|
||||||
* Git clone the AkkomaBE repository from stable-branch and make the Akkoma user the owner of the directory:
|
### Install Elixir and Erlang
|
||||||
|
|
||||||
|
If your distribution packages a recent enough version of Elixir, you can install it directly from the distro repositories and skip to the next section of the guide:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo apt install elixir erlang-dev erlang-nox
|
||||||
|
```
|
||||||
|
|
||||||
|
Otherwise use [asdf](https://github.com/asdf-vm/asdf) to install the latest versions of Elixir and Erlang.
|
||||||
|
|
||||||
|
First, install some dependencies needed to build Elixir and Erlang:
|
||||||
|
```shell
|
||||||
|
sudo apt install curl unzip build-essential autoconf m4 libncurses5-dev libssh-dev unixodbc-dev xsltproc libxml2-utils libncurses-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Then login to the `akkoma` user and install asdf:
|
||||||
|
```shell
|
||||||
|
git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.11.3
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the following lines to `~/.bashrc`:
|
||||||
|
```shell
|
||||||
|
. "$HOME/.asdf/asdf.sh"
|
||||||
|
# asdf completions
|
||||||
|
. "$HOME/.asdf/completions/asdf.bash"
|
||||||
|
```
|
||||||
|
|
||||||
|
Restart the shell:
|
||||||
|
```shell
|
||||||
|
exec $SHELL
|
||||||
|
```
|
||||||
|
|
||||||
|
Next install Erlang:
|
||||||
|
```shell
|
||||||
|
asdf plugin add erlang https://github.com/asdf-vm/asdf-erlang.git
|
||||||
|
export KERL_CONFIGURE_OPTIONS="--disable-debug --without-javac"
|
||||||
|
asdf install erlang 25.3.2.1
|
||||||
|
asdf global erlang 25.3.2.1
|
||||||
|
```
|
||||||
|
|
||||||
|
Now install Elixir:
|
||||||
|
```shell
|
||||||
|
asdf plugin-add elixir https://github.com/asdf-vm/asdf-elixir.git
|
||||||
|
asdf install elixir 1.14.5-otp-25
|
||||||
|
asdf global elixir 1.14.5-otp-25
|
||||||
|
```
|
||||||
|
|
||||||
|
Confirm that Elixir is installed correctly by checking the version:
|
||||||
|
```shell
|
||||||
|
elixir --version
|
||||||
|
```
|
||||||
|
|
||||||
|
### Optional packages: [`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md)
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo apt install imagemagick ffmpeg libimage-exiftool-perl
|
||||||
|
```
|
||||||
|
|
||||||
|
### Install AkkomaBE
|
||||||
|
|
||||||
|
* Log into the `akkoma` user and clone the AkkomaBE repository from the stable branch and make the Akkoma user the owner of the directory:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo mkdir -p /opt/akkoma
|
sudo mkdir -p /opt/akkoma
|
||||||
|
|
|
@ -10,7 +10,7 @@ If you want to migrate from or OTP to docker, check out [the migration guide](./
|
||||||
|
|
||||||
### Prepare the system
|
### Prepare the system
|
||||||
|
|
||||||
* Install docker and docker-compose
|
* Install docker and docker compose
|
||||||
* [Docker](https://docs.docker.com/engine/install/)
|
* [Docker](https://docs.docker.com/engine/install/)
|
||||||
* [Docker-compose](https://docs.docker.com/compose/install/)
|
* [Docker-compose](https://docs.docker.com/compose/install/)
|
||||||
* This will usually just be a repository installation and a package manager invocation.
|
* This will usually just be a repository installation and a package manager invocation.
|
||||||
|
@ -26,7 +26,7 @@ echo "DOCKER_USER=$(id -u):$(id -g)" >> .env
|
||||||
```
|
```
|
||||||
|
|
||||||
This probably won't need to be changed, it's only there to set basic environment
|
This probably won't need to be changed, it's only there to set basic environment
|
||||||
variables for the docker-compose file.
|
variables for the docker compose file.
|
||||||
|
|
||||||
### Building the container
|
### Building the container
|
||||||
|
|
||||||
|
@ -65,9 +65,9 @@ cp config/generated_config.exs config/prod.secret.exs
|
||||||
We need to run a few commands on the database container, this isn't too bad
|
We need to run a few commands on the database container, this isn't too bad
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker-compose run --rm --user akkoma -d db
|
docker compose run --rm --user akkoma -d db
|
||||||
# Note down the name it gives here, it will be something like akkoma_db_run
|
# Note down the name it gives here, it will be something like akkoma_db_run
|
||||||
docker-compose run --rm akkoma psql -h db -U akkoma -f config/setup_db.psql
|
docker compose run --rm akkoma psql -h db -U akkoma -f config/setup_db.psql
|
||||||
docker stop akkoma_db_run # Replace with the name you noted down
|
docker stop akkoma_db_run # Replace with the name you noted down
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -84,17 +84,17 @@ We're going to run it in the foreground on the first run, just to make sure
|
||||||
everything start up.
|
everything start up.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker-compose up
|
docker compose up
|
||||||
```
|
```
|
||||||
|
|
||||||
If everything went well, you should be able to access your instance at http://localhost:4000
|
If everything went well, you should be able to access your instance at http://localhost:4000
|
||||||
|
|
||||||
You can `ctrl-c` out of the docker-compose now to shutdown the server.
|
You can `ctrl-c` out of the docker compose now to shutdown the server.
|
||||||
|
|
||||||
### Running in the background
|
### Running in the background
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker-compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
### Create your first user
|
### Create your first user
|
||||||
|
@ -125,8 +125,8 @@ cp docker-resources/Caddyfile.example docker-resources/Caddyfile
|
||||||
|
|
||||||
Then edit the TLD in your caddyfile to the domain you're serving on.
|
Then edit the TLD in your caddyfile to the domain you're serving on.
|
||||||
|
|
||||||
Uncomment the `caddy` section in the docker-compose file,
|
Uncomment the `caddy` section in the docker compose file,
|
||||||
then run `docker-compose up -d` again.
|
then run `docker compose up -d` again.
|
||||||
|
|
||||||
#### Running a reverse proxy on the host
|
#### Running a reverse proxy on the host
|
||||||
|
|
||||||
|
@ -152,7 +152,7 @@ git pull
|
||||||
./docker-resources/manage.sh mix deps.get
|
./docker-resources/manage.sh mix deps.get
|
||||||
./docker-resources/manage.sh mix compile
|
./docker-resources/manage.sh mix compile
|
||||||
./docker-resources/manage.sh mix ecto.migrate
|
./docker-resources/manage.sh mix ecto.migrate
|
||||||
docker-compose restart akkoma db
|
docker compose restart akkoma db
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Further reading
|
#### Further reading
|
||||||
|
|
|
@ -118,3 +118,15 @@ To fix this, run:
|
||||||
```
|
```
|
||||||
|
|
||||||
which will remove the config from the database. Things should work now.
|
which will remove the config from the database. Things should work now.
|
||||||
|
|
||||||
|
## Migrating back to Pleroma
|
||||||
|
|
||||||
|
Akkoma is a hard fork of Pleroma. As such, migrating back is not guaranteed to always work. But if you want to migrate back to Pleroma, you can always try. Just note that you may run into unexpected issues and you're basically on your own. The following are some tips that may help, but note that these are barely tested, so proceed at your own risk.
|
||||||
|
|
||||||
|
First you will need to roll back the database migrations. The latest migration both Akkoma and Pleroma still have in common should be 20210416051708, so roll back to that. If you run from source, that should be
|
||||||
|
|
||||||
|
```sh
|
||||||
|
MIX_ENV=prod mix ecto.rollback --to 20210416051708
|
||||||
|
```
|
||||||
|
|
||||||
|
Then switch back to Pleroma for updates (similar to how was done to migrate to Akkoma), and remove the front-ends. The front-ends are installed in the `frontends` folder in the [static directory](../configuration/static_dir.md). Once you are back to Pleroma, you will need to run the database migrations again. See the Pleroma documentation for this.
|
||||||
|
|
|
@ -10,7 +10,7 @@ You probably should, in the first instance.
|
||||||
|
|
||||||
### Prepare the system
|
### Prepare the system
|
||||||
|
|
||||||
* Install docker and docker-compose
|
* Install docker and docker compose
|
||||||
* [Docker](https://docs.docker.com/engine/install/)
|
* [Docker](https://docs.docker.com/engine/install/)
|
||||||
* [Docker-compose](https://docs.docker.com/compose/install/)
|
* [Docker-compose](https://docs.docker.com/compose/install/)
|
||||||
* This will usually just be a repository installation and a package manager invocation.
|
* This will usually just be a repository installation and a package manager invocation.
|
||||||
|
@ -46,7 +46,7 @@ For *most* from-source installs it'll already be there.
|
||||||
And the same with `uploads`, make sure your uploads (if you have them on disk) are
|
And the same with `uploads`, make sure your uploads (if you have them on disk) are
|
||||||
located at `uploads/` in the akkoma source directory.
|
located at `uploads/` in the akkoma source directory.
|
||||||
|
|
||||||
If you have them on a different disk, you will need to mount that disk into the docker-compose file,
|
If you have them on a different disk, you will need to mount that disk into the docker compose file,
|
||||||
with an entry that looks like this:
|
with an entry that looks like this:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
@ -66,7 +66,7 @@ echo "DOCKER_USER=$(id -u):$(id -g)" >> .env
|
||||||
```
|
```
|
||||||
|
|
||||||
This probably won't need to be changed, it's only there to set basic environment
|
This probably won't need to be changed, it's only there to set basic environment
|
||||||
variables for the docker-compose file.
|
variables for the docker compose file.
|
||||||
|
|
||||||
=== "From source"
|
=== "From source"
|
||||||
|
|
||||||
|
@ -126,21 +126,21 @@ mkdir pgdata
|
||||||
Now we can import our database to the container.
|
Now we can import our database to the container.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker-compose run --rm --user akkoma -d db
|
docker compose run --rm --user akkoma -d db
|
||||||
docker-compose run --rm akkoma pg_restore -v -U akkoma -j $(grep -c ^processor /proc/cpuinfo) -d akkoma -h db akkoma_backup.sql
|
docker compose run --rm akkoma pg_restore -v -U akkoma -j $(grep -c ^processor /proc/cpuinfo) -d akkoma -h db akkoma_backup.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
### Reverse proxies
|
### Reverse proxies
|
||||||
|
|
||||||
If you're just reusing your old proxy, you may have to uncomment the line in
|
If you're just reusing your old proxy, you may have to uncomment the line in
|
||||||
the docker-compose file under `ports`. You'll find it.
|
the docker compose file under `ports`. You'll find it.
|
||||||
|
|
||||||
Otherwise, you can use the same setup as the [docker installation guide](./docker_en.md#reverse-proxies).
|
Otherwise, you can use the same setup as the [docker installation guide](./docker_en.md#reverse-proxies).
|
||||||
|
|
||||||
### Let's go
|
### Let's go
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker-compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
You should now be at the same point as you were before, but with a docker install.
|
You should now be at the same point as you were before, but with a docker install.
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
# Optional software packages needed for specific functionality
|
# Optional software packages needed for specific functionality
|
||||||
|
|
||||||
For specific Pleroma functionality (which is disabled by default) some or all of the below packages are required:
|
For specific Akkoma functionality (which is disabled by default) some or all of the below packages are required:
|
||||||
* `ImageMagic`
|
* `ImageMagick`
|
||||||
* `ffmpeg`
|
* `ffmpeg`
|
||||||
* `exiftool`
|
* `exiftool`
|
||||||
|
|
||||||
Please refer to documentation in `docs/installation` on how to install them on specific OS.
|
Please refer to documentation in `docs/installation` on how to install them on specific OS.
|
||||||
|
|
||||||
Note: the packages are not required with the current default settings of Pleroma.
|
Note: the packages are not required with the current default settings of Akkoma.
|
||||||
|
|
||||||
## `ImageMagick`
|
## `ImageMagick`
|
||||||
|
|
||||||
`ImageMagick` is a set of tools to create, edit, compose, or convert bitmap images.
|
`ImageMagick` is a set of tools to create, edit, compose, or convert bitmap images.
|
||||||
|
|
||||||
It is required for the following Pleroma features:
|
It is required for the following Akkoma features:
|
||||||
* `Pleroma.Upload.Filters.Mogrify`, `Pleroma.Upload.Filters.Mogrifun` upload filters (related config: `Plaroma.Upload/filters` in `config/config.exs`)
|
* `Pleroma.Upload.Filters.Mogrify`, `Pleroma.Upload.Filters.Mogrifun` upload filters (related config: `Plaroma.Upload/filters` in `config/config.exs`)
|
||||||
* Media preview proxy for still images (related config: `media_preview_proxy/enabled` in `config/config.exs`)
|
* Media preview proxy for still images (related config: `media_preview_proxy/enabled` in `config/config.exs`)
|
||||||
|
|
||||||
|
@ -21,12 +21,12 @@ It is required for the following Pleroma features:
|
||||||
|
|
||||||
`ffmpeg` is software to record, convert and stream audio and video.
|
`ffmpeg` is software to record, convert and stream audio and video.
|
||||||
|
|
||||||
It is required for the following Pleroma features:
|
It is required for the following Akkoma features:
|
||||||
* Media preview proxy for videos (related config: `media_preview_proxy/enabled` in `config/config.exs`)
|
* Media preview proxy for videos (related config: `media_preview_proxy/enabled` in `config/config.exs`)
|
||||||
|
|
||||||
## `exiftool`
|
## `exiftool`
|
||||||
|
|
||||||
`exiftool` is media files metadata reader/writer.
|
`exiftool` is media files metadata reader/writer.
|
||||||
|
|
||||||
It is required for the following Pleroma features:
|
It is required for the following Akkoma features:
|
||||||
* `Pleroma.Upload.Filters.Exiftool` upload filter (related config: `Plaroma.Upload/filters` in `config/config.exs`)
|
* `Pleroma.Upload.Filters.Exiftool` upload filter (related config: `Plaroma.Upload/filters` in `config/config.exs`)
|
||||||
|
|
|
@ -19,12 +19,16 @@ This is a little more complex than it used to be (thanks ubuntu)
|
||||||
|
|
||||||
Use the following mapping to figure out your flavour:
|
Use the following mapping to figure out your flavour:
|
||||||
|
|
||||||
| distribution | flavour | available branches |
|
| distribution | architecture | flavour | available branches |
|
||||||
| ------------- | ------------------ | ------------------- |
|
| --------------- | ------------------ | ------------------- | ------------------- |
|
||||||
| debian stable | amd64 | develop, stable |
|
| debian bullseye | amd64 | amd64 | develop, stable |
|
||||||
| ubuntu focal | amd64 | develop, stable |
|
| debian bullseye | arm64 | arm64 | stable |
|
||||||
| ubuntu jammy | amd64-ubuntu-jammy | develop, stable |
|
| ubuntu focal | amd64 | amd64 | develop, stable |
|
||||||
| alpine | amd64-musl | stable |
|
| ubuntu focal | arm64 | arm64 | stable |
|
||||||
|
| ubuntu jammy | amd64 | amd64-ubuntu-jammy | develop, stable |
|
||||||
|
| ubuntu jammy | arm64 | arm64-ubuntu-jammy | develop, stable |
|
||||||
|
| alpine | amd64 | amd64-musl | stable |
|
||||||
|
| alpine | arm64 | arm64-musl | stable |
|
||||||
|
|
||||||
Other similar distributions will _probably_ work, but if it is not listed above, there is no official
|
Other similar distributions will _probably_ work, but if it is not listed above, there is no official
|
||||||
support.
|
support.
|
||||||
|
@ -118,8 +122,12 @@ Restart PostgreSQL to apply configuration changes:
|
||||||
adduser --system --shell /bin/false --home /opt/akkoma akkoma
|
adduser --system --shell /bin/false --home /opt/akkoma akkoma
|
||||||
|
|
||||||
# Set the flavour environment variable to the string you got in Detecting flavour section.
|
# Set the flavour environment variable to the string you got in Detecting flavour section.
|
||||||
# For example if the flavour is `amd64` the command will be
|
# For example if the flavour is `amd64-musl` the command will be
|
||||||
export FLAVOUR="amd64"
|
# export FLAVOUR="amd64-musl"
|
||||||
|
export FLAVOUR="<replace-this-with-the-correct-flavour-string>"
|
||||||
|
|
||||||
|
# Make sure the SHELL variable is set
|
||||||
|
export SHELL="${SHELL:-/bin/sh}"
|
||||||
|
|
||||||
# Clone the release build into a temporary directory and unpack it
|
# Clone the release build into a temporary directory and unpack it
|
||||||
su akkoma -s $SHELL -lc "
|
su akkoma -s $SHELL -lc "
|
||||||
|
|
8
docs/theme/partials/source.html
vendored
8
docs/theme/partials/source.html
vendored
|
@ -38,11 +38,11 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if page and page.url.startswith('backend') %}
|
{% if page and page.url.startswith('backend') %}
|
||||||
{% set repo_url = "https://git.pleroma.social/pleroma/pleroma" %}
|
{% set repo_url = "https://akkoma.dev/AkkomaGang/akkoma" %}
|
||||||
{% set repo_name = "pleroma/pleroma" %}
|
{% set repo_name = "AkkomaGang/akkoma" %}
|
||||||
{% elif page and page.url.startswith('frontend') %}
|
{% elif page and page.url.startswith('frontend') %}
|
||||||
{% set repo_url = "https://git.pleroma.social/pleroma/pleroma-fe" %}
|
{% set repo_url = "https://akkoma.dev/AkkomaGang/akkoma-fe" %}
|
||||||
{% set repo_name = "pleroma/pleroma-fe" %}
|
{% set repo_name = "AkkomaGang/akkoma-fe" %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% set repo_url = config.repo_url %}
|
{% set repo_url = config.repo_url %}
|
||||||
{% set repo_name = config.repo_name %}
|
{% set repo_name = config.repo_name %}
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
# 1. Replace 'example.tld' with your instance's domain wherever it appears.
|
# 1. Replace 'example.tld' with your instance's domain wherever it appears.
|
||||||
# 2. Copy this section into your Caddyfile and restart Caddy.
|
# 2. Copy this section into your Caddyfile and restart Caddy.
|
||||||
|
|
||||||
|
# If you are able to, it's highly recommended to have your media served via a separate subdomain for improved security.
|
||||||
|
# Uncomment the relevant sectons here and modify the base_url setting for Pleroma.Upload and :media_proxy accordingly.
|
||||||
|
|
||||||
example.tld {
|
example.tld {
|
||||||
log {
|
log {
|
||||||
output file /var/log/caddy/akkoma.log
|
output file /var/log/caddy/akkoma.log
|
||||||
|
@ -14,4 +17,21 @@ example.tld {
|
||||||
# this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only
|
# this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only
|
||||||
# and `localhost.` resolves to [::0] on some systems: see issue #930
|
# and `localhost.` resolves to [::0] on some systems: see issue #930
|
||||||
reverse_proxy 127.0.0.1:4000
|
reverse_proxy 127.0.0.1:4000
|
||||||
|
|
||||||
|
# Uncomment if using a separate media subdomain
|
||||||
|
#@mediaproxy path /media/* /proxy/*
|
||||||
|
#handle @mediaproxy {
|
||||||
|
# redir https://media.example.tld{uri} permanent
|
||||||
|
#}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Uncomment if using a separate media subdomain
|
||||||
|
#media.example.tld {
|
||||||
|
# @mediaproxy path /media/* /proxy/*
|
||||||
|
# reverse_proxy @mediaproxy 127.0.0.1:4000 {
|
||||||
|
# transport http {
|
||||||
|
# response_header_timeout 10s
|
||||||
|
# read_timeout 15s
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
#}
|
||||||
|
|
|
@ -8,6 +8,7 @@ pidfile="/var/run/akkoma.pid"
|
||||||
directory=/opt/akkoma
|
directory=/opt/akkoma
|
||||||
healthcheck_delay=60
|
healthcheck_delay=60
|
||||||
healthcheck_timer=30
|
healthcheck_timer=30
|
||||||
|
no_new_privs="yes"
|
||||||
|
|
||||||
: ${akkoma_port:-4000}
|
: ${akkoma_port:-4000}
|
||||||
|
|
||||||
|
|
|
@ -171,6 +171,21 @@ def run(["prune_objects" | args]) do
|
||||||
end
|
end
|
||||||
|> Repo.delete_all(timeout: :infinity)
|
|> Repo.delete_all(timeout: :infinity)
|
||||||
|
|
||||||
|
if !Keyword.get(options, :keep_threads) do
|
||||||
|
# Without the --keep-threads option, it's possible that bookmarked
|
||||||
|
# objects have been deleted. We remove the corresponding bookmarks.
|
||||||
|
"""
|
||||||
|
delete from public.bookmarks
|
||||||
|
where id in (
|
||||||
|
select b.id from public.bookmarks b
|
||||||
|
left join public.activities a on b.activity_id = a.id
|
||||||
|
left join public.objects o on a."data" ->> 'object' = o.data ->> 'id'
|
||||||
|
where o.id is null
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|> Repo.query([], timeout: :infinity)
|
||||||
|
end
|
||||||
|
|
||||||
if Keyword.get(options, :prune_orphaned_activities) do
|
if Keyword.get(options, :prune_orphaned_activities) do
|
||||||
# Prune activities who link to a single object
|
# Prune activities who link to a single object
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -21,7 +21,7 @@ defp generate_topics(object, activity) do
|
||||||
["user", "list"] ++ visibility_tags(object, activity)
|
["user", "list"] ++ visibility_tags(object, activity)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp visibility_tags(object, activity) do
|
defp visibility_tags(object, %{data: %{"type" => type}} = activity) when type != "Announce" do
|
||||||
case Visibility.get_visibility(activity) do
|
case Visibility.get_visibility(activity) do
|
||||||
"public" ->
|
"public" ->
|
||||||
if activity.local do
|
if activity.local do
|
||||||
|
@ -31,6 +31,10 @@ defp visibility_tags(object, activity) do
|
||||||
end
|
end
|
||||||
|> item_creation_tags(object, activity)
|
|> item_creation_tags(object, activity)
|
||||||
|
|
||||||
|
"local" ->
|
||||||
|
["public:local"]
|
||||||
|
|> item_creation_tags(object, activity)
|
||||||
|
|
||||||
"direct" ->
|
"direct" ->
|
||||||
["direct"]
|
["direct"]
|
||||||
|
|
||||||
|
@ -39,6 +43,10 @@ defp visibility_tags(object, activity) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp visibility_tags(_object, _activity) do
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
defp item_creation_tags(tags, object, %{data: %{"type" => "Create"}} = activity) do
|
defp item_creation_tags(tags, object, %{data: %{"type" => "Create"}} = activity) do
|
||||||
tags ++
|
tags ++
|
||||||
remote_topics(activity) ++ hashtags_to_topics(object) ++ attachment_topics(object, activity)
|
remote_topics(activity) ++ hashtags_to_topics(object) ++ attachment_topics(object, activity)
|
||||||
|
@ -63,7 +71,18 @@ defp remote_topics(_), do: []
|
||||||
|
|
||||||
defp attachment_topics(%{data: %{"attachment" => []}}, _act), do: []
|
defp attachment_topics(%{data: %{"attachment" => []}}, _act), do: []
|
||||||
|
|
||||||
defp attachment_topics(_object, %{local: true}), do: ["public:media", "public:local:media"]
|
defp attachment_topics(_object, %{local: true} = activity) do
|
||||||
|
case Visibility.get_visibility(activity) do
|
||||||
|
"public" ->
|
||||||
|
["public:media", "public:local:media"]
|
||||||
|
|
||||||
|
"local" ->
|
||||||
|
["public:local:media"]
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp attachment_topics(_object, %{actor: actor}) when is_binary(actor),
|
defp attachment_topics(_object, %{actor: actor}) when is_binary(actor),
|
||||||
do: ["public:media", "public:remote:media:" <> URI.parse(actor).host]
|
do: ["public:media", "public:remote:media:" <> URI.parse(actor).host]
|
||||||
|
|
|
@ -40,7 +40,7 @@ def translate(string, from_language, to_language) do
|
||||||
if Map.has_key?(body, "detectedLanguage") do
|
if Map.has_key?(body, "detectedLanguage") do
|
||||||
get_in(body, ["detectedLanguage", "language"])
|
get_in(body, ["detectedLanguage", "language"])
|
||||||
else
|
else
|
||||||
from_language
|
from_language || ""
|
||||||
end
|
end
|
||||||
|
|
||||||
{:ok, detected, translated}
|
{:ok, detected, translated}
|
||||||
|
|
|
@ -262,11 +262,14 @@ defp http_children do
|
||||||
proxy = Pleroma.HTTP.AdapterHelper.format_proxy(proxy_url)
|
proxy = Pleroma.HTTP.AdapterHelper.format_proxy(proxy_url)
|
||||||
pool_size = Config.get([:http, :pool_size])
|
pool_size = Config.get([:http, :pool_size])
|
||||||
|
|
||||||
|
:public_key.cacerts_load()
|
||||||
|
|
||||||
config =
|
config =
|
||||||
[:http, :adapter]
|
[:http, :adapter]
|
||||||
|> Config.get([])
|
|> Config.get([])
|
||||||
|> Pleroma.HTTP.AdapterHelper.add_pool_size(pool_size)
|
|> Pleroma.HTTP.AdapterHelper.add_pool_size(pool_size)
|
||||||
|> Pleroma.HTTP.AdapterHelper.maybe_add_proxy_pool(proxy)
|
|> Pleroma.HTTP.AdapterHelper.maybe_add_proxy_pool(proxy)
|
||||||
|
|> Pleroma.HTTP.AdapterHelper.maybe_add_cacerts(:public_key.cacerts_get())
|
||||||
|> Keyword.put(:name, MyFinch)
|
|> Keyword.put(:name, MyFinch)
|
||||||
|
|
||||||
[{Finch, config}]
|
[{Finch, config}]
|
||||||
|
|
|
@ -25,7 +25,7 @@ defmodule Pleroma.Constants do
|
||||||
|
|
||||||
const(static_only_files,
|
const(static_only_files,
|
||||||
do:
|
do:
|
||||||
~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc embed.js embed.css)
|
~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance embed sw.js sw-pleroma.js favicon.png schemas doc)
|
||||||
)
|
)
|
||||||
|
|
||||||
const(status_updatable_fields,
|
const(status_updatable_fields,
|
||||||
|
|
|
@ -27,10 +27,10 @@ defmodule Pleroma.HTTP do
|
||||||
nil | {:ok, Env.t()} | {:error, any()}
|
nil | {:ok, Env.t()} | {:error, any()}
|
||||||
def get(url, headers \\ [], options \\ [])
|
def get(url, headers \\ [], options \\ [])
|
||||||
def get(nil, _, _), do: nil
|
def get(nil, _, _), do: nil
|
||||||
def get(url, headers, options), do: request(:get, url, "", headers, options)
|
def get(url, headers, options), do: request(:get, url, nil, headers, options)
|
||||||
|
|
||||||
@spec head(Request.url(), Request.headers(), keyword()) :: {:ok, Env.t()} | {:error, any()}
|
@spec head(Request.url(), Request.headers(), keyword()) :: {:ok, Env.t()} | {:error, any()}
|
||||||
def head(url, headers \\ [], options \\ []), do: request(:head, url, "", headers, options)
|
def head(url, headers \\ [], options \\ []), do: request(:head, url, nil, headers, options)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Performs POST request.
|
Performs POST request.
|
||||||
|
|
|
@ -47,6 +47,17 @@ def maybe_add_proxy_pool(opts, proxy) do
|
||||||
|> put_in([:pools, :default, :conn_opts, :proxy], proxy)
|
|> put_in([:pools, :default, :conn_opts, :proxy], proxy)
|
||||||
end
|
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
|
def add_pool_size(opts, pool_size) do
|
||||||
opts
|
opts
|
||||||
|> maybe_add_pools()
|
|> maybe_add_pools()
|
||||||
|
@ -82,6 +93,16 @@ defp maybe_add_conn_opts(opts) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp maybe_add_transport_opts(opts) do
|
||||||
|
transport_opts = get_in(opts, [:pools, :default, :conn_opts, :transport_opts])
|
||||||
|
|
||||||
|
unless is_nil(transport_opts) do
|
||||||
|
opts
|
||||||
|
else
|
||||||
|
put_in(opts, [:pools, :default, :conn_opts, :transport_opts], [])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Merge default connection & adapter options with received ones.
|
Merge default connection & adapter options with received ones.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -195,6 +195,7 @@ defp exclude_filtered(query, user) do
|
||||||
from([_n, a, o] in query,
|
from([_n, a, o] in query,
|
||||||
where:
|
where:
|
||||||
fragment("not(?->>'content' ~* ?)", o.data, ^regex) or
|
fragment("not(?->>'content' ~* ?)", o.data, ^regex) or
|
||||||
|
fragment("?->>'content' is null", o.data) or
|
||||||
fragment("?->>'actor' = ?", o.data, ^user.ap_id)
|
fragment("?->>'actor' = ?", o.data, ^user.ap_id)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
@ -695,7 +696,7 @@ def skip?(
|
||||||
cond do
|
cond do
|
||||||
opts[:type] == "poll" -> false
|
opts[:type] == "poll" -> false
|
||||||
user.ap_id == actor -> false
|
user.ap_id == actor -> false
|
||||||
!User.following?(follower, user) -> true
|
!User.following?(user, follower) -> true
|
||||||
true -> false
|
true -> false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -251,6 +251,7 @@ defp build_resp_headers(headers, opts) do
|
||||||
|> Enum.filter(fn {k, _} -> k in @keep_resp_headers end)
|
|> Enum.filter(fn {k, _} -> k in @keep_resp_headers end)
|
||||||
|> build_resp_cache_headers(opts)
|
|> build_resp_cache_headers(opts)
|
||||||
|> build_resp_content_disposition_header(opts)
|
|> build_resp_content_disposition_header(opts)
|
||||||
|
|> build_csp_headers()
|
||||||
|> Keyword.merge(Keyword.get(opts, :resp_headers, []))
|
|> Keyword.merge(Keyword.get(opts, :resp_headers, []))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -316,6 +317,10 @@ defp build_resp_content_disposition_header(headers, opts) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
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 header_length_constraint(headers, limit) when is_integer(limit) and limit > 0 do
|
||||||
with {_, size} <- List.keyfind(headers, "content-length", 0),
|
with {_, size} <- List.keyfind(headers, "content-length", 0),
|
||||||
{size, _} <- Integer.parse(size),
|
{size, _} <- Integer.parse(size),
|
||||||
|
|
|
@ -38,9 +38,9 @@ def filter([filter | rest], upload) do
|
||||||
{:ok, :noop} ->
|
{:ok, :noop} ->
|
||||||
filter(rest, upload)
|
filter(rest, upload)
|
||||||
|
|
||||||
error ->
|
{:error, e} ->
|
||||||
Logger.error("#{__MODULE__}: Filter #{filter} failed: #{inspect(error)}")
|
Logger.error("#{__MODULE__}: Filter #{filter} failed: #{inspect(e)}")
|
||||||
error
|
{:error, e}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
20
lib/pleroma/upload/filter/only_media.ex
Normal file
20
lib/pleroma/upload/filter/only_media.ex
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Upload.Filter.OnlyMedia do
|
||||||
|
@behaviour Pleroma.Upload.Filter
|
||||||
|
alias Pleroma.Upload
|
||||||
|
|
||||||
|
def filter(%Upload{content_type: content_type}) do
|
||||||
|
[type, _subtype] = String.split(content_type, "/")
|
||||||
|
|
||||||
|
if type in ["image", "video", "audio"] do
|
||||||
|
{:ok, :noop}
|
||||||
|
else
|
||||||
|
{:error, "Disallowed content-type: #{content_type}"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def filter(_), do: {:ok, :noop}
|
||||||
|
end
|
|
@ -159,6 +159,11 @@ defmodule Pleroma.User do
|
||||||
field(:language, :string)
|
field(:language, :string)
|
||||||
field(:status_ttl_days, :integer, default: nil)
|
field(:status_ttl_days, :integer, default: nil)
|
||||||
|
|
||||||
|
field(:accepts_direct_messages_from, Ecto.Enum,
|
||||||
|
values: [:everybody, :people_i_follow, :nobody],
|
||||||
|
default: :everybody
|
||||||
|
)
|
||||||
|
|
||||||
embeds_one(
|
embeds_one(
|
||||||
:notification_settings,
|
:notification_settings,
|
||||||
Pleroma.User.NotificationSetting,
|
Pleroma.User.NotificationSetting,
|
||||||
|
@ -536,7 +541,8 @@ def update_changeset(struct, params \\ %{}) do
|
||||||
:is_discoverable,
|
:is_discoverable,
|
||||||
:actor_type,
|
:actor_type,
|
||||||
:disclose_client,
|
:disclose_client,
|
||||||
:status_ttl_days
|
:status_ttl_days,
|
||||||
|
:accepts_direct_messages_from
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|> unique_constraint(:nickname)
|
|> unique_constraint(:nickname)
|
||||||
|
@ -870,7 +876,7 @@ def post_register_action(%User{is_approved: true, is_confirmed: true} = user) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp send_user_approval_email(user) do
|
defp send_user_approval_email(%User{email: email} = user) when is_binary(email) do
|
||||||
user
|
user
|
||||||
|> Pleroma.Emails.UserEmail.approval_pending_email()
|
|> Pleroma.Emails.UserEmail.approval_pending_email()
|
||||||
|> Pleroma.Emails.Mailer.deliver_async()
|
|> Pleroma.Emails.Mailer.deliver_async()
|
||||||
|
@ -878,6 +884,10 @@ defp send_user_approval_email(user) do
|
||||||
{:ok, :enqueued}
|
{:ok, :enqueued}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp send_user_approval_email(_user) do
|
||||||
|
{:ok, :skipped}
|
||||||
|
end
|
||||||
|
|
||||||
defp send_admin_approval_emails(user) do
|
defp send_admin_approval_emails(user) do
|
||||||
all_superusers()
|
all_superusers()
|
||||||
|> Enum.filter(fn user -> not is_nil(user.email) end)
|
|> Enum.filter(fn user -> not is_nil(user.email) end)
|
||||||
|
@ -2000,7 +2010,6 @@ defp create_service_actor(uri, nickname) do
|
||||||
%User{
|
%User{
|
||||||
invisible: true,
|
invisible: true,
|
||||||
local: true,
|
local: true,
|
||||||
actor_type: "Application",
|
|
||||||
ap_id: uri,
|
ap_id: uri,
|
||||||
nickname: nickname,
|
nickname: nickname,
|
||||||
follower_address: uri <> "/followers"
|
follower_address: uri <> "/followers"
|
||||||
|
@ -2722,4 +2731,16 @@ def unfollow_hashtag(%User{} = user, %Hashtag{} = hashtag) do
|
||||||
def following_hashtag?(%User{} = user, %Hashtag{} = hashtag) do
|
def following_hashtag?(%User{} = user, %Hashtag{} = hashtag) do
|
||||||
not is_nil(HashtagFollow.get(user, hashtag))
|
not is_nil(HashtagFollow.get(user, hashtag))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def accepts_direct_messages?(
|
||||||
|
%User{accepts_direct_messages_from: :people_i_follow} = receiver,
|
||||||
|
%User{} = sender
|
||||||
|
) do
|
||||||
|
User.following?(receiver, sender)
|
||||||
|
end
|
||||||
|
|
||||||
|
def accepts_direct_messages?(%User{accepts_direct_messages_from: :everybody}, _), do: true
|
||||||
|
|
||||||
|
def accepts_direct_messages?(%User{accepts_direct_messages_from: :nobody}, _),
|
||||||
|
do: false
|
||||||
end
|
end
|
||||||
|
|
|
@ -31,7 +31,7 @@ def show(%User{} = source, %User{} = target) do
|
||||||
UserNote
|
UserNote
|
||||||
|> where(source_id: ^source.id, target_id: ^target.id)
|
|> where(source_id: ^source.id, target_id: ^target.id)
|
||||||
|> Repo.one() do
|
|> Repo.one() do
|
||||||
note.comment
|
note.comment || ""
|
||||||
else
|
else
|
||||||
_ -> ""
|
_ -> ""
|
||||||
end
|
end
|
||||||
|
|
|
@ -147,7 +147,8 @@ def get_policies do
|
||||||
|> Enum.concat([
|
|> Enum.concat([
|
||||||
Pleroma.Web.ActivityPub.MRF.HashtagPolicy,
|
Pleroma.Web.ActivityPub.MRF.HashtagPolicy,
|
||||||
Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy,
|
Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy,
|
||||||
Pleroma.Web.ActivityPub.MRF.NormalizeMarkup
|
Pleroma.Web.ActivityPub.MRF.NormalizeMarkup,
|
||||||
|
Pleroma.Web.ActivityPub.MRF.DirectMessageDisabledPolicy
|
||||||
])
|
])
|
||||||
|> Enum.uniq()
|
|> Enum.uniq()
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.DirectMessageDisabledPolicy do
|
||||||
|
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
|
||||||
|
|
||||||
|
alias Pleroma.User
|
||||||
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
@moduledoc """
|
||||||
|
Removes entries from the "To" field from direct messages if the user has requested to not
|
||||||
|
allow direct messages
|
||||||
|
"""
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(
|
||||||
|
%{
|
||||||
|
"type" => "Create",
|
||||||
|
"actor" => actor,
|
||||||
|
"object" => %{
|
||||||
|
"type" => "Note"
|
||||||
|
}
|
||||||
|
} = activity
|
||||||
|
) do
|
||||||
|
with recipients <- Map.get(activity, "to", []),
|
||||||
|
cc <- Map.get(activity, "cc", []),
|
||||||
|
true <- is_direct?(recipients, cc),
|
||||||
|
sender <- User.get_cached_by_ap_id(actor) do
|
||||||
|
new_to =
|
||||||
|
Enum.filter(recipients, fn recv ->
|
||||||
|
should_include?(sender, recv)
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok,
|
||||||
|
activity
|
||||||
|
|> Map.put("to", new_to)
|
||||||
|
|> maybe_replace_object_to(new_to)}
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
{:ok, activity}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(object), do: {:ok, object}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe, do: {:ok, %{}}
|
||||||
|
|
||||||
|
defp should_include?(sender, receiver_ap_id) do
|
||||||
|
with %User{local: true} = receiver <- User.get_cached_by_ap_id(receiver_ap_id) do
|
||||||
|
User.accepts_direct_messages?(receiver, sender)
|
||||||
|
else
|
||||||
|
_ -> true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_replace_object_to(%{"object" => %{"to" => _}} = activity, to) do
|
||||||
|
Kernel.put_in(activity, ["object", "to"], to)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_replace_object_to(other, _), do: other
|
||||||
|
|
||||||
|
defp is_direct?(to, cc) do
|
||||||
|
!(Enum.member?(to, Pleroma.Constants.as_public()) ||
|
||||||
|
Enum.member?(cc, Pleroma.Constants.as_public()))
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,50 @@
|
||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.RejectNewlyCreatedAccountNotesPolicy do
|
||||||
|
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
|
||||||
|
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
@moduledoc """
|
||||||
|
Rejects notes from accounts that were created below a certain threshold of time ago
|
||||||
|
"""
|
||||||
|
@impl true
|
||||||
|
def filter(
|
||||||
|
%{
|
||||||
|
"type" => type,
|
||||||
|
"actor" => actor
|
||||||
|
} = activity
|
||||||
|
)
|
||||||
|
when type in ["Note", "Create"] do
|
||||||
|
min_age = Pleroma.Config.get([:mrf_reject_newly_created_account_notes, :age])
|
||||||
|
|
||||||
|
with %User{local: false} = user <- Pleroma.User.get_cached_by_ap_id(actor),
|
||||||
|
true <- Timex.diff(Timex.now(), user.inserted_at, :seconds) < min_age do
|
||||||
|
{:reject, "[RejectNewlyCreatedAccountNotesPolicy] Account created too recently"}
|
||||||
|
else
|
||||||
|
_ -> {:ok, activity}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(object), do: {:ok, object}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe, do: {:ok, %{}}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def config_description do
|
||||||
|
%{
|
||||||
|
key: :mrf_reject_newly_created_account_notes,
|
||||||
|
related_policy: "Pleroma.Web.ActivityPub.MRF.RejectNewlyCreatedAccountNotesPolicy",
|
||||||
|
label: "MRF Reject New Accounts",
|
||||||
|
description: "Reject notes from accounts created too recently",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :age,
|
||||||
|
type: :integer,
|
||||||
|
description: "Time below which to reject (in seconds)",
|
||||||
|
suggestions: [86_400]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -22,7 +22,10 @@ def cast_and_filter_recipients(message, field, follower_collection, field_fallba
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_object_defaults(data) do
|
def fix_object_defaults(data) do
|
||||||
context = Utils.maybe_create_context(data["context"] || data["conversation"])
|
context =
|
||||||
|
Utils.maybe_create_context(
|
||||||
|
data["context"] || data["conversation"] || data["inReplyTo"] || data["id"]
|
||||||
|
)
|
||||||
|
|
||||||
%User{follower_address: follower_collection} = User.get_cached_by_ap_id(data["attributedTo"])
|
%User{follower_address: follower_collection} = User.get_cached_by_ap_id(data["attributedTo"])
|
||||||
|
|
||||||
|
|
|
@ -920,8 +920,13 @@ def add_attributed_to(object) do
|
||||||
|
|
||||||
def prepare_attachments(object) do
|
def prepare_attachments(object) do
|
||||||
attachments =
|
attachments =
|
||||||
object
|
case Map.get(object, "attachment", []) do
|
||||||
|> Map.get("attachment", [])
|
[_ | _] = list -> list
|
||||||
|
_ -> []
|
||||||
|
end
|
||||||
|
|
||||||
|
attachments =
|
||||||
|
attachments
|
||||||
|> Enum.map(fn data ->
|
|> Enum.map(fn data ->
|
||||||
[%{"mediaType" => media_type, "href" => href} = url | _] = data["url"]
|
[%{"mediaType" => media_type, "href" => href} = url | _] = data["url"]
|
||||||
|
|
||||||
|
|
|
@ -410,7 +410,7 @@ def blocks_operation do
|
||||||
operationId: "AccountController.blocks",
|
operationId: "AccountController.blocks",
|
||||||
description: "View your blocks. See also accounts/:id/{block,unblock}",
|
description: "View your blocks. See also accounts/:id/{block,unblock}",
|
||||||
security: [%{"oAuth" => ["read:blocks"]}],
|
security: [%{"oAuth" => ["read:blocks"]}],
|
||||||
parameters: pagination_params(),
|
parameters: [with_relationships_param() | pagination_params()],
|
||||||
responses: %{
|
responses: %{
|
||||||
200 => Operation.response("Accounts", "application/json", array_of_accounts())
|
200 => Operation.response("Accounts", "application/json", array_of_accounts())
|
||||||
}
|
}
|
||||||
|
@ -708,6 +708,16 @@ defp update_credentials_request do
|
||||||
nullable: true,
|
nullable: true,
|
||||||
description:
|
description:
|
||||||
"Number of days after which statuses will be deleted. Set to -1 to disable."
|
"Number of days after which statuses will be deleted. Set to -1 to disable."
|
||||||
|
},
|
||||||
|
accepts_direct_messages_from: %Schema{
|
||||||
|
type: :string,
|
||||||
|
enum: [
|
||||||
|
"everybody",
|
||||||
|
"nobody",
|
||||||
|
"people_i_follow"
|
||||||
|
],
|
||||||
|
nullable: true,
|
||||||
|
description: "Who to accept DMs from"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
example: %{
|
example: %{
|
||||||
|
@ -729,7 +739,8 @@ defp update_credentials_request do
|
||||||
also_known_as: ["https://foo.bar/users/foo"],
|
also_known_as: ["https://foo.bar/users/foo"],
|
||||||
discoverable: false,
|
discoverable: false,
|
||||||
actor_type: "Person",
|
actor_type: "Person",
|
||||||
status_ttl_days: 30
|
status_ttl_days: 30,
|
||||||
|
accepts_direct_messages_from: "everybody"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -756,7 +767,7 @@ defp array_of_relationships do
|
||||||
"showing_reblogs" => true,
|
"showing_reblogs" => true,
|
||||||
"followed_by" => true,
|
"followed_by" => true,
|
||||||
"blocking" => false,
|
"blocking" => false,
|
||||||
"blocked_by" => true,
|
"blocked_by" => false,
|
||||||
"muting" => false,
|
"muting" => false,
|
||||||
"muting_notifications" => false,
|
"muting_notifications" => false,
|
||||||
"note" => "",
|
"note" => "",
|
||||||
|
@ -772,7 +783,7 @@ defp array_of_relationships do
|
||||||
"showing_reblogs" => true,
|
"showing_reblogs" => true,
|
||||||
"followed_by" => true,
|
"followed_by" => true,
|
||||||
"blocking" => false,
|
"blocking" => false,
|
||||||
"blocked_by" => true,
|
"blocked_by" => false,
|
||||||
"muting" => true,
|
"muting" => true,
|
||||||
"muting_notifications" => false,
|
"muting_notifications" => false,
|
||||||
"note" => "",
|
"note" => "",
|
||||||
|
|
|
@ -143,7 +143,7 @@ def admin_account do
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tags: %Schema{type: :string},
|
tags: %Schema{type: :string},
|
||||||
is_confirmed: %Schema{type: :string}
|
is_confirmed: %Schema{type: :boolean}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,7 +13,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do
|
||||||
description: "Relationship between current account and requested account",
|
description: "Relationship between current account and requested account",
|
||||||
type: :object,
|
type: :object,
|
||||||
properties: %{
|
properties: %{
|
||||||
blocked_by: %Schema{type: :boolean},
|
blocked_by: %Schema{
|
||||||
|
type: :boolean,
|
||||||
|
description: "Represents being blocked by this user. Always false."
|
||||||
|
},
|
||||||
blocking: %Schema{type: :boolean},
|
blocking: %Schema{type: :boolean},
|
||||||
domain_blocking: %Schema{type: :boolean},
|
domain_blocking: %Schema{type: :boolean},
|
||||||
endorsed: %Schema{type: :boolean},
|
endorsed: %Schema{type: :boolean},
|
||||||
|
|
|
@ -88,7 +88,7 @@ def reject_follow_request(follower, followed) do
|
||||||
|
|
||||||
def delete(activity_id, user) do
|
def delete(activity_id, user) do
|
||||||
with {_, %Activity{data: %{"object" => _, "type" => "Create"}} = activity} <-
|
with {_, %Activity{data: %{"object" => _, "type" => "Create"}} = activity} <-
|
||||||
{:find_activity, Activity.get_by_id(activity_id)},
|
{:find_activity, Activity.get_by_id(activity_id, filter: [])},
|
||||||
{_, %Object{} = object, _} <-
|
{_, %Object{} = object, _} <-
|
||||||
{:find_object, Object.normalize(activity, fetch: false), activity},
|
{:find_object, Object.normalize(activity, fetch: false), activity},
|
||||||
true <- User.superuser?(user) || user.ap_id == object.data["actor"],
|
true <- User.superuser?(user) || user.ap_id == object.data["actor"],
|
||||||
|
|
|
@ -144,6 +144,8 @@ def make_poll_data(%{poll: %{options: options, expires_in: expires_in}} = data)
|
||||||
when is_list(options) do
|
when is_list(options) do
|
||||||
limits = Config.get([:instance, :poll_limits])
|
limits = Config.get([:instance, :poll_limits])
|
||||||
|
|
||||||
|
options = options |> Enum.uniq()
|
||||||
|
|
||||||
with :ok <- validate_poll_expiration(expires_in, limits),
|
with :ok <- validate_poll_expiration(expires_in, limits),
|
||||||
:ok <- validate_poll_options_amount(options, limits),
|
:ok <- validate_poll_options_amount(options, limits),
|
||||||
:ok <- validate_poll_options_length(options, limits) do
|
:ok <- validate_poll_options_length(options, limits) do
|
||||||
|
@ -179,9 +181,14 @@ def make_poll_data(_data) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp validate_poll_options_amount(options, %{max_options: max_options}) do
|
defp validate_poll_options_amount(options, %{max_options: max_options}) do
|
||||||
if Enum.count(options) > max_options do
|
cond do
|
||||||
|
Enum.count(options) < 2 ->
|
||||||
|
{:error, "Poll must contain at least 2 options"}
|
||||||
|
|
||||||
|
Enum.count(options) > max_options ->
|
||||||
{:error, "Poll can't contain more than #{max_options} options"}
|
{:error, "Poll can't contain more than #{max_options} options"}
|
||||||
else
|
|
||||||
|
true ->
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,22 +11,31 @@ defmodule Pleroma.Web.EmbedController do
|
||||||
|
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
|
|
||||||
plug(:put_layout, :embed)
|
|
||||||
|
|
||||||
def show(conn, %{"id" => id}) do
|
def show(conn, %{"id" => id}) do
|
||||||
with %Activity{local: true} = activity <-
|
with {:activity, %Activity{} = activity} <-
|
||||||
Activity.get_by_id_with_object(id),
|
{:activity, Activity.get_by_id_with_object(id)},
|
||||||
true <- Visibility.is_public?(activity.object) do
|
{:local, true} <- {:local, activity.local},
|
||||||
|
{:visible, true} <- {:visible, Visibility.visible_for_user?(activity, nil)} do
|
||||||
{:ok, author} = User.get_or_fetch(activity.object.data["actor"])
|
{:ok, author} = User.get_or_fetch(activity.object.data["actor"])
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> delete_resp_header("x-frame-options")
|
|> delete_resp_header("x-frame-options")
|
||||||
|> delete_resp_header("content-security-policy")
|
|> delete_resp_header("content-security-policy")
|
||||||
|
|> put_view(Pleroma.Web.EmbedView)
|
||||||
|> render("show.html",
|
|> render("show.html",
|
||||||
activity: activity,
|
activity: activity,
|
||||||
author: User.sanitize_html(author),
|
author: User.sanitize_html(author),
|
||||||
counts: get_counts(activity)
|
counts: get_counts(activity)
|
||||||
)
|
)
|
||||||
|
else
|
||||||
|
{:activity, _} ->
|
||||||
|
render_error(conn, :not_found, "Post not found")
|
||||||
|
|
||||||
|
{:local, false} ->
|
||||||
|
render_error(conn, :unauthorized, "Federated posts cannot be embedded")
|
||||||
|
|
||||||
|
{:visible, false} ->
|
||||||
|
render_error(conn, :unauthorized, "Not authorized to view this post")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -221,6 +221,7 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
|
||||||
|> Maps.put_if_present(:is_discoverable, params[:discoverable])
|
|> 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.Gettext.normalize_locale(params[:language]))
|
||||||
|> Maps.put_if_present(:status_ttl_days, params[:status_ttl_days], status_ttl_days_value)
|
|> 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])
|
||||||
|
|
||||||
# What happens here:
|
# What happens here:
|
||||||
#
|
#
|
||||||
|
@ -517,7 +518,12 @@ def blocks(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(users)
|
|> add_link_headers(users)
|
||||||
|> render("index.json", users: users, for: user, as: :user)
|
|> render("index.json",
|
||||||
|
users: users,
|
||||||
|
for: user,
|
||||||
|
as: :user,
|
||||||
|
embed_relationships: embed_relationships?(params)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "GET /api/v1/accounts/lookup"
|
@doc "GET /api/v1/accounts/lookup"
|
||||||
|
|
|
@ -124,14 +124,7 @@ def render(
|
||||||
target,
|
target,
|
||||||
&User.blocks_user?(&1, &2)
|
&User.blocks_user?(&1, &2)
|
||||||
),
|
),
|
||||||
blocked_by:
|
blocked_by: false,
|
||||||
UserRelationship.exists?(
|
|
||||||
user_relationships,
|
|
||||||
:block,
|
|
||||||
target,
|
|
||||||
reading_user,
|
|
||||||
&User.blocks_user?(&1, &2)
|
|
||||||
),
|
|
||||||
muting:
|
muting:
|
||||||
UserRelationship.exists?(
|
UserRelationship.exists?(
|
||||||
user_relationships,
|
user_relationships,
|
||||||
|
@ -354,6 +347,7 @@ defp maybe_put_settings(
|
||||||
|> Kernel.put_in([:source, :privacy], user.default_scope)
|
|> Kernel.put_in([:source, :privacy], user.default_scope)
|
||||||
|> Kernel.put_in([:source, :pleroma, :show_role], user.show_role)
|
|> Kernel.put_in([:source, :pleroma, :show_role], user.show_role)
|
||||||
|> Kernel.put_in([:source, :pleroma, :no_rich_text], user.no_rich_text)
|
|> Kernel.put_in([:source, :pleroma, :no_rich_text], user.no_rich_text)
|
||||||
|
|> Kernel.put_in([:accepts_direct_messages_from], user.accepts_direct_messages_from)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_put_settings(data, _, _, _), do: data
|
defp maybe_put_settings(data, _, _, _), do: data
|
||||||
|
|
|
@ -52,7 +52,7 @@ def url(url) do
|
||||||
|
|
||||||
@spec url_proxiable?(String.t()) :: boolean()
|
@spec url_proxiable?(String.t()) :: boolean()
|
||||||
def url_proxiable?(url) do
|
def url_proxiable?(url) do
|
||||||
not local?(url) and not whitelisted?(url)
|
not local?(url) and not whitelisted?(url) and not blocked?(url)
|
||||||
end
|
end
|
||||||
|
|
||||||
def preview_url(url, preview_params \\ []) do
|
def preview_url(url, preview_params \\ []) do
|
||||||
|
@ -83,6 +83,16 @@ def whitelisted?(url) do
|
||||||
domain in mediaproxy_whitelist_domains
|
domain in mediaproxy_whitelist_domains
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def blocked?(url) do
|
||||||
|
%{scheme: scheme, host: domain} = URI.parse(url)
|
||||||
|
# Block either the bare domain or the scheme-domain combo
|
||||||
|
scheme_domain = "#{scheme}://#{domain}"
|
||||||
|
blocklist = Config.get([:media_proxy, :blocklist])
|
||||||
|
|
||||||
|
Enum.member?(blocklist, domain) ||
|
||||||
|
Enum.member?(blocklist, scheme_domain)
|
||||||
|
end
|
||||||
|
|
||||||
defp maybe_get_domain_from_url("http" <> _ = url) do
|
defp maybe_get_domain_from_url("http" <> _ = url) do
|
||||||
URI.parse(url).host
|
URI.parse(url).host
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,12 +8,20 @@ defmodule Pleroma.Web.Metadata.Providers.RelMe do
|
||||||
|
|
||||||
@impl Provider
|
@impl Provider
|
||||||
def build_tags(%{user: user}) do
|
def build_tags(%{user: user}) do
|
||||||
bio_tree = Floki.parse_fragment!(user.bio)
|
profile_tree =
|
||||||
|
user.bio
|
||||||
|
|> append_fields_tag(user.fields)
|
||||||
|
|> Floki.parse_fragment!()
|
||||||
|
|
||||||
(Floki.attribute(bio_tree, "link[rel~=me]", "href") ++
|
(Floki.attribute(profile_tree, "link[rel~=me]", "href") ++
|
||||||
Floki.attribute(bio_tree, "a[rel~=me]", "href"))
|
Floki.attribute(profile_tree, "a[rel~=me]", "href"))
|
||||||
|> Enum.map(fn link ->
|
|> Enum.map(fn link ->
|
||||||
{:link, [rel: "me", href: link], []}
|
{:link, [rel: "me", href: link], []}
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp append_fields_tag(bio, fields) do
|
||||||
|
fields
|
||||||
|
|> Enum.reduce(bio, fn %{"value" => v}, res -> res <> v end)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,12 +20,12 @@ def build_tags(%{activity_id: id, object: object, user: user}) do
|
||||||
|
|
||||||
[
|
[
|
||||||
title_tag(user),
|
title_tag(user),
|
||||||
{:meta, [property: "twitter:description", content: scrubbed_content], []}
|
{:meta, [name: "twitter:description", content: scrubbed_content], []}
|
||||||
] ++
|
] ++
|
||||||
if attachments == [] or Metadata.activity_nsfw?(object) do
|
if attachments == [] or Metadata.activity_nsfw?(object) do
|
||||||
[
|
[
|
||||||
image_tag(user),
|
image_tag(user),
|
||||||
{:meta, [property: "twitter:card", content: "summary"], []}
|
{:meta, [name: "twitter:card", content: "summary"], []}
|
||||||
]
|
]
|
||||||
else
|
else
|
||||||
attachments
|
attachments
|
||||||
|
@ -37,20 +37,19 @@ def build_tags(%{user: user}) do
|
||||||
with truncated_bio = Utils.scrub_html_and_truncate(user.bio) do
|
with truncated_bio = Utils.scrub_html_and_truncate(user.bio) do
|
||||||
[
|
[
|
||||||
title_tag(user),
|
title_tag(user),
|
||||||
{:meta, [property: "twitter:description", content: truncated_bio], []},
|
{:meta, [name: "twitter:description", content: truncated_bio], []},
|
||||||
image_tag(user),
|
image_tag(user),
|
||||||
{:meta, [property: "twitter:card", content: "summary"], []}
|
{:meta, [name: "twitter:card", content: "summary"], []}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp title_tag(user) do
|
defp title_tag(user) do
|
||||||
{:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []}
|
{:meta, [name: "twitter:title", content: Utils.user_name_string(user)], []}
|
||||||
end
|
end
|
||||||
|
|
||||||
def image_tag(user) do
|
def image_tag(user) do
|
||||||
{:meta, [property: "twitter:image", content: MediaProxy.preview_url(User.avatar_url(user))],
|
{:meta, [name: "twitter:image", content: MediaProxy.preview_url(User.avatar_url(user))], []}
|
||||||
[]}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
|
defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
|
||||||
|
@ -60,10 +59,10 @@ defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
|
||||||
case Utils.fetch_media_type(@media_types, url["mediaType"]) do
|
case Utils.fetch_media_type(@media_types, url["mediaType"]) do
|
||||||
"audio" ->
|
"audio" ->
|
||||||
[
|
[
|
||||||
{:meta, [property: "twitter:card", content: "player"], []},
|
{:meta, [name: "twitter:card", content: "player"], []},
|
||||||
{:meta, [property: "twitter:player:width", content: "480"], []},
|
{:meta, [name: "twitter:player:width", content: "480"], []},
|
||||||
{:meta, [property: "twitter:player:height", content: "80"], []},
|
{:meta, [name: "twitter:player:height", content: "80"], []},
|
||||||
{:meta, [property: "twitter:player", content: player_url(id)], []}
|
{:meta, [name: "twitter:player", content: player_url(id)], []}
|
||||||
| acc
|
| acc
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -74,10 +73,10 @@ defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
|
||||||
# workaround.
|
# workaround.
|
||||||
"image" ->
|
"image" ->
|
||||||
[
|
[
|
||||||
{:meta, [property: "twitter:card", content: "summary_large_image"], []},
|
{:meta, [name: "twitter:card", content: "summary_large_image"], []},
|
||||||
{:meta,
|
{:meta,
|
||||||
[
|
[
|
||||||
property: "twitter:player",
|
name: "twitter:player",
|
||||||
content: MediaProxy.url(url["href"])
|
content: MediaProxy.url(url["href"])
|
||||||
], []}
|
], []}
|
||||||
| acc
|
| acc
|
||||||
|
@ -90,14 +89,14 @@ defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
|
||||||
width = url["width"] || 480
|
width = url["width"] || 480
|
||||||
|
|
||||||
[
|
[
|
||||||
{:meta, [property: "twitter:card", content: "player"], []},
|
{:meta, [name: "twitter:card", content: "player"], []},
|
||||||
{:meta, [property: "twitter:player", content: player_url(id)], []},
|
{:meta, [name: "twitter:player", content: player_url(id)], []},
|
||||||
{:meta, [property: "twitter:player:width", content: "#{width}"], []},
|
{:meta, [name: "twitter:player:width", content: "#{width}"], []},
|
||||||
{:meta, [property: "twitter:player:height", content: "#{height}"], []},
|
{:meta, [name: "twitter:player:height", content: "#{height}"], []},
|
||||||
{:meta, [property: "twitter:player:stream", content: MediaProxy.url(url["href"])],
|
{:meta, [name: "twitter:player:stream", content: MediaProxy.url(url["href"])],
|
||||||
[]},
|
[]},
|
||||||
{:meta,
|
{:meta, [name: "twitter:player:stream:content_type", content: url["mediaType"]],
|
||||||
[property: "twitter:player:stream:content_type", content: url["mediaType"]], []}
|
[]}
|
||||||
| acc
|
| acc
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -123,8 +122,8 @@ defp maybe_add_dimensions(metadata, url) do
|
||||||
!is_nil(url["height"]) && !is_nil(url["width"]) ->
|
!is_nil(url["height"]) && !is_nil(url["width"]) ->
|
||||||
metadata ++
|
metadata ++
|
||||||
[
|
[
|
||||||
{:meta, [property: "twitter:player:width", content: "#{url["width"]}"], []},
|
{:meta, [name: "twitter:player:width", content: "#{url["width"]}"], []},
|
||||||
{:meta, [property: "twitter:player:height", content: "#{url["height"]}"], []}
|
{:meta, [name: "twitter:player:height", content: "#{url["height"]}"], []}
|
||||||
]
|
]
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
|
|
|
@ -55,7 +55,7 @@ def scrub_html(content), do: content
|
||||||
def user_name_string(user) do
|
def user_name_string(user) do
|
||||||
"#{user.name} " <>
|
"#{user.name} " <>
|
||||||
if user.local do
|
if user.local do
|
||||||
"(@#{user.nickname}@#{Pleroma.Web.Endpoint.host()})"
|
"(@#{user.nickname}@#{Pleroma.Web.WebFinger.domain()})"
|
||||||
else
|
else
|
||||||
"(@#{user.nickname})"
|
"(@#{user.nickname})"
|
||||||
end
|
end
|
||||||
|
|
|
@ -41,6 +41,17 @@ def index(%{assigns: %{user: user}} = conn, %{id: activity_id} = params) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp filter_allowed_user_by_ap_id(ap_ids, excluded_ap_ids) do
|
||||||
|
Enum.reject(ap_ids, fn ap_id ->
|
||||||
|
with false <- ap_id in excluded_ap_ids,
|
||||||
|
%{is_active: true} <- User.get_cached_by_ap_id(ap_id) do
|
||||||
|
false
|
||||||
|
else
|
||||||
|
_ -> true
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
def filter_allowed_users(reactions, user, with_muted) do
|
def filter_allowed_users(reactions, user, with_muted) do
|
||||||
exclude_ap_ids =
|
exclude_ap_ids =
|
||||||
if is_nil(user) do
|
if is_nil(user) do
|
||||||
|
@ -51,7 +62,7 @@ def filter_allowed_users(reactions, user, with_muted) do
|
||||||
end
|
end
|
||||||
|
|
||||||
filter_emoji = fn emoji, users, url ->
|
filter_emoji = fn emoji, users, url ->
|
||||||
case Enum.reject(users, &(&1 in exclude_ap_ids)) do
|
case filter_allowed_user_by_ap_id(users, exclude_ap_ids) do
|
||||||
[] -> nil
|
[] -> nil
|
||||||
users -> {emoji, users, url}
|
users -> {emoji, users, url}
|
||||||
end
|
end
|
||||||
|
|
|
@ -37,7 +37,7 @@ def call(%{request_path: <<"/", @path, "/", file::binary>>} = conn, opts) do
|
||||||
%{query_params: %{"name" => name}} = conn ->
|
%{query_params: %{"name" => name}} = conn ->
|
||||||
name = escape_header_value(name)
|
name = escape_header_value(name)
|
||||||
|
|
||||||
put_resp_header(conn, "content-disposition", "filename=\"#{name}\"")
|
put_resp_header(conn, "content-disposition", ~s[inline; filename="#{name}"])
|
||||||
|
|
||||||
conn ->
|
conn ->
|
||||||
conn
|
conn
|
||||||
|
|
|
@ -6,8 +6,8 @@ defmodule Pleroma.Web.RichMedia.Parsers.OEmbed do
|
||||||
def parse(html, _data) do
|
def parse(html, _data) do
|
||||||
with elements = [_ | _] <- get_discovery_data(html),
|
with elements = [_ | _] <- get_discovery_data(html),
|
||||||
oembed_url when is_binary(oembed_url) <- get_oembed_url(elements),
|
oembed_url when is_binary(oembed_url) <- get_oembed_url(elements),
|
||||||
{:ok, oembed_data} <- get_oembed_data(oembed_url) do
|
{:ok, oembed_data = %{"html" => html}} <- get_oembed_data(oembed_url) do
|
||||||
oembed_data
|
%{oembed_data | "html" => Pleroma.HTML.filter_tags(html)}
|
||||||
else
|
else
|
||||||
_e -> %{}
|
_e -> %{}
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,6 +25,7 @@ defmodule Pleroma.Web.Streamer do
|
||||||
def registry, do: @registry
|
def registry, do: @registry
|
||||||
|
|
||||||
@public_streams ["public", "public:local", "public:media", "public:local:media"]
|
@public_streams ["public", "public:local", "public:media", "public:local:media"]
|
||||||
|
@local_streams ["public:local", "public:local:media"]
|
||||||
@user_streams ["user", "user:notification", "direct"]
|
@user_streams ["user", "user:notification", "direct"]
|
||||||
|
|
||||||
@doc "Expands and authorizes a stream, and registers the process for streaming."
|
@doc "Expands and authorizes a stream, and registers the process for streaming."
|
||||||
|
@ -41,14 +42,37 @@ def get_topic_and_add_socket(stream, user, oauth_token, params \\ %{}) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp can_access_stream(user, oauth_token, kind) do
|
||||||
|
with {_, true} <- {:restrict?, Config.restrict_unauthenticated_access?(:timelines, kind)},
|
||||||
|
{_, %User{id: user_id}, %Token{user_id: user_id}} <- {:user, user, oauth_token},
|
||||||
|
{_, true} <-
|
||||||
|
{:scopes,
|
||||||
|
OAuthScopesPlug.filter_descendants(["read:statuses"], oauth_token.scopes) != []} do
|
||||||
|
true
|
||||||
|
else
|
||||||
|
{:restrict?, _} ->
|
||||||
|
true
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc "Expand and authorizes a stream"
|
@doc "Expand and authorizes a stream"
|
||||||
@spec get_topic(stream :: String.t(), User.t() | nil, Token.t() | nil, Map.t()) ::
|
@spec get_topic(stream :: String.t(), User.t() | nil, Token.t() | nil, Map.t()) ::
|
||||||
{:ok, topic :: String.t()} | {:error, :bad_topic}
|
{:ok, topic :: String.t()} | {:error, :bad_topic}
|
||||||
def get_topic(stream, user, oauth_token, params \\ %{})
|
def get_topic(stream, user, oauth_token, params \\ %{})
|
||||||
|
|
||||||
# Allow all public steams.
|
# Allow all public steams if the instance allows unauthenticated access.
|
||||||
def get_topic(stream, _user, _oauth_token, _params) when stream in @public_streams do
|
# Otherwise, only allow users with valid oauth tokens.
|
||||||
|
def get_topic(stream, user, oauth_token, _params) when stream in @public_streams do
|
||||||
|
kind = if stream in @local_streams, do: :local, else: :federated
|
||||||
|
|
||||||
|
if can_access_stream(user, oauth_token, kind) do
|
||||||
{:ok, stream}
|
{:ok, stream}
|
||||||
|
else
|
||||||
|
{:error, :unauthorized}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Allow all hashtags streams.
|
# Allow all hashtags streams.
|
||||||
|
@ -57,12 +81,20 @@ def get_topic("hashtag", _user, _oauth_token, %{"tag" => tag} = _params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
# Allow remote instance streams.
|
# Allow remote instance streams.
|
||||||
def get_topic("public:remote", _user, _oauth_token, %{"instance" => instance} = _params) do
|
def get_topic("public:remote", user, oauth_token, %{"instance" => instance} = _params) do
|
||||||
|
if can_access_stream(user, oauth_token, :federated) do
|
||||||
{:ok, "public:remote:" <> instance}
|
{:ok, "public:remote:" <> instance}
|
||||||
|
else
|
||||||
|
{:error, :unauthorized}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_topic("public:remote:media", _user, _oauth_token, %{"instance" => instance} = _params) do
|
def get_topic("public:remote:media", user, oauth_token, %{"instance" => instance} = _params) do
|
||||||
|
if can_access_stream(user, oauth_token, :federated) do
|
||||||
{:ok, "public:remote:media:" <> instance}
|
{:ok, "public:remote:media:" <> instance}
|
||||||
|
else
|
||||||
|
{:error, :unauthorized}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Expand user streams.
|
# Expand user streams.
|
||||||
|
|
|
@ -6,10 +6,10 @@
|
||||||
<title><%= Pleroma.Config.get([:instance, :name]) %></title>
|
<title><%= Pleroma.Config.get([:instance, :name]) %></title>
|
||||||
<meta content='noindex' name='robots'>
|
<meta content='noindex' name='robots'>
|
||||||
<%= Phoenix.HTML.raw(assigns[:meta] || "") %>
|
<%= Phoenix.HTML.raw(assigns[:meta] || "") %>
|
||||||
<link rel="stylesheet" href="/embed.css">
|
<link rel="stylesheet" href="/embed/embed.css">
|
||||||
<base target="_parent">
|
<base target="_parent">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<%= render @view_module, @view_template, assigns %>
|
<%= render view_module(@conn), view_template(@conn), assigns %>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -15,7 +15,7 @@ defmodule Pleroma.Web.EmbedView do
|
||||||
alias Pleroma.Web.Metadata.Utils
|
alias Pleroma.Web.Metadata.Utils
|
||||||
alias Pleroma.Web.Router.Helpers
|
alias Pleroma.Web.Router.Helpers
|
||||||
|
|
||||||
use Phoenix.HTML
|
import Phoenix.HTML
|
||||||
|
|
||||||
defdelegate full_nickname(user), to: User
|
defdelegate full_nickname(user), to: User
|
||||||
|
|
||||||
|
@ -55,10 +55,13 @@ defp activity_url(%User{local: false}, %Activity{object: %Object{data: data}}) d
|
||||||
data["url"] || data["external_url"] || data["id"]
|
data["url"] || data["external_url"] || data["id"]
|
||||||
end
|
end
|
||||||
|
|
||||||
defp attachments(%Activity{object: %Object{data: %{"attachment" => attachments}}}) do
|
defp attachments(%Activity{object: %Object{data: %{"attachment" => attachments}}})
|
||||||
|
when is_list(attachments) do
|
||||||
attachments
|
attachments
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp attachments(_), do: []
|
||||||
|
|
||||||
defp sensitive?(%Activity{object: %Object{data: %{"sensitive" => sensitive}}}) do
|
defp sensitive?(%Activity{object: %Object{data: %{"sensitive" => sensitive}}}) do
|
||||||
sensitive
|
sensitive
|
||||||
end
|
end
|
||||||
|
|
|
@ -96,7 +96,7 @@ def represent_user(user, "XML") do
|
||||||
|> XmlBuilder.to_doc()
|
|> XmlBuilder.to_doc()
|
||||||
end
|
end
|
||||||
|
|
||||||
defp domain do
|
def domain do
|
||||||
Pleroma.Config.get([__MODULE__, :domain]) || Pleroma.Web.Endpoint.host()
|
Pleroma.Config.get([__MODULE__, :domain]) || Pleroma.Web.Endpoint.host()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
2
mix.exs
2
mix.exs
|
@ -134,7 +134,7 @@ defp deps do
|
||||||
{:tesla, "~> 1.4.4"},
|
{:tesla, "~> 1.4.4"},
|
||||||
{:castore, "~> 0.1"},
|
{:castore, "~> 0.1"},
|
||||||
{:cowlib, "~> 2.9"},
|
{:cowlib, "~> 2.9"},
|
||||||
{:finch, "~> 0.14.0"},
|
{:finch, "~> 0.16.0"},
|
||||||
{:jason, "~> 1.2"},
|
{:jason, "~> 1.2"},
|
||||||
{:trailing_format_plug, "~> 0.0.7"},
|
{:trailing_format_plug, "~> 0.0.7"},
|
||||||
{:mogrify, "~> 0.9.1"},
|
{:mogrify, "~> 0.9.1"},
|
||||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
6359
priv/gettext/th/LC_MESSAGES/config_descriptions.po
Normal file
6359
priv/gettext/th/LC_MESSAGES/config_descriptions.po
Normal file
File diff suppressed because it is too large
Load diff
|
@ -3,16 +3,16 @@ msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2020-09-20 13:18+0000\n"
|
"POT-Creation-Date: 2020-09-20 13:18+0000\n"
|
||||||
"PO-Revision-Date: 2020-12-14 06:00+0000\n"
|
"PO-Revision-Date: 2023-02-26 08:57+0000\n"
|
||||||
"Last-Translator: shironeko <shironeko@tesaguri.club>\n"
|
"Last-Translator: SevicheCC <sevicheee@outlook.com>\n"
|
||||||
"Language-Team: Chinese (Simplified) <https://translate.pleroma.social/"
|
"Language-Team: Chinese (Simplified) <http://translate.akkoma.dev/projects/"
|
||||||
"projects/pleroma/pleroma/zh_Hans/>\n"
|
"akkoma/akkoma-backend-errors/zh_Hans/>\n"
|
||||||
"Language: zh_Hans\n"
|
"Language: zh_Hans\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
"X-Generator: Weblate 4.0.4\n"
|
"X-Generator: Weblate 4.14\n"
|
||||||
|
|
||||||
## This file is a PO Template file.
|
## This file is a PO Template file.
|
||||||
##
|
##
|
||||||
|
@ -146,7 +146,7 @@ msgid "Cannot post an empty status without attachments"
|
||||||
msgstr "无法发送空白且不包含附件的状态"
|
msgstr "无法发送空白且不包含附件的状态"
|
||||||
|
|
||||||
#: lib/pleroma/web/common_api/utils.ex:511
|
#: lib/pleroma/web/common_api/utils.ex:511
|
||||||
#, elixir-format, fuzzy
|
#, elixir-format
|
||||||
msgid "Comment must be up to %{max_size} characters"
|
msgid "Comment must be up to %{max_size} characters"
|
||||||
msgstr "评论最多可使用 %{max_size} 字符"
|
msgstr "评论最多可使用 %{max_size} 字符"
|
||||||
|
|
||||||
|
@ -250,7 +250,7 @@ msgstr "没有该对话"
|
||||||
|
|
||||||
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:388
|
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:388
|
||||||
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:414 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:456
|
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:414 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:456
|
||||||
#, elixir-format, fuzzy
|
#, elixir-format
|
||||||
msgid "No such permission_group"
|
msgid "No such permission_group"
|
||||||
msgstr "没有该权限组"
|
msgstr "没有该权限组"
|
||||||
|
|
||||||
|
@ -297,7 +297,7 @@ msgid "This resource requires authentication."
|
||||||
msgstr "该资源需要认证。"
|
msgstr "该资源需要认证。"
|
||||||
|
|
||||||
#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206
|
#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206
|
||||||
#, elixir-format, fuzzy
|
#, elixir-format
|
||||||
msgid "Throttled"
|
msgid "Throttled"
|
||||||
msgstr "节流了"
|
msgstr "节流了"
|
||||||
|
|
||||||
|
@ -380,7 +380,7 @@ msgid "Failed"
|
||||||
msgstr "失败"
|
msgstr "失败"
|
||||||
|
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:410
|
#: lib/pleroma/web/oauth/oauth_controller.ex:410
|
||||||
#, elixir-format, fuzzy
|
#, elixir-format
|
||||||
msgid "Failed to authenticate: %{message}."
|
msgid "Failed to authenticate: %{message}."
|
||||||
msgstr "认证失败:%{message}。"
|
msgstr "认证失败:%{message}。"
|
||||||
|
|
||||||
|
@ -406,7 +406,7 @@ msgid "Invalid Username/Password"
|
||||||
msgstr "无效的用户名/密码"
|
msgstr "无效的用户名/密码"
|
||||||
|
|
||||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:118
|
#: lib/pleroma/web/twitter_api/twitter_api.ex:118
|
||||||
#, elixir-format, fuzzy
|
#, elixir-format
|
||||||
msgid "Invalid answer data"
|
msgid "Invalid answer data"
|
||||||
msgstr "无效的回答数据"
|
msgstr "无效的回答数据"
|
||||||
|
|
||||||
|
@ -437,7 +437,7 @@ msgid "Unsupported OAuth provider: %{provider}."
|
||||||
msgstr "不支持的 OAuth 提供者:%{provider}。"
|
msgstr "不支持的 OAuth 提供者:%{provider}。"
|
||||||
|
|
||||||
#: lib/pleroma/uploaders/uploader.ex:72
|
#: lib/pleroma/uploaders/uploader.ex:72
|
||||||
#, elixir-format, fuzzy
|
#, elixir-format
|
||||||
msgid "Uploader callback timeout"
|
msgid "Uploader callback timeout"
|
||||||
msgstr "上传回复超时"
|
msgstr "上传回复超时"
|
||||||
|
|
||||||
|
@ -452,7 +452,7 @@ msgid "CAPTCHA Error"
|
||||||
msgstr "验证码错误"
|
msgstr "验证码错误"
|
||||||
|
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:290
|
#: lib/pleroma/web/common_api/common_api.ex:290
|
||||||
#, elixir-format, fuzzy
|
#, elixir-format
|
||||||
msgid "Could not add reaction emoji"
|
msgid "Could not add reaction emoji"
|
||||||
msgstr "无法添加表情反应"
|
msgstr "无法添加表情反应"
|
||||||
|
|
||||||
|
|
163
priv/gettext/zh_Hans/LC_MESSAGES/posix_errors.po
Normal file
163
priv/gettext/zh_Hans/LC_MESSAGES/posix_errors.po
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2023-02-23 12:40+0000\n"
|
||||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
|
"Last-Translator: Automatically generated\n"
|
||||||
|
"Language-Team: none\n"
|
||||||
|
"Language: zh_Hans\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"X-Generator: Translate Toolkit 3.7.2\n"
|
||||||
|
|
||||||
|
## This file is a PO Template file.
|
||||||
|
##
|
||||||
|
## `msgid`s here are often extracted from source code.
|
||||||
|
## Add new translations manually only if they're dynamic
|
||||||
|
## translations that can't be statically extracted.
|
||||||
|
##
|
||||||
|
## Run `mix gettext.extract` to bring this file up to
|
||||||
|
## date. Leave `msgstr`s empty as changing them here as no
|
||||||
|
## effect: edit them in PO (`.po`) files instead.
|
||||||
|
msgid "eperm"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "eacces"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "eagain"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "ebadf"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "ebadmsg"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "ebusy"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "edeadlk"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "edeadlock"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "edquot"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "eexist"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "efault"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "efbig"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "eftype"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "eintr"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "einval"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "eio"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "eisdir"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "eloop"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "emfile"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "emlink"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "emultihop"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "enametoolong"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "enfile"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "enobufs"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "enodev"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "enolck"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "enolink"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "enoent"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "enomem"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "enospc"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "enosr"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "enostr"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "enosys"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "enotblk"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "enotdir"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "enotsup"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "enxio"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "eopnotsupp"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "eoverflow"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "epipe"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "erange"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "erofs"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "espipe"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "esrch"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "estale"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "etxtbsy"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "exdev"
|
||||||
|
msgstr ""
|
601
priv/gettext/zh_Hans/LC_MESSAGES/static_pages.po
Normal file
601
priv/gettext/zh_Hans/LC_MESSAGES/static_pages.po
Normal file
|
@ -0,0 +1,601 @@
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2023-02-23 12:41+0000\n"
|
||||||
|
"PO-Revision-Date: 2023-02-24 13:57+0000\n"
|
||||||
|
"Last-Translator: SevicheCC <sevicheee@outlook.com>\n"
|
||||||
|
"Language-Team: Chinese (Simplified) <http://translate.akkoma.dev/projects/"
|
||||||
|
"akkoma/akkoma-backend-static-pages/zh_Hans/>\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"
|
||||||
|
|
||||||
|
## This file is a PO Template file.
|
||||||
|
##
|
||||||
|
## "msgid"s here are often extracted from source code.
|
||||||
|
## Add new translations manually only if they're dynamic
|
||||||
|
## translations that can't be statically extracted.
|
||||||
|
##
|
||||||
|
## Run "mix gettext.extract" to bring this file up to
|
||||||
|
## date. Leave "msgstr"s empty as changing them here as no
|
||||||
|
## effect: edit them in PO (.po) files instead.
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:9
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "remote follow authorization button"
|
||||||
|
msgid "Authorize"
|
||||||
|
msgstr "批准"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:2
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "remote follow error"
|
||||||
|
msgid "Error fetching user"
|
||||||
|
msgstr "无法获取用户信息"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:4
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "remote follow header"
|
||||||
|
msgid "Remote follow"
|
||||||
|
msgstr "跨站关注"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:8
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "placeholder text for auth code entry"
|
||||||
|
msgid "Authentication code"
|
||||||
|
msgstr "验证码"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:10
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "placeholder text for password entry"
|
||||||
|
msgid "Password"
|
||||||
|
msgstr "密码"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:8
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "placeholder text for username entry"
|
||||||
|
msgid "Username"
|
||||||
|
msgstr "用户名"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:13
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "remote follow authorization button for login"
|
||||||
|
msgid "Authorize"
|
||||||
|
msgstr "授权"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:12
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "remote follow authorization button for mfa"
|
||||||
|
msgid "Authorize"
|
||||||
|
msgstr "授权"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex:2
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "remote follow error"
|
||||||
|
msgid "Error following account"
|
||||||
|
msgstr "无法关注账户"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:4
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "remote follow header, need login"
|
||||||
|
msgid "Log in to follow"
|
||||||
|
msgstr "登录以关注"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:4
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "remote follow mfa header"
|
||||||
|
msgid "Two-factor authentication"
|
||||||
|
msgstr "双因素身份验证"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex:4
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "remote follow success"
|
||||||
|
msgid "Account followed!"
|
||||||
|
msgstr "已关注该账号!"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:7
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:7
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "placeholder text for account id"
|
||||||
|
msgid "Your account ID, e.g. lain@quitter.se"
|
||||||
|
msgstr "你的账号ID,比如:lain@quitter.se"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:8
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "remote follow authorization button for following with a remote account"
|
||||||
|
msgid "Follow"
|
||||||
|
msgstr "关注"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:2
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "remote follow error"
|
||||||
|
msgid "Error: %{error}"
|
||||||
|
msgstr "错误:%{error}"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:4
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "remote follow header"
|
||||||
|
msgid "Remotely follow %{nickname}"
|
||||||
|
msgstr "远程关注 %{nickname}"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:12
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "password reset button"
|
||||||
|
msgid "Reset"
|
||||||
|
msgstr "重制"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex:4
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "password reset failed homepage link"
|
||||||
|
msgid "Homepage"
|
||||||
|
msgstr "主页"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex:1
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "password reset failed message"
|
||||||
|
msgid "Password reset failed"
|
||||||
|
msgstr "密码重置失败"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:8
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "password reset form confirm password prompt"
|
||||||
|
msgid "Confirmation"
|
||||||
|
msgstr "确认"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:4
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "password reset form password prompt"
|
||||||
|
msgid "Password"
|
||||||
|
msgstr "密码"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex:1
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "password reset invalid token message"
|
||||||
|
msgid "Invalid Token"
|
||||||
|
msgstr "无效 Token"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex:2
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "password reset successful homepage link"
|
||||||
|
msgid "Homepage"
|
||||||
|
msgstr "主页"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex:1
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "password reset successful message"
|
||||||
|
msgid "Password changed!"
|
||||||
|
msgstr "密码已修改!"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/feed/feed/tag.atom.eex:15
|
||||||
|
#: lib/pleroma/web/templates/feed/feed/tag.rss.eex:7
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "tag feed description"
|
||||||
|
msgid "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse."
|
||||||
|
msgstr "这些是带有 #%{tag} "
|
||||||
|
"标签的公开帖文。如果你在联邦宇宙的任何地方有账户,你可以与它们进行交互。"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex:3
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "oauth authorization exists page title"
|
||||||
|
msgid "Authorization exists"
|
||||||
|
msgstr "已存在授权"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:37
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "oauth authorize approve button"
|
||||||
|
msgid "Approve"
|
||||||
|
msgstr "允许"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:35
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "oauth authorize cancel button"
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr "取消"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:26
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "oauth authorize message"
|
||||||
|
msgid "Application <strong>%{client_name}</strong> is requesting access to your account."
|
||||||
|
msgstr "应用程序 <strong>%{client_name}</strong> 正在请求访问您的帐户。"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex:3
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "oauth authorized page title"
|
||||||
|
msgid "Successfully authorized"
|
||||||
|
msgstr "授权成功"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex:1
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "oauth external provider page title"
|
||||||
|
msgid "Sign in with external provider"
|
||||||
|
msgstr "使用外部服务进行登录"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex:13
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "oauth external provider sign in button"
|
||||||
|
msgid "Sign in with %{strategy}"
|
||||||
|
msgstr "用 %{strategy} 登录"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:59
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "oauth login button"
|
||||||
|
msgid "Log In"
|
||||||
|
msgstr "登录"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:56
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "oauth login password prompt"
|
||||||
|
msgid "Password"
|
||||||
|
msgstr "密码"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:52
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "oauth login username prompt"
|
||||||
|
msgid "Username"
|
||||||
|
msgstr "用户名"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:44
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "oauth register nickname prompt"
|
||||||
|
msgid "Pleroma Handle"
|
||||||
|
msgstr "Pleroma账号"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:42
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "oauth register nickname unchangeable warning"
|
||||||
|
msgid "Choose carefully! You won't be able to change this later. You will be able to change your display name, though."
|
||||||
|
msgstr "选择时要慎重!您以后将无法更改此项。不过您可以更改您的显示名称。"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:18
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "oauth register page email prompt"
|
||||||
|
msgid "Email"
|
||||||
|
msgstr "邮箱"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:10
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "oauth register page fill form prompt"
|
||||||
|
msgid "If you'd like to register a new account, please provide the details below."
|
||||||
|
msgstr "如果您想注册一个新账户,请提供以下细节。"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:35
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "oauth register page login button"
|
||||||
|
msgid "Proceed as existing user"
|
||||||
|
msgstr "以现有用户身份进行"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:31
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "oauth register page login password prompt"
|
||||||
|
msgid "Password"
|
||||||
|
msgstr "密码"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:24
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "oauth register page login prompt"
|
||||||
|
msgid "Alternatively, sign in to connect to existing account."
|
||||||
|
msgstr "或者登录后连接到现有账户。"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:27
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "oauth register page login username prompt"
|
||||||
|
msgid "Name or email"
|
||||||
|
msgstr "名字或者邮箱"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:14
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "oauth register page nickname prompt"
|
||||||
|
msgid "Nickname"
|
||||||
|
msgstr "昵称"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:22
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "oauth register page register button"
|
||||||
|
msgid "Proceed as new user"
|
||||||
|
msgstr "以新用户身份进行"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:8
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "oauth register page title"
|
||||||
|
msgid "Registration Details"
|
||||||
|
msgstr "注册细节"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex:2
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "oauth scopes message"
|
||||||
|
msgid "The following permissions will be granted"
|
||||||
|
msgstr "将授予以下权限"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex:6
|
||||||
|
#: lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex:6
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "oauth token code message"
|
||||||
|
msgid "Token code is <br>%{token}"
|
||||||
|
msgstr "Token 码是<br>%{token}"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:14
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "mfa auth code prompt"
|
||||||
|
msgid "Authentication code"
|
||||||
|
msgstr "授权码"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:9
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "mfa auth page title"
|
||||||
|
msgid "Two-factor authentication"
|
||||||
|
msgstr "双因素身份验证"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:25
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "mfa auth page use recovery code link"
|
||||||
|
msgid "Enter a two-factor recovery code"
|
||||||
|
msgstr "输入一个双因素恢复的恢复代码"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:22
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "mfa auth verify code button"
|
||||||
|
msgid "Verify"
|
||||||
|
msgstr "认证"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:9
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "mfa recover page title"
|
||||||
|
msgid "Two-factor recovery"
|
||||||
|
msgstr "双因素恢复"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:14
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "mfa recover recovery code prompt"
|
||||||
|
msgid "Recovery code"
|
||||||
|
msgstr "恢复码"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:25
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "mfa recover use 2fa code link"
|
||||||
|
msgid "Enter a two-factor code"
|
||||||
|
msgstr "输入一个双重因素验证码"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:22
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "mfa recover verify recovery code button"
|
||||||
|
msgid "Verify"
|
||||||
|
msgstr "验证"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex:42
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "static fe profile page remote follow button"
|
||||||
|
msgid "Remote follow"
|
||||||
|
msgstr "跨站关注"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/email/digest.html.eex:163
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "digest email header line"
|
||||||
|
msgid "Hey %{nickname}, here is what you've missed!"
|
||||||
|
msgstr "嗨 %{nickname},这是你错过的一些东西!"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/email/digest.html.eex:544
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "digest email receiver address"
|
||||||
|
msgid "The email address you are subscribed as is <a href='mailto:%{@user.email}' style='color: %{color};text-decoration: none;'>%{email}</a>. "
|
||||||
|
msgstr ""
|
||||||
|
"您订阅的电子邮件地址是 <a href='mailto:%{@user.email}' style='color: %{color"
|
||||||
|
"};text-decoration: none;'>%{email}</a>。 "
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/email/digest.html.eex:538
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "digest email sending reason"
|
||||||
|
msgid "You have received this email because you have signed up to receive digest emails from <b>%{instance}</b> Pleroma instance."
|
||||||
|
msgstr "您之所以会收到来自<b>%{instance}</b> Pleroma "
|
||||||
|
"实例的邮件摘要,是因为您已经注册了该服务实例。"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/email/digest.html.eex:547
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "digest email unsubscribe action"
|
||||||
|
msgid "To unsubscribe, please go %{here}."
|
||||||
|
msgstr "取消订阅,请点击 %{here}."
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/email/digest.html.eex:547
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "digest email unsubscribe action link text"
|
||||||
|
msgid "here"
|
||||||
|
msgstr "这里"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/mailer/subscription/unsubscribe_failure.html.eex:1
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "mailer unsubscribe failed message"
|
||||||
|
msgid "UNSUBSCRIBE FAILURE"
|
||||||
|
msgstr "取消订阅失败"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex:1
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "mailer unsubscribe successful message"
|
||||||
|
msgid "UNSUBSCRIBE SUCCESSFUL"
|
||||||
|
msgstr "成功取消订阅"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/email/digest.html.eex:385
|
||||||
|
#, elixir-format
|
||||||
|
msgctxt "new followers count header"
|
||||||
|
msgid "%{count} New Follower"
|
||||||
|
msgid_plural "%{count} New Followers"
|
||||||
|
msgstr[0] "%{count} 个新关注者"
|
||||||
|
|
||||||
|
#: lib/pleroma/emails/user_email.ex:384
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "account archive email subject"
|
||||||
|
msgid "Your account archive is ready"
|
||||||
|
msgstr "您的账户档案已经准备好了"
|
||||||
|
|
||||||
|
#: lib/pleroma/emails/user_email.ex:188
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "approval pending email body"
|
||||||
|
msgid "<h3>Awaiting Approval</h3>\n<p>Your account at %{instance_name} is being reviewed by staff. You will receive another email once your account is approved.</p>\n"
|
||||||
|
msgstr ""
|
||||||
|
"<h3>正在等待批准</h3>\n"
|
||||||
|
"<p> 您在%{instance_name}的账户正在被工作人员审查。一旦您的账户被批准通过,您"
|
||||||
|
"将收到另一封电子邮件。</p>\n"
|
||||||
|
|
||||||
|
#: lib/pleroma/emails/user_email.ex:202
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "approval pending email subject"
|
||||||
|
msgid "Your account is awaiting approval"
|
||||||
|
msgstr "您的账户正在等待审批"
|
||||||
|
|
||||||
|
#: lib/pleroma/emails/user_email.ex:158
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "confirmation email body"
|
||||||
|
msgid "<h3>Thank you for registering on %{instance_name}</h3>\n<p>Email confirmation is required to activate the account.</p>\n<p>Please click the following link to <a href=\"%{confirmation_url}\">activate your account</a>.</p>\n"
|
||||||
|
msgstr ""
|
||||||
|
"<h3>感谢注册%{instance_name}</h3>\n"
|
||||||
|
"<p>需要电子邮件确认才能激活该账户</p>\n"
|
||||||
|
"<p>请点击以下链接以 <a href=\"%{confirmation_url}\">确认您的账户</a></p>\n"
|
||||||
|
|
||||||
|
#: lib/pleroma/emails/user_email.ex:174
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "confirmation email subject"
|
||||||
|
msgid "%{instance_name} account confirmation"
|
||||||
|
msgstr "%{instance_name} 账户确认"
|
||||||
|
|
||||||
|
#: lib/pleroma/emails/user_email.ex:310
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "digest email subject"
|
||||||
|
msgid "Your digest from %{instance_name}"
|
||||||
|
msgstr "您来自 %{instance_name} 的摘要邮件"
|
||||||
|
|
||||||
|
#: lib/pleroma/emails/user_email.ex:81
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "password reset email body"
|
||||||
|
msgid "<h3>Reset your password at %{instance_name}</h3>\n<p>Someone has requested password change for your account at %{instance_name}.</p>\n<p>If it was you, visit the following link to proceed: <a href=\"%{password_reset_url}\">reset password</a>.</p>\n<p>If it was someone else, nothing to worry about: your data is secure and your password has not been changed.</p>\n"
|
||||||
|
msgstr ""
|
||||||
|
"<h3>在 %{instance_name} 重置您的密码</h3>\n"
|
||||||
|
"<p>有人请求更改您在 %{instance_name} 的账户密码。</p>\n"
|
||||||
|
"<p>如果这是您的操作,请点击以下链接继续重置密码:<a href=\""
|
||||||
|
"%{password_reset_url}\">重置密码</a>。</p>\n"
|
||||||
|
"<p>如果这不是您的操作,请不用担心:您的数据是安全的,您的密码未被更改。</p>\n"
|
||||||
|
|
||||||
|
#: lib/pleroma/emails/user_email.ex:98
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "password reset email subject"
|
||||||
|
msgid "Password reset"
|
||||||
|
msgstr "重置密码"
|
||||||
|
|
||||||
|
#: lib/pleroma/emails/user_email.ex:215
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "successful registration email body"
|
||||||
|
msgid "<h3>Hello @%{nickname},</h3>\n<p>Your account at %{instance_name} has been registered successfully.</p>\n<p>No further action is required to activate your account.</p>\n"
|
||||||
|
msgstr ""
|
||||||
|
"<h3>你好,@%{nickname},</h3>\n"
|
||||||
|
"<p>你在 %{instance_name} 的账户已经成功注册。</p>\n"
|
||||||
|
"<p>无需进行其他操作即可激活你的账户。</p>\n"
|
||||||
|
|
||||||
|
#: lib/pleroma/emails/user_email.ex:231
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "successful registration email subject"
|
||||||
|
msgid "Account registered on %{instance_name}"
|
||||||
|
msgstr "账号注册在 %{instance_name}"
|
||||||
|
|
||||||
|
#: lib/pleroma/emails/user_email.ex:136
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "user invitation email subject"
|
||||||
|
msgid "Invitation to %{instance_name}"
|
||||||
|
msgstr "邀请加入 %{instance_name}"
|
||||||
|
|
||||||
|
#: lib/pleroma/emails/user_email.ex:53
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "welcome email html body"
|
||||||
|
msgid "Welcome to %{instance_name}!"
|
||||||
|
msgstr "欢迎来到%{instance_name}!"
|
||||||
|
|
||||||
|
#: lib/pleroma/emails/user_email.ex:41
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "welcome email subject"
|
||||||
|
msgid "Welcome to %{instance_name}!"
|
||||||
|
msgstr "欢迎来到%{instance_name}!"
|
||||||
|
|
||||||
|
#: lib/pleroma/emails/user_email.ex:65
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "welcome email text body"
|
||||||
|
msgid "Welcome to %{instance_name}!"
|
||||||
|
msgstr "欢迎来到 %{instance_name}!"
|
||||||
|
|
||||||
|
#: lib/pleroma/emails/user_email.ex:368
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "account archive email body - admin requested"
|
||||||
|
msgid "<p>Admin @%{admin_nickname} requested a full backup of your Akkoma account. It's ready for download:</p>\n<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
|
||||||
|
msgstr ""
|
||||||
|
"<p>管理员 @%{admin_nickname} 请求对你的 Akkoma "
|
||||||
|
"账户进行完整备份,备份已准备好可供下载:</p>\n"
|
||||||
|
"<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
|
||||||
|
|
||||||
|
#: lib/pleroma/emails/user_email.ex:356
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "account archive email body - self-requested"
|
||||||
|
msgid "<p>You requested a full backup of your Akkoma account. It's ready for download:</p>\n<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
|
||||||
|
msgstr ""
|
||||||
|
"<p>您请求了 Akkoma 帐户的完整备份。备份已准备就绪,可以下载:</p>\n"
|
||||||
|
"<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:41
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "oauth register page title"
|
||||||
|
msgid "This is your first visit! Please enter your Akkoma handle."
|
||||||
|
msgstr "这是您的第一次访问!请填写您的Akkoma账号。"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:123
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "remote follow error message - unknown error"
|
||||||
|
msgid "Something went wrong."
|
||||||
|
msgstr "发生了一些错误。"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:67
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "remote follow error message - user not found"
|
||||||
|
msgid "Could not find user"
|
||||||
|
msgstr "无法找到相应用户"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:8
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "status interact authorization button"
|
||||||
|
msgid "Interact"
|
||||||
|
msgstr "互动"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:2
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "status interact error"
|
||||||
|
msgid "Error: %{error}"
|
||||||
|
msgstr "错误:%{error}"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:95
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "status interact error message - status not found"
|
||||||
|
msgid "Could not find status"
|
||||||
|
msgstr "无法找到帖文"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:144
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "status interact error message - unknown error"
|
||||||
|
msgid "Something went wrong."
|
||||||
|
msgstr "发生了一些错误。"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:4
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "status interact header"
|
||||||
|
msgid "Interacting with %{nickname}'s %{status_link}"
|
||||||
|
msgstr "与 %{nickname} 的 %{status_link} 进行交互"
|
||||||
|
|
||||||
|
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:4
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "status interact header - status link text"
|
||||||
|
msgid "status"
|
||||||
|
msgstr "帖文"
|
||||||
|
|
||||||
|
#: lib/pleroma/emails/user_email.ex:119
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgctxt "user invitation email body"
|
||||||
|
msgid "<h3>You are invited to %{instance_name}</h3>\n<p>%{inviter_name} invites you to join %{instance_name}, an instance of Akkoma federated social networking platform.</p>\n<p>Click the following link to register: <a href=\"%{registration_url}\">accept invitation</a>.</p>\n"
|
||||||
|
msgstr ""
|
||||||
|
"<h3>您被邀请加入 %{instance_name}</h3>\n"
|
||||||
|
"<p>%{inviter_name} 邀请您加入 %{instance_name},这是一个使用 Akkoma "
|
||||||
|
"联邦社交网络平台的实例。</p>\n"
|
||||||
|
"<p>点击以下链接注册: <a href=\"%{registration_url}\">接受邀请</a>.</p>\n"
|
|
@ -1,11 +1,19 @@
|
||||||
defmodule Pleroma.Repo.Migrations.ForcePinnedObjectsToExist do
|
defmodule Pleroma.Repo.Migrations.ForcePinnedObjectsToExist do
|
||||||
use Ecto.Migration
|
use Ecto.Migration
|
||||||
|
|
||||||
def change do
|
def up do
|
||||||
execute("UPDATE users SET pinned_objects = '{}' WHERE pinned_objects IS NULL")
|
execute("UPDATE users SET pinned_objects = '{}' WHERE pinned_objects IS NULL")
|
||||||
|
|
||||||
alter table("users") do
|
alter table("users") do
|
||||||
modify(:pinned_objects, :map, null: false, default: %{})
|
modify(:pinned_objects, :map, null: false, default: %{})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
alter table("users") do
|
||||||
|
modify(:pinned_objects, :map, null: true, default: nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
execute("UPDATE users SET pinned_objects = NULL WHERE pinned_objects = '{}'")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
defmodule Pleroma.Repo.Migrations.AddMastofeSettings do
|
defmodule Pleroma.Repo.Migrations.AddMastofeSettings do
|
||||||
use Ecto.Migration
|
use Ecto.Migration
|
||||||
|
|
||||||
def change do
|
def up do
|
||||||
alter table(:users) do
|
alter table(:users) do
|
||||||
add_if_not_exists(:mastofe_settings, :map)
|
add_if_not_exists(:mastofe_settings, :map)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
alter table(:users) do
|
||||||
|
remove_if_exists(:mastofe_settings, :map)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
defmodule Pleroma.Repo.Migrations.AddLanguageToUsers do
|
defmodule Pleroma.Repo.Migrations.AddLanguageToUsers do
|
||||||
use Ecto.Migration
|
use Ecto.Migration
|
||||||
|
|
||||||
def change do
|
def up do
|
||||||
alter table(:users) do
|
alter table(:users) do
|
||||||
add_if_not_exists(:language, :string)
|
add_if_not_exists(:language, :string)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
alter table(:users) do
|
||||||
|
remove_if_exists(:language, :string)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
defmodule Pleroma.Repo.Migrations.InstanceActorsToActorTypeApplication do
|
|
||||||
use Ecto.Migration
|
|
||||||
|
|
||||||
def up do
|
|
||||||
execute("""
|
|
||||||
update users
|
|
||||||
set actor_type = 'Application'
|
|
||||||
where local
|
|
||||||
and (ap_id like '%/relay' or ap_id like '%/internal/fetch')
|
|
||||||
""")
|
|
||||||
end
|
|
||||||
|
|
||||||
def down do
|
|
||||||
execute("""
|
|
||||||
update users
|
|
||||||
set actor_type = 'Person'
|
|
||||||
where local
|
|
||||||
and (ap_id like '%/relay' or ap_id like '%/internal/fetch')
|
|
||||||
""")
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.AddUnfollowedDmRestrictions do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table(:users) do
|
||||||
|
add(:accepts_direct_messages_from, :string, default: "everybody")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
115
priv/static/embed/embed.css
Normal file
115
priv/static/embed/embed.css
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
body {
|
||||||
|
background-color: #282c37;
|
||||||
|
font-family: sans-serif;
|
||||||
|
color: white;
|
||||||
|
margin: 0;
|
||||||
|
padding: 1em;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar img {
|
||||||
|
float: left;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-content {
|
||||||
|
padding-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date a,
|
||||||
|
.counts {
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counts dt,
|
||||||
|
.counts dd {
|
||||||
|
float: left;
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-card {
|
||||||
|
min-height: 48px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-card a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-card a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.display-name {
|
||||||
|
padding-top: 4px;
|
||||||
|
display: block;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* keep emoji from being hilariously huge */
|
||||||
|
.display-name img {
|
||||||
|
max-height: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.display-name .nickname {
|
||||||
|
padding-top: 4px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nickname:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pull-right {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse {
|
||||||
|
margin: 0;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.button {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: inline-block;
|
||||||
|
color: white;
|
||||||
|
background-color: #419bdd;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: none;
|
||||||
|
padding: 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.button:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
background-color: #61a6d9;
|
||||||
|
}
|
43
priv/static/embed/embed.js
Normal file
43
priv/static/embed/embed.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
(function () {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
var ready = function (loaded) {
|
||||||
|
if (['interactive', 'complete'].indexOf(document.readyState) !== -1) {
|
||||||
|
loaded()
|
||||||
|
} else {
|
||||||
|
document.addEventListener('DOMContentLoaded', loaded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ready(function () {
|
||||||
|
var iframes = []
|
||||||
|
|
||||||
|
window.addEventListener('message', function (e) {
|
||||||
|
var data = e.data || {}
|
||||||
|
|
||||||
|
if (data.type !== 'setHeightPleromaEmbed' || !iframes[data.id]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
iframes[data.id].height = data.height
|
||||||
|
});
|
||||||
|
|
||||||
|
[].forEach.call(document.querySelectorAll('iframe.pleroma-embed'), function (iframe) {
|
||||||
|
iframe.scrolling = 'no'
|
||||||
|
iframe.style.overflow = 'hidden'
|
||||||
|
|
||||||
|
iframes.push(iframe)
|
||||||
|
|
||||||
|
var id = iframes.length - 1
|
||||||
|
|
||||||
|
iframe.onload = function () {
|
||||||
|
iframe.contentWindow.postMessage({
|
||||||
|
type: 'setHeightPleromaEmbed',
|
||||||
|
id: id
|
||||||
|
}, '*')
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe.onload()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})()
|
|
@ -14,13 +14,13 @@ input {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
background-color: var(--background-color);
|
background-color: transparent;
|
||||||
color: var(--primary-text-color);
|
color: inherit;
|
||||||
border: 0;
|
border: 0;
|
||||||
transition-property: border-bottom;
|
transition-property: border-bottom;
|
||||||
transition-duration: 0.35s;
|
transition-duration: 0.35s;
|
||||||
border-bottom: 2px solid #2a384a;
|
border-bottom: 2px solid var(--faint);
|
||||||
font-size: 14px;
|
font: inherit;
|
||||||
width: inherit;
|
width: inherit;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
@ -91,26 +91,22 @@ [type="checkbox"]:checked+label:before {
|
||||||
a.button,
|
a.button,
|
||||||
button {
|
button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #1c2a3a;
|
background-color: var(--btn);
|
||||||
color: var(--primary-text-color);
|
color: var(--btnText);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 10px 16px;
|
padding: 10px 16px;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-size: 16px;
|
box-shadow: var(--btnShadow);
|
||||||
box-shadow: 0px 0px 2px 0px black,
|
font: inherit;
|
||||||
0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset,
|
|
||||||
0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a.button:hover,
|
a.button:hover,
|
||||||
button:hover {
|
button:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
box-shadow: 0px 0px 0px 1px var(--brand-color),
|
box-shadow: var(--btnHoverShadow);
|
||||||
0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset,
|
|
||||||
0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
|
@ -156,3 +152,20 @@ .account-header__nickname {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: var(--muted-text-color);
|
color: var(--muted-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.oauth {
|
||||||
|
/* Remote interaction /main/ostatus has such hierarchy, and its header and
|
||||||
|
* content do not pad themselves:
|
||||||
|
* (.panel.oauth (h2)
|
||||||
|
* (form (input)
|
||||||
|
* (button))) */
|
||||||
|
padding: 1px 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oauth .container__content {
|
||||||
|
/* Frontend selection /oauth/authorize needs an inverse because its heading
|
||||||
|
* and content have their own background and padding:
|
||||||
|
* (.panel.oauth (form (.container__content (.panel-heading)
|
||||||
|
* (.panel-content)))) */
|
||||||
|
margin: -1px -1em;
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ command=/opt/akkoma/bin/pleroma
|
||||||
command_args="start"
|
command_args="start"
|
||||||
command_user=akkoma
|
command_user=akkoma
|
||||||
command_background=1
|
command_background=1
|
||||||
|
no_new_privs="yes"
|
||||||
|
|
||||||
# Ask process to terminate within 30 seconds, otherwise kill it
|
# Ask process to terminate within 30 seconds, otherwise kill it
|
||||||
retry="SIGTERM/30/SIGKILL/5"
|
retry="SIGTERM/30/SIGKILL/5"
|
||||||
|
|
1
test/fixtures/tesla_mock/mk.absturztau.be-93e7nm8wqg-activity.json
vendored
Normal file
1
test/fixtures/tesla_mock/mk.absturztau.be-93e7nm8wqg-activity.json
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","sensitive":"as:sensitive","Hashtag":"as:Hashtag","quoteUrl":"as:quoteUrl","toot":"http://joinmastodon.org/ns#","Emoji":"toot:Emoji","featured":"toot:featured","discoverable":"toot:discoverable","schema":"http://schema.org#","PropertyValue":"schema:PropertyValue","value":"schema:value","misskey":"https://misskey-hub.net/ns#","_misskey_content":"misskey:_misskey_content","_misskey_quote":"misskey:_misskey_quote","_misskey_reaction":"misskey:_misskey_reaction","_misskey_votes":"misskey:_misskey_votes","_misskey_talk":"misskey:_misskey_talk","isCat":"misskey:isCat","vcard":"http://www.w3.org/2006/vcard/ns#"}],"id":"https://mk.absturztau.be/notes/93e7nm8wqg/activity","actor":"https://mk.absturztau.be/users/8ozbzjs3o8","type":"Create","published":"2022-08-01T11:06:49.568Z","object":{"id":"https://mk.absturztau.be/notes/93e7nm8wqg","type":"Note","attributedTo":"https://mk.absturztau.be/users/8ozbzjs3o8","summary":null,"content":"<p><span>meow</span></p>","_misskey_content":"meow","published":"2022-08-01T11:06:49.568Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://mk.absturztau.be/users/8ozbzjs3o8/followers"],"inReplyTo":null,"attachment":[],"sensitive":false,"tag":[]},"to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://mk.absturztau.be/users/8ozbzjs3o8/followers"]}
|
|
@ -7,6 +7,7 @@ defmodule Mix.Tasks.Pleroma.DatabaseTest do
|
||||||
use Oban.Testing, repo: Pleroma.Repo
|
use Oban.Testing, repo: Pleroma.Repo
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Bookmark
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
@ -45,21 +46,25 @@ test "it replaces objects with references" do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "prune_objects" do
|
describe "prune_objects" do
|
||||||
test "it prunes old objects from the database" do
|
setup do
|
||||||
deadline = Pleroma.Config.get([:instance, :remote_post_retention_days]) + 1
|
deadline = Pleroma.Config.get([:instance, :remote_post_retention_days]) + 1
|
||||||
|
|
||||||
date =
|
old_insert_date =
|
||||||
Timex.now()
|
Timex.now()
|
||||||
|> Timex.shift(days: -deadline)
|
|> Timex.shift(days: -deadline)
|
||||||
|> Timex.to_naive_datetime()
|
|> Timex.to_naive_datetime()
|
||||||
|> NaiveDateTime.truncate(:second)
|
|> NaiveDateTime.truncate(:second)
|
||||||
|
|
||||||
|
%{old_insert_date: old_insert_date}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it prunes old objects from the database", %{old_insert_date: old_insert_date} do
|
||||||
insert(:note)
|
insert(:note)
|
||||||
|
|
||||||
%{id: note_remote_public_id} =
|
%{id: note_remote_public_id} =
|
||||||
:note
|
:note
|
||||||
|> insert()
|
|> insert()
|
||||||
|> Ecto.Changeset.change(%{updated_at: date})
|
|> Ecto.Changeset.change(%{updated_at: old_insert_date})
|
||||||
|> Repo.update!()
|
|> Repo.update!()
|
||||||
|
|
||||||
note_remote_non_public =
|
note_remote_non_public =
|
||||||
|
@ -69,7 +74,7 @@ test "it prunes old objects from the database" do
|
||||||
|
|
||||||
note_remote_non_public
|
note_remote_non_public
|
||||||
|> Ecto.Changeset.change(%{
|
|> Ecto.Changeset.change(%{
|
||||||
updated_at: date,
|
updated_at: old_insert_date,
|
||||||
data: note_remote_non_public_data |> update_in(["to"], fn _ -> [] end)
|
data: note_remote_non_public_data |> update_in(["to"], fn _ -> [] end)
|
||||||
})
|
})
|
||||||
|> Repo.update!()
|
|> Repo.update!()
|
||||||
|
@ -83,21 +88,37 @@ test "it prunes old objects from the database" do
|
||||||
refute Object.get_by_id(note_remote_non_public_id)
|
refute Object.get_by_id(note_remote_non_public_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "with the --keep-non-public option it still keeps non-public posts even if they are not local" do
|
test "it cleans up bookmarks", %{old_insert_date: old_insert_date} do
|
||||||
deadline = Pleroma.Config.get([:instance, :remote_post_retention_days]) + 1
|
user = insert(:user)
|
||||||
|
{:ok, old_object_activity} = CommonAPI.post(user, %{status: "yadayada"})
|
||||||
|
|
||||||
date =
|
Repo.one(Object)
|
||||||
Timex.now()
|
|> Ecto.Changeset.change(%{updated_at: old_insert_date})
|
||||||
|> Timex.shift(days: -deadline)
|
|> Repo.update!()
|
||||||
|> Timex.to_naive_datetime()
|
|
||||||
|> NaiveDateTime.truncate(:second)
|
|
||||||
|
|
||||||
|
{:ok, new_object_activity} = CommonAPI.post(user, %{status: "yadayada"})
|
||||||
|
|
||||||
|
{:ok, _} = Bookmark.create(user.id, old_object_activity.id)
|
||||||
|
{:ok, _} = Bookmark.create(user.id, new_object_activity.id)
|
||||||
|
|
||||||
|
assert length(Repo.all(Object)) == 2
|
||||||
|
assert length(Repo.all(Bookmark)) == 2
|
||||||
|
|
||||||
|
Mix.Tasks.Pleroma.Database.run(["prune_objects"])
|
||||||
|
|
||||||
|
assert length(Repo.all(Object)) == 1
|
||||||
|
assert length(Repo.all(Bookmark)) == 1
|
||||||
|
refute Bookmark.get(user.id, old_object_activity.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "with the --keep-non-public option it still keeps non-public posts even if they are not local",
|
||||||
|
%{old_insert_date: old_insert_date} do
|
||||||
insert(:note)
|
insert(:note)
|
||||||
|
|
||||||
%{id: note_remote_id} =
|
%{id: note_remote_id} =
|
||||||
:note
|
:note
|
||||||
|> insert()
|
|> insert()
|
||||||
|> Ecto.Changeset.change(%{updated_at: date})
|
|> Ecto.Changeset.change(%{updated_at: old_insert_date})
|
||||||
|> Repo.update!()
|
|> Repo.update!()
|
||||||
|
|
||||||
note_remote_non_public =
|
note_remote_non_public =
|
||||||
|
@ -107,7 +128,7 @@ test "with the --keep-non-public option it still keeps non-public posts even if
|
||||||
|
|
||||||
note_remote_non_public
|
note_remote_non_public
|
||||||
|> Ecto.Changeset.change(%{
|
|> Ecto.Changeset.change(%{
|
||||||
updated_at: date,
|
updated_at: old_insert_date,
|
||||||
data: note_remote_non_public_data |> update_in(["to"], fn _ -> [] end)
|
data: note_remote_non_public_data |> update_in(["to"], fn _ -> [] end)
|
||||||
})
|
})
|
||||||
|> Repo.update!()
|
|> Repo.update!()
|
||||||
|
@ -120,16 +141,10 @@ test "with the --keep-non-public option it still keeps non-public posts even if
|
||||||
refute Object.get_by_id(note_remote_id)
|
refute Object.get_by_id(note_remote_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "with the --keep-threads and --keep-non-public option it keeps old threads with non-public replies even if the interaction is not local" do
|
test "with the --keep-threads and --keep-non-public option it keeps old threads with non-public replies even if the interaction is not local",
|
||||||
|
%{old_insert_date: old_insert_date} do
|
||||||
# For non-public we only check Create Activities because only these are relevant for threads
|
# For non-public we only check Create Activities because only these are relevant for threads
|
||||||
# Flags are always non-public, Announces from relays can be non-public...
|
# Flags are always non-public, Announces from relays can be non-public...
|
||||||
deadline = Pleroma.Config.get([:instance, :remote_post_retention_days]) + 1
|
|
||||||
|
|
||||||
old_insert_date =
|
|
||||||
Timex.now()
|
|
||||||
|> Timex.shift(days: -deadline)
|
|
||||||
|> Timex.to_naive_datetime()
|
|
||||||
|> NaiveDateTime.truncate(:second)
|
|
||||||
|
|
||||||
remote_user1 = insert(:user, local: false)
|
remote_user1 = insert(:user, local: false)
|
||||||
remote_user2 = insert(:user, local: false)
|
remote_user2 = insert(:user, local: false)
|
||||||
|
@ -212,15 +227,9 @@ test "with the --keep-threads option it still keeps non-old threads even with no
|
||||||
assert length(Repo.all(Object)) == 2
|
assert length(Repo.all(Object)) == 2
|
||||||
end
|
end
|
||||||
|
|
||||||
test "with the --keep-threads option it deletes old threads with no local interaction" do
|
test "with the --keep-threads option it deletes old threads with no local interaction", %{
|
||||||
deadline = Pleroma.Config.get([:instance, :remote_post_retention_days]) + 1
|
old_insert_date: old_insert_date
|
||||||
|
} do
|
||||||
old_insert_date =
|
|
||||||
Timex.now()
|
|
||||||
|> Timex.shift(days: -deadline)
|
|
||||||
|> Timex.to_naive_datetime()
|
|
||||||
|> NaiveDateTime.truncate(:second)
|
|
||||||
|
|
||||||
remote_user = insert(:user, local: false)
|
remote_user = insert(:user, local: false)
|
||||||
remote_user2 = insert(:user, local: false)
|
remote_user2 = insert(:user, local: false)
|
||||||
|
|
||||||
|
@ -261,15 +270,9 @@ test "with the --keep-threads option it deletes old threads with no local intera
|
||||||
assert length(Repo.all(Object)) == 0
|
assert length(Repo.all(Object)) == 0
|
||||||
end
|
end
|
||||||
|
|
||||||
test "with the --keep-threads option it keeps old threads with local interaction" do
|
test "with the --keep-threads option it keeps old threads with local interaction", %{
|
||||||
deadline = Pleroma.Config.get([:instance, :remote_post_retention_days]) + 1
|
old_insert_date: old_insert_date
|
||||||
|
} do
|
||||||
old_insert_date =
|
|
||||||
Timex.now()
|
|
||||||
|> Timex.shift(days: -deadline)
|
|
||||||
|> Timex.to_naive_datetime()
|
|
||||||
|> NaiveDateTime.truncate(:second)
|
|
||||||
|
|
||||||
remote_user = insert(:user, local: false)
|
remote_user = insert(:user, local: false)
|
||||||
local_user = insert(:user, local: true)
|
local_user = insert(:user, local: true)
|
||||||
|
|
||||||
|
@ -326,15 +329,9 @@ test "with the --keep-threads option it keeps old threads with local interaction
|
||||||
assert length(Repo.all(Object)) == 4
|
assert length(Repo.all(Object)) == 4
|
||||||
end
|
end
|
||||||
|
|
||||||
test "with the --keep-threads option it keeps old threads with bookmarked posts" do
|
test "with the --keep-threads option it keeps old threads with bookmarked posts", %{
|
||||||
deadline = Pleroma.Config.get([:instance, :remote_post_retention_days]) + 1
|
old_insert_date: old_insert_date
|
||||||
|
} do
|
||||||
old_insert_date =
|
|
||||||
Timex.now()
|
|
||||||
|> Timex.shift(days: -deadline)
|
|
||||||
|> Timex.to_naive_datetime()
|
|
||||||
|> NaiveDateTime.truncate(:second)
|
|
||||||
|
|
||||||
remote_user = insert(:user, local: false)
|
remote_user = insert(:user, local: false)
|
||||||
local_user = insert(:user, local: true)
|
local_user = insert(:user, local: true)
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ test "always add user and list topics" do
|
||||||
setup do
|
setup do
|
||||||
activity = %Activity{
|
activity = %Activity{
|
||||||
object: %Object{data: %{"type" => "Note"}},
|
object: %Object{data: %{"type" => "Note"}},
|
||||||
data: %{"to" => [Pleroma.Constants.as_public()]}
|
data: %{"to" => [Pleroma.Constants.as_public()], "type" => "Create"}
|
||||||
}
|
}
|
||||||
|
|
||||||
{:ok, activity: activity}
|
{:ok, activity: activity}
|
||||||
|
@ -114,6 +114,55 @@ test "local action doesn't produce public:remote topic", %{activity: activity} d
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "public visibility Announces" do
|
||||||
|
setup do
|
||||||
|
activity = %Activity{
|
||||||
|
object: %Object{data: %{"attachment" => []}},
|
||||||
|
data: %{"type" => "Announce", "to" => [Pleroma.Constants.as_public()]}
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, activity: activity}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not generate public topics", %{activity: activity} do
|
||||||
|
topics = Topics.get_activity_topics(activity)
|
||||||
|
|
||||||
|
refute "public" in topics
|
||||||
|
refute "public:remote" in topics
|
||||||
|
refute "public:local" in topics
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "local-public visibility create events" do
|
||||||
|
setup do
|
||||||
|
activity = %Activity{
|
||||||
|
object: %Object{data: %{"attachment" => []}},
|
||||||
|
data: %{"type" => "Create", "to" => [Pleroma.Web.ActivityPub.Utils.as_local_public()]}
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, activity: activity}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "doesn't produce public topics", %{activity: activity} do
|
||||||
|
topics = Topics.get_activity_topics(activity)
|
||||||
|
|
||||||
|
refute Enum.member?(topics, "public")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "produces public:local topics", %{activity: activity} do
|
||||||
|
topics = Topics.get_activity_topics(activity)
|
||||||
|
|
||||||
|
assert Enum.member?(topics, "public:local")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "with no attachments doesn't produce public:media topics", %{activity: activity} do
|
||||||
|
topics = Topics.get_activity_topics(activity)
|
||||||
|
|
||||||
|
refute Enum.member?(topics, "public:media")
|
||||||
|
refute Enum.member?(topics, "public:local:media")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "public visibility create events with attachments" do
|
describe "public visibility create events with attachments" do
|
||||||
setup do
|
setup do
|
||||||
activity = %Activity{
|
activity = %Activity{
|
||||||
|
@ -152,9 +201,36 @@ test "non-local action produces public:remote:media topic", %{activity: activity
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "local-public visibility create events with attachments" do
|
||||||
|
setup do
|
||||||
|
activity = %Activity{
|
||||||
|
object: %Object{data: %{"attachment" => ["foo"]}},
|
||||||
|
data: %{"type" => "Create", "to" => [Pleroma.Web.ActivityPub.Utils.as_local_public()]}
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, activity: activity}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "do not produce public:media topics", %{activity: activity} do
|
||||||
|
topics = Topics.get_activity_topics(activity)
|
||||||
|
|
||||||
|
refute Enum.member?(topics, "public:media")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "produces public:local:media topics", %{activity: activity} do
|
||||||
|
topics = Topics.get_activity_topics(activity)
|
||||||
|
|
||||||
|
assert Enum.member?(topics, "public:local:media")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "non-public visibility" do
|
describe "non-public visibility" do
|
||||||
test "produces direct topic" do
|
test "produces direct topic" do
|
||||||
activity = %Activity{object: %Object{data: %{"type" => "Note"}}, data: %{"to" => []}}
|
activity = %Activity{
|
||||||
|
object: %Object{data: %{"type" => "Note"}},
|
||||||
|
data: %{"to" => [], "type" => "Create"}
|
||||||
|
}
|
||||||
|
|
||||||
topics = Topics.get_activity_topics(activity)
|
topics = Topics.get_activity_topics(activity)
|
||||||
|
|
||||||
assert Enum.member?(topics, "direct")
|
assert Enum.member?(topics, "direct")
|
||||||
|
|
|
@ -328,6 +328,32 @@ test "it disables notifications from strangers" do
|
||||||
refute Notification.create_notification(activity, followed)
|
refute Notification.create_notification(activity, followed)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it disables notifications from non-followees" do
|
||||||
|
follower = insert(:user)
|
||||||
|
|
||||||
|
followed =
|
||||||
|
insert(:user,
|
||||||
|
notification_settings: %Pleroma.User.NotificationSetting{block_from_strangers: true}
|
||||||
|
)
|
||||||
|
|
||||||
|
CommonAPI.follow(follower, followed)
|
||||||
|
{:ok, activity} = CommonAPI.post(follower, %{status: "hey @#{followed.nickname}"})
|
||||||
|
refute Notification.create_notification(activity, followed)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it allows notifications from followees" do
|
||||||
|
poster = insert(:user)
|
||||||
|
|
||||||
|
receiver =
|
||||||
|
insert(:user,
|
||||||
|
notification_settings: %Pleroma.User.NotificationSetting{block_from_strangers: true}
|
||||||
|
)
|
||||||
|
|
||||||
|
CommonAPI.follow(receiver, poster)
|
||||||
|
{:ok, activity} = CommonAPI.post(poster, %{status: "hey @#{receiver.nickname}"})
|
||||||
|
assert Notification.create_notification(activity, receiver)
|
||||||
|
end
|
||||||
|
|
||||||
test "it doesn't create a notification for user if he is the activity author" do
|
test "it doesn't create a notification for user if he is the activity author" do
|
||||||
activity = insert(:note_activity)
|
activity = insert(:note_activity)
|
||||||
author = User.get_cached_by_ap_id(activity.data["actor"])
|
author = User.get_cached_by_ap_id(activity.data["actor"])
|
||||||
|
@ -1225,5 +1251,32 @@ test "it returns notifications about favorites with filtered word", %{user: user
|
||||||
|
|
||||||
assert length(Notification.for_user(user)) == 1
|
assert length(Notification.for_user(user)) == 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it returns notifications when related object is without content and filters are defined",
|
||||||
|
%{user: user} do
|
||||||
|
followed_user = insert(:user, is_locked: true)
|
||||||
|
|
||||||
|
insert(:filter, user: followed_user, phrase: "test", hide: true)
|
||||||
|
|
||||||
|
{:ok, _, _, _activity} = CommonAPI.follow(user, followed_user)
|
||||||
|
refute FollowingRelationship.following?(user, followed_user)
|
||||||
|
assert [notification] = Notification.for_user(followed_user)
|
||||||
|
|
||||||
|
assert %{type: "follow_request"} =
|
||||||
|
NotificationView.render("show.json", %{
|
||||||
|
notification: notification,
|
||||||
|
for: followed_user
|
||||||
|
})
|
||||||
|
|
||||||
|
assert {:ok, _} = CommonAPI.accept_follow_request(user, followed_user)
|
||||||
|
|
||||||
|
assert [notification] = Notification.for_user(followed_user)
|
||||||
|
|
||||||
|
assert %{type: "follow"} =
|
||||||
|
NotificationView.render("show.json", %{
|
||||||
|
notification: notification,
|
||||||
|
for: followed_user
|
||||||
|
})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -133,5 +133,20 @@ test "should gracefully handle an unsupported language" do
|
||||||
assert {:error, "libre_translate: request failed (code 400)"} =
|
assert {:error, "libre_translate: request failed (code 400)"} =
|
||||||
LibreTranslate.translate("ギュギュ握りつぶしちゃうぞ", nil, "zoop")
|
LibreTranslate.translate("ギュギュ握りつぶしちゃうぞ", nil, "zoop")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "should work when no detected language is received" do
|
||||||
|
Tesla.Mock.mock(fn
|
||||||
|
%{method: :post, url: "http://libre.translate/translate"} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body:
|
||||||
|
Jason.encode!(%{
|
||||||
|
translatedText: "I will crush you"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:ok, "", "I will crush you"} = LibreTranslate.translate("ギュギュ握りつぶしちゃうぞ", nil, "en")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
32
test/pleroma/upload/filter/only_media_test.exs
Normal file
32
test/pleroma/upload/filter/only_media_test.exs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Upload.Filter.OnlyMediaTest do
|
||||||
|
use Pleroma.DataCase, async: true
|
||||||
|
|
||||||
|
alias Pleroma.Upload
|
||||||
|
alias Pleroma.Upload.Filter.OnlyMedia
|
||||||
|
|
||||||
|
test "Allows media Content-Type" do
|
||||||
|
["audio/mpeg", "image/jpeg", "video/mp4"]
|
||||||
|
|> Enum.each(fn type ->
|
||||||
|
upload = %Upload{
|
||||||
|
content_type: type
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:ok, :noop} = OnlyMedia.filter(upload)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Disallows non-media Content-Type" do
|
||||||
|
["application/javascript", "application/pdf", "text/html"]
|
||||||
|
|> Enum.each(fn type ->
|
||||||
|
upload = %Upload{
|
||||||
|
content_type: type
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:error, _} = OnlyMedia.filter(upload)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
39
test/pleroma/user_note_test.exs
Normal file
39
test/pleroma/user_note_test.exs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.UserNoteTest do
|
||||||
|
alias Pleroma.UserNote
|
||||||
|
|
||||||
|
use Pleroma.DataCase, async: false
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
describe "show/2" do
|
||||||
|
setup do
|
||||||
|
{:ok, users: insert_list(2, :user)}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "if record does not exist, returns empty string", %{users: [user1, user2]} do
|
||||||
|
comment = UserNote.show(user1, user2)
|
||||||
|
|
||||||
|
assert comment == ""
|
||||||
|
end
|
||||||
|
|
||||||
|
test "if record exists with comment == nil, returns empty string", %{users: [user1, user2]} do
|
||||||
|
UserNote.create(user1, user2, nil)
|
||||||
|
|
||||||
|
comment = UserNote.show(user1, user2)
|
||||||
|
|
||||||
|
assert comment == ""
|
||||||
|
end
|
||||||
|
|
||||||
|
test "if record exists with non-nil comment, returns comment", %{users: [user1, user2]} do
|
||||||
|
expected_comment = "hello"
|
||||||
|
UserNote.create(user1, user2, expected_comment)
|
||||||
|
|
||||||
|
comment = UserNote.show(user1, user2)
|
||||||
|
|
||||||
|
assert comment == expected_comment
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -557,6 +557,21 @@ test "it fails gracefully with invalid email config" do
|
||||||
refute_email_sent()
|
refute_email_sent()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it works when the registering user does not provide an email" do
|
||||||
|
clear_config([Pleroma.Emails.Mailer, :enabled], false)
|
||||||
|
clear_config([:instance, :account_activation_required], false)
|
||||||
|
clear_config([:instance, :account_approval_required], true)
|
||||||
|
|
||||||
|
cng = User.register_changeset(%User{}, @full_user_data |> Map.put(:email, ""))
|
||||||
|
|
||||||
|
# The user is still created
|
||||||
|
assert {:ok, %User{nickname: "nick"}} = User.register(cng)
|
||||||
|
|
||||||
|
# No emails are sent
|
||||||
|
ObanHelpers.perform_all()
|
||||||
|
refute_email_sent()
|
||||||
|
end
|
||||||
|
|
||||||
test "it requires an email, name, nickname and password, bio is optional when account_activation_required is enabled" do
|
test "it requires an email, name, nickname and password, bio is optional when account_activation_required is enabled" do
|
||||||
clear_config([:instance, :account_activation_required], true)
|
clear_config([:instance, :account_activation_required], true)
|
||||||
|
|
||||||
|
@ -2756,4 +2771,35 @@ test "should not error when trying to unfollow a hashtag twice" do
|
||||||
assert user.followed_hashtags |> Enum.count() == 0
|
assert user.followed_hashtags |> Enum.count() == 0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "accepts_direct_messages?/2" do
|
||||||
|
test "should return true if the recipient follows the sender and has set accept to :people_i_follow" do
|
||||||
|
recipient =
|
||||||
|
insert(:user, %{
|
||||||
|
accepts_direct_messages_from: :people_i_follow
|
||||||
|
})
|
||||||
|
|
||||||
|
sender = insert(:user)
|
||||||
|
|
||||||
|
refute User.accepts_direct_messages?(recipient, sender)
|
||||||
|
|
||||||
|
CommonAPI.follow(recipient, sender)
|
||||||
|
|
||||||
|
assert User.accepts_direct_messages?(recipient, sender)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "should return true if the recipient has set accept to :everyone" do
|
||||||
|
recipient = insert(:user, %{accepts_direct_messages_from: :everybody})
|
||||||
|
sender = insert(:user)
|
||||||
|
|
||||||
|
assert User.accepts_direct_messages?(recipient, sender)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "should return false if the receipient set accept to :nobody" do
|
||||||
|
recipient = insert(:user, %{accepts_direct_messages_from: :nobody})
|
||||||
|
sender = insert(:user)
|
||||||
|
|
||||||
|
refute User.accepts_direct_messages?(recipient, sender)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.DirectMessageDisabledPolicyTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
alias Pleroma.Web.ActivityPub.MRF.DirectMessageDisabledPolicy
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
describe "strips recipients" do
|
||||||
|
test "when the user denies the direct message" do
|
||||||
|
sender = insert(:user)
|
||||||
|
recipient = insert(:user, %{accepts_direct_messages_from: :nobody})
|
||||||
|
|
||||||
|
refute User.accepts_direct_messages?(recipient, sender)
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"actor" => sender.ap_id,
|
||||||
|
"to" => [recipient.ap_id],
|
||||||
|
"cc" => [],
|
||||||
|
"type" => "Create",
|
||||||
|
"object" => %{
|
||||||
|
"type" => "Note",
|
||||||
|
"to" => [recipient.ap_id]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:ok, %{"to" => [], "object" => %{"to" => []}}} =
|
||||||
|
DirectMessageDisabledPolicy.filter(message)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "when the user does not deny the direct message" do
|
||||||
|
sender = insert(:user)
|
||||||
|
recipient = insert(:user, %{accepts_direct_messages_from: :everybody})
|
||||||
|
|
||||||
|
assert User.accepts_direct_messages?(recipient, sender)
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"actor" => sender.ap_id,
|
||||||
|
"to" => [recipient.ap_id],
|
||||||
|
"cc" => [],
|
||||||
|
"type" => "Create",
|
||||||
|
"object" => %{
|
||||||
|
"type" => "Note",
|
||||||
|
"to" => [recipient.ap_id]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:ok, message} = DirectMessageDisabledPolicy.filter(message)
|
||||||
|
assert message["to"] == [recipient.ap_id]
|
||||||
|
assert message["object"]["to"] == [recipient.ap_id]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,45 @@
|
||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.RejectNewlyCreatedAccountNotesPolicyTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
alias Pleroma.Web.ActivityPub.MRF.RejectNewlyCreatedAccountNotesPolicy
|
||||||
|
|
||||||
|
describe "reject notes from new accounts" do
|
||||||
|
test "rejects notes from accounts created more recently than `age`" do
|
||||||
|
clear_config([:mrf_reject_newly_created_account_notes, :age], 86_400)
|
||||||
|
sender = insert(:user, %{inserted_at: Timex.now(), local: false})
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"actor" => sender.ap_id,
|
||||||
|
"type" => "Create"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:reject, _} = RejectNewlyCreatedAccountNotesPolicy.filter(message)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not reject notes from accounts created longer ago" do
|
||||||
|
clear_config([:mrf_reject_newly_created_account_notes, :age], 86_400)
|
||||||
|
a_day_ago = Timex.shift(Timex.now(), days: -1)
|
||||||
|
sender = insert(:user, %{inserted_at: a_day_ago, local: false})
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"actor" => sender.ap_id,
|
||||||
|
"type" => "Create"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:ok, _} = RejectNewlyCreatedAccountNotesPolicy.filter(message)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not affect local users" do
|
||||||
|
clear_config([:mrf_reject_newly_created_account_notes, :age], 86_400)
|
||||||
|
sender = insert(:user, %{inserted_at: Timex.now(), local: true})
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"actor" => sender.ap_id,
|
||||||
|
"type" => "Create"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:ok, _} = RejectNewlyCreatedAccountNotesPolicy.filter(message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -102,7 +102,13 @@ test "it works as expected with noop policy" do
|
||||||
clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.NoOpPolicy])
|
clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.NoOpPolicy])
|
||||||
|
|
||||||
expected = %{
|
expected = %{
|
||||||
mrf_policies: ["NoOpPolicy", "HashtagPolicy", "InlineQuotePolicy", "NormalizeMarkup"],
|
mrf_policies: [
|
||||||
|
"NoOpPolicy",
|
||||||
|
"HashtagPolicy",
|
||||||
|
"InlineQuotePolicy",
|
||||||
|
"NormalizeMarkup",
|
||||||
|
"DirectMessageDisabledPolicy"
|
||||||
|
],
|
||||||
mrf_hashtag: %{
|
mrf_hashtag: %{
|
||||||
federated_timeline_removal: [],
|
federated_timeline_removal: [],
|
||||||
reject: [],
|
reject: [],
|
||||||
|
@ -118,7 +124,13 @@ test "it works as expected with mock policy" do
|
||||||
clear_config([:mrf, :policies], [MRFModuleMock])
|
clear_config([:mrf, :policies], [MRFModuleMock])
|
||||||
|
|
||||||
expected = %{
|
expected = %{
|
||||||
mrf_policies: ["MRFModuleMock", "HashtagPolicy", "InlineQuotePolicy", "NormalizeMarkup"],
|
mrf_policies: [
|
||||||
|
"MRFModuleMock",
|
||||||
|
"HashtagPolicy",
|
||||||
|
"InlineQuotePolicy",
|
||||||
|
"NormalizeMarkup",
|
||||||
|
"DirectMessageDisabledPolicy"
|
||||||
|
],
|
||||||
mrf_module_mock: "some config data",
|
mrf_module_mock: "some config data",
|
||||||
mrf_hashtag: %{
|
mrf_hashtag: %{
|
||||||
federated_timeline_removal: [],
|
federated_timeline_removal: [],
|
||||||
|
|
|
@ -19,12 +19,6 @@ test "gets an actor for the relay" do
|
||||||
assert user.ap_id == "#{Pleroma.Web.Endpoint.url()}/relay"
|
assert user.ap_id == "#{Pleroma.Web.Endpoint.url()}/relay"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "relay actor is an application" do
|
|
||||||
# See <https://www.w3.org/TR/activitystreams-vocabulary/#dfn-application>
|
|
||||||
user = Relay.get_actor()
|
|
||||||
assert user.actor_type == "Application"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "relay actor is invisible" do
|
test "relay actor is invisible" do
|
||||||
user = Relay.get_actor()
|
user = Relay.get_actor()
|
||||||
assert User.invisible?(user)
|
assert User.invisible?(user)
|
||||||
|
|
|
@ -732,9 +732,7 @@ test "it streams out the announce", %{announce: announce} do
|
||||||
]) do
|
]) do
|
||||||
{:ok, announce, _} = SideEffects.handle(announce)
|
{:ok, announce, _} = SideEffects.handle(announce)
|
||||||
|
|
||||||
assert called(
|
assert called(Pleroma.Web.Streamer.stream(["user", "list"], announce))
|
||||||
Pleroma.Web.Streamer.stream(["user", "list", "public", "public:local"], announce)
|
|
||||||
)
|
|
||||||
|
|
||||||
assert called(Pleroma.Web.Push.send(:_))
|
assert called(Pleroma.Web.Push.send(:_))
|
||||||
end
|
end
|
||||||
|
|
|
@ -316,7 +316,7 @@ test "it strips internal reactions" do
|
||||||
test "it correctly processes messages with non-array to field" do
|
test "it correctly processes messages with non-array to field" do
|
||||||
data =
|
data =
|
||||||
File.read!("test/fixtures/mastodon-post-activity.json")
|
File.read!("test/fixtures/mastodon-post-activity.json")
|
||||||
|> Poison.decode!()
|
|> Jason.decode!()
|
||||||
|> Map.put("to", "https://www.w3.org/ns/activitystreams#Public")
|
|> Map.put("to", "https://www.w3.org/ns/activitystreams#Public")
|
||||||
|> put_in(["object", "to"], "https://www.w3.org/ns/activitystreams#Public")
|
|> put_in(["object", "to"], "https://www.w3.org/ns/activitystreams#Public")
|
||||||
|
|
||||||
|
@ -333,7 +333,7 @@ test "it correctly processes messages with non-array to field" do
|
||||||
test "it correctly processes messages with non-array cc field" do
|
test "it correctly processes messages with non-array cc field" do
|
||||||
data =
|
data =
|
||||||
File.read!("test/fixtures/mastodon-post-activity.json")
|
File.read!("test/fixtures/mastodon-post-activity.json")
|
||||||
|> Poison.decode!()
|
|> Jason.decode!()
|
||||||
|> Map.put("cc", "http://mastodon.example.org/users/admin/followers")
|
|> Map.put("cc", "http://mastodon.example.org/users/admin/followers")
|
||||||
|> put_in(["object", "cc"], "http://mastodon.example.org/users/admin/followers")
|
|> put_in(["object", "cc"], "http://mastodon.example.org/users/admin/followers")
|
||||||
|
|
||||||
|
@ -346,7 +346,7 @@ test "it correctly processes messages with non-array cc field" do
|
||||||
test "it correctly processes messages with weirdness in address fields" do
|
test "it correctly processes messages with weirdness in address fields" do
|
||||||
data =
|
data =
|
||||||
File.read!("test/fixtures/mastodon-post-activity.json")
|
File.read!("test/fixtures/mastodon-post-activity.json")
|
||||||
|> Poison.decode!()
|
|> Jason.decode!()
|
||||||
|> Map.put("cc", ["http://mastodon.example.org/users/admin/followers", ["¿"]])
|
|> Map.put("cc", ["http://mastodon.example.org/users/admin/followers", ["¿"]])
|
||||||
|> put_in(["object", "cc"], ["http://mastodon.example.org/users/admin/followers", ["¿"]])
|
|> put_in(["object", "cc"], ["http://mastodon.example.org/users/admin/followers", ["¿"]])
|
||||||
|
|
||||||
|
@ -412,7 +412,7 @@ test "does NOT schedule background fetching of `replies` beyond max thread depth
|
||||||
|
|
||||||
activity =
|
activity =
|
||||||
File.read!("test/fixtures/mastodon-post-activity.json")
|
File.read!("test/fixtures/mastodon-post-activity.json")
|
||||||
|> Poison.decode!()
|
|> Jason.decode!()
|
||||||
|> Kernel.put_in(["object", "replies"], replies)
|
|> Kernel.put_in(["object", "replies"], replies)
|
||||||
|
|
||||||
%{activity: activity}
|
%{activity: activity}
|
||||||
|
@ -783,4 +783,42 @@ test "quote fetching should stop after n levels", _ do
|
||||||
} = Transmogrifier.fix_quote_url(note)
|
} = Transmogrifier.fix_quote_url(note)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "the standalone note uses its own ID when context is missing" do
|
||||||
|
insert(:user, ap_id: "https://mk.absturztau.be/users/8ozbzjs3o8")
|
||||||
|
|
||||||
|
activity =
|
||||||
|
"test/fixtures/tesla_mock/mk.absturztau.be-93e7nm8wqg-activity.json"
|
||||||
|
|> File.read!()
|
||||||
|
|> Jason.decode!()
|
||||||
|
|
||||||
|
{:ok, %Activity{} = modified} = Transmogrifier.handle_incoming(activity)
|
||||||
|
object = Object.normalize(modified, fetch: false)
|
||||||
|
|
||||||
|
assert object.data["context"] == object.data["id"]
|
||||||
|
assert modified.data["context"] == object.data["id"]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "the reply note uses its parent's ID when context is missing and reply is unreachable" do
|
||||||
|
insert(:user, ap_id: "https://mk.absturztau.be/users/8ozbzjs3o8")
|
||||||
|
|
||||||
|
activity =
|
||||||
|
"test/fixtures/tesla_mock/mk.absturztau.be-93e7nm8wqg-activity.json"
|
||||||
|
|> File.read!()
|
||||||
|
|> Jason.decode!()
|
||||||
|
|
||||||
|
object =
|
||||||
|
activity["object"]
|
||||||
|
|> Map.put("inReplyTo", "https://404.site/object/went-to-buy-milk")
|
||||||
|
|
||||||
|
activity =
|
||||||
|
activity
|
||||||
|
|> Map.put("object", object)
|
||||||
|
|
||||||
|
{:ok, %Activity{} = modified} = Transmogrifier.handle_incoming(activity)
|
||||||
|
object = Object.normalize(modified, fetch: false)
|
||||||
|
|
||||||
|
assert object.data["context"] == object.data["inReplyTo"]
|
||||||
|
assert modified.data["context"] == object.data["inReplyTo"]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -225,6 +225,20 @@ test "superusers deleting non-local posts won't federate the delete" do
|
||||||
|
|
||||||
refute Activity.get_by_id(post.id)
|
refute Activity.get_by_id(post.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it allows privileged users to delete banned user's posts" do
|
||||||
|
clear_config([:instance, :moderator_privileges], [:messages_delete])
|
||||||
|
user = insert(:user)
|
||||||
|
moderator = insert(:user, is_moderator: true)
|
||||||
|
|
||||||
|
{:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
|
||||||
|
User.set_activation(user, false)
|
||||||
|
|
||||||
|
assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
|
||||||
|
assert delete.local
|
||||||
|
|
||||||
|
refute Activity.get_by_id(post.id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "favoriting race condition" do
|
test "favoriting race condition" do
|
||||||
|
|
44
test/pleroma/web/embed_controller_test.exs
Normal file
44
test/pleroma/web/embed_controller_test.exs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.EmbedControllerTest do
|
||||||
|
use Pleroma.Web.ConnCase, async: true
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "/embed", %{conn: conn} do
|
||||||
|
activity = insert(:note_activity)
|
||||||
|
|
||||||
|
resp =
|
||||||
|
conn
|
||||||
|
|> get("/embed/#{activity.id}")
|
||||||
|
|> response(200)
|
||||||
|
|
||||||
|
object = Pleroma.Object.get_by_ap_id(activity.data["object"])
|
||||||
|
|
||||||
|
assert String.contains?(resp, object.data["content"])
|
||||||
|
end
|
||||||
|
|
||||||
|
test "/embed with a restricted post", %{conn: conn} do
|
||||||
|
activity = insert(:note_activity)
|
||||||
|
clear_config([:restrict_unauthenticated, :activities, :local], true)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> get("/embed/#{activity.id}")
|
||||||
|
|> response(401)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "/embed with a private post", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
Pleroma.Web.CommonAPI.post(user, %{
|
||||||
|
status: "Mega ultra chicken status: #fried",
|
||||||
|
visibility: "private"
|
||||||
|
})
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> get("/embed/#{activity.id}")
|
||||||
|
|> response(401)
|
||||||
|
end
|
||||||
|
end
|
|
@ -1895,6 +1895,39 @@ test "getting a list of blocks" do
|
||||||
assert [%{"id" => ^id2}] = result
|
assert [%{"id" => ^id2}] = result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "list of blocks with with_relationships parameter" do
|
||||||
|
%{user: user, conn: conn} = oauth_access(["read:blocks"])
|
||||||
|
%{id: id1} = other_user1 = insert(:user)
|
||||||
|
%{id: id2} = other_user2 = insert(:user)
|
||||||
|
%{id: id3} = other_user3 = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _, _} = User.follow(other_user1, user)
|
||||||
|
{:ok, _, _} = User.follow(other_user2, user)
|
||||||
|
{:ok, _, _} = User.follow(other_user3, user)
|
||||||
|
|
||||||
|
{:ok, _} = User.block(user, other_user1)
|
||||||
|
{:ok, _} = User.block(user, other_user2)
|
||||||
|
{:ok, _} = User.block(user, other_user3)
|
||||||
|
|
||||||
|
assert [
|
||||||
|
%{
|
||||||
|
"id" => ^id1,
|
||||||
|
"pleroma" => %{"relationship" => %{"blocking" => true, "followed_by" => false}}
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"id" => ^id2,
|
||||||
|
"pleroma" => %{"relationship" => %{"blocking" => true, "followed_by" => false}}
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"id" => ^id3,
|
||||||
|
"pleroma" => %{"relationship" => %{"blocking" => true, "followed_by" => false}}
|
||||||
|
}
|
||||||
|
] =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/blocks?with_relationships=true")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
end
|
||||||
|
|
||||||
test "account lookup", %{conn: conn} do
|
test "account lookup", %{conn: conn} do
|
||||||
%{nickname: acct} = insert(:user, %{nickname: "nickname"})
|
%{nickname: acct} = insert(:user, %{nickname: "nickname"})
|
||||||
%{nickname: acct_two} = insert(:user, %{nickname: "nickname@notlocaldoma.in"})
|
%{nickname: acct_two} = insert(:user, %{nickname: "nickname@notlocaldoma.in"})
|
||||||
|
|
|
@ -674,7 +674,10 @@ test "option limit is enforced", %{conn: conn} do
|
||||||
|> put_req_header("content-type", "application/json")
|
|> put_req_header("content-type", "application/json")
|
||||||
|> post("/api/v1/statuses", %{
|
|> post("/api/v1/statuses", %{
|
||||||
"status" => "desu~",
|
"status" => "desu~",
|
||||||
"poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1}
|
"poll" => %{
|
||||||
|
"options" => Enum.map(0..limit, fn num -> "desu #{num}" end),
|
||||||
|
"expires_in" => 1
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
%{"error" => error} = json_response_and_validate_schema(conn, 422)
|
%{"error" => error} = json_response_and_validate_schema(conn, 422)
|
||||||
|
@ -690,7 +693,7 @@ test "option character limit is enforced", %{conn: conn} do
|
||||||
|> post("/api/v1/statuses", %{
|
|> post("/api/v1/statuses", %{
|
||||||
"status" => "...",
|
"status" => "...",
|
||||||
"poll" => %{
|
"poll" => %{
|
||||||
"options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)],
|
"options" => [String.duplicate(".", limit + 1), "lol"],
|
||||||
"expires_in" => 1
|
"expires_in" => 1
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -772,6 +775,32 @@ test "scheduled poll", %{conn: conn} do
|
||||||
assert object.data["type"] == "Question"
|
assert object.data["type"] == "Question"
|
||||||
assert length(object.data["oneOf"]) == 3
|
assert length(object.data["oneOf"]) == 3
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "cannot have only one option", %{conn: conn} do
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses", %{
|
||||||
|
"status" => "desu~",
|
||||||
|
"poll" => %{"options" => ["mew"], "expires_in" => 1}
|
||||||
|
})
|
||||||
|
|
||||||
|
%{"error" => error} = json_response_and_validate_schema(conn, 422)
|
||||||
|
assert error == "Poll must contain at least 2 options"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "cannot have only duplicated options", %{conn: conn} do
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses", %{
|
||||||
|
"status" => "desu~",
|
||||||
|
"poll" => %{"options" => ["mew", "mew"], "expires_in" => 1}
|
||||||
|
})
|
||||||
|
|
||||||
|
%{"error" => error} = json_response_and_validate_schema(conn, 422)
|
||||||
|
assert error == "Poll must contain at least 2 options"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "get a status" do
|
test "get a status" do
|
||||||
|
@ -1044,6 +1073,27 @@ test "when you're an admin or moderator", %{conn: conn} do
|
||||||
refute Activity.get_by_id(activity1.id)
|
refute Activity.get_by_id(activity1.id)
|
||||||
refute Activity.get_by_id(activity2.id)
|
refute Activity.get_by_id(activity2.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "when you're privileged and the user is banned", %{conn: conn} do
|
||||||
|
clear_config([:instance, :moderator_privileges], [:messages_delete])
|
||||||
|
posting_user = insert(:user, is_active: false)
|
||||||
|
refute posting_user.is_active
|
||||||
|
activity = insert(:note_activity, user: posting_user)
|
||||||
|
user = insert(:user, is_moderator: true)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> assign(:token, insert(:oauth_token, user: user, scopes: ["write:statuses"]))
|
||||||
|
|> delete("/api/v1/statuses/#{activity.id}")
|
||||||
|
|
||||||
|
assert %{} = json_response_and_validate_schema(res_conn, 200)
|
||||||
|
|
||||||
|
# assert ModerationLog |> Repo.one() |> ModerationLog.get_log_entry_message() ==
|
||||||
|
# "@#{user.nickname} deleted status ##{activity.id}"
|
||||||
|
|
||||||
|
refute Activity.get_by_id(activity.id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "reblogging" do
|
describe "reblogging" do
|
||||||
|
@ -1960,6 +2010,10 @@ test "index" do
|
||||||
{:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
|
{:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
|
||||||
User.mute(user, other_user)
|
User.mute(user, other_user)
|
||||||
|
|
||||||
|
deactivated_user = insert(:user)
|
||||||
|
{:ok, _} = CommonAPI.react_with_emoji(activity.id, deactivated_user, "🎅")
|
||||||
|
User.set_activation(deactivated_user, false)
|
||||||
|
|
||||||
result =
|
result =
|
||||||
conn
|
conn
|
||||||
|> get("/api/v1/statuses/?ids[]=#{activity.id}")
|
|> get("/api/v1/statuses/?ids[]=#{activity.id}")
|
||||||
|
@ -1967,6 +2021,7 @@ test "index" do
|
||||||
|
|
||||||
assert [
|
assert [
|
||||||
%{
|
%{
|
||||||
|
"emoji_reactions" => [],
|
||||||
"pleroma" => %{
|
"pleroma" => %{
|
||||||
"emoji_reactions" => []
|
"emoji_reactions" => []
|
||||||
}
|
}
|
||||||
|
|
|
@ -727,4 +727,56 @@ test "actor_type field has a higher priority than bot", %{conn: conn} do
|
||||||
assert account["source"]["pleroma"]["actor_type"] == "Person"
|
assert account["source"]["pleroma"]["actor_type"] == "Person"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "Updating direct message settings" do
|
||||||
|
setup do: oauth_access(["write:accounts"])
|
||||||
|
setup :request_content_type
|
||||||
|
|
||||||
|
test "changing to :everybody", %{conn: conn} do
|
||||||
|
account =
|
||||||
|
conn
|
||||||
|
|> patch("/api/v1/accounts/update_credentials", %{
|
||||||
|
accepts_direct_messages_from: "everybody"
|
||||||
|
})
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert account["accepts_direct_messages_from"]
|
||||||
|
assert account["accepts_direct_messages_from"] == "everybody"
|
||||||
|
assert Pleroma.User.get_by_ap_id(account["url"]).accepts_direct_messages_from == :everybody
|
||||||
|
end
|
||||||
|
|
||||||
|
test "changing to :nobody", %{conn: conn} do
|
||||||
|
account =
|
||||||
|
conn
|
||||||
|
|> patch("/api/v1/accounts/update_credentials", %{accepts_direct_messages_from: "nobody"})
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert account["accepts_direct_messages_from"]
|
||||||
|
assert account["accepts_direct_messages_from"] == "nobody"
|
||||||
|
assert Pleroma.User.get_by_ap_id(account["url"]).accepts_direct_messages_from == :nobody
|
||||||
|
end
|
||||||
|
|
||||||
|
test "changing to :people_i_follow", %{conn: conn} do
|
||||||
|
account =
|
||||||
|
conn
|
||||||
|
|> patch("/api/v1/accounts/update_credentials", %{
|
||||||
|
accepts_direct_messages_from: "people_i_follow"
|
||||||
|
})
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert account["accepts_direct_messages_from"]
|
||||||
|
assert account["accepts_direct_messages_from"] == "people_i_follow"
|
||||||
|
|
||||||
|
assert Pleroma.User.get_by_ap_id(account["url"]).accepts_direct_messages_from ==
|
||||||
|
:people_i_follow
|
||||||
|
end
|
||||||
|
|
||||||
|
test "changing to an unsupported value", %{conn: conn} do
|
||||||
|
conn
|
||||||
|
|> patch("/api/v1/accounts/update_credentials", %{
|
||||||
|
accepts_direct_messages_from: "unsupported"
|
||||||
|
})
|
||||||
|
|> json_response(400)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -397,7 +397,22 @@ test "represent a relationship for the blocking and blocked user" do
|
||||||
expected =
|
expected =
|
||||||
Map.merge(
|
Map.merge(
|
||||||
@blank_response,
|
@blank_response,
|
||||||
%{following: false, blocking: true, blocked_by: true, id: to_string(other_user.id)}
|
%{following: false, blocking: true, blocked_by: false, id: to_string(other_user.id)}
|
||||||
|
)
|
||||||
|
|
||||||
|
test_relationship_rendering(user, other_user, expected)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "blocks are not visible to the blocked user" do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _user_relationship} = User.block(other_user, user)
|
||||||
|
|
||||||
|
expected =
|
||||||
|
Map.merge(
|
||||||
|
@blank_response,
|
||||||
|
%{following: false, blocking: false, blocked_by: false, id: to_string(other_user.id)}
|
||||||
)
|
)
|
||||||
|
|
||||||
test_relationship_rendering(user, other_user, expected)
|
test_relationship_rendering(user, other_user, expected)
|
||||||
|
|
|
@ -199,6 +199,15 @@ test "mediaproxy whitelist" do
|
||||||
assert unencoded == url
|
assert unencoded == url
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "mediaproxy blocklist" do
|
||||||
|
clear_config([:media_proxy, :whitelist], ["https://google.com"])
|
||||||
|
clear_config([:media_proxy, :blocklist], ["https://feld.me"])
|
||||||
|
url = "https://feld.me/foo.png"
|
||||||
|
|
||||||
|
unencoded = MediaProxy.url(url)
|
||||||
|
assert unencoded == url
|
||||||
|
end
|
||||||
|
|
||||||
# TODO: delete after removing support bare domains for media proxy whitelist
|
# TODO: delete after removing support bare domains for media proxy whitelist
|
||||||
test "mediaproxy whitelist bare domains whitelist (deprecated)" do
|
test "mediaproxy whitelist bare domains whitelist (deprecated)" do
|
||||||
clear_config([:media_proxy, :whitelist], ["google.com", "feld.me"])
|
clear_config([:media_proxy, :whitelist], ["google.com", "feld.me"])
|
||||||
|
@ -220,6 +229,18 @@ test "does not change whitelisted urls" do
|
||||||
assert String.starts_with?(encoded, media_url)
|
assert String.starts_with?(encoded, media_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "does not change blocked urls" do
|
||||||
|
clear_config([:media_proxy, :whitelist], ["mycdn.akamai.com"])
|
||||||
|
clear_config([:media_proxy, :base_url], "https://cache.pleroma.social")
|
||||||
|
|
||||||
|
media_url = "https://mycdn.akamai.com"
|
||||||
|
|
||||||
|
url = "#{media_url}/static/logo.png"
|
||||||
|
encoded = MediaProxy.url(url)
|
||||||
|
|
||||||
|
assert String.starts_with?(encoded, media_url)
|
||||||
|
end
|
||||||
|
|
||||||
test "ensure Pleroma.Upload base_url is always whitelisted" do
|
test "ensure Pleroma.Upload base_url is always whitelisted" do
|
||||||
media_url = "https://media.pleroma.social"
|
media_url = "https://media.pleroma.social"
|
||||||
clear_config([Pleroma.Upload, :base_url], media_url)
|
clear_config([Pleroma.Upload, :base_url], media_url)
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue