Merge remote-tracking branch 'origin/develop' into shigusegubu
* origin/develop: (466 commits) Fix compatibility with Elixir v1.10 Add test for custom base_url when deleting an attachment Fix base_url string trimming Add logging Remove `whole_db` parameter everywhere, it's not used anymore Fix __MODULE__ usage DOCS General update for the "Introduction to Pleroma" section update ecto Remove unused var explicitly describe perform op in AttachmentsClenupWorker Remove user recommendation by third party engine little fixes Fix typo description typos, Oban verbose type fix, new keys can be changed in runtime respect settings from database in mix tasks Document dynamic_configuration breaking change Emoji reactions: Update docs and changelog Emoji reactions: Change api format once more fix for non existing atom ...
This commit is contained in:
commit
43ea736c37
898 changed files with 18283 additions and 9351 deletions
|
|
@ -5,7 +5,6 @@ CC-BY-SA-4.0
|
||||||
COPYING
|
COPYING
|
||||||
*file
|
*file
|
||||||
elixir_buildpack.config
|
elixir_buildpack.config
|
||||||
docs/
|
|
||||||
test/
|
test/
|
||||||
|
|
||||||
# Required to get version
|
# Required to get version
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
[
|
[
|
||||||
inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}", "priv/repo/migrations/*.exs"]
|
inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}", "priv/repo/migrations/*.exs", "priv/scrubbers/*.ex"]
|
||||||
]
|
]
|
||||||
|
|
|
||||||
120
.gitlab-ci.yml
120
.gitlab-ci.yml
|
|
@ -1,23 +1,25 @@
|
||||||
image: elixir:1.8.1
|
image: elixir:1.8.1
|
||||||
|
|
||||||
variables:
|
variables: &global_variables
|
||||||
POSTGRES_DB: pleroma_test
|
POSTGRES_DB: pleroma_test
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
DB_HOST: postgres
|
DB_HOST: postgres
|
||||||
MIX_ENV: test
|
MIX_ENV: test
|
||||||
|
|
||||||
cache:
|
cache: &global_cache_policy
|
||||||
key: ${CI_COMMIT_REF_SLUG}
|
key: ${CI_COMMIT_REF_SLUG}
|
||||||
paths:
|
paths:
|
||||||
- deps
|
- deps
|
||||||
- _build
|
- _build
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- build
|
- build
|
||||||
- test
|
- test
|
||||||
- benchmark
|
- benchmark
|
||||||
- deploy
|
- deploy
|
||||||
- release
|
- release
|
||||||
|
- docker
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- mix local.hex --force
|
- mix local.hex --force
|
||||||
|
|
@ -29,28 +31,13 @@ build:
|
||||||
- mix deps.get
|
- mix deps.get
|
||||||
- mix compile --force
|
- mix compile --force
|
||||||
|
|
||||||
docs-build:
|
|
||||||
stage: build
|
|
||||||
only:
|
|
||||||
- master@pleroma/pleroma
|
|
||||||
- develop@pleroma/pleroma
|
|
||||||
variables:
|
|
||||||
MIX_ENV: dev
|
|
||||||
PLEROMA_BUILD_ENV: prod
|
|
||||||
script:
|
|
||||||
- mix deps.get
|
|
||||||
- mix compile
|
|
||||||
- mix docs
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- priv/static/doc
|
|
||||||
|
|
||||||
benchmark:
|
benchmark:
|
||||||
stage: benchmark
|
stage: benchmark
|
||||||
|
when: manual
|
||||||
variables:
|
variables:
|
||||||
MIX_ENV: benchmark
|
MIX_ENV: benchmark
|
||||||
services:
|
services:
|
||||||
- name: lainsoykaf/postgres-with-rum
|
- name: postgres:9.6
|
||||||
alias: postgres
|
alias: postgres
|
||||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||||
script:
|
script:
|
||||||
|
|
@ -61,44 +48,67 @@ benchmark:
|
||||||
|
|
||||||
unit-testing:
|
unit-testing:
|
||||||
stage: test
|
stage: test
|
||||||
|
cache: &testing_cache_policy
|
||||||
|
<<: *global_cache_policy
|
||||||
|
policy: pull
|
||||||
|
|
||||||
services:
|
services:
|
||||||
- name: lainsoykaf/postgres-with-rum
|
- name: postgres:9.6
|
||||||
alias: postgres
|
alias: postgres
|
||||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||||
script:
|
script:
|
||||||
- mix deps.get
|
- mix deps.get
|
||||||
- mix ecto.create
|
- mix ecto.create
|
||||||
- mix ecto.migrate
|
- mix ecto.migrate
|
||||||
- mix coveralls --trace --preload-modules
|
- mix coveralls --preload-modules
|
||||||
|
|
||||||
|
federated-testing:
|
||||||
|
stage: test
|
||||||
|
cache: *testing_cache_policy
|
||||||
|
services:
|
||||||
|
- name: minibikini/postgres-with-rum:12
|
||||||
|
alias: postgres
|
||||||
|
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||||
|
script:
|
||||||
|
- mix deps.get
|
||||||
|
- mix ecto.create
|
||||||
|
- mix ecto.migrate
|
||||||
|
- epmd -daemon
|
||||||
|
- mix test --trace --only federated
|
||||||
|
|
||||||
unit-testing-rum:
|
unit-testing-rum:
|
||||||
stage: test
|
stage: test
|
||||||
|
cache: *testing_cache_policy
|
||||||
services:
|
services:
|
||||||
- name: lainsoykaf/postgres-with-rum
|
- name: minibikini/postgres-with-rum:12
|
||||||
alias: postgres
|
alias: postgres
|
||||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||||
variables:
|
variables:
|
||||||
|
<<: *global_variables
|
||||||
RUM_ENABLED: "true"
|
RUM_ENABLED: "true"
|
||||||
script:
|
script:
|
||||||
- mix deps.get
|
- mix deps.get
|
||||||
- mix ecto.create
|
- mix ecto.create
|
||||||
- mix ecto.migrate
|
- mix ecto.migrate
|
||||||
- "mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/"
|
- "mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/"
|
||||||
- mix test --trace --preload-modules
|
- mix test --preload-modules
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
stage: test
|
stage: test
|
||||||
|
cache: *testing_cache_policy
|
||||||
script:
|
script:
|
||||||
- mix format --check-formatted
|
- mix format --check-formatted
|
||||||
|
|
||||||
analysis:
|
analysis:
|
||||||
stage: test
|
stage: test
|
||||||
|
cache: *testing_cache_policy
|
||||||
script:
|
script:
|
||||||
- mix deps.get
|
- mix deps.get
|
||||||
- mix credo --strict --only=warnings,todo,fixme,consistency,readability
|
- mix credo --strict --only=warnings,todo,fixme,consistency,readability
|
||||||
|
|
||||||
docs-deploy:
|
docs-deploy:
|
||||||
stage: deploy
|
stage: deploy
|
||||||
|
cache: *testing_cache_policy
|
||||||
image: alpine:latest
|
image: alpine:latest
|
||||||
only:
|
only:
|
||||||
- stable@pleroma/pleroma
|
- stable@pleroma/pleroma
|
||||||
|
|
@ -129,6 +139,7 @@ review_app:
|
||||||
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
|
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
|
||||||
- ssh-keyscan -H "pleroma.online" >> ~/.ssh/known_hosts
|
- ssh-keyscan -H "pleroma.online" >> ~/.ssh/known_hosts
|
||||||
- (ssh -t dokku@pleroma.online -- apps:create "$CI_ENVIRONMENT_SLUG") || true
|
- (ssh -t dokku@pleroma.online -- apps:create "$CI_ENVIRONMENT_SLUG") || true
|
||||||
|
- (ssh -t dokku@pleroma.online -- git:set "$CI_ENVIRONMENT_SLUG" keep-git-dir true) || true
|
||||||
- ssh -t dokku@pleroma.online -- config:set "$CI_ENVIRONMENT_SLUG" APP_NAME="$CI_ENVIRONMENT_SLUG" APP_HOST="$CI_ENVIRONMENT_SLUG.pleroma.online" MIX_ENV=dokku
|
- ssh -t dokku@pleroma.online -- config:set "$CI_ENVIRONMENT_SLUG" APP_NAME="$CI_ENVIRONMENT_SLUG" APP_HOST="$CI_ENVIRONMENT_SLUG.pleroma.online" MIX_ENV=dokku
|
||||||
- (ssh -t dokku@pleroma.online -- postgres:create $(echo $CI_ENVIRONMENT_SLUG | sed -e 's/-/_/g')_db) || true
|
- (ssh -t dokku@pleroma.online -- postgres:create $(echo $CI_ENVIRONMENT_SLUG | sed -e 's/-/_/g')_db) || true
|
||||||
- (ssh -t dokku@pleroma.online -- postgres:link $(echo $CI_ENVIRONMENT_SLUG | sed -e 's/-/_/g')_db "$CI_ENVIRONMENT_SLUG") || true
|
- (ssh -t dokku@pleroma.online -- postgres:link $(echo $CI_ENVIRONMENT_SLUG | sed -e 's/-/_/g')_db "$CI_ENVIRONMENT_SLUG") || true
|
||||||
|
|
@ -255,3 +266,66 @@ arm64-musl:
|
||||||
variables: *release-variables
|
variables: *release-variables
|
||||||
before_script: *before-release-musl
|
before_script: *before-release-musl
|
||||||
script: *release
|
script: *release
|
||||||
|
|
||||||
|
docker:
|
||||||
|
stage: docker
|
||||||
|
image: docker:latest
|
||||||
|
cache: {}
|
||||||
|
dependencies: []
|
||||||
|
variables: &docker-variables
|
||||||
|
DOCKER_DRIVER: overlay2
|
||||||
|
DOCKER_HOST: unix:///var/run/docker.sock
|
||||||
|
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
|
||||||
|
IMAGE_TAG_SLUG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
|
||||||
|
IMAGE_TAG_LATEST: $CI_REGISTRY_IMAGE:latest
|
||||||
|
IMAGE_TAG_LATEST_STABLE: $CI_REGISTRY_IMAGE:latest-stable
|
||||||
|
before_script: &before-docker
|
||||||
|
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||||
|
- docker pull $IMAGE_TAG_SLUG || true
|
||||||
|
- export CI_JOB_TIMESTAMP=$(date --utc -Iseconds)
|
||||||
|
- export CI_VCS_REF=$CI_COMMIT_SHORT_SHA
|
||||||
|
allow_failure: true
|
||||||
|
script:
|
||||||
|
- docker build --cache-from $IMAGE_TAG_SLUG --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP -t $IMAGE_TAG -t $IMAGE_TAG_SLUG -t $IMAGE_TAG_LATEST .
|
||||||
|
- docker push $IMAGE_TAG
|
||||||
|
- docker push $IMAGE_TAG_SLUG
|
||||||
|
- docker push $IMAGE_TAG_LATEST
|
||||||
|
tags:
|
||||||
|
- dind
|
||||||
|
only:
|
||||||
|
- develop@pleroma/pleroma
|
||||||
|
|
||||||
|
docker-stable:
|
||||||
|
stage: docker
|
||||||
|
image: docker:latest
|
||||||
|
cache: {}
|
||||||
|
dependencies: []
|
||||||
|
variables: *docker-variables
|
||||||
|
before_script: *before-docker
|
||||||
|
allow_failure: true
|
||||||
|
script:
|
||||||
|
- docker build --cache-from $IMAGE_TAG_SLUG --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP -t $IMAGE_TAG -t $IMAGE_TAG_SLUG -t $IMAGE_TAG_LATEST_STABLE .
|
||||||
|
- docker push $IMAGE_TAG
|
||||||
|
- docker push $IMAGE_TAG_SLUG
|
||||||
|
- docker push $IMAGE_TAG_LATEST_STABLE
|
||||||
|
tags:
|
||||||
|
- dind
|
||||||
|
only:
|
||||||
|
- stable@pleroma/pleroma
|
||||||
|
|
||||||
|
docker-release:
|
||||||
|
stage: docker
|
||||||
|
image: docker:latest
|
||||||
|
cache: {}
|
||||||
|
dependencies: []
|
||||||
|
variables: *docker-variables
|
||||||
|
before_script: *before-docker
|
||||||
|
allow_failure: true
|
||||||
|
script:
|
||||||
|
- docker build --cache-from $IMAGE_TAG_SLUG --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP -t $IMAGE_TAG -t $IMAGE_TAG_SLUG .
|
||||||
|
- docker push $IMAGE_TAG
|
||||||
|
- docker push $IMAGE_TAG_SLUG
|
||||||
|
tags:
|
||||||
|
- dind
|
||||||
|
only:
|
||||||
|
- /^release/.*$/@pleroma/pleroma
|
||||||
|
|
|
||||||
108
CHANGELOG.md
108
CHANGELOG.md
|
|
@ -7,37 +7,67 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
### Removed
|
### Removed
|
||||||
- **Breaking**: Removed 1.0+ deprecated configurations `Pleroma.Upload, :strip_exif` and `:instance, :dedupe_media`
|
- **Breaking**: Removed 1.0+ deprecated configurations `Pleroma.Upload, :strip_exif` and `:instance, :dedupe_media`
|
||||||
- **Breaking**: OStatus protocol support
|
- **Breaking**: OStatus protocol support
|
||||||
|
- **Breaking**: MDII uploader
|
||||||
|
- **Breaking**: Using third party engines for user recommendation
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
- **Breaking:** Pleroma won't start if it detects unapplied migrations
|
||||||
|
- **Breaking:** attachments are removed along with statuses when there are no other references to it
|
||||||
- **Breaking:** Elixir >=1.8 is now required (was >= 1.7)
|
- **Breaking:** Elixir >=1.8 is now required (was >= 1.7)
|
||||||
|
- **Breaking:** attachment links (`config :pleroma, :instance, no_attachment_links` and `config :pleroma, Pleroma.Upload, link_name`) disabled by default
|
||||||
|
- **Breaking:** OAuth: defaulted `[:auth, :enforce_oauth_admin_scope_usage]` setting to `true` which demands `admin` OAuth scope to perform admin actions (in addition to `is_admin` flag on User); make sure to use bundled or newer versions of AdminFE & PleromaFE to access admin / moderator features.
|
||||||
|
- **Breaking:** Dynamic configuration has been rearchitected. The `:pleroma, :instance, dynamic_configuration` setting has been replaced with `config :pleroma, configurable_from_database`. Please backup your configuration to a file and run the migration task to ensure consistency with the new schema.
|
||||||
- Replaced [pleroma_job_queue](https://git.pleroma.social/pleroma/pleroma_job_queue) and `Pleroma.Web.Federator.RetryQueue` with [Oban](https://github.com/sorentwo/oban) (see [`docs/config.md`](docs/config.md) on migrating customized worker / retry settings)
|
- Replaced [pleroma_job_queue](https://git.pleroma.social/pleroma/pleroma_job_queue) and `Pleroma.Web.Federator.RetryQueue` with [Oban](https://github.com/sorentwo/oban) (see [`docs/config.md`](docs/config.md) on migrating customized worker / retry settings)
|
||||||
- Introduced [quantum](https://github.com/quantum-elixir/quantum-core) job scheduler
|
- Introduced [quantum](https://github.com/quantum-elixir/quantum-core) job scheduler
|
||||||
- Enabled `:instance, extended_nickname_format` in the default config
|
- Enabled `:instance, extended_nickname_format` in the default config
|
||||||
- Add `rel="ugc"` to all links in statuses, to prevent SEO spam
|
- Add `rel="ugc"` to all links in statuses, to prevent SEO spam
|
||||||
- Extract RSS functionality from OStatus
|
- Extract RSS functionality from OStatus
|
||||||
- MRF (Simple Policy): Also use `:accept`/`:reject` on the actors rather than only their activities
|
- MRF (Simple Policy): Also use `:accept`/`:reject` on the actors rather than only their activities
|
||||||
|
- OStatus: Extract RSS functionality
|
||||||
|
- Deprecated `User.Info` embedded schema (fields moved to `User`)
|
||||||
|
- Store status data inside Flag activity
|
||||||
|
- Deprecated (reorganized as `UserRelationship` entity) User fields with user AP IDs (`blocks`, `mutes`, `muted_reblogs`, `muted_notifications`, `subscribers`).
|
||||||
|
- Logger: default log level changed from `warn` to `info`.
|
||||||
|
- Config mix task `migrate_to_db` truncates `config` table before migrating the config file.
|
||||||
<details>
|
<details>
|
||||||
<summary>API Changes</summary>
|
<summary>API Changes</summary>
|
||||||
|
|
||||||
|
- **Breaking** Admin API: `PATCH /api/pleroma/admin/users/:nickname/force_password_reset` is now `PATCH /api/pleroma/admin/users/force_password_reset` (accepts `nicknames` array in the request body)
|
||||||
- **Breaking:** Admin API: Return link alongside with token on password reset
|
- **Breaking:** Admin API: Return link alongside with token on password reset
|
||||||
|
- **Breaking:** Admin API: `PUT /api/pleroma/admin/reports/:id` is now `PATCH /api/pleroma/admin/reports`, see admin_api.md for details
|
||||||
- **Breaking:** `/api/pleroma/admin/users/invite_token` now uses `POST`, changed accepted params and returns full invite in json instead of only token string.
|
- **Breaking:** `/api/pleroma/admin/users/invite_token` now uses `POST`, changed accepted params and returns full invite in json instead of only token string.
|
||||||
|
- **Breaking** replying to reports is now "report notes", enpoint changed from `POST /api/pleroma/admin/reports/:id/respond` to `POST /api/pleroma/admin/reports/:id/notes`
|
||||||
- Admin API: Return `total` when querying for reports
|
- Admin API: Return `total` when querying for reports
|
||||||
- Mastodon API: Return `pleroma.direct_conversation_id` when creating a direct message (`POST /api/v1/statuses`)
|
- Mastodon API: Return `pleroma.direct_conversation_id` when creating a direct message (`POST /api/v1/statuses`)
|
||||||
- Admin API: Return link alongside with token on password reset
|
- Admin API: Return link alongside with token on password reset
|
||||||
|
- Admin API: Support authentication via `x-admin-token` HTTP header
|
||||||
- Mastodon API: Add `pleroma.direct_conversation_id` to the status endpoint (`GET /api/v1/statuses/:id`)
|
- Mastodon API: Add `pleroma.direct_conversation_id` to the status endpoint (`GET /api/v1/statuses/:id`)
|
||||||
- Mastodon API: `pleroma.thread_muted` to the Status entity
|
- Mastodon API: `pleroma.thread_muted` to the Status entity
|
||||||
- Mastodon API: Mark the direct conversation as read for the author when they send a new direct message
|
- Mastodon API: Mark the direct conversation as read for the author when they send a new direct message
|
||||||
|
- Mastodon API, streaming: Add `pleroma.direct_conversation_id` to the `conversation` stream event payload.
|
||||||
|
- Admin API: Render whole status in grouped reports
|
||||||
|
- Mastodon API: User timelines will now respect blocks, unless you are getting the user timeline of somebody you blocked (which would be empty otherwise).
|
||||||
|
- Mastodon API: Favoriting / Repeating a post multiple times will now return the identical response every time. Before, executing that action twice would return an error ("already favorited") on the second try.
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
- `:chat_limit` option to limit chat characters.
|
||||||
- Refreshing poll results for remote polls
|
- Refreshing poll results for remote polls
|
||||||
- Authentication: Added rate limit for password-authorized actions / login existence checks
|
- Authentication: Added rate limit for password-authorized actions / login existence checks
|
||||||
|
- Static Frontend: Add the ability to render user profiles and notices server-side without requiring JS app.
|
||||||
- Mix task to re-count statuses for all users (`mix pleroma.count_statuses`)
|
- Mix task to re-count statuses for all users (`mix pleroma.count_statuses`)
|
||||||
|
- Mix task to list all users (`mix pleroma.user list`)
|
||||||
- Support for `X-Forwarded-For` and similar HTTP headers which used by reverse proxies to pass a real user IP address to the backend. Must not be enabled unless your instance is behind at least one reverse proxy (such as Nginx, Apache HTTPD or Varnish Cache).
|
- Support for `X-Forwarded-For` and similar HTTP headers which used by reverse proxies to pass a real user IP address to the backend. Must not be enabled unless your instance is behind at least one reverse proxy (such as Nginx, Apache HTTPD or Varnish Cache).
|
||||||
|
- MRF: New module which handles incoming posts based on their age. By default, all incoming posts that are older than 2 days will be unlisted and not shown to their followers.
|
||||||
|
- User notification settings: Add `privacy_option` option.
|
||||||
|
- Support for custom Elixir modules (such as MRF policies)
|
||||||
|
- User settings: Add _This account is a_ option.
|
||||||
|
- OAuth: admin scopes support (relevant setting: `[:auth, :enforce_oauth_admin_scope_usage]`).
|
||||||
<details>
|
<details>
|
||||||
<summary>API Changes</summary>
|
<summary>API Changes</summary>
|
||||||
|
|
||||||
- Job queue stats to the healthcheck page
|
- Job queue stats to the healthcheck page
|
||||||
|
- Admin API: Add ability to fetch reports, grouped by status `GET /api/pleroma/admin/grouped_reports`
|
||||||
- Admin API: Add ability to require password reset
|
- Admin API: Add ability to require password reset
|
||||||
- Mastodon API: Account entities now include `follow_requests_count` (planned Mastodon 3.x addition)
|
- Mastodon API: Account entities now include `follow_requests_count` (planned Mastodon 3.x addition)
|
||||||
- Pleroma API: `GET /api/v1/pleroma/accounts/:id/scrobbles` to get a list of recently scrobbled items
|
- Pleroma API: `GET /api/v1/pleroma/accounts/:id/scrobbles` to get a list of recently scrobbled items
|
||||||
|
|
@ -46,37 +76,85 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Mastodon API: Add `pleroma.unread_conversation_count` to the Account entity
|
- Mastodon API: Add `pleroma.unread_conversation_count` to the Account entity
|
||||||
- OAuth: support for hierarchical permissions / [Mastodon 2.4.3 OAuth permissions](https://docs.joinmastodon.org/api/permissions/)
|
- OAuth: support for hierarchical permissions / [Mastodon 2.4.3 OAuth permissions](https://docs.joinmastodon.org/api/permissions/)
|
||||||
- Metadata Link: Atom syndication Feed
|
- Metadata Link: Atom syndication Feed
|
||||||
|
- Mix task to re-count statuses for all users (`mix pleroma.count_statuses`)
|
||||||
- Mastodon API: Add `exclude_visibilities` parameter to the timeline and notification endpoints
|
- Mastodon API: Add `exclude_visibilities` parameter to the timeline and notification endpoints
|
||||||
- Admin API: `/users/:nickname/toggle_activation` endpoint is now deprecated in favor of: `/users/activate`, `/users/deactivate`, both accept `nicknames` array
|
- Admin API: `/users/:nickname/toggle_activation` endpoint is now deprecated in favor of: `/users/activate`, `/users/deactivate`, both accept `nicknames` array
|
||||||
- Admin API: `POST/DELETE /api/pleroma/admin/users/:nickname/permission_group/:permission_group` are deprecated in favor of: `POST/DELETE /api/pleroma/admin/users/permission_group/:permission_group` (both accept `nicknames` array), `DELETE /api/pleroma/admin/users` (`nickname` query param or `nickname` sent in JSON body) is deprecated in favor of: `DELETE /api/pleroma/admin/users` (`nicknames` query array param or `nicknames` sent in JSON body).
|
- Admin API: Multiple endpoints now require `nicknames` array, instead of singe `nickname`:
|
||||||
|
- `POST/DELETE /api/pleroma/admin/users/:nickname/permission_group/:permission_group` are deprecated in favor of: `POST/DELETE /api/pleroma/admin/users/permission_group/:permission_group`
|
||||||
|
- `DELETE /api/pleroma/admin/users` (`nickname` query param or `nickname` sent in JSON body) is deprecated in favor of: `DELETE /api/pleroma/admin/users` (`nicknames` query array param or `nicknames` sent in JSON body)
|
||||||
- Admin API: Add `GET /api/pleroma/admin/relay` endpoint - lists all followed relays
|
- Admin API: Add `GET /api/pleroma/admin/relay` endpoint - lists all followed relays
|
||||||
- Pleroma API: `POST /api/v1/pleroma/conversations/read` to mark all conversations as read
|
- Pleroma API: `POST /api/v1/pleroma/conversations/read` to mark all conversations as read
|
||||||
|
- ActivityPub: Support `Move` activities
|
||||||
- Mastodon API: Add `/api/v1/markers` for managing timeline read markers
|
- Mastodon API: Add `/api/v1/markers` for managing timeline read markers
|
||||||
|
- Mastodon API: Add the `recipients` parameter to `GET /api/v1/conversations`
|
||||||
### Changed
|
- Configuration: `feed` option for user atom feed.
|
||||||
- **Breaking:** Elixir >=1.8 is now required (was >= 1.7)
|
- Pleroma API: Add Emoji reactions
|
||||||
- **Breaking:** Admin API: Return link alongside with token on password reset
|
- Admin API: Add `/api/pleroma/admin/instances/:instance/statuses` - lists all statuses from a given instance
|
||||||
- Replaced [pleroma_job_queue](https://git.pleroma.social/pleroma/pleroma_job_queue) and `Pleroma.Web.Federator.RetryQueue` with [Oban](https://github.com/sorentwo/oban) (see [`docs/config.md`](docs/config.md) on migrating customized worker / retry settings)
|
- Admin API: `PATCH /api/pleroma/users/confirm_email` to confirm email for multiple users, `PATCH /api/pleroma/users/resend_confirmation_email` to resend confirmation email for multiple users
|
||||||
- Introduced [quantum](https://github.com/quantum-elixir/quantum-core) job scheduler
|
- ActivityPub: Configurable `type` field of the actors.
|
||||||
- Admin API: Return `total` when querying for reports
|
- Mastodon API: `/api/v1/accounts/:id` has `source/pleroma/actor_type` field.
|
||||||
- Mastodon API: Return `pleroma.direct_conversation_id` when creating a direct message (`POST /api/v1/statuses`)
|
- Mastodon API: `/api/v1/update_credentials` accepts `actor_type` field.
|
||||||
- Admin API: Return link alongside with token on password reset
|
- Captcha: Support native provider
|
||||||
- MRF (Simple Policy): Also use `:accept`/`:reject` on the actors rather than only their activities
|
- Captcha: Enable by default
|
||||||
- OStatus: Extract RSS functionality
|
- Mastodon API: Add support for `account_id` param to filter notifications by the account
|
||||||
- Mastodon API: Add `pleroma.direct_conversation_id` to the status endpoint (`GET /api/v1/statuses/:id`)
|
- Mastodon API: Add `emoji_reactions` property to Statuses
|
||||||
- Mastodon API: Mark the direct conversation as read for the author when they send a new direct message
|
- Mastodon API: Change emoji reaction reply format
|
||||||
|
- Notifications: Added `pleroma:emoji_reaction` notification type
|
||||||
|
- Mastodon API: Change emoji reaction reply format once more
|
||||||
</details>
|
</details>
|
||||||
- Deprecated `User.Info` embedded schema (fields moved to `User`)
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Report emails now include functional links to profiles of remote user accounts
|
- Report emails now include functional links to profiles of remote user accounts
|
||||||
|
- Not being able to log in to some third-party apps when logged in to MastoFE
|
||||||
|
- MRF: `Delete` activities being exempt from MRF policies
|
||||||
|
- OTP releases: Not being able to configure OAuth expired token cleanup interval
|
||||||
|
- OTP releases: Not being able to configure HTML sanitization policy
|
||||||
|
- Favorites timeline now ordered by favorite date instead of post date
|
||||||
<details>
|
<details>
|
||||||
<summary>API Changes</summary>
|
<summary>API Changes</summary>
|
||||||
|
|
||||||
- Mastodon API: Fix private and direct statuses not being filtered out from the public timeline for an authenticated user (`GET /api/v1/timelines/public`)
|
- Mastodon API: Fix private and direct statuses not being filtered out from the public timeline for an authenticated user (`GET /api/v1/timelines/public`)
|
||||||
- Mastodon API: Inability to get some local users by nickname in `/api/v1/accounts/:id_or_nickname`
|
- Mastodon API: Inability to get some local users by nickname in `/api/v1/accounts/:id_or_nickname`
|
||||||
|
- AdminAPI: If some status received reports both in the "new" format and "old" format it was considered reports on two different statuses (in the context of grouped reports)
|
||||||
|
- Admin API: Error when trying to update reports in the "old" format
|
||||||
|
- Mastodon API: Marking a conversation as read (`POST /api/v1/conversations/:id/read`) now no longer brings it to the top in the user's direct conversation list
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
## [1.1.6] - 2019-11-19
|
||||||
|
### Fixed
|
||||||
|
- Not being able to log into to third party apps when the browser is logged into mastofe
|
||||||
|
- Email confirmation not being required even when enabled
|
||||||
|
- Mastodon API: conversations API crashing when one status is malformed
|
||||||
|
|
||||||
|
### Bundled Pleroma-FE Changes
|
||||||
|
#### Added
|
||||||
|
- About page
|
||||||
|
- Meme arrows
|
||||||
|
|
||||||
|
#### Fixed
|
||||||
|
- Image modal not closing unless clicked outside of image
|
||||||
|
- Attachment upload spinner not being centered
|
||||||
|
- Showing follow counters being 0 when they are actually hidden
|
||||||
|
|
||||||
|
## [1.1.5] - 2019-11-09
|
||||||
|
### Fixed
|
||||||
|
- Polls having different numbers in timelines/notifications/poll api endpoints due to cache desyncronization
|
||||||
|
- Pleroma API: OAuth token endpoint not being found when ".json" suffix is appended
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Frontend bundle updated to [044c9ad0](https://git.pleroma.social/pleroma/pleroma-fe/commit/044c9ad0562af059dd961d50961a3880fca9c642)
|
||||||
|
|
||||||
|
## [1.1.4] - 2019-11-01
|
||||||
|
### Fixed
|
||||||
|
- Added a migration that fills up empty user.info fields to prevent breakage after previous unsafe migrations.
|
||||||
|
- Failure to migrate from pre-1.0.0 versions
|
||||||
|
- Mastodon API: Notification stream not including follow notifications
|
||||||
|
|
||||||
|
## [1.1.3] - 2019-10-25
|
||||||
|
### Fixed
|
||||||
|
- Blocked users showing up in notifications collapsed as if they were muted
|
||||||
|
- `pleroma_ctl` not working on Debian's default shell
|
||||||
|
|
||||||
## [1.1.2] - 2019-10-18
|
## [1.1.2] - 2019-10-18
|
||||||
### Fixed
|
### Fixed
|
||||||
- `pleroma_ctl` trying to connect to a running instance when generating the config, which of course doesn't exist.
|
- `pleroma_ctl` trying to connect to a running instance when generating the config, which of course doesn't exist.
|
||||||
|
|
|
||||||
14
Dockerfile
14
Dockerfile
|
|
@ -14,6 +14,20 @@ RUN apk add git gcc g++ musl-dev make &&\
|
||||||
|
|
||||||
FROM alpine:3.9
|
FROM alpine:3.9
|
||||||
|
|
||||||
|
ARG BUILD_DATE
|
||||||
|
ARG VCS_REF
|
||||||
|
|
||||||
|
LABEL maintainer="ops@pleroma.social" \
|
||||||
|
org.opencontainers.image.title="pleroma" \
|
||||||
|
org.opencontainers.image.description="Pleroma for Docker" \
|
||||||
|
org.opencontainers.image.authors="ops@pleroma.social" \
|
||||||
|
org.opencontainers.image.vendor="pleroma.social" \
|
||||||
|
org.opencontainers.image.documentation="https://git.pleroma.social/pleroma/pleroma" \
|
||||||
|
org.opencontainers.image.licenses="AGPL-3.0" \
|
||||||
|
org.opencontainers.image.url="https://pleroma.social" \
|
||||||
|
org.opencontainers.image.revision=$VCS_REF \
|
||||||
|
org.opencontainers.image.created=$BUILD_DATE
|
||||||
|
|
||||||
ARG HOME=/opt/pleroma
|
ARG HOME=/opt/pleroma
|
||||||
ARG DATA=/var/lib/pleroma
|
ARG DATA=/var/lib/pleroma
|
||||||
|
|
||||||
|
|
|
||||||
95
README.md
95
README.md
|
|
@ -1,80 +1,43 @@
|
||||||
# Pleroma
|
<img src="https://git.pleroma.social/pleroma/pleroma/uploads/8cec84f5a084d887339f57deeb8a293e/pleroma-banner-vector-nopad-notext.svg" width="300px" />
|
||||||
|
|
||||||
**Note**: This readme as well as complete documentation is also available at <https://docs-develop.pleroma.social>
|
## About
|
||||||
|
|
||||||
## About Pleroma
|
Pleroma is a microblogging server software that can federate (= exchange messages with) other servers that support ActivityPub. What that means is that you can host a server for yourself or your friends and stay in control of your online identity, but still exchange messages with people on larger servers. Pleroma will federate with all servers that implement ActivityPub, like Friendica, GNU Social, Hubzilla, Mastodon, Misskey, Peertube, and Pixelfed.
|
||||||
|
|
||||||
Pleroma is a microblogging server software that can federate (= exchange messages with) other servers that support the same federation standards (OStatus and ActivityPub). What that means is that you can host a server for yourself or your friends and stay in control of your online identity, but still exchange messages with people on larger servers. Pleroma will federate with all servers that implement either OStatus or ActivityPub, like Friendica, GNU Social, Hubzilla, Mastodon, Misskey, Peertube, and Pixelfed.
|
Pleroma is written in Elixir and uses PostgresSQL for data storage. It's efficient enough to be ran on low-power devices like Raspberry Pi (though we wouldn't recommend storing the database on the internal SD card ;) but can scale well when ran on more powerful hardware (albeit only single-node for now).
|
||||||
|
|
||||||
Pleroma is written in Elixir, high-performance and can run on small devices like a Raspberry Pi.
|
For clients it supports the [Mastodon client API](https://docs.joinmastodon.org/api/guidelines/) with Pleroma extensions (see the API section on <https://docs-develop.pleroma.social>).
|
||||||
|
|
||||||
For clients it supports the [Mastodon client API](https://docs.joinmastodon.org/api/guidelines/) with Pleroma extensions (see "Pleroma's APIs and Mastodon API extensions" section on <https://docs-develop.pleroma.social>).
|
- [Client Applications for Pleroma](https://docs-develop.pleroma.social/backend/clients/)
|
||||||
|
|
||||||
- [Client Applications for Pleroma](https://docs-develop.pleroma.social/clients.html)
|
|
||||||
|
|
||||||
If you want to run your own server, feel free to contact us at @lain@pleroma.soykaf.com or in our dev chat at #pleroma on freenode or via matrix at <https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org>.
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
**Note:** The guide below may be outdated and in most cases shouldn't be used. Instead check out our [wiki](https://docs.pleroma.social) for platform-specific installation instructions, most likely [Installing on Linux using OTP releases](https://docs.pleroma.social/otp_en.html) is the guide you need.
|
|
||||||
|
### OTP releases (Recommended)
|
||||||
|
If you are running Linux (glibc or musl) on x86/arm, the recommended way to install Pleroma is by using OTP releases. OTP releases are as close as you can get to binary releases with Erlang/Elixir. The release is self-contained, and provides everything needed to boot it. The installation instructions are available [here](https://docs-develop.pleroma.social/backend/installation/otp_en/).
|
||||||
|
|
||||||
|
### From Source
|
||||||
|
If your platform is not supported, or you just want to be able to edit the source code easily, you may install Pleroma from source.
|
||||||
|
|
||||||
|
- [Debian-based](https://docs-develop.pleroma.social/backend/installation/debian_based_en/)
|
||||||
|
- [Debian-based (jp)](https://docs-develop.pleroma.social/backend/installation/debian_based_jp/)
|
||||||
|
- [Alpine Linux](https://docs-develop.pleroma.social/backend/installation/alpine_linux_en/)
|
||||||
|
- [Arch Linux](https://docs-develop.pleroma.social/backend/installation/arch_linux_en/)
|
||||||
|
- [Gentoo Linux](https://docs-develop.pleroma.social/backend/installation/gentoo_en/)
|
||||||
|
- [NetBSD](https://docs-develop.pleroma.social/backend/installation/netbsd_en/)
|
||||||
|
- [OpenBSD](https://docs-develop.pleroma.social/backend/installation/openbsd_en/)
|
||||||
|
- [OpenBSD (fi)](https://docs-develop.pleroma.social/backend/installation/openbsd_fi/)
|
||||||
|
- [CentOS 7](https://docs-develop.pleroma.social/backend/installation/centos7_en/)
|
||||||
|
|
||||||
### OS/Distro packages
|
### OS/Distro packages
|
||||||
Currently Pleroma is not packaged by any OS/Distros, but feel free to reach out to us at [#pleroma-dev on freenode](https://webchat.freenode.net/?channels=%23pleroma-dev) or via matrix at <https://matrix.heldscal.la/#/room/#freenode_#pleroma-dev:matrix.org> for assistance. If you want to change default options in your Pleroma package, please **discuss it with us first**.
|
Currently Pleroma is not packaged by any OS/Distros, but if you want to package it for one, we can guide you through the process on our [community channels](#community-channels). If you want to change default options in your Pleroma package, please **discuss it with us first**.
|
||||||
|
|
||||||
### Docker
|
### Docker
|
||||||
While we don’t provide docker files, other people have written very good ones. Take a look at <https://github.com/angristan/docker-pleroma> or <https://glitch.sh/sn0w/pleroma-docker>.
|
While we don’t provide docker files, other people have written very good ones. Take a look at <https://github.com/angristan/docker-pleroma> or <https://glitch.sh/sn0w/pleroma-docker>.
|
||||||
|
|
||||||
### Dependencies
|
## Documentation
|
||||||
|
- Latest Released revision: <https://docs.pleroma.social>
|
||||||
|
- Latest Git revision: <https://docs-develop.pleroma.social>
|
||||||
|
|
||||||
* Postgresql version 9.6 or newer, including the contrib modules
|
## Community Channels
|
||||||
* Elixir version 1.7 or newer. If your distribution only has an old version available, check [Elixir’s install page](https://elixir-lang.org/install.html) or use a tool like [asdf](https://github.com/asdf-vm/asdf).
|
* IRC: **#pleroma** and **#pleroma-dev** on freenode, webchat is available at <https://irc.pleroma.social>
|
||||||
* Build-essential tools
|
* Matrix: <https://matrix.to/#/#freenode_#pleroma:matrix.org> and <https://matrix.to/#/#freenode_#pleroma-dev:matrix.org>
|
||||||
|
|
||||||
### Configuration
|
|
||||||
|
|
||||||
* Run `mix deps.get` to install elixir dependencies.
|
|
||||||
* Run `mix pleroma.instance gen`. This will ask you questions about your instance and generate a configuration file in `config/generated_config.exs`. Check that and copy it to either `config/dev.secret.exs` or `config/prod.secret.exs`. It will also create a `config/setup_db.psql`, which you should run as the PostgreSQL superuser (i.e., `sudo -u postgres psql -f config/setup_db.psql`). It will create the database, user, and password you gave `mix pleroma.gen.instance` earlier, as well as set up the necessary extensions in the database. PostgreSQL superuser privileges are only needed for this step.
|
|
||||||
* For these next steps, the default will be to run pleroma using the dev configuration file, `config/dev.secret.exs`. To run them using the prod config file, prefix each command at the shell with `MIX_ENV=prod`. For example: `MIX_ENV=prod mix phx.server`. Documentation for the config can be found at [`docs/config.md`](docs/config.md) in the repository, or at the "Configuration" page on <https://docs-develop.pleroma.social/config.html>
|
|
||||||
* Run `mix ecto.migrate` to run the database migrations. You will have to do this again after certain updates.
|
|
||||||
* You can check if your instance is configured correctly by running it with `mix phx.server` and checking the instance info endpoint at `/api/v1/instance`. If it shows your uri, name and email correctly, you are configured correctly. If it shows something like `localhost:4000`, your configuration is probably wrong, unless you are running a local development setup.
|
|
||||||
* The common and convenient way for adding HTTPS is by using Nginx as a reverse proxy. You can look at example Nginx configuration in `installation/pleroma.nginx`. If you need TLS/SSL certificates for HTTPS, you can look get some for free with letsencrypt: <https://letsencrypt.org/>. The simplest way to obtain and install a certificate is to use [Certbot.](https://certbot.eff.org) Depending on your specific setup, certbot may be able to get a certificate and configure your web server automatically.
|
|
||||||
|
|
||||||
## Running
|
|
||||||
|
|
||||||
* By default, it listens on port 4000 (TCP), so you can access it on <http://localhost:4000/> (if you are on the same machine). In case of an error it will restart automatically.
|
|
||||||
|
|
||||||
### Frontends
|
|
||||||
|
|
||||||
Pleroma comes with two frontends. The first one, Pleroma FE, can be reached by normally visiting the site. The other one, based on the Mastodon project, can be found by visiting the /web path of your site.
|
|
||||||
|
|
||||||
### As systemd service (with provided .service file)
|
|
||||||
|
|
||||||
Example .service file can be found in `installation/pleroma.service`. Copy this to `/etc/systemd/system/`. Running `systemctl enable --now pleroma.service` will run Pleroma and enable startup on boot. Logs can be watched by using `journalctl -fu pleroma.service`.
|
|
||||||
|
|
||||||
### As OpenRC service (with provided RC file)
|
|
||||||
|
|
||||||
Copy `installation/init.d/pleroma` to `/etc/init.d/pleroma`. You can add it to the services ran by default with: `rc-update add pleroma`
|
|
||||||
|
|
||||||
### Standalone/run by other means
|
|
||||||
|
|
||||||
Run `mix phx.server` in repository’s root, it will output log into stdout/stderr.
|
|
||||||
|
|
||||||
### Using an upstream proxy for federation
|
|
||||||
|
|
||||||
Add the following to your `dev.secret.exs` or `prod.secret.exs` if you want to proxify all http requests that Pleroma makes to an upstream proxy server:
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
config :pleroma, :http,
|
|
||||||
proxy_url: "127.0.0.1:8123"
|
|
||||||
```
|
|
||||||
|
|
||||||
This is useful for running Pleroma inside Tor or I2P.
|
|
||||||
|
|
||||||
## Customization and contribution
|
|
||||||
|
|
||||||
The [Pleroma Documentation](https://docs-develop.pleroma.social) offers manuals and guides on how to further customize your instance to your liking and how you can contribute to the project.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### No incoming federation
|
|
||||||
|
|
||||||
Check that you correctly forward the `host` header to the backend. It is needed to validate signatures.
|
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,36 @@ defmodule Pleroma.LoadTesting.Fetcher do
|
||||||
for: user,
|
for: user,
|
||||||
as: :activity
|
as: :activity
|
||||||
})
|
})
|
||||||
end
|
end,
|
||||||
|
"Rendering favorites timeline" => fn ->
|
||||||
|
conn = Phoenix.ConnTest.build_conn(:get, "http://localhost:4001/api/v1/favourites", nil)
|
||||||
|
Pleroma.Web.MastodonAPI.StatusController.favourites(
|
||||||
|
%Plug.Conn{conn |
|
||||||
|
assigns: %{user: user},
|
||||||
|
query_params: %{"limit" => "0"},
|
||||||
|
body_params: %{},
|
||||||
|
cookies: %{},
|
||||||
|
params: %{},
|
||||||
|
path_params: %{},
|
||||||
|
private: %{
|
||||||
|
Pleroma.Web.Router => {[], %{}},
|
||||||
|
phoenix_router: Pleroma.Web.Router,
|
||||||
|
phoenix_action: :favourites,
|
||||||
|
phoenix_controller: Pleroma.Web.MastodonAPI.StatusController,
|
||||||
|
phoenix_endpoint: Pleroma.Web.Endpoint,
|
||||||
|
phoenix_format: "json",
|
||||||
|
phoenix_layout: {Pleroma.Web.LayoutView, "app.html"},
|
||||||
|
phoenix_recycled: true,
|
||||||
|
|
||||||
|
phoenix_view: Pleroma.Web.MastodonAPI.StatusView,
|
||||||
|
plug_session: %{"user_id" => user.id},
|
||||||
|
plug_session_fetch: :done,
|
||||||
|
plug_session_info: :write,
|
||||||
|
plug_skip_csrf_protection: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
%{})
|
||||||
|
end,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,24 @@ defmodule Pleroma.LoadTesting.Generator do
|
||||||
use Pleroma.LoadTesting.Helper
|
use Pleroma.LoadTesting.Helper
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
def generate_like_activities(user, posts) do
|
||||||
|
count_likes = Kernel.trunc(length(posts) / 4)
|
||||||
|
IO.puts("Starting generating #{count_likes} like activities...")
|
||||||
|
|
||||||
|
{time, _} =
|
||||||
|
:timer.tc(fn ->
|
||||||
|
Task.async_stream(
|
||||||
|
Enum.take_random(posts, count_likes),
|
||||||
|
fn post -> {:ok, _, _} = CommonAPI.favorite(post.id, user) end,
|
||||||
|
max_concurrency: 10,
|
||||||
|
timeout: 30_000
|
||||||
|
)
|
||||||
|
|> Stream.run()
|
||||||
|
end)
|
||||||
|
|
||||||
|
IO.puts("Inserting like activities take #{to_sec(time)} sec.\n")
|
||||||
|
end
|
||||||
|
|
||||||
def generate_users(opts) do
|
def generate_users(opts) do
|
||||||
IO.puts("Starting generating #{opts[:users_max]} users...")
|
IO.puts("Starting generating #{opts[:users_max]} users...")
|
||||||
{time, _} = :timer.tc(fn -> do_generate_users(opts) end)
|
{time, _} = :timer.tc(fn -> do_generate_users(opts) end)
|
||||||
|
|
@ -31,7 +49,6 @@ defmodule Pleroma.LoadTesting.Generator do
|
||||||
password_hash:
|
password_hash:
|
||||||
"$pbkdf2-sha512$160000$bU.OSFI7H/yqWb5DPEqyjw$uKp/2rmXw12QqnRRTqTtuk2DTwZfF8VR4MYW2xMeIlqPR/UX1nT1CEKVUx2CowFMZ5JON8aDvURrZpJjSgqXrg",
|
"$pbkdf2-sha512$160000$bU.OSFI7H/yqWb5DPEqyjw$uKp/2rmXw12QqnRRTqTtuk2DTwZfF8VR4MYW2xMeIlqPR/UX1nT1CEKVUx2CowFMZ5JON8aDvURrZpJjSgqXrg",
|
||||||
bio: "Tester Number #{i}",
|
bio: "Tester Number #{i}",
|
||||||
info: %{},
|
|
||||||
local: remote
|
local: remote
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,6 +142,48 @@ defmodule Pleroma.LoadTesting.Generator do
|
||||||
CommonAPI.post(Enum.random(users), post)
|
CommonAPI.post(Enum.random(users), post)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def generate_power_intervals(opts \\ []) do
|
||||||
|
count = Keyword.get(opts, :count, 20)
|
||||||
|
power = Keyword.get(opts, :power, 2)
|
||||||
|
IO.puts("Generating #{count} intervals for a power #{power} series...")
|
||||||
|
counts = Enum.map(1..count, fn n -> :math.pow(n, power) end)
|
||||||
|
sum = Enum.sum(counts)
|
||||||
|
|
||||||
|
densities =
|
||||||
|
Enum.map(counts, fn c ->
|
||||||
|
c / sum
|
||||||
|
end)
|
||||||
|
|
||||||
|
densities
|
||||||
|
|> Enum.reduce(0, fn density, acc ->
|
||||||
|
if acc == 0 do
|
||||||
|
[{0, density}]
|
||||||
|
else
|
||||||
|
[{_, lower} | _] = acc
|
||||||
|
[{lower, lower + density} | acc]
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|> Enum.reverse()
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate_tagged_activities(opts \\ []) do
|
||||||
|
tag_count = Keyword.get(opts, :tag_count, 20)
|
||||||
|
users = Keyword.get(opts, :users, Repo.all(User))
|
||||||
|
activity_count = Keyword.get(opts, :count, 200_000)
|
||||||
|
|
||||||
|
intervals = generate_power_intervals(count: tag_count)
|
||||||
|
|
||||||
|
IO.puts(
|
||||||
|
"Generating #{activity_count} activities using #{tag_count} different tags of format `tag_n`, starting at tag_0"
|
||||||
|
)
|
||||||
|
|
||||||
|
Enum.each(1..activity_count, fn _ ->
|
||||||
|
random = :rand.uniform()
|
||||||
|
i = Enum.find_index(intervals, fn {lower, upper} -> lower <= random && upper > random end)
|
||||||
|
CommonAPI.post(Enum.random(users), %{"status" => "a post with the tag #tag_#{i}"})
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
defp do_generate_activity_with_mention(user, users) do
|
defp do_generate_activity_with_mention(user, users) do
|
||||||
mentions_cnt = Enum.random([2, 3, 4, 5])
|
mentions_cnt = Enum.random([2, 3, 4, 5])
|
||||||
with_user = Enum.random([true, false])
|
with_user = Enum.random([true, false])
|
||||||
|
|
|
||||||
87
benchmarks/mix/tasks/pleroma/benchmarks/tags.ex
Normal file
87
benchmarks/mix/tasks/pleroma/benchmarks/tags.ex
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
defmodule Mix.Tasks.Pleroma.Benchmarks.Tags do
|
||||||
|
use Mix.Task
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.LoadTesting.Generator
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
def run(_args) do
|
||||||
|
Mix.Pleroma.start_pleroma()
|
||||||
|
activities_count = Repo.aggregate(from(a in Pleroma.Activity), :count, :id)
|
||||||
|
|
||||||
|
if activities_count == 0 do
|
||||||
|
IO.puts("Did not find any activities, cleaning and generating")
|
||||||
|
clean_tables()
|
||||||
|
Generator.generate_users(users_max: 10)
|
||||||
|
Generator.generate_tagged_activities()
|
||||||
|
else
|
||||||
|
IO.puts("Found #{activities_count} activities, won't generate new ones")
|
||||||
|
end
|
||||||
|
|
||||||
|
tags = Enum.map(0..20, fn i -> {"For #tag_#{i}", "tag_#{i}"} end)
|
||||||
|
|
||||||
|
Enum.each(tags, fn {_, tag} ->
|
||||||
|
query =
|
||||||
|
from(o in Pleroma.Object,
|
||||||
|
where: fragment("(?)->'tag' \\? (?)", o.data, ^tag)
|
||||||
|
)
|
||||||
|
|
||||||
|
count = Repo.aggregate(query, :count, :id)
|
||||||
|
IO.puts("Database contains #{count} posts tagged with #{tag}")
|
||||||
|
end)
|
||||||
|
|
||||||
|
user = Repo.all(Pleroma.User) |> List.first()
|
||||||
|
|
||||||
|
Benchee.run(
|
||||||
|
%{
|
||||||
|
"Hashtag fetching, any" => fn tags ->
|
||||||
|
Pleroma.Web.MastodonAPI.TimelineController.hashtag_fetching(
|
||||||
|
%{
|
||||||
|
"any" => tags
|
||||||
|
},
|
||||||
|
user,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
end,
|
||||||
|
# Will always return zero results because no overlapping hashtags are generated.
|
||||||
|
"Hashtag fetching, all" => fn tags ->
|
||||||
|
Pleroma.Web.MastodonAPI.TimelineController.hashtag_fetching(
|
||||||
|
%{
|
||||||
|
"all" => tags
|
||||||
|
},
|
||||||
|
user,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
end
|
||||||
|
},
|
||||||
|
inputs:
|
||||||
|
tags
|
||||||
|
|> Enum.map(fn {_, v} -> v end)
|
||||||
|
|> Enum.chunk_every(2)
|
||||||
|
|> Enum.map(fn tags -> {"For #{inspect(tags)}", tags} end),
|
||||||
|
time: 5
|
||||||
|
)
|
||||||
|
|
||||||
|
Benchee.run(
|
||||||
|
%{
|
||||||
|
"Hashtag fetching" => fn tag ->
|
||||||
|
Pleroma.Web.MastodonAPI.TimelineController.hashtag_fetching(
|
||||||
|
%{
|
||||||
|
"tag" => tag
|
||||||
|
},
|
||||||
|
user,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
end
|
||||||
|
},
|
||||||
|
inputs: tags,
|
||||||
|
time: 5
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp clean_tables do
|
||||||
|
IO.puts("Deleting old data...\n")
|
||||||
|
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE users CASCADE;")
|
||||||
|
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE activities CASCADE;")
|
||||||
|
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE objects CASCADE;")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -100,6 +100,10 @@ defmodule Mix.Tasks.Pleroma.LoadTesting do
|
||||||
|
|
||||||
generate_remote_activities(user, remote_users)
|
generate_remote_activities(user, remote_users)
|
||||||
|
|
||||||
|
generate_like_activities(
|
||||||
|
user, Pleroma.Repo.all(Pleroma.Activity.Queries.by_type("Create"))
|
||||||
|
)
|
||||||
|
|
||||||
generate_dms(user, users, opts)
|
generate_dms(user, users, opts)
|
||||||
|
|
||||||
{:ok, activity} = generate_long_thread(user, users, opts)
|
{:ok, activity} = generate_long_thread(user, users, opts)
|
||||||
|
|
|
||||||
|
|
@ -82,3 +82,11 @@ config :pleroma, :database, rum_enabled: rum_enabled
|
||||||
IO.puts("RUM enabled: #{rum_enabled}")
|
IO.puts("RUM enabled: #{rum_enabled}")
|
||||||
|
|
||||||
config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.ClientMock
|
config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.ClientMock
|
||||||
|
|
||||||
|
if File.exists?("./config/benchmark.secret.exs") do
|
||||||
|
import_config "benchmark.secret.exs"
|
||||||
|
else
|
||||||
|
IO.puts(
|
||||||
|
"You may want to create benchmark.secret.exs to declare custom database connection parameters."
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
|
||||||
|
|
@ -66,9 +66,11 @@ config :pleroma, Pleroma.Scheduler,
|
||||||
jobs: scheduled_jobs
|
jobs: scheduled_jobs
|
||||||
|
|
||||||
config :pleroma, Pleroma.Captcha,
|
config :pleroma, Pleroma.Captcha,
|
||||||
enabled: false,
|
enabled: true,
|
||||||
seconds_valid: 60,
|
seconds_valid: 300,
|
||||||
method: Pleroma.Captcha.Kocaptcha
|
method: Pleroma.Captcha.Native
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.Captcha.Kocaptcha, endpoint: "https://captcha.kotobank.ch"
|
||||||
|
|
||||||
config :pleroma, :hackney_pools,
|
config :pleroma, :hackney_pools,
|
||||||
federation: [
|
federation: [
|
||||||
|
|
@ -84,13 +86,11 @@ config :pleroma, :hackney_pools,
|
||||||
timeout: 300_000
|
timeout: 300_000
|
||||||
]
|
]
|
||||||
|
|
||||||
config :pleroma, Pleroma.Captcha.Kocaptcha, endpoint: "https://captcha.kotobank.ch"
|
|
||||||
|
|
||||||
# Upload configuration
|
# Upload configuration
|
||||||
config :pleroma, Pleroma.Upload,
|
config :pleroma, Pleroma.Upload,
|
||||||
uploader: Pleroma.Uploaders.Local,
|
uploader: Pleroma.Uploaders.Local,
|
||||||
filters: [Pleroma.Upload.Filter.Dedupe],
|
filters: [Pleroma.Upload.Filter.Dedupe],
|
||||||
link_name: true,
|
link_name: false,
|
||||||
proxy_remote: false,
|
proxy_remote: false,
|
||||||
proxy_opts: [
|
proxy_opts: [
|
||||||
redirect_on_failure: false,
|
redirect_on_failure: false,
|
||||||
|
|
@ -108,15 +108,10 @@ config :pleroma, Pleroma.Uploaders.S3,
|
||||||
streaming_enabled: true,
|
streaming_enabled: true,
|
||||||
public_endpoint: "https://s3.amazonaws.com"
|
public_endpoint: "https://s3.amazonaws.com"
|
||||||
|
|
||||||
config :pleroma, Pleroma.Uploaders.MDII,
|
|
||||||
cgi: "https://mdii.sakura.ne.jp/mdii-post.cgi",
|
|
||||||
files: "https://mdii.sakura.ne.jp"
|
|
||||||
|
|
||||||
config :pleroma, :emoji,
|
config :pleroma, :emoji,
|
||||||
shortcode_globs: ["/emoji/custom/**/*.png"],
|
shortcode_globs: ["/emoji/custom/**/*.png"],
|
||||||
pack_extensions: [".png", ".gif"],
|
pack_extensions: [".png", ".gif"],
|
||||||
groups: [
|
groups: [
|
||||||
# Put groups that have higher priority than defaults here. Example in `docs/config/custom_emoji.md`
|
|
||||||
Custom: ["/emoji/*.png", "/emoji/**/*.png"]
|
Custom: ["/emoji/*.png", "/emoji/**/*.png"]
|
||||||
],
|
],
|
||||||
default_manifest: "https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json",
|
default_manifest: "https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json",
|
||||||
|
|
@ -180,7 +175,8 @@ config :pleroma, Pleroma.Web.Endpoint,
|
||||||
|
|
||||||
# Configures Elixir's Logger
|
# Configures Elixir's Logger
|
||||||
config :logger, :console,
|
config :logger, :console,
|
||||||
format: "$time $metadata[$level] $message\n",
|
level: :debug,
|
||||||
|
format: "\n$time $metadata[$level] $message\n",
|
||||||
metadata: [:request_id]
|
metadata: [:request_id]
|
||||||
|
|
||||||
config :logger, :ex_syslogger,
|
config :logger, :ex_syslogger,
|
||||||
|
|
@ -208,6 +204,7 @@ config :tesla, adapter: Tesla.Adapter.Hackney
|
||||||
config :pleroma, :http,
|
config :pleroma, :http,
|
||||||
proxy_url: nil,
|
proxy_url: nil,
|
||||||
send_user_agent: true,
|
send_user_agent: true,
|
||||||
|
user_agent: :default,
|
||||||
adapter: [
|
adapter: [
|
||||||
ssl_options: [
|
ssl_options: [
|
||||||
# Workaround for remote server certificate chain issues
|
# Workaround for remote server certificate chain issues
|
||||||
|
|
@ -223,6 +220,7 @@ config :pleroma, :instance,
|
||||||
notify_email: "pleroma@hjkos.com",
|
notify_email: "pleroma@hjkos.com",
|
||||||
description: "SigSegV, a pleroma instance",
|
description: "SigSegV, a pleroma instance",
|
||||||
limit: 5_000,
|
limit: 5_000,
|
||||||
|
chat_limit: 5_000,
|
||||||
remote_limit: 100_000,
|
remote_limit: 100_000,
|
||||||
upload_limit: 200_000_000,
|
upload_limit: 200_000_000,
|
||||||
avatar_upload_limit: 2_000_000,
|
avatar_upload_limit: 2_000_000,
|
||||||
|
|
@ -259,7 +257,7 @@ config :pleroma, :instance,
|
||||||
mrf_transparency_exclusions: [],
|
mrf_transparency_exclusions: [],
|
||||||
autofollowed_nicknames: [],
|
autofollowed_nicknames: [],
|
||||||
max_pinned_statuses: 1,
|
max_pinned_statuses: 1,
|
||||||
no_attachment_links: false,
|
no_attachment_links: true,
|
||||||
welcome_user_nickname: nil,
|
welcome_user_nickname: nil,
|
||||||
welcome_message: nil,
|
welcome_message: nil,
|
||||||
max_report_comment_size: 1000,
|
max_report_comment_size: 1000,
|
||||||
|
|
@ -268,7 +266,6 @@ config :pleroma, :instance,
|
||||||
remote_post_retention_days: 90,
|
remote_post_retention_days: 90,
|
||||||
skip_thread_containment: true,
|
skip_thread_containment: true,
|
||||||
limit_to_local_content: :unauthenticated,
|
limit_to_local_content: :unauthenticated,
|
||||||
dynamic_configuration: false,
|
|
||||||
user_bio_length: 5000,
|
user_bio_length: 5000,
|
||||||
user_name_length: 100,
|
user_name_length: 100,
|
||||||
max_account_fields: 10,
|
max_account_fields: 10,
|
||||||
|
|
@ -276,7 +273,13 @@ config :pleroma, :instance,
|
||||||
account_field_name_length: 512,
|
account_field_name_length: 512,
|
||||||
account_field_value_length: 2048,
|
account_field_value_length: 2048,
|
||||||
external_user_synchronization: true,
|
external_user_synchronization: true,
|
||||||
extended_nickname_format: false
|
extended_nickname_format: true
|
||||||
|
|
||||||
|
config :pleroma, :feed,
|
||||||
|
post_title: %{
|
||||||
|
max_length: 100,
|
||||||
|
omission: "..."
|
||||||
|
}
|
||||||
|
|
||||||
config :pleroma, :markup,
|
config :pleroma, :markup,
|
||||||
# XXX - unfortunately, inline images must be enabled by default right now, because
|
# XXX - unfortunately, inline images must be enabled by default right now, because
|
||||||
|
|
@ -388,6 +391,10 @@ config :pleroma, :mrf_vocabulary,
|
||||||
accept: [],
|
accept: [],
|
||||||
reject: []
|
reject: []
|
||||||
|
|
||||||
|
config :pleroma, :mrf_object_age,
|
||||||
|
threshold: 172_800,
|
||||||
|
actions: [:delist, :strip_followers]
|
||||||
|
|
||||||
config :pleroma, :rich_media,
|
config :pleroma, :rich_media,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
ignore_hosts: [],
|
ignore_hosts: [],
|
||||||
|
|
@ -431,14 +438,6 @@ config :pleroma, Pleroma.Web.Metadata,
|
||||||
],
|
],
|
||||||
unfurl_nsfw: false
|
unfurl_nsfw: false
|
||||||
|
|
||||||
config :pleroma, :suggestions,
|
|
||||||
enabled: false,
|
|
||||||
third_party_engine:
|
|
||||||
"http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-suggestions-api.cgi?{{host}}+{{user}}",
|
|
||||||
timeout: 300_000,
|
|
||||||
limit: 40,
|
|
||||||
web: "https://vinayaka.distsn.org"
|
|
||||||
|
|
||||||
config :pleroma, :http_security,
|
config :pleroma, :http_security,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
sts: false,
|
sts: false,
|
||||||
|
|
@ -506,7 +505,8 @@ config :pleroma, Oban,
|
||||||
mailer: 10,
|
mailer: 10,
|
||||||
transmogrifier: 20,
|
transmogrifier: 20,
|
||||||
scheduled_activities: 10,
|
scheduled_activities: 10,
|
||||||
background: 5
|
background: 5,
|
||||||
|
attachments_cleanup: 5
|
||||||
]
|
]
|
||||||
|
|
||||||
config :pleroma, :workers,
|
config :pleroma, :workers,
|
||||||
|
|
@ -563,7 +563,10 @@ config :ueberauth,
|
||||||
base_path: "/oauth",
|
base_path: "/oauth",
|
||||||
providers: ueberauth_providers
|
providers: ueberauth_providers
|
||||||
|
|
||||||
config :pleroma, :auth, oauth_consumer_strategies: oauth_consumer_strategies
|
config :pleroma,
|
||||||
|
:auth,
|
||||||
|
enforce_oauth_admin_scope_usage: true,
|
||||||
|
oauth_consumer_strategies: oauth_consumer_strategies
|
||||||
|
|
||||||
config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Sendmail, enabled: false
|
config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Sendmail, enabled: false
|
||||||
|
|
||||||
|
|
@ -612,11 +615,17 @@ config :pleroma, Pleroma.ActivityExpiration, enabled: true
|
||||||
|
|
||||||
config :pleroma, Pleroma.Plugs.RemoteIp, enabled: false
|
config :pleroma, Pleroma.Plugs.RemoteIp, enabled: false
|
||||||
|
|
||||||
|
config :pleroma, :static_fe, enabled: false
|
||||||
|
|
||||||
config :pleroma, :web_cache_ttl,
|
config :pleroma, :web_cache_ttl,
|
||||||
activity_pub: nil,
|
activity_pub: nil,
|
||||||
activity_pub_question: 30_000
|
activity_pub_question: 30_000
|
||||||
|
|
||||||
config :swarm, node_blacklist: [~r/myhtmlex_.*$/]
|
config :pleroma, :modules, runtime_dir: "instance/modules"
|
||||||
|
|
||||||
|
config :pleroma, configurable_from_database: false
|
||||||
|
|
||||||
|
config :swarm, node_blacklist: [~r/myhtml_.*$/]
|
||||||
# Import environment specific config. This must remain at the bottom
|
# Import environment specific config. This must remain at the bottom
|
||||||
# of this file so it overrides the configuration defined above.
|
# of this file so it overrides the configuration defined above.
|
||||||
import_config "#{Mix.env()}.exs"
|
import_config "#{Mix.env()}.exs"
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -20,7 +20,8 @@ config :pleroma, Pleroma.Web.Endpoint,
|
||||||
config :phoenix, serve_endpoints: true
|
config :phoenix, serve_endpoints: true
|
||||||
|
|
||||||
# Do not print debug messages in production
|
# Do not print debug messages in production
|
||||||
config :logger, level: :warn
|
config :logger, :console, level: :info
|
||||||
|
config :logger, :ex_syslogger, level: :info
|
||||||
|
|
||||||
# ## SSL Support
|
# ## SSL Support
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
import Config
|
import Config
|
||||||
|
|
||||||
config :pleroma, :instance, static: "/var/lib/pleroma/static"
|
config :pleroma, :instance, static_dir: "/var/lib/pleroma/static"
|
||||||
config :pleroma, Pleroma.Uploaders.Local, uploads: "/var/lib/pleroma/uploads"
|
config :pleroma, Pleroma.Uploaders.Local, uploads: "/var/lib/pleroma/uploads"
|
||||||
|
config :pleroma, :modules, runtime_dir: "/var/lib/pleroma/modules"
|
||||||
|
|
||||||
config_path = System.get_env("PLEROMA_CONFIG_PATH") || "/etc/pleroma/config.exs"
|
config_path = System.get_env("PLEROMA_CONFIG_PATH") || "/etc/pleroma/config.exs"
|
||||||
|
|
||||||
|
config :pleroma, release: true, config_path: config_path
|
||||||
|
|
||||||
if File.exists?(config_path) do
|
if File.exists?(config_path) do
|
||||||
import_config config_path
|
import_config config_path
|
||||||
else
|
else
|
||||||
|
|
@ -17,3 +20,12 @@ else
|
||||||
|
|
||||||
IO.puts(warning)
|
IO.puts(warning)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
exported_config =
|
||||||
|
config_path
|
||||||
|
|> Path.dirname()
|
||||||
|
|> Path.join("prod.exported_from_db.secret.exs")
|
||||||
|
|
||||||
|
if File.exists?(exported_config) do
|
||||||
|
import_config exported_config
|
||||||
|
end
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,9 @@ config :pleroma, Pleroma.Captcha,
|
||||||
method: Pleroma.Captcha.Mock
|
method: Pleroma.Captcha.Mock
|
||||||
|
|
||||||
# Print only warnings and errors during test
|
# Print only warnings and errors during test
|
||||||
config :logger, level: :warn
|
config :logger, :console,
|
||||||
|
level: :warn,
|
||||||
|
format: "\n[$level] $message\n"
|
||||||
|
|
||||||
config :pleroma, :auth, oauth_consumer_strategies: []
|
config :pleroma, :auth, oauth_consumer_strategies: []
|
||||||
|
|
||||||
|
|
@ -66,7 +68,9 @@ config :pleroma, Oban,
|
||||||
queues: false,
|
queues: false,
|
||||||
prune: :disabled
|
prune: :disabled
|
||||||
|
|
||||||
config :pleroma, Pleroma.Scheduler, jobs: []
|
config :pleroma, Pleroma.Scheduler,
|
||||||
|
jobs: [],
|
||||||
|
global: false
|
||||||
|
|
||||||
config :pleroma, Pleroma.ScheduledActivity,
|
config :pleroma, Pleroma.ScheduledActivity,
|
||||||
daily_user_limit: 2,
|
daily_user_limit: 2,
|
||||||
|
|
@ -91,6 +95,8 @@ config :joken, default_signer: "yU8uHKq+yyAkZ11Hx//jcdacWc8yQ1bxAAGrplzB0Zwwjkp3
|
||||||
|
|
||||||
config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.ClientMock
|
config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.ClientMock
|
||||||
|
|
||||||
|
config :pleroma, :modules, runtime_dir: "test/fixtures/modules"
|
||||||
|
|
||||||
if File.exists?("./config/test.secret.exs") do
|
if File.exists?("./config/test.secret.exs") do
|
||||||
import_config "test.secret.exs"
|
import_config "test.secret.exs"
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,17 @@
|
||||||
|
|
||||||
Authentication is required and the user must be an admin.
|
Authentication is required and the user must be an admin.
|
||||||
|
|
||||||
## `/api/pleroma/admin/users`
|
Configuration options:
|
||||||
|
|
||||||
|
* `[:auth, :enforce_oauth_admin_scope_usage]` — OAuth admin scope requirement toggle.
|
||||||
|
If `true`, admin actions explicitly demand admin OAuth scope(s) presence in OAuth token (client app must support admin scopes).
|
||||||
|
If `false` and token doesn't have admin scope(s), `is_admin` user flag grants access to admin-specific actions.
|
||||||
|
Note that client app needs to explicitly support admin scopes and request them when obtaining auth token.
|
||||||
|
|
||||||
|
## `GET /api/pleroma/admin/users`
|
||||||
|
|
||||||
### List users
|
### List users
|
||||||
|
|
||||||
- Method `GET`
|
|
||||||
- Query Params:
|
- Query Params:
|
||||||
- *optional* `query`: **string** search term (e.g. nickname, domain, nickname@domain)
|
- *optional* `query`: **string** search term (e.g. nickname, domain, nickname@domain)
|
||||||
- *optional* `filters`: **string** comma-separated string of filters:
|
- *optional* `filters`: **string** comma-separated string of filters:
|
||||||
|
|
@ -51,7 +57,6 @@ Authentication is required and the user must be an admin.
|
||||||
|
|
||||||
### Remove a user
|
### Remove a user
|
||||||
|
|
||||||
- Method `DELETE`
|
|
||||||
- Params:
|
- Params:
|
||||||
- `nickname`
|
- `nickname`
|
||||||
- Response: User’s nickname
|
- Response: User’s nickname
|
||||||
|
|
@ -60,7 +65,6 @@ Authentication is required and the user must be an admin.
|
||||||
|
|
||||||
### Remove a user
|
### Remove a user
|
||||||
|
|
||||||
- Method `DELETE`
|
|
||||||
- Params:
|
- Params:
|
||||||
- `nicknames`
|
- `nicknames`
|
||||||
- Response: Array of user nicknames
|
- Response: Array of user nicknames
|
||||||
|
|
@ -78,31 +82,30 @@ Authentication is required and the user must be an admin.
|
||||||
]
|
]
|
||||||
- Response: User’s nickname
|
- Response: User’s nickname
|
||||||
|
|
||||||
## `/api/pleroma/admin/users/follow`
|
## `POST /api/pleroma/admin/users/follow`
|
||||||
|
|
||||||
### Make a user follow another user
|
### Make a user follow another user
|
||||||
|
|
||||||
- Methods: `POST`
|
|
||||||
- Params:
|
- Params:
|
||||||
- `follower`: The nickname of the follower
|
- `follower`: The nickname of the follower
|
||||||
- `followed`: The nickname of the followed
|
- `followed`: The nickname of the followed
|
||||||
- Response:
|
- Response:
|
||||||
- "ok"
|
- "ok"
|
||||||
|
|
||||||
## `/api/pleroma/admin/users/unfollow`
|
## `POST /api/pleroma/admin/users/unfollow`
|
||||||
|
|
||||||
### Make a user unfollow another user
|
### Make a user unfollow another user
|
||||||
|
|
||||||
- Methods: `POST`
|
|
||||||
- Params:
|
- Params:
|
||||||
- `follower`: The nickname of the follower
|
- `follower`: The nickname of the follower
|
||||||
- `followed`: The nickname of the followed
|
- `followed`: The nickname of the followed
|
||||||
- Response:
|
- Response:
|
||||||
- "ok"
|
- "ok"
|
||||||
|
|
||||||
## `/api/pleroma/admin/users/:nickname/toggle_activation`
|
## `PATCH /api/pleroma/admin/users/:nickname/toggle_activation`
|
||||||
|
|
||||||
### Toggle user activation
|
### Toggle user activation
|
||||||
|
|
||||||
- Method: `PATCH`
|
|
||||||
- Params:
|
- Params:
|
||||||
- `nickname`
|
- `nickname`
|
||||||
- Response: User’s object
|
- Response: User’s object
|
||||||
|
|
@ -115,27 +118,26 @@ Authentication is required and the user must be an admin.
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## `/api/pleroma/admin/users/tag`
|
## `PUT /api/pleroma/admin/users/tag`
|
||||||
|
|
||||||
### Tag a list of users
|
### Tag a list of users
|
||||||
|
|
||||||
- Method: `PUT`
|
|
||||||
- Params:
|
- Params:
|
||||||
- `nicknames` (array)
|
- `nicknames` (array)
|
||||||
- `tags` (array)
|
- `tags` (array)
|
||||||
|
|
||||||
|
## `DELETE /api/pleroma/admin/users/tag`
|
||||||
|
|
||||||
### Untag a list of users
|
### Untag a list of users
|
||||||
|
|
||||||
- Method: `DELETE`
|
|
||||||
- Params:
|
- Params:
|
||||||
- `nicknames` (array)
|
- `nicknames` (array)
|
||||||
- `tags` (array)
|
- `tags` (array)
|
||||||
|
|
||||||
## `/api/pleroma/admin/users/:nickname/permission_group`
|
## `GET /api/pleroma/admin/users/:nickname/permission_group`
|
||||||
|
|
||||||
### Get user user permission groups membership
|
### Get user user permission groups membership
|
||||||
|
|
||||||
- Method: `GET`
|
|
||||||
- Params: none
|
- Params: none
|
||||||
- Response:
|
- Response:
|
||||||
|
|
||||||
|
|
@ -146,13 +148,12 @@ Authentication is required and the user must be an admin.
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## `/api/pleroma/admin/users/:nickname/permission_group/:permission_group`
|
## `GET /api/pleroma/admin/users/:nickname/permission_group/:permission_group`
|
||||||
|
|
||||||
Note: Available `:permission_group` is currently moderator and admin. 404 is returned when the permission group doesn’t exist.
|
Note: Available `:permission_group` is currently moderator and admin. 404 is returned when the permission group doesn’t exist.
|
||||||
|
|
||||||
### Get user user permission groups membership per permission group
|
### Get user user permission groups membership per permission group
|
||||||
|
|
||||||
- Method: `GET`
|
|
||||||
- Params: none
|
- Params: none
|
||||||
- Response:
|
- Response:
|
||||||
|
|
||||||
|
|
@ -184,6 +185,8 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
|
|
||||||
## DEPRECATED `DELETE /api/pleroma/admin/users/:nickname/permission_group/:permission_group`
|
## DEPRECATED `DELETE /api/pleroma/admin/users/:nickname/permission_group/:permission_group`
|
||||||
|
|
||||||
|
## `DELETE /api/pleroma/admin/users/:nickname/permission_group/:permission_group`
|
||||||
|
|
||||||
### Remove user from permission group
|
### Remove user from permission group
|
||||||
|
|
||||||
- Params: none
|
- Params: none
|
||||||
|
|
@ -239,30 +242,20 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## DEPRECATED `PATCH /api/pleroma/admin/users/:nickname/activation_status`
|
## `GET /api/pleroma/admin/users/:nickname_or_id`
|
||||||
|
|
||||||
### Active or deactivate a user
|
|
||||||
|
|
||||||
- Params:
|
|
||||||
- `nickname`
|
|
||||||
- `status` BOOLEAN field, false value means deactivation.
|
|
||||||
|
|
||||||
## `/api/pleroma/admin/users/:nickname_or_id`
|
|
||||||
|
|
||||||
### Retrive the details of a user
|
### Retrive the details of a user
|
||||||
|
|
||||||
- Method: `GET`
|
|
||||||
- Params:
|
- Params:
|
||||||
- `nickname` or `id`
|
- `nickname` or `id`
|
||||||
- Response:
|
- Response:
|
||||||
- On failure: `Not found`
|
- On failure: `Not found`
|
||||||
- On success: JSON of the user
|
- On success: JSON of the user
|
||||||
|
|
||||||
## `/api/pleroma/admin/users/:nickname_or_id/statuses`
|
## `GET /api/pleroma/admin/users/:nickname_or_id/statuses`
|
||||||
|
|
||||||
### Retrive user's latest statuses
|
### Retrive user's latest statuses
|
||||||
|
|
||||||
- Method: `GET`
|
|
||||||
- Params:
|
- Params:
|
||||||
- `nickname` or `id`
|
- `nickname` or `id`
|
||||||
- *optional* `page_size`: number of statuses to return (default is `20`)
|
- *optional* `page_size`: number of statuses to return (default is `20`)
|
||||||
|
|
@ -271,19 +264,19 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
- On failure: `Not found`
|
- On failure: `Not found`
|
||||||
- On success: JSON array of user's latest statuses
|
- On success: JSON array of user's latest statuses
|
||||||
|
|
||||||
## `/api/pleroma/admin/relay`
|
## `POST /api/pleroma/admin/relay`
|
||||||
|
|
||||||
### Follow a Relay
|
### Follow a Relay
|
||||||
|
|
||||||
- Methods: `POST`
|
|
||||||
- Params:
|
- Params:
|
||||||
- `relay_url`
|
- `relay_url`
|
||||||
- Response:
|
- Response:
|
||||||
- On success: URL of the followed relay
|
- On success: URL of the followed relay
|
||||||
|
|
||||||
|
## `DELETE /api/pleroma/admin/relay`
|
||||||
|
|
||||||
### Unfollow a Relay
|
### Unfollow a Relay
|
||||||
|
|
||||||
- Methods: `DELETE`
|
|
||||||
- Params:
|
- Params:
|
||||||
- `relay_url`
|
- `relay_url`
|
||||||
- Response:
|
- Response:
|
||||||
|
|
@ -297,11 +290,10 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
- Response:
|
- Response:
|
||||||
- On success: JSON array of relays
|
- On success: JSON array of relays
|
||||||
|
|
||||||
## `/api/pleroma/admin/users/invite_token`
|
## `POST /api/pleroma/admin/users/invite_token`
|
||||||
|
|
||||||
### Create an account registration invite token
|
### Create an account registration invite token
|
||||||
|
|
||||||
- Methods: `POST`
|
|
||||||
- Params:
|
- Params:
|
||||||
- *optional* `max_use` (integer)
|
- *optional* `max_use` (integer)
|
||||||
- *optional* `expires_at` (date string e.g. "2019-04-07")
|
- *optional* `expires_at` (date string e.g. "2019-04-07")
|
||||||
|
|
@ -319,11 +311,10 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## `/api/pleroma/admin/users/invites`
|
## `GET /api/pleroma/admin/users/invites`
|
||||||
|
|
||||||
### Get a list of generated invites
|
### Get a list of generated invites
|
||||||
|
|
||||||
- Methods: `GET`
|
|
||||||
- Params: none
|
- Params: none
|
||||||
- Response:
|
- Response:
|
||||||
|
|
||||||
|
|
@ -345,11 +336,10 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## `/api/pleroma/admin/users/revoke_invite`
|
## `POST /api/pleroma/admin/users/revoke_invite`
|
||||||
|
|
||||||
### Revoke invite by token
|
### Revoke invite by token
|
||||||
|
|
||||||
- Methods: `POST`
|
|
||||||
- Params:
|
- Params:
|
||||||
- `token`
|
- `token`
|
||||||
- Response:
|
- Response:
|
||||||
|
|
@ -367,21 +357,18 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `POST /api/pleroma/admin/users/email_invite`
|
||||||
## `/api/pleroma/admin/users/email_invite`
|
|
||||||
|
|
||||||
### Sends registration invite via email
|
### Sends registration invite via email
|
||||||
|
|
||||||
- Methods: `POST`
|
|
||||||
- Params:
|
- Params:
|
||||||
- `email`
|
- `email`
|
||||||
- `name`, optional
|
- `name`, optional
|
||||||
|
|
||||||
## `/api/pleroma/admin/users/:nickname/password_reset`
|
## `GET /api/pleroma/admin/users/:nickname/password_reset`
|
||||||
|
|
||||||
### Get a password reset token for a given nickname
|
### Get a password reset token for a given nickname
|
||||||
|
|
||||||
- Methods: `GET`
|
|
||||||
- Params: none
|
- Params: none
|
||||||
- Response:
|
- Response:
|
||||||
|
|
||||||
|
|
@ -392,18 +379,18 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `PATCH /api/pleroma/admin/users/force_password_reset`
|
||||||
## `/api/pleroma/admin/users/:nickname/force_password_reset`
|
|
||||||
|
|
||||||
### Force passord reset for a user with a given nickname
|
### Force passord reset for a user with a given nickname
|
||||||
|
|
||||||
- Methods: `PATCH`
|
- Params:
|
||||||
- Params: none
|
- `nicknames`
|
||||||
- Response: none (code `204`)
|
- Response: none (code `204`)
|
||||||
|
|
||||||
## `/api/pleroma/admin/reports`
|
## `GET /api/pleroma/admin/reports`
|
||||||
|
|
||||||
### Get a list of reports
|
### Get a list of reports
|
||||||
- Method `GET`
|
|
||||||
- Params:
|
- Params:
|
||||||
- *optional* `state`: **string** the state of reports. Valid values are `open`, `closed` and `resolved`
|
- *optional* `state`: **string** the state of reports. Valid values are `open`, `closed` and `resolved`
|
||||||
- *optional* `limit`: **integer** the number of records to retrieve
|
- *optional* `limit`: **integer** the number of records to retrieve
|
||||||
|
|
@ -418,7 +405,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"total" : 1,
|
"totalReports" : 1,
|
||||||
"reports": [
|
"reports": [
|
||||||
{
|
{
|
||||||
"account": {
|
"account": {
|
||||||
|
|
@ -560,9 +547,34 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## `/api/pleroma/admin/reports/:id`
|
## `GET /api/pleroma/admin/grouped_reports`
|
||||||
|
|
||||||
|
### Get a list of reports, grouped by status
|
||||||
|
|
||||||
|
- Params: none
|
||||||
|
- On success: JSON, returns a list of reports, where:
|
||||||
|
- `date`: date of the latest report
|
||||||
|
- `account`: the user who has been reported (see `/api/pleroma/admin/reports` for reference)
|
||||||
|
- `status`: reported status (see `/api/pleroma/admin/reports` for reference)
|
||||||
|
- `actors`: users who had reported this status (see `/api/pleroma/admin/reports` for reference)
|
||||||
|
- `reports`: reports (see `/api/pleroma/admin/reports` for reference)
|
||||||
|
|
||||||
|
```json
|
||||||
|
"reports": [
|
||||||
|
{
|
||||||
|
"date": "2019-10-07T12:31:39.615149Z",
|
||||||
|
"account": { ... },
|
||||||
|
"status": { ... },
|
||||||
|
"actors": [{ ... }, { ... }],
|
||||||
|
"reports": [{ ... }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## `GET /api/pleroma/admin/reports/:id`
|
||||||
|
|
||||||
### Get an individual report
|
### Get an individual report
|
||||||
- Method `GET`
|
|
||||||
- Params:
|
- Params:
|
||||||
- `id`
|
- `id`
|
||||||
- Response:
|
- Response:
|
||||||
|
|
@ -571,94 +583,65 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
- 404 Not Found `"Not found"`
|
- 404 Not Found `"Not found"`
|
||||||
- On success: JSON, Report object (see above)
|
- On success: JSON, Report object (see above)
|
||||||
|
|
||||||
## `/api/pleroma/admin/reports/:id`
|
## `PATCH /api/pleroma/admin/reports`
|
||||||
### Change the state of the report
|
|
||||||
- Method `PUT`
|
### Change the state of one or multiple reports
|
||||||
|
|
||||||
- Params:
|
- Params:
|
||||||
- `id`
|
|
||||||
- `state`: required, the new state. Valid values are `open`, `closed` and `resolved`
|
```json
|
||||||
|
`reports`: [
|
||||||
|
{
|
||||||
|
`id`, // required, report id
|
||||||
|
`state` // required, the new state. Valid values are `open`, `closed` and `resolved`
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
- Response:
|
- Response:
|
||||||
- On failure:
|
- On failure:
|
||||||
- 400 Bad Request `"Unsupported state"`
|
- 400 Bad Request, JSON:
|
||||||
- 403 Forbidden `{"error": "error_msg"}`
|
|
||||||
- 404 Not Found `"Not found"`
|
```json
|
||||||
- On success: JSON, Report object (see above)
|
[
|
||||||
|
{
|
||||||
|
`id`, // report id
|
||||||
|
`error` // error message
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
- On success: `204`, empty response
|
||||||
|
|
||||||
|
## `POST /api/pleroma/admin/reports/:id/notes`
|
||||||
|
|
||||||
|
### Create report note
|
||||||
|
|
||||||
## `/api/pleroma/admin/reports/:id/respond`
|
|
||||||
### Respond to a report
|
|
||||||
- Method `POST`
|
|
||||||
- Params:
|
- Params:
|
||||||
- `id`
|
- `id`: required, report id
|
||||||
- `status`: required, the message
|
- `content`: required, the message
|
||||||
- Response:
|
- Response:
|
||||||
- On failure:
|
- On failure:
|
||||||
- 400 Bad Request `"Invalid parameters"` when `status` is missing
|
- 400 Bad Request `"Invalid parameters"` when `status` is missing
|
||||||
- 403 Forbidden `{"error": "error_msg"}`
|
- On success: `204`, empty response
|
||||||
- 404 Not Found `"Not found"`
|
|
||||||
- On success: JSON, created Mastodon Status entity
|
|
||||||
|
|
||||||
```json
|
## `POST /api/pleroma/admin/reports/:report_id/notes/:id`
|
||||||
{
|
|
||||||
"account": { ... },
|
### Delete report note
|
||||||
"application": {
|
|
||||||
"name": "Web",
|
- Params:
|
||||||
"website": null
|
- `report_id`: required, report id
|
||||||
},
|
- `id`: required, note id
|
||||||
"bookmarked": false,
|
- Response:
|
||||||
"card": null,
|
- On failure:
|
||||||
"content": "Your claim is going to be closed",
|
- 400 Bad Request `"Invalid parameters"` when `status` is missing
|
||||||
"created_at": "2019-05-11T17:13:03.000Z",
|
- On success: `204`, empty response
|
||||||
"emojis": [],
|
|
||||||
"favourited": false,
|
## `PUT /api/pleroma/admin/statuses/:id`
|
||||||
"favourites_count": 0,
|
|
||||||
"id": "9ihuiSL1405I65TmEq",
|
|
||||||
"in_reply_to_account_id": null,
|
|
||||||
"in_reply_to_id": null,
|
|
||||||
"language": null,
|
|
||||||
"media_attachments": [],
|
|
||||||
"mentions": [
|
|
||||||
{
|
|
||||||
"acct": "user",
|
|
||||||
"id": "9i6dAJqSGSKMzLG2Lo",
|
|
||||||
"url": "https://pleroma.example.org/users/user",
|
|
||||||
"username": "user"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"acct": "admin",
|
|
||||||
"id": "9hEkA5JsvAdlSrocam",
|
|
||||||
"url": "https://pleroma.example.org/users/admin",
|
|
||||||
"username": "admin"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"muted": false,
|
|
||||||
"pinned": false,
|
|
||||||
"pleroma": {
|
|
||||||
"content": {
|
|
||||||
"text/plain": "Your claim is going to be closed"
|
|
||||||
},
|
|
||||||
"conversation_id": 35,
|
|
||||||
"in_reply_to_account_acct": null,
|
|
||||||
"local": true,
|
|
||||||
"spoiler_text": {
|
|
||||||
"text/plain": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"reblog": null,
|
|
||||||
"reblogged": false,
|
|
||||||
"reblogs_count": 0,
|
|
||||||
"replies_count": 0,
|
|
||||||
"sensitive": false,
|
|
||||||
"spoiler_text": "",
|
|
||||||
"tags": [],
|
|
||||||
"uri": "https://pleroma.example.org/objects/cab0836d-9814-46cd-a0ea-529da9db5fcb",
|
|
||||||
"url": "https://pleroma.example.org/notice/9ihuiSL1405I65TmEq",
|
|
||||||
"visibility": "direct"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## `/api/pleroma/admin/statuses/:id`
|
|
||||||
### Change the scope of an individual reported status
|
### Change the scope of an individual reported status
|
||||||
- Method `PUT`
|
|
||||||
- Params:
|
- Params:
|
||||||
- `id`
|
- `id`
|
||||||
- `sensitive`: optional, valid values are `true` or `false`
|
- `sensitive`: optional, valid values are `true` or `false`
|
||||||
|
|
@ -670,9 +653,10 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
- 404 Not Found `"Not found"`
|
- 404 Not Found `"Not found"`
|
||||||
- On success: JSON, Mastodon Status entity
|
- On success: JSON, Mastodon Status entity
|
||||||
|
|
||||||
## `/api/pleroma/admin/statuses/:id`
|
## `DELETE /api/pleroma/admin/statuses/:id`
|
||||||
|
|
||||||
### Delete an individual reported status
|
### Delete an individual reported status
|
||||||
- Method `DELETE`
|
|
||||||
- Params:
|
- Params:
|
||||||
- `id`
|
- `id`
|
||||||
- Response:
|
- Response:
|
||||||
|
|
@ -681,89 +665,151 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
- 404 Not Found `"Not found"`
|
- 404 Not Found `"Not found"`
|
||||||
- On success: 200 OK `{}`
|
- On success: 200 OK `{}`
|
||||||
|
|
||||||
|
## `GET /api/pleroma/admin/config/migrate_from_db`
|
||||||
|
|
||||||
## `/api/pleroma/admin/config/migrate_to_db`
|
|
||||||
### Run mix task pleroma.config migrate_to_db
|
|
||||||
Copy settings on key `:pleroma` to DB.
|
|
||||||
- Method `GET`
|
|
||||||
- Params: none
|
|
||||||
- Response:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{}
|
|
||||||
```
|
|
||||||
|
|
||||||
## `/api/pleroma/admin/config/migrate_from_db`
|
|
||||||
### Run mix task pleroma.config migrate_from_db
|
### Run mix task pleroma.config migrate_from_db
|
||||||
Copy all settings from DB to `config/prod.exported_from_db.secret.exs` with deletion from DB.
|
|
||||||
- Method `GET`
|
Copies all settings from database to `config/{env}.exported_from_db.secret.exs` with deletion from the table. Where `{env}` is the environment in which `pleroma` is running.
|
||||||
|
|
||||||
- Params: none
|
- Params: none
|
||||||
- Response:
|
- Response:
|
||||||
|
- On failure:
|
||||||
|
- 400 Bad Request `"To use this endpoint you need to enable configuration from database."`
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{}
|
{}
|
||||||
```
|
```
|
||||||
|
|
||||||
## `/api/pleroma/admin/config`
|
## `GET /api/pleroma/admin/config`
|
||||||
### List config settings
|
|
||||||
List config settings only works with `:pleroma => :instance => :dynamic_configuration` setting to `true`.
|
### Get list of merged default settings with saved in database.
|
||||||
- Method `GET`
|
|
||||||
- Params: none
|
**Only works when configuration from database is enabled.**
|
||||||
|
|
||||||
|
- Params:
|
||||||
|
- `only_db`: true (*optional*, get only saved in database settings)
|
||||||
- Response:
|
- Response:
|
||||||
|
- On failure:
|
||||||
|
- 400 Bad Request `"To use this endpoint you need to enable configuration from database."`
|
||||||
|
- 400 Bad Request `"To use configuration from database migrate your settings to database."`
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
configs: [
|
configs: [
|
||||||
{
|
{
|
||||||
"group": string,
|
"group": ":pleroma",
|
||||||
"key": string or string with leading `:` for atoms,
|
"key": "Pleroma.Upload",
|
||||||
"value": string or {} or [] or {"tuple": []}
|
"value": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## `/api/pleroma/admin/config`
|
## `POST /api/pleroma/admin/config`
|
||||||
|
|
||||||
### Update config settings
|
### Update config settings
|
||||||
Updating config settings only works with `:pleroma => :instance => :dynamic_configuration` setting to `true`.
|
|
||||||
Module name can be passed as string, which starts with `Pleroma`, e.g. `"Pleroma.Upload"`.
|
|
||||||
Atom keys and values can be passed with `:` in the beginning, e.g. `":upload"`.
|
|
||||||
Tuples can be passed as `{"tuple": ["first_val", Pleroma.Module, []]}`.
|
|
||||||
`{"tuple": ["some_string", "Pleroma.Some.Module", []]}` will be converted to `{"some_string", Pleroma.Some.Module, []}`.
|
|
||||||
Keywords can be passed as lists with 2 child tuples, e.g.
|
|
||||||
`[{"tuple": ["first_val", Pleroma.Module]}, {"tuple": ["second_val", true]}]`.
|
|
||||||
|
|
||||||
If value contains list of settings `[subkey: val1, subkey2: val2, subkey3: val3]`, it's possible to remove only subkeys instead of all settings passing `subkeys` parameter. E.g.:
|
**Only works when configuration from database is enabled.**
|
||||||
{"group": "pleroma", "key": "some_key", "delete": "true", "subkeys": [":subkey", ":subkey3"]}.
|
|
||||||
|
|
||||||
Compile time settings (need instance reboot):
|
Some modifications are necessary to save the config settings correctly:
|
||||||
- all settings by this keys:
|
|
||||||
|
- strings which start with `Pleroma.`, `Phoenix.`, `Tesla.` or strings like `Oban`, `Ueberauth` will be converted to modules;
|
||||||
|
```
|
||||||
|
"Pleroma.Upload" -> Pleroma.Upload
|
||||||
|
"Oban" -> Oban
|
||||||
|
```
|
||||||
|
- strings starting with `:` will be converted to atoms;
|
||||||
|
```
|
||||||
|
":pleroma" -> :pleroma
|
||||||
|
```
|
||||||
|
- objects with `tuple` key and array value will be converted to tuples;
|
||||||
|
```
|
||||||
|
{"tuple": ["string", "Pleroma.Upload", []]} -> {"string", Pleroma.Upload, []}
|
||||||
|
```
|
||||||
|
- arrays with *tuple objects* will be converted to keywords;
|
||||||
|
```
|
||||||
|
[{"tuple": [":key1", "value"]}, {"tuple": [":key2", "value"]}] -> [key1: "value", key2: "value"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Most of the settings will be applied in `runtime`, this means that you don't need to restart the instance. But some settings are applied in `compile time` and require a reboot of the instance, such as:
|
||||||
|
- all settings inside these keys:
|
||||||
- `:hackney_pools`
|
- `:hackney_pools`
|
||||||
- `:chat`
|
- `:chat`
|
||||||
- `Pleroma.Web.Endpoint`
|
- partially settings inside these keys:
|
||||||
- `Pleroma.Repo`
|
- `:seconds_valid` in `Pleroma.Captcha`
|
||||||
- part settings:
|
- `:proxy_remote` in `Pleroma.Upload`
|
||||||
- `Pleroma.Captcha` -> `:seconds_valid`
|
- `:upload_limit` in `:instance`
|
||||||
- `Pleroma.Upload` -> `:proxy_remote`
|
|
||||||
- `:instance` -> `:upload_limit`
|
|
||||||
|
|
||||||
- Method `POST`
|
|
||||||
- Params:
|
- Params:
|
||||||
- `configs` => [
|
- `configs` - array of config objects
|
||||||
- `group` (string)
|
- config object params:
|
||||||
- `key` (string or string with leading `:` for atoms)
|
- `group` - string (**required**)
|
||||||
- `value` (string, [], {} or {"tuple": []})
|
- `key` - string (**required**)
|
||||||
- `delete` = true (optional, if parameter must be deleted)
|
- `value` - string, [], {} or {"tuple": []} (**required**)
|
||||||
- `subkeys` [(string with leading `:` for atoms)] (optional, works only if `delete=true` parameter is passed, otherwise will be ignored)
|
- `delete` - true (*optional*, if setting must be deleted)
|
||||||
]
|
- `subkeys` - array of strings (*optional*, only works when `delete=true` parameter is passed, otherwise will be ignored)
|
||||||
|
|
||||||
- Request (example):
|
*When a value have several nested settings, you can delete only some nested settings by passing a parameter `subkeys`, without deleting all settings by key.*
|
||||||
|
```
|
||||||
|
[subkey: val1, subkey2: val2, subkey3: val3] \\ initial value
|
||||||
|
{"group": ":pleroma", "key": "some_key", "delete": true, "subkeys": [":subkey", ":subkey3"]} \\ passing json for deletion
|
||||||
|
[subkey2: val2] \\ value after deletion
|
||||||
|
```
|
||||||
|
|
||||||
|
*Most of the settings can be partially updated through merge old values with new values, except settings value of which is list or is not keyword.*
|
||||||
|
|
||||||
|
Example of setting without keyword in value:
|
||||||
|
```elixir
|
||||||
|
config :tesla, :adapter, Tesla.Adapter.Hackney
|
||||||
|
```
|
||||||
|
|
||||||
|
List of settings which support only full update by key:
|
||||||
|
```elixir
|
||||||
|
@full_key_update [
|
||||||
|
{:pleroma, :ecto_repos},
|
||||||
|
{:quack, :meta},
|
||||||
|
{:mime, :types},
|
||||||
|
{:cors_plug, [:max_age, :methods, :expose, :headers]},
|
||||||
|
{:auto_linker, :opts},
|
||||||
|
{:swarm, :node_blacklist},
|
||||||
|
{:logger, :backends}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
List of settings which support only full update by subkey:
|
||||||
|
```elixir
|
||||||
|
@full_subkey_update [
|
||||||
|
{:pleroma, :assets, :mascots},
|
||||||
|
{:pleroma, :emoji, :groups},
|
||||||
|
{:pleroma, :workers, :retries},
|
||||||
|
{:pleroma, :mrf_subchain, :match_actor},
|
||||||
|
{:pleroma, :mrf_keyword, :replace}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
*Settings without explicit key must be sended in separate config object params.*
|
||||||
|
```elixir
|
||||||
|
config :quack,
|
||||||
|
level: :debug,
|
||||||
|
meta: [:all],
|
||||||
|
...
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
configs: [
|
||||||
|
{"group": ":quack", "key": ":level", "value": ":debug"},
|
||||||
|
{"group": ":quack", "key": ":meta", "value": [":all"]},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Request:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
configs: [
|
configs: [
|
||||||
{
|
{
|
||||||
"group": "pleroma",
|
"group": ":pleroma",
|
||||||
"key": "Pleroma.Upload",
|
"key": "Pleroma.Upload",
|
||||||
"value": [
|
"value": [
|
||||||
{"tuple": [":uploader", "Pleroma.Uploaders.Local"]},
|
{"tuple": [":uploader", "Pleroma.Uploaders.Local"]},
|
||||||
|
|
@ -773,7 +819,7 @@ Compile time settings (need instance reboot):
|
||||||
{"tuple": [":proxy_opts", [
|
{"tuple": [":proxy_opts", [
|
||||||
{"tuple": [":redirect_on_failure", false]},
|
{"tuple": [":redirect_on_failure", false]},
|
||||||
{"tuple": [":max_body_length", 1048576]},
|
{"tuple": [":max_body_length", 1048576]},
|
||||||
{"tuple": [":http": [
|
{"tuple": [":http", [
|
||||||
{"tuple": [":follow_redirect", true]},
|
{"tuple": [":follow_redirect", true]},
|
||||||
{"tuple": [":pool", ":upload"]},
|
{"tuple": [":pool", ":upload"]},
|
||||||
]]}
|
]]}
|
||||||
|
|
@ -789,22 +835,57 @@ Compile time settings (need instance reboot):
|
||||||
```
|
```
|
||||||
|
|
||||||
- Response:
|
- Response:
|
||||||
|
- On failure:
|
||||||
|
- 400 Bad Request `"To use this endpoint you need to enable configuration from database."`
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
configs: [
|
configs: [
|
||||||
{
|
{
|
||||||
"group": string,
|
"group": ":pleroma",
|
||||||
"key": string or string with leading `:` for atoms,
|
"key": "Pleroma.Upload",
|
||||||
"value": string or {} or [] or {"tuple": []}
|
"value": [...]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## `/api/pleroma/admin/moderation_log`
|
## ` GET /api/pleroma/admin/config/descriptions`
|
||||||
|
|
||||||
|
### Get JSON with config descriptions.
|
||||||
|
Loads json generated from `config/descriptions.exs`.
|
||||||
|
|
||||||
|
- Params: none
|
||||||
|
- Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[{
|
||||||
|
"group": ":pleroma", // string
|
||||||
|
"key": "ModuleName", // string
|
||||||
|
"type": "group", // string or list with possible values,
|
||||||
|
"description": "Upload general settings", // string
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"key": ":uploader", // string or module name `Pleroma.Upload`
|
||||||
|
"type": "module",
|
||||||
|
"description": "Module which will be used for uploads",
|
||||||
|
"suggestions": ["module1", "module2"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": ":filters",
|
||||||
|
"type": ["list", "module"],
|
||||||
|
"description": "List of filter modules for uploads",
|
||||||
|
"suggestions": [
|
||||||
|
"module1", "module2", "module3"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
```
|
||||||
|
|
||||||
|
## `GET /api/pleroma/admin/moderation_log`
|
||||||
|
|
||||||
### Get moderation log
|
### Get moderation log
|
||||||
- Method `GET`
|
|
||||||
- Params:
|
- Params:
|
||||||
- *optional* `page`: **integer** page number
|
- *optional* `page`: **integer** page number
|
||||||
- *optional* `page_size`: **integer** number of log entries per page (default is `50`)
|
- *optional* `page_size`: **integer** number of log entries per page (default is `50`)
|
||||||
|
|
@ -831,8 +912,25 @@ Compile time settings (need instance reboot):
|
||||||
```
|
```
|
||||||
|
|
||||||
## `POST /api/pleroma/admin/reload_emoji`
|
## `POST /api/pleroma/admin/reload_emoji`
|
||||||
|
|
||||||
### Reload the instance's custom emoji
|
### Reload the instance's custom emoji
|
||||||
* Method `POST`
|
|
||||||
* Authentication: required
|
- Authentication: required
|
||||||
* Params: None
|
- Params: None
|
||||||
* Response: JSON, "ok" and 200 status
|
- Response: JSON, "ok" and 200 status
|
||||||
|
|
||||||
|
## `PATCH /api/pleroma/admin/users/confirm_email`
|
||||||
|
|
||||||
|
### Confirm users' emails
|
||||||
|
|
||||||
|
- Params:
|
||||||
|
- `nicknames`
|
||||||
|
- Response: Array of user nicknames
|
||||||
|
|
||||||
|
## `PATCH /api/pleroma/admin/users/resend_confirmation_email`
|
||||||
|
|
||||||
|
### Resend confirmation email
|
||||||
|
|
||||||
|
- Params:
|
||||||
|
- `nicknames`
|
||||||
|
- Response: Array of user nicknames
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ 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 it's 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 it's 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 {emoji: "☕", count: 1}. Contains no information about the reacting users, for that use the `emoji_reactions_by` endpoint.
|
||||||
|
|
||||||
## Attachments
|
## Attachments
|
||||||
|
|
||||||
|
|
@ -46,7 +47,7 @@ The `id` parameter can also be the `nickname` of the user. This only works in th
|
||||||
Has these additional fields under the `pleroma` object:
|
Has these additional fields under the `pleroma` object:
|
||||||
|
|
||||||
- `tags`: Lists an array of tags for the user
|
- `tags`: Lists an array of tags for the user
|
||||||
- `relationship{}`: Includes fields as documented for Mastodon API https://docs.joinmastodon.org/api/entities/#relationship
|
- `relationship{}`: Includes fields as documented for Mastodon API https://docs.joinmastodon.org/entities/relationship/
|
||||||
- `is_moderator`: boolean, nullable, true if user is a moderator
|
- `is_moderator`: boolean, nullable, true if user is a moderator
|
||||||
- `is_admin`: boolean, nullable, true if user is an admin
|
- `is_admin`: boolean, nullable, true if user is an admin
|
||||||
- `confirmation_pending`: boolean, true if a new user account is waiting on email confirmation to be activated
|
- `confirmation_pending`: boolean, true if a new user account is waiting on email confirmation to be activated
|
||||||
|
|
@ -57,6 +58,7 @@ Has these additional fields under the `pleroma` object:
|
||||||
- `settings_store`: A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials`
|
- `settings_store`: A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials`
|
||||||
- `chat_token`: The token needed for Pleroma chat. Only returned in `verify_credentials`
|
- `chat_token`: The token needed for Pleroma chat. Only returned in `verify_credentials`
|
||||||
- `deactivated`: boolean, true when the user is deactivated
|
- `deactivated`: boolean, true when the user is deactivated
|
||||||
|
- `allow_following_move`: boolean, true when the user allows automatically follow moved following accounts
|
||||||
- `unread_conversation_count`: The count of unread conversations. Only returned to the account owner.
|
- `unread_conversation_count`: The count of unread conversations. Only returned to the account owner.
|
||||||
|
|
||||||
### Source
|
### Source
|
||||||
|
|
@ -65,6 +67,8 @@ Has these additional fields under the `pleroma` object:
|
||||||
|
|
||||||
- `show_role`: boolean, nullable, true when the user wants his role (e.g admin, moderator) to be shown
|
- `show_role`: boolean, nullable, true when the user wants his role (e.g admin, moderator) to be shown
|
||||||
- `no_rich_text` - boolean, nullable, true when html tags are stripped from all statuses requested from the API
|
- `no_rich_text` - boolean, nullable, true when html tags are stripped from all statuses requested from the API
|
||||||
|
- `discoverable`: boolean, true when the user allows discovery of the account in search results and other services.
|
||||||
|
- `actor_type`: string, the type of this account.
|
||||||
|
|
||||||
## Conversations
|
## Conversations
|
||||||
|
|
||||||
|
|
@ -72,6 +76,12 @@ Has an additional field under the `pleroma` object:
|
||||||
|
|
||||||
- `recipients`: The list of the recipients of this Conversation. These will be addressed when replying to this conversation.
|
- `recipients`: The list of the recipients of this Conversation. These will be addressed when replying to this conversation.
|
||||||
|
|
||||||
|
## GET `/api/v1/conversations`
|
||||||
|
|
||||||
|
Accepts additional parameters:
|
||||||
|
|
||||||
|
- `recipients`: Only return conversations with the given recipients (a list of user ids). Usage example: `GET /api/v1/conversations?recipients[]=1&recipients[]=2`
|
||||||
|
|
||||||
## Account Search
|
## Account Search
|
||||||
|
|
||||||
Behavior has changed:
|
Behavior has changed:
|
||||||
|
|
@ -85,11 +95,26 @@ Has these additional fields under the `pleroma` object:
|
||||||
|
|
||||||
- `is_seen`: true if the notification was read by the user
|
- `is_seen`: true if the notification was read by the user
|
||||||
|
|
||||||
|
### Move Notification
|
||||||
|
|
||||||
|
The `type` value is `move`. Has an additional field:
|
||||||
|
|
||||||
|
- `target`: new account
|
||||||
|
|
||||||
|
### EmojiReaction Notification
|
||||||
|
|
||||||
|
The `type` value is `pleroma:emoji_reaction`. Has these fields:
|
||||||
|
|
||||||
|
- `emoji`: The used emoji
|
||||||
|
- `account`: The account of the user who reacted
|
||||||
|
- `status`: The status that was reacted on
|
||||||
|
|
||||||
## GET `/api/v1/notifications`
|
## GET `/api/v1/notifications`
|
||||||
|
|
||||||
Accepts additional parameters:
|
Accepts additional parameters:
|
||||||
|
|
||||||
- `exclude_visibilities`: will exclude the notifications for activities with the given visibilities. The parameter accepts an array of visibility types (`public`, `unlisted`, `private`, `direct`). Usage example: `GET /api/v1/notifications?exclude_visibilities[]=direct&exclude_visibilities[]=private`.
|
- `exclude_visibilities`: will exclude the notifications for activities with the given visibilities. The parameter accepts an array of visibility types (`public`, `unlisted`, `private`, `direct`). Usage example: `GET /api/v1/notifications?exclude_visibilities[]=direct&exclude_visibilities[]=private`.
|
||||||
|
- `with_move`: boolean, when set to `true` will include Move notifications. `false` by default.
|
||||||
|
|
||||||
## POST `/api/v1/statuses`
|
## POST `/api/v1/statuses`
|
||||||
|
|
||||||
|
|
@ -130,7 +155,10 @@ Additional parameters can be added to the JSON body/Form data:
|
||||||
- `default_scope` - the scope returned under `privacy` key in Source subentity
|
- `default_scope` - the scope returned under `privacy` key in Source subentity
|
||||||
- `pleroma_settings_store` - Opaque user settings to be saved on the backend.
|
- `pleroma_settings_store` - Opaque user settings to be saved on the backend.
|
||||||
- `skip_thread_containment` - if true, skip filtering out broken threads
|
- `skip_thread_containment` - if true, skip filtering out broken threads
|
||||||
|
- `allow_following_move` - if true, allows automatically follow moved following accounts
|
||||||
- `pleroma_background_image` - sets the background image of the user.
|
- `pleroma_background_image` - sets the background image of the user.
|
||||||
|
- `discoverable` - if true, discovery of this account in search results and other services is allowed.
|
||||||
|
- `actor_type` - the type of this account.
|
||||||
|
|
||||||
### Pleroma Settings Store
|
### Pleroma Settings Store
|
||||||
Pleroma has mechanism that allows frontends to save blobs of json for each user on the backend. This can be used to save frontend-specific settings for a user that the backend does not need to know about.
|
Pleroma has mechanism that allows frontends to save blobs of json for each user on the backend. This can be used to save frontend-specific settings for a user that the backend does not need to know about.
|
||||||
|
|
|
||||||
|
|
@ -70,59 +70,6 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi
|
||||||
* Response: JSON. Returns `{"status": "success"}` if the account was successfully disabled, `{"error": "[error message]"}` otherwise
|
* Response: JSON. Returns `{"status": "success"}` if the account was successfully disabled, `{"error": "[error message]"}` otherwise
|
||||||
* Example response: `{"error": "Invalid password."}`
|
* Example response: `{"error": "Invalid password."}`
|
||||||
|
|
||||||
## `/api/account/register`
|
|
||||||
### Register a new user
|
|
||||||
* Method `POST`
|
|
||||||
* Authentication: not required
|
|
||||||
* Params:
|
|
||||||
* `nickname`
|
|
||||||
* `fullname`
|
|
||||||
* `bio`
|
|
||||||
* `email`
|
|
||||||
* `password`
|
|
||||||
* `confirm`
|
|
||||||
* `captcha_solution`: optional, contains provider-specific captcha solution,
|
|
||||||
* `captcha_token`: optional, contains provider-specific captcha token
|
|
||||||
* `token`: invite token required when the registrations aren't public.
|
|
||||||
* Response: JSON. Returns a user object on success, otherwise returns `{"error": "error_msg"}`
|
|
||||||
* Example response:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"background_image": null,
|
|
||||||
"cover_photo": "https://pleroma.soykaf.com/images/banner.png",
|
|
||||||
"created_at": "Tue Dec 18 16:55:56 +0000 2018",
|
|
||||||
"default_scope": "public",
|
|
||||||
"description": "blushy-crushy fediverse idol + pleroma dev\nlet's be friends \nぷれろまの生徒会長。謎の外人。日本語OK. \n公主病.",
|
|
||||||
"description_html": "blushy-crushy fediverse idol + pleroma dev.<br />let's be friends <br />ぷれろまの生徒会長。謎の外人。日本語OK. <br />公主病.",
|
|
||||||
"favourites_count": 0,
|
|
||||||
"fields": [],
|
|
||||||
"followers_count": 0,
|
|
||||||
"following": false,
|
|
||||||
"follows_you": false,
|
|
||||||
"friends_count": 0,
|
|
||||||
"id": 6,
|
|
||||||
"is_local": true,
|
|
||||||
"locked": false,
|
|
||||||
"name": "lain",
|
|
||||||
"name_html": "lain",
|
|
||||||
"no_rich_text": false,
|
|
||||||
"pleroma": {
|
|
||||||
"tags": []
|
|
||||||
},
|
|
||||||
"profile_image_url": "https://pleroma.soykaf.com/images/avi.png",
|
|
||||||
"profile_image_url_https": "https://pleroma.soykaf.com/images/avi.png",
|
|
||||||
"profile_image_url_original": "https://pleroma.soykaf.com/images/avi.png",
|
|
||||||
"profile_image_url_profile_size": "https://pleroma.soykaf.com/images/avi.png",
|
|
||||||
"rights": {
|
|
||||||
"delete_others_notice": false
|
|
||||||
},
|
|
||||||
"screen_name": "lain",
|
|
||||||
"statuses_count": 0,
|
|
||||||
"statusnet_blocking": false,
|
|
||||||
"statusnet_profile_url": "https://pleroma.soykaf.com/users/lain"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## `/api/pleroma/admin/`…
|
## `/api/pleroma/admin/`…
|
||||||
See [Admin-API](admin_api.md)
|
See [Admin-API](admin_api.md)
|
||||||
|
|
||||||
|
|
@ -302,6 +249,7 @@ See [Admin-API](admin_api.md)
|
||||||
* `follows`: BOOLEAN field, receives notifications from people the user follows
|
* `follows`: BOOLEAN field, receives notifications from people the user follows
|
||||||
* `remote`: BOOLEAN field, receives notifications from people on remote instances
|
* `remote`: BOOLEAN field, receives notifications from people on remote instances
|
||||||
* `local`: BOOLEAN field, receives notifications from people on the local instance
|
* `local`: BOOLEAN field, receives notifications from people on the local instance
|
||||||
|
* `privacy_option`: BOOLEAN field. When set to true, it removes the contents of a message from the push notification.
|
||||||
* Response: JSON. Returns `{"status": "success"}` if the update was successful, otherwise returns `{"error": "error_msg"}`
|
* Response: JSON. Returns `{"status": "success"}` if the update was successful, otherwise returns `{"error": "error_msg"}`
|
||||||
|
|
||||||
## `/api/pleroma/healthcheck`
|
## `/api/pleroma/healthcheck`
|
||||||
|
|
@ -479,3 +427,35 @@ The status posting endpoint takes an additional parameter, `in_reply_to_conversa
|
||||||
* `artist`: the artist of the media playing [optional]
|
* `artist`: the artist of the media playing [optional]
|
||||||
* `length`: the length of the media playing [optional]
|
* `length`: the length of the media playing [optional]
|
||||||
* Response: the newly created media metadata entity representing the Listen activity
|
* Response: the newly created media metadata entity representing the Listen activity
|
||||||
|
|
||||||
|
# Emoji Reactions
|
||||||
|
|
||||||
|
Emoji reactions work a lot like favourites do. They make it possible to react to a post with a single emoji character.
|
||||||
|
|
||||||
|
## `POST /api/v1/pleroma/statuses/:id/react_with_emoji`
|
||||||
|
### React to a post with a unicode emoji
|
||||||
|
* Method: `POST`
|
||||||
|
* Authentication: required
|
||||||
|
* Params: `emoji`: A single character unicode emoji
|
||||||
|
* Response: JSON, the status.
|
||||||
|
|
||||||
|
## `POST /api/v1/pleroma/statuses/:id/unreact_with_emoji`
|
||||||
|
### Remove a reaction to a post with a unicode emoji
|
||||||
|
* Method: `POST`
|
||||||
|
* Authentication: required
|
||||||
|
* Params: `emoji`: A single character unicode emoji
|
||||||
|
* Response: JSON, the status.
|
||||||
|
|
||||||
|
## `GET /api/v1/pleroma/statuses/:id/emoji_reactions_by`
|
||||||
|
### Get an object of emoji to account mappings with accounts that reacted to the post
|
||||||
|
* Method: `GET`
|
||||||
|
* Authentication: optional
|
||||||
|
* Params: None
|
||||||
|
* Response: JSON, a list of emoji/account list tuples, sorted by emoji insertion date, in ascending order, e.g, the first emoji in the list is the oldest.
|
||||||
|
* Example Response:
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{"emoji": "😀", "count": 2, "accounts": [{"id" => "xyz.."...}, {"id" => "zyx..."}]},
|
||||||
|
{"emoji": "☕", "count": 1, "accounts": [{"id" => "abc..."}]}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
|
||||||
79
docs/admin/config.md
Normal file
79
docs/admin/config.md
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
# Configuring instance
|
||||||
|
You can configure your instance from admin interface. You need account with admin rights and little change in config file, which will allow settings configuration from database.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
config :pleroma, configurable_from_database: true
|
||||||
|
```
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
Settings are stored in database and are applied in `runtime` after each change. Most of the settings take effect immediately, except some, which need instance reboot. These settings are needed in `compile time`, that's why settings are duplicated to the file.
|
||||||
|
|
||||||
|
File with duplicated settings is located in `config/{env}.exported_from_db.exs` if pleroma is runned from source. For prod env it will be `config/prod.exported_from_db.exs`.
|
||||||
|
|
||||||
|
For releases: `/etc/pleroma/prod.exported_from_db.secret.exs` or `PLEROMA_CONFIG_PATH/prod.exported_from_db.exs`.
|
||||||
|
|
||||||
|
## How to set it up
|
||||||
|
You need to migrate your existing settings to the database. This task will migrate only added by user settings.
|
||||||
|
For example you add settings to `prod.secret.exs` file, only these settings will be migrated to database. For release it will be `/etc/pleroma/config.exs` or `PLEROMA_CONFIG_PATH`.
|
||||||
|
You can do this with mix task (all config files will remain untouched):
|
||||||
|
|
||||||
|
```sh tab="OTP"
|
||||||
|
./bin/pleroma_ctl config migrate_to_db
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.config migrate_to_db
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you can change settings in admin interface. After each save, settings from database are duplicated to the `config/{env}.exported_from_db.exs` file.
|
||||||
|
|
||||||
|
<span style="color:red">**ATTENTION**</span>
|
||||||
|
|
||||||
|
**<span style="color:red">Be careful while changing the settings. Every inaccurate configuration change can break the federation or the instance load.</span>**
|
||||||
|
|
||||||
|
*Compile time settings, which require instance reboot and can break instance loading:*
|
||||||
|
- all settings inside these keys:
|
||||||
|
- `:hackney_pools`
|
||||||
|
- `:chat`
|
||||||
|
- partially settings inside these keys:
|
||||||
|
- `:seconds_valid` in `Pleroma.Captcha`
|
||||||
|
- `:proxy_remote` in `Pleroma.Upload`
|
||||||
|
- `:upload_limit` in `:instance`
|
||||||
|
|
||||||
|
## How to dump settings from database to file
|
||||||
|
|
||||||
|
*Adding `-d` flag will delete migrated settings from database table.*
|
||||||
|
|
||||||
|
```sh tab="OTP"
|
||||||
|
./bin/pleroma_ctl config migrate_from_db [-d]
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.config migrate_from_db [-d]
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## How to completely remove it
|
||||||
|
|
||||||
|
1. Truncate or delete all values from `config` table
|
||||||
|
```sql
|
||||||
|
TRUNCATE TABLE config;
|
||||||
|
```
|
||||||
|
2. Delete `config/{env}.exported_from_db.exs`.
|
||||||
|
|
||||||
|
For `prod` env:
|
||||||
|
```bash
|
||||||
|
cd /opt/pleroma
|
||||||
|
cp config/prod.exported_from_db.exs config/exported_from_db.back
|
||||||
|
rm -rf config/prod.exported_from_db.exs
|
||||||
|
```
|
||||||
|
*If you don't want to backup settings, you can skip step with `cp` command.*
|
||||||
|
|
||||||
|
3. Set configurable_from_database to `false`.
|
||||||
|
```elixir
|
||||||
|
config :pleroma, configurable_from_database: false
|
||||||
|
```
|
||||||
|
4. Restart pleroma instance
|
||||||
|
```bash
|
||||||
|
sudo service pleroma restart
|
||||||
|
```
|
||||||
|
|
@ -3,17 +3,26 @@
|
||||||
!!! danger
|
!!! danger
|
||||||
This is a Work In Progress, not usable just yet.
|
This is a Work In Progress, not usable just yet.
|
||||||
|
|
||||||
Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl config` and in case of source installs it's
|
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
|
||||||
`mix pleroma.config`.
|
|
||||||
|
|
||||||
## Transfer config from file to DB.
|
## Transfer config from file to DB.
|
||||||
|
|
||||||
```sh
|
```sh tab="OTP"
|
||||||
$PREFIX migrate_to_db
|
./bin/pleroma_ctl config migrate_to_db
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.config migrate_to_db
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Transfer config from DB to `config/env.exported_from_db.secret.exs`
|
## Transfer config from DB to `config/env.exported_from_db.secret.exs`
|
||||||
|
|
||||||
```sh
|
To delete transfered settings from database optional flag `-d` can be used. <env> is `prod` by default.
|
||||||
$PREFIX migrate_from_db <env>
|
```sh tab="OTP"
|
||||||
|
./bin/pleroma_ctl config migrate_from_db [--env=<env>] [-d]
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.config migrate_from_db [--env=<env>] [-d]
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,20 @@
|
||||||
# Database maintenance tasks
|
# Database maintenance tasks
|
||||||
|
|
||||||
Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl database` and in case of source installs it's `mix pleroma.database`.
|
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
|
||||||
|
|
||||||
|
!!! danger
|
||||||
|
These mix tasks can take a long time to complete. Many of them were written to address specific database issues that happened because of bugs in migrations or other specific scenarios. Do not run these tasks "just in case" if everything is fine your instance.
|
||||||
|
|
||||||
## Replace embedded objects with their references
|
## Replace embedded objects with their references
|
||||||
|
|
||||||
Replaces embedded objects with references to them in the `objects` table. Only needs to be ran once if the instance was created before Pleroma 1.0.5. The reason why this is not a migration is because it could significantly increase the database size after being ran, however after this `VACUUM FULL` will be able to reclaim about 20% (really depends on what is in the database, your mileage may vary) of the db size before the migration.
|
Replaces embedded objects with references to them in the `objects` table. Only needs to be ran once if the instance was created before Pleroma 1.0.5. The reason why this is not a migration is because it could significantly increase the database size after being ran, however after this `VACUUM FULL` will be able to reclaim about 20% (really depends on what is in the database, your mileage may vary) of the db size before the migration.
|
||||||
|
|
||||||
```sh
|
```sh tab="OTP"
|
||||||
$PREFIX remove_embedded_objects [<options>]
|
./bin/pleroma_ctl database remove_embedded_objects [<options>]
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.database remove_embedded_objects [<options>]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
@ -17,11 +24,15 @@ $PREFIX remove_embedded_objects [<options>]
|
||||||
|
|
||||||
This will prune remote posts older than 90 days (configurable with [`config :pleroma, :instance, remote_post_retention_days`](../../configuration/cheatsheet.md#instance)) from the database, they will be refetched from source when accessed.
|
This will prune remote posts older than 90 days (configurable with [`config :pleroma, :instance, remote_post_retention_days`](../../configuration/cheatsheet.md#instance)) from the database, they will be refetched from source when accessed.
|
||||||
|
|
||||||
!!! note
|
!!! danger
|
||||||
The disk space will only be reclaimed after `VACUUM FULL`
|
The disk space will only be reclaimed after `VACUUM FULL`. You may run out of disk space during the execution of the task or vacuuming if you don't have about 1/3rds of the database size free.
|
||||||
|
|
||||||
```sh
|
```sh tab="OTP"
|
||||||
$PREFIX pleroma.database prune_objects [<options>]
|
./bin/pleroma_ctl database prune_objects [<options>]
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.database prune_objects [<options>]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
@ -31,18 +42,30 @@ $PREFIX pleroma.database prune_objects [<options>]
|
||||||
|
|
||||||
Can be safely re-run
|
Can be safely re-run
|
||||||
|
|
||||||
```sh
|
```sh tab="OTP"
|
||||||
$PREFIX bump_all_conversations
|
./bin/pleroma_ctl database bump_all_conversations
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.database bump_all_conversations
|
||||||
```
|
```
|
||||||
|
|
||||||
## Remove duplicated items from following and update followers count for all users
|
## Remove duplicated items from following and update followers count for all users
|
||||||
|
|
||||||
```sh
|
```sh tab="OTP"
|
||||||
$PREFIX update_users_following_followers_counts
|
./bin/pleroma_ctl database update_users_following_followers_counts
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.database update_users_following_followers_counts
|
||||||
```
|
```
|
||||||
|
|
||||||
## Fix the pre-existing "likes" collections for all objects
|
## Fix the pre-existing "likes" collections for all objects
|
||||||
|
|
||||||
```sh
|
```sh tab="OTP"
|
||||||
$PREFIX fix_likes_collections
|
./bin/pleroma_ctl database fix_likes_collections
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.database fix_likes_collections
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,25 @@
|
||||||
# Managing digest emails
|
# Managing digest emails
|
||||||
Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl digest` and in case of source installs it's `mix pleroma.digest`.
|
|
||||||
|
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
|
||||||
|
|
||||||
## Send digest email since given date (user registration date by default) ignoring user activity status.
|
## Send digest email since given date (user registration date by default) ignoring user activity status.
|
||||||
|
|
||||||
```sh
|
```sh tab="OTP"
|
||||||
$PREFIX test <nickname> [<since_date>]
|
./bin/pleroma_ctl digest test <nickname> [<since_date>]
|
||||||
```
|
```
|
||||||
|
|
||||||
Example:
|
```sh tab="From Source"
|
||||||
```sh
|
mix pleroma.digest test <nickname> [<since_date>]
|
||||||
$PREFIX test donaldtheduck 2019-05-20
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```sh tab="OTP"
|
||||||
|
./bin/pleroma_ctl digest test donaldtheduck 2019-05-20
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.digest test donaldtheduck 2019-05-20
|
||||||
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,44 @@
|
||||||
# Managing emoji packs
|
# Managing emoji packs
|
||||||
|
|
||||||
Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl emoji` and in case of source installs it's `mix pleroma.emoji`.
|
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
|
||||||
|
|
||||||
## Lists emoji packs and metadata specified in the manifest
|
## Lists emoji packs and metadata specified in the manifest
|
||||||
|
|
||||||
```sh
|
```sh tab="OTP"
|
||||||
$PREFIX ls-packs [<options>]
|
./bin/pleroma_ctl emoji ls-packs [<options>]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.emoji ls-packs [<options>]
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
- `-m, --manifest PATH/URL` - path to a custom manifest, it can either be an URL starting with `http`, in that case the manifest will be fetched from that address, or a local path
|
- `-m, --manifest PATH/URL` - path to a custom manifest, it can either be an URL starting with `http`, in that case the manifest will be fetched from that address, or a local path
|
||||||
|
|
||||||
## Fetch, verify and install the specified packs from the manifest into `STATIC-DIR/emoji/PACK-NAME`
|
## Fetch, verify and install the specified packs from the manifest into `STATIC-DIR/emoji/PACK-NAME`
|
||||||
```sh
|
|
||||||
$PREFIX get-packs [<options>] <packs>
|
```sh tab="OTP"
|
||||||
|
./bin/pleroma_ctl emoji get-packs [<options>] <packs>
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.emoji get-packs [<options>] <packs>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
- `-m, --manifest PATH/URL` - same as [`ls-packs`](#ls-packs)
|
- `-m, --manifest PATH/URL` - same as [`ls-packs`](#ls-packs)
|
||||||
|
|
||||||
## Create a new manifest entry and a file list from the specified remote pack file
|
## Create a new manifest entry and a file list from the specified remote pack file
|
||||||
```sh
|
|
||||||
$PREFIX gen-pack PACK-URL
|
```sh tab="OTP"
|
||||||
|
./bin/pleroma_ctl emoji gen-pack PACK-URL
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.emoji gen-pack PACK-URL
|
||||||
|
```
|
||||||
|
|
||||||
Currently, only .zip archives are recognized as remote pack files and packs are therefore assumed to be zip archives. This command is intended to run interactively and will first ask you some basic questions about the pack, then download the remote file and generate an SHA256 checksum for it, then generate an emoji file list for you.
|
Currently, only .zip archives are recognized as remote pack files and packs are therefore assumed to be zip archives. This command is intended to run interactively and will first ask you some basic questions about the pack, then download the remote file and generate an SHA256 checksum for it, then generate an emoji file list for you.
|
||||||
|
|
||||||
The manifest entry will either be written to a newly created `index.json` file or appended to the existing one, *replacing* the old pack with the same name if it was in the file previously.
|
The manifest entry will either be written to a newly created `index.json` file or appended to the existing one, *replacing* the old pack with the same name if it was in the file previously.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
Every command should be ran as the `pleroma` user from it's home directory. For example if you are superuser, you would have to wrap the command in `su pleroma -s $SHELL -lc "$COMMAND"`.
|
||||||
|
|
||||||
|
??? note "From source note about `MIX_ENV`"
|
||||||
|
|
||||||
|
The `mix` command should be prefixed with the name of environment your Pleroma server is running in, usually it's `MIX_ENV=prod`
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
# Managing instance configuration
|
# Managing instance configuration
|
||||||
|
|
||||||
Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl instance` and in case of source installs it's `mix pleroma.instance`.
|
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
|
||||||
|
|
||||||
## Generate a new configuration file
|
## Generate a new configuration file
|
||||||
```sh
|
```sh tab="OTP"
|
||||||
$PREFIX gen [<options>]
|
./bin/pleroma_ctl instance gen [<options>]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.instance gen [<options>]
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
If any of the options are left unspecified, you will be prompted interactively.
|
If any of the options are left unspecified, you will be prompted interactively.
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,33 @@
|
||||||
# Managing relays
|
# Managing relays
|
||||||
|
|
||||||
Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl relay` and in case of source installs it's `mix pleroma.relay`.
|
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
|
||||||
|
|
||||||
## Follow a relay
|
## Follow a relay
|
||||||
```sh
|
|
||||||
$PREFIX follow <relay_url>
|
```sh tab="OTP"
|
||||||
|
./bin/pleroma_ctl relay follow <relay_url>
|
||||||
```
|
```
|
||||||
|
|
||||||
Example:
|
```sh tab="From Source"
|
||||||
```sh
|
mix pleroma.relay follow <relay_url>
|
||||||
$PREFIX follow https://example.org/relay
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Unfollow a remote relay
|
## Unfollow a remote relay
|
||||||
|
|
||||||
```sh
|
```sh tab="OTP"
|
||||||
$PREFIX unfollow <relay_url>
|
./bin/pleroma_ctl relay unfollow <relay_url>
|
||||||
```
|
```
|
||||||
|
|
||||||
Example:
|
```sh tab="From Source"
|
||||||
```sh
|
mix pleroma.relay unfollow <relay_url>
|
||||||
$PREFIX unfollow https://example.org/relay
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## List relay subscriptions
|
## List relay subscriptions
|
||||||
|
|
||||||
```sh
|
```sh tab="OTP"
|
||||||
$PREFIX list
|
./bin/pleroma_ctl relay list
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.relay list
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,16 @@
|
||||||
# Managing uploads
|
# Managing uploads
|
||||||
|
|
||||||
Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl uploads` and in case of source installs it's `mix pleroma.uploads`.
|
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
|
||||||
|
|
||||||
## Migrate uploads from local to remote storage
|
## Migrate uploads from local to remote storage
|
||||||
```sh
|
```sh tab="OTP"
|
||||||
$PREFIX migrate_local <target_uploader> [<options>]
|
./bin/pleroma_ctl uploads migrate_local <target_uploader> [<options>]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.uploads migrate_local <target_uploader> [<options>]
|
||||||
|
```
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
- `--delete` - delete local uploads after migrating them to the target uploader
|
- `--delete` - delete local uploads after migrating them to the target uploader
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,18 @@
|
||||||
# Managing users
|
# Managing users
|
||||||
|
|
||||||
Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl user` and in case of source installs it's `mix pleroma.user`.
|
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
|
||||||
|
|
||||||
## Create a user
|
## Create a user
|
||||||
```sh
|
|
||||||
$PREFIX new <nickname> <email> [<options>]
|
```sh tab="OTP"
|
||||||
|
./bin/pleroma_ctl user new <email> [<options>]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.user new <email> [<options>]
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
- `--name <name>` - the user's display name
|
- `--name <name>` - the user's display name
|
||||||
- `--bio <bio>` - the user's bio
|
- `--bio <bio>` - the user's bio
|
||||||
|
|
@ -15,80 +21,160 @@ $PREFIX new <nickname> <email> [<options>]
|
||||||
- `--admin`/`--no-admin` - whether the user should be an admin
|
- `--admin`/`--no-admin` - whether the user should be an admin
|
||||||
- `-y`, `--assume-yes`/`--no-assume-yes` - whether to assume yes to all questions
|
- `-y`, `--assume-yes`/`--no-assume-yes` - whether to assume yes to all questions
|
||||||
|
|
||||||
## Generate an invite link
|
## List local users
|
||||||
```sh
|
```sh tab="OTP"
|
||||||
$PREFIX invite [<options>]
|
./bin/pleroma_ctl user list
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.user list
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Generate an invite link
|
||||||
|
```sh tab="OTP"
|
||||||
|
./bin/pleroma_ctl user invite [<options>]
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.user invite [<options>]
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
- `--expires-at DATE` - last day on which token is active (e.g. "2019-04-05")
|
- `--expires-at DATE` - last day on which token is active (e.g. "2019-04-05")
|
||||||
- `--max-use NUMBER` - maximum numbers of token uses
|
- `--max-use NUMBER` - maximum numbers of token uses
|
||||||
|
|
||||||
## List generated invites
|
## List generated invites
|
||||||
```sh
|
```sh tab="OTP"
|
||||||
$PREFIX invites
|
./bin/pleroma_ctl user invites
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.user invites
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Revoke invite
|
## Revoke invite
|
||||||
```sh
|
```sh tab="OTP"
|
||||||
$PREFIX revoke_invite <token_or_id>
|
./bin/pleroma_ctl user revoke_invite <token_or_id>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.user revoke_invite <token_or_id>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Delete a user
|
## Delete a user
|
||||||
```sh
|
```sh tab="OTP"
|
||||||
$PREFIX rm <nickname>
|
./bin/pleroma_ctl user rm <nickname>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.user rm <nickname>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Delete user's posts and interactions
|
## Delete user's posts and interactions
|
||||||
```sh
|
```sh tab="OTP"
|
||||||
$PREFIX delete_activities <nickname>
|
./bin/pleroma_ctl user delete_activities <nickname>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.user delete_activities <nickname>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Sign user out from all applications (delete user's OAuth tokens and authorizations)
|
## Sign user out from all applications (delete user's OAuth tokens and authorizations)
|
||||||
```sh
|
```sh tab="OTP"
|
||||||
$PREFIX sign_out <nickname>
|
./bin/pleroma_ctl user sign_out <nickname>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.user sign_out <nickname>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Deactivate or activate a user
|
## Deactivate or activate a user
|
||||||
```sh
|
```sh tab="OTP"
|
||||||
$PREFIX toggle_activated <nickname>
|
./bin/pleroma_ctl user toggle_activated <nickname>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.user toggle_activated <nickname>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Unsubscribe local users from a user and deactivate the user
|
## Unsubscribe local users from a user and deactivate the user
|
||||||
```sh
|
```sh tab="OTP"
|
||||||
$PREFIX unsubscribe NICKNAME
|
./bin/pleroma_ctl user unsubscribe NICKNAME
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.user unsubscribe NICKNAME
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Unsubscribe local users from an instance and deactivate all accounts on it
|
## Unsubscribe local users from an instance and deactivate all accounts on it
|
||||||
```sh
|
```sh tab="OTP"
|
||||||
$PREFIX unsubscribe_all_from_instance <instance>
|
./bin/pleroma_ctl user unsubscribe_all_from_instance <instance>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.user unsubscribe_all_from_instance <instance>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Create a password reset link for user
|
## Create a password reset link for user
|
||||||
```sh
|
```sh tab="OTP"
|
||||||
$PREFIX reset_password <nickname>
|
./bin/pleroma_ctl user reset_password <nickname>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Set the value of the given user's settings
|
```sh tab="From Source"
|
||||||
```sh
|
mix pleroma.user reset_password <nickname>
|
||||||
$PREFIX set <nickname> [<options>]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Set the value of the given user's settings
|
||||||
|
```sh tab="OTP"
|
||||||
|
./bin/pleroma_ctl user set <nickname> [<options>]
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.user set <nickname> [<options>]
|
||||||
|
```
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
- `--locked`/`--no-locked` - whether the user should be locked
|
- `--locked`/`--no-locked` - whether the user should be locked
|
||||||
- `--moderator`/`--no-moderator` - whether the user should be a moderator
|
- `--moderator`/`--no-moderator` - whether the user should be a moderator
|
||||||
- `--admin`/`--no-admin` - whether the user should be an admin
|
- `--admin`/`--no-admin` - whether the user should be an admin
|
||||||
|
|
||||||
## Add tags to a user
|
## Add tags to a user
|
||||||
```sh
|
```sh tab="OTP"
|
||||||
$PREFIX tag <nickname> <tags>
|
./bin/pleroma_ctl user tag <nickname> <tags>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.user tag <nickname> <tags>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Delete tags from a user
|
## Delete tags from a user
|
||||||
```sh
|
```sh tab="OTP"
|
||||||
$PREFIX untag <nickname> <tags>
|
./bin/pleroma_ctl user untag <nickname> <tags>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Toggle confirmation status of the user
|
```sh tab="From Source"
|
||||||
```sh
|
mix pleroma.user untag <nickname> <tags>
|
||||||
$PREFIX toggle_confirmed <nickname>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Toggle confirmation status of the user
|
||||||
|
```sh tab="OTP"
|
||||||
|
./bin/pleroma_ctl user toggle_confirmed <nickname>
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh tab="From Source"
|
||||||
|
mix pleroma.user toggle_confirmed <nickname>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,31 +0,0 @@
|
||||||
# How to activate user recommendation (Who to follow panel)
|
|
||||||

|
|
||||||
|
|
||||||
To show the *who to follow* panel, edit `config/prod.secret.exs` in the Pleroma backend. Following code activates the *who to follow* panel:
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
config :pleroma, :suggestions,
|
|
||||||
enabled: true,
|
|
||||||
third_party_engine:
|
|
||||||
"http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-suggestions-api.cgi?{{host}}+{{user}}",
|
|
||||||
timeout: 300_000,
|
|
||||||
limit: 40,
|
|
||||||
web: "https://vinayaka.distsn.org"
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
`config/config.exs` already includes this code, but `enabled:` is `false`.
|
|
||||||
|
|
||||||
`/api/v1/suggestions` is also provided when *who to follow* panel is enabled.
|
|
||||||
|
|
||||||
For advanced customization, following code shows the newcomers of the fediverse at the *who to follow* panel:
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
config :pleroma, :suggestions,
|
|
||||||
enabled: true,
|
|
||||||
third_party_engine:
|
|
||||||
"http://vinayaka.distsn.org/cgi-bin/vinayaka-user-new-suggestions-api.cgi?{{host}}+{{user}}",
|
|
||||||
timeout: 60_000,
|
|
||||||
limit: 40,
|
|
||||||
web: "https://vinayaka.distsn.org/user-new.html"
|
|
||||||
```
|
|
||||||
|
|
@ -1,274 +0,0 @@
|
||||||
# Installing on CentOS 7
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
This guide is a step-by-step installation guide for CentOS 7. It also assumes that you have administrative rights, either as root or a user with [sudo permissions](https://www.digitalocean.com/community/tutorials/how-to-create-a-sudo-user-on-centos-quickstart). If you want to run this guide with root, ignore the `sudo` at the beginning of the lines, unless it calls a user like `sudo -Hu pleroma`; in this case, use `su <username> -s $SHELL -c 'command'` instead.
|
|
||||||
|
|
||||||
### Required packages
|
|
||||||
|
|
||||||
* `postgresql` (9,6+, CentOS 7 comes with 9.2, we will install version 11 in this guide)
|
|
||||||
* `elixir` (1.5+)
|
|
||||||
* `erlang`
|
|
||||||
* `erlang-parsetools`
|
|
||||||
* `erlang-xmerl`
|
|
||||||
* `git`
|
|
||||||
* Development Tools
|
|
||||||
|
|
||||||
#### Optional packages used in this guide
|
|
||||||
|
|
||||||
* `nginx` (preferred, example configs for other reverse proxies can be found in the repo)
|
|
||||||
* `certbot` (or any other ACME client for Let’s Encrypt certificates)
|
|
||||||
|
|
||||||
### Prepare the system
|
|
||||||
|
|
||||||
* First update the system, if not already done:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo yum update
|
|
||||||
```
|
|
||||||
|
|
||||||
* Install some of the above mentioned programs:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo yum install wget git unzip
|
|
||||||
```
|
|
||||||
|
|
||||||
* Install development tools:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo yum group install "Development Tools"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Install Elixir and Erlang
|
|
||||||
|
|
||||||
* Add the EPEL repo:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo yum install epel-release
|
|
||||||
sudo yum -y update
|
|
||||||
```
|
|
||||||
|
|
||||||
* Install Erlang repository:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
wget -P /tmp/ https://packages.erlang-solutions.com/erlang-solutions-1.0-1.noarch.rpm
|
|
||||||
sudo rpm -Uvh erlang-solutions-1.0-1.noarch.rpm
|
|
||||||
```
|
|
||||||
|
|
||||||
* Install Erlang:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo yum install erlang erlang-parsetools erlang-xmerl
|
|
||||||
```
|
|
||||||
|
|
||||||
* Download [latest Elixir release from Github](https://github.com/elixir-lang/elixir/releases/tag/v1.8.1) (Example for the newest version at the time when this manual was written)
|
|
||||||
|
|
||||||
```shell
|
|
||||||
wget -P /tmp/ https://github.com/elixir-lang/elixir/releases/download/v1.8.1/Precompiled.zip
|
|
||||||
```
|
|
||||||
|
|
||||||
* Create folder where you want to install Elixir, we’ll use:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo mkdir -p /opt/elixir
|
|
||||||
```
|
|
||||||
|
|
||||||
* Unzip downloaded file there:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo unzip /tmp/Precompiled.zip -d /opt/elixir
|
|
||||||
```
|
|
||||||
|
|
||||||
* Create symlinks for the pre-compiled binaries:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
for e in elixir elixirc iex mix; do sudo ln -s /opt/elixir/bin/${e} /usr/local/bin/${e}; done
|
|
||||||
```
|
|
||||||
|
|
||||||
### Install PostgreSQL
|
|
||||||
|
|
||||||
* Add the Postgresql repository:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo yum install https://download.postgresql.org/pub/repos/yum/11/redhat/rhel-7-x86_64/pgdg-centos11-11-2.noarch.rpm
|
|
||||||
```
|
|
||||||
|
|
||||||
* Install the Postgresql server:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo yum install postgresql11-server postgresql11-contrib
|
|
||||||
```
|
|
||||||
|
|
||||||
* Initialize database:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo /usr/pgsql-11/bin/postgresql-11-setup initdb
|
|
||||||
```
|
|
||||||
|
|
||||||
* Open configuration file `/var/lib/pgsql/11/data/pg_hba.conf` and change the following lines from:
|
|
||||||
|
|
||||||
```plain
|
|
||||||
# IPv4 local connections:
|
|
||||||
host all all 127.0.0.1/32 ident
|
|
||||||
# IPv6 local connections:
|
|
||||||
host all all ::1/128 ident
|
|
||||||
```
|
|
||||||
|
|
||||||
to
|
|
||||||
|
|
||||||
```plain
|
|
||||||
# IPv4 local connections:
|
|
||||||
host all all 127.0.0.1/32 md5
|
|
||||||
# IPv6 local connections:
|
|
||||||
host all all ::1/128 md5
|
|
||||||
```
|
|
||||||
|
|
||||||
* Enable and start postgresql server:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo systemctl enable --now postgresql-11.service
|
|
||||||
```
|
|
||||||
|
|
||||||
### Install PleromaBE
|
|
||||||
|
|
||||||
* Add a new system user for the Pleroma service:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo useradd -r -s /bin/false -m -d /var/lib/pleroma -U pleroma
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note**: To execute a single command as the Pleroma system user, use `sudo -Hu pleroma command`. You can also switch to a shell by using `sudo -Hu pleroma $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 pleroma -s $SHELL -c 'command'` and `su -l pleroma -s $SHELL` for starting a shell.
|
|
||||||
|
|
||||||
* Git clone the PleromaBE repository and make the Pleroma user the owner of the directory:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo mkdir -p /opt/pleroma
|
|
||||||
sudo chown -R pleroma:pleroma /opt/pleroma
|
|
||||||
sudo -Hu pleroma git clone -b stable https://git.pleroma.social/pleroma/pleroma /opt/pleroma
|
|
||||||
```
|
|
||||||
|
|
||||||
* Change to the new directory:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
cd /opt/pleroma
|
|
||||||
```
|
|
||||||
|
|
||||||
* Install the dependencies for Pleroma and answer with `yes` if it asks you to install `Hex`:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo -Hu pleroma mix deps.get
|
|
||||||
```
|
|
||||||
|
|
||||||
* Generate the configuration: `sudo -Hu pleroma mix pleroma.instance gen`
|
|
||||||
* Answer with `yes` if it asks you to install `rebar3`.
|
|
||||||
* This may take some time, because parts of pleroma get compiled first.
|
|
||||||
* After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
|
|
||||||
|
|
||||||
* Check the configuration and if all looks right, rename it, so Pleroma will load it (`prod.secret.exs` for productive instance, `dev.secret.exs` for development instances):
|
|
||||||
|
|
||||||
```shell
|
|
||||||
mv config/{generated_config.exs,prod.secret.exs}
|
|
||||||
```
|
|
||||||
|
|
||||||
* The previous command creates also the file `config/setup_db.psql`, with which you can create the database:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo -Hu postgres psql -f config/setup_db.psql
|
|
||||||
```
|
|
||||||
|
|
||||||
* Now run the database migration:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo -Hu pleroma MIX_ENV=prod mix ecto.migrate
|
|
||||||
```
|
|
||||||
|
|
||||||
* Now you can start Pleroma already
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo -Hu pleroma MIX_ENV=prod mix phx.server
|
|
||||||
```
|
|
||||||
|
|
||||||
### Finalize installation
|
|
||||||
|
|
||||||
If you want to open your newly installed instance to the world, you should run nginx or some other webserver/proxy in front of Pleroma and you should consider to create a systemd service file for Pleroma.
|
|
||||||
|
|
||||||
#### Nginx
|
|
||||||
|
|
||||||
* Install nginx, if not already done:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo yum install nginx
|
|
||||||
```
|
|
||||||
|
|
||||||
* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo yum install certbot-nginx
|
|
||||||
```
|
|
||||||
|
|
||||||
and then set it up:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo mkdir -p /var/lib/letsencrypt/
|
|
||||||
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --standalone
|
|
||||||
```
|
|
||||||
|
|
||||||
If that doesn’t work, make sure, that nginx is not already running. If it still doesn’t work, try setting up nginx first (change ssl “on” to “off” and try again).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
* Copy the example nginx configuration to the nginx folder
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/conf.d/pleroma.conf
|
|
||||||
```
|
|
||||||
|
|
||||||
* Before starting nginx edit the configuration and change it to your needs (e.g. change servername, change cert paths)
|
|
||||||
* Enable and start nginx:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo systemctl enable --now nginx
|
|
||||||
```
|
|
||||||
|
|
||||||
If you need to renew the certificate in the future, uncomment the relevant location block in the nginx config and run:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --webroot -w /var/lib/letsencrypt/
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Other webserver/proxies
|
|
||||||
|
|
||||||
You can find example configurations for them in `/opt/pleroma/installation/`.
|
|
||||||
|
|
||||||
#### Systemd service
|
|
||||||
|
|
||||||
* Copy example service file
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo cp /opt/pleroma/installation/pleroma.service /etc/systemd/system/pleroma.service
|
|
||||||
```
|
|
||||||
|
|
||||||
* Edit the service file and make sure that all paths fit your installation
|
|
||||||
* Enable and start `pleroma.service`:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo systemctl enable --now pleroma.service
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Create your first user
|
|
||||||
|
|
||||||
If your instance is up and running, you can create your first user with administrative rights with the following task:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress> --admin
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Further reading
|
|
||||||
|
|
||||||
* [Backup your instance](../administration/backup.md)
|
|
||||||
* [Hardening your instance](../configuration/hardening.md)
|
|
||||||
* [How to activate mediaproxy](../configuration/howto_mediaproxy.md)
|
|
||||||
* [Updating your instance](../administration/updating.md)
|
|
||||||
|
|
||||||
## Questions
|
|
||||||
|
|
||||||
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
|
|
||||||
|
|
@ -1,42 +1,28 @@
|
||||||
# Switching a from-source install to OTP releases
|
# Switching a from-source install to OTP releases
|
||||||
|
|
||||||
## What are OTP releases?
|
## What are OTP releases?
|
||||||
OTP releases are as close as you can get to binary releases with Erlang/Elixir. The release is self-contained, and provides everything needed to boot it, it is easily administered via the provided shell script to open up a remote console, start/stop/restart the release, start in the background, send remote commands, and more.
|
OTP releases are as close as you can get to binary releases with Erlang/Elixir. The release is self-contained, and provides everything needed to boot it, it is easily administered via the provided shell script to open up a remote console, start/stop/restart the release, start in the background, send remote commands, and more.
|
||||||
### Can I still run the develop branch if I decide to use them?
|
|
||||||
Yes, we produce builds for every commit in `develop`. However `develop` is considered unstable, please don't use it in production because of faster access to new features, unless you need them as an app developer.
|
|
||||||
## Why would one want to switch?
|
|
||||||
Benefits of OTP releases over from-source installs include:
|
|
||||||
* **Less space used.** OTP releases come without source code, build tools, have docs and debug symbols stripped from the compiled bytecode and do not cointain tests, docs, revision history.
|
|
||||||
* **Minimal system dependencies.** Excluding the database and reverse proxy, only `curl`, `unzip` and `ncurses` are needed to download and run the release. Because Erlang runtime and Elixir are shipped with Pleroma, one can use the latest BEAM optimizations and Pleroma features, without having to worry about outdated system repos or a missing `erlang-*` package.
|
|
||||||
* **Potentially less bugs and better performance.** This extends on the previous point, because we have control over exactly what gets shipped, we can tweak the VM arguments and forget about weird bugs due to Erlang/Elixir version mismatches.
|
|
||||||
* **Faster and less bug-prone mix tasks.** On a from-source install one has to wait untill a new Pleroma node is started for each mix task and they execute outside of the instance context (for example if a user was deleted via a mix task, the instance will have no knowledge of that and continue to display status count and follows before the cache expires). Mix tasks in OTP releases are executed by calling into a running instance via RPC, which solves both of these problems.
|
|
||||||
|
|
||||||
### Sounds great, how do I switch?
|
|
||||||
Currently we support Linux machines with GNU (e.g. Debian, Ubuntu) or musl (e.g. Alpine) libc and `x86_64`, `aarch64` or `armv7l` CPUs. If you are unsure, check the [Detecting flavour](otp_en.md#detecting-flavour) section in OTP install guide. If your platform is supported, proceed with the guide, if not check the [My platform is not supported](#my-platform-is-not-supported) section.
|
|
||||||
### I don't think it is worth the effort, can I stay on a from-source install?
|
|
||||||
Yes, currently there are no plans to deprecate them.
|
|
||||||
|
|
||||||
### My platform is not supported
|
|
||||||
If you think your platform is a popular choice for running Pleroma instances, or has the potential to become one, you can [file an issue on our Gitlab](https://git.pleroma.social/pleroma/pleroma/issues/new). If not, guides on how to build and update releases by yourself will be available soon.
|
|
||||||
## Pre-requisites
|
## Pre-requisites
|
||||||
You will be running commands as root. If you aren't root already, please elevate your priviledges by executing `sudo su`/`su`.
|
You will be running commands as root. If you aren't root already, please elevate your priviledges by executing `sudo su`/`su`.
|
||||||
|
|
||||||
The system needs to have `curl` and `unzip` installed for downloading and unpacking release builds.
|
The system needs to have `curl` and `unzip` installed for downloading and unpacking release builds.
|
||||||
|
|
||||||
Debian/Ubuntu:
|
```sh tab="Alpine"
|
||||||
```sh
|
apk add curl unzip
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh tab="Debian/Ubuntu"
|
||||||
apt install curl unzip
|
apt install curl unzip
|
||||||
```
|
```
|
||||||
Alpine:
|
|
||||||
```
|
|
||||||
apk add curl unzip
|
|
||||||
|
|
||||||
```
|
|
||||||
## Moving content out of the application directory
|
## Moving content out of the application directory
|
||||||
When using OTP releases the application directory changes with every version so it would be a bother to keep content there (and also dangerous unless `--no-rm` option is used when updating). Fortunately almost all paths in Pleroma are configurable, so it is possible to move them out of there.
|
When using OTP releases the application directory changes with every version so it would be a bother to keep content there (and also dangerous unless `--no-rm` option is used when updating). Fortunately almost all paths in Pleroma are configurable, so it is possible to move them out of there.
|
||||||
|
|
||||||
Pleroma should be stopped before proceeding.
|
Pleroma should be stopped before proceeding.
|
||||||
|
|
||||||
### Moving uploads/custom public files directory
|
### Moving uploads/custom public files directory
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Create uploads directory and set proper permissions (skip if using a remote uploader)
|
# Create uploads directory and set proper permissions (skip if using a remote uploader)
|
||||||
# Note: It does not have to be `/var/lib/pleroma/uploads`, you can configure it to be something else later
|
# Note: It does not have to be `/var/lib/pleroma/uploads`, you can configure it to be something else later
|
||||||
|
|
@ -92,8 +78,8 @@ Before proceeding, get the flavour from [Detecting flavour](otp_en.md#detecting-
|
||||||
rm -r ~pleroma/*
|
rm -r ~pleroma/*
|
||||||
|
|
||||||
# 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 `arm64-musl` the command will be
|
# For example if the flavour is `amd64-musl` the command will be
|
||||||
export FLAVOUR="arm64-musl"
|
export FLAVOUR="amd64-musl"
|
||||||
|
|
||||||
# Clone the release build into a temporary directory and unpack it
|
# Clone the release build into a temporary directory and unpack it
|
||||||
# Replace `stable` with `unstable` if you want to run the unstable branch
|
# Replace `stable` with `unstable` if you want to run the unstable branch
|
||||||
|
|
@ -124,8 +110,15 @@ OTP releases have different service files than from-source installs so they need
|
||||||
|
|
||||||
**Warning:** The service files assume pleroma user's home directory is `/opt/pleroma`, please make sure all paths fit your installation.
|
**Warning:** The service files assume pleroma user's home directory is `/opt/pleroma`, please make sure all paths fit your installation.
|
||||||
|
|
||||||
Debian/Ubuntu:
|
```sh tab="Alpine"
|
||||||
```sh
|
# Copy the service into a proper directory
|
||||||
|
cp -f ~pleroma/installation/init.d/pleroma /etc/init.d/pleroma
|
||||||
|
|
||||||
|
# Start pleroma
|
||||||
|
rc-service pleroma start
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh tab="Debian/Ubuntu"
|
||||||
# Copy the service into a proper directory
|
# Copy the service into a proper directory
|
||||||
cp ~pleroma/installation/pleroma.service /etc/systemd/system/pleroma.service
|
cp ~pleroma/installation/pleroma.service /etc/systemd/system/pleroma.service
|
||||||
|
|
||||||
|
|
@ -139,14 +132,6 @@ systemctl reenable pleroma
|
||||||
systemctl start pleroma
|
systemctl start pleroma
|
||||||
```
|
```
|
||||||
|
|
||||||
Alpine:
|
|
||||||
```sh
|
|
||||||
# Copy the service into a proper directory
|
|
||||||
cp -f ~pleroma/installation/init.d/pleroma /etc/init.d/pleroma
|
|
||||||
|
|
||||||
# Start pleroma
|
|
||||||
rc-service pleroma start
|
|
||||||
```
|
|
||||||
## Running mix tasks
|
## Running mix tasks
|
||||||
Refer to [Running mix tasks](otp_en.md#running-mix-tasks) section from OTP release installation guide.
|
Refer to [Running mix tasks](otp_en.md#running-mix-tasks) section from OTP release installation guide.
|
||||||
## Updating
|
## Updating
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
# Installing on OpenBSD
|
# Installing on OpenBSD
|
||||||
This guide describes the installation and configuration of pleroma (and the required software to run it) on a single OpenBSD 6.4 server.
|
|
||||||
|
This guide describes the installation and configuration of pleroma (and the required software to run it) on a single OpenBSD 6.6 server.
|
||||||
|
|
||||||
For any additional information regarding commands and configuration files mentioned here, check the man pages [online](https://man.openbsd.org/) or directly on your server with the man command.
|
For any additional information regarding commands and configuration files mentioned here, check the man pages [online](https://man.openbsd.org/) or directly on your server with the man command.
|
||||||
|
|
||||||
#### Required software
|
#### Required software
|
||||||
|
|
||||||
The following packages need to be installed:
|
The following packages need to be installed:
|
||||||
|
|
||||||
* elixir
|
* elixir
|
||||||
* gmake
|
* gmake
|
||||||
* ImageMagick
|
* ImageMagick
|
||||||
|
|
@ -12,7 +16,10 @@ The following packages need to be installed:
|
||||||
* postgresql-contrib
|
* postgresql-contrib
|
||||||
|
|
||||||
To install them, run the following command (with doas or as root):
|
To install them, run the following command (with doas or as root):
|
||||||
`pkg_add elixir gmake ImageMagick git postgresql-server postgresql-contrib`
|
|
||||||
|
```
|
||||||
|
pkg_add elixir gmake ImageMagick git postgresql-server postgresql-contrib
|
||||||
|
```
|
||||||
|
|
||||||
Pleroma requires a reverse proxy, OpenBSD has relayd in base (and is used in this guide) and packages/ports are available for nginx (www/nginx) and apache (www/apache-httpd). Independently of the reverse proxy, [acme-client(1)](https://man.openbsd.org/acme-client) can be used to get a certificate from Let's Encrypt.
|
Pleroma requires a reverse proxy, OpenBSD has relayd in base (and is used in this guide) and packages/ports are available for nginx (www/nginx) and apache (www/apache-httpd). Independently of the reverse proxy, [acme-client(1)](https://man.openbsd.org/acme-client) can be used to get a certificate from Let's Encrypt.
|
||||||
|
|
||||||
|
|
@ -31,9 +38,14 @@ Create the \_pleroma user, assign it the pleroma login class and create its home
|
||||||
#### Clone pleroma's directory
|
#### Clone pleroma's directory
|
||||||
Enter a shell as the \_pleroma user. As root, run `su _pleroma -;cd`. Then clone the repository with `git clone -b stable https://git.pleroma.social/pleroma/pleroma.git`. Pleroma is now installed in /home/\_pleroma/pleroma/, it will be configured and started at the end of this guide.
|
Enter a shell as the \_pleroma user. As root, run `su _pleroma -;cd`. Then clone the repository with `git clone -b stable https://git.pleroma.social/pleroma/pleroma.git`. Pleroma is now installed in /home/\_pleroma/pleroma/, it will be configured and started at the end of this guide.
|
||||||
|
|
||||||
#### Postgresql
|
#### PostgreSQL
|
||||||
Start a shell as the \_postgresql user (as root run `su _postgresql -` then run the `initdb` command to initialize postgresql:
|
Start a shell as the \_postgresql user (as root run `su _postgresql -` then run the `initdb` command to initialize postgresql:
|
||||||
If you wish to not use the default location for postgresql's data (/var/postgresql/data), add the following switch at the end of the command: `-D <path>` and modify the `datadir` variable in the /etc/rc.d/postgresql script.
|
You will need to specify pgdata directory to the default (/var/postgresql/data) with the `-D <path>` and set the user to postgres with the `-U <username>` flag. This can be done as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
initdb -D /var/postgresql/data -U postgres
|
||||||
|
```
|
||||||
|
If you are not using the default directory, you will have to update the `datadir` variable in the /etc/rc.d/postgresql script.
|
||||||
|
|
||||||
When this is done, enable postgresql so that it starts on boot and start it. As root, run:
|
When this is done, enable postgresql so that it starts on boot and start it. As root, run:
|
||||||
```
|
```
|
||||||
|
|
@ -44,6 +56,7 @@ To check that it started properly and didn't fail right after starting, you can
|
||||||
|
|
||||||
#### httpd
|
#### httpd
|
||||||
httpd will have three fuctions:
|
httpd will have three fuctions:
|
||||||
|
|
||||||
* redirect requests trying to reach the instance over http to the https URL
|
* redirect requests trying to reach the instance over http to the https URL
|
||||||
* serve a robots.txt file
|
* serve a robots.txt file
|
||||||
* get Let's Encrypt certificates, with acme-client
|
* get Let's Encrypt certificates, with acme-client
|
||||||
|
|
@ -73,10 +86,9 @@ server "default" {
|
||||||
}
|
}
|
||||||
|
|
||||||
types {
|
types {
|
||||||
include "/usr/share/misc/mime.types"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Do not forget to change *\<IPv4/6 address\>* to your server's address(es). If httpd should only listen on one protocol family, comment one of the two first *listen* options.
|
Do not forget to change *<IPv4/6 address\>* to your server's address(es). If httpd should only listen on one protocol family, comment one of the two first *listen* options.
|
||||||
|
|
||||||
Create the /var/www/htdocs/local/ folder and write the content of your robots.txt in /var/www/htdocs/local/robots.txt.
|
Create the /var/www/htdocs/local/ folder and write the content of your robots.txt in /var/www/htdocs/local/robots.txt.
|
||||||
Check the configuration with `httpd -n`, if it is OK enable and start httpd (as root):
|
Check the configuration with `httpd -n`, if it is OK enable and start httpd (as root):
|
||||||
|
|
@ -95,7 +107,7 @@ Insert the following configuration in /etc/acme-client.conf:
|
||||||
|
|
||||||
authority letsencrypt-<domain name> {
|
authority letsencrypt-<domain name> {
|
||||||
#agreement url "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf"
|
#agreement url "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf"
|
||||||
api url "https://acme-v01.api.letsencrypt.org/directory"
|
api url "https://acme-v02.api.letsencrypt.org/directory"
|
||||||
account key "/etc/acme/letsencrypt-privkey-<domain name>.pem"
|
account key "/etc/acme/letsencrypt-privkey-<domain name>.pem"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,7 +119,7 @@ domain <domain name> {
|
||||||
challengedir "/var/www/acme/"
|
challengedir "/var/www/acme/"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Replace *\<domain name\>* by the domain name you'll use for your instance. As root, run `acme-client -n` to check the config, then `acme-client -ADv <domain name>` to create account and domain keys, and request a certificate for the first time.
|
Replace *<domain name\>* by the domain name you'll use for your instance. As root, run `acme-client -n` to check the config, then `acme-client -ADv <domain name>` to create account and domain keys, and request a certificate for the first time.
|
||||||
Make acme-client run everyday by adding it in /etc/daily.local. As root, run the following command: `echo "acme-client <domain name>" >> /etc/daily.local`.
|
Make acme-client run everyday by adding it in /etc/daily.local. As root, run the following command: `echo "acme-client <domain name>" >> /etc/daily.local`.
|
||||||
|
|
||||||
Relayd will look for certificates and keys based on the address it listens on (see next part), the easiest way to make them available to relayd is to create a link, as root run:
|
Relayd will look for certificates and keys based on the address it listens on (see next part), the easiest way to make them available to relayd is to create a link, as root run:
|
||||||
|
|
@ -169,7 +181,7 @@ relay wwwtls {
|
||||||
forward to <httpd_server> port 80 check http "/robots.txt" code 200
|
forward to <httpd_server> port 80 check http "/robots.txt" code 200
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Again, change *\<IPv4/6 address\>* to your server's address(es) and comment one of the two *listen* options if needed. Also change *wss://CHANGEME.tld* to *wss://\<your instance's domain name\>*.
|
Again, change *<IPv4/6 address\>* to your server's address(es) and comment one of the two *listen* options if needed. Also change *wss://CHANGEME.tld* to *wss://<your instance's domain name\>*.
|
||||||
Check the configuration with `relayd -n`, if it is OK enable and start relayd (as root):
|
Check the configuration with `relayd -n`, if it is OK enable and start relayd (as root):
|
||||||
```
|
```
|
||||||
rcctl enable relayd
|
rcctl enable relayd
|
||||||
|
|
@ -202,17 +214,19 @@ pass in quick on $if inet6 proto icmp6 to ($if) icmp6-type { echoreq unreach par
|
||||||
pass in quick on $if proto tcp to ($if) port { http https } # relayd/httpd
|
pass in quick on $if proto tcp to ($if) port { http https } # relayd/httpd
|
||||||
pass in quick on $if proto tcp from $authorized_ssh_clients to ($if) port ssh
|
pass in quick on $if proto tcp from $authorized_ssh_clients to ($if) port ssh
|
||||||
```
|
```
|
||||||
Replace *\<network interface\>* by your server's network interface name (which you can get with ifconfig). Consider replacing the content of the authorized\_ssh\_clients macro by, for exemple, your home IP address, to avoid SSH connection attempts from bots.
|
Replace *<network interface\>* by your server's network interface name (which you can get with ifconfig). Consider replacing the content of the authorized\_ssh\_clients macro by, for exemple, your home IP address, to avoid SSH connection attempts from bots.
|
||||||
|
|
||||||
Check pf's configuration by running `pfctl -nf /etc/pf.conf`, load it with `pfctl -f /etc/pf.conf` and enable pf at boot with `rcctl enable pf`.
|
Check pf's configuration by running `pfctl -nf /etc/pf.conf`, load it with `pfctl -f /etc/pf.conf` and enable pf at boot with `rcctl enable pf`.
|
||||||
|
|
||||||
#### Configure and start pleroma
|
#### Configure and start pleroma
|
||||||
Enter a shell as \_pleroma (as root `su _pleroma -`) and enter pleroma's installation directory (`cd ~/pleroma/`).
|
Enter a shell as \_pleroma (as root `su _pleroma -`) and enter pleroma's installation directory (`cd ~/pleroma/`).
|
||||||
|
|
||||||
Then follow the main installation guide:
|
Then follow the main installation guide:
|
||||||
|
|
||||||
* run `mix deps.get`
|
* run `mix deps.get`
|
||||||
* run `mix pleroma.instance gen` and enter your instance's information when asked
|
* run `mix pleroma.instance gen` and enter your instance's information when asked
|
||||||
* copy config/generated\_config.exs to config/prod.secret.exs. The default values should be sufficient but you should edit it and check that everything seems OK.
|
* copy config/generated\_config.exs to config/prod.secret.exs. The default values should be sufficient but you should edit it and check that everything seems OK.
|
||||||
* exit your current shell back to a root one and run `psql -U postgres -f /home/_pleroma/config/setup_db.psql` to setup the database.
|
* exit your current shell back to a root one and run `psql -U postgres -f /home/_pleroma/pleroma/config/setup_db.psql` to setup the database.
|
||||||
* return to a \_pleroma shell into pleroma's installation directory (`su _pleroma -;cd ~/pleroma`) and run `MIX_ENV=prod mix ecto.migrate`
|
* return to a \_pleroma shell into pleroma's installation directory (`su _pleroma -;cd ~/pleroma`) and run `MIX_ENV=prod mix ecto.migrate`
|
||||||
|
|
||||||
As \_pleroma in /home/\_pleroma/pleroma, you can now run `LC_ALL=en_US.UTF-8 MIX_ENV=prod mix phx.server` to start your instance.
|
As \_pleroma in /home/\_pleroma/pleroma, you can now run `LC_ALL=en_US.UTF-8 MIX_ENV=prod mix phx.server` to start your instance.
|
||||||
|
|
@ -220,3 +234,11 @@ In another SSH session/tmux window, check that it is working properly by running
|
||||||
|
|
||||||
##### Starting pleroma at boot
|
##### Starting pleroma at boot
|
||||||
An rc script to automatically start pleroma at boot hasn't been written yet, it can be run in a tmux session (tmux is in base).
|
An rc script to automatically start pleroma at boot hasn't been written yet, it can be run in a tmux session (tmux is in base).
|
||||||
|
|
||||||
|
|
||||||
|
#### Create administrative user
|
||||||
|
|
||||||
|
If your instance is up and running, you can create your first user with administrative rights with the following command as the \_pleroma user.
|
||||||
|
```
|
||||||
|
LC_ALL=en_US.UTF-8 MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress> --admin
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
You will be running commands as root. If you aren't root already, please elevate your priviledges by executing `sudo su`/`su`.
|
You will be running commands as root. If you aren't root already, please elevate your priviledges by executing `sudo su`/`su`.
|
||||||
|
|
||||||
While in theory OTP releases are possbile to install on any compatible machine, for the sake of simplicity this guide focuses only on Debian/Ubuntu/Alpine.
|
While in theory OTP releases are possbile to install on any compatible machine, for the sake of simplicity this guide focuses only on Debian/Ubuntu and Alpine.
|
||||||
|
|
||||||
### Detecting flavour
|
### Detecting flavour
|
||||||
|
|
||||||
|
|
@ -20,6 +20,7 @@ If your platform is supported the output will contain the flavour string, you wi
|
||||||
### Installing the required packages
|
### Installing the required packages
|
||||||
|
|
||||||
Other than things bundled in the OTP release Pleroma depends on:
|
Other than things bundled in the OTP release Pleroma depends on:
|
||||||
|
|
||||||
* curl (to download the release build)
|
* curl (to download the release build)
|
||||||
* unzip (needed to unpack release builds)
|
* unzip (needed to unpack release builds)
|
||||||
* ncurses (ERTS won't run without it)
|
* ncurses (ERTS won't run without it)
|
||||||
|
|
@ -27,29 +28,26 @@ Other than things bundled in the OTP release Pleroma depends on:
|
||||||
* nginx (could be swapped with another reverse proxy but this guide covers only it)
|
* nginx (could be swapped with another reverse proxy but this guide covers only it)
|
||||||
* certbot (for Let's Encrypt certificates, could be swapped with another ACME client, but this guide covers only it)
|
* certbot (for Let's Encrypt certificates, could be swapped with another ACME client, but this guide covers only it)
|
||||||
|
|
||||||
Debian/Ubuntu:
|
```sh tab="Alpine"
|
||||||
```sh
|
|
||||||
apt install curl unzip libncurses5 postgresql postgresql-contrib nginx certbot
|
|
||||||
```
|
|
||||||
Alpine:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
echo "http://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories
|
echo "http://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories
|
||||||
apk update
|
apk update
|
||||||
apk add curl unzip ncurses postgresql postgresql-contrib nginx certbot
|
apk add curl unzip ncurses postgresql postgresql-contrib nginx certbot
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```sh tab="Debian/Ubuntu"
|
||||||
|
apt install curl unzip libncurses5 postgresql postgresql-contrib nginx certbot
|
||||||
|
```
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
### Configuring PostgreSQL
|
### Configuring PostgreSQL
|
||||||
#### (Optional) Installing RUM indexes
|
#### (Optional) Installing RUM indexes
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
It is recommended to use PostgreSQL v11 or newer. We have seen some minor issues with lower PostgreSQL versions.
|
||||||
|
|
||||||
RUM indexes are an alternative indexing scheme that is not included in PostgreSQL by default. You can read more about them on the [Configuration page](../configuration/cheatsheet.md#rum-indexing-for-full-text-search). They are completely optional and most of the time are not worth it, especially if you are running a single user instance (unless you absolutely need ordered search results).
|
RUM indexes are an alternative indexing scheme that is not included in PostgreSQL by default. You can read more about them on the [Configuration page](../configuration/cheatsheet.md#rum-indexing-for-full-text-search). They are completely optional and most of the time are not worth it, especially if you are running a single user instance (unless you absolutely need ordered search results).
|
||||||
|
|
||||||
Debian/Ubuntu (available only on Buster/19.04):
|
```sh tab="Alpine"
|
||||||
```sh
|
|
||||||
apt install postgresql-11-rum
|
|
||||||
```
|
|
||||||
Alpine:
|
|
||||||
```sh
|
|
||||||
apk add git build-base postgresql-dev
|
apk add git build-base postgresql-dev
|
||||||
git clone https://github.com/postgrespro/rum /tmp/rum
|
git clone https://github.com/postgrespro/rum /tmp/rum
|
||||||
cd /tmp/rum
|
cd /tmp/rum
|
||||||
|
|
@ -58,25 +56,31 @@ make USE_PGXS=1 install
|
||||||
cd
|
cd
|
||||||
rm -r /tmp/rum
|
rm -r /tmp/rum
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```sh tab="Debian/Ubuntu"
|
||||||
|
# Available only on Buster/19.04
|
||||||
|
apt install postgresql-11-rum
|
||||||
|
```
|
||||||
|
|
||||||
#### (Optional) Performance configuration
|
#### (Optional) Performance configuration
|
||||||
For optimal performance, you may use [PGTune](https://pgtune.leopard.in.ua), don't forget to restart postgresql after editing the configuration
|
For optimal performance, you may use [PGTune](https://pgtune.leopard.in.ua), don't forget to restart postgresql after editing the configuration
|
||||||
|
|
||||||
Debian/Ubuntu:
|
```sh tab="Alpine"
|
||||||
```sh
|
|
||||||
systemctl restart postgresql
|
|
||||||
```
|
|
||||||
Alpine:
|
|
||||||
```sh
|
|
||||||
rc-service postgresql restart
|
rc-service postgresql restart
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```sh tab="Debian/Ubuntu"
|
||||||
|
systemctl restart postgresql
|
||||||
|
```
|
||||||
|
|
||||||
### Installing Pleroma
|
### Installing Pleroma
|
||||||
```sh
|
```sh
|
||||||
# Create the Pleroma user
|
# Create a Pleroma user
|
||||||
adduser --system --shell /bin/false --home /opt/pleroma pleroma
|
adduser --system --shell /bin/false --home /opt/pleroma pleroma
|
||||||
|
|
||||||
# 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 `arm64-musl` the command will be
|
# For example if the flavour is `amd64-musl` the command will be
|
||||||
export FLAVOUR="arm64-musl"
|
export FLAVOUR="amd64-musl"
|
||||||
|
|
||||||
# Clone the release build into a temporary directory and unpack it
|
# Clone the release build into a temporary directory and unpack it
|
||||||
su pleroma -s $SHELL -lc "
|
su pleroma -s $SHELL -lc "
|
||||||
|
|
@ -129,49 +133,52 @@ su pleroma -s $SHELL -lc "./bin/pleroma stop"
|
||||||
|
|
||||||
### Setting up nginx and getting Let's Encrypt SSL certificaties
|
### Setting up nginx and getting Let's Encrypt SSL certificaties
|
||||||
|
|
||||||
|
#### Get a Let's Encrypt certificate
|
||||||
```sh
|
```sh
|
||||||
# Get a Let's Encrypt certificate
|
|
||||||
certbot certonly --standalone --preferred-challenges http -d yourinstance.tld
|
certbot certonly --standalone --preferred-challenges http -d yourinstance.tld
|
||||||
|
```
|
||||||
|
|
||||||
# Copy the Pleroma nginx configuration to the nginx folder
|
#### Copy Pleroma nginx configuration to the nginx folder
|
||||||
# The location of nginx configs is dependent on the distro
|
|
||||||
|
|
||||||
# For Debian/Ubuntu:
|
The location of nginx configs is dependent on the distro
|
||||||
|
|
||||||
|
```sh tab="Alpine"
|
||||||
|
cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/conf.d/pleroma.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh tab="Debian/Ubuntu"
|
||||||
cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/sites-available/pleroma.nginx
|
cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/sites-available/pleroma.nginx
|
||||||
ln -s /etc/nginx/sites-available/pleroma.nginx /etc/nginx/sites-enabled/pleroma.nginx
|
ln -s /etc/nginx/sites-available/pleroma.nginx /etc/nginx/sites-enabled/pleroma.nginx
|
||||||
# For Alpine:
|
```
|
||||||
cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/conf.d/pleroma.conf
|
|
||||||
# If your distro does not have either of those you can append
|
|
||||||
# `include /etc/nginx/pleroma.conf` to the end of the http section in /etc/nginx/nginx.conf and
|
|
||||||
cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/pleroma.conf
|
|
||||||
|
|
||||||
# Edit the nginx config replacing example.tld with your (sub)domain
|
If your distro does not have either of those you can append `include /etc/nginx/pleroma.conf` to the end of the http section in /etc/nginx/nginx.conf and
|
||||||
|
```sh
|
||||||
|
cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/pleroma.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Edit the nginx config
|
||||||
|
```sh
|
||||||
|
# Replace example.tld with your (sub)domain
|
||||||
$EDITOR path-to-nginx-config
|
$EDITOR path-to-nginx-config
|
||||||
|
|
||||||
# Verify that the config is valid
|
# Verify that the config is valid
|
||||||
nginx -t
|
nginx -t
|
||||||
|
```
|
||||||
|
#### Start nginx
|
||||||
|
|
||||||
# Start nginx
|
```sh tab="Alpine"
|
||||||
# For Debian/Ubuntu:
|
|
||||||
systemctl start nginx
|
|
||||||
# For Alpine:
|
|
||||||
rc-service nginx start
|
rc-service nginx start
|
||||||
```
|
```
|
||||||
|
|
||||||
At this point if you open your (sub)domain in a browser you should see a 502 error, that's because pleroma is not started yet.
|
```sh tab="Debian/Ubuntu"
|
||||||
|
systemctl start nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
At this point if you open your (sub)domain in a browser you should see a 502 error, that's because Pleroma is not started yet.
|
||||||
|
|
||||||
### Setting up a system service
|
### Setting up a system service
|
||||||
Debian/Ubuntu:
|
|
||||||
```sh
|
|
||||||
# Copy the service into a proper directory
|
|
||||||
cp /opt/pleroma/installation/pleroma.service /etc/systemd/system/pleroma.service
|
|
||||||
|
|
||||||
# Start pleroma and enable it on boot
|
```sh tab="Alpine"
|
||||||
systemctl start pleroma
|
|
||||||
systemctl enable pleroma
|
|
||||||
```
|
|
||||||
Alpine:
|
|
||||||
```sh
|
|
||||||
# Copy the service into a proper directory
|
# Copy the service into a proper directory
|
||||||
cp /opt/pleroma/installation/init.d/pleroma /etc/init.d/pleroma
|
cp /opt/pleroma/installation/init.d/pleroma /etc/init.d/pleroma
|
||||||
|
|
||||||
|
|
@ -180,13 +187,22 @@ rc-service pleroma start
|
||||||
rc-update add pleroma
|
rc-update add pleroma
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```sh tab="Debian/Ubuntu"
|
||||||
|
# Copy the service into a proper directory
|
||||||
|
cp /opt/pleroma/installation/pleroma.service /etc/systemd/system/pleroma.service
|
||||||
|
|
||||||
|
# Start pleroma and enable it on boot
|
||||||
|
systemctl start pleroma
|
||||||
|
systemctl enable pleroma
|
||||||
|
```
|
||||||
|
|
||||||
If everything worked, you should see Pleroma-FE when visiting your domain. If that didn't happen, try reviewing the installation steps, starting Pleroma in the foreground and seeing if there are any errrors.
|
If everything worked, you should see Pleroma-FE when visiting your domain. If that didn't happen, try reviewing the installation steps, starting Pleroma in the foreground and seeing if there are any errrors.
|
||||||
|
|
||||||
Still doesn't work? Feel free to contact us on [#pleroma on freenode](https://webchat.freenode.net/?channels=%23pleroma) or via matrix at <https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org>, you can also [file an issue on our Gitlab](https://git.pleroma.social/pleroma/pleroma/issues/new)
|
Still doesn't work? Feel free to contact us on [#pleroma on freenode](https://irc.pleroma.social) or via matrix at <https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org>, you can also [file an issue on our Gitlab](https://git.pleroma.social/pleroma/pleroma-support/issues/new)
|
||||||
|
|
||||||
## Post installation
|
## Post installation
|
||||||
|
|
||||||
### Setting up auto-renew Let's Encrypt certificate
|
### Setting up auto-renew of the Let's Encrypt certificate
|
||||||
```sh
|
```sh
|
||||||
# Create the directory for webroot challenges
|
# Create the directory for webroot challenges
|
||||||
mkdir -p /var/lib/letsencrypt
|
mkdir -p /var/lib/letsencrypt
|
||||||
|
|
@ -197,25 +213,8 @@ $EDITOR path-to-nginx-config
|
||||||
# Verify that the config is valid
|
# Verify that the config is valid
|
||||||
nginx -t
|
nginx -t
|
||||||
```
|
```
|
||||||
Debian/Ubuntu:
|
|
||||||
```sh
|
|
||||||
# Restart nginx
|
|
||||||
systemctl restart nginx
|
|
||||||
|
|
||||||
# Ensure the webroot menthod and post hook is working
|
```sh tab="Alpine"
|
||||||
certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --dry-run --post-hook 'systemctl nginx reload'
|
|
||||||
|
|
||||||
# Add it to the daily cron
|
|
||||||
echo '#!/bin/sh
|
|
||||||
certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --post-hook "systemctl reload nginx"
|
|
||||||
' > /etc/cron.daily/renew-pleroma-cert
|
|
||||||
chmod +x /etc/cron.daily/renew-pleroma-cert
|
|
||||||
|
|
||||||
# If everything worked the output should contain /etc/cron.daily/renew-pleroma-cert
|
|
||||||
run-parts --test /etc/cron.daily
|
|
||||||
```
|
|
||||||
Alpine:
|
|
||||||
```sh
|
|
||||||
# Restart nginx
|
# Restart nginx
|
||||||
rc-service nginx restart
|
rc-service nginx restart
|
||||||
|
|
||||||
|
|
@ -232,15 +231,25 @@ certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --
|
||||||
' > /etc/periodic/daily/renew-pleroma-cert
|
' > /etc/periodic/daily/renew-pleroma-cert
|
||||||
chmod +x /etc/periodic/daily/renew-pleroma-cert
|
chmod +x /etc/periodic/daily/renew-pleroma-cert
|
||||||
|
|
||||||
# If everything worked this should output /etc/periodic/daily/renew-pleroma-cert
|
# If everything worked the output should contain /etc/cron.daily/renew-pleroma-cert
|
||||||
run-parts --test /etc/periodic/daily
|
run-parts --test /etc/periodic/daily
|
||||||
```
|
```
|
||||||
### Running mix tasks
|
|
||||||
Throughout the wiki and guides there is a lot of references to mix tasks. Since `mix` is a build tool, you can't just call `mix pleroma.task`, instead you should call `pleroma_ctl` stripping pleroma/ecto namespace.
|
|
||||||
|
|
||||||
So for example, if the task is `mix pleroma.user set admin --admin`, you should run it like this:
|
```sh tab="Debian/Ubuntu"
|
||||||
```sh
|
# Restart nginx
|
||||||
su pleroma -s $SHELL -lc "./bin/pleroma_ctl user set admin --admin"
|
systemctl restart nginx
|
||||||
|
|
||||||
|
# Ensure the webroot menthod and post hook is working
|
||||||
|
certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --dry-run --post-hook 'systemctl reload nginx'
|
||||||
|
|
||||||
|
# Add it to the daily cron
|
||||||
|
echo '#!/bin/sh
|
||||||
|
certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --post-hook "systemctl reload nginx"
|
||||||
|
' > /etc/cron.daily/renew-pleroma-cert
|
||||||
|
chmod +x /etc/cron.daily/renew-pleroma-cert
|
||||||
|
|
||||||
|
# If everything worked the output should contain /etc/cron.daily/renew-pleroma-cert
|
||||||
|
run-parts --test /etc/cron.daily
|
||||||
```
|
```
|
||||||
|
|
||||||
## Create your first user and set as admin
|
## Create your first user and set as admin
|
||||||
|
|
@ -266,4 +275,3 @@ But you should **always check the release notes/changelog** in case there are co
|
||||||
* [Backup your instance](../administration/backup.md)
|
* [Backup your instance](../administration/backup.md)
|
||||||
* [Hardening your instance](../configuration/hardening.md)
|
* [Hardening your instance](../configuration/hardening.md)
|
||||||
* [How to activate mediaproxy](../configuration/howto_mediaproxy.md)
|
* [How to activate mediaproxy](../configuration/howto_mediaproxy.md)
|
||||||
* [Updating your instance](../administration/updating.md)
|
|
||||||
|
|
|
||||||
|
|
@ -3,53 +3,63 @@
|
||||||
Pleroma is a federated social networking platform, compatible with GNU social, Mastodon and other OStatus and ActivityPub implementations. It is free software licensed under the AGPLv3.
|
Pleroma is a federated social networking platform, compatible with GNU social, Mastodon and other OStatus and ActivityPub implementations. It is free software licensed under the AGPLv3.
|
||||||
It actually consists of two components: a backend, named simply Pleroma, 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 Pleroma, and a user-facing frontend, named Pleroma-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 a instance is enough to talk to the entire fediverse!
|
One account on an instance is enough to talk to the entire fediverse!
|
||||||
|
|
||||||
## How can I use it?
|
## How can I use it?
|
||||||
|
|
||||||
Pleroma instances are already widely deployed, a list can be found here:
|
Pleroma instances are already widely deployed, a list can be found at <http://distsn.org/pleroma-instances.html>. Information on all existing fediverse instances can be found at <https://fediverse.network/>.
|
||||||
http://distsn.org/pleroma-instances.html
|
|
||||||
|
|
||||||
If you don't feel like joining an existing instance, but instead prefer to deploy your own instance, that's easy too!
|
If you don't feel like joining an existing instance, but instead prefer to deploy your own instance, that's easy too!
|
||||||
Installation instructions can be found here:
|
Installation instructions can be found in the installation section of these docs.
|
||||||
[main Pleroma wiki](/)
|
|
||||||
|
|
||||||
## I got an account, now what?
|
## I got an account, now what?
|
||||||
Great! Now you can explore the fediverse!
|
Great! Now you can explore the fediverse! Open the login page for your Pleroma instance (e.g. <https://pleroma.soykaf.com>) and login with your username and password. (If you don't have an account yet, click on Register)
|
||||||
- Open the login page for your Pleroma instance (for ex. https://pleroma.soykaf.com) and login with your username and password.
|
|
||||||
(If you don't have one yet, click on Register) :slightly_smiling_face:
|
|
||||||
|
|
||||||
At this point you will have two columns in front of you.
|
At this point you will have two columns in front of you.
|
||||||
|
|
||||||
### Left column
|
### Left column
|
||||||
- first block: here you can see your avatar, your nickname a bio, and statistics (Statuses, Following, Followers).
|
|
||||||
Under that you have a text form which allows you to post new statuses. The icon on the left is for uploading media files and attach them to your post. The number under the text form is a character counter, every instance can have a different character limit (the default is 5000).
|
- first block: here you can see your avatar, your nickname and statistics (Statuses, Following, Followers). Clicking your profile pic will open your profile.
|
||||||
If you want to mention someone, type @ + name of the person. A drop-down menu will help you in finding the right person. :slight_smile:
|
Under that you have a text form which allows you to post new statuses. The number on the bottom of the text form is a character counter, every instance can have a different character limit (the default is 5000).
|
||||||
|
If you want to mention someone, type @ + name of the person. A drop-down menu will help you in finding the right person.
|
||||||
|
Under the text form there are also several visibility options and there is the option to use rich text.
|
||||||
|
Under that the icon on the left is for uploading media files and attach them to your post. There is also an emoji-picker and an option to post a poll.
|
||||||
To post your status, simply press Submit.
|
To post your status, simply press Submit.
|
||||||
|
On the top right you will also see a wrench icon. This opens your personal settings.
|
||||||
|
|
||||||
- second block: Here you can switch between the different timelines:
|
- second block: Here you can switch between the different timelines:
|
||||||
- Timeline: all the people that you follow
|
- Timeline: all the people that you follow
|
||||||
- Mentions: all the statutes where you are mentioned
|
- Interactions: here you can switch between different timelines where there was interaction with your account. There is Mentions, Repeats and Favorites, and New follows
|
||||||
|
- Direct Messages: these are the Direct Messages sent to you
|
||||||
- Public Timeline: all the statutes from the local instance
|
- Public Timeline: all the statutes from the local instance
|
||||||
- The Whole Known Network: everything, local and remote!
|
- The Whole Known Network: all public posts the instance knows about, both local and remote!
|
||||||
|
- About: This isn't a Timeline but shows relevant info about the instance. You can find a list of the moderators and admins, Terms of Service, MRF policies and enabled features.
|
||||||
- third block: this is the Chat block, where you communicate with people on the same instance in realtime. It is local-only, for now, but we're planning to make it extendable to the entire fediverse! :sweat_smile:
|
- Optional third block: This is the Instance panel that can be activated, but is deactivated by default. It's fully customisable and by default has links to the pleroma-fe and Mastodon-fe.
|
||||||
|
|
||||||
- fourth block: This is the Notifications block, here you will get notified whenever somebody mentions you, follows you, repeats or favorites one of your statuses.
|
- fourth block: This is the Notifications block, here you will get notified whenever somebody mentions you, follows you, repeats or favorites one of your statuses.
|
||||||
|
|
||||||
### Right column
|
### Right column
|
||||||
This is where the interesting stuff happens! :slight_smile:
|
This is where the interesting stuff happens!
|
||||||
Depending on the timeline you will see different statuses, but each status has a standard structure:
|
Depending on the timeline you will see different statuses, but each status has a standard structure:
|
||||||
- Icon + name + link to profile. An optional left-arrow if it's a reply to another status (hovering will reveal the replied-to status).
|
|
||||||
- A + button on the right allows you to Expand/Collapse an entire discussion thread. It also updates in realtime!
|
|
||||||
- A binocular icon allows you to open the status on the instance where it's originating from.
|
|
||||||
- The text of the status, including mentions. If you click on a mention, it will automatically open the profile page of that person.
|
|
||||||
- Four buttons (left to right): Reply, Repeat, Favorite, Delete.
|
|
||||||
|
|
||||||
## Mastodon interface
|
- Profile pic, name and link to profile. An optional left-arrow if it's a reply to another status (hovering will reveal the replied-to status). Clicking on the profile pic will uncollapse the user's profile.
|
||||||
If the Pleroma 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! :smile:
|
- A `+` button on the right allows you to Expand/Collapse an entire discussion thread. It also updates in realtime!
|
||||||
Just add a "/web" after your instance url (for ex. https://pleroma.soycaf.com/web) and you'll end on the Mastodon web interface, but with a Pleroma backend! MAGIC! :fireworks:
|
- An arrow icon allows you to open the status on the instance where it's originating from.
|
||||||
For more information on the Mastodon interface, please look here:
|
- The text of the status, including mentions and attachements. If you click on a mention, it will automatically open the profile page of that person.
|
||||||
https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/User-guide.md
|
- Three buttons (left to right): Reply, Repeat, Favorite. There is also a forth button, this is a dropdown menu for simple moderation like muting the conversation or, if you have moderation rights, delete the status from the server.
|
||||||
|
|
||||||
|
### Top right
|
||||||
|
|
||||||
|
- The magnifier icon opens the search screen where you can search for statuses, people and hashtags. It's also possible to import statusses from remote servers by pasting the url to the post in the search field.
|
||||||
|
- The gear icon gives you general settings
|
||||||
|
- If you have admin rights, you'll see an icon that opens the admin interface
|
||||||
|
- The last icon is to log out
|
||||||
|
|
||||||
|
### Bottom right
|
||||||
|
On the bottom right you have a chatbox. Here you can communicate with people on the same instance in realtime. It is local-only, for now, but there are plans to make it extendable to the entire fediverse!
|
||||||
|
|
||||||
|
### Mastodon interface
|
||||||
|
If the Pleroma 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://pleroma.soycaf.com/web>) and you'll end on the Mastodon web interface, but with a Pleroma 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.
|
||||||
|
|
||||||
Remember, what you see is only the frontend part of Mastodon, the backend is still Pleroma.
|
Remember, what you see is only the frontend part of Mastodon, the backend is still Pleroma.
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,11 @@ defmodule Mix.Pleroma do
|
||||||
@doc "Common functions to be reused in mix tasks"
|
@doc "Common functions to be reused in mix tasks"
|
||||||
def start_pleroma do
|
def start_pleroma do
|
||||||
Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
|
Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
|
||||||
|
|
||||||
|
if Pleroma.Config.get(:env) != :test do
|
||||||
|
Application.put_env(:logger, :console, level: :debug)
|
||||||
|
end
|
||||||
|
|
||||||
{:ok, _} = Application.ensure_all_started(:pleroma)
|
{:ok, _} = Application.ensure_all_started(:pleroma)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,69 +4,147 @@
|
||||||
|
|
||||||
defmodule Mix.Tasks.Pleroma.Config do
|
defmodule Mix.Tasks.Pleroma.Config do
|
||||||
use Mix.Task
|
use Mix.Task
|
||||||
|
|
||||||
import Mix.Pleroma
|
import Mix.Pleroma
|
||||||
|
|
||||||
|
alias Pleroma.ConfigDB
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.Web.AdminAPI.Config
|
|
||||||
@shortdoc "Manages the location of the config"
|
@shortdoc "Manages the location of the config"
|
||||||
@moduledoc File.read!("docs/administration/CLI_tasks/config.md")
|
@moduledoc File.read!("docs/administration/CLI_tasks/config.md")
|
||||||
|
|
||||||
def run(["migrate_to_db"]) do
|
def run(["migrate_to_db"]) do
|
||||||
start_pleroma()
|
start_pleroma()
|
||||||
|
migrate_to_db()
|
||||||
if Pleroma.Config.get([:instance, :dynamic_configuration]) do
|
|
||||||
Application.get_all_env(:pleroma)
|
|
||||||
|> Enum.reject(fn {k, _v} -> k in [Pleroma.Repo, :env] end)
|
|
||||||
|> Enum.each(fn {k, v} ->
|
|
||||||
key = to_string(k) |> String.replace("Elixir.", "")
|
|
||||||
|
|
||||||
key =
|
|
||||||
if String.starts_with?(key, "Pleroma.") do
|
|
||||||
key
|
|
||||||
else
|
|
||||||
":" <> key
|
|
||||||
end
|
end
|
||||||
|
|
||||||
{:ok, _} = Config.update_or_create(%{group: "pleroma", key: key, value: v})
|
def run(["migrate_from_db" | options]) do
|
||||||
Mix.shell().info("#{key} is migrated.")
|
|
||||||
end)
|
|
||||||
|
|
||||||
Mix.shell().info("Settings migrated.")
|
|
||||||
else
|
|
||||||
Mix.shell().info(
|
|
||||||
"Migration is not allowed by config. You can change this behavior in instance settings."
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def run(["migrate_from_db", env, delete?]) do
|
|
||||||
start_pleroma()
|
start_pleroma()
|
||||||
|
|
||||||
delete? = if delete? == "true", do: true, else: false
|
{opts, _} =
|
||||||
|
OptionParser.parse!(options,
|
||||||
if Pleroma.Config.get([:instance, :dynamic_configuration]) do
|
strict: [env: :string, delete: :boolean],
|
||||||
config_path = "config/#{env}.exported_from_db.secret.exs"
|
aliases: [d: :delete]
|
||||||
|
|
||||||
{:ok, file} = File.open(config_path, [:write])
|
|
||||||
IO.write(file, "use Mix.Config\r\n")
|
|
||||||
|
|
||||||
Repo.all(Config)
|
|
||||||
|> Enum.each(fn config ->
|
|
||||||
IO.write(
|
|
||||||
file,
|
|
||||||
"config :#{config.group}, #{config.key}, #{inspect(Config.from_binary(config.value))}\r\n\r\n"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if delete? do
|
migrate_from_db(opts)
|
||||||
{:ok, _} = Repo.delete(config)
|
|
||||||
Mix.shell().info("#{config.key} deleted from DB.")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec migrate_to_db(Path.t() | nil) :: any()
|
||||||
|
def migrate_to_db(file_path \\ nil) do
|
||||||
|
if Pleroma.Config.get([:configurable_from_database]) do
|
||||||
|
config_file =
|
||||||
|
if file_path do
|
||||||
|
file_path
|
||||||
|
else
|
||||||
|
if Pleroma.Config.get(:release) do
|
||||||
|
Pleroma.Config.get(:config_path)
|
||||||
|
else
|
||||||
|
"config/#{Pleroma.Config.get(:env)}.secret.exs"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
do_migrate_to_db(config_file)
|
||||||
|
else
|
||||||
|
migration_error()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_migrate_to_db(config_file) do
|
||||||
|
if File.exists?(config_file) do
|
||||||
|
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;")
|
||||||
|
Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;")
|
||||||
|
|
||||||
|
custom_config =
|
||||||
|
config_file
|
||||||
|
|> read_file()
|
||||||
|
|> elem(0)
|
||||||
|
|
||||||
|
custom_config
|
||||||
|
|> Keyword.keys()
|
||||||
|
|> Enum.each(&create(&1, custom_config))
|
||||||
|
else
|
||||||
|
shell_info("To migrate settings, you must define custom settings in #{config_file}.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp create(group, settings) do
|
||||||
|
group
|
||||||
|
|> Pleroma.Config.Loader.filter_group(settings)
|
||||||
|
|> Enum.each(fn {key, value} ->
|
||||||
|
key = inspect(key)
|
||||||
|
{:ok, _} = ConfigDB.update_or_create(%{group: inspect(group), key: key, value: value})
|
||||||
|
|
||||||
|
shell_info("Settings for key #{key} migrated.")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
File.close(file)
|
shell_info("Settings for group :#{group} migrated.")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp migrate_from_db(opts) do
|
||||||
|
if Pleroma.Config.get([:configurable_from_database]) do
|
||||||
|
env = opts[:env] || "prod"
|
||||||
|
|
||||||
|
config_path =
|
||||||
|
if Pleroma.Config.get(:release) do
|
||||||
|
:config_path
|
||||||
|
|> Pleroma.Config.get()
|
||||||
|
|> Path.dirname()
|
||||||
|
else
|
||||||
|
"config"
|
||||||
|
end
|
||||||
|
|> Path.join("#{env}.exported_from_db.secret.exs")
|
||||||
|
|
||||||
|
file = File.open!(config_path, [:write, :utf8])
|
||||||
|
|
||||||
|
IO.write(file, config_header())
|
||||||
|
|
||||||
|
ConfigDB
|
||||||
|
|> Repo.all()
|
||||||
|
|> Enum.each(&write_and_delete(&1, file, opts[:delete]))
|
||||||
|
|
||||||
|
:ok = File.close(file)
|
||||||
System.cmd("mix", ["format", config_path])
|
System.cmd("mix", ["format", config_path])
|
||||||
else
|
else
|
||||||
Mix.shell().info(
|
migration_error()
|
||||||
"Migration is not allowed by config. You can change this behavior in instance settings."
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp migration_error do
|
||||||
|
shell_error(
|
||||||
|
"Migration is not allowed in config. You can change this behavior by setting `configurable_from_database` to true."
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if Code.ensure_loaded?(Config.Reader) do
|
||||||
|
defp config_header, do: "import Config\r\n\r\n"
|
||||||
|
defp read_file(config_file), do: Config.Reader.read_imports!(config_file)
|
||||||
|
else
|
||||||
|
defp config_header, do: "use Mix.Config\r\n\r\n"
|
||||||
|
defp read_file(config_file), do: Mix.Config.eval!(config_file)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp write_and_delete(config, file, delete?) do
|
||||||
|
config
|
||||||
|
|> write(file)
|
||||||
|
|> delete(delete?)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp write(config, file) do
|
||||||
|
value =
|
||||||
|
config.value
|
||||||
|
|> ConfigDB.from_binary()
|
||||||
|
|> inspect(limit: :infinity)
|
||||||
|
|
||||||
|
IO.write(file, "config #{config.group}, #{config.key}, #{value}\r\n\r\n")
|
||||||
|
|
||||||
|
config
|
||||||
|
end
|
||||||
|
|
||||||
|
defp delete(config, true) do
|
||||||
|
{:ok, _} = Repo.delete(config)
|
||||||
|
shell_info("#{config.key} deleted from DB.")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp delete(_config, _), do: :ok
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ defmodule Mix.Tasks.Pleroma.Docs do
|
||||||
defp do_run(implementation) do
|
defp do_run(implementation) do
|
||||||
start_pleroma()
|
start_pleroma()
|
||||||
|
|
||||||
with {descriptions, _paths} <- Mix.Config.eval!("config/description.exs"),
|
with descriptions <- Pleroma.Config.Loader.load("config/description.exs"),
|
||||||
{:ok, file_path} <-
|
{:ok, file_path} <-
|
||||||
Pleroma.Docs.Generator.process(
|
Pleroma.Docs.Generator.process(
|
||||||
implementation,
|
implementation,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
||||||
@moduledoc File.read!("docs/administration/CLI_tasks/emoji.md")
|
@moduledoc File.read!("docs/administration/CLI_tasks/emoji.md")
|
||||||
|
|
||||||
def run(["ls-packs" | args]) do
|
def run(["ls-packs" | args]) do
|
||||||
|
Mix.Pleroma.start_pleroma()
|
||||||
Application.ensure_all_started(:hackney)
|
Application.ensure_all_started(:hackney)
|
||||||
|
|
||||||
{options, [], []} = parse_global_opts(args)
|
{options, [], []} = parse_global_opts(args)
|
||||||
|
|
@ -35,6 +36,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
||||||
end
|
end
|
||||||
|
|
||||||
def run(["get-packs" | args]) do
|
def run(["get-packs" | args]) do
|
||||||
|
Mix.Pleroma.start_pleroma()
|
||||||
Application.ensure_all_started(:hackney)
|
Application.ensure_all_started(:hackney)
|
||||||
|
|
||||||
{options, pack_names, []} = parse_global_opts(args)
|
{options, pack_names, []} = parse_global_opts(args)
|
||||||
|
|
|
||||||
83
lib/mix/tasks/pleroma/notification_settings.ex
Normal file
83
lib/mix/tasks/pleroma/notification_settings.ex
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
defmodule Mix.Tasks.Pleroma.NotificationSettings do
|
||||||
|
@shortdoc "Enable&Disable privacy option for push notifications"
|
||||||
|
@moduledoc """
|
||||||
|
Example:
|
||||||
|
|
||||||
|
> mix pleroma.notification_settings --privacy-option=false --nickname-users="parallel588" # set false only for parallel588 user
|
||||||
|
> mix pleroma.notification_settings --privacy-option=true # set true for all users
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
use Mix.Task
|
||||||
|
import Mix.Pleroma
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
def run(args) do
|
||||||
|
start_pleroma()
|
||||||
|
|
||||||
|
{options, _, _} =
|
||||||
|
OptionParser.parse(
|
||||||
|
args,
|
||||||
|
strict: [
|
||||||
|
privacy_option: :boolean,
|
||||||
|
email_users: :string,
|
||||||
|
nickname_users: :string
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
privacy_option = Keyword.get(options, :privacy_option)
|
||||||
|
|
||||||
|
if not is_nil(privacy_option) do
|
||||||
|
privacy_option
|
||||||
|
|> build_query(options)
|
||||||
|
|> Pleroma.Repo.update_all([])
|
||||||
|
end
|
||||||
|
|
||||||
|
shell_info("Done")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp build_query(privacy_option, options) do
|
||||||
|
query =
|
||||||
|
from(u in Pleroma.User,
|
||||||
|
update: [
|
||||||
|
set: [
|
||||||
|
notification_settings:
|
||||||
|
fragment(
|
||||||
|
"jsonb_set(notification_settings, '{privacy_option}', ?)",
|
||||||
|
^privacy_option
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
user_emails =
|
||||||
|
options
|
||||||
|
|> Keyword.get(:email_users, "")
|
||||||
|
|> String.split(",")
|
||||||
|
|> Enum.map(&String.trim(&1))
|
||||||
|
|> Enum.reject(&(&1 == ""))
|
||||||
|
|
||||||
|
query =
|
||||||
|
if length(user_emails) > 0 do
|
||||||
|
where(query, [u], u.email in ^user_emails)
|
||||||
|
else
|
||||||
|
query
|
||||||
|
end
|
||||||
|
|
||||||
|
user_nicknames =
|
||||||
|
options
|
||||||
|
|> Keyword.get(:nickname_users, "")
|
||||||
|
|> String.split(",")
|
||||||
|
|> Enum.map(&String.trim(&1))
|
||||||
|
|> Enum.reject(&(&1 == ""))
|
||||||
|
|
||||||
|
query =
|
||||||
|
if length(user_nicknames) > 0 do
|
||||||
|
where(query, [u], u.nickname in ^user_nicknames)
|
||||||
|
else
|
||||||
|
query
|
||||||
|
end
|
||||||
|
|
||||||
|
query
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -18,6 +18,7 @@ defmodule Mix.Tasks.Pleroma.RobotsTxt do
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def run(["disallow_all"]) do
|
def run(["disallow_all"]) do
|
||||||
|
Mix.Pleroma.start_pleroma()
|
||||||
static_dir = Pleroma.Config.get([:instance, :static_dir], "instance/static/")
|
static_dir = Pleroma.Config.get([:instance, :static_dir], "instance/static/")
|
||||||
|
|
||||||
if !File.exists?(static_dir) do
|
if !File.exists?(static_dir) do
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ defmodule Mix.Tasks.Pleroma.User do
|
||||||
alias Ecto.Changeset
|
alias Ecto.Changeset
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.UserInviteToken
|
alias Pleroma.UserInviteToken
|
||||||
alias Pleroma.Web.OAuth
|
|
||||||
|
|
||||||
@shortdoc "Manages Pleroma users"
|
@shortdoc "Manages Pleroma users"
|
||||||
@moduledoc File.read!("docs/administration/CLI_tasks/user.md")
|
@moduledoc File.read!("docs/administration/CLI_tasks/user.md")
|
||||||
|
|
@ -354,8 +353,7 @@ defmodule Mix.Tasks.Pleroma.User do
|
||||||
start_pleroma()
|
start_pleroma()
|
||||||
|
|
||||||
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
|
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
|
||||||
OAuth.Token.delete_user_tokens(user)
|
User.global_sign_out(user)
|
||||||
OAuth.Authorization.delete_user_authorizations(user)
|
|
||||||
|
|
||||||
shell_info("#{nickname} signed out from all apps.")
|
shell_info("#{nickname} signed out from all apps.")
|
||||||
else
|
else
|
||||||
|
|
@ -364,6 +362,24 @@ defmodule Mix.Tasks.Pleroma.User do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def run(["list"]) do
|
||||||
|
start_pleroma()
|
||||||
|
|
||||||
|
Pleroma.User.Query.build(%{local: true})
|
||||||
|
|> Pleroma.RepoStreamer.chunk_stream(500)
|
||||||
|
|> Stream.each(fn users ->
|
||||||
|
users
|
||||||
|
|> Enum.each(fn user ->
|
||||||
|
shell_info(
|
||||||
|
"#{user.nickname} moderator: #{user.is_moderator}, admin: #{user.is_admin}, locked: #{
|
||||||
|
user.locked
|
||||||
|
}, deactivated: #{user.deactivated}"
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|> Stream.run()
|
||||||
|
end
|
||||||
|
|
||||||
defp set_moderator(user, value) do
|
defp set_moderator(user, value) do
|
||||||
{:ok, user} =
|
{:ok, user} =
|
||||||
user
|
user
|
||||||
|
|
@ -375,10 +391,7 @@ defmodule Mix.Tasks.Pleroma.User do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp set_admin(user, value) do
|
defp set_admin(user, value) do
|
||||||
{:ok, user} =
|
{:ok, user} = User.admin_api_update(user, %{is_admin: value})
|
||||||
user
|
|
||||||
|> Changeset.change(%{is_admin: value})
|
|
||||||
|> User.update_and_set_cache()
|
|
||||||
|
|
||||||
shell_info("Admin status of #{user.nickname}: #{user.is_admin}")
|
shell_info("Admin status of #{user.nickname}: #{user.is_admin}")
|
||||||
user
|
user
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ defmodule Pleroma.Activity do
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.ReportNote
|
||||||
alias Pleroma.ThreadMute
|
alias Pleroma.ThreadMute
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
|
|
@ -28,7 +29,9 @@ defmodule Pleroma.Activity do
|
||||||
"Create" => "mention",
|
"Create" => "mention",
|
||||||
"Follow" => "follow",
|
"Follow" => "follow",
|
||||||
"Announce" => "reblog",
|
"Announce" => "reblog",
|
||||||
"Like" => "favourite"
|
"Like" => "favourite",
|
||||||
|
"Move" => "move",
|
||||||
|
"EmojiReaction" => "pleroma:emoji_reaction"
|
||||||
}
|
}
|
||||||
|
|
||||||
@mastodon_to_ap_notification_types for {k, v} <- @mastodon_notification_types,
|
@mastodon_to_ap_notification_types for {k, v} <- @mastodon_notification_types,
|
||||||
|
|
@ -41,8 +44,14 @@ defmodule Pleroma.Activity do
|
||||||
field(:actor, :string)
|
field(:actor, :string)
|
||||||
field(:recipients, {:array, :string}, default: [])
|
field(:recipients, {:array, :string}, default: [])
|
||||||
field(:thread_muted?, :boolean, virtual: true)
|
field(:thread_muted?, :boolean, virtual: true)
|
||||||
|
|
||||||
|
# This is a fake relation,
|
||||||
|
# do not use outside of with_preloaded_user_actor/with_joined_user_actor
|
||||||
|
has_one(:user_actor, User, on_delete: :nothing, foreign_key: :id)
|
||||||
# This is a fake relation, do not use outside of with_preloaded_bookmark/get_bookmark
|
# This is a fake relation, do not use outside of with_preloaded_bookmark/get_bookmark
|
||||||
has_one(:bookmark, Bookmark)
|
has_one(:bookmark, Bookmark)
|
||||||
|
# This is a fake relation, do not use outside of with_preloaded_report_notes
|
||||||
|
has_many(:report_notes, ReportNote)
|
||||||
has_many(:notifications, Notification, on_delete: :delete_all)
|
has_many(:notifications, Notification, on_delete: :delete_all)
|
||||||
|
|
||||||
# Attention: this is a fake relation, don't try to preload it blindly and expect it to work!
|
# Attention: this is a fake relation, don't try to preload it blindly and expect it to work!
|
||||||
|
|
@ -86,6 +95,19 @@ defmodule Pleroma.Activity do
|
||||||
|> preload([activity, object: object], object: object)
|
|> preload([activity, object: object], object: object)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def with_joined_user_actor(query, join_type \\ :inner) do
|
||||||
|
join(query, join_type, [activity], u in User,
|
||||||
|
on: u.ap_id == activity.actor,
|
||||||
|
as: :user_actor
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def with_preloaded_user_actor(query, join_type \\ :inner) do
|
||||||
|
query
|
||||||
|
|> with_joined_user_actor(join_type)
|
||||||
|
|> preload([activity, user_actor: user_actor], user_actor: user_actor)
|
||||||
|
end
|
||||||
|
|
||||||
def with_preloaded_bookmark(query, %User{} = user) do
|
def with_preloaded_bookmark(query, %User{} = user) do
|
||||||
from([a] in query,
|
from([a] in query,
|
||||||
left_join: b in Bookmark,
|
left_join: b in Bookmark,
|
||||||
|
|
@ -96,6 +118,16 @@ defmodule Pleroma.Activity do
|
||||||
|
|
||||||
def with_preloaded_bookmark(query, _), do: query
|
def with_preloaded_bookmark(query, _), do: query
|
||||||
|
|
||||||
|
def with_preloaded_report_notes(query) do
|
||||||
|
from([a] in query,
|
||||||
|
left_join: r in ReportNote,
|
||||||
|
on: a.id == r.activity_id,
|
||||||
|
preload: [report_notes: r]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def with_preloaded_report_notes(query, _), do: query
|
||||||
|
|
||||||
def with_set_thread_muted_field(query, %User{} = user) do
|
def with_set_thread_muted_field(query, %User{} = user) do
|
||||||
from([a] in query,
|
from([a] in query,
|
||||||
left_join: tm in ThreadMute,
|
left_join: tm in ThreadMute,
|
||||||
|
|
@ -223,9 +255,10 @@ defmodule Pleroma.Activity do
|
||||||
def normalize(ap_id) when is_binary(ap_id), do: get_by_ap_id_with_object(ap_id)
|
def normalize(ap_id) when is_binary(ap_id), do: get_by_ap_id_with_object(ap_id)
|
||||||
def normalize(_), do: nil
|
def normalize(_), do: nil
|
||||||
|
|
||||||
def delete_by_ap_id(id) when is_binary(id) do
|
def delete_all_by_object_ap_id(id) when is_binary(id) do
|
||||||
id
|
id
|
||||||
|> Queries.by_object_id()
|
|> Queries.by_object_id()
|
||||||
|
|> Queries.exclude_type("Delete")
|
||||||
|> select([u], u)
|
|> select([u], u)
|
||||||
|> Repo.delete_all()
|
|> Repo.delete_all()
|
||||||
|> elem(1)
|
|> elem(1)
|
||||||
|
|
@ -237,7 +270,7 @@ defmodule Pleroma.Activity do
|
||||||
|> purge_web_resp_cache()
|
|> purge_web_resp_cache()
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_by_ap_id(_), do: nil
|
def delete_all_by_object_ap_id(_), do: nil
|
||||||
|
|
||||||
defp purge_web_resp_cache(%Activity{} = activity) do
|
defp purge_web_resp_cache(%Activity{} = activity) do
|
||||||
%{path: path} = URI.parse(activity.data["id"])
|
%{path: path} = URI.parse(activity.data["id"])
|
||||||
|
|
@ -280,10 +313,21 @@ defmodule Pleroma.Activity do
|
||||||
from(u in User.Query.build(deactivated: true), select: u.ap_id)
|
from(u in User.Query.build(deactivated: true), select: u.ap_id)
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|
|
||||||
from(activity in query,
|
Activity.Queries.exclude_authors(query, deactivated_users)
|
||||||
where: activity.actor not in ^deactivated_users
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search
|
defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search
|
||||||
|
|
||||||
|
def direct_conversation_id(activity, for_user) do
|
||||||
|
alias Pleroma.Conversation.Participation
|
||||||
|
|
||||||
|
with %{data: %{"context" => context}} when is_binary(context) <- activity,
|
||||||
|
%Pleroma.Conversation{} = conversation <- Pleroma.Conversation.get_for_ap_id(context),
|
||||||
|
%Participation{id: participation_id} <-
|
||||||
|
Participation.for_user_and_conversation(for_user, conversation) do
|
||||||
|
participation_id
|
||||||
|
else
|
||||||
|
_ -> nil
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ defmodule Pleroma.Activity.Queries do
|
||||||
@type query :: Ecto.Queryable.t() | Activity.t()
|
@type query :: Ecto.Queryable.t() | Activity.t()
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
@spec by_ap_id(query, String.t()) :: query
|
@spec by_ap_id(query, String.t()) :: query
|
||||||
def by_ap_id(query \\ Activity, ap_id) do
|
def by_ap_id(query \\ Activity, ap_id) do
|
||||||
|
|
@ -29,6 +30,11 @@ defmodule Pleroma.Activity.Queries do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec by_author(query, String.t()) :: query
|
||||||
|
def by_author(query \\ Activity, %User{ap_id: ap_id}) do
|
||||||
|
from(a in query, where: a.actor == ^ap_id)
|
||||||
|
end
|
||||||
|
|
||||||
@spec by_object_id(query, String.t() | [String.t()]) :: query
|
@spec by_object_id(query, String.t() | [String.t()]) :: query
|
||||||
def by_object_id(query \\ Activity, object_id)
|
def by_object_id(query \\ Activity, object_id)
|
||||||
|
|
||||||
|
|
@ -64,4 +70,16 @@ defmodule Pleroma.Activity.Queries do
|
||||||
where: fragment("(?)->>'type' = ?", activity.data, ^activity_type)
|
where: fragment("(?)->>'type' = ?", activity.data, ^activity_type)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec exclude_type(query, String.t()) :: query
|
||||||
|
def exclude_type(query \\ Activity, activity_type) do
|
||||||
|
from(
|
||||||
|
activity in query,
|
||||||
|
where: fragment("(?)->>'type' != ?", activity.data, ^activity_type)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def exclude_authors(query \\ Activity, actors) do
|
||||||
|
from(activity in query, where: activity.actor not in ^actors)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -26,18 +26,23 @@ defmodule Pleroma.Activity.Search do
|
||||||
|> query_with(index_type, search_query)
|
|> query_with(index_type, search_query)
|
||||||
|> maybe_restrict_local(user)
|
|> maybe_restrict_local(user)
|
||||||
|> maybe_restrict_author(author)
|
|> maybe_restrict_author(author)
|
||||||
|
|> maybe_restrict_blocked(user)
|
||||||
|> Pagination.fetch_paginated(%{"offset" => offset, "limit" => limit}, :offset)
|
|> Pagination.fetch_paginated(%{"offset" => offset, "limit" => limit}, :offset)
|
||||||
|> maybe_fetch(user, search_query)
|
|> maybe_fetch(user, search_query)
|
||||||
end
|
end
|
||||||
|
|
||||||
def maybe_restrict_author(query, %User{} = author) do
|
def maybe_restrict_author(query, %User{} = author) do
|
||||||
from([a, o] in query,
|
Activity.Queries.by_author(query, author)
|
||||||
where: a.actor == ^author.ap_id
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def maybe_restrict_author(query, _), do: query
|
def maybe_restrict_author(query, _), do: query
|
||||||
|
|
||||||
|
def maybe_restrict_blocked(query, %User{} = user) do
|
||||||
|
Activity.Queries.exclude_authors(query, User.blocked_users_ap_ids(user))
|
||||||
|
end
|
||||||
|
|
||||||
|
def maybe_restrict_blocked(query, _), do: query
|
||||||
|
|
||||||
defp restrict_public(q) do
|
defp restrict_public(q) do
|
||||||
from([a, o] in q,
|
from([a, o] in q,
|
||||||
where: fragment("?->>'type' = 'Create'", a.data),
|
where: fragment("?->>'type' = 'Create'", a.data),
|
||||||
|
|
@ -86,7 +91,7 @@ defmodule Pleroma.Activity.Search do
|
||||||
{:ok, object} <- Fetcher.fetch_object_from_id(search_query),
|
{:ok, object} <- Fetcher.fetch_object_from_id(search_query),
|
||||||
%Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
|
%Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
|
||||||
true <- Visibility.visible_for_user?(activity, user) do
|
true <- Visibility.visible_for_user?(activity, user) do
|
||||||
activities ++ [activity]
|
[activity | activities]
|
||||||
else
|
else
|
||||||
_ -> activities
|
_ -> activities
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
defmodule Pleroma.Application do
|
defmodule Pleroma.Application do
|
||||||
import Cachex.Spec
|
import Cachex.Spec
|
||||||
use Application
|
use Application
|
||||||
|
require Logger
|
||||||
|
|
||||||
@name Mix.Project.config()[:name]
|
@name Mix.Project.config()[:name]
|
||||||
@version Mix.Project.config()[:version]
|
@version Mix.Project.config()[:version]
|
||||||
|
|
@ -17,15 +18,24 @@ defmodule Pleroma.Application do
|
||||||
def repository, do: @repository
|
def repository, do: @repository
|
||||||
|
|
||||||
def user_agent do
|
def user_agent do
|
||||||
|
case Pleroma.Config.get([:http, :user_agent], :default) do
|
||||||
|
:default ->
|
||||||
info = "#{Pleroma.Web.base_url()} <#{Pleroma.Config.get([:instance, :email], "")}>"
|
info = "#{Pleroma.Web.base_url()} <#{Pleroma.Config.get([:instance, :email], "")}>"
|
||||||
named_version() <> "; " <> info
|
named_version() <> "; " <> info
|
||||||
|
|
||||||
|
custom ->
|
||||||
|
custom
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# See http://elixir-lang.org/docs/stable/elixir/Application.html
|
# See http://elixir-lang.org/docs/stable/elixir/Application.html
|
||||||
# for more information on OTP Applications
|
# for more information on OTP Applications
|
||||||
def start(_type, _args) do
|
def start(_type, _args) do
|
||||||
|
Pleroma.HTML.compile_scrubbers()
|
||||||
Pleroma.Config.DeprecationWarnings.warn()
|
Pleroma.Config.DeprecationWarnings.warn()
|
||||||
|
Pleroma.Repo.check_migrations_applied!()
|
||||||
setup_instrumenters()
|
setup_instrumenters()
|
||||||
|
load_custom_modules()
|
||||||
|
|
||||||
# Define workers and child supervisors to be supervised
|
# Define workers and child supervisors to be supervised
|
||||||
children =
|
children =
|
||||||
|
|
@ -36,7 +46,8 @@ defmodule Pleroma.Application do
|
||||||
Pleroma.Emoji,
|
Pleroma.Emoji,
|
||||||
Pleroma.Captcha,
|
Pleroma.Captcha,
|
||||||
Pleroma.Daemons.ScheduledActivityDaemon,
|
Pleroma.Daemons.ScheduledActivityDaemon,
|
||||||
Pleroma.Daemons.ActivityExpirationDaemon
|
Pleroma.Daemons.ActivityExpirationDaemon,
|
||||||
|
Pleroma.Plugs.RateLimiter.Supervisor
|
||||||
] ++
|
] ++
|
||||||
cachex_children() ++
|
cachex_children() ++
|
||||||
hackney_pool_children() ++
|
hackney_pool_children() ++
|
||||||
|
|
@ -60,6 +71,28 @@ defmodule Pleroma.Application do
|
||||||
Supervisor.start_link(children, opts)
|
Supervisor.start_link(children, opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def load_custom_modules do
|
||||||
|
dir = Pleroma.Config.get([:modules, :runtime_dir])
|
||||||
|
|
||||||
|
if dir && File.exists?(dir) do
|
||||||
|
dir
|
||||||
|
|> Pleroma.Utils.compile_dir()
|
||||||
|
|> case do
|
||||||
|
{:error, _errors, _warnings} ->
|
||||||
|
raise "Invalid custom modules"
|
||||||
|
|
||||||
|
{:ok, modules, _warnings} ->
|
||||||
|
if @env != :test do
|
||||||
|
Enum.each(modules, fn mod ->
|
||||||
|
Logger.info("Custom module loaded: #{inspect(mod)}")
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp setup_instrumenters do
|
defp setup_instrumenters do
|
||||||
require Prometheus.Registry
|
require Prometheus.Registry
|
||||||
|
|
||||||
|
|
@ -140,8 +173,6 @@ defmodule Pleroma.Application do
|
||||||
|
|
||||||
defp oauth_cleanup_child(_), do: []
|
defp oauth_cleanup_child(_), do: []
|
||||||
|
|
||||||
defp chat_child(:test, _), do: []
|
|
||||||
|
|
||||||
defp chat_child(_env, true) do
|
defp chat_child(_env, true) do
|
||||||
[Pleroma.Web.ChatChannel.ChatChannelState]
|
[Pleroma.Web.ChatChannel.ChatChannelState]
|
||||||
end
|
end
|
||||||
|
|
|
||||||
35
lib/pleroma/captcha/native.ex
Normal file
35
lib/pleroma/captcha/native.ex
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Captcha.Native do
|
||||||
|
import Pleroma.Web.Gettext
|
||||||
|
alias Pleroma.Captcha.Service
|
||||||
|
@behaviour Service
|
||||||
|
|
||||||
|
@impl Service
|
||||||
|
def new do
|
||||||
|
case Captcha.get() do
|
||||||
|
{:timeout} ->
|
||||||
|
%{error: dgettext("errors", "Captcha timeout")}
|
||||||
|
|
||||||
|
{:ok, answer_data, img_binary} ->
|
||||||
|
%{
|
||||||
|
type: :native,
|
||||||
|
token: token(),
|
||||||
|
url: "data:image/png;base64," <> Base.encode64(img_binary),
|
||||||
|
answer_data: answer_data
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl Service
|
||||||
|
def validate(_token, captcha, captcha) when not is_nil(captcha), do: :ok
|
||||||
|
def validate(_token, _captcha, _answer), do: {:error, dgettext("errors", "Invalid CAPTCHA")}
|
||||||
|
|
||||||
|
defp token do
|
||||||
|
10
|
||||||
|
|> :crypto.strong_rand_bytes()
|
||||||
|
|> Base.url_encode64(padding: false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
defmodule Pleroma.Clippy do
|
defmodule Pleroma.Clippy do
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
|
|
||||||
# No software is complete until they have a Clippy implementation.
|
# No software is complete until they have a Clippy implementation.
|
||||||
# A ballmer peak _may_ be required to change this module.
|
# A ballmer peak _may_ be required to change this module.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,4 +65,16 @@ defmodule Pleroma.Config do
|
||||||
def oauth_consumer_strategies, do: get([:auth, :oauth_consumer_strategies], [])
|
def oauth_consumer_strategies, do: get([:auth, :oauth_consumer_strategies], [])
|
||||||
|
|
||||||
def oauth_consumer_enabled?, do: oauth_consumer_strategies() != []
|
def oauth_consumer_enabled?, do: oauth_consumer_strategies() != []
|
||||||
|
|
||||||
|
def enforce_oauth_admin_scope_usage?, do: !!get([:auth, :enforce_oauth_admin_scope_usage])
|
||||||
|
|
||||||
|
def oauth_admin_scopes(scopes) when is_list(scopes) do
|
||||||
|
Enum.flat_map(
|
||||||
|
scopes,
|
||||||
|
fn scope ->
|
||||||
|
["admin:#{scope}"] ++
|
||||||
|
if enforce_oauth_admin_scope_usage?(), do: [], else: [scope]
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
414
lib/pleroma/config/config_db.ex
Normal file
414
lib/pleroma/config/config_db.ex
Normal file
|
|
@ -0,0 +1,414 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.ConfigDB do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
import Ecto.Query
|
||||||
|
import Pleroma.Web.Gettext
|
||||||
|
|
||||||
|
alias __MODULE__
|
||||||
|
alias Pleroma.Repo
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{}
|
||||||
|
|
||||||
|
@full_key_update [
|
||||||
|
{:pleroma, :ecto_repos},
|
||||||
|
{:quack, :meta},
|
||||||
|
{:mime, :types},
|
||||||
|
{:cors_plug, [:max_age, :methods, :expose, :headers]},
|
||||||
|
{:auto_linker, :opts},
|
||||||
|
{:swarm, :node_blacklist},
|
||||||
|
{:logger, :backends}
|
||||||
|
]
|
||||||
|
|
||||||
|
@full_subkey_update [
|
||||||
|
{:pleroma, :assets, :mascots},
|
||||||
|
{:pleroma, :emoji, :groups},
|
||||||
|
{:pleroma, :workers, :retries},
|
||||||
|
{:pleroma, :mrf_subchain, :match_actor},
|
||||||
|
{:pleroma, :mrf_keyword, :replace}
|
||||||
|
]
|
||||||
|
|
||||||
|
@regex ~r/^~r(?'delimiter'[\/|"'([{<]{1})(?'pattern'.+)[\/|"')\]}>]{1}(?'modifier'[uismxfU]*)/u
|
||||||
|
|
||||||
|
@delimiters ["/", "|", "\"", "'", {"(", ")"}, {"[", "]"}, {"{", "}"}, {"<", ">"}]
|
||||||
|
|
||||||
|
schema "config" do
|
||||||
|
field(:key, :string)
|
||||||
|
field(:group, :string)
|
||||||
|
field(:value, :binary)
|
||||||
|
field(:db, {:array, :string}, virtual: true, default: [])
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec get_all_as_keyword() :: keyword()
|
||||||
|
def get_all_as_keyword do
|
||||||
|
ConfigDB
|
||||||
|
|> select([c], {c.group, c.key, c.value})
|
||||||
|
|> Repo.all()
|
||||||
|
|> Enum.reduce([], fn {group, key, value}, acc ->
|
||||||
|
group = ConfigDB.from_string(group)
|
||||||
|
key = ConfigDB.from_string(key)
|
||||||
|
value = from_binary(value)
|
||||||
|
|
||||||
|
Keyword.update(acc, group, [{key, value}], &Keyword.merge(&1, [{key, value}]))
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec get_by_params(map()) :: ConfigDB.t() | nil
|
||||||
|
def get_by_params(params), do: Repo.get_by(ConfigDB, params)
|
||||||
|
|
||||||
|
@spec changeset(ConfigDB.t(), map()) :: Changeset.t()
|
||||||
|
def changeset(config, params \\ %{}) do
|
||||||
|
params = Map.put(params, :value, transform(params[:value]))
|
||||||
|
|
||||||
|
config
|
||||||
|
|> cast(params, [:key, :group, :value])
|
||||||
|
|> validate_required([:key, :group, :value])
|
||||||
|
|> unique_constraint(:key, name: :config_group_key_index)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec create(map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
|
||||||
|
def create(params) do
|
||||||
|
%ConfigDB{}
|
||||||
|
|> changeset(params)
|
||||||
|
|> Repo.insert()
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec update(ConfigDB.t(), map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
|
||||||
|
def update(%ConfigDB{} = config, %{value: value}) do
|
||||||
|
config
|
||||||
|
|> changeset(%{value: value})
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec get_db_keys(ConfigDB.t()) :: [String.t()]
|
||||||
|
def get_db_keys(%ConfigDB{} = config) do
|
||||||
|
config.value
|
||||||
|
|> ConfigDB.from_binary()
|
||||||
|
|> get_db_keys(config.key)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec get_db_keys(keyword(), any()) :: [String.t()]
|
||||||
|
def get_db_keys(value, key) do
|
||||||
|
if Keyword.keyword?(value) do
|
||||||
|
value |> Keyword.keys() |> Enum.map(&convert(&1))
|
||||||
|
else
|
||||||
|
[convert(key)]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec merge_group(atom(), atom(), keyword(), keyword()) :: keyword()
|
||||||
|
def merge_group(group, key, old_value, new_value) do
|
||||||
|
new_keys = to_map_set(new_value)
|
||||||
|
|
||||||
|
intersect_keys =
|
||||||
|
old_value |> to_map_set() |> MapSet.intersection(new_keys) |> MapSet.to_list()
|
||||||
|
|
||||||
|
merged_value = ConfigDB.merge(old_value, new_value)
|
||||||
|
|
||||||
|
@full_subkey_update
|
||||||
|
|> Enum.map(fn
|
||||||
|
{g, k, subkey} when g == group and k == key ->
|
||||||
|
if subkey in intersect_keys, do: subkey, else: []
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
[]
|
||||||
|
end)
|
||||||
|
|> List.flatten()
|
||||||
|
|> Enum.reduce(merged_value, fn subkey, acc ->
|
||||||
|
Keyword.put(acc, subkey, new_value[subkey])
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp to_map_set(keyword) do
|
||||||
|
keyword
|
||||||
|
|> Keyword.keys()
|
||||||
|
|> MapSet.new()
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec sub_key_full_update?(atom(), atom(), [Keyword.key()]) :: boolean()
|
||||||
|
def sub_key_full_update?(group, key, subkeys) do
|
||||||
|
Enum.any?(@full_subkey_update, fn {g, k, subkey} ->
|
||||||
|
g == group and k == key and subkey in subkeys
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec merge(keyword(), keyword()) :: keyword()
|
||||||
|
def merge(config1, config2) when is_list(config1) and is_list(config2) do
|
||||||
|
Keyword.merge(config1, config2, fn _, app1, app2 ->
|
||||||
|
if Keyword.keyword?(app1) and Keyword.keyword?(app2) do
|
||||||
|
Keyword.merge(app1, app2, &deep_merge/3)
|
||||||
|
else
|
||||||
|
app2
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp deep_merge(_key, value1, value2) do
|
||||||
|
if Keyword.keyword?(value1) and Keyword.keyword?(value2) do
|
||||||
|
Keyword.merge(value1, value2, &deep_merge/3)
|
||||||
|
else
|
||||||
|
value2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec update_or_create(map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
|
||||||
|
def update_or_create(params) do
|
||||||
|
search_opts = Map.take(params, [:group, :key])
|
||||||
|
|
||||||
|
with %ConfigDB{} = config <- ConfigDB.get_by_params(search_opts),
|
||||||
|
{:partial_update, true, config} <-
|
||||||
|
{:partial_update, can_be_partially_updated?(config), config},
|
||||||
|
old_value <- from_binary(config.value),
|
||||||
|
transformed_value <- do_transform(params[:value]),
|
||||||
|
{:can_be_merged, true, config} <- {:can_be_merged, is_list(transformed_value), config},
|
||||||
|
new_value <-
|
||||||
|
merge_group(
|
||||||
|
ConfigDB.from_string(config.group),
|
||||||
|
ConfigDB.from_string(config.key),
|
||||||
|
old_value,
|
||||||
|
transformed_value
|
||||||
|
) do
|
||||||
|
ConfigDB.update(config, %{value: new_value})
|
||||||
|
else
|
||||||
|
{reason, false, config} when reason in [:partial_update, :can_be_merged] ->
|
||||||
|
ConfigDB.update(config, params)
|
||||||
|
|
||||||
|
nil ->
|
||||||
|
ConfigDB.create(params)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp can_be_partially_updated?(%ConfigDB{} = config), do: not only_full_update?(config)
|
||||||
|
|
||||||
|
defp only_full_update?(%ConfigDB{} = config) do
|
||||||
|
config_group = ConfigDB.from_string(config.group)
|
||||||
|
config_key = ConfigDB.from_string(config.key)
|
||||||
|
|
||||||
|
Enum.any?(@full_key_update, fn
|
||||||
|
{group, key} when is_list(key) ->
|
||||||
|
config_group == group and config_key in key
|
||||||
|
|
||||||
|
{group, key} ->
|
||||||
|
config_group == group and config_key == key
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec delete(map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
|
||||||
|
def delete(params) do
|
||||||
|
search_opts = Map.delete(params, :subkeys)
|
||||||
|
|
||||||
|
with %ConfigDB{} = config <- ConfigDB.get_by_params(search_opts),
|
||||||
|
{config, sub_keys} when is_list(sub_keys) <- {config, params[:subkeys]},
|
||||||
|
old_value <- from_binary(config.value),
|
||||||
|
keys <- Enum.map(sub_keys, &do_transform_string(&1)),
|
||||||
|
{:partial_remove, config, new_value} when new_value != [] <-
|
||||||
|
{:partial_remove, config, Keyword.drop(old_value, keys)} do
|
||||||
|
ConfigDB.update(config, %{value: new_value})
|
||||||
|
else
|
||||||
|
{:partial_remove, config, []} ->
|
||||||
|
Repo.delete(config)
|
||||||
|
|
||||||
|
{config, nil} ->
|
||||||
|
Repo.delete(config)
|
||||||
|
|
||||||
|
nil ->
|
||||||
|
err =
|
||||||
|
dgettext("errors", "Config with params %{params} not found", params: inspect(params))
|
||||||
|
|
||||||
|
{:error, err}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec from_binary(binary()) :: term()
|
||||||
|
def from_binary(binary), do: :erlang.binary_to_term(binary)
|
||||||
|
|
||||||
|
@spec from_binary_with_convert(binary()) :: any()
|
||||||
|
def from_binary_with_convert(binary) do
|
||||||
|
binary
|
||||||
|
|> from_binary()
|
||||||
|
|> do_convert()
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec from_string(String.t()) :: atom() | no_return()
|
||||||
|
def from_string(string), do: do_transform_string(string)
|
||||||
|
|
||||||
|
@spec convert(any()) :: any()
|
||||||
|
def convert(entity), do: do_convert(entity)
|
||||||
|
|
||||||
|
defp do_convert(entity) when is_list(entity) do
|
||||||
|
for v <- entity, into: [], do: do_convert(v)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_convert(%Regex{} = entity), do: inspect(entity)
|
||||||
|
|
||||||
|
defp do_convert(entity) when is_map(entity) do
|
||||||
|
for {k, v} <- entity, into: %{}, do: {do_convert(k), do_convert(v)}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_convert({:proxy_url, {type, :localhost, port}}) do
|
||||||
|
%{"tuple" => [":proxy_url", %{"tuple" => [do_convert(type), "localhost", port]}]}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_convert({:proxy_url, {type, host, port}}) when is_tuple(host) do
|
||||||
|
ip =
|
||||||
|
host
|
||||||
|
|> :inet_parse.ntoa()
|
||||||
|
|> to_string()
|
||||||
|
|
||||||
|
%{
|
||||||
|
"tuple" => [
|
||||||
|
":proxy_url",
|
||||||
|
%{"tuple" => [do_convert(type), ip, port]}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_convert({:proxy_url, {type, host, port}}) do
|
||||||
|
%{
|
||||||
|
"tuple" => [
|
||||||
|
":proxy_url",
|
||||||
|
%{"tuple" => [do_convert(type), to_string(host), port]}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_convert({:partial_chain, entity}), do: %{"tuple" => [":partial_chain", inspect(entity)]}
|
||||||
|
|
||||||
|
defp do_convert(entity) when is_tuple(entity) do
|
||||||
|
value =
|
||||||
|
entity
|
||||||
|
|> Tuple.to_list()
|
||||||
|
|> do_convert()
|
||||||
|
|
||||||
|
%{"tuple" => value}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_convert(entity) when is_boolean(entity) or is_number(entity) or is_nil(entity) do
|
||||||
|
entity
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_convert(entity)
|
||||||
|
when is_atom(entity) and entity in [:"tlsv1.1", :"tlsv1.2", :"tlsv1.3"] do
|
||||||
|
":#{entity}"
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_convert(entity) when is_atom(entity), do: inspect(entity)
|
||||||
|
|
||||||
|
defp do_convert(entity) when is_binary(entity), do: entity
|
||||||
|
|
||||||
|
@spec transform(any()) :: binary() | no_return()
|
||||||
|
def transform(entity) when is_binary(entity) or is_map(entity) or is_list(entity) do
|
||||||
|
entity
|
||||||
|
|> do_transform()
|
||||||
|
|> to_binary()
|
||||||
|
end
|
||||||
|
|
||||||
|
def transform(entity), do: to_binary(entity)
|
||||||
|
|
||||||
|
@spec transform_with_out_binary(any()) :: any()
|
||||||
|
def transform_with_out_binary(entity), do: do_transform(entity)
|
||||||
|
|
||||||
|
@spec to_binary(any()) :: binary()
|
||||||
|
def to_binary(entity), do: :erlang.term_to_binary(entity)
|
||||||
|
|
||||||
|
defp do_transform(%Regex{} = entity), do: entity
|
||||||
|
|
||||||
|
defp do_transform(%{"tuple" => [":proxy_url", %{"tuple" => [type, host, port]}]}) do
|
||||||
|
{:proxy_url, {do_transform_string(type), parse_host(host), port}}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_transform(%{"tuple" => [":partial_chain", entity]}) do
|
||||||
|
{partial_chain, []} =
|
||||||
|
entity
|
||||||
|
|> String.replace(~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "")
|
||||||
|
|> Code.eval_string()
|
||||||
|
|
||||||
|
{:partial_chain, partial_chain}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_transform(%{"tuple" => entity}) do
|
||||||
|
Enum.reduce(entity, {}, fn val, acc -> Tuple.append(acc, do_transform(val)) end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_transform(entity) when is_map(entity) do
|
||||||
|
for {k, v} <- entity, into: %{}, do: {do_transform(k), do_transform(v)}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_transform(entity) when is_list(entity) do
|
||||||
|
for v <- entity, into: [], do: do_transform(v)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_transform(entity) when is_binary(entity) do
|
||||||
|
entity
|
||||||
|
|> String.trim()
|
||||||
|
|> do_transform_string()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_transform(entity), do: entity
|
||||||
|
|
||||||
|
defp parse_host("localhost"), do: :localhost
|
||||||
|
|
||||||
|
defp parse_host(host) do
|
||||||
|
charlist = to_charlist(host)
|
||||||
|
|
||||||
|
case :inet.parse_address(charlist) do
|
||||||
|
{:error, :einval} ->
|
||||||
|
charlist
|
||||||
|
|
||||||
|
{:ok, ip} ->
|
||||||
|
ip
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp find_valid_delimiter([], _string, _) do
|
||||||
|
raise(ArgumentError, message: "valid delimiter for Regex expression not found")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp find_valid_delimiter([{leading, closing} = delimiter | others], pattern, regex_delimiter)
|
||||||
|
when is_tuple(delimiter) do
|
||||||
|
if String.contains?(pattern, closing) do
|
||||||
|
find_valid_delimiter(others, pattern, regex_delimiter)
|
||||||
|
else
|
||||||
|
{:ok, {leading, closing}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp find_valid_delimiter([delimiter | others], pattern, regex_delimiter) do
|
||||||
|
if String.contains?(pattern, delimiter) do
|
||||||
|
find_valid_delimiter(others, pattern, regex_delimiter)
|
||||||
|
else
|
||||||
|
{:ok, {delimiter, delimiter}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_transform_string("~r" <> _pattern = regex) do
|
||||||
|
with %{"modifier" => modifier, "pattern" => pattern, "delimiter" => regex_delimiter} <-
|
||||||
|
Regex.named_captures(@regex, regex),
|
||||||
|
{:ok, {leading, closing}} <- find_valid_delimiter(@delimiters, pattern, regex_delimiter),
|
||||||
|
{result, _} <- Code.eval_string("~r#{leading}#{pattern}#{closing}#{modifier}") do
|
||||||
|
result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_transform_string(":" <> atom), do: String.to_atom(atom)
|
||||||
|
|
||||||
|
defp do_transform_string(value) do
|
||||||
|
if is_module_name?(value) do
|
||||||
|
String.to_existing_atom("Elixir." <> value)
|
||||||
|
else
|
||||||
|
value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec is_module_name?(String.t()) :: boolean()
|
||||||
|
def is_module_name?(string) do
|
||||||
|
Regex.match?(~r/^(Pleroma|Phoenix|Tesla|Quack|Ueberauth|Swoosh)\./, string) or
|
||||||
|
string in ["Oban", "Ueberauth", "ExSyslogger"]
|
||||||
|
end
|
||||||
|
end
|
||||||
16
lib/pleroma/config/holder.ex
Normal file
16
lib/pleroma/config/holder.ex
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Config.Holder do
|
||||||
|
@config Pleroma.Config.Loader.load_and_merge()
|
||||||
|
|
||||||
|
@spec config() :: keyword()
|
||||||
|
def config, do: @config
|
||||||
|
|
||||||
|
@spec config(atom()) :: any()
|
||||||
|
def config(group), do: @config[group]
|
||||||
|
|
||||||
|
@spec config(atom(), atom()) :: any()
|
||||||
|
def config(group, key), do: @config[group][key]
|
||||||
|
end
|
||||||
59
lib/pleroma/config/loader.ex
Normal file
59
lib/pleroma/config/loader.ex
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Config.Loader do
|
||||||
|
@paths ["config/config.exs", "config/#{Mix.env()}.exs"]
|
||||||
|
|
||||||
|
@reject_keys [
|
||||||
|
Pleroma.Repo,
|
||||||
|
Pleroma.Web.Endpoint,
|
||||||
|
:env,
|
||||||
|
:configurable_from_database,
|
||||||
|
:database,
|
||||||
|
:swarm
|
||||||
|
]
|
||||||
|
|
||||||
|
if Code.ensure_loaded?(Config.Reader) do
|
||||||
|
@spec load(Path.t()) :: keyword()
|
||||||
|
def load(path), do: Config.Reader.read!(path)
|
||||||
|
|
||||||
|
defp do_merge(conf1, conf2), do: Config.Reader.merge(conf1, conf2)
|
||||||
|
else
|
||||||
|
# support for Elixir less than 1.9
|
||||||
|
@spec load(Path.t()) :: keyword()
|
||||||
|
def load(path) do
|
||||||
|
path
|
||||||
|
|> Mix.Config.eval!()
|
||||||
|
|> elem(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_merge(conf1, conf2), do: Mix.Config.merge(conf1, conf2)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec load_and_merge() :: keyword()
|
||||||
|
def load_and_merge do
|
||||||
|
all_paths =
|
||||||
|
if Pleroma.Config.get(:release),
|
||||||
|
do: @paths ++ ["config/releases.exs"],
|
||||||
|
else: @paths
|
||||||
|
|
||||||
|
all_paths
|
||||||
|
|> Enum.map(&load(&1))
|
||||||
|
|> Enum.reduce([], &do_merge(&2, &1))
|
||||||
|
|> filter()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp filter(configs) do
|
||||||
|
configs
|
||||||
|
|> Keyword.keys()
|
||||||
|
|> Enum.reduce([], &Keyword.put(&2, &1, filter_group(&1, configs)))
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec filter_group(atom(), keyword()) :: keyword()
|
||||||
|
def filter_group(group, configs) do
|
||||||
|
Enum.reject(configs[group], fn {key, _v} ->
|
||||||
|
key in @reject_keys or (group == :phoenix and key == :serve_endpoints)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -4,56 +4,111 @@
|
||||||
|
|
||||||
defmodule Pleroma.Config.TransferTask do
|
defmodule Pleroma.Config.TransferTask do
|
||||||
use Task
|
use Task
|
||||||
alias Pleroma.Web.AdminAPI.Config
|
|
||||||
|
alias Pleroma.ConfigDB
|
||||||
|
alias Pleroma.Repo
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
def start_link(_) do
|
def start_link(_) do
|
||||||
load_and_update_env()
|
load_and_update_env()
|
||||||
if Pleroma.Config.get(:env) == :test, do: Ecto.Adapters.SQL.Sandbox.checkin(Pleroma.Repo)
|
if Pleroma.Config.get(:env) == :test, do: Ecto.Adapters.SQL.Sandbox.checkin(Repo)
|
||||||
:ignore
|
:ignore
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_and_update_env do
|
@spec load_and_update_env([ConfigDB.t()]) :: :ok | false
|
||||||
if Pleroma.Config.get([:instance, :dynamic_configuration]) and
|
def load_and_update_env(deleted \\ []) do
|
||||||
Ecto.Adapters.SQL.table_exists?(Pleroma.Repo, "config") do
|
with true <- Pleroma.Config.get(:configurable_from_database),
|
||||||
for_restart =
|
true <- Ecto.Adapters.SQL.table_exists?(Repo, "config"),
|
||||||
Pleroma.Repo.all(Config)
|
started_applications <- Application.started_applications() do
|
||||||
|> Enum.map(&update_env(&1))
|
|
||||||
|
|
||||||
# We need to restart applications for loaded settings take effect
|
# We need to restart applications for loaded settings take effect
|
||||||
for_restart
|
in_db = Repo.all(ConfigDB)
|
||||||
|> Enum.reject(&(&1 in [:pleroma, :ok]))
|
|
||||||
|> Enum.each(fn app ->
|
with_deleted = in_db ++ deleted
|
||||||
Application.stop(app)
|
|
||||||
:ok = Application.start(app)
|
with_deleted
|
||||||
end)
|
|> Enum.map(&merge_and_update(&1))
|
||||||
|
|> Enum.uniq()
|
||||||
|
# TODO: some problem with prometheus after restart!
|
||||||
|
|> Enum.reject(&(&1 in [:pleroma, nil, :prometheus]))
|
||||||
|
|> Enum.each(&restart(started_applications, &1))
|
||||||
|
|
||||||
|
:ok
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp update_env(setting) do
|
defp merge_and_update(setting) do
|
||||||
try do
|
try do
|
||||||
key =
|
key = ConfigDB.from_string(setting.key)
|
||||||
if String.starts_with?(setting.key, "Pleroma.") do
|
group = ConfigDB.from_string(setting.group)
|
||||||
"Elixir." <> setting.key
|
|
||||||
else
|
|
||||||
String.trim_leading(setting.key, ":")
|
|
||||||
end
|
|
||||||
|
|
||||||
group = String.to_existing_atom(setting.group)
|
default = Pleroma.Config.Holder.config(group, key)
|
||||||
|
merged_value = merge_value(setting, default, group, key)
|
||||||
|
|
||||||
Application.put_env(
|
:ok = update_env(group, key, merged_value)
|
||||||
group,
|
|
||||||
String.to_existing_atom(key),
|
|
||||||
Config.from_binary(setting.value)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
if group != :logger do
|
||||||
group
|
group
|
||||||
rescue
|
else
|
||||||
e ->
|
# change logger configuration in runtime, without restart
|
||||||
require Logger
|
if Keyword.keyword?(merged_value) and
|
||||||
|
key not in [:compile_time_application, :backends, :compile_time_purge_matching] do
|
||||||
|
Logger.configure_backend(key, merged_value)
|
||||||
|
else
|
||||||
|
Logger.configure([{key, merged_value}])
|
||||||
|
end
|
||||||
|
|
||||||
Logger.warn(
|
nil
|
||||||
"updating env causes error, key: #{inspect(setting.key)}, error: #{inspect(e)}"
|
end
|
||||||
)
|
rescue
|
||||||
|
error ->
|
||||||
|
error_msg =
|
||||||
|
"updating env causes error, group: " <>
|
||||||
|
inspect(setting.group) <>
|
||||||
|
" key: " <>
|
||||||
|
inspect(setting.key) <>
|
||||||
|
" value: " <>
|
||||||
|
inspect(ConfigDB.from_binary(setting.value)) <> " error: " <> inspect(error)
|
||||||
|
|
||||||
|
Logger.warn(error_msg)
|
||||||
|
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp merge_value(%{__meta__: %{state: :deleted}}, default, _group, _key), do: default
|
||||||
|
|
||||||
|
defp merge_value(setting, default, group, key) do
|
||||||
|
value = ConfigDB.from_binary(setting.value)
|
||||||
|
|
||||||
|
if can_be_merged?(default, value) do
|
||||||
|
ConfigDB.merge_group(group, key, default, value)
|
||||||
|
else
|
||||||
|
value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp update_env(group, key, nil), do: Application.delete_env(group, key)
|
||||||
|
defp update_env(group, key, value), do: Application.put_env(group, key, value)
|
||||||
|
|
||||||
|
defp restart(started_applications, app) do
|
||||||
|
with {^app, _, _} <- List.keyfind(started_applications, app, 0),
|
||||||
|
:ok <- Application.stop(app) do
|
||||||
|
:ok = Application.start(app)
|
||||||
|
else
|
||||||
|
nil ->
|
||||||
|
Logger.warn("#{app} is not started.")
|
||||||
|
|
||||||
|
error ->
|
||||||
|
error
|
||||||
|
|> inspect()
|
||||||
|
|> Logger.warn()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp can_be_merged?(val1, val2) when is_list(val1) and is_list(val2) do
|
||||||
|
Keyword.keyword?(val1) and Keyword.keyword?(val2)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp can_be_merged?(_val1, _val2), do: false
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ defmodule Pleroma.Constants do
|
||||||
|
|
||||||
const(object_internal_fields,
|
const(object_internal_fields,
|
||||||
do: [
|
do: [
|
||||||
|
"reactions",
|
||||||
|
"reaction_count",
|
||||||
"likes",
|
"likes",
|
||||||
"like_count",
|
"like_count",
|
||||||
"announcements",
|
"announcements",
|
||||||
|
|
|
||||||
|
|
@ -64,11 +64,13 @@ defmodule Pleroma.Conversation.Participation do
|
||||||
end
|
end
|
||||||
|
|
||||||
def mark_as_read(participation) do
|
def mark_as_read(participation) do
|
||||||
participation
|
__MODULE__
|
||||||
|> read_cng(%{read: true})
|
|> where(id: ^participation.id)
|
||||||
|> Repo.update()
|
|> update(set: [read: true])
|
||||||
|
|> select([p], p)
|
||||||
|
|> Repo.update_all([])
|
||||||
|> case do
|
|> case do
|
||||||
{:ok, participation} ->
|
{1, [participation]} ->
|
||||||
participation = Repo.preload(participation, :user)
|
participation = Repo.preload(participation, :user)
|
||||||
User.set_unread_conversation_count(participation.user)
|
User.set_unread_conversation_count(participation.user)
|
||||||
{:ok, participation}
|
{:ok, participation}
|
||||||
|
|
@ -122,9 +124,37 @@ defmodule Pleroma.Conversation.Participation do
|
||||||
order_by: [desc: p.updated_at],
|
order_by: [desc: p.updated_at],
|
||||||
preload: [conversation: [:users]]
|
preload: [conversation: [:users]]
|
||||||
)
|
)
|
||||||
|
|> restrict_recipients(user, params)
|
||||||
|> Pleroma.Pagination.fetch_paginated(params)
|
|> Pleroma.Pagination.fetch_paginated(params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def restrict_recipients(query, user, %{"recipients" => user_ids}) do
|
||||||
|
user_ids =
|
||||||
|
[user.id | user_ids]
|
||||||
|
|> Enum.uniq()
|
||||||
|
|> Enum.reduce([], fn user_id, acc ->
|
||||||
|
case FlakeId.Ecto.CompatType.dump(user_id) do
|
||||||
|
{:ok, user_id} -> [user_id | acc]
|
||||||
|
_ -> acc
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
conversation_subquery =
|
||||||
|
__MODULE__
|
||||||
|
|> group_by([p], p.conversation_id)
|
||||||
|
|> having(
|
||||||
|
[p],
|
||||||
|
count(p.user_id) == ^length(user_ids) and
|
||||||
|
fragment("array_agg(?) @> ?", p.user_id, ^user_ids)
|
||||||
|
)
|
||||||
|
|> select([p], %{id: p.conversation_id})
|
||||||
|
|
||||||
|
query
|
||||||
|
|> join(:inner, [p], c in subquery(conversation_subquery), on: p.conversation_id == c.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def restrict_recipients(query, _, _), do: query
|
||||||
|
|
||||||
def for_user_and_conversation(user, conversation) do
|
def for_user_and_conversation(user, conversation) do
|
||||||
from(p in __MODULE__,
|
from(p in __MODULE__,
|
||||||
where: p.user_id == ^user.id,
|
where: p.user_id == ^user.id,
|
||||||
|
|
|
||||||
|
|
@ -6,68 +6,116 @@ defmodule Pleroma.Docs.Generator do
|
||||||
implementation.process(descriptions)
|
implementation.process(descriptions)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec uploaders_list() :: [module()]
|
@spec list_modules_in_dir(String.t(), String.t()) :: [module()]
|
||||||
def uploaders_list do
|
def list_modules_in_dir(dir, start) do
|
||||||
{:ok, modules} = :application.get_key(:pleroma, :modules)
|
with {:ok, files} <- File.ls(dir) do
|
||||||
|
files
|
||||||
Enum.filter(modules, fn module ->
|
|> Enum.filter(&String.ends_with?(&1, ".ex"))
|
||||||
name_as_list = Module.split(module)
|
|> Enum.map(fn filename ->
|
||||||
|
module = filename |> String.trim_trailing(".ex") |> Macro.camelize()
|
||||||
List.starts_with?(name_as_list, ["Pleroma", "Uploaders"]) and
|
String.to_existing_atom(start <> module)
|
||||||
List.last(name_as_list) != "Uploader"
|
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec filters_list() :: [module()]
|
|
||||||
def filters_list do
|
|
||||||
{:ok, modules} = :application.get_key(:pleroma, :modules)
|
|
||||||
|
|
||||||
Enum.filter(modules, fn module ->
|
|
||||||
name_as_list = Module.split(module)
|
|
||||||
|
|
||||||
List.starts_with?(name_as_list, ["Pleroma", "Upload", "Filter"])
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec mrf_list() :: [module()]
|
@doc """
|
||||||
def mrf_list do
|
Converts:
|
||||||
{:ok, modules} = :application.get_key(:pleroma, :modules)
|
- atoms to strings with leading `:`
|
||||||
|
- module names to strings, without leading `Elixir.`
|
||||||
Enum.filter(modules, fn module ->
|
- add humanized labels to `keys` if label is not defined, e.g. `:instance` -> `Instance`
|
||||||
name_as_list = Module.split(module)
|
"""
|
||||||
|
@spec convert_to_strings([map()]) :: [map()]
|
||||||
List.starts_with?(name_as_list, ["Pleroma", "Web", "ActivityPub", "MRF"]) and
|
def convert_to_strings(descriptions) do
|
||||||
length(name_as_list) > 4
|
Enum.map(descriptions, &format_entity(&1))
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec richmedia_parsers() :: [module()]
|
defp format_entity(entity) do
|
||||||
def richmedia_parsers do
|
entity
|
||||||
{:ok, modules} = :application.get_key(:pleroma, :modules)
|
|> format_key()
|
||||||
|
|> Map.put(:group, atom_to_string(entity[:group]))
|
||||||
Enum.filter(modules, fn module ->
|
|> format_children()
|
||||||
name_as_list = Module.split(module)
|
|
||||||
|
|
||||||
List.starts_with?(name_as_list, ["Pleroma", "Web", "RichMedia", "Parsers"]) and
|
|
||||||
length(name_as_list) == 5
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp format_key(%{key: key} = entity) do
|
||||||
|
entity
|
||||||
|
|> Map.put(:key, atom_to_string(key))
|
||||||
|
|> Map.put(:label, entity[:label] || humanize(key))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_key(%{group: group} = entity) do
|
||||||
|
Map.put(entity, :label, entity[:label] || humanize(group))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_key(entity), do: entity
|
||||||
|
|
||||||
|
defp format_children(%{children: children} = entity) do
|
||||||
|
Map.put(entity, :children, Enum.map(children, &format_child(&1)))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_children(entity), do: entity
|
||||||
|
|
||||||
|
defp format_child(%{suggestions: suggestions} = entity) do
|
||||||
|
entity
|
||||||
|
|> Map.put(:suggestions, format_suggestions(suggestions))
|
||||||
|
|> format_key()
|
||||||
|
|> format_group()
|
||||||
|
|> format_children()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_child(entity) do
|
||||||
|
entity
|
||||||
|
|> format_key()
|
||||||
|
|> format_group()
|
||||||
|
|> format_children()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_group(%{group: group} = entity) do
|
||||||
|
Map.put(entity, :group, format_suggestion(group))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_group(entity), do: entity
|
||||||
|
|
||||||
|
defp atom_to_string(entity) when is_binary(entity), do: entity
|
||||||
|
|
||||||
|
defp atom_to_string(entity) when is_atom(entity), do: inspect(entity)
|
||||||
|
|
||||||
|
defp humanize(entity) do
|
||||||
|
string = inspect(entity)
|
||||||
|
|
||||||
|
if String.starts_with?(string, ":"),
|
||||||
|
do: Phoenix.Naming.humanize(entity),
|
||||||
|
else: string
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_suggestions([]), do: []
|
||||||
|
|
||||||
|
defp format_suggestions([suggestion | tail]) do
|
||||||
|
[format_suggestion(suggestion) | format_suggestions(tail)]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_suggestion(entity) when is_atom(entity) do
|
||||||
|
atom_to_string(entity)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_suggestion([head | tail] = entity) when is_list(entity) do
|
||||||
|
[format_suggestion(head) | format_suggestions(tail)]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_suggestion(entity) when is_tuple(entity) do
|
||||||
|
format_suggestions(Tuple.to_list(entity)) |> List.to_tuple()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_suggestion(entity), do: entity
|
||||||
end
|
end
|
||||||
|
|
||||||
defimpl Jason.Encoder, for: Tuple do
|
defimpl Jason.Encoder, for: Tuple do
|
||||||
def encode(tuple, opts) do
|
def encode(tuple, opts), do: Jason.Encode.list(Tuple.to_list(tuple), opts)
|
||||||
Jason.Encode.list(Tuple.to_list(tuple), opts)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defimpl Jason.Encoder, for: [Regex, Function] do
|
defimpl Jason.Encoder, for: [Regex, Function] do
|
||||||
def encode(term, opts) do
|
def encode(term, opts), do: Jason.Encode.string(inspect(term), opts)
|
||||||
Jason.Encode.string(inspect(term), opts)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defimpl String.Chars, for: Regex do
|
defimpl String.Chars, for: Regex do
|
||||||
def to_string(term) do
|
def to_string(term), do: inspect(term)
|
||||||
inspect(term)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,22 @@ defmodule Pleroma.Docs.JSON do
|
||||||
|
|
||||||
@spec process(keyword()) :: {:ok, String.t()}
|
@spec process(keyword()) :: {:ok, String.t()}
|
||||||
def process(descriptions) do
|
def process(descriptions) do
|
||||||
config_path = "docs/generate_config.json"
|
with path <- "docs/generated_config.json",
|
||||||
|
{:ok, file} <- File.open(path, [:write, :utf8]),
|
||||||
with {:ok, file} <- File.open(config_path, [:write]),
|
formatted_descriptions <-
|
||||||
json <- generate_json(descriptions),
|
Pleroma.Docs.Generator.convert_to_strings(descriptions),
|
||||||
|
json <- Jason.encode!(formatted_descriptions),
|
||||||
:ok <- IO.write(file, json),
|
:ok <- IO.write(file, json),
|
||||||
:ok <- File.close(file) do
|
:ok <- File.close(file) do
|
||||||
{:ok, config_path}
|
{:ok, path}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec generate_json([keyword()]) :: String.t()
|
def compile do
|
||||||
def generate_json(descriptions) do
|
with config <- Pleroma.Config.Loader.load("config/description.exs") do
|
||||||
Jason.encode!(descriptions)
|
config[:pleroma][:config_description]
|
||||||
|
|> Pleroma.Docs.Generator.convert_to_strings()
|
||||||
|
|> Jason.encode!()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
13
lib/pleroma/ecto_enums.ex
Normal file
13
lib/pleroma/ecto_enums.ex
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import EctoEnum
|
||||||
|
|
||||||
|
defenum(UserRelationshipTypeEnum,
|
||||||
|
block: 1,
|
||||||
|
mute: 2,
|
||||||
|
reblog_mute: 3,
|
||||||
|
notification_mute: 4,
|
||||||
|
inverse_subscription: 5
|
||||||
|
)
|
||||||
769
lib/pleroma/emoji-data.txt
Normal file
769
lib/pleroma/emoji-data.txt
Normal file
|
|
@ -0,0 +1,769 @@
|
||||||
|
# emoji-data.txt
|
||||||
|
# Date: 2019-01-15, 12:10:05 GMT
|
||||||
|
# © 2019 Unicode®, Inc.
|
||||||
|
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
|
||||||
|
# For terms of use, see http://www.unicode.org/terms_of_use.html
|
||||||
|
#
|
||||||
|
# Emoji Data for UTS #51
|
||||||
|
# Version: 12.0
|
||||||
|
#
|
||||||
|
# For documentation and usage, see http://www.unicode.org/reports/tr51
|
||||||
|
#
|
||||||
|
# Format:
|
||||||
|
# <codepoint(s)> ; <property> # <comments>
|
||||||
|
# Note: there is no guarantee as to the structure of whitespace or comments
|
||||||
|
#
|
||||||
|
# Characters and sequences are listed in code point order. Users should be shown a more natural order.
|
||||||
|
# See the CLDR collation order for Emoji.
|
||||||
|
|
||||||
|
|
||||||
|
# ================================================
|
||||||
|
|
||||||
|
# All omitted code points have Emoji=No
|
||||||
|
# @missing: 0000..10FFFF ; Emoji ; No
|
||||||
|
|
||||||
|
0023 ; Emoji # 1.1 [1] (#️) number sign
|
||||||
|
002A ; Emoji # 1.1 [1] (*️) asterisk
|
||||||
|
0030..0039 ; Emoji # 1.1 [10] (0️..9️) digit zero..digit nine
|
||||||
|
00A9 ; Emoji # 1.1 [1] (©️) copyright
|
||||||
|
00AE ; Emoji # 1.1 [1] (®️) registered
|
||||||
|
203C ; Emoji # 1.1 [1] (‼️) double exclamation mark
|
||||||
|
2049 ; Emoji # 3.0 [1] (⁉️) exclamation question mark
|
||||||
|
2122 ; Emoji # 1.1 [1] (™️) trade mark
|
||||||
|
2139 ; Emoji # 3.0 [1] (ℹ️) information
|
||||||
|
2194..2199 ; Emoji # 1.1 [6] (↔️..↙️) left-right arrow..down-left arrow
|
||||||
|
21A9..21AA ; Emoji # 1.1 [2] (↩️..↪️) right arrow curving left..left arrow curving right
|
||||||
|
231A..231B ; Emoji # 1.1 [2] (⌚..⌛) watch..hourglass done
|
||||||
|
2328 ; Emoji # 1.1 [1] (⌨️) keyboard
|
||||||
|
23CF ; Emoji # 4.0 [1] (⏏️) eject button
|
||||||
|
23E9..23F3 ; Emoji # 6.0 [11] (⏩..⏳) fast-forward button..hourglass not done
|
||||||
|
23F8..23FA ; Emoji # 7.0 [3] (⏸️..⏺️) pause button..record button
|
||||||
|
24C2 ; Emoji # 1.1 [1] (Ⓜ️) circled M
|
||||||
|
25AA..25AB ; Emoji # 1.1 [2] (▪️..▫️) black small square..white small square
|
||||||
|
25B6 ; Emoji # 1.1 [1] (▶️) play button
|
||||||
|
25C0 ; Emoji # 1.1 [1] (◀️) reverse button
|
||||||
|
25FB..25FE ; Emoji # 3.2 [4] (◻️..◾) white medium square..black medium-small square
|
||||||
|
2600..2604 ; Emoji # 1.1 [5] (☀️..☄️) sun..comet
|
||||||
|
260E ; Emoji # 1.1 [1] (☎️) telephone
|
||||||
|
2611 ; Emoji # 1.1 [1] (☑️) check box with check
|
||||||
|
2614..2615 ; Emoji # 4.0 [2] (☔..☕) umbrella with rain drops..hot beverage
|
||||||
|
2618 ; Emoji # 4.1 [1] (☘️) shamrock
|
||||||
|
261D ; Emoji # 1.1 [1] (☝️) index pointing up
|
||||||
|
2620 ; Emoji # 1.1 [1] (☠️) skull and crossbones
|
||||||
|
2622..2623 ; Emoji # 1.1 [2] (☢️..☣️) radioactive..biohazard
|
||||||
|
2626 ; Emoji # 1.1 [1] (☦️) orthodox cross
|
||||||
|
262A ; Emoji # 1.1 [1] (☪️) star and crescent
|
||||||
|
262E..262F ; Emoji # 1.1 [2] (☮️..☯️) peace symbol..yin yang
|
||||||
|
2638..263A ; Emoji # 1.1 [3] (☸️..☺️) wheel of dharma..smiling face
|
||||||
|
2640 ; Emoji # 1.1 [1] (♀️) female sign
|
||||||
|
2642 ; Emoji # 1.1 [1] (♂️) male sign
|
||||||
|
2648..2653 ; Emoji # 1.1 [12] (♈..♓) Aries..Pisces
|
||||||
|
265F..2660 ; Emoji # 1.1 [2] (♟️..♠️) chess pawn..spade suit
|
||||||
|
2663 ; Emoji # 1.1 [1] (♣️) club suit
|
||||||
|
2665..2666 ; Emoji # 1.1 [2] (♥️..♦️) heart suit..diamond suit
|
||||||
|
2668 ; Emoji # 1.1 [1] (♨️) hot springs
|
||||||
|
267B ; Emoji # 3.2 [1] (♻️) recycling symbol
|
||||||
|
267E..267F ; Emoji # 4.1 [2] (♾️..♿) infinity..wheelchair symbol
|
||||||
|
2692..2697 ; Emoji # 4.1 [6] (⚒️..⚗️) hammer and pick..alembic
|
||||||
|
2699 ; Emoji # 4.1 [1] (⚙️) gear
|
||||||
|
269B..269C ; Emoji # 4.1 [2] (⚛️..⚜️) atom symbol..fleur-de-lis
|
||||||
|
26A0..26A1 ; Emoji # 4.0 [2] (⚠️..⚡) warning..high voltage
|
||||||
|
26AA..26AB ; Emoji # 4.1 [2] (⚪..⚫) white circle..black circle
|
||||||
|
26B0..26B1 ; Emoji # 4.1 [2] (⚰️..⚱️) coffin..funeral urn
|
||||||
|
26BD..26BE ; Emoji # 5.2 [2] (⚽..⚾) soccer ball..baseball
|
||||||
|
26C4..26C5 ; Emoji # 5.2 [2] (⛄..⛅) snowman without snow..sun behind cloud
|
||||||
|
26C8 ; Emoji # 5.2 [1] (⛈️) cloud with lightning and rain
|
||||||
|
26CE ; Emoji # 6.0 [1] (⛎) Ophiuchus
|
||||||
|
26CF ; Emoji # 5.2 [1] (⛏️) pick
|
||||||
|
26D1 ; Emoji # 5.2 [1] (⛑️) rescue worker’s helmet
|
||||||
|
26D3..26D4 ; Emoji # 5.2 [2] (⛓️..⛔) chains..no entry
|
||||||
|
26E9..26EA ; Emoji # 5.2 [2] (⛩️..⛪) shinto shrine..church
|
||||||
|
26F0..26F5 ; Emoji # 5.2 [6] (⛰️..⛵) mountain..sailboat
|
||||||
|
26F7..26FA ; Emoji # 5.2 [4] (⛷️..⛺) skier..tent
|
||||||
|
26FD ; Emoji # 5.2 [1] (⛽) fuel pump
|
||||||
|
2702 ; Emoji # 1.1 [1] (✂️) scissors
|
||||||
|
2705 ; Emoji # 6.0 [1] (✅) check mark button
|
||||||
|
2708..2709 ; Emoji # 1.1 [2] (✈️..✉️) airplane..envelope
|
||||||
|
270A..270B ; Emoji # 6.0 [2] (✊..✋) raised fist..raised hand
|
||||||
|
270C..270D ; Emoji # 1.1 [2] (✌️..✍️) victory hand..writing hand
|
||||||
|
270F ; Emoji # 1.1 [1] (✏️) pencil
|
||||||
|
2712 ; Emoji # 1.1 [1] (✒️) black nib
|
||||||
|
2714 ; Emoji # 1.1 [1] (✔️) check mark
|
||||||
|
2716 ; Emoji # 1.1 [1] (✖️) multiplication sign
|
||||||
|
271D ; Emoji # 1.1 [1] (✝️) latin cross
|
||||||
|
2721 ; Emoji # 1.1 [1] (✡️) star of David
|
||||||
|
2728 ; Emoji # 6.0 [1] (✨) sparkles
|
||||||
|
2733..2734 ; Emoji # 1.1 [2] (✳️..✴️) eight-spoked asterisk..eight-pointed star
|
||||||
|
2744 ; Emoji # 1.1 [1] (❄️) snowflake
|
||||||
|
2747 ; Emoji # 1.1 [1] (❇️) sparkle
|
||||||
|
274C ; Emoji # 6.0 [1] (❌) cross mark
|
||||||
|
274E ; Emoji # 6.0 [1] (❎) cross mark button
|
||||||
|
2753..2755 ; Emoji # 6.0 [3] (❓..❕) question mark..white exclamation mark
|
||||||
|
2757 ; Emoji # 5.2 [1] (❗) exclamation mark
|
||||||
|
2763..2764 ; Emoji # 1.1 [2] (❣️..❤️) heart exclamation..red heart
|
||||||
|
2795..2797 ; Emoji # 6.0 [3] (➕..➗) plus sign..division sign
|
||||||
|
27A1 ; Emoji # 1.1 [1] (➡️) right arrow
|
||||||
|
27B0 ; Emoji # 6.0 [1] (➰) curly loop
|
||||||
|
27BF ; Emoji # 6.0 [1] (➿) double curly loop
|
||||||
|
2934..2935 ; Emoji # 3.2 [2] (⤴️..⤵️) right arrow curving up..right arrow curving down
|
||||||
|
2B05..2B07 ; Emoji # 4.0 [3] (⬅️..⬇️) left arrow..down arrow
|
||||||
|
2B1B..2B1C ; Emoji # 5.1 [2] (⬛..⬜) black large square..white large square
|
||||||
|
2B50 ; Emoji # 5.1 [1] (⭐) star
|
||||||
|
2B55 ; Emoji # 5.2 [1] (⭕) hollow red circle
|
||||||
|
3030 ; Emoji # 1.1 [1] (〰️) wavy dash
|
||||||
|
303D ; Emoji # 3.2 [1] (〽️) part alternation mark
|
||||||
|
3297 ; Emoji # 1.1 [1] (㊗️) Japanese “congratulations” button
|
||||||
|
3299 ; Emoji # 1.1 [1] (㊙️) Japanese “secret” button
|
||||||
|
1F004 ; Emoji # 5.1 [1] (🀄) mahjong red dragon
|
||||||
|
1F0CF ; Emoji # 6.0 [1] (🃏) joker
|
||||||
|
1F170..1F171 ; Emoji # 6.0 [2] (🅰️..🅱️) A button (blood type)..B button (blood type)
|
||||||
|
1F17E ; Emoji # 6.0 [1] (🅾️) O button (blood type)
|
||||||
|
1F17F ; Emoji # 5.2 [1] (🅿️) P button
|
||||||
|
1F18E ; Emoji # 6.0 [1] (🆎) AB button (blood type)
|
||||||
|
1F191..1F19A ; Emoji # 6.0 [10] (🆑..🆚) CL button..VS button
|
||||||
|
1F1E6..1F1FF ; Emoji # 6.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z
|
||||||
|
1F201..1F202 ; Emoji # 6.0 [2] (🈁..🈂️) Japanese “here” button..Japanese “service charge” button
|
||||||
|
1F21A ; Emoji # 5.2 [1] (🈚) Japanese “free of charge” button
|
||||||
|
1F22F ; Emoji # 5.2 [1] (🈯) Japanese “reserved” button
|
||||||
|
1F232..1F23A ; Emoji # 6.0 [9] (🈲..🈺) Japanese “prohibited” button..Japanese “open for business” button
|
||||||
|
1F250..1F251 ; Emoji # 6.0 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button
|
||||||
|
1F300..1F320 ; Emoji # 6.0 [33] (🌀..🌠) cyclone..shooting star
|
||||||
|
1F321 ; Emoji # 7.0 [1] (🌡️) thermometer
|
||||||
|
1F324..1F32C ; Emoji # 7.0 [9] (🌤️..🌬️) sun behind small cloud..wind face
|
||||||
|
1F32D..1F32F ; Emoji # 8.0 [3] (🌭..🌯) hot dog..burrito
|
||||||
|
1F330..1F335 ; Emoji # 6.0 [6] (🌰..🌵) chestnut..cactus
|
||||||
|
1F336 ; Emoji # 7.0 [1] (🌶️) hot pepper
|
||||||
|
1F337..1F37C ; Emoji # 6.0 [70] (🌷..🍼) tulip..baby bottle
|
||||||
|
1F37D ; Emoji # 7.0 [1] (🍽️) fork and knife with plate
|
||||||
|
1F37E..1F37F ; Emoji # 8.0 [2] (🍾..🍿) bottle with popping cork..popcorn
|
||||||
|
1F380..1F393 ; Emoji # 6.0 [20] (🎀..🎓) ribbon..graduation cap
|
||||||
|
1F396..1F397 ; Emoji # 7.0 [2] (🎖️..🎗️) military medal..reminder ribbon
|
||||||
|
1F399..1F39B ; Emoji # 7.0 [3] (🎙️..🎛️) studio microphone..control knobs
|
||||||
|
1F39E..1F39F ; Emoji # 7.0 [2] (🎞️..🎟️) film frames..admission tickets
|
||||||
|
1F3A0..1F3C4 ; Emoji # 6.0 [37] (🎠..🏄) carousel horse..person surfing
|
||||||
|
1F3C5 ; Emoji # 7.0 [1] (🏅) sports medal
|
||||||
|
1F3C6..1F3CA ; Emoji # 6.0 [5] (🏆..🏊) trophy..person swimming
|
||||||
|
1F3CB..1F3CE ; Emoji # 7.0 [4] (🏋️..🏎️) person lifting weights..racing car
|
||||||
|
1F3CF..1F3D3 ; Emoji # 8.0 [5] (🏏..🏓) cricket game..ping pong
|
||||||
|
1F3D4..1F3DF ; Emoji # 7.0 [12] (🏔️..🏟️) snow-capped mountain..stadium
|
||||||
|
1F3E0..1F3F0 ; Emoji # 6.0 [17] (🏠..🏰) house..castle
|
||||||
|
1F3F3..1F3F5 ; Emoji # 7.0 [3] (🏳️..🏵️) white flag..rosette
|
||||||
|
1F3F7 ; Emoji # 7.0 [1] (🏷️) label
|
||||||
|
1F3F8..1F3FF ; Emoji # 8.0 [8] (🏸..🏿) badminton..dark skin tone
|
||||||
|
1F400..1F43E ; Emoji # 6.0 [63] (🐀..🐾) rat..paw prints
|
||||||
|
1F43F ; Emoji # 7.0 [1] (🐿️) chipmunk
|
||||||
|
1F440 ; Emoji # 6.0 [1] (👀) eyes
|
||||||
|
1F441 ; Emoji # 7.0 [1] (👁️) eye
|
||||||
|
1F442..1F4F7 ; Emoji # 6.0[182] (👂..📷) ear..camera
|
||||||
|
1F4F8 ; Emoji # 7.0 [1] (📸) camera with flash
|
||||||
|
1F4F9..1F4FC ; Emoji # 6.0 [4] (📹..📼) video camera..videocassette
|
||||||
|
1F4FD ; Emoji # 7.0 [1] (📽️) film projector
|
||||||
|
1F4FF ; Emoji # 8.0 [1] (📿) prayer beads
|
||||||
|
1F500..1F53D ; Emoji # 6.0 [62] (🔀..🔽) shuffle tracks button..downwards button
|
||||||
|
1F549..1F54A ; Emoji # 7.0 [2] (🕉️..🕊️) om..dove
|
||||||
|
1F54B..1F54E ; Emoji # 8.0 [4] (🕋..🕎) kaaba..menorah
|
||||||
|
1F550..1F567 ; Emoji # 6.0 [24] (🕐..🕧) one o’clock..twelve-thirty
|
||||||
|
1F56F..1F570 ; Emoji # 7.0 [2] (🕯️..🕰️) candle..mantelpiece clock
|
||||||
|
1F573..1F579 ; Emoji # 7.0 [7] (🕳️..🕹️) hole..joystick
|
||||||
|
1F57A ; Emoji # 9.0 [1] (🕺) man dancing
|
||||||
|
1F587 ; Emoji # 7.0 [1] (🖇️) linked paperclips
|
||||||
|
1F58A..1F58D ; Emoji # 7.0 [4] (🖊️..🖍️) pen..crayon
|
||||||
|
1F590 ; Emoji # 7.0 [1] (🖐️) hand with fingers splayed
|
||||||
|
1F595..1F596 ; Emoji # 7.0 [2] (🖕..🖖) middle finger..vulcan salute
|
||||||
|
1F5A4 ; Emoji # 9.0 [1] (🖤) black heart
|
||||||
|
1F5A5 ; Emoji # 7.0 [1] (🖥️) desktop computer
|
||||||
|
1F5A8 ; Emoji # 7.0 [1] (🖨️) printer
|
||||||
|
1F5B1..1F5B2 ; Emoji # 7.0 [2] (🖱️..🖲️) computer mouse..trackball
|
||||||
|
1F5BC ; Emoji # 7.0 [1] (🖼️) framed picture
|
||||||
|
1F5C2..1F5C4 ; Emoji # 7.0 [3] (🗂️..🗄️) card index dividers..file cabinet
|
||||||
|
1F5D1..1F5D3 ; Emoji # 7.0 [3] (🗑️..🗓️) wastebasket..spiral calendar
|
||||||
|
1F5DC..1F5DE ; Emoji # 7.0 [3] (🗜️..🗞️) clamp..rolled-up newspaper
|
||||||
|
1F5E1 ; Emoji # 7.0 [1] (🗡️) dagger
|
||||||
|
1F5E3 ; Emoji # 7.0 [1] (🗣️) speaking head
|
||||||
|
1F5E8 ; Emoji # 7.0 [1] (🗨️) left speech bubble
|
||||||
|
1F5EF ; Emoji # 7.0 [1] (🗯️) right anger bubble
|
||||||
|
1F5F3 ; Emoji # 7.0 [1] (🗳️) ballot box with ballot
|
||||||
|
1F5FA ; Emoji # 7.0 [1] (🗺️) world map
|
||||||
|
1F5FB..1F5FF ; Emoji # 6.0 [5] (🗻..🗿) mount fuji..moai
|
||||||
|
1F600 ; Emoji # 6.1 [1] (😀) grinning face
|
||||||
|
1F601..1F610 ; Emoji # 6.0 [16] (😁..😐) beaming face with smiling eyes..neutral face
|
||||||
|
1F611 ; Emoji # 6.1 [1] (😑) expressionless face
|
||||||
|
1F612..1F614 ; Emoji # 6.0 [3] (😒..😔) unamused face..pensive face
|
||||||
|
1F615 ; Emoji # 6.1 [1] (😕) confused face
|
||||||
|
1F616 ; Emoji # 6.0 [1] (😖) confounded face
|
||||||
|
1F617 ; Emoji # 6.1 [1] (😗) kissing face
|
||||||
|
1F618 ; Emoji # 6.0 [1] (😘) face blowing a kiss
|
||||||
|
1F619 ; Emoji # 6.1 [1] (😙) kissing face with smiling eyes
|
||||||
|
1F61A ; Emoji # 6.0 [1] (😚) kissing face with closed eyes
|
||||||
|
1F61B ; Emoji # 6.1 [1] (😛) face with tongue
|
||||||
|
1F61C..1F61E ; Emoji # 6.0 [3] (😜..😞) winking face with tongue..disappointed face
|
||||||
|
1F61F ; Emoji # 6.1 [1] (😟) worried face
|
||||||
|
1F620..1F625 ; Emoji # 6.0 [6] (😠..😥) angry face..sad but relieved face
|
||||||
|
1F626..1F627 ; Emoji # 6.1 [2] (😦..😧) frowning face with open mouth..anguished face
|
||||||
|
1F628..1F62B ; Emoji # 6.0 [4] (😨..😫) fearful face..tired face
|
||||||
|
1F62C ; Emoji # 6.1 [1] (😬) grimacing face
|
||||||
|
1F62D ; Emoji # 6.0 [1] (😭) loudly crying face
|
||||||
|
1F62E..1F62F ; Emoji # 6.1 [2] (😮..😯) face with open mouth..hushed face
|
||||||
|
1F630..1F633 ; Emoji # 6.0 [4] (😰..😳) anxious face with sweat..flushed face
|
||||||
|
1F634 ; Emoji # 6.1 [1] (😴) sleeping face
|
||||||
|
1F635..1F640 ; Emoji # 6.0 [12] (😵..🙀) dizzy face..weary cat
|
||||||
|
1F641..1F642 ; Emoji # 7.0 [2] (🙁..🙂) slightly frowning face..slightly smiling face
|
||||||
|
1F643..1F644 ; Emoji # 8.0 [2] (🙃..🙄) upside-down face..face with rolling eyes
|
||||||
|
1F645..1F64F ; Emoji # 6.0 [11] (🙅..🙏) person gesturing NO..folded hands
|
||||||
|
1F680..1F6C5 ; Emoji # 6.0 [70] (🚀..🛅) rocket..left luggage
|
||||||
|
1F6CB..1F6CF ; Emoji # 7.0 [5] (🛋️..🛏️) couch and lamp..bed
|
||||||
|
1F6D0 ; Emoji # 8.0 [1] (🛐) place of worship
|
||||||
|
1F6D1..1F6D2 ; Emoji # 9.0 [2] (🛑..🛒) stop sign..shopping cart
|
||||||
|
1F6D5 ; Emoji # 12.0 [1] (🛕) hindu temple
|
||||||
|
1F6E0..1F6E5 ; Emoji # 7.0 [6] (🛠️..🛥️) hammer and wrench..motor boat
|
||||||
|
1F6E9 ; Emoji # 7.0 [1] (🛩️) small airplane
|
||||||
|
1F6EB..1F6EC ; Emoji # 7.0 [2] (🛫..🛬) airplane departure..airplane arrival
|
||||||
|
1F6F0 ; Emoji # 7.0 [1] (🛰️) satellite
|
||||||
|
1F6F3 ; Emoji # 7.0 [1] (🛳️) passenger ship
|
||||||
|
1F6F4..1F6F6 ; Emoji # 9.0 [3] (🛴..🛶) kick scooter..canoe
|
||||||
|
1F6F7..1F6F8 ; Emoji # 10.0 [2] (🛷..🛸) sled..flying saucer
|
||||||
|
1F6F9 ; Emoji # 11.0 [1] (🛹) skateboard
|
||||||
|
1F6FA ; Emoji # 12.0 [1] (🛺) auto rickshaw
|
||||||
|
1F7E0..1F7EB ; Emoji # 12.0 [12] (🟠..🟫) orange circle..brown square
|
||||||
|
1F90D..1F90F ; Emoji # 12.0 [3] (🤍..🤏) white heart..pinching hand
|
||||||
|
1F910..1F918 ; Emoji # 8.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns
|
||||||
|
1F919..1F91E ; Emoji # 9.0 [6] (🤙..🤞) call me hand..crossed fingers
|
||||||
|
1F91F ; Emoji # 10.0 [1] (🤟) love-you gesture
|
||||||
|
1F920..1F927 ; Emoji # 9.0 [8] (🤠..🤧) cowboy hat face..sneezing face
|
||||||
|
1F928..1F92F ; Emoji # 10.0 [8] (🤨..🤯) face with raised eyebrow..exploding head
|
||||||
|
1F930 ; Emoji # 9.0 [1] (🤰) pregnant woman
|
||||||
|
1F931..1F932 ; Emoji # 10.0 [2] (🤱..🤲) breast-feeding..palms up together
|
||||||
|
1F933..1F93A ; Emoji # 9.0 [8] (🤳..🤺) selfie..person fencing
|
||||||
|
1F93C..1F93E ; Emoji # 9.0 [3] (🤼..🤾) people wrestling..person playing handball
|
||||||
|
1F93F ; Emoji # 12.0 [1] (🤿) diving mask
|
||||||
|
1F940..1F945 ; Emoji # 9.0 [6] (🥀..🥅) wilted flower..goal net
|
||||||
|
1F947..1F94B ; Emoji # 9.0 [5] (🥇..🥋) 1st place medal..martial arts uniform
|
||||||
|
1F94C ; Emoji # 10.0 [1] (🥌) curling stone
|
||||||
|
1F94D..1F94F ; Emoji # 11.0 [3] (🥍..🥏) lacrosse..flying disc
|
||||||
|
1F950..1F95E ; Emoji # 9.0 [15] (🥐..🥞) croissant..pancakes
|
||||||
|
1F95F..1F96B ; Emoji # 10.0 [13] (🥟..🥫) dumpling..canned food
|
||||||
|
1F96C..1F970 ; Emoji # 11.0 [5] (🥬..🥰) leafy green..smiling face with hearts
|
||||||
|
1F971 ; Emoji # 12.0 [1] (🥱) yawning face
|
||||||
|
1F973..1F976 ; Emoji # 11.0 [4] (🥳..🥶) partying face..cold face
|
||||||
|
1F97A ; Emoji # 11.0 [1] (🥺) pleading face
|
||||||
|
1F97B ; Emoji # 12.0 [1] (🥻) sari
|
||||||
|
1F97C..1F97F ; Emoji # 11.0 [4] (🥼..🥿) lab coat..flat shoe
|
||||||
|
1F980..1F984 ; Emoji # 8.0 [5] (🦀..🦄) crab..unicorn
|
||||||
|
1F985..1F991 ; Emoji # 9.0 [13] (🦅..🦑) eagle..squid
|
||||||
|
1F992..1F997 ; Emoji # 10.0 [6] (🦒..🦗) giraffe..cricket
|
||||||
|
1F998..1F9A2 ; Emoji # 11.0 [11] (🦘..🦢) kangaroo..swan
|
||||||
|
1F9A5..1F9AA ; Emoji # 12.0 [6] (🦥..🦪) sloth..oyster
|
||||||
|
1F9AE..1F9AF ; Emoji # 12.0 [2] (🦮..🦯) guide dog..probing cane
|
||||||
|
1F9B0..1F9B9 ; Emoji # 11.0 [10] (🦰..🦹) red hair..supervillain
|
||||||
|
1F9BA..1F9BF ; Emoji # 12.0 [6] (🦺..🦿) safety vest..mechanical leg
|
||||||
|
1F9C0 ; Emoji # 8.0 [1] (🧀) cheese wedge
|
||||||
|
1F9C1..1F9C2 ; Emoji # 11.0 [2] (🧁..🧂) cupcake..salt
|
||||||
|
1F9C3..1F9CA ; Emoji # 12.0 [8] (🧃..🧊) beverage box..ice cube
|
||||||
|
1F9CD..1F9CF ; Emoji # 12.0 [3] (🧍..🧏) person standing..deaf person
|
||||||
|
1F9D0..1F9E6 ; Emoji # 10.0 [23] (🧐..🧦) face with monocle..socks
|
||||||
|
1F9E7..1F9FF ; Emoji # 11.0 [25] (🧧..🧿) red envelope..nazar amulet
|
||||||
|
1FA70..1FA73 ; Emoji # 12.0 [4] (🩰..🩳) ballet shoes..shorts
|
||||||
|
1FA78..1FA7A ; Emoji # 12.0 [3] (🩸..🩺) drop of blood..stethoscope
|
||||||
|
1FA80..1FA82 ; Emoji # 12.0 [3] (🪀..🪂) yo-yo..parachute
|
||||||
|
1FA90..1FA95 ; Emoji # 12.0 [6] (🪐..🪕) ringed planet..banjo
|
||||||
|
|
||||||
|
# Total elements: 1311
|
||||||
|
|
||||||
|
# ================================================
|
||||||
|
|
||||||
|
# All omitted code points have Emoji_Presentation=No
|
||||||
|
# @missing: 0000..10FFFF ; Emoji_Presentation ; No
|
||||||
|
|
||||||
|
231A..231B ; Emoji_Presentation # 1.1 [2] (⌚..⌛) watch..hourglass done
|
||||||
|
23E9..23EC ; Emoji_Presentation # 6.0 [4] (⏩..⏬) fast-forward button..fast down button
|
||||||
|
23F0 ; Emoji_Presentation # 6.0 [1] (⏰) alarm clock
|
||||||
|
23F3 ; Emoji_Presentation # 6.0 [1] (⏳) hourglass not done
|
||||||
|
25FD..25FE ; Emoji_Presentation # 3.2 [2] (◽..◾) white medium-small square..black medium-small square
|
||||||
|
2614..2615 ; Emoji_Presentation # 4.0 [2] (☔..☕) umbrella with rain drops..hot beverage
|
||||||
|
2648..2653 ; Emoji_Presentation # 1.1 [12] (♈..♓) Aries..Pisces
|
||||||
|
267F ; Emoji_Presentation # 4.1 [1] (♿) wheelchair symbol
|
||||||
|
2693 ; Emoji_Presentation # 4.1 [1] (⚓) anchor
|
||||||
|
26A1 ; Emoji_Presentation # 4.0 [1] (⚡) high voltage
|
||||||
|
26AA..26AB ; Emoji_Presentation # 4.1 [2] (⚪..⚫) white circle..black circle
|
||||||
|
26BD..26BE ; Emoji_Presentation # 5.2 [2] (⚽..⚾) soccer ball..baseball
|
||||||
|
26C4..26C5 ; Emoji_Presentation # 5.2 [2] (⛄..⛅) snowman without snow..sun behind cloud
|
||||||
|
26CE ; Emoji_Presentation # 6.0 [1] (⛎) Ophiuchus
|
||||||
|
26D4 ; Emoji_Presentation # 5.2 [1] (⛔) no entry
|
||||||
|
26EA ; Emoji_Presentation # 5.2 [1] (⛪) church
|
||||||
|
26F2..26F3 ; Emoji_Presentation # 5.2 [2] (⛲..⛳) fountain..flag in hole
|
||||||
|
26F5 ; Emoji_Presentation # 5.2 [1] (⛵) sailboat
|
||||||
|
26FA ; Emoji_Presentation # 5.2 [1] (⛺) tent
|
||||||
|
26FD ; Emoji_Presentation # 5.2 [1] (⛽) fuel pump
|
||||||
|
2705 ; Emoji_Presentation # 6.0 [1] (✅) check mark button
|
||||||
|
270A..270B ; Emoji_Presentation # 6.0 [2] (✊..✋) raised fist..raised hand
|
||||||
|
2728 ; Emoji_Presentation # 6.0 [1] (✨) sparkles
|
||||||
|
274C ; Emoji_Presentation # 6.0 [1] (❌) cross mark
|
||||||
|
274E ; Emoji_Presentation # 6.0 [1] (❎) cross mark button
|
||||||
|
2753..2755 ; Emoji_Presentation # 6.0 [3] (❓..❕) question mark..white exclamation mark
|
||||||
|
2757 ; Emoji_Presentation # 5.2 [1] (❗) exclamation mark
|
||||||
|
2795..2797 ; Emoji_Presentation # 6.0 [3] (➕..➗) plus sign..division sign
|
||||||
|
27B0 ; Emoji_Presentation # 6.0 [1] (➰) curly loop
|
||||||
|
27BF ; Emoji_Presentation # 6.0 [1] (➿) double curly loop
|
||||||
|
2B1B..2B1C ; Emoji_Presentation # 5.1 [2] (⬛..⬜) black large square..white large square
|
||||||
|
2B50 ; Emoji_Presentation # 5.1 [1] (⭐) star
|
||||||
|
2B55 ; Emoji_Presentation # 5.2 [1] (⭕) hollow red circle
|
||||||
|
1F004 ; Emoji_Presentation # 5.1 [1] (🀄) mahjong red dragon
|
||||||
|
1F0CF ; Emoji_Presentation # 6.0 [1] (🃏) joker
|
||||||
|
1F18E ; Emoji_Presentation # 6.0 [1] (🆎) AB button (blood type)
|
||||||
|
1F191..1F19A ; Emoji_Presentation # 6.0 [10] (🆑..🆚) CL button..VS button
|
||||||
|
1F1E6..1F1FF ; Emoji_Presentation # 6.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z
|
||||||
|
1F201 ; Emoji_Presentation # 6.0 [1] (🈁) Japanese “here” button
|
||||||
|
1F21A ; Emoji_Presentation # 5.2 [1] (🈚) Japanese “free of charge” button
|
||||||
|
1F22F ; Emoji_Presentation # 5.2 [1] (🈯) Japanese “reserved” button
|
||||||
|
1F232..1F236 ; Emoji_Presentation # 6.0 [5] (🈲..🈶) Japanese “prohibited” button..Japanese “not free of charge” button
|
||||||
|
1F238..1F23A ; Emoji_Presentation # 6.0 [3] (🈸..🈺) Japanese “application” button..Japanese “open for business” button
|
||||||
|
1F250..1F251 ; Emoji_Presentation # 6.0 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button
|
||||||
|
1F300..1F320 ; Emoji_Presentation # 6.0 [33] (🌀..🌠) cyclone..shooting star
|
||||||
|
1F32D..1F32F ; Emoji_Presentation # 8.0 [3] (🌭..🌯) hot dog..burrito
|
||||||
|
1F330..1F335 ; Emoji_Presentation # 6.0 [6] (🌰..🌵) chestnut..cactus
|
||||||
|
1F337..1F37C ; Emoji_Presentation # 6.0 [70] (🌷..🍼) tulip..baby bottle
|
||||||
|
1F37E..1F37F ; Emoji_Presentation # 8.0 [2] (🍾..🍿) bottle with popping cork..popcorn
|
||||||
|
1F380..1F393 ; Emoji_Presentation # 6.0 [20] (🎀..🎓) ribbon..graduation cap
|
||||||
|
1F3A0..1F3C4 ; Emoji_Presentation # 6.0 [37] (🎠..🏄) carousel horse..person surfing
|
||||||
|
1F3C5 ; Emoji_Presentation # 7.0 [1] (🏅) sports medal
|
||||||
|
1F3C6..1F3CA ; Emoji_Presentation # 6.0 [5] (🏆..🏊) trophy..person swimming
|
||||||
|
1F3CF..1F3D3 ; Emoji_Presentation # 8.0 [5] (🏏..🏓) cricket game..ping pong
|
||||||
|
1F3E0..1F3F0 ; Emoji_Presentation # 6.0 [17] (🏠..🏰) house..castle
|
||||||
|
1F3F4 ; Emoji_Presentation # 7.0 [1] (🏴) black flag
|
||||||
|
1F3F8..1F3FF ; Emoji_Presentation # 8.0 [8] (🏸..🏿) badminton..dark skin tone
|
||||||
|
1F400..1F43E ; Emoji_Presentation # 6.0 [63] (🐀..🐾) rat..paw prints
|
||||||
|
1F440 ; Emoji_Presentation # 6.0 [1] (👀) eyes
|
||||||
|
1F442..1F4F7 ; Emoji_Presentation # 6.0[182] (👂..📷) ear..camera
|
||||||
|
1F4F8 ; Emoji_Presentation # 7.0 [1] (📸) camera with flash
|
||||||
|
1F4F9..1F4FC ; Emoji_Presentation # 6.0 [4] (📹..📼) video camera..videocassette
|
||||||
|
1F4FF ; Emoji_Presentation # 8.0 [1] (📿) prayer beads
|
||||||
|
1F500..1F53D ; Emoji_Presentation # 6.0 [62] (🔀..🔽) shuffle tracks button..downwards button
|
||||||
|
1F54B..1F54E ; Emoji_Presentation # 8.0 [4] (🕋..🕎) kaaba..menorah
|
||||||
|
1F550..1F567 ; Emoji_Presentation # 6.0 [24] (🕐..🕧) one o’clock..twelve-thirty
|
||||||
|
1F57A ; Emoji_Presentation # 9.0 [1] (🕺) man dancing
|
||||||
|
1F595..1F596 ; Emoji_Presentation # 7.0 [2] (🖕..🖖) middle finger..vulcan salute
|
||||||
|
1F5A4 ; Emoji_Presentation # 9.0 [1] (🖤) black heart
|
||||||
|
1F5FB..1F5FF ; Emoji_Presentation # 6.0 [5] (🗻..🗿) mount fuji..moai
|
||||||
|
1F600 ; Emoji_Presentation # 6.1 [1] (😀) grinning face
|
||||||
|
1F601..1F610 ; Emoji_Presentation # 6.0 [16] (😁..😐) beaming face with smiling eyes..neutral face
|
||||||
|
1F611 ; Emoji_Presentation # 6.1 [1] (😑) expressionless face
|
||||||
|
1F612..1F614 ; Emoji_Presentation # 6.0 [3] (😒..😔) unamused face..pensive face
|
||||||
|
1F615 ; Emoji_Presentation # 6.1 [1] (😕) confused face
|
||||||
|
1F616 ; Emoji_Presentation # 6.0 [1] (😖) confounded face
|
||||||
|
1F617 ; Emoji_Presentation # 6.1 [1] (😗) kissing face
|
||||||
|
1F618 ; Emoji_Presentation # 6.0 [1] (😘) face blowing a kiss
|
||||||
|
1F619 ; Emoji_Presentation # 6.1 [1] (😙) kissing face with smiling eyes
|
||||||
|
1F61A ; Emoji_Presentation # 6.0 [1] (😚) kissing face with closed eyes
|
||||||
|
1F61B ; Emoji_Presentation # 6.1 [1] (😛) face with tongue
|
||||||
|
1F61C..1F61E ; Emoji_Presentation # 6.0 [3] (😜..😞) winking face with tongue..disappointed face
|
||||||
|
1F61F ; Emoji_Presentation # 6.1 [1] (😟) worried face
|
||||||
|
1F620..1F625 ; Emoji_Presentation # 6.0 [6] (😠..😥) angry face..sad but relieved face
|
||||||
|
1F626..1F627 ; Emoji_Presentation # 6.1 [2] (😦..😧) frowning face with open mouth..anguished face
|
||||||
|
1F628..1F62B ; Emoji_Presentation # 6.0 [4] (😨..😫) fearful face..tired face
|
||||||
|
1F62C ; Emoji_Presentation # 6.1 [1] (😬) grimacing face
|
||||||
|
1F62D ; Emoji_Presentation # 6.0 [1] (😭) loudly crying face
|
||||||
|
1F62E..1F62F ; Emoji_Presentation # 6.1 [2] (😮..😯) face with open mouth..hushed face
|
||||||
|
1F630..1F633 ; Emoji_Presentation # 6.0 [4] (😰..😳) anxious face with sweat..flushed face
|
||||||
|
1F634 ; Emoji_Presentation # 6.1 [1] (😴) sleeping face
|
||||||
|
1F635..1F640 ; Emoji_Presentation # 6.0 [12] (😵..🙀) dizzy face..weary cat
|
||||||
|
1F641..1F642 ; Emoji_Presentation # 7.0 [2] (🙁..🙂) slightly frowning face..slightly smiling face
|
||||||
|
1F643..1F644 ; Emoji_Presentation # 8.0 [2] (🙃..🙄) upside-down face..face with rolling eyes
|
||||||
|
1F645..1F64F ; Emoji_Presentation # 6.0 [11] (🙅..🙏) person gesturing NO..folded hands
|
||||||
|
1F680..1F6C5 ; Emoji_Presentation # 6.0 [70] (🚀..🛅) rocket..left luggage
|
||||||
|
1F6CC ; Emoji_Presentation # 7.0 [1] (🛌) person in bed
|
||||||
|
1F6D0 ; Emoji_Presentation # 8.0 [1] (🛐) place of worship
|
||||||
|
1F6D1..1F6D2 ; Emoji_Presentation # 9.0 [2] (🛑..🛒) stop sign..shopping cart
|
||||||
|
1F6D5 ; Emoji_Presentation # 12.0 [1] (🛕) hindu temple
|
||||||
|
1F6EB..1F6EC ; Emoji_Presentation # 7.0 [2] (🛫..🛬) airplane departure..airplane arrival
|
||||||
|
1F6F4..1F6F6 ; Emoji_Presentation # 9.0 [3] (🛴..🛶) kick scooter..canoe
|
||||||
|
1F6F7..1F6F8 ; Emoji_Presentation # 10.0 [2] (🛷..🛸) sled..flying saucer
|
||||||
|
1F6F9 ; Emoji_Presentation # 11.0 [1] (🛹) skateboard
|
||||||
|
1F6FA ; Emoji_Presentation # 12.0 [1] (🛺) auto rickshaw
|
||||||
|
1F7E0..1F7EB ; Emoji_Presentation # 12.0 [12] (🟠..🟫) orange circle..brown square
|
||||||
|
1F90D..1F90F ; Emoji_Presentation # 12.0 [3] (🤍..🤏) white heart..pinching hand
|
||||||
|
1F910..1F918 ; Emoji_Presentation # 8.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns
|
||||||
|
1F919..1F91E ; Emoji_Presentation # 9.0 [6] (🤙..🤞) call me hand..crossed fingers
|
||||||
|
1F91F ; Emoji_Presentation # 10.0 [1] (🤟) love-you gesture
|
||||||
|
1F920..1F927 ; Emoji_Presentation # 9.0 [8] (🤠..🤧) cowboy hat face..sneezing face
|
||||||
|
1F928..1F92F ; Emoji_Presentation # 10.0 [8] (🤨..🤯) face with raised eyebrow..exploding head
|
||||||
|
1F930 ; Emoji_Presentation # 9.0 [1] (🤰) pregnant woman
|
||||||
|
1F931..1F932 ; Emoji_Presentation # 10.0 [2] (🤱..🤲) breast-feeding..palms up together
|
||||||
|
1F933..1F93A ; Emoji_Presentation # 9.0 [8] (🤳..🤺) selfie..person fencing
|
||||||
|
1F93C..1F93E ; Emoji_Presentation # 9.0 [3] (🤼..🤾) people wrestling..person playing handball
|
||||||
|
1F93F ; Emoji_Presentation # 12.0 [1] (🤿) diving mask
|
||||||
|
1F940..1F945 ; Emoji_Presentation # 9.0 [6] (🥀..🥅) wilted flower..goal net
|
||||||
|
1F947..1F94B ; Emoji_Presentation # 9.0 [5] (🥇..🥋) 1st place medal..martial arts uniform
|
||||||
|
1F94C ; Emoji_Presentation # 10.0 [1] (🥌) curling stone
|
||||||
|
1F94D..1F94F ; Emoji_Presentation # 11.0 [3] (🥍..🥏) lacrosse..flying disc
|
||||||
|
1F950..1F95E ; Emoji_Presentation # 9.0 [15] (🥐..🥞) croissant..pancakes
|
||||||
|
1F95F..1F96B ; Emoji_Presentation # 10.0 [13] (🥟..🥫) dumpling..canned food
|
||||||
|
1F96C..1F970 ; Emoji_Presentation # 11.0 [5] (🥬..🥰) leafy green..smiling face with hearts
|
||||||
|
1F971 ; Emoji_Presentation # 12.0 [1] (🥱) yawning face
|
||||||
|
1F973..1F976 ; Emoji_Presentation # 11.0 [4] (🥳..🥶) partying face..cold face
|
||||||
|
1F97A ; Emoji_Presentation # 11.0 [1] (🥺) pleading face
|
||||||
|
1F97B ; Emoji_Presentation # 12.0 [1] (🥻) sari
|
||||||
|
1F97C..1F97F ; Emoji_Presentation # 11.0 [4] (🥼..🥿) lab coat..flat shoe
|
||||||
|
1F980..1F984 ; Emoji_Presentation # 8.0 [5] (🦀..🦄) crab..unicorn
|
||||||
|
1F985..1F991 ; Emoji_Presentation # 9.0 [13] (🦅..🦑) eagle..squid
|
||||||
|
1F992..1F997 ; Emoji_Presentation # 10.0 [6] (🦒..🦗) giraffe..cricket
|
||||||
|
1F998..1F9A2 ; Emoji_Presentation # 11.0 [11] (🦘..🦢) kangaroo..swan
|
||||||
|
1F9A5..1F9AA ; Emoji_Presentation # 12.0 [6] (🦥..🦪) sloth..oyster
|
||||||
|
1F9AE..1F9AF ; Emoji_Presentation # 12.0 [2] (🦮..🦯) guide dog..probing cane
|
||||||
|
1F9B0..1F9B9 ; Emoji_Presentation # 11.0 [10] (🦰..🦹) red hair..supervillain
|
||||||
|
1F9BA..1F9BF ; Emoji_Presentation # 12.0 [6] (🦺..🦿) safety vest..mechanical leg
|
||||||
|
1F9C0 ; Emoji_Presentation # 8.0 [1] (🧀) cheese wedge
|
||||||
|
1F9C1..1F9C2 ; Emoji_Presentation # 11.0 [2] (🧁..🧂) cupcake..salt
|
||||||
|
1F9C3..1F9CA ; Emoji_Presentation # 12.0 [8] (🧃..🧊) beverage box..ice cube
|
||||||
|
1F9CD..1F9CF ; Emoji_Presentation # 12.0 [3] (🧍..🧏) person standing..deaf person
|
||||||
|
1F9D0..1F9E6 ; Emoji_Presentation # 10.0 [23] (🧐..🧦) face with monocle..socks
|
||||||
|
1F9E7..1F9FF ; Emoji_Presentation # 11.0 [25] (🧧..🧿) red envelope..nazar amulet
|
||||||
|
1FA70..1FA73 ; Emoji_Presentation # 12.0 [4] (🩰..🩳) ballet shoes..shorts
|
||||||
|
1FA78..1FA7A ; Emoji_Presentation # 12.0 [3] (🩸..🩺) drop of blood..stethoscope
|
||||||
|
1FA80..1FA82 ; Emoji_Presentation # 12.0 [3] (🪀..🪂) yo-yo..parachute
|
||||||
|
1FA90..1FA95 ; Emoji_Presentation # 12.0 [6] (🪐..🪕) ringed planet..banjo
|
||||||
|
|
||||||
|
# Total elements: 1093
|
||||||
|
|
||||||
|
# ================================================
|
||||||
|
|
||||||
|
# All omitted code points have Emoji_Modifier=No
|
||||||
|
# @missing: 0000..10FFFF ; Emoji_Modifier ; No
|
||||||
|
|
||||||
|
1F3FB..1F3FF ; Emoji_Modifier # 8.0 [5] (🏻..🏿) light skin tone..dark skin tone
|
||||||
|
|
||||||
|
# Total elements: 5
|
||||||
|
|
||||||
|
# ================================================
|
||||||
|
|
||||||
|
# All omitted code points have Emoji_Modifier_Base=No
|
||||||
|
# @missing: 0000..10FFFF ; Emoji_Modifier_Base ; No
|
||||||
|
|
||||||
|
261D ; Emoji_Modifier_Base # 1.1 [1] (☝️) index pointing up
|
||||||
|
26F9 ; Emoji_Modifier_Base # 5.2 [1] (⛹️) person bouncing ball
|
||||||
|
270A..270B ; Emoji_Modifier_Base # 6.0 [2] (✊..✋) raised fist..raised hand
|
||||||
|
270C..270D ; Emoji_Modifier_Base # 1.1 [2] (✌️..✍️) victory hand..writing hand
|
||||||
|
1F385 ; Emoji_Modifier_Base # 6.0 [1] (🎅) Santa Claus
|
||||||
|
1F3C2..1F3C4 ; Emoji_Modifier_Base # 6.0 [3] (🏂..🏄) snowboarder..person surfing
|
||||||
|
1F3C7 ; Emoji_Modifier_Base # 6.0 [1] (🏇) horse racing
|
||||||
|
1F3CA ; Emoji_Modifier_Base # 6.0 [1] (🏊) person swimming
|
||||||
|
1F3CB..1F3CC ; Emoji_Modifier_Base # 7.0 [2] (🏋️..🏌️) person lifting weights..person golfing
|
||||||
|
1F442..1F443 ; Emoji_Modifier_Base # 6.0 [2] (👂..👃) ear..nose
|
||||||
|
1F446..1F450 ; Emoji_Modifier_Base # 6.0 [11] (👆..👐) backhand index pointing up..open hands
|
||||||
|
1F466..1F478 ; Emoji_Modifier_Base # 6.0 [19] (👦..👸) boy..princess
|
||||||
|
1F47C ; Emoji_Modifier_Base # 6.0 [1] (👼) baby angel
|
||||||
|
1F481..1F483 ; Emoji_Modifier_Base # 6.0 [3] (💁..💃) person tipping hand..woman dancing
|
||||||
|
1F485..1F487 ; Emoji_Modifier_Base # 6.0 [3] (💅..💇) nail polish..person getting haircut
|
||||||
|
1F48F ; Emoji_Modifier_Base # 6.0 [1] (💏) kiss
|
||||||
|
1F491 ; Emoji_Modifier_Base # 6.0 [1] (💑) couple with heart
|
||||||
|
1F4AA ; Emoji_Modifier_Base # 6.0 [1] (💪) flexed biceps
|
||||||
|
1F574..1F575 ; Emoji_Modifier_Base # 7.0 [2] (🕴️..🕵️) man in suit levitating..detective
|
||||||
|
1F57A ; Emoji_Modifier_Base # 9.0 [1] (🕺) man dancing
|
||||||
|
1F590 ; Emoji_Modifier_Base # 7.0 [1] (🖐️) hand with fingers splayed
|
||||||
|
1F595..1F596 ; Emoji_Modifier_Base # 7.0 [2] (🖕..🖖) middle finger..vulcan salute
|
||||||
|
1F645..1F647 ; Emoji_Modifier_Base # 6.0 [3] (🙅..🙇) person gesturing NO..person bowing
|
||||||
|
1F64B..1F64F ; Emoji_Modifier_Base # 6.0 [5] (🙋..🙏) person raising hand..folded hands
|
||||||
|
1F6A3 ; Emoji_Modifier_Base # 6.0 [1] (🚣) person rowing boat
|
||||||
|
1F6B4..1F6B6 ; Emoji_Modifier_Base # 6.0 [3] (🚴..🚶) person biking..person walking
|
||||||
|
1F6C0 ; Emoji_Modifier_Base # 6.0 [1] (🛀) person taking bath
|
||||||
|
1F6CC ; Emoji_Modifier_Base # 7.0 [1] (🛌) person in bed
|
||||||
|
1F90F ; Emoji_Modifier_Base # 12.0 [1] (🤏) pinching hand
|
||||||
|
1F918 ; Emoji_Modifier_Base # 8.0 [1] (🤘) sign of the horns
|
||||||
|
1F919..1F91E ; Emoji_Modifier_Base # 9.0 [6] (🤙..🤞) call me hand..crossed fingers
|
||||||
|
1F91F ; Emoji_Modifier_Base # 10.0 [1] (🤟) love-you gesture
|
||||||
|
1F926 ; Emoji_Modifier_Base # 9.0 [1] (🤦) person facepalming
|
||||||
|
1F930 ; Emoji_Modifier_Base # 9.0 [1] (🤰) pregnant woman
|
||||||
|
1F931..1F932 ; Emoji_Modifier_Base # 10.0 [2] (🤱..🤲) breast-feeding..palms up together
|
||||||
|
1F933..1F939 ; Emoji_Modifier_Base # 9.0 [7] (🤳..🤹) selfie..person juggling
|
||||||
|
1F93C..1F93E ; Emoji_Modifier_Base # 9.0 [3] (🤼..🤾) people wrestling..person playing handball
|
||||||
|
1F9B5..1F9B6 ; Emoji_Modifier_Base # 11.0 [2] (🦵..🦶) leg..foot
|
||||||
|
1F9B8..1F9B9 ; Emoji_Modifier_Base # 11.0 [2] (🦸..🦹) superhero..supervillain
|
||||||
|
1F9BB ; Emoji_Modifier_Base # 12.0 [1] (🦻) ear with hearing aid
|
||||||
|
1F9CD..1F9CF ; Emoji_Modifier_Base # 12.0 [3] (🧍..🧏) person standing..deaf person
|
||||||
|
1F9D1..1F9DD ; Emoji_Modifier_Base # 10.0 [13] (🧑..🧝) person..elf
|
||||||
|
|
||||||
|
# Total elements: 120
|
||||||
|
|
||||||
|
# ================================================
|
||||||
|
|
||||||
|
# All omitted code points have Emoji_Component=No
|
||||||
|
# @missing: 0000..10FFFF ; Emoji_Component ; No
|
||||||
|
|
||||||
|
0023 ; Emoji_Component # 1.1 [1] (#️) number sign
|
||||||
|
002A ; Emoji_Component # 1.1 [1] (*️) asterisk
|
||||||
|
0030..0039 ; Emoji_Component # 1.1 [10] (0️..9️) digit zero..digit nine
|
||||||
|
200D ; Emoji_Component # 1.1 [1] () zero width joiner
|
||||||
|
20E3 ; Emoji_Component # 3.0 [1] (⃣) combining enclosing keycap
|
||||||
|
FE0F ; Emoji_Component # 3.2 [1] () VARIATION SELECTOR-16
|
||||||
|
1F1E6..1F1FF ; Emoji_Component # 6.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z
|
||||||
|
1F3FB..1F3FF ; Emoji_Component # 8.0 [5] (🏻..🏿) light skin tone..dark skin tone
|
||||||
|
1F9B0..1F9B3 ; Emoji_Component # 11.0 [4] (🦰..🦳) red hair..white hair
|
||||||
|
E0020..E007F ; Emoji_Component # 3.1 [96] (..) tag space..cancel tag
|
||||||
|
|
||||||
|
# Total elements: 146
|
||||||
|
|
||||||
|
# ================================================
|
||||||
|
|
||||||
|
# All omitted code points have Extended_Pictographic=No
|
||||||
|
# @missing: 0000..10FFFF ; Extended_Pictographic ; No
|
||||||
|
|
||||||
|
00A9 ; Extended_Pictographic# 1.1 [1] (©️) copyright
|
||||||
|
00AE ; Extended_Pictographic# 1.1 [1] (®️) registered
|
||||||
|
203C ; Extended_Pictographic# 1.1 [1] (‼️) double exclamation mark
|
||||||
|
2049 ; Extended_Pictographic# 3.0 [1] (⁉️) exclamation question mark
|
||||||
|
2122 ; Extended_Pictographic# 1.1 [1] (™️) trade mark
|
||||||
|
2139 ; Extended_Pictographic# 3.0 [1] (ℹ️) information
|
||||||
|
2194..2199 ; Extended_Pictographic# 1.1 [6] (↔️..↙️) left-right arrow..down-left arrow
|
||||||
|
21A9..21AA ; Extended_Pictographic# 1.1 [2] (↩️..↪️) right arrow curving left..left arrow curving right
|
||||||
|
231A..231B ; Extended_Pictographic# 1.1 [2] (⌚..⌛) watch..hourglass done
|
||||||
|
2328 ; Extended_Pictographic# 1.1 [1] (⌨️) keyboard
|
||||||
|
2388 ; Extended_Pictographic# 3.0 [1] (⎈) HELM SYMBOL
|
||||||
|
23CF ; Extended_Pictographic# 4.0 [1] (⏏️) eject button
|
||||||
|
23E9..23F3 ; Extended_Pictographic# 6.0 [11] (⏩..⏳) fast-forward button..hourglass not done
|
||||||
|
23F8..23FA ; Extended_Pictographic# 7.0 [3] (⏸️..⏺️) pause button..record button
|
||||||
|
24C2 ; Extended_Pictographic# 1.1 [1] (Ⓜ️) circled M
|
||||||
|
25AA..25AB ; Extended_Pictographic# 1.1 [2] (▪️..▫️) black small square..white small square
|
||||||
|
25B6 ; Extended_Pictographic# 1.1 [1] (▶️) play button
|
||||||
|
25C0 ; Extended_Pictographic# 1.1 [1] (◀️) reverse button
|
||||||
|
25FB..25FE ; Extended_Pictographic# 3.2 [4] (◻️..◾) white medium square..black medium-small square
|
||||||
|
2600..2605 ; Extended_Pictographic# 1.1 [6] (☀️..★) sun..BLACK STAR
|
||||||
|
2607..2612 ; Extended_Pictographic# 1.1 [12] (☇..☒) LIGHTNING..BALLOT BOX WITH X
|
||||||
|
2614..2615 ; Extended_Pictographic# 4.0 [2] (☔..☕) umbrella with rain drops..hot beverage
|
||||||
|
2616..2617 ; Extended_Pictographic# 3.2 [2] (☖..☗) WHITE SHOGI PIECE..BLACK SHOGI PIECE
|
||||||
|
2618 ; Extended_Pictographic# 4.1 [1] (☘️) shamrock
|
||||||
|
2619 ; Extended_Pictographic# 3.0 [1] (☙) REVERSED ROTATED FLORAL HEART BULLET
|
||||||
|
261A..266F ; Extended_Pictographic# 1.1 [86] (☚..♯) BLACK LEFT POINTING INDEX..MUSIC SHARP SIGN
|
||||||
|
2670..2671 ; Extended_Pictographic# 3.0 [2] (♰..♱) WEST SYRIAC CROSS..EAST SYRIAC CROSS
|
||||||
|
2672..267D ; Extended_Pictographic# 3.2 [12] (♲..♽) UNIVERSAL RECYCLING SYMBOL..PARTIALLY-RECYCLED PAPER SYMBOL
|
||||||
|
267E..267F ; Extended_Pictographic# 4.1 [2] (♾️..♿) infinity..wheelchair symbol
|
||||||
|
2680..2685 ; Extended_Pictographic# 3.2 [6] (⚀..⚅) DIE FACE-1..DIE FACE-6
|
||||||
|
2690..2691 ; Extended_Pictographic# 4.0 [2] (⚐..⚑) WHITE FLAG..BLACK FLAG
|
||||||
|
2692..269C ; Extended_Pictographic# 4.1 [11] (⚒️..⚜️) hammer and pick..fleur-de-lis
|
||||||
|
269D ; Extended_Pictographic# 5.1 [1] (⚝) OUTLINED WHITE STAR
|
||||||
|
269E..269F ; Extended_Pictographic# 5.2 [2] (⚞..⚟) THREE LINES CONVERGING RIGHT..THREE LINES CONVERGING LEFT
|
||||||
|
26A0..26A1 ; Extended_Pictographic# 4.0 [2] (⚠️..⚡) warning..high voltage
|
||||||
|
26A2..26B1 ; Extended_Pictographic# 4.1 [16] (⚢..⚱️) DOUBLED FEMALE SIGN..funeral urn
|
||||||
|
26B2 ; Extended_Pictographic# 5.0 [1] (⚲) NEUTER
|
||||||
|
26B3..26BC ; Extended_Pictographic# 5.1 [10] (⚳..⚼) CERES..SESQUIQUADRATE
|
||||||
|
26BD..26BF ; Extended_Pictographic# 5.2 [3] (⚽..⚿) soccer ball..SQUARED KEY
|
||||||
|
26C0..26C3 ; Extended_Pictographic# 5.1 [4] (⛀..⛃) WHITE DRAUGHTS MAN..BLACK DRAUGHTS KING
|
||||||
|
26C4..26CD ; Extended_Pictographic# 5.2 [10] (⛄..⛍) snowman without snow..DISABLED CAR
|
||||||
|
26CE ; Extended_Pictographic# 6.0 [1] (⛎) Ophiuchus
|
||||||
|
26CF..26E1 ; Extended_Pictographic# 5.2 [19] (⛏️..⛡) pick..RESTRICTED LEFT ENTRY-2
|
||||||
|
26E2 ; Extended_Pictographic# 6.0 [1] (⛢) ASTRONOMICAL SYMBOL FOR URANUS
|
||||||
|
26E3 ; Extended_Pictographic# 5.2 [1] (⛣) HEAVY CIRCLE WITH STROKE AND TWO DOTS ABOVE
|
||||||
|
26E4..26E7 ; Extended_Pictographic# 6.0 [4] (⛤..⛧) PENTAGRAM..INVERTED PENTAGRAM
|
||||||
|
26E8..26FF ; Extended_Pictographic# 5.2 [24] (⛨..⛿) BLACK CROSS ON SHIELD..WHITE FLAG WITH HORIZONTAL MIDDLE BLACK STRIPE
|
||||||
|
2700 ; Extended_Pictographic# 7.0 [1] (✀) BLACK SAFETY SCISSORS
|
||||||
|
2701..2704 ; Extended_Pictographic# 1.1 [4] (✁..✄) UPPER BLADE SCISSORS..WHITE SCISSORS
|
||||||
|
2705 ; Extended_Pictographic# 6.0 [1] (✅) check mark button
|
||||||
|
2708..2709 ; Extended_Pictographic# 1.1 [2] (✈️..✉️) airplane..envelope
|
||||||
|
270A..270B ; Extended_Pictographic# 6.0 [2] (✊..✋) raised fist..raised hand
|
||||||
|
270C..2712 ; Extended_Pictographic# 1.1 [7] (✌️..✒️) victory hand..black nib
|
||||||
|
2714 ; Extended_Pictographic# 1.1 [1] (✔️) check mark
|
||||||
|
2716 ; Extended_Pictographic# 1.1 [1] (✖️) multiplication sign
|
||||||
|
271D ; Extended_Pictographic# 1.1 [1] (✝️) latin cross
|
||||||
|
2721 ; Extended_Pictographic# 1.1 [1] (✡️) star of David
|
||||||
|
2728 ; Extended_Pictographic# 6.0 [1] (✨) sparkles
|
||||||
|
2733..2734 ; Extended_Pictographic# 1.1 [2] (✳️..✴️) eight-spoked asterisk..eight-pointed star
|
||||||
|
2744 ; Extended_Pictographic# 1.1 [1] (❄️) snowflake
|
||||||
|
2747 ; Extended_Pictographic# 1.1 [1] (❇️) sparkle
|
||||||
|
274C ; Extended_Pictographic# 6.0 [1] (❌) cross mark
|
||||||
|
274E ; Extended_Pictographic# 6.0 [1] (❎) cross mark button
|
||||||
|
2753..2755 ; Extended_Pictographic# 6.0 [3] (❓..❕) question mark..white exclamation mark
|
||||||
|
2757 ; Extended_Pictographic# 5.2 [1] (❗) exclamation mark
|
||||||
|
2763..2767 ; Extended_Pictographic# 1.1 [5] (❣️..❧) heart exclamation..ROTATED FLORAL HEART BULLET
|
||||||
|
2795..2797 ; Extended_Pictographic# 6.0 [3] (➕..➗) plus sign..division sign
|
||||||
|
27A1 ; Extended_Pictographic# 1.1 [1] (➡️) right arrow
|
||||||
|
27B0 ; Extended_Pictographic# 6.0 [1] (➰) curly loop
|
||||||
|
27BF ; Extended_Pictographic# 6.0 [1] (➿) double curly loop
|
||||||
|
2934..2935 ; Extended_Pictographic# 3.2 [2] (⤴️..⤵️) right arrow curving up..right arrow curving down
|
||||||
|
2B05..2B07 ; Extended_Pictographic# 4.0 [3] (⬅️..⬇️) left arrow..down arrow
|
||||||
|
2B1B..2B1C ; Extended_Pictographic# 5.1 [2] (⬛..⬜) black large square..white large square
|
||||||
|
2B50 ; Extended_Pictographic# 5.1 [1] (⭐) star
|
||||||
|
2B55 ; Extended_Pictographic# 5.2 [1] (⭕) hollow red circle
|
||||||
|
3030 ; Extended_Pictographic# 1.1 [1] (〰️) wavy dash
|
||||||
|
303D ; Extended_Pictographic# 3.2 [1] (〽️) part alternation mark
|
||||||
|
3297 ; Extended_Pictographic# 1.1 [1] (㊗️) Japanese “congratulations” button
|
||||||
|
3299 ; Extended_Pictographic# 1.1 [1] (㊙️) Japanese “secret” button
|
||||||
|
1F000..1F02B ; Extended_Pictographic# 5.1 [44] (🀀..🀫) MAHJONG TILE EAST WIND..MAHJONG TILE BACK
|
||||||
|
1F02C..1F02F ; Extended_Pictographic# NA [4] (..) <reserved-1F02C>..<reserved-1F02F>
|
||||||
|
1F030..1F093 ; Extended_Pictographic# 5.1[100] (🀰..🂓) DOMINO TILE HORIZONTAL BACK..DOMINO TILE VERTICAL-06-06
|
||||||
|
1F094..1F09F ; Extended_Pictographic# NA [12] (..) <reserved-1F094>..<reserved-1F09F>
|
||||||
|
1F0A0..1F0AE ; Extended_Pictographic# 6.0 [15] (🂠..🂮) PLAYING CARD BACK..PLAYING CARD KING OF SPADES
|
||||||
|
1F0AF..1F0B0 ; Extended_Pictographic# NA [2] (..) <reserved-1F0AF>..<reserved-1F0B0>
|
||||||
|
1F0B1..1F0BE ; Extended_Pictographic# 6.0 [14] (🂱..🂾) PLAYING CARD ACE OF HEARTS..PLAYING CARD KING OF HEARTS
|
||||||
|
1F0BF ; Extended_Pictographic# 7.0 [1] (🂿) PLAYING CARD RED JOKER
|
||||||
|
1F0C0 ; Extended_Pictographic# NA [1] () <reserved-1F0C0>
|
||||||
|
1F0C1..1F0CF ; Extended_Pictographic# 6.0 [15] (🃁..🃏) PLAYING CARD ACE OF DIAMONDS..joker
|
||||||
|
1F0D0 ; Extended_Pictographic# NA [1] () <reserved-1F0D0>
|
||||||
|
1F0D1..1F0DF ; Extended_Pictographic# 6.0 [15] (🃑..🃟) PLAYING CARD ACE OF CLUBS..PLAYING CARD WHITE JOKER
|
||||||
|
1F0E0..1F0F5 ; Extended_Pictographic# 7.0 [22] (🃠..🃵) PLAYING CARD FOOL..PLAYING CARD TRUMP-21
|
||||||
|
1F0F6..1F0FF ; Extended_Pictographic# NA [10] (..) <reserved-1F0F6>..<reserved-1F0FF>
|
||||||
|
1F10D..1F10F ; Extended_Pictographic# NA [3] (🄍..🄏) <reserved-1F10D>..<reserved-1F10F>
|
||||||
|
1F12F ; Extended_Pictographic# 11.0 [1] (🄯) COPYLEFT SYMBOL
|
||||||
|
1F16C ; Extended_Pictographic# 12.0 [1] (🅬) RAISED MR SIGN
|
||||||
|
1F16D..1F16F ; Extended_Pictographic# NA [3] (🅭..🅯) <reserved-1F16D>..<reserved-1F16F>
|
||||||
|
1F170..1F171 ; Extended_Pictographic# 6.0 [2] (🅰️..🅱️) A button (blood type)..B button (blood type)
|
||||||
|
1F17E ; Extended_Pictographic# 6.0 [1] (🅾️) O button (blood type)
|
||||||
|
1F17F ; Extended_Pictographic# 5.2 [1] (🅿️) P button
|
||||||
|
1F18E ; Extended_Pictographic# 6.0 [1] (🆎) AB button (blood type)
|
||||||
|
1F191..1F19A ; Extended_Pictographic# 6.0 [10] (🆑..🆚) CL button..VS button
|
||||||
|
1F1AD..1F1E5 ; Extended_Pictographic# NA [57] (🆭..) <reserved-1F1AD>..<reserved-1F1E5>
|
||||||
|
1F201..1F202 ; Extended_Pictographic# 6.0 [2] (🈁..🈂️) Japanese “here” button..Japanese “service charge” button
|
||||||
|
1F203..1F20F ; Extended_Pictographic# NA [13] (..) <reserved-1F203>..<reserved-1F20F>
|
||||||
|
1F21A ; Extended_Pictographic# 5.2 [1] (🈚) Japanese “free of charge” button
|
||||||
|
1F22F ; Extended_Pictographic# 5.2 [1] (🈯) Japanese “reserved” button
|
||||||
|
1F232..1F23A ; Extended_Pictographic# 6.0 [9] (🈲..🈺) Japanese “prohibited” button..Japanese “open for business” button
|
||||||
|
1F23C..1F23F ; Extended_Pictographic# NA [4] (..) <reserved-1F23C>..<reserved-1F23F>
|
||||||
|
1F249..1F24F ; Extended_Pictographic# NA [7] (..) <reserved-1F249>..<reserved-1F24F>
|
||||||
|
1F250..1F251 ; Extended_Pictographic# 6.0 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button
|
||||||
|
1F252..1F25F ; Extended_Pictographic# NA [14] (..) <reserved-1F252>..<reserved-1F25F>
|
||||||
|
1F260..1F265 ; Extended_Pictographic# 10.0 [6] (🉠..🉥) ROUNDED SYMBOL FOR FU..ROUNDED SYMBOL FOR CAI
|
||||||
|
1F266..1F2FF ; Extended_Pictographic# NA[154] (..) <reserved-1F266>..<reserved-1F2FF>
|
||||||
|
1F300..1F320 ; Extended_Pictographic# 6.0 [33] (🌀..🌠) cyclone..shooting star
|
||||||
|
1F321..1F32C ; Extended_Pictographic# 7.0 [12] (🌡️..🌬️) thermometer..wind face
|
||||||
|
1F32D..1F32F ; Extended_Pictographic# 8.0 [3] (🌭..🌯) hot dog..burrito
|
||||||
|
1F330..1F335 ; Extended_Pictographic# 6.0 [6] (🌰..🌵) chestnut..cactus
|
||||||
|
1F336 ; Extended_Pictographic# 7.0 [1] (🌶️) hot pepper
|
||||||
|
1F337..1F37C ; Extended_Pictographic# 6.0 [70] (🌷..🍼) tulip..baby bottle
|
||||||
|
1F37D ; Extended_Pictographic# 7.0 [1] (🍽️) fork and knife with plate
|
||||||
|
1F37E..1F37F ; Extended_Pictographic# 8.0 [2] (🍾..🍿) bottle with popping cork..popcorn
|
||||||
|
1F380..1F393 ; Extended_Pictographic# 6.0 [20] (🎀..🎓) ribbon..graduation cap
|
||||||
|
1F394..1F39F ; Extended_Pictographic# 7.0 [12] (🎔..🎟️) HEART WITH TIP ON THE LEFT..admission tickets
|
||||||
|
1F3A0..1F3C4 ; Extended_Pictographic# 6.0 [37] (🎠..🏄) carousel horse..person surfing
|
||||||
|
1F3C5 ; Extended_Pictographic# 7.0 [1] (🏅) sports medal
|
||||||
|
1F3C6..1F3CA ; Extended_Pictographic# 6.0 [5] (🏆..🏊) trophy..person swimming
|
||||||
|
1F3CB..1F3CE ; Extended_Pictographic# 7.0 [4] (🏋️..🏎️) person lifting weights..racing car
|
||||||
|
1F3CF..1F3D3 ; Extended_Pictographic# 8.0 [5] (🏏..🏓) cricket game..ping pong
|
||||||
|
1F3D4..1F3DF ; Extended_Pictographic# 7.0 [12] (🏔️..🏟️) snow-capped mountain..stadium
|
||||||
|
1F3E0..1F3F0 ; Extended_Pictographic# 6.0 [17] (🏠..🏰) house..castle
|
||||||
|
1F3F1..1F3F7 ; Extended_Pictographic# 7.0 [7] (🏱..🏷️) WHITE PENNANT..label
|
||||||
|
1F3F8..1F3FA ; Extended_Pictographic# 8.0 [3] (🏸..🏺) badminton..amphora
|
||||||
|
1F400..1F43E ; Extended_Pictographic# 6.0 [63] (🐀..🐾) rat..paw prints
|
||||||
|
1F43F ; Extended_Pictographic# 7.0 [1] (🐿️) chipmunk
|
||||||
|
1F440 ; Extended_Pictographic# 6.0 [1] (👀) eyes
|
||||||
|
1F441 ; Extended_Pictographic# 7.0 [1] (👁️) eye
|
||||||
|
1F442..1F4F7 ; Extended_Pictographic# 6.0[182] (👂..📷) ear..camera
|
||||||
|
1F4F8 ; Extended_Pictographic# 7.0 [1] (📸) camera with flash
|
||||||
|
1F4F9..1F4FC ; Extended_Pictographic# 6.0 [4] (📹..📼) video camera..videocassette
|
||||||
|
1F4FD..1F4FE ; Extended_Pictographic# 7.0 [2] (📽️..📾) film projector..PORTABLE STEREO
|
||||||
|
1F4FF ; Extended_Pictographic# 8.0 [1] (📿) prayer beads
|
||||||
|
1F500..1F53D ; Extended_Pictographic# 6.0 [62] (🔀..🔽) shuffle tracks button..downwards button
|
||||||
|
1F546..1F54A ; Extended_Pictographic# 7.0 [5] (🕆..🕊️) WHITE LATIN CROSS..dove
|
||||||
|
1F54B..1F54F ; Extended_Pictographic# 8.0 [5] (🕋..🕏) kaaba..BOWL OF HYGIEIA
|
||||||
|
1F550..1F567 ; Extended_Pictographic# 6.0 [24] (🕐..🕧) one o’clock..twelve-thirty
|
||||||
|
1F568..1F579 ; Extended_Pictographic# 7.0 [18] (🕨..🕹️) RIGHT SPEAKER..joystick
|
||||||
|
1F57A ; Extended_Pictographic# 9.0 [1] (🕺) man dancing
|
||||||
|
1F57B..1F5A3 ; Extended_Pictographic# 7.0 [41] (🕻..🖣) LEFT HAND TELEPHONE RECEIVER..BLACK DOWN POINTING BACKHAND INDEX
|
||||||
|
1F5A4 ; Extended_Pictographic# 9.0 [1] (🖤) black heart
|
||||||
|
1F5A5..1F5FA ; Extended_Pictographic# 7.0 [86] (🖥️..🗺️) desktop computer..world map
|
||||||
|
1F5FB..1F5FF ; Extended_Pictographic# 6.0 [5] (🗻..🗿) mount fuji..moai
|
||||||
|
1F600 ; Extended_Pictographic# 6.1 [1] (😀) grinning face
|
||||||
|
1F601..1F610 ; Extended_Pictographic# 6.0 [16] (😁..😐) beaming face with smiling eyes..neutral face
|
||||||
|
1F611 ; Extended_Pictographic# 6.1 [1] (😑) expressionless face
|
||||||
|
1F612..1F614 ; Extended_Pictographic# 6.0 [3] (😒..😔) unamused face..pensive face
|
||||||
|
1F615 ; Extended_Pictographic# 6.1 [1] (😕) confused face
|
||||||
|
1F616 ; Extended_Pictographic# 6.0 [1] (😖) confounded face
|
||||||
|
1F617 ; Extended_Pictographic# 6.1 [1] (😗) kissing face
|
||||||
|
1F618 ; Extended_Pictographic# 6.0 [1] (😘) face blowing a kiss
|
||||||
|
1F619 ; Extended_Pictographic# 6.1 [1] (😙) kissing face with smiling eyes
|
||||||
|
1F61A ; Extended_Pictographic# 6.0 [1] (😚) kissing face with closed eyes
|
||||||
|
1F61B ; Extended_Pictographic# 6.1 [1] (😛) face with tongue
|
||||||
|
1F61C..1F61E ; Extended_Pictographic# 6.0 [3] (😜..😞) winking face with tongue..disappointed face
|
||||||
|
1F61F ; Extended_Pictographic# 6.1 [1] (😟) worried face
|
||||||
|
1F620..1F625 ; Extended_Pictographic# 6.0 [6] (😠..😥) angry face..sad but relieved face
|
||||||
|
1F626..1F627 ; Extended_Pictographic# 6.1 [2] (😦..😧) frowning face with open mouth..anguished face
|
||||||
|
1F628..1F62B ; Extended_Pictographic# 6.0 [4] (😨..😫) fearful face..tired face
|
||||||
|
1F62C ; Extended_Pictographic# 6.1 [1] (😬) grimacing face
|
||||||
|
1F62D ; Extended_Pictographic# 6.0 [1] (😭) loudly crying face
|
||||||
|
1F62E..1F62F ; Extended_Pictographic# 6.1 [2] (😮..😯) face with open mouth..hushed face
|
||||||
|
1F630..1F633 ; Extended_Pictographic# 6.0 [4] (😰..😳) anxious face with sweat..flushed face
|
||||||
|
1F634 ; Extended_Pictographic# 6.1 [1] (😴) sleeping face
|
||||||
|
1F635..1F640 ; Extended_Pictographic# 6.0 [12] (😵..🙀) dizzy face..weary cat
|
||||||
|
1F641..1F642 ; Extended_Pictographic# 7.0 [2] (🙁..🙂) slightly frowning face..slightly smiling face
|
||||||
|
1F643..1F644 ; Extended_Pictographic# 8.0 [2] (🙃..🙄) upside-down face..face with rolling eyes
|
||||||
|
1F645..1F64F ; Extended_Pictographic# 6.0 [11] (🙅..🙏) person gesturing NO..folded hands
|
||||||
|
1F680..1F6C5 ; Extended_Pictographic# 6.0 [70] (🚀..🛅) rocket..left luggage
|
||||||
|
1F6C6..1F6CF ; Extended_Pictographic# 7.0 [10] (🛆..🛏️) TRIANGLE WITH ROUNDED CORNERS..bed
|
||||||
|
1F6D0 ; Extended_Pictographic# 8.0 [1] (🛐) place of worship
|
||||||
|
1F6D1..1F6D2 ; Extended_Pictographic# 9.0 [2] (🛑..🛒) stop sign..shopping cart
|
||||||
|
1F6D3..1F6D4 ; Extended_Pictographic# 10.0 [2] (🛓..🛔) STUPA..PAGODA
|
||||||
|
1F6D5 ; Extended_Pictographic# 12.0 [1] (🛕) hindu temple
|
||||||
|
1F6D6..1F6DF ; Extended_Pictographic# NA [10] (🛖..🛟) <reserved-1F6D6>..<reserved-1F6DF>
|
||||||
|
1F6E0..1F6EC ; Extended_Pictographic# 7.0 [13] (🛠️..🛬) hammer and wrench..airplane arrival
|
||||||
|
1F6ED..1F6EF ; Extended_Pictographic# NA [3] (..) <reserved-1F6ED>..<reserved-1F6EF>
|
||||||
|
1F6F0..1F6F3 ; Extended_Pictographic# 7.0 [4] (🛰️..🛳️) satellite..passenger ship
|
||||||
|
1F6F4..1F6F6 ; Extended_Pictographic# 9.0 [3] (🛴..🛶) kick scooter..canoe
|
||||||
|
1F6F7..1F6F8 ; Extended_Pictographic# 10.0 [2] (🛷..🛸) sled..flying saucer
|
||||||
|
1F6F9 ; Extended_Pictographic# 11.0 [1] (🛹) skateboard
|
||||||
|
1F6FA ; Extended_Pictographic# 12.0 [1] (🛺) auto rickshaw
|
||||||
|
1F6FB..1F6FF ; Extended_Pictographic# NA [5] (🛻..) <reserved-1F6FB>..<reserved-1F6FF>
|
||||||
|
1F774..1F77F ; Extended_Pictographic# NA [12] (🝴..🝿) <reserved-1F774>..<reserved-1F77F>
|
||||||
|
1F7D5..1F7D8 ; Extended_Pictographic# 11.0 [4] (🟕..🟘) CIRCLED TRIANGLE..NEGATIVE CIRCLED SQUARE
|
||||||
|
1F7D9..1F7DF ; Extended_Pictographic# NA [7] (🟙..) <reserved-1F7D9>..<reserved-1F7DF>
|
||||||
|
1F7E0..1F7EB ; Extended_Pictographic# 12.0 [12] (🟠..🟫) orange circle..brown square
|
||||||
|
1F7EC..1F7FF ; Extended_Pictographic# NA [20] (..) <reserved-1F7EC>..<reserved-1F7FF>
|
||||||
|
1F80C..1F80F ; Extended_Pictographic# NA [4] (..) <reserved-1F80C>..<reserved-1F80F>
|
||||||
|
1F848..1F84F ; Extended_Pictographic# NA [8] (..) <reserved-1F848>..<reserved-1F84F>
|
||||||
|
1F85A..1F85F ; Extended_Pictographic# NA [6] (..) <reserved-1F85A>..<reserved-1F85F>
|
||||||
|
1F888..1F88F ; Extended_Pictographic# NA [8] (..) <reserved-1F888>..<reserved-1F88F>
|
||||||
|
1F8AE..1F8FF ; Extended_Pictographic# NA [82] (..) <reserved-1F8AE>..<reserved-1F8FF>
|
||||||
|
1F90C ; Extended_Pictographic# NA [1] (🤌) <reserved-1F90C>
|
||||||
|
1F90D..1F90F ; Extended_Pictographic# 12.0 [3] (🤍..🤏) white heart..pinching hand
|
||||||
|
1F910..1F918 ; Extended_Pictographic# 8.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns
|
||||||
|
1F919..1F91E ; Extended_Pictographic# 9.0 [6] (🤙..🤞) call me hand..crossed fingers
|
||||||
|
1F91F ; Extended_Pictographic# 10.0 [1] (🤟) love-you gesture
|
||||||
|
1F920..1F927 ; Extended_Pictographic# 9.0 [8] (🤠..🤧) cowboy hat face..sneezing face
|
||||||
|
1F928..1F92F ; Extended_Pictographic# 10.0 [8] (🤨..🤯) face with raised eyebrow..exploding head
|
||||||
|
1F930 ; Extended_Pictographic# 9.0 [1] (🤰) pregnant woman
|
||||||
|
1F931..1F932 ; Extended_Pictographic# 10.0 [2] (🤱..🤲) breast-feeding..palms up together
|
||||||
|
1F933..1F93A ; Extended_Pictographic# 9.0 [8] (🤳..🤺) selfie..person fencing
|
||||||
|
1F93C..1F93E ; Extended_Pictographic# 9.0 [3] (🤼..🤾) people wrestling..person playing handball
|
||||||
|
1F93F ; Extended_Pictographic# 12.0 [1] (🤿) diving mask
|
||||||
|
1F940..1F945 ; Extended_Pictographic# 9.0 [6] (🥀..🥅) wilted flower..goal net
|
||||||
|
1F947..1F94B ; Extended_Pictographic# 9.0 [5] (🥇..🥋) 1st place medal..martial arts uniform
|
||||||
|
1F94C ; Extended_Pictographic# 10.0 [1] (🥌) curling stone
|
||||||
|
1F94D..1F94F ; Extended_Pictographic# 11.0 [3] (🥍..🥏) lacrosse..flying disc
|
||||||
|
1F950..1F95E ; Extended_Pictographic# 9.0 [15] (🥐..🥞) croissant..pancakes
|
||||||
|
1F95F..1F96B ; Extended_Pictographic# 10.0 [13] (🥟..🥫) dumpling..canned food
|
||||||
|
1F96C..1F970 ; Extended_Pictographic# 11.0 [5] (🥬..🥰) leafy green..smiling face with hearts
|
||||||
|
1F971 ; Extended_Pictographic# 12.0 [1] (🥱) yawning face
|
||||||
|
1F972 ; Extended_Pictographic# NA [1] (🥲) <reserved-1F972>
|
||||||
|
1F973..1F976 ; Extended_Pictographic# 11.0 [4] (🥳..🥶) partying face..cold face
|
||||||
|
1F977..1F979 ; Extended_Pictographic# NA [3] (🥷..🥹) <reserved-1F977>..<reserved-1F979>
|
||||||
|
1F97A ; Extended_Pictographic# 11.0 [1] (🥺) pleading face
|
||||||
|
1F97B ; Extended_Pictographic# 12.0 [1] (🥻) sari
|
||||||
|
1F97C..1F97F ; Extended_Pictographic# 11.0 [4] (🥼..🥿) lab coat..flat shoe
|
||||||
|
1F980..1F984 ; Extended_Pictographic# 8.0 [5] (🦀..🦄) crab..unicorn
|
||||||
|
1F985..1F991 ; Extended_Pictographic# 9.0 [13] (🦅..🦑) eagle..squid
|
||||||
|
1F992..1F997 ; Extended_Pictographic# 10.0 [6] (🦒..🦗) giraffe..cricket
|
||||||
|
1F998..1F9A2 ; Extended_Pictographic# 11.0 [11] (🦘..🦢) kangaroo..swan
|
||||||
|
1F9A3..1F9A4 ; Extended_Pictographic# NA [2] (🦣..🦤) <reserved-1F9A3>..<reserved-1F9A4>
|
||||||
|
1F9A5..1F9AA ; Extended_Pictographic# 12.0 [6] (🦥..🦪) sloth..oyster
|
||||||
|
1F9AB..1F9AD ; Extended_Pictographic# NA [3] (🦫..🦭) <reserved-1F9AB>..<reserved-1F9AD>
|
||||||
|
1F9AE..1F9AF ; Extended_Pictographic# 12.0 [2] (🦮..🦯) guide dog..probing cane
|
||||||
|
1F9B0..1F9B9 ; Extended_Pictographic# 11.0 [10] (🦰..🦹) red hair..supervillain
|
||||||
|
1F9BA..1F9BF ; Extended_Pictographic# 12.0 [6] (🦺..🦿) safety vest..mechanical leg
|
||||||
|
1F9C0 ; Extended_Pictographic# 8.0 [1] (🧀) cheese wedge
|
||||||
|
1F9C1..1F9C2 ; Extended_Pictographic# 11.0 [2] (🧁..🧂) cupcake..salt
|
||||||
|
1F9C3..1F9CA ; Extended_Pictographic# 12.0 [8] (🧃..🧊) beverage box..ice cube
|
||||||
|
1F9CB..1F9CC ; Extended_Pictographic# NA [2] (🧋..🧌) <reserved-1F9CB>..<reserved-1F9CC>
|
||||||
|
1F9CD..1F9CF ; Extended_Pictographic# 12.0 [3] (🧍..🧏) person standing..deaf person
|
||||||
|
1F9D0..1F9E6 ; Extended_Pictographic# 10.0 [23] (🧐..🧦) face with monocle..socks
|
||||||
|
1F9E7..1F9FF ; Extended_Pictographic# 11.0 [25] (🧧..🧿) red envelope..nazar amulet
|
||||||
|
1FA00..1FA53 ; Extended_Pictographic# 12.0 [84] (🨀..🩓) NEUTRAL CHESS KING..BLACK CHESS KNIGHT-BISHOP
|
||||||
|
1FA54..1FA5F ; Extended_Pictographic# NA [12] (..) <reserved-1FA54>..<reserved-1FA5F>
|
||||||
|
1FA60..1FA6D ; Extended_Pictographic# 11.0 [14] (🩠..🩭) XIANGQI RED GENERAL..XIANGQI BLACK SOLDIER
|
||||||
|
1FA6E..1FA6F ; Extended_Pictographic# NA [2] (..) <reserved-1FA6E>..<reserved-1FA6F>
|
||||||
|
1FA70..1FA73 ; Extended_Pictographic# 12.0 [4] (🩰..🩳) ballet shoes..shorts
|
||||||
|
1FA74..1FA77 ; Extended_Pictographic# NA [4] (🩴..🩷) <reserved-1FA74>..<reserved-1FA77>
|
||||||
|
1FA78..1FA7A ; Extended_Pictographic# 12.0 [3] (🩸..🩺) drop of blood..stethoscope
|
||||||
|
1FA7B..1FA7F ; Extended_Pictographic# NA [5] (🩻..) <reserved-1FA7B>..<reserved-1FA7F>
|
||||||
|
1FA80..1FA82 ; Extended_Pictographic# 12.0 [3] (🪀..🪂) yo-yo..parachute
|
||||||
|
1FA83..1FA8F ; Extended_Pictographic# NA [13] (🪃..) <reserved-1FA83>..<reserved-1FA8F>
|
||||||
|
1FA90..1FA95 ; Extended_Pictographic# 12.0 [6] (🪐..🪕) ringed planet..banjo
|
||||||
|
1FA96..1FFFD ; Extended_Pictographic# NA[1384] (🪖..) <reserved-1FA96>..<reserved-1FFFD>
|
||||||
|
|
||||||
|
# Total elements: 3793
|
||||||
|
|
||||||
|
#EOF
|
||||||
|
|
@ -98,4 +98,35 @@ defmodule Pleroma.Emoji do
|
||||||
defp update_emojis(emojis) do
|
defp update_emojis(emojis) do
|
||||||
:ets.insert(@ets, emojis)
|
:ets.insert(@ets, emojis)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@external_resource "lib/pleroma/emoji-data.txt"
|
||||||
|
|
||||||
|
emojis =
|
||||||
|
@external_resource
|
||||||
|
|> File.read!()
|
||||||
|
|> String.split("\n")
|
||||||
|
|> Enum.filter(fn line -> line != "" and not String.starts_with?(line, "#") end)
|
||||||
|
|> Enum.map(fn line ->
|
||||||
|
line
|
||||||
|
|> String.split(";", parts: 2)
|
||||||
|
|> hd()
|
||||||
|
|> String.trim()
|
||||||
|
|> String.split("..")
|
||||||
|
|> case do
|
||||||
|
[number] ->
|
||||||
|
<<String.to_integer(number, 16)::utf8>>
|
||||||
|
|
||||||
|
[first, last] ->
|
||||||
|
String.to_integer(first, 16)..String.to_integer(last, 16)
|
||||||
|
|> Enum.map(&<<&1::utf8>>)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|> List.flatten()
|
||||||
|
|> Enum.uniq()
|
||||||
|
|
||||||
|
for emoji <- emojis do
|
||||||
|
def is_unicode_emoji?(unquote(emoji)), do: true
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_unicode_emoji?(_), do: false
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -101,10 +101,32 @@ defmodule Pleroma.FollowingRelationship do
|
||||||
|> select([r, u], u.follower_address)
|
|> select([r, u], u.follower_address)
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|
|
||||||
if not user.local or user.nickname in [nil, "internal.fetch"] do
|
if not user.local or user.invisible do
|
||||||
following
|
following
|
||||||
else
|
else
|
||||||
[user.follower_address | following]
|
[user.follower_address | following]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def move_following(origin, target) do
|
||||||
|
__MODULE__
|
||||||
|
|> join(:inner, [r], f in assoc(r, :follower))
|
||||||
|
|> where(following_id: ^origin.id)
|
||||||
|
|> where([r, f], f.allow_following_move == true)
|
||||||
|
|> limit(50)
|
||||||
|
|> preload([:follower])
|
||||||
|
|> Repo.all()
|
||||||
|
|> Enum.map(fn following_relationship ->
|
||||||
|
Repo.delete(following_relationship)
|
||||||
|
Pleroma.Web.CommonAPI.follow(following_relationship.follower, target)
|
||||||
|
end)
|
||||||
|
|> case do
|
||||||
|
[] ->
|
||||||
|
User.update_follower_count(origin)
|
||||||
|
:ok
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
move_following(origin, target)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,23 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.HTML do
|
defmodule Pleroma.HTML do
|
||||||
|
# Scrubbers are compiled on boot so they can be configured in OTP releases
|
||||||
|
# @on_load :compile_scrubbers
|
||||||
|
|
||||||
|
def compile_scrubbers do
|
||||||
|
dir = Path.join(:code.priv_dir(:pleroma), "scrubbers")
|
||||||
|
|
||||||
|
dir
|
||||||
|
|> Pleroma.Utils.compile_dir()
|
||||||
|
|> case do
|
||||||
|
{:error, _errors, _warnings} ->
|
||||||
|
raise "Compiling scrubbers failed"
|
||||||
|
|
||||||
|
{:ok, _modules, _warnings} ->
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp get_scrubbers(scrubber) when is_atom(scrubber), do: [scrubber]
|
defp get_scrubbers(scrubber) when is_atom(scrubber), do: [scrubber]
|
||||||
defp get_scrubbers(scrubbers) when is_list(scrubbers), do: scrubbers
|
defp get_scrubbers(scrubbers) when is_list(scrubbers), do: scrubbers
|
||||||
defp get_scrubbers(_), do: [Pleroma.HTML.Scrubber.Default]
|
defp get_scrubbers(_), do: [Pleroma.HTML.Scrubber.Default]
|
||||||
|
|
@ -99,215 +116,3 @@ defmodule Pleroma.HTML do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defmodule Pleroma.HTML.Scrubber.TwitterText do
|
|
||||||
@moduledoc """
|
|
||||||
An HTML scrubbing policy which limits to twitter-style text. Only
|
|
||||||
paragraphs, breaks and links are allowed through the filter.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
|
|
||||||
|
|
||||||
require FastSanitize.Sanitizer.Meta
|
|
||||||
alias FastSanitize.Sanitizer.Meta
|
|
||||||
|
|
||||||
Meta.strip_comments()
|
|
||||||
|
|
||||||
# links
|
|
||||||
Meta.allow_tag_with_uri_attributes(:a, ["href", "data-user", "data-tag"], @valid_schemes)
|
|
||||||
|
|
||||||
Meta.allow_tag_with_this_attribute_values(:a, "class", [
|
|
||||||
"hashtag",
|
|
||||||
"u-url",
|
|
||||||
"mention",
|
|
||||||
"u-url mention",
|
|
||||||
"mention u-url"
|
|
||||||
])
|
|
||||||
|
|
||||||
Meta.allow_tag_with_this_attribute_values(:a, "rel", [
|
|
||||||
"tag",
|
|
||||||
"nofollow",
|
|
||||||
"noopener",
|
|
||||||
"noreferrer"
|
|
||||||
])
|
|
||||||
|
|
||||||
Meta.allow_tag_with_these_attributes(:a, ["name", "title"])
|
|
||||||
|
|
||||||
# paragraphs and linebreaks
|
|
||||||
Meta.allow_tag_with_these_attributes(:br, [])
|
|
||||||
Meta.allow_tag_with_these_attributes(:p, [])
|
|
||||||
|
|
||||||
# microformats
|
|
||||||
Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card"])
|
|
||||||
Meta.allow_tag_with_these_attributes(:span, [])
|
|
||||||
|
|
||||||
# allow inline images for custom emoji
|
|
||||||
if Pleroma.Config.get([:markup, :allow_inline_images]) do
|
|
||||||
# restrict img tags to http/https only, because of MediaProxy.
|
|
||||||
Meta.allow_tag_with_uri_attributes(:img, ["src"], ["http", "https"])
|
|
||||||
|
|
||||||
Meta.allow_tag_with_these_attributes(:img, [
|
|
||||||
"width",
|
|
||||||
"height",
|
|
||||||
"class",
|
|
||||||
"title",
|
|
||||||
"alt"
|
|
||||||
])
|
|
||||||
end
|
|
||||||
|
|
||||||
Meta.strip_everything_not_covered()
|
|
||||||
end
|
|
||||||
|
|
||||||
defmodule Pleroma.HTML.Scrubber.Default do
|
|
||||||
@doc "The default HTML scrubbing policy: no "
|
|
||||||
|
|
||||||
require FastSanitize.Sanitizer.Meta
|
|
||||||
alias FastSanitize.Sanitizer.Meta
|
|
||||||
# credo:disable-for-previous-line
|
|
||||||
# No idea how to fix this one…
|
|
||||||
|
|
||||||
@valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
|
|
||||||
|
|
||||||
Meta.strip_comments()
|
|
||||||
|
|
||||||
Meta.allow_tag_with_uri_attributes(:a, ["href", "data-user", "data-tag"], @valid_schemes)
|
|
||||||
|
|
||||||
Meta.allow_tag_with_this_attribute_values(:a, "class", [
|
|
||||||
"hashtag",
|
|
||||||
"u-url",
|
|
||||||
"mention",
|
|
||||||
"u-url mention",
|
|
||||||
"mention u-url"
|
|
||||||
])
|
|
||||||
|
|
||||||
Meta.allow_tag_with_this_attribute_values(:a, "rel", [
|
|
||||||
"tag",
|
|
||||||
"nofollow",
|
|
||||||
"noopener",
|
|
||||||
"noreferrer",
|
|
||||||
"ugc"
|
|
||||||
])
|
|
||||||
|
|
||||||
Meta.allow_tag_with_these_attributes(:a, ["name", "title"])
|
|
||||||
|
|
||||||
Meta.allow_tag_with_these_attributes(:abbr, ["title"])
|
|
||||||
|
|
||||||
Meta.allow_tag_with_these_attributes(:b, [])
|
|
||||||
Meta.allow_tag_with_these_attributes(:blockquote, [])
|
|
||||||
Meta.allow_tag_with_these_attributes(:br, [])
|
|
||||||
Meta.allow_tag_with_these_attributes(:code, [])
|
|
||||||
Meta.allow_tag_with_these_attributes(:del, [])
|
|
||||||
Meta.allow_tag_with_these_attributes(:em, [])
|
|
||||||
Meta.allow_tag_with_these_attributes(:i, [])
|
|
||||||
Meta.allow_tag_with_these_attributes(:li, [])
|
|
||||||
Meta.allow_tag_with_these_attributes(:ol, [])
|
|
||||||
Meta.allow_tag_with_these_attributes(:p, [])
|
|
||||||
Meta.allow_tag_with_these_attributes(:pre, [])
|
|
||||||
Meta.allow_tag_with_these_attributes(:strong, [])
|
|
||||||
Meta.allow_tag_with_these_attributes(:sub, [])
|
|
||||||
Meta.allow_tag_with_these_attributes(:sup, [])
|
|
||||||
Meta.allow_tag_with_these_attributes(:u, [])
|
|
||||||
Meta.allow_tag_with_these_attributes(:ul, [])
|
|
||||||
|
|
||||||
Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card"])
|
|
||||||
Meta.allow_tag_with_these_attributes(:span, [])
|
|
||||||
|
|
||||||
@allow_inline_images Pleroma.Config.get([:markup, :allow_inline_images])
|
|
||||||
|
|
||||||
if @allow_inline_images do
|
|
||||||
# restrict img tags to http/https only, because of MediaProxy.
|
|
||||||
Meta.allow_tag_with_uri_attributes(:img, ["src"], ["http", "https"])
|
|
||||||
|
|
||||||
Meta.allow_tag_with_these_attributes(:img, [
|
|
||||||
"width",
|
|
||||||
"height",
|
|
||||||
"class",
|
|
||||||
"title",
|
|
||||||
"alt"
|
|
||||||
])
|
|
||||||
end
|
|
||||||
|
|
||||||
if Pleroma.Config.get([:markup, :allow_tables]) do
|
|
||||||
Meta.allow_tag_with_these_attributes(:table, [])
|
|
||||||
Meta.allow_tag_with_these_attributes(:tbody, [])
|
|
||||||
Meta.allow_tag_with_these_attributes(:td, [])
|
|
||||||
Meta.allow_tag_with_these_attributes(:th, [])
|
|
||||||
Meta.allow_tag_with_these_attributes(:thead, [])
|
|
||||||
Meta.allow_tag_with_these_attributes(:tr, [])
|
|
||||||
end
|
|
||||||
|
|
||||||
if Pleroma.Config.get([:markup, :allow_headings]) do
|
|
||||||
Meta.allow_tag_with_these_attributes(:h1, [])
|
|
||||||
Meta.allow_tag_with_these_attributes(:h2, [])
|
|
||||||
Meta.allow_tag_with_these_attributes(:h3, [])
|
|
||||||
Meta.allow_tag_with_these_attributes(:h4, [])
|
|
||||||
Meta.allow_tag_with_these_attributes(:h5, [])
|
|
||||||
end
|
|
||||||
|
|
||||||
if Pleroma.Config.get([:markup, :allow_fonts]) do
|
|
||||||
Meta.allow_tag_with_these_attributes(:font, ["face"])
|
|
||||||
end
|
|
||||||
|
|
||||||
Meta.strip_everything_not_covered()
|
|
||||||
end
|
|
||||||
|
|
||||||
defmodule Pleroma.HTML.Transform.MediaProxy do
|
|
||||||
@moduledoc "Transforms inline image URIs to use MediaProxy."
|
|
||||||
|
|
||||||
alias Pleroma.Web.MediaProxy
|
|
||||||
|
|
||||||
def before_scrub(html), do: html
|
|
||||||
|
|
||||||
def scrub_attribute(:img, {"src", "http" <> target}) do
|
|
||||||
media_url =
|
|
||||||
("http" <> target)
|
|
||||||
|> MediaProxy.url()
|
|
||||||
|
|
||||||
{"src", media_url}
|
|
||||||
end
|
|
||||||
|
|
||||||
def scrub_attribute(_tag, attribute), do: attribute
|
|
||||||
|
|
||||||
def scrub({:img, attributes, children}) do
|
|
||||||
attributes =
|
|
||||||
attributes
|
|
||||||
|> Enum.map(fn attr -> scrub_attribute(:img, attr) end)
|
|
||||||
|> Enum.reject(&is_nil(&1))
|
|
||||||
|
|
||||||
{:img, attributes, children}
|
|
||||||
end
|
|
||||||
|
|
||||||
def scrub({:comment, _text, _children}), do: ""
|
|
||||||
|
|
||||||
def scrub({tag, attributes, children}), do: {tag, attributes, children}
|
|
||||||
def scrub({_tag, children}), do: children
|
|
||||||
def scrub(text), do: text
|
|
||||||
end
|
|
||||||
|
|
||||||
defmodule Pleroma.HTML.Scrubber.LinksOnly do
|
|
||||||
@moduledoc """
|
|
||||||
An HTML scrubbing policy which limits to links only.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
|
|
||||||
|
|
||||||
require FastSanitize.Sanitizer.Meta
|
|
||||||
alias FastSanitize.Sanitizer.Meta
|
|
||||||
|
|
||||||
Meta.strip_comments()
|
|
||||||
|
|
||||||
# links
|
|
||||||
Meta.allow_tag_with_uri_attributes(:a, ["href"], @valid_schemes)
|
|
||||||
|
|
||||||
Meta.allow_tag_with_this_attribute_values(:a, "rel", [
|
|
||||||
"tag",
|
|
||||||
"nofollow",
|
|
||||||
"noopener",
|
|
||||||
"noreferrer",
|
|
||||||
"me",
|
|
||||||
"ugc"
|
|
||||||
])
|
|
||||||
|
|
||||||
Meta.allow_tag_with_these_attributes(:a, ["name", "title"])
|
|
||||||
Meta.strip_everything_not_covered()
|
|
||||||
end
|
|
||||||
|
|
|
||||||
|
|
@ -128,17 +128,35 @@ defmodule Pleroma.ModerationLog do
|
||||||
{:ok, ModerationLog} | {:error, any}
|
{:ok, ModerationLog} | {:error, any}
|
||||||
def insert_log(%{
|
def insert_log(%{
|
||||||
actor: %User{} = actor,
|
actor: %User{} = actor,
|
||||||
action: "report_response",
|
action: "report_note",
|
||||||
subject: %Activity{} = subject,
|
subject: %Activity{} = subject,
|
||||||
text: text
|
text: text
|
||||||
}) do
|
}) do
|
||||||
%ModerationLog{
|
%ModerationLog{
|
||||||
data: %{
|
data: %{
|
||||||
"actor" => user_to_map(actor),
|
"actor" => user_to_map(actor),
|
||||||
"action" => "report_response",
|
"action" => "report_note",
|
||||||
"subject" => report_to_map(subject),
|
"subject" => report_to_map(subject),
|
||||||
"text" => text,
|
"text" => text
|
||||||
"message" => ""
|
}
|
||||||
|
}
|
||||||
|
|> insert_log_entry_with_message()
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec insert_log(%{actor: User, subject: Activity, action: String.t(), text: String.t()}) ::
|
||||||
|
{:ok, ModerationLog} | {:error, any}
|
||||||
|
def insert_log(%{
|
||||||
|
actor: %User{} = actor,
|
||||||
|
action: "report_note_delete",
|
||||||
|
subject: %Activity{} = subject,
|
||||||
|
text: text
|
||||||
|
}) do
|
||||||
|
%ModerationLog{
|
||||||
|
data: %{
|
||||||
|
"actor" => user_to_map(actor),
|
||||||
|
"action" => "report_note_delete",
|
||||||
|
"subject" => report_to_map(subject),
|
||||||
|
"text" => text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> insert_log_entry_with_message()
|
|> insert_log_entry_with_message()
|
||||||
|
|
@ -556,12 +574,24 @@ defmodule Pleroma.ModerationLog do
|
||||||
def get_log_entry_message(%ModerationLog{
|
def get_log_entry_message(%ModerationLog{
|
||||||
data: %{
|
data: %{
|
||||||
"actor" => %{"nickname" => actor_nickname},
|
"actor" => %{"nickname" => actor_nickname},
|
||||||
"action" => "report_response",
|
"action" => "report_note",
|
||||||
"subject" => %{"id" => subject_id, "type" => "report"},
|
"subject" => %{"id" => subject_id, "type" => "report"},
|
||||||
"text" => text
|
"text" => text
|
||||||
}
|
}
|
||||||
}) do
|
}) do
|
||||||
"@#{actor_nickname} responded with '#{text}' to report ##{subject_id}"
|
"@#{actor_nickname} added note '#{text}' to report ##{subject_id}"
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||||
|
def get_log_entry_message(%ModerationLog{
|
||||||
|
data: %{
|
||||||
|
"actor" => %{"nickname" => actor_nickname},
|
||||||
|
"action" => "report_note_delete",
|
||||||
|
"subject" => %{"id" => subject_id, "type" => "report"},
|
||||||
|
"text" => text
|
||||||
|
}
|
||||||
|
}) do
|
||||||
|
"@#{actor_nickname} deleted note '#{text}' from report ##{subject_id}"
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec get_log_entry_message(ModerationLog) :: String.t()
|
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||||
|
|
@ -616,6 +646,41 @@ defmodule Pleroma.ModerationLog do
|
||||||
"@#{actor_nickname} deleted status ##{subject_id}"
|
"@#{actor_nickname} deleted status ##{subject_id}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||||
|
def get_log_entry_message(%ModerationLog{
|
||||||
|
data: %{
|
||||||
|
"actor" => %{"nickname" => actor_nickname},
|
||||||
|
"action" => "force_password_reset",
|
||||||
|
"subject" => subjects
|
||||||
|
}
|
||||||
|
}) do
|
||||||
|
"@#{actor_nickname} forced password reset for users: #{users_to_nicknames_string(subjects)}"
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||||
|
def get_log_entry_message(%ModerationLog{
|
||||||
|
data: %{
|
||||||
|
"actor" => %{"nickname" => actor_nickname},
|
||||||
|
"action" => "confirm_email",
|
||||||
|
"subject" => subjects
|
||||||
|
}
|
||||||
|
}) do
|
||||||
|
"@#{actor_nickname} confirmed email for users: #{users_to_nicknames_string(subjects)}"
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||||
|
def get_log_entry_message(%ModerationLog{
|
||||||
|
data: %{
|
||||||
|
"actor" => %{"nickname" => actor_nickname},
|
||||||
|
"action" => "resend_confirmation_email",
|
||||||
|
"subject" => subjects
|
||||||
|
}
|
||||||
|
}) do
|
||||||
|
"@#{actor_nickname} re-sent confirmation email for users: #{
|
||||||
|
users_to_nicknames_string(subjects)
|
||||||
|
}"
|
||||||
|
end
|
||||||
|
|
||||||
defp nicknames_to_string(nicknames) do
|
defp nicknames_to_string(nicknames) do
|
||||||
nicknames
|
nicknames
|
||||||
|> Enum.map(&"@#{&1}")
|
|> Enum.map(&"@#{&1}")
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ defmodule Pleroma.Notification do
|
||||||
|
|
||||||
@type t :: %__MODULE__{}
|
@type t :: %__MODULE__{}
|
||||||
|
|
||||||
|
@include_muted_option :with_muted
|
||||||
|
|
||||||
schema "notifications" do
|
schema "notifications" do
|
||||||
field(:seen, :boolean, default: false)
|
field(:seen, :boolean, default: false)
|
||||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||||
|
|
@ -34,7 +36,25 @@ defmodule Pleroma.Notification do
|
||||||
|> cast(attrs, [:seen])
|
|> cast(attrs, [:seen])
|
||||||
end
|
end
|
||||||
|
|
||||||
def for_user_query(user, opts \\ []) do
|
defp for_user_query_ap_id_opts(user, opts) do
|
||||||
|
ap_id_relations =
|
||||||
|
[:block] ++
|
||||||
|
if opts[@include_muted_option], do: [], else: [:notification_mute]
|
||||||
|
|
||||||
|
preloaded_ap_ids = User.outgoing_relations_ap_ids(user, ap_id_relations)
|
||||||
|
|
||||||
|
exclude_blocked_opts = Map.merge(%{blocked_users_ap_ids: preloaded_ap_ids[:block]}, opts)
|
||||||
|
|
||||||
|
exclude_notification_muted_opts =
|
||||||
|
Map.merge(%{notification_muted_users_ap_ids: preloaded_ap_ids[:notification_mute]}, opts)
|
||||||
|
|
||||||
|
{exclude_blocked_opts, exclude_notification_muted_opts}
|
||||||
|
end
|
||||||
|
|
||||||
|
def for_user_query(user, opts \\ %{}) do
|
||||||
|
{exclude_blocked_opts, exclude_notification_muted_opts} =
|
||||||
|
for_user_query_ap_id_opts(user, opts)
|
||||||
|
|
||||||
Notification
|
Notification
|
||||||
|> where(user_id: ^user.id)
|
|> where(user_id: ^user.id)
|
||||||
|> where(
|
|> where(
|
||||||
|
|
@ -54,43 +74,75 @@ defmodule Pleroma.Notification do
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|> preload([n, a, o], activity: {a, object: o})
|
|> preload([n, a, o], activity: {a, object: o})
|
||||||
|> exclude_muted(user, opts)
|
|> exclude_notification_muted(user, exclude_notification_muted_opts)
|
||||||
|> exclude_blocked(user)
|
|> exclude_blocked(user, exclude_blocked_opts)
|
||||||
|> exclude_visibility(opts)
|
|> exclude_visibility(opts)
|
||||||
|
|> exclude_move(opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp exclude_blocked(query, user) do
|
defp exclude_blocked(query, user, opts) do
|
||||||
|
blocked_ap_ids = opts[:blocked_users_ap_ids] || User.blocked_users_ap_ids(user)
|
||||||
|
|
||||||
query
|
query
|
||||||
|> where([n, a], a.actor not in ^user.blocks)
|
|> where([n, a], a.actor not in ^blocked_ap_ids)
|
||||||
|> where(
|
|> where(
|
||||||
[n, a],
|
[n, a],
|
||||||
fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.domain_blocks
|
fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.domain_blocks
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp exclude_muted(query, _, %{with_muted: true}) do
|
defp exclude_notification_muted(query, _, %{@include_muted_option => true}) do
|
||||||
query
|
query
|
||||||
end
|
end
|
||||||
|
|
||||||
defp exclude_muted(query, user, _opts) do
|
defp exclude_notification_muted(query, user, opts) do
|
||||||
|
notification_muted_ap_ids =
|
||||||
|
opts[:notification_muted_users_ap_ids] || User.notification_muted_users_ap_ids(user)
|
||||||
|
|
||||||
query
|
query
|
||||||
|> where([n, a], a.actor not in ^user.muted_notifications)
|
|> where([n, a], a.actor not in ^notification_muted_ap_ids)
|
||||||
|> join(:left, [n, a], tm in Pleroma.ThreadMute,
|
|> join(:left, [n, a], tm in Pleroma.ThreadMute,
|
||||||
on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data)
|
on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data)
|
||||||
)
|
)
|
||||||
|> where([n, a, o, tm], is_nil(tm.user_id))
|
|> where([n, a, o, tm], is_nil(tm.user_id))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp exclude_move(query, %{with_move: true}) do
|
||||||
|
query
|
||||||
|
end
|
||||||
|
|
||||||
|
defp exclude_move(query, _opts) do
|
||||||
|
where(query, [n, a], fragment("?->>'type' != 'Move'", a.data))
|
||||||
|
end
|
||||||
|
|
||||||
@valid_visibilities ~w[direct unlisted public private]
|
@valid_visibilities ~w[direct unlisted public private]
|
||||||
|
|
||||||
defp exclude_visibility(query, %{exclude_visibilities: visibility})
|
defp exclude_visibility(query, %{exclude_visibilities: visibility})
|
||||||
when is_list(visibility) do
|
when is_list(visibility) do
|
||||||
if Enum.all?(visibility, &(&1 in @valid_visibilities)) do
|
if Enum.all?(visibility, &(&1 in @valid_visibilities)) do
|
||||||
query
|
query
|
||||||
|
|> join(:left, [n, a], mutated_activity in Pleroma.Activity,
|
||||||
|
on:
|
||||||
|
fragment("?->>'context'", a.data) ==
|
||||||
|
fragment("?->>'context'", mutated_activity.data) and
|
||||||
|
fragment("(?->>'type' = 'Like' or ?->>'type' = 'Announce')", a.data, a.data) and
|
||||||
|
fragment("?->>'type'", mutated_activity.data) == "Create",
|
||||||
|
as: :mutated_activity
|
||||||
|
)
|
||||||
|> where(
|
|> where(
|
||||||
[n, a],
|
[n, a, mutated_activity: mutated_activity],
|
||||||
not fragment(
|
not fragment(
|
||||||
"activity_visibility(?, ?, ?) = ANY (?)",
|
"""
|
||||||
|
CASE WHEN (?->>'type') = 'Like' or (?->>'type') = 'Announce'
|
||||||
|
THEN (activity_visibility(?, ?, ?) = ANY (?))
|
||||||
|
ELSE (activity_visibility(?, ?, ?) = ANY (?)) END
|
||||||
|
""",
|
||||||
|
a.data,
|
||||||
|
a.data,
|
||||||
|
mutated_activity.actor,
|
||||||
|
mutated_activity.recipients,
|
||||||
|
mutated_activity.data,
|
||||||
|
^visibility,
|
||||||
a.actor,
|
a.actor,
|
||||||
a.recipients,
|
a.recipients,
|
||||||
a.data,
|
a.data,
|
||||||
|
|
@ -105,17 +157,7 @@ defmodule Pleroma.Notification do
|
||||||
|
|
||||||
defp exclude_visibility(query, %{exclude_visibilities: visibility})
|
defp exclude_visibility(query, %{exclude_visibilities: visibility})
|
||||||
when visibility in @valid_visibilities do
|
when visibility in @valid_visibilities do
|
||||||
query
|
exclude_visibility(query, [visibility])
|
||||||
|> where(
|
|
||||||
[n, a],
|
|
||||||
not fragment(
|
|
||||||
"activity_visibility(?, ?, ?) = (?)",
|
|
||||||
a.actor,
|
|
||||||
a.recipients,
|
|
||||||
a.data,
|
|
||||||
^visibility
|
|
||||||
)
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp exclude_visibility(query, %{exclude_visibilities: visibility})
|
defp exclude_visibility(query, %{exclude_visibilities: visibility})
|
||||||
|
|
@ -251,10 +293,13 @@ defmodule Pleroma.Notification do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity)
|
def create_notifications(%Activity{data: %{"type" => type}} = activity)
|
||||||
when type in ["Like", "Announce", "Follow"] do
|
when type in ["Like", "Announce", "Follow", "Move", "EmojiReaction"] do
|
||||||
users = get_notified_from_activity(activity)
|
notifications =
|
||||||
notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
|
activity
|
||||||
|
|> get_notified_from_activity()
|
||||||
|
|> Enum.map(&create_notification(activity, &1))
|
||||||
|
|
||||||
{:ok, notifications}
|
{:ok, notifications}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -276,19 +321,15 @@ defmodule Pleroma.Notification do
|
||||||
|
|
||||||
def get_notified_from_activity(activity, local_only \\ true)
|
def get_notified_from_activity(activity, local_only \\ true)
|
||||||
|
|
||||||
def get_notified_from_activity(
|
def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, local_only)
|
||||||
%Activity{data: %{"to" => _, "type" => type} = _data} = activity,
|
when type in ["Create", "Like", "Announce", "Follow", "Move", "EmojiReaction"] do
|
||||||
local_only
|
|
||||||
)
|
|
||||||
when type in ["Create", "Like", "Announce", "Follow"] do
|
|
||||||
recipients =
|
|
||||||
[]
|
[]
|
||||||
|> Utils.maybe_notify_to_recipients(activity)
|
|> Utils.maybe_notify_to_recipients(activity)
|
||||||
|> Utils.maybe_notify_mentioned_recipients(activity)
|
|> Utils.maybe_notify_mentioned_recipients(activity)
|
||||||
|> Utils.maybe_notify_subscribers(activity)
|
|> Utils.maybe_notify_subscribers(activity)
|
||||||
|
|> Utils.maybe_notify_followers(activity)
|
||||||
|> Enum.uniq()
|
|> Enum.uniq()
|
||||||
|
|> User.get_users_from_set(local_only)
|
||||||
User.get_users_from_set(recipients, local_only)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_notified_from_activity(_, _local_only), do: []
|
def get_notified_from_activity(_, _local_only), do: []
|
||||||
|
|
@ -314,7 +355,7 @@ defmodule Pleroma.Notification do
|
||||||
def skip?(
|
def skip?(
|
||||||
:followers,
|
:followers,
|
||||||
activity,
|
activity,
|
||||||
%{notification_settings: %{"followers" => false}} = user
|
%{notification_settings: %{followers: false}} = user
|
||||||
) do
|
) do
|
||||||
actor = activity.data["actor"]
|
actor = activity.data["actor"]
|
||||||
follower = User.get_cached_by_ap_id(actor)
|
follower = User.get_cached_by_ap_id(actor)
|
||||||
|
|
@ -324,14 +365,14 @@ defmodule Pleroma.Notification do
|
||||||
def skip?(
|
def skip?(
|
||||||
:non_followers,
|
:non_followers,
|
||||||
activity,
|
activity,
|
||||||
%{notification_settings: %{"non_followers" => false}} = user
|
%{notification_settings: %{non_followers: false}} = user
|
||||||
) do
|
) do
|
||||||
actor = activity.data["actor"]
|
actor = activity.data["actor"]
|
||||||
follower = User.get_cached_by_ap_id(actor)
|
follower = User.get_cached_by_ap_id(actor)
|
||||||
!User.following?(follower, user)
|
!User.following?(follower, user)
|
||||||
end
|
end
|
||||||
|
|
||||||
def skip?(:follows, activity, %{notification_settings: %{"follows" => false}} = user) do
|
def skip?(:follows, activity, %{notification_settings: %{follows: false}} = user) do
|
||||||
actor = activity.data["actor"]
|
actor = activity.data["actor"]
|
||||||
followed = User.get_cached_by_ap_id(actor)
|
followed = User.get_cached_by_ap_id(actor)
|
||||||
User.following?(user, followed)
|
User.following?(user, followed)
|
||||||
|
|
@ -340,7 +381,7 @@ defmodule Pleroma.Notification do
|
||||||
def skip?(
|
def skip?(
|
||||||
:non_follows,
|
:non_follows,
|
||||||
activity,
|
activity,
|
||||||
%{notification_settings: %{"non_follows" => false}} = user
|
%{notification_settings: %{non_follows: false}} = user
|
||||||
) do
|
) do
|
||||||
actor = activity.data["actor"]
|
actor = activity.data["actor"]
|
||||||
followed = User.get_cached_by_ap_id(actor)
|
followed = User.get_cached_by_ap_id(actor)
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,33 @@ defmodule Pleroma.Object do
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
@type t() :: %__MODULE__{}
|
||||||
|
|
||||||
|
@derive {Jason.Encoder, only: [:data]}
|
||||||
|
|
||||||
schema "objects" do
|
schema "objects" do
|
||||||
field(:data, :map)
|
field(:data, :map)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def with_joined_activity(query, activity_type \\ "Create", join_type \\ :inner) do
|
||||||
|
object_position = Map.get(query.aliases, :object, 0)
|
||||||
|
|
||||||
|
join(query, join_type, [{object, object_position}], a in Activity,
|
||||||
|
on:
|
||||||
|
fragment(
|
||||||
|
"COALESCE(?->'object'->>'id', ?->>'object') = (? ->> 'id') AND (?->>'type' = ?) ",
|
||||||
|
a.data,
|
||||||
|
a.data,
|
||||||
|
object.data,
|
||||||
|
a.data,
|
||||||
|
^activity_type
|
||||||
|
),
|
||||||
|
as: :object_activity
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def create(data) do
|
def create(data) do
|
||||||
Object.change(%Object{}, %{data: data})
|
Object.change(%Object{}, %{data: data})
|
||||||
|> Repo.insert()
|
|> Repo.insert()
|
||||||
|
|
@ -62,8 +83,22 @@ defmodule Pleroma.Object do
|
||||||
Repo.one(from(object in Object, where: fragment("(?)->>'id' = ?", object.data, ^ap_id)))
|
Repo.one(from(object in Object, where: fragment("(?)->>'id' = ?", object.data, ^ap_id)))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Get a single attachment by it's name and href
|
||||||
|
"""
|
||||||
|
@spec get_attachment_by_name_and_href(String.t(), String.t()) :: Object.t() | nil
|
||||||
|
def get_attachment_by_name_and_href(name, href) do
|
||||||
|
query =
|
||||||
|
from(o in Object,
|
||||||
|
where: fragment("(?)->>'name' = ?", o.data, ^name),
|
||||||
|
where: fragment("(?)->>'href' = ?", o.data, ^href)
|
||||||
|
)
|
||||||
|
|
||||||
|
Repo.one(query)
|
||||||
|
end
|
||||||
|
|
||||||
defp warn_on_no_object_preloaded(ap_id) do
|
defp warn_on_no_object_preloaded(ap_id) do
|
||||||
"Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object"
|
"Object.normalize() called without preloaded object (#{inspect(ap_id)}). Consider preloading the object"
|
||||||
|> Logger.debug()
|
|> Logger.debug()
|
||||||
|
|
||||||
Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}")
|
Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}")
|
||||||
|
|
@ -147,9 +182,13 @@ defmodule Pleroma.Object do
|
||||||
|
|
||||||
def delete(%Object{data: %{"id" => id}} = object) do
|
def delete(%Object{data: %{"id" => id}} = object) do
|
||||||
with {:ok, _obj} = swap_object_with_tombstone(object),
|
with {:ok, _obj} = swap_object_with_tombstone(object),
|
||||||
deleted_activity = Activity.delete_by_ap_id(id),
|
deleted_activity = Activity.delete_all_by_object_ap_id(id),
|
||||||
{:ok, true} <- Cachex.del(:object_cache, "object:#{id}"),
|
{:ok, true} <- Cachex.del(:object_cache, "object:#{id}"),
|
||||||
{:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path) do
|
{:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path),
|
||||||
|
{:ok, _} <-
|
||||||
|
Pleroma.Workers.AttachmentsCleanupWorker.enqueue("cleanup_attachments", %{
|
||||||
|
"object" => object
|
||||||
|
}) do
|
||||||
{:ok, object, deleted_activity}
|
{:ok, object, deleted_activity}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -255,4 +294,8 @@ defmodule Pleroma.Object do
|
||||||
|> Object.change(%{data: Map.merge(data || %{}, attrs)})
|
|> Object.change(%{data: Map.merge(data || %{}, attrs)})
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def local?(%Object{data: %{"id" => id}}) do
|
||||||
|
String.starts_with?(id, Pleroma.Web.base_url() <> "/")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -64,15 +64,17 @@ defmodule Pleroma.Object.Containment do
|
||||||
def contain_origin(id, %{"attributedTo" => actor} = params),
|
def contain_origin(id, %{"attributedTo" => actor} = params),
|
||||||
do: contain_origin(id, Map.put(params, "actor", actor))
|
do: contain_origin(id, Map.put(params, "actor", actor))
|
||||||
|
|
||||||
def contain_origin_from_id(_id, %{"id" => nil}), do: :error
|
def contain_origin(_id, _data), do: :error
|
||||||
|
|
||||||
def contain_origin_from_id(id, %{"id" => other_id} = _params) do
|
def contain_origin_from_id(id, %{"id" => other_id} = _params) when is_binary(other_id) do
|
||||||
id_uri = URI.parse(id)
|
id_uri = URI.parse(id)
|
||||||
other_uri = URI.parse(other_id)
|
other_uri = URI.parse(other_id)
|
||||||
|
|
||||||
compare_uris(id_uri, other_uri)
|
compare_uris(id_uri, other_uri)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def contain_origin_from_id(_id, _data), do: :error
|
||||||
|
|
||||||
def contain_child(%{"object" => %{"id" => id, "attributedTo" => _} = object}),
|
def contain_child(%{"object" => %{"id" => id, "attributedTo" => _} = object}),
|
||||||
do: contain_origin(id, object)
|
do: contain_origin(id, object)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,8 @@ defmodule Pleroma.Object.Fetcher do
|
||||||
data <- maybe_reinject_internal_fields(data, struct),
|
data <- maybe_reinject_internal_fields(data, struct),
|
||||||
changeset <- Object.change(struct, %{data: data}),
|
changeset <- Object.change(struct, %{data: data}),
|
||||||
changeset <- touch_changeset(changeset),
|
changeset <- touch_changeset(changeset),
|
||||||
{:ok, object} <- Repo.insert_or_update(changeset) do
|
{:ok, object} <- Repo.insert_or_update(changeset),
|
||||||
|
{:ok, object} <- Object.set_cache(object) do
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
else
|
else
|
||||||
e ->
|
e ->
|
||||||
|
|
@ -48,12 +49,12 @@ defmodule Pleroma.Object.Fetcher do
|
||||||
end
|
end
|
||||||
|
|
||||||
def refetch_object(%Object{data: %{"id" => id}} = object) do
|
def refetch_object(%Object{data: %{"id" => id}} = object) do
|
||||||
with {:local, false} <- {:local, String.starts_with?(id, Pleroma.Web.base_url() <> "/")},
|
with {:local, false} <- {:local, Object.local?(object)},
|
||||||
{:ok, data} <- fetch_and_contain_remote_object_from_id(id),
|
{:ok, data} <- fetch_and_contain_remote_object_from_id(id),
|
||||||
{:ok, object} <- reinject_object(object, data) do
|
{:ok, object} <- reinject_object(object, data) do
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
else
|
else
|
||||||
{:local, true} -> object
|
{:local, true} -> {:ok, object}
|
||||||
e -> {:error, e}
|
e -> {:error, e}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -116,6 +117,9 @@ defmodule Pleroma.Object.Fetcher do
|
||||||
{:error, %Tesla.Mock.Error{}} ->
|
{:error, %Tesla.Mock.Error{}} ->
|
||||||
nil
|
nil
|
||||||
|
|
||||||
|
{:error, "Object has been deleted"} ->
|
||||||
|
nil
|
||||||
|
|
||||||
e ->
|
e ->
|
||||||
Logger.error("Error while fetching #{id}: #{inspect(e)}")
|
Logger.error("Error while fetching #{id}: #{inspect(e)}")
|
||||||
nil
|
nil
|
||||||
|
|
@ -153,7 +157,7 @@ defmodule Pleroma.Object.Fetcher do
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
|
def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
|
||||||
Logger.info("Fetching object #{id} via AP")
|
Logger.debug("Fetching object #{id} via AP")
|
||||||
|
|
||||||
date = Pleroma.Signature.signed_date()
|
date = Pleroma.Signature.signed_date()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,60 +13,66 @@ defmodule Pleroma.Pagination do
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
|
||||||
@default_limit 20
|
@default_limit 20
|
||||||
|
@page_keys ["max_id", "min_id", "limit", "since_id", "order"]
|
||||||
|
|
||||||
def fetch_paginated(query, params, type \\ :keyset)
|
def page_keys, do: @page_keys
|
||||||
|
|
||||||
def fetch_paginated(query, %{"total" => true} = params, :keyset) do
|
def fetch_paginated(query, params, type \\ :keyset, table_binding \\ nil)
|
||||||
|
|
||||||
|
def fetch_paginated(query, %{"total" => true} = params, :keyset, table_binding) do
|
||||||
total = Repo.aggregate(query, :count, :id)
|
total = Repo.aggregate(query, :count, :id)
|
||||||
|
|
||||||
%{
|
%{
|
||||||
total: total,
|
total: total,
|
||||||
items: fetch_paginated(query, Map.drop(params, ["total"]), :keyset)
|
items: fetch_paginated(query, Map.drop(params, ["total"]), :keyset, table_binding)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_paginated(query, params, :keyset) do
|
def fetch_paginated(query, params, :keyset, table_binding) do
|
||||||
options = cast_params(params)
|
options = cast_params(params)
|
||||||
|
|
||||||
query
|
query
|
||||||
|> paginate(options, :keyset)
|
|> paginate(options, :keyset, table_binding)
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|> enforce_order(options)
|
|> enforce_order(options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_paginated(query, %{"total" => true} = params, :offset) do
|
def fetch_paginated(query, %{"total" => true} = params, :offset, table_binding) do
|
||||||
total = Repo.aggregate(query, :count, :id)
|
total =
|
||||||
|
query
|
||||||
|
|> Ecto.Query.exclude(:left_join)
|
||||||
|
|> Repo.aggregate(:count, :id)
|
||||||
|
|
||||||
%{
|
%{
|
||||||
total: total,
|
total: total,
|
||||||
items: fetch_paginated(query, Map.drop(params, ["total"]), :offset)
|
items: fetch_paginated(query, Map.drop(params, ["total"]), :offset, table_binding)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_paginated(query, params, :offset) do
|
def fetch_paginated(query, params, :offset, table_binding) do
|
||||||
options = cast_params(params)
|
options = cast_params(params)
|
||||||
|
|
||||||
query
|
query
|
||||||
|> paginate(options, :offset)
|
|> paginate(options, :offset, table_binding)
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
def paginate(query, options, method \\ :keyset)
|
def paginate(query, options, method \\ :keyset, table_binding \\ nil)
|
||||||
|
|
||||||
def paginate(query, options, :keyset) do
|
def paginate(query, options, :keyset, table_binding) do
|
||||||
query
|
query
|
||||||
|> restrict(:min_id, options)
|
|> restrict(:min_id, options, table_binding)
|
||||||
|> restrict(:since_id, options)
|
|> restrict(:since_id, options, table_binding)
|
||||||
|> restrict(:max_id, options)
|
|> restrict(:max_id, options, table_binding)
|
||||||
|> restrict(:order, options)
|
|> restrict(:order, options, table_binding)
|
||||||
|> restrict(:limit, options)
|
|> restrict(:limit, options, table_binding)
|
||||||
end
|
end
|
||||||
|
|
||||||
def paginate(query, options, :offset) do
|
def paginate(query, options, :offset, table_binding) do
|
||||||
query
|
query
|
||||||
|> restrict(:order, options)
|
|> restrict(:order, options, table_binding)
|
||||||
|> restrict(:offset, options)
|
|> restrict(:offset, options, table_binding)
|
||||||
|> restrict(:limit, options)
|
|> restrict(:limit, options, table_binding)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp cast_params(params) do
|
defp cast_params(params) do
|
||||||
|
|
@ -75,7 +81,8 @@ defmodule Pleroma.Pagination do
|
||||||
since_id: :string,
|
since_id: :string,
|
||||||
max_id: :string,
|
max_id: :string,
|
||||||
offset: :integer,
|
offset: :integer,
|
||||||
limit: :integer
|
limit: :integer,
|
||||||
|
skip_order: :boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
params =
|
params =
|
||||||
|
|
@ -88,38 +95,48 @@ defmodule Pleroma.Pagination do
|
||||||
changeset.changes
|
changeset.changes
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict(query, :min_id, %{min_id: min_id}) do
|
defp restrict(query, :min_id, %{min_id: min_id}, table_binding) do
|
||||||
where(query, [q], q.id > ^min_id)
|
where(query, [{q, table_position(query, table_binding)}], q.id > ^min_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict(query, :since_id, %{since_id: since_id}) do
|
defp restrict(query, :since_id, %{since_id: since_id}, table_binding) do
|
||||||
where(query, [q], q.id > ^since_id)
|
where(query, [{q, table_position(query, table_binding)}], q.id > ^since_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict(query, :max_id, %{max_id: max_id}) do
|
defp restrict(query, :max_id, %{max_id: max_id}, table_binding) do
|
||||||
where(query, [q], q.id < ^max_id)
|
where(query, [{q, table_position(query, table_binding)}], q.id < ^max_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict(query, :order, %{min_id: _}) do
|
defp restrict(query, :order, %{skip_order: true}, _), do: query
|
||||||
order_by(query, [u], fragment("? asc nulls last", u.id))
|
|
||||||
|
defp restrict(query, :order, %{min_id: _}, table_binding) do
|
||||||
|
order_by(
|
||||||
|
query,
|
||||||
|
[{u, table_position(query, table_binding)}],
|
||||||
|
fragment("? asc nulls last", u.id)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict(query, :order, _options) do
|
defp restrict(query, :order, _options, table_binding) do
|
||||||
order_by(query, [u], fragment("? desc nulls last", u.id))
|
order_by(
|
||||||
|
query,
|
||||||
|
[{u, table_position(query, table_binding)}],
|
||||||
|
fragment("? desc nulls last", u.id)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict(query, :offset, %{offset: offset}) do
|
defp restrict(query, :offset, %{offset: offset}, _table_binding) do
|
||||||
offset(query, ^offset)
|
offset(query, ^offset)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict(query, :limit, options) do
|
defp restrict(query, :limit, options, _table_binding) do
|
||||||
limit = Map.get(options, :limit, @default_limit)
|
limit = Map.get(options, :limit, @default_limit)
|
||||||
|
|
||||||
query
|
query
|
||||||
|> limit(^limit)
|
|> limit(^limit)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict(query, _, _), do: query
|
defp restrict(query, _, _, _), do: query
|
||||||
|
|
||||||
defp enforce_order(result, %{min_id: _}) do
|
defp enforce_order(result, %{min_id: _}) do
|
||||||
result
|
result
|
||||||
|
|
@ -127,4 +144,10 @@ defmodule Pleroma.Pagination do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp enforce_order(result, _), do: result
|
defp enforce_order(result, _), do: result
|
||||||
|
|
||||||
|
defp table_position(%Ecto.Query{} = query, binding_name) do
|
||||||
|
Map.get(query.aliases, binding_name, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp table_position(_, _), do: 0
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -16,14 +16,28 @@ defmodule Pleroma.Plugs.AdminSecretAuthenticationPlug do
|
||||||
|
|
||||||
def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
|
def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
|
||||||
|
|
||||||
def call(%{params: %{"admin_token" => admin_token}} = conn, _) do
|
def call(conn, _) do
|
||||||
if secret_token() && admin_token == secret_token() do
|
if secret_token() do
|
||||||
conn
|
authenticate(conn)
|
||||||
|> assign(:user, %User{is_admin: true})
|
|
||||||
else
|
else
|
||||||
conn
|
conn
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def call(conn, _), do: conn
|
def authenticate(%{params: %{"admin_token" => admin_token}} = conn) do
|
||||||
|
if admin_token == secret_token() do
|
||||||
|
assign(conn, :user, %User{is_admin: true})
|
||||||
|
else
|
||||||
|
conn
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def authenticate(conn) do
|
||||||
|
token = secret_token()
|
||||||
|
|
||||||
|
case get_req_header(conn, "x-admin-token") do
|
||||||
|
[^token] -> assign(conn, :user, %User{is_admin: true})
|
||||||
|
_ -> conn
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ defmodule Pleroma.Plugs.OAuthPlug do
|
||||||
)
|
)
|
||||||
|
|
||||||
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
|
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
|
||||||
with %Token{user: %{deactivated: false} = user} = token_record <- Repo.one(query) do
|
with %Token{user: user} = token_record <- Repo.one(query) do
|
||||||
{:ok, user, token_record}
|
{:ok, user, token_record}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Plugs.OAuthScopesPlug do
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
import Pleroma.Web.Gettext
|
import Pleroma.Web.Gettext
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
|
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
|
||||||
|
|
||||||
@behaviour Plug
|
@behaviour Plug
|
||||||
|
|
@ -15,16 +16,15 @@ defmodule Pleroma.Plugs.OAuthScopesPlug do
|
||||||
def call(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do
|
def call(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do
|
||||||
op = options[:op] || :|
|
op = options[:op] || :|
|
||||||
token = assigns[:token]
|
token = assigns[:token]
|
||||||
matched_scopes = token && filter_descendants(scopes, token.scopes)
|
|
||||||
|
scopes = transform_scopes(scopes, options)
|
||||||
|
matched_scopes = (token && filter_descendants(scopes, token.scopes)) || []
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
is_nil(token) ->
|
token && op == :| && Enum.any?(matched_scopes) ->
|
||||||
maybe_perform_instance_privacy_check(conn, options)
|
|
||||||
|
|
||||||
op == :| && Enum.any?(matched_scopes) ->
|
|
||||||
conn
|
conn
|
||||||
|
|
||||||
op == :& && matched_scopes == scopes ->
|
token && op == :& && matched_scopes == scopes ->
|
||||||
conn
|
conn
|
||||||
|
|
||||||
options[:fallback] == :proceed_unauthenticated ->
|
options[:fallback] == :proceed_unauthenticated ->
|
||||||
|
|
@ -60,6 +60,15 @@ defmodule Pleroma.Plugs.OAuthScopesPlug do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc "Transforms scopes by applying supported options (e.g. :admin)"
|
||||||
|
def transform_scopes(scopes, options) do
|
||||||
|
if options[:admin] do
|
||||||
|
Config.oauth_admin_scopes(scopes)
|
||||||
|
else
|
||||||
|
scopes
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp maybe_perform_instance_privacy_check(%Plug.Conn{} = conn, options) do
|
defp maybe_perform_instance_privacy_check(%Plug.Conn{} = conn, options) do
|
||||||
if options[:skip_instance_privacy_check] do
|
if options[:skip_instance_privacy_check] do
|
||||||
conn
|
conn
|
||||||
|
|
|
||||||
21
lib/pleroma/plugs/parsers_plug.ex
Normal file
21
lib/pleroma/plugs/parsers_plug.ex
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Plugs.Parsers do
|
||||||
|
@moduledoc "Initializes Plug.Parsers with upload limit set at boot time"
|
||||||
|
|
||||||
|
@behaviour Plug
|
||||||
|
|
||||||
|
def init(_opts) do
|
||||||
|
Plug.Parsers.init(
|
||||||
|
parsers: [:urlencoded, :multipart, :json],
|
||||||
|
pass: ["*/*"],
|
||||||
|
json_decoder: Jason,
|
||||||
|
length: Pleroma.Config.get([:instance, :upload_limit]),
|
||||||
|
body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defdelegate call(conn, opts), to: Plug.Parsers
|
||||||
|
end
|
||||||
|
|
@ -1,131 +0,0 @@
|
||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Plugs.RateLimiter do
|
|
||||||
@moduledoc """
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
A keyword list of rate limiters where a key is a limiter name and value is the limiter configuration. The basic configuration is a tuple where:
|
|
||||||
|
|
||||||
* The first element: `scale` (Integer). The time scale in milliseconds.
|
|
||||||
* The second element: `limit` (Integer). How many requests to limit in the time scale provided.
|
|
||||||
|
|
||||||
It is also possible to have different limits for unauthenticated and authenticated users: the keyword value must be a list of two tuples where the first one is a config for unauthenticated users and the second one is for authenticated.
|
|
||||||
|
|
||||||
To disable a limiter set its value to `nil`.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
config :pleroma, :rate_limit,
|
|
||||||
one: {1000, 10},
|
|
||||||
two: [{10_000, 10}, {10_000, 50}],
|
|
||||||
foobar: nil
|
|
||||||
|
|
||||||
Here we have three limiters:
|
|
||||||
|
|
||||||
* `one` which is not over 10req/1s
|
|
||||||
* `two` which has two limits: 10req/10s for unauthenticated users and 50req/10s for authenticated users
|
|
||||||
* `foobar` which is disabled
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
AllowedSyntax:
|
|
||||||
|
|
||||||
plug(Pleroma.Plugs.RateLimiter, :limiter_name)
|
|
||||||
plug(Pleroma.Plugs.RateLimiter, {:limiter_name, options})
|
|
||||||
|
|
||||||
Allowed options:
|
|
||||||
|
|
||||||
* `bucket_name` overrides bucket name (e.g. to have a separate limit for a set of actions)
|
|
||||||
* `params` appends values of specified request params (e.g. ["id"]) to bucket name
|
|
||||||
|
|
||||||
Inside a controller:
|
|
||||||
|
|
||||||
plug(Pleroma.Plugs.RateLimiter, :one when action == :one)
|
|
||||||
plug(Pleroma.Plugs.RateLimiter, :two when action in [:two, :three])
|
|
||||||
|
|
||||||
plug(
|
|
||||||
Pleroma.Plugs.RateLimiter,
|
|
||||||
{:status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]}
|
|
||||||
when action in ~w(fav_status unfav_status)a
|
|
||||||
)
|
|
||||||
|
|
||||||
or inside a router pipeline:
|
|
||||||
|
|
||||||
pipeline :api do
|
|
||||||
...
|
|
||||||
plug(Pleroma.Plugs.RateLimiter, :one)
|
|
||||||
...
|
|
||||||
end
|
|
||||||
"""
|
|
||||||
import Pleroma.Web.TranslationHelpers
|
|
||||||
import Plug.Conn
|
|
||||||
|
|
||||||
alias Pleroma.User
|
|
||||||
|
|
||||||
def init(limiter_name) when is_atom(limiter_name) do
|
|
||||||
init({limiter_name, []})
|
|
||||||
end
|
|
||||||
|
|
||||||
def init({limiter_name, opts}) do
|
|
||||||
case Pleroma.Config.get([:rate_limit, limiter_name]) do
|
|
||||||
nil -> nil
|
|
||||||
config -> {limiter_name, config, opts}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Do not limit if there is no limiter configuration
|
|
||||||
def call(conn, nil), do: conn
|
|
||||||
|
|
||||||
def call(conn, settings) do
|
|
||||||
case check_rate(conn, settings) do
|
|
||||||
{:ok, _count} ->
|
|
||||||
conn
|
|
||||||
|
|
||||||
{:error, _count} ->
|
|
||||||
render_throttled_error(conn)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp bucket_name(conn, limiter_name, opts) do
|
|
||||||
bucket_name = opts[:bucket_name] || limiter_name
|
|
||||||
|
|
||||||
if params_names = opts[:params] do
|
|
||||||
params_values = for p <- Enum.sort(params_names), do: conn.params[p]
|
|
||||||
Enum.join([bucket_name] ++ params_values, ":")
|
|
||||||
else
|
|
||||||
bucket_name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp check_rate(
|
|
||||||
%{assigns: %{user: %User{id: user_id}}} = conn,
|
|
||||||
{limiter_name, [_, {scale, limit}], opts}
|
|
||||||
) do
|
|
||||||
bucket_name = bucket_name(conn, limiter_name, opts)
|
|
||||||
ExRated.check_rate("#{bucket_name}:#{user_id}", scale, limit)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp check_rate(conn, {limiter_name, [{scale, limit} | _], opts}) do
|
|
||||||
bucket_name = bucket_name(conn, limiter_name, opts)
|
|
||||||
ExRated.check_rate("#{bucket_name}:#{ip(conn)}", scale, limit)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp check_rate(conn, {limiter_name, {scale, limit}, opts}) do
|
|
||||||
check_rate(conn, {limiter_name, [{scale, limit}, {scale, limit}], opts})
|
|
||||||
end
|
|
||||||
|
|
||||||
def ip(%{remote_ip: remote_ip}) do
|
|
||||||
remote_ip
|
|
||||||
|> Tuple.to_list()
|
|
||||||
|> Enum.join(".")
|
|
||||||
end
|
|
||||||
|
|
||||||
defp render_throttled_error(conn) do
|
|
||||||
conn
|
|
||||||
|> render_error(:too_many_requests, "Throttled")
|
|
||||||
|> halt()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
44
lib/pleroma/plugs/rate_limiter/limiter_supervisor.ex
Normal file
44
lib/pleroma/plugs/rate_limiter/limiter_supervisor.ex
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
defmodule Pleroma.Plugs.RateLimiter.LimiterSupervisor do
|
||||||
|
use DynamicSupervisor
|
||||||
|
|
||||||
|
import Cachex.Spec
|
||||||
|
|
||||||
|
def start_link(init_arg) do
|
||||||
|
DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_limiter(limiter_name, expiration) do
|
||||||
|
{:ok, _pid} =
|
||||||
|
DynamicSupervisor.start_child(
|
||||||
|
__MODULE__,
|
||||||
|
%{
|
||||||
|
id: String.to_atom("rl_#{limiter_name}"),
|
||||||
|
start:
|
||||||
|
{Cachex, :start_link,
|
||||||
|
[
|
||||||
|
limiter_name,
|
||||||
|
[
|
||||||
|
expiration:
|
||||||
|
expiration(
|
||||||
|
default: expiration,
|
||||||
|
interval: check_interval(expiration),
|
||||||
|
lazy: true
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def init(_init_arg) do
|
||||||
|
DynamicSupervisor.init(strategy: :one_for_one)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp check_interval(exp) do
|
||||||
|
(exp / 2)
|
||||||
|
|> Kernel.trunc()
|
||||||
|
|> Kernel.min(5000)
|
||||||
|
|> Kernel.max(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
227
lib/pleroma/plugs/rate_limiter/rate_limiter.ex
Normal file
227
lib/pleroma/plugs/rate_limiter/rate_limiter.ex
Normal file
|
|
@ -0,0 +1,227 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Plugs.RateLimiter do
|
||||||
|
@moduledoc """
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
A keyword list of rate limiters where a key is a limiter name and value is the limiter configuration. The basic configuration is a tuple where:
|
||||||
|
|
||||||
|
* The first element: `scale` (Integer). The time scale in milliseconds.
|
||||||
|
* The second element: `limit` (Integer). How many requests to limit in the time scale provided.
|
||||||
|
|
||||||
|
It is also possible to have different limits for unauthenticated and authenticated users: the keyword value must be a list of two tuples where the first one is a config for unauthenticated users and the second one is for authenticated.
|
||||||
|
|
||||||
|
To disable a limiter set its value to `nil`.
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
config :pleroma, :rate_limit,
|
||||||
|
one: {1000, 10},
|
||||||
|
two: [{10_000, 10}, {10_000, 50}],
|
||||||
|
foobar: nil
|
||||||
|
|
||||||
|
Here we have three limiters:
|
||||||
|
|
||||||
|
* `one` which is not over 10req/1s
|
||||||
|
* `two` which has two limits: 10req/10s for unauthenticated users and 50req/10s for authenticated users
|
||||||
|
* `foobar` which is disabled
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
AllowedSyntax:
|
||||||
|
|
||||||
|
plug(Pleroma.Plugs.RateLimiter, name: :limiter_name)
|
||||||
|
plug(Pleroma.Plugs.RateLimiter, options) # :name is a required option
|
||||||
|
|
||||||
|
Allowed options:
|
||||||
|
|
||||||
|
* `name` required, always used to fetch the limit values from the config
|
||||||
|
* `bucket_name` overrides name for counting purposes (e.g. to have a separate limit for a set of actions)
|
||||||
|
* `params` appends values of specified request params (e.g. ["id"]) to bucket name
|
||||||
|
|
||||||
|
Inside a controller:
|
||||||
|
|
||||||
|
plug(Pleroma.Plugs.RateLimiter, [name: :one] when action == :one)
|
||||||
|
plug(Pleroma.Plugs.RateLimiter, [name: :two] when action in [:two, :three])
|
||||||
|
|
||||||
|
plug(
|
||||||
|
Pleroma.Plugs.RateLimiter,
|
||||||
|
[name: :status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]]
|
||||||
|
when action in ~w(fav_status unfav_status)a
|
||||||
|
)
|
||||||
|
|
||||||
|
or inside a router pipeline:
|
||||||
|
|
||||||
|
pipeline :api do
|
||||||
|
...
|
||||||
|
plug(Pleroma.Plugs.RateLimiter, name: :one)
|
||||||
|
...
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
import Pleroma.Web.TranslationHelpers
|
||||||
|
import Plug.Conn
|
||||||
|
|
||||||
|
alias Pleroma.Plugs.RateLimiter.LimiterSupervisor
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
def init(opts) do
|
||||||
|
limiter_name = Keyword.get(opts, :name)
|
||||||
|
|
||||||
|
case Pleroma.Config.get([:rate_limit, limiter_name]) do
|
||||||
|
nil ->
|
||||||
|
nil
|
||||||
|
|
||||||
|
config ->
|
||||||
|
name_root = Keyword.get(opts, :bucket_name, limiter_name)
|
||||||
|
|
||||||
|
%{
|
||||||
|
name: name_root,
|
||||||
|
limits: config,
|
||||||
|
opts: opts
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Do not limit if there is no limiter configuration
|
||||||
|
def call(conn, nil), do: conn
|
||||||
|
|
||||||
|
def call(conn, settings) do
|
||||||
|
settings
|
||||||
|
|> incorporate_conn_info(conn)
|
||||||
|
|> check_rate()
|
||||||
|
|> case do
|
||||||
|
{:ok, _count} ->
|
||||||
|
conn
|
||||||
|
|
||||||
|
{:error, _count} ->
|
||||||
|
render_throttled_error(conn)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def inspect_bucket(conn, name_root, settings) do
|
||||||
|
settings =
|
||||||
|
settings
|
||||||
|
|> incorporate_conn_info(conn)
|
||||||
|
|
||||||
|
bucket_name = make_bucket_name(%{settings | name: name_root})
|
||||||
|
key_name = make_key_name(settings)
|
||||||
|
limit = get_limits(settings)
|
||||||
|
|
||||||
|
case Cachex.get(bucket_name, key_name) do
|
||||||
|
{:error, :no_cache} ->
|
||||||
|
{:err, :not_found}
|
||||||
|
|
||||||
|
{:ok, nil} ->
|
||||||
|
{0, limit}
|
||||||
|
|
||||||
|
{:ok, value} ->
|
||||||
|
{value, limit - value}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp check_rate(settings) do
|
||||||
|
bucket_name = make_bucket_name(settings)
|
||||||
|
key_name = make_key_name(settings)
|
||||||
|
limit = get_limits(settings)
|
||||||
|
|
||||||
|
case Cachex.get_and_update(bucket_name, key_name, &increment_value(&1, limit)) do
|
||||||
|
{:commit, value} ->
|
||||||
|
{:ok, value}
|
||||||
|
|
||||||
|
{:ignore, value} ->
|
||||||
|
{:error, value}
|
||||||
|
|
||||||
|
{:error, :no_cache} ->
|
||||||
|
initialize_buckets(settings)
|
||||||
|
check_rate(settings)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp increment_value(nil, _limit), do: {:commit, 1}
|
||||||
|
|
||||||
|
defp increment_value(val, limit) when val >= limit, do: {:ignore, val}
|
||||||
|
|
||||||
|
defp increment_value(val, _limit), do: {:commit, val + 1}
|
||||||
|
|
||||||
|
defp incorporate_conn_info(settings, %{assigns: %{user: %User{id: user_id}}, params: params}) do
|
||||||
|
Map.merge(settings, %{
|
||||||
|
mode: :user,
|
||||||
|
conn_params: params,
|
||||||
|
conn_info: "#{user_id}"
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp incorporate_conn_info(settings, %{params: params} = conn) do
|
||||||
|
Map.merge(settings, %{
|
||||||
|
mode: :anon,
|
||||||
|
conn_params: params,
|
||||||
|
conn_info: "#{ip(conn)}"
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp ip(%{remote_ip: remote_ip}) do
|
||||||
|
remote_ip
|
||||||
|
|> Tuple.to_list()
|
||||||
|
|> Enum.join(".")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp render_throttled_error(conn) do
|
||||||
|
conn
|
||||||
|
|> render_error(:too_many_requests, "Throttled")
|
||||||
|
|> halt()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp make_key_name(settings) do
|
||||||
|
""
|
||||||
|
|> attach_params(settings)
|
||||||
|
|> attach_identity(settings)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_scale(_, {scale, _}), do: scale
|
||||||
|
|
||||||
|
defp get_scale(:anon, [{scale, _}, {_, _}]), do: scale
|
||||||
|
|
||||||
|
defp get_scale(:user, [{_, _}, {scale, _}]), do: scale
|
||||||
|
|
||||||
|
defp get_limits(%{limits: {_scale, limit}}), do: limit
|
||||||
|
|
||||||
|
defp get_limits(%{mode: :user, limits: [_, {_, limit}]}), do: limit
|
||||||
|
|
||||||
|
defp get_limits(%{limits: [{_, limit}, _]}), do: limit
|
||||||
|
|
||||||
|
defp make_bucket_name(%{mode: :user, name: name_root}),
|
||||||
|
do: user_bucket_name(name_root)
|
||||||
|
|
||||||
|
defp make_bucket_name(%{mode: :anon, name: name_root}),
|
||||||
|
do: anon_bucket_name(name_root)
|
||||||
|
|
||||||
|
defp attach_params(input, %{conn_params: conn_params, opts: opts}) do
|
||||||
|
param_string =
|
||||||
|
opts
|
||||||
|
|> Keyword.get(:params, [])
|
||||||
|
|> Enum.sort()
|
||||||
|
|> Enum.map(&Map.get(conn_params, &1, ""))
|
||||||
|
|> Enum.join(":")
|
||||||
|
|
||||||
|
"#{input}#{param_string}"
|
||||||
|
end
|
||||||
|
|
||||||
|
defp initialize_buckets(%{name: _name, limits: nil}), do: :ok
|
||||||
|
|
||||||
|
defp initialize_buckets(%{name: name, limits: limits}) do
|
||||||
|
LimiterSupervisor.add_limiter(anon_bucket_name(name), get_scale(:anon, limits))
|
||||||
|
LimiterSupervisor.add_limiter(user_bucket_name(name), get_scale(:user, limits))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp attach_identity(base, %{mode: :user, conn_info: conn_info}),
|
||||||
|
do: "user:#{base}:#{conn_info}"
|
||||||
|
|
||||||
|
defp attach_identity(base, %{mode: :anon, conn_info: conn_info}),
|
||||||
|
do: "ip:#{base}:#{conn_info}"
|
||||||
|
|
||||||
|
defp user_bucket_name(name_root), do: "user:#{name_root}" |> String.to_atom()
|
||||||
|
defp anon_bucket_name(name_root), do: "anon:#{name_root}" |> String.to_atom()
|
||||||
|
end
|
||||||
16
lib/pleroma/plugs/rate_limiter/supervisor.ex
Normal file
16
lib/pleroma/plugs/rate_limiter/supervisor.ex
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
defmodule Pleroma.Plugs.RateLimiter.Supervisor do
|
||||||
|
use Supervisor
|
||||||
|
|
||||||
|
def start_link(opts) do
|
||||||
|
Supervisor.start_link(__MODULE__, opts, name: __MODULE__)
|
||||||
|
end
|
||||||
|
|
||||||
|
def init(_args) do
|
||||||
|
children = [
|
||||||
|
Pleroma.Plugs.RateLimiter.LimiterSupervisor
|
||||||
|
]
|
||||||
|
|
||||||
|
opts = [strategy: :one_for_one, name: Pleroma.Web.Streamer.Supervisor]
|
||||||
|
Supervisor.init(children, opts)
|
||||||
|
end
|
||||||
|
end
|
||||||
26
lib/pleroma/plugs/static_fe_plug.ex
Normal file
26
lib/pleroma/plugs/static_fe_plug.ex
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Plugs.StaticFEPlug do
|
||||||
|
import Plug.Conn
|
||||||
|
alias Pleroma.Web.StaticFE.StaticFEController
|
||||||
|
|
||||||
|
def init(options), do: options
|
||||||
|
|
||||||
|
def call(conn, _) do
|
||||||
|
if enabled?() and accepts_html?(conn) do
|
||||||
|
conn
|
||||||
|
|> StaticFEController.call(:show)
|
||||||
|
|> halt()
|
||||||
|
else
|
||||||
|
conn
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp enabled?, do: Pleroma.Config.get([:static_fe, :enabled], false)
|
||||||
|
|
||||||
|
defp accepts_html?(conn) do
|
||||||
|
conn |> get_req_header("accept") |> List.first() |> String.contains?("text/html")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -24,7 +24,8 @@ defmodule Pleroma.Plugs.TrailingFormatPlug do
|
||||||
"/api/help",
|
"/api/help",
|
||||||
"/api/externalprofile",
|
"/api/externalprofile",
|
||||||
"/notice",
|
"/notice",
|
||||||
"/api/pleroma/emoji"
|
"/api/pleroma/emoji",
|
||||||
|
"/api/oauth_tokens"
|
||||||
]
|
]
|
||||||
|
|
||||||
def init(opts) do
|
def init(opts) do
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,11 @@ defmodule Pleroma.Plugs.UserEnabledPlug do
|
||||||
options
|
options
|
||||||
end
|
end
|
||||||
|
|
||||||
def call(%{assigns: %{user: %User{deactivated: true}}} = conn, _) do
|
def call(%{assigns: %{user: %User{} = user}} = conn, _) do
|
||||||
conn
|
case User.account_status(user) do
|
||||||
|> assign(:user, nil)
|
:active -> conn
|
||||||
|
_ -> assign(conn, :user, nil)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def call(conn, _) do
|
def call(conn, _) do
|
||||||
|
|
|
||||||
|
|
@ -5,19 +5,39 @@
|
||||||
defmodule Pleroma.Plugs.UserIsAdminPlug do
|
defmodule Pleroma.Plugs.UserIsAdminPlug do
|
||||||
import Pleroma.Web.TranslationHelpers
|
import Pleroma.Web.TranslationHelpers
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
|
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.OAuth
|
||||||
|
|
||||||
def init(options) do
|
def init(options) do
|
||||||
options
|
options
|
||||||
end
|
end
|
||||||
|
|
||||||
def call(%{assigns: %{user: %User{is_admin: true}}} = conn, _) do
|
def call(%{assigns: %{user: %User{is_admin: true}} = assigns} = conn, _) do
|
||||||
|
token = assigns[:token]
|
||||||
|
|
||||||
|
cond do
|
||||||
|
not Pleroma.Config.enforce_oauth_admin_scope_usage?() ->
|
||||||
conn
|
conn
|
||||||
|
|
||||||
|
token && OAuth.Scopes.contains_admin_scopes?(token.scopes) ->
|
||||||
|
# Note: checking for _any_ admin scope presence, not necessarily fitting requested action.
|
||||||
|
# Thus, controller must explicitly invoke OAuthScopesPlug to verify scope requirements.
|
||||||
|
# Admin might opt out of admin scope for some apps to block any admin actions from them.
|
||||||
|
conn
|
||||||
|
|
||||||
|
true ->
|
||||||
|
fail(conn)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def call(conn, _) do
|
def call(conn, _) do
|
||||||
|
fail(conn)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fail(conn) do
|
||||||
conn
|
conn
|
||||||
|> render_error(:forbidden, "User is not admin.")
|
|> render_error(:forbidden, "User is not an admin or OAuth admin scope is not granted.")
|
||||||
|> halt
|
|> halt()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ defmodule Pleroma.Repo do
|
||||||
adapter: Ecto.Adapters.Postgres,
|
adapter: Ecto.Adapters.Postgres,
|
||||||
migration_timestamps: [type: :naive_datetime_usec]
|
migration_timestamps: [type: :naive_datetime_usec]
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
defmodule Instrumenter do
|
defmodule Instrumenter do
|
||||||
use Prometheus.EctoInstrumenter
|
use Prometheus.EctoInstrumenter
|
||||||
end
|
end
|
||||||
|
|
@ -47,4 +49,37 @@ defmodule Pleroma.Repo do
|
||||||
_ -> {:error, :not_found}
|
_ -> {:error, :not_found}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def check_migrations_applied!() do
|
||||||
|
unless Pleroma.Config.get(
|
||||||
|
[:i_am_aware_this_may_cause_data_loss, :disable_migration_check],
|
||||||
|
false
|
||||||
|
) do
|
||||||
|
Ecto.Migrator.with_repo(__MODULE__, fn repo ->
|
||||||
|
down_migrations =
|
||||||
|
Ecto.Migrator.migrations(repo)
|
||||||
|
|> Enum.reject(fn
|
||||||
|
{:up, _, _} -> true
|
||||||
|
{:down, _, _} -> false
|
||||||
|
end)
|
||||||
|
|
||||||
|
if length(down_migrations) > 0 do
|
||||||
|
down_migrations_text =
|
||||||
|
Enum.map(down_migrations, fn {:down, id, name} -> "- #{name} (#{id})\n" end)
|
||||||
|
|
||||||
|
Logger.error(
|
||||||
|
"The following migrations were not applied:\n#{down_migrations_text}If you want to start Pleroma anyway, set\nconfig :pleroma, :i_am_aware_this_may_cause_data_loss, disable_migration_check: true"
|
||||||
|
)
|
||||||
|
|
||||||
|
raise Pleroma.Repo.UnappliedMigrationsError
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule Pleroma.Repo.UnappliedMigrationsError do
|
||||||
|
defexception message: "Unapplied Migrations detected"
|
||||||
end
|
end
|
||||||
|
|
|
||||||
48
lib/pleroma/report_note.ex
Normal file
48
lib/pleroma/report_note.ex
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.ReportNote do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.ReportNote
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{}
|
||||||
|
|
||||||
|
schema "report_notes" do
|
||||||
|
field(:content, :string)
|
||||||
|
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||||
|
belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec create(FlakeId.Ecto.CompatType.t(), FlakeId.Ecto.CompatType.t(), String.t()) ::
|
||||||
|
{:ok, ReportNote.t()} | {:error, Changeset.t()}
|
||||||
|
def create(user_id, activity_id, content) do
|
||||||
|
attrs = %{
|
||||||
|
user_id: user_id,
|
||||||
|
activity_id: activity_id,
|
||||||
|
content: content
|
||||||
|
}
|
||||||
|
|
||||||
|
%ReportNote{}
|
||||||
|
|> cast(attrs, [:user_id, :activity_id, :content])
|
||||||
|
|> validate_required([:user_id, :activity_id, :content])
|
||||||
|
|> Repo.insert()
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec destroy(FlakeId.Ecto.CompatType.t()) ::
|
||||||
|
{:ok, ReportNote.t()} | {:error, Changeset.t()}
|
||||||
|
def destroy(id) do
|
||||||
|
from(r in ReportNote, where: r.id == ^id)
|
||||||
|
|> Repo.one()
|
||||||
|
|> Repo.delete()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -5,10 +5,12 @@
|
||||||
defmodule Pleroma.Uploaders.Local do
|
defmodule Pleroma.Uploaders.Local do
|
||||||
@behaviour Pleroma.Uploaders.Uploader
|
@behaviour Pleroma.Uploaders.Uploader
|
||||||
|
|
||||||
|
@impl true
|
||||||
def get_file(_) do
|
def get_file(_) do
|
||||||
{:ok, {:static_dir, upload_path()}}
|
{:ok, {:static_dir, upload_path()}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
def put_file(upload) do
|
def put_file(upload) do
|
||||||
{local_path, file} =
|
{local_path, file} =
|
||||||
case Enum.reverse(Path.split(upload.path)) do
|
case Enum.reverse(Path.split(upload.path)) do
|
||||||
|
|
@ -33,4 +35,15 @@ defmodule Pleroma.Uploaders.Local do
|
||||||
def upload_path do
|
def upload_path do
|
||||||
Pleroma.Config.get!([__MODULE__, :uploads])
|
Pleroma.Config.get!([__MODULE__, :uploads])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def delete_file(path) do
|
||||||
|
upload_path()
|
||||||
|
|> Path.join(path)
|
||||||
|
|> File.rm()
|
||||||
|
|> case do
|
||||||
|
:ok -> :ok
|
||||||
|
{:error, posix_error} -> {:error, to_string(posix_error)}
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Uploaders.MDII do
|
|
||||||
@moduledoc "Represents uploader for https://github.com/hakaba-hitoyo/minimal-digital-image-infrastructure"
|
|
||||||
|
|
||||||
alias Pleroma.Config
|
|
||||||
alias Pleroma.HTTP
|
|
||||||
|
|
||||||
@behaviour Pleroma.Uploaders.Uploader
|
|
||||||
|
|
||||||
# MDII-hosted images are never passed through the MediaPlug; only local media.
|
|
||||||
# Delegate to Pleroma.Uploaders.Local
|
|
||||||
def get_file(file) do
|
|
||||||
Pleroma.Uploaders.Local.get_file(file)
|
|
||||||
end
|
|
||||||
|
|
||||||
def put_file(upload) do
|
|
||||||
cgi = Config.get([Pleroma.Uploaders.MDII, :cgi])
|
|
||||||
files = Config.get([Pleroma.Uploaders.MDII, :files])
|
|
||||||
|
|
||||||
{:ok, file_data} = File.read(upload.tempfile)
|
|
||||||
|
|
||||||
extension = String.split(upload.name, ".") |> List.last()
|
|
||||||
query = "#{cgi}?#{extension}"
|
|
||||||
|
|
||||||
with {:ok, %{status: 200, body: body}} <-
|
|
||||||
HTTP.post(query, file_data, [], adapter: [pool: :default]) do
|
|
||||||
remote_file_name = String.split(body) |> List.first()
|
|
||||||
public_url = "#{files}/#{remote_file_name}.#{extension}"
|
|
||||||
{:ok, {:url, public_url}}
|
|
||||||
else
|
|
||||||
_ -> Pleroma.Uploaders.Local.put_file(upload)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Uploaders.S3 do
|
||||||
|
|
||||||
# The file name is re-encoded with S3's constraints here to comply with previous
|
# The file name is re-encoded with S3's constraints here to comply with previous
|
||||||
# links with less strict filenames
|
# links with less strict filenames
|
||||||
|
@impl true
|
||||||
def get_file(file) do
|
def get_file(file) do
|
||||||
config = Config.get([__MODULE__])
|
config = Config.get([__MODULE__])
|
||||||
bucket = Keyword.fetch!(config, :bucket)
|
bucket = Keyword.fetch!(config, :bucket)
|
||||||
|
|
@ -35,6 +36,7 @@ defmodule Pleroma.Uploaders.S3 do
|
||||||
])}}
|
])}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
def put_file(%Pleroma.Upload{} = upload) do
|
def put_file(%Pleroma.Upload{} = upload) do
|
||||||
config = Config.get([__MODULE__])
|
config = Config.get([__MODULE__])
|
||||||
bucket = Keyword.get(config, :bucket)
|
bucket = Keyword.get(config, :bucket)
|
||||||
|
|
@ -69,6 +71,18 @@ defmodule Pleroma.Uploaders.S3 do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def delete_file(file) do
|
||||||
|
[__MODULE__, :bucket]
|
||||||
|
|> Config.get()
|
||||||
|
|> ExAws.S3.delete_object(file)
|
||||||
|
|> ExAws.request()
|
||||||
|
|> case do
|
||||||
|
{:ok, %{status_code: 204}} -> :ok
|
||||||
|
error -> {:error, inspect(error)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@regex Regex.compile!("[^0-9a-zA-Z!.*/'()_-]")
|
@regex Regex.compile!("[^0-9a-zA-Z!.*/'()_-]")
|
||||||
def strict_encode(name) do
|
def strict_encode(name) do
|
||||||
String.replace(name, @regex, "-")
|
String.replace(name, @regex, "-")
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,8 @@ defmodule Pleroma.Uploaders.Uploader do
|
||||||
@callback put_file(Pleroma.Upload.t()) ::
|
@callback put_file(Pleroma.Upload.t()) ::
|
||||||
:ok | {:ok, file_spec()} | {:error, String.t()} | :wait_callback
|
:ok | {:ok, file_spec()} | {:error, String.t()} | :wait_callback
|
||||||
|
|
||||||
|
@callback delete_file(file :: String.t()) :: :ok | {:error, String.t()}
|
||||||
|
|
||||||
@callback http_callback(Plug.Conn.t(), Map.t()) ::
|
@callback http_callback(Plug.Conn.t(), Map.t()) ::
|
||||||
{:ok, Plug.Conn.t()}
|
{:ok, Plug.Conn.t()}
|
||||||
| {:ok, Plug.Conn.t(), file_spec()}
|
| {:ok, Plug.Conn.t(), file_spec()}
|
||||||
|
|
@ -43,7 +45,6 @@ defmodule Pleroma.Uploaders.Uploader do
|
||||||
@optional_callbacks http_callback: 2
|
@optional_callbacks http_callback: 2
|
||||||
|
|
||||||
@spec put_file(module(), Pleroma.Upload.t()) :: {:ok, file_spec()} | {:error, String.t()}
|
@spec put_file(module(), Pleroma.Upload.t()) :: {:ok, file_spec()} | {:error, String.t()}
|
||||||
|
|
||||||
def put_file(uploader, upload) do
|
def put_file(uploader, upload) do
|
||||||
case uploader.put_file(upload) do
|
case uploader.put_file(upload) do
|
||||||
:ok -> {:ok, {:file, upload.path}}
|
:ok -> {:ok, {:file, upload.path}}
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,12 @@ defmodule Pleroma.User do
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
import Ecto, only: [assoc: 2]
|
||||||
|
|
||||||
alias Comeonin.Pbkdf2
|
alias Comeonin.Pbkdf2
|
||||||
alias Ecto.Multi
|
alias Ecto.Multi
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Config
|
||||||
alias Pleroma.Conversation.Participation
|
alias Pleroma.Conversation.Participation
|
||||||
alias Pleroma.Delivery
|
alias Pleroma.Delivery
|
||||||
alias Pleroma.FollowingRelationship
|
alias Pleroma.FollowingRelationship
|
||||||
|
|
@ -21,6 +23,7 @@ defmodule Pleroma.User do
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.RepoStreamer
|
alias Pleroma.RepoStreamer
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.UserRelationship
|
||||||
alias Pleroma.Web
|
alias Pleroma.Web
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
|
|
@ -33,7 +36,7 @@ defmodule Pleroma.User do
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@type t :: %__MODULE__{}
|
@type t :: %__MODULE__{}
|
||||||
|
@type account_status :: :active | :deactivated | :password_reset_pending | :confirmation_pending
|
||||||
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
|
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
|
||||||
|
|
||||||
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
|
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
|
||||||
|
|
@ -42,6 +45,32 @@ defmodule Pleroma.User do
|
||||||
@strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
|
@strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
|
||||||
@extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
|
@extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
|
||||||
|
|
||||||
|
# AP ID user relationships (blocks, mutes etc.)
|
||||||
|
# Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
|
||||||
|
@user_relationships_config [
|
||||||
|
block: [
|
||||||
|
blocker_blocks: :blocked_users,
|
||||||
|
blockee_blocks: :blocker_users
|
||||||
|
],
|
||||||
|
mute: [
|
||||||
|
muter_mutes: :muted_users,
|
||||||
|
mutee_mutes: :muter_users
|
||||||
|
],
|
||||||
|
reblog_mute: [
|
||||||
|
reblog_muter_mutes: :reblog_muted_users,
|
||||||
|
reblog_mutee_mutes: :reblog_muter_users
|
||||||
|
],
|
||||||
|
notification_mute: [
|
||||||
|
notification_muter_mutes: :notification_muted_users,
|
||||||
|
notification_mutee_mutes: :notification_muter_users
|
||||||
|
],
|
||||||
|
# Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
|
||||||
|
inverse_subscription: [
|
||||||
|
subscribee_subscriptions: :subscriber_users,
|
||||||
|
subscriber_subscriptions: :subscribee_users
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
schema "users" do
|
schema "users" do
|
||||||
field(:bio, :string)
|
field(:bio, :string)
|
||||||
field(:email, :string)
|
field(:email, :string)
|
||||||
|
|
@ -61,25 +90,18 @@ defmodule Pleroma.User do
|
||||||
field(:tags, {:array, :string}, default: [])
|
field(:tags, {:array, :string}, default: [])
|
||||||
field(:last_refreshed_at, :naive_datetime_usec)
|
field(:last_refreshed_at, :naive_datetime_usec)
|
||||||
field(:last_digest_emailed_at, :naive_datetime)
|
field(:last_digest_emailed_at, :naive_datetime)
|
||||||
|
|
||||||
field(:banner, :map, default: %{})
|
field(:banner, :map, default: %{})
|
||||||
field(:background, :map, default: %{})
|
field(:background, :map, default: %{})
|
||||||
field(:source_data, :map, default: %{})
|
field(:source_data, :map, default: %{})
|
||||||
field(:note_count, :integer, default: 0)
|
field(:note_count, :integer, default: 0)
|
||||||
field(:follower_count, :integer, default: 0)
|
field(:follower_count, :integer, default: 0)
|
||||||
# Should be filled in only for remote users
|
field(:following_count, :integer, default: 0)
|
||||||
field(:following_count, :integer, default: nil)
|
|
||||||
field(:locked, :boolean, default: false)
|
field(:locked, :boolean, default: false)
|
||||||
field(:confirmation_pending, :boolean, default: false)
|
field(:confirmation_pending, :boolean, default: false)
|
||||||
field(:password_reset_pending, :boolean, default: false)
|
field(:password_reset_pending, :boolean, default: false)
|
||||||
field(:confirmation_token, :string, default: nil)
|
field(:confirmation_token, :string, default: nil)
|
||||||
field(:default_scope, :string, default: "public")
|
field(:default_scope, :string, default: "public")
|
||||||
field(:blocks, {:array, :string}, default: [])
|
|
||||||
field(:domain_blocks, {:array, :string}, default: [])
|
field(:domain_blocks, {:array, :string}, default: [])
|
||||||
field(:mutes, {:array, :string}, default: [])
|
|
||||||
field(:muted_reblogs, {:array, :string}, default: [])
|
|
||||||
field(:muted_notifications, {:array, :string}, default: [])
|
|
||||||
field(:subscribers, {:array, :string}, default: [])
|
|
||||||
field(:deactivated, :boolean, default: false)
|
field(:deactivated, :boolean, default: false)
|
||||||
field(:no_rich_text, :boolean, default: false)
|
field(:no_rich_text, :boolean, default: false)
|
||||||
field(:ap_enabled, :boolean, default: false)
|
field(:ap_enabled, :boolean, default: false)
|
||||||
|
|
@ -104,45 +126,130 @@ defmodule Pleroma.User do
|
||||||
field(:raw_fields, {:array, :map}, default: [])
|
field(:raw_fields, {:array, :map}, default: [])
|
||||||
field(:discoverable, :boolean, default: false)
|
field(:discoverable, :boolean, default: false)
|
||||||
field(:invisible, :boolean, default: false)
|
field(:invisible, :boolean, default: false)
|
||||||
|
field(:allow_following_move, :boolean, default: true)
|
||||||
field(:skip_thread_containment, :boolean, default: false)
|
field(:skip_thread_containment, :boolean, default: false)
|
||||||
|
field(:actor_type, :string, default: "Person")
|
||||||
|
field(:also_known_as, {:array, :string}, default: [])
|
||||||
|
|
||||||
field(:notification_settings, :map,
|
embeds_one(
|
||||||
default: %{
|
:notification_settings,
|
||||||
"followers" => true,
|
Pleroma.User.NotificationSetting,
|
||||||
"follows" => true,
|
on_replace: :update
|
||||||
"non_follows" => true,
|
|
||||||
"non_followers" => true
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
has_many(:notifications, Notification)
|
has_many(:notifications, Notification)
|
||||||
has_many(:registrations, Registration)
|
has_many(:registrations, Registration)
|
||||||
has_many(:deliveries, Delivery)
|
has_many(:deliveries, Delivery)
|
||||||
|
|
||||||
field(:info, :map, default: %{})
|
has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
|
||||||
|
has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
|
||||||
|
|
||||||
|
for {relationship_type,
|
||||||
|
[
|
||||||
|
{outgoing_relation, outgoing_relation_target},
|
||||||
|
{incoming_relation, incoming_relation_source}
|
||||||
|
]} <- @user_relationships_config do
|
||||||
|
# Definitions of `has_many :blocker_blocks`, `has_many :muter_mutes` etc.
|
||||||
|
has_many(outgoing_relation, UserRelationship,
|
||||||
|
foreign_key: :source_id,
|
||||||
|
where: [relationship_type: relationship_type]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Definitions of `has_many :blockee_blocks`, `has_many :mutee_mutes` etc.
|
||||||
|
has_many(incoming_relation, UserRelationship,
|
||||||
|
foreign_key: :target_id,
|
||||||
|
where: [relationship_type: relationship_type]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Definitions of `has_many :blocked_users`, `has_many :muted_users` etc.
|
||||||
|
has_many(outgoing_relation_target, through: [outgoing_relation, :target])
|
||||||
|
|
||||||
|
# Definitions of `has_many :blocker_users`, `has_many :muter_users` etc.
|
||||||
|
has_many(incoming_relation_source, through: [incoming_relation, :source])
|
||||||
|
end
|
||||||
|
|
||||||
|
# `:blocks` is deprecated (replaced with `blocked_users` relation)
|
||||||
|
field(:blocks, {:array, :string}, default: [])
|
||||||
|
# `:mutes` is deprecated (replaced with `muted_users` relation)
|
||||||
|
field(:mutes, {:array, :string}, default: [])
|
||||||
|
# `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
|
||||||
|
field(:muted_reblogs, {:array, :string}, default: [])
|
||||||
|
# `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
|
||||||
|
field(:muted_notifications, {:array, :string}, default: [])
|
||||||
|
# `:subscribers` is deprecated (replaced with `subscriber_users` relation)
|
||||||
|
field(:subscribers, {:array, :string}, default: [])
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
def auth_active?(%User{confirmation_pending: true}),
|
for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
|
||||||
do: !Pleroma.Config.get([:instance, :account_activation_required])
|
@user_relationships_config do
|
||||||
|
# Definitions of `blocked_users_relation/1`, `muted_users_relation/1`, etc.
|
||||||
|
def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
|
||||||
|
target_users_query = assoc(user, unquote(outgoing_relation_target))
|
||||||
|
|
||||||
def auth_active?(%User{}), do: true
|
if restrict_deactivated? do
|
||||||
|
restrict_deactivated(target_users_query)
|
||||||
|
else
|
||||||
|
target_users_query
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Definitions of `blocked_users/1`, `muted_users/1`, etc.
|
||||||
|
def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
|
||||||
|
__MODULE__
|
||||||
|
|> apply(unquote(:"#{outgoing_relation_target}_relation"), [
|
||||||
|
user,
|
||||||
|
restrict_deactivated?
|
||||||
|
])
|
||||||
|
|> Repo.all()
|
||||||
|
end
|
||||||
|
|
||||||
|
# Definitions of `blocked_users_ap_ids/1`, `muted_users_ap_ids/1`, etc.
|
||||||
|
def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
|
||||||
|
__MODULE__
|
||||||
|
|> apply(unquote(:"#{outgoing_relation_target}_relation"), [
|
||||||
|
user,
|
||||||
|
restrict_deactivated?
|
||||||
|
])
|
||||||
|
|> select([u], u.ap_id)
|
||||||
|
|> Repo.all()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "Returns status account"
|
||||||
|
@spec account_status(User.t()) :: account_status()
|
||||||
|
def account_status(%User{deactivated: true}), do: :deactivated
|
||||||
|
def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
|
||||||
|
|
||||||
|
def account_status(%User{confirmation_pending: true}) do
|
||||||
|
case Config.get([:instance, :account_activation_required]) do
|
||||||
|
true -> :confirmation_pending
|
||||||
|
_ -> :active
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def account_status(%User{}), do: :active
|
||||||
|
|
||||||
|
@spec visible_for?(User.t(), User.t() | nil) :: boolean()
|
||||||
def visible_for?(user, for_user \\ nil)
|
def visible_for?(user, for_user \\ nil)
|
||||||
|
|
||||||
|
def visible_for?(%User{invisible: true}, _), do: false
|
||||||
|
|
||||||
def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
|
def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
|
||||||
|
|
||||||
def visible_for?(%User{} = user, for_user) do
|
def visible_for?(%User{} = user, for_user) do
|
||||||
auth_active?(user) || superuser?(for_user)
|
account_status(user) == :active || superuser?(for_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
def visible_for?(_, _), do: false
|
def visible_for?(_, _), do: false
|
||||||
|
|
||||||
|
@spec superuser?(User.t()) :: boolean()
|
||||||
def superuser?(%User{local: true, is_admin: true}), do: true
|
def superuser?(%User{local: true, is_admin: true}), do: true
|
||||||
def superuser?(%User{local: true, is_moderator: true}), do: true
|
def superuser?(%User{local: true, is_moderator: true}), do: true
|
||||||
def superuser?(_), do: false
|
def superuser?(_), do: false
|
||||||
|
|
||||||
|
@spec invisible?(User.t()) :: boolean()
|
||||||
def invisible?(%User{invisible: true}), do: true
|
def invisible?(%User{invisible: true}), do: true
|
||||||
def invisible?(_), do: false
|
def invisible?(_), do: false
|
||||||
|
|
||||||
|
|
@ -173,22 +280,6 @@ defmodule Pleroma.User do
|
||||||
def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
|
def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
|
||||||
def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
|
def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
|
||||||
|
|
||||||
def user_info(%User{} = user, args \\ %{}) do
|
|
||||||
following_count =
|
|
||||||
Map.get(args, :following_count, user.following_count || following_count(user))
|
|
||||||
|
|
||||||
follower_count = Map.get(args, :follower_count, user.follower_count)
|
|
||||||
|
|
||||||
%{
|
|
||||||
note_count: user.note_count,
|
|
||||||
locked: user.locked,
|
|
||||||
confirmation_pending: user.confirmation_pending,
|
|
||||||
default_scope: user.default_scope
|
|
||||||
}
|
|
||||||
|> Map.put(:following_count, following_count)
|
|
||||||
|> Map.put(:follower_count, follower_count)
|
|
||||||
end
|
|
||||||
|
|
||||||
def follow_state(%User{} = user, %User{} = target) do
|
def follow_state(%User{} = user, %User{} = target) do
|
||||||
case Utils.fetch_latest_follow(user, target) do
|
case Utils.fetch_latest_follow(user, target) do
|
||||||
%{data: %{"state" => state}} -> state
|
%{data: %{"state" => state}} -> state
|
||||||
|
|
@ -207,10 +298,6 @@ defmodule Pleroma.User do
|
||||||
Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
|
Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_info_cache(user, args) do
|
|
||||||
Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
|
@spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
|
||||||
def restrict_deactivated(query) do
|
def restrict_deactivated(query) do
|
||||||
from(u in query, where: u.deactivated != ^true)
|
from(u in query, where: u.deactivated != ^true)
|
||||||
|
|
@ -241,7 +328,6 @@ defmodule Pleroma.User do
|
||||||
|
|
||||||
params =
|
params =
|
||||||
params
|
params
|
||||||
|> Map.put(:info, params[:info] || %{})
|
|
||||||
|> truncate_if_exists(:name, name_limit)
|
|> truncate_if_exists(:name, name_limit)
|
||||||
|> truncate_if_exists(:bio, bio_limit)
|
|> truncate_if_exists(:bio, bio_limit)
|
||||||
|> truncate_fields_param()
|
|> truncate_fields_param()
|
||||||
|
|
@ -270,7 +356,9 @@ defmodule Pleroma.User do
|
||||||
:fields,
|
:fields,
|
||||||
:following_count,
|
:following_count,
|
||||||
:discoverable,
|
:discoverable,
|
||||||
:invisible
|
:invisible,
|
||||||
|
:actor_type,
|
||||||
|
:also_known_as
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|> validate_required([:name, :ap_id])
|
|> validate_required([:name, :ap_id])
|
||||||
|
|
@ -312,13 +400,16 @@ defmodule Pleroma.User do
|
||||||
:hide_followers_count,
|
:hide_followers_count,
|
||||||
:hide_follows_count,
|
:hide_follows_count,
|
||||||
:hide_favorites,
|
:hide_favorites,
|
||||||
|
:allow_following_move,
|
||||||
:background,
|
:background,
|
||||||
:show_role,
|
:show_role,
|
||||||
:skip_thread_containment,
|
:skip_thread_containment,
|
||||||
:fields,
|
:fields,
|
||||||
:raw_fields,
|
:raw_fields,
|
||||||
:pleroma_settings_store,
|
:pleroma_settings_store,
|
||||||
:discoverable
|
:discoverable,
|
||||||
|
:actor_type,
|
||||||
|
:also_known_as
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|> unique_constraint(:nickname)
|
|> unique_constraint(:nickname)
|
||||||
|
|
@ -356,9 +447,12 @@ defmodule Pleroma.User do
|
||||||
:hide_follows,
|
:hide_follows,
|
||||||
:fields,
|
:fields,
|
||||||
:hide_followers,
|
:hide_followers,
|
||||||
|
:allow_following_move,
|
||||||
:discoverable,
|
:discoverable,
|
||||||
:hide_followers_count,
|
:hide_followers_count,
|
||||||
:hide_follows_count
|
:hide_follows_count,
|
||||||
|
:actor_type,
|
||||||
|
:also_known_as
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|> unique_constraint(:nickname)
|
|> unique_constraint(:nickname)
|
||||||
|
|
@ -489,6 +583,10 @@ defmodule Pleroma.User do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def try_send_confirmation_email(users) do
|
||||||
|
Enum.each(users, &try_send_confirmation_email/1)
|
||||||
|
end
|
||||||
|
|
||||||
def needs_update?(%User{local: true}), do: false
|
def needs_update?(%User{local: true}), do: false
|
||||||
|
|
||||||
def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
|
def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
|
||||||
|
|
@ -519,14 +617,9 @@ defmodule Pleroma.User do
|
||||||
@doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
|
@doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
|
||||||
@spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
|
@spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
|
||||||
def follow_all(follower, followeds) do
|
def follow_all(follower, followeds) do
|
||||||
followeds =
|
followeds
|
||||||
Enum.reject(followeds, fn followed ->
|
|> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
|
||||||
blocks?(follower, followed) || blocks?(followed, follower)
|
|> Enum.each(&follow(follower, &1, "accept"))
|
||||||
end)
|
|
||||||
|
|
||||||
Enum.each(followeds, &follow(follower, &1, "accept"))
|
|
||||||
|
|
||||||
Enum.each(followeds, &update_follower_count/1)
|
|
||||||
|
|
||||||
set_cache(follower)
|
set_cache(follower)
|
||||||
end
|
end
|
||||||
|
|
@ -546,11 +639,11 @@ defmodule Pleroma.User do
|
||||||
true ->
|
true ->
|
||||||
FollowingRelationship.follow(follower, followed, state)
|
FollowingRelationship.follow(follower, followed, state)
|
||||||
|
|
||||||
follower = maybe_update_following_count(follower)
|
|
||||||
|
|
||||||
{:ok, _} = update_follower_count(followed)
|
{:ok, _} = update_follower_count(followed)
|
||||||
|
|
||||||
set_cache(follower)
|
follower
|
||||||
|
|> update_following_count()
|
||||||
|
|> set_cache()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -558,11 +651,12 @@ defmodule Pleroma.User do
|
||||||
if following?(follower, followed) and follower.ap_id != followed.ap_id do
|
if following?(follower, followed) and follower.ap_id != followed.ap_id do
|
||||||
FollowingRelationship.unfollow(follower, followed)
|
FollowingRelationship.unfollow(follower, followed)
|
||||||
|
|
||||||
follower = maybe_update_following_count(follower)
|
|
||||||
|
|
||||||
{:ok, followed} = update_follower_count(followed)
|
{:ok, followed} = update_follower_count(followed)
|
||||||
|
|
||||||
set_cache(follower)
|
{:ok, follower} =
|
||||||
|
follower
|
||||||
|
|> update_following_count()
|
||||||
|
|> set_cache()
|
||||||
|
|
||||||
{:ok, follower, Utils.fetch_latest_follow(follower, followed)}
|
{:ok, follower, Utils.fetch_latest_follow(follower, followed)}
|
||||||
else
|
else
|
||||||
|
|
@ -612,7 +706,6 @@ defmodule Pleroma.User do
|
||||||
def set_cache(%User{} = user) do
|
def set_cache(%User{} = user) do
|
||||||
Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
|
Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
|
||||||
Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
|
Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
|
||||||
Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
|
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -631,7 +724,6 @@ defmodule Pleroma.User do
|
||||||
def invalidate_cache(user) do
|
def invalidate_cache(user) do
|
||||||
Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
|
Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
|
||||||
Cachex.del(:user_cache, "nickname:#{user.nickname}")
|
Cachex.del(:user_cache, "nickname:#{user.nickname}")
|
||||||
Cachex.del(:user_cache, "user_info:#{user.id}")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_cached_by_ap_id(ap_id) do
|
def get_cached_by_ap_id(ap_id) do
|
||||||
|
|
@ -699,11 +791,6 @@ defmodule Pleroma.User do
|
||||||
get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
|
get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_cached_user_info(user) do
|
|
||||||
key = "user_info:#{user.id}"
|
|
||||||
Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
|
|
||||||
end
|
|
||||||
|
|
||||||
def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
|
def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
|
||||||
|
|
||||||
def get_or_fetch_by_nickname(nickname) do
|
def get_or_fetch_by_nickname(nickname) do
|
||||||
|
|
@ -785,6 +872,13 @@ defmodule Pleroma.User do
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_friends_ap_ids(user) do
|
||||||
|
user
|
||||||
|
|> get_friends_query(nil)
|
||||||
|
|> select([u], u.ap_id)
|
||||||
|
|> Repo.all()
|
||||||
|
end
|
||||||
|
|
||||||
def get_friends_ids(user, page \\ nil) do
|
def get_friends_ids(user, page \\ nil) do
|
||||||
user
|
user
|
||||||
|> get_friends_query(page)
|
|> get_friends_query(page)
|
||||||
|
|
@ -892,8 +986,8 @@ defmodule Pleroma.User do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec maybe_update_following_count(User.t()) :: User.t()
|
@spec update_following_count(User.t()) :: User.t()
|
||||||
def maybe_update_following_count(%User{local: false} = user) do
|
def update_following_count(%User{local: false} = user) do
|
||||||
if Pleroma.Config.get([:instance, :external_user_synchronization]) do
|
if Pleroma.Config.get([:instance, :external_user_synchronization]) do
|
||||||
maybe_fetch_follow_information(user)
|
maybe_fetch_follow_information(user)
|
||||||
else
|
else
|
||||||
|
|
@ -901,7 +995,13 @@ defmodule Pleroma.User do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def maybe_update_following_count(user), do: user
|
def update_following_count(%User{local: true} = user) do
|
||||||
|
following_count = FollowingRelationship.following_count(user)
|
||||||
|
|
||||||
|
user
|
||||||
|
|> follow_information_changeset(%{following_count: following_count})
|
||||||
|
|> Repo.update!()
|
||||||
|
end
|
||||||
|
|
||||||
def set_unread_conversation_count(%User{local: true} = user) do
|
def set_unread_conversation_count(%User{local: true} = user) do
|
||||||
unread_query = Participation.unread_conversation_count_for_user(user)
|
unread_query = Participation.unread_conversation_count_for_user(user)
|
||||||
|
|
@ -959,34 +1059,45 @@ defmodule Pleroma.User do
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
|
@spec mute(User.t(), User.t(), boolean()) ::
|
||||||
def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
|
{:ok, list(UserRelationship.t())} | {:error, String.t()}
|
||||||
add_to_mutes(muter, ap_id, notifications?)
|
def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
|
||||||
|
add_to_mutes(muter, mutee, notifications?)
|
||||||
end
|
end
|
||||||
|
|
||||||
def unmute(muter, %{ap_id: ap_id}) do
|
def unmute(%User{} = muter, %User{} = mutee) do
|
||||||
remove_from_mutes(muter, ap_id)
|
remove_from_mutes(muter, mutee)
|
||||||
end
|
end
|
||||||
|
|
||||||
def subscribe(subscriber, %{ap_id: ap_id}) do
|
def subscribe(%User{} = subscriber, %User{} = target) do
|
||||||
with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
|
|
||||||
deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
|
deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
|
||||||
|
|
||||||
if blocks?(subscribed, subscriber) and deny_follow_blocked do
|
if blocks?(target, subscriber) and deny_follow_blocked do
|
||||||
{:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
|
{:error, "Could not subscribe: #{target.nickname} is blocking you"}
|
||||||
else
|
else
|
||||||
User.add_to_subscribers(subscribed, subscriber.ap_id)
|
# Note: the relationship is inverse: subscriber acts as relationship target
|
||||||
end
|
UserRelationship.create_inverse_subscription(target, subscriber)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
|
def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
|
||||||
|
with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
|
||||||
|
subscribe(subscriber, subscribee)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def unsubscribe(%User{} = unsubscriber, %User{} = target) do
|
||||||
|
# Note: the relationship is inverse: subscriber acts as relationship target
|
||||||
|
UserRelationship.delete_inverse_subscription(target, unsubscriber)
|
||||||
|
end
|
||||||
|
|
||||||
|
def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
|
||||||
with %User{} = user <- get_cached_by_ap_id(ap_id) do
|
with %User{} = user <- get_cached_by_ap_id(ap_id) do
|
||||||
User.remove_from_subscribers(user, unsubscriber.ap_id)
|
unsubscribe(unsubscriber, user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def block(blocker, %User{ap_id: ap_id} = blocked) do
|
def block(%User{} = blocker, %User{} = blocked) do
|
||||||
# sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
|
# sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
|
||||||
blocker =
|
blocker =
|
||||||
if following?(blocker, blocked) do
|
if following?(blocker, blocked) do
|
||||||
|
|
@ -1003,50 +1114,54 @@ defmodule Pleroma.User do
|
||||||
nil -> blocked
|
nil -> blocked
|
||||||
end
|
end
|
||||||
|
|
||||||
blocker =
|
unsubscribe(blocked, blocker)
|
||||||
if subscribed_to?(blocked, blocker) do
|
|
||||||
{:ok, blocker} = unsubscribe(blocked, blocker)
|
|
||||||
blocker
|
|
||||||
else
|
|
||||||
blocker
|
|
||||||
end
|
|
||||||
|
|
||||||
if following?(blocked, blocker), do: unfollow(blocked, blocker)
|
if following?(blocked, blocker), do: unfollow(blocked, blocker)
|
||||||
|
|
||||||
{:ok, blocker} = update_follower_count(blocker)
|
{:ok, blocker} = update_follower_count(blocker)
|
||||||
{:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
|
{:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
|
||||||
add_to_block(blocker, ap_id)
|
add_to_block(blocker, blocked)
|
||||||
end
|
end
|
||||||
|
|
||||||
# helper to handle the block given only an actor's AP id
|
# helper to handle the block given only an actor's AP id
|
||||||
def block(blocker, %{ap_id: ap_id}) do
|
def block(%User{} = blocker, %{ap_id: ap_id}) do
|
||||||
block(blocker, get_cached_by_ap_id(ap_id))
|
block(blocker, get_cached_by_ap_id(ap_id))
|
||||||
end
|
end
|
||||||
|
|
||||||
def unblock(blocker, %{ap_id: ap_id}) do
|
def unblock(%User{} = blocker, %User{} = blocked) do
|
||||||
remove_from_block(blocker, ap_id)
|
remove_from_block(blocker, blocked)
|
||||||
|
end
|
||||||
|
|
||||||
|
# helper to handle the block given only an actor's AP id
|
||||||
|
def unblock(%User{} = blocker, %{ap_id: ap_id}) do
|
||||||
|
unblock(blocker, get_cached_by_ap_id(ap_id))
|
||||||
end
|
end
|
||||||
|
|
||||||
def mutes?(nil, _), do: false
|
def mutes?(nil, _), do: false
|
||||||
def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id)
|
def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
|
||||||
|
|
||||||
|
def mutes_user?(%User{} = user, %User{} = target) do
|
||||||
|
UserRelationship.mute_exists?(user, target)
|
||||||
|
end
|
||||||
|
|
||||||
@spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
|
@spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
|
||||||
def muted_notifications?(nil, _), do: false
|
def muted_notifications?(nil, _), do: false
|
||||||
|
|
||||||
def muted_notifications?(user, %{ap_id: ap_id}),
|
def muted_notifications?(%User{} = user, %User{} = target),
|
||||||
do: Enum.member?(user.muted_notifications, ap_id)
|
do: UserRelationship.notification_mute_exists?(user, target)
|
||||||
|
|
||||||
def blocks?(%User{} = user, %User{} = target) do
|
|
||||||
blocks_ap_id?(user, target) || blocks_domain?(user, target)
|
|
||||||
end
|
|
||||||
|
|
||||||
def blocks?(nil, _), do: false
|
def blocks?(nil, _), do: false
|
||||||
|
|
||||||
def blocks_ap_id?(%User{} = user, %User{} = target) do
|
def blocks?(%User{} = user, %User{} = target) do
|
||||||
Enum.member?(user.blocks, target.ap_id)
|
blocks_user?(user, target) ||
|
||||||
|
(!User.following?(user, target) && blocks_domain?(user, target))
|
||||||
end
|
end
|
||||||
|
|
||||||
def blocks_ap_id?(_, _), do: false
|
def blocks_user?(%User{} = user, %User{} = target) do
|
||||||
|
UserRelationship.block_exists?(user, target)
|
||||||
|
end
|
||||||
|
|
||||||
|
def blocks_user?(_, _), do: false
|
||||||
|
|
||||||
def blocks_domain?(%User{} = user, %User{} = target) do
|
def blocks_domain?(%User{} = user, %User{} = target) do
|
||||||
domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
|
domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
|
||||||
|
|
@ -1056,28 +1171,41 @@ defmodule Pleroma.User do
|
||||||
|
|
||||||
def blocks_domain?(_, _), do: false
|
def blocks_domain?(_, _), do: false
|
||||||
|
|
||||||
def subscribed_to?(user, %{ap_id: ap_id}) do
|
def subscribed_to?(%User{} = user, %User{} = target) do
|
||||||
|
# Note: the relationship is inverse: subscriber acts as relationship target
|
||||||
|
UserRelationship.inverse_subscription_exists?(target, user)
|
||||||
|
end
|
||||||
|
|
||||||
|
def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
|
||||||
with %User{} = target <- get_cached_by_ap_id(ap_id) do
|
with %User{} = target <- get_cached_by_ap_id(ap_id) do
|
||||||
Enum.member?(target.subscribers, user.ap_id)
|
subscribed_to?(user, target)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec muted_users(User.t()) :: [User.t()]
|
@doc """
|
||||||
def muted_users(user) do
|
Returns map of outgoing (blocked, muted etc.) relations' user AP IDs by relation type.
|
||||||
User.Query.build(%{ap_id: user.mutes, deactivated: false})
|
E.g. `outgoing_relations_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
|
||||||
|> Repo.all()
|
"""
|
||||||
end
|
@spec outgoing_relations_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
|
||||||
|
def outgoing_relations_ap_ids(_, []), do: %{}
|
||||||
|
|
||||||
@spec blocked_users(User.t()) :: [User.t()]
|
def outgoing_relations_ap_ids(%User{} = user, relationship_types)
|
||||||
def blocked_users(user) do
|
when is_list(relationship_types) do
|
||||||
User.Query.build(%{ap_id: user.blocks, deactivated: false})
|
db_result =
|
||||||
|
user
|
||||||
|
|> assoc(:outgoing_relationships)
|
||||||
|
|> join(:inner, [user_rel], u in assoc(user_rel, :target))
|
||||||
|
|> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
|
||||||
|
|> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
|
||||||
|
|> group_by([user_rel, u], user_rel.relationship_type)
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
|> Enum.into(%{}, fn [k, v] -> {k, v} end)
|
||||||
|
|
||||||
@spec subscribers(User.t()) :: [User.t()]
|
Enum.into(
|
||||||
def subscribers(user) do
|
relationship_types,
|
||||||
User.Query.build(%{ap_id: user.subscribers, deactivated: false})
|
%{},
|
||||||
|> Repo.all()
|
fn rel_type -> {rel_type, db_result[rel_type] || []} end
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def deactivate_async(user, status \\ true) do
|
def deactivate_async(user, status \\ true) do
|
||||||
|
|
@ -1094,28 +1222,27 @@ defmodule Pleroma.User do
|
||||||
|
|
||||||
def deactivate(%User{} = user, status) do
|
def deactivate(%User{} = user, status) do
|
||||||
with {:ok, user} <- set_activation_status(user, status) do
|
with {:ok, user} <- set_activation_status(user, status) do
|
||||||
Enum.each(get_followers(user), &invalidate_cache/1)
|
user
|
||||||
Enum.each(get_friends(user), &update_follower_count/1)
|
|> get_followers()
|
||||||
|
|> Enum.filter(& &1.local)
|
||||||
|
|> Enum.each(fn follower ->
|
||||||
|
follower |> update_following_count() |> set_cache()
|
||||||
|
end)
|
||||||
|
|
||||||
|
# Only update local user counts, remote will be update during the next pull.
|
||||||
|
user
|
||||||
|
|> get_friends()
|
||||||
|
|> Enum.filter(& &1.local)
|
||||||
|
|> Enum.each(&update_follower_count/1)
|
||||||
|
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_notification_settings(%User{} = user, settings) do
|
def update_notification_settings(%User{} = user, settings) do
|
||||||
settings =
|
|
||||||
settings
|
|
||||||
|> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
|
|
||||||
|> Map.new()
|
|
||||||
|
|
||||||
notification_settings =
|
|
||||||
user.notification_settings
|
|
||||||
|> Map.merge(settings)
|
|
||||||
|> Map.take(["followers", "follows", "non_follows", "non_followers"])
|
|
||||||
|
|
||||||
params = %{notification_settings: notification_settings}
|
|
||||||
|
|
||||||
user
|
user
|
||||||
|> cast(params, [:notification_settings])
|
|> cast(%{notification_settings: settings}, [])
|
||||||
|
|> cast_embed(:notification_settings)
|
||||||
|> validate_required([:notification_settings])
|
|> validate_required([:notification_settings])
|
||||||
|> update_and_set_cache()
|
|> update_and_set_cache()
|
||||||
end
|
end
|
||||||
|
|
@ -1174,7 +1301,7 @@ defmodule Pleroma.User do
|
||||||
blocked_identifiers,
|
blocked_identifiers,
|
||||||
fn blocked_identifier ->
|
fn blocked_identifier ->
|
||||||
with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
|
with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
|
||||||
{:ok, blocker} <- block(blocker, blocked),
|
{:ok, _user_block} <- block(blocker, blocked),
|
||||||
{:ok, _} <- ActivityPub.block(blocker, blocked) do
|
{:ok, _} <- ActivityPub.block(blocker, blocked) do
|
||||||
blocked
|
blocked
|
||||||
else
|
else
|
||||||
|
|
@ -1218,7 +1345,7 @@ defmodule Pleroma.User do
|
||||||
def external_users(opts \\ []) do
|
def external_users(opts \\ []) do
|
||||||
query =
|
query =
|
||||||
external_users_query()
|
external_users_query()
|
||||||
|> select([u], struct(u, [:id, :ap_id, :info]))
|
|> select([u], struct(u, [:id, :ap_id]))
|
||||||
|
|
||||||
query =
|
query =
|
||||||
if opts[:max_id],
|
if opts[:max_id],
|
||||||
|
|
@ -1309,23 +1436,51 @@ defmodule Pleroma.User do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
|
@doc """
|
||||||
def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
|
Creates an internal service actor by URI if missing.
|
||||||
with %User{} = user <- get_cached_by_ap_id(uri) do
|
Optionally takes nickname for addressing.
|
||||||
user
|
"""
|
||||||
else
|
@spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
|
||||||
_ ->
|
def get_or_create_service_actor_by_ap_id(uri, nickname) do
|
||||||
{:ok, user} =
|
{_, user} =
|
||||||
%User{}
|
case get_cached_by_ap_id(uri) do
|
||||||
|> cast(%{}, [:ap_id, :nickname, :local])
|
nil ->
|
||||||
|> put_change(:ap_id, uri)
|
with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
|
||||||
|> put_change(:nickname, nickname)
|
Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
|
||||||
|> put_change(:local, true)
|
{:error, nil}
|
||||||
|> put_change(:follower_address, uri <> "/followers")
|
end
|
||||||
|> Repo.insert()
|
|
||||||
|
%User{invisible: false} = user ->
|
||||||
|
set_invisible(user)
|
||||||
|
|
||||||
|
user ->
|
||||||
|
{:ok, user}
|
||||||
|
end
|
||||||
|
|
||||||
user
|
user
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec set_invisible(User.t()) :: {:ok, User.t()}
|
||||||
|
defp set_invisible(user) do
|
||||||
|
user
|
||||||
|
|> change(%{invisible: true})
|
||||||
|
|> update_and_set_cache()
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec create_service_actor(String.t(), String.t()) ::
|
||||||
|
{:ok, User.t()} | {:error, Ecto.Changeset.t()}
|
||||||
|
defp create_service_actor(uri, nickname) do
|
||||||
|
%User{
|
||||||
|
invisible: true,
|
||||||
|
local: true,
|
||||||
|
ap_id: uri,
|
||||||
|
nickname: nickname,
|
||||||
|
follower_address: uri <> "/followers"
|
||||||
|
}
|
||||||
|
|> change
|
||||||
|
|> unique_constraint(:nickname)
|
||||||
|
|> Repo.insert()
|
||||||
|
|> set_cache()
|
||||||
end
|
end
|
||||||
|
|
||||||
# AP style
|
# AP style
|
||||||
|
|
@ -1357,7 +1512,7 @@ defmodule Pleroma.User do
|
||||||
data
|
data
|
||||||
|> Map.put(:name, blank?(data[:name]) || data[:nickname])
|
|> Map.put(:name, blank?(data[:name]) || data[:nickname])
|
||||||
|> remote_user_creation()
|
|> remote_user_creation()
|
||||||
|> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
|
|> Repo.insert(on_conflict: {:replace_all_except, [:id]}, conflict_target: :nickname)
|
||||||
|> set_cache()
|
|> set_cache()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -1487,7 +1642,7 @@ defmodule Pleroma.User do
|
||||||
end
|
end
|
||||||
|
|
||||||
def showing_reblogs?(%User{} = user, %User{} = target) do
|
def showing_reblogs?(%User{} = user, %User{} = target) do
|
||||||
target.ap_id not in user.muted_reblogs
|
not UserRelationship.reblog_mute_exists?(user, target)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
@ -1567,6 +1722,11 @@ defmodule Pleroma.User do
|
||||||
|> update_and_set_cache()
|
|> update_and_set_cache()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
|
||||||
|
def toggle_confirmation(users) do
|
||||||
|
Enum.map(users, &toggle_confirmation/1)
|
||||||
|
end
|
||||||
|
|
||||||
def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
|
def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
|
||||||
mascot
|
mascot
|
||||||
end
|
end
|
||||||
|
|
@ -1733,6 +1893,12 @@ defmodule Pleroma.User do
|
||||||
|> update_and_set_cache()
|
|> update_and_set_cache()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc "Signs user out of all applications"
|
||||||
|
def global_sign_out(user) do
|
||||||
|
OAuth.Authorization.delete_user_authorizations(user)
|
||||||
|
OAuth.Token.delete_user_tokens(user)
|
||||||
|
end
|
||||||
|
|
||||||
def mascot_update(user, url) do
|
def mascot_update(user, url) do
|
||||||
user
|
user
|
||||||
|> cast(%{mascot: url}, [:mascot])
|
|> cast(%{mascot: url}, [:mascot])
|
||||||
|
|
@ -1805,23 +1971,6 @@ defmodule Pleroma.User do
|
||||||
|> update_and_set_cache()
|
|> update_and_set_cache()
|
||||||
end
|
end
|
||||||
|
|
||||||
defp set_subscribers(user, subscribers) do
|
|
||||||
params = %{subscribers: subscribers}
|
|
||||||
|
|
||||||
user
|
|
||||||
|> cast(params, [:subscribers])
|
|
||||||
|> validate_required([:subscribers])
|
|
||||||
|> update_and_set_cache()
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_to_subscribers(user, subscribed) do
|
|
||||||
set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove_from_subscribers(user, subscribed) do
|
|
||||||
set_subscribers(user, List.delete(user.subscribers, subscribed))
|
|
||||||
end
|
|
||||||
|
|
||||||
defp set_domain_blocks(user, domain_blocks) do
|
defp set_domain_blocks(user, domain_blocks) do
|
||||||
params = %{domain_blocks: domain_blocks}
|
params = %{domain_blocks: domain_blocks}
|
||||||
|
|
||||||
|
|
@ -1839,81 +1988,35 @@ defmodule Pleroma.User do
|
||||||
set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
|
set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp set_blocks(user, blocks) do
|
@spec add_to_block(User.t(), User.t()) ::
|
||||||
params = %{blocks: blocks}
|
{:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
|
||||||
|
defp add_to_block(%User{} = user, %User{} = blocked) do
|
||||||
user
|
UserRelationship.create_block(user, blocked)
|
||||||
|> cast(params, [:blocks])
|
|
||||||
|> validate_required([:blocks])
|
|
||||||
|> update_and_set_cache()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_to_block(user, blocked) do
|
@spec add_to_block(User.t(), User.t()) ::
|
||||||
set_blocks(user, Enum.uniq([blocked | user.blocks]))
|
{:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
|
||||||
|
defp remove_from_block(%User{} = user, %User{} = blocked) do
|
||||||
|
UserRelationship.delete_block(user, blocked)
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_from_block(user, blocked) do
|
defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
|
||||||
set_blocks(user, List.delete(user.blocks, blocked))
|
with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
|
||||||
end
|
{:ok, user_notification_mute} <-
|
||||||
|
(notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
|
||||||
defp set_mutes(user, mutes) do
|
{:ok, nil} do
|
||||||
params = %{mutes: mutes}
|
{:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
|
||||||
|
|
||||||
user
|
|
||||||
|> cast(params, [:mutes])
|
|
||||||
|> validate_required([:mutes])
|
|
||||||
|> update_and_set_cache()
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_to_mutes(user, muted, notifications?) do
|
|
||||||
with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
|
|
||||||
set_notification_mutes(
|
|
||||||
user,
|
|
||||||
Enum.uniq([muted | user.muted_notifications]),
|
|
||||||
notifications?
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_from_mutes(user, muted) do
|
defp remove_from_mutes(user, %User{} = muted_user) do
|
||||||
with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
|
with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
|
||||||
set_notification_mutes(
|
{:ok, user_notification_mute} <-
|
||||||
user,
|
UserRelationship.delete_notification_mute(user, muted_user) do
|
||||||
List.delete(user.muted_notifications, muted),
|
{:ok, [user_mute, user_notification_mute]}
|
||||||
true
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
|
|
||||||
{:ok, user}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
|
|
||||||
params = %{muted_notifications: muted_notifications}
|
|
||||||
|
|
||||||
user
|
|
||||||
|> cast(params, [:muted_notifications])
|
|
||||||
|> validate_required([:muted_notifications])
|
|
||||||
|> update_and_set_cache()
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_reblog_mute(user, ap_id) do
|
|
||||||
params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
|
|
||||||
|
|
||||||
user
|
|
||||||
|> cast(params, [:muted_reblogs])
|
|
||||||
|> update_and_set_cache()
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove_reblog_mute(user, ap_id) do
|
|
||||||
params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
|
|
||||||
|
|
||||||
user
|
|
||||||
|> cast(params, [:muted_reblogs])
|
|
||||||
|> update_and_set_cache()
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_invisible(user, invisible) do
|
def set_invisible(user, invisible) do
|
||||||
params = %{invisible: invisible}
|
params = %{invisible: invisible}
|
||||||
|
|
||||||
|
|
|
||||||
40
lib/pleroma/user/notification_setting.ex
Normal file
40
lib/pleroma/user/notification_setting.ex
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.User.NotificationSetting do
|
||||||
|
use Ecto.Schema
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@derive Jason.Encoder
|
||||||
|
@primary_key false
|
||||||
|
|
||||||
|
embedded_schema do
|
||||||
|
field(:followers, :boolean, default: true)
|
||||||
|
field(:follows, :boolean, default: true)
|
||||||
|
field(:non_follows, :boolean, default: true)
|
||||||
|
field(:non_followers, :boolean, default: true)
|
||||||
|
field(:privacy_option, :boolean, default: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(schema, params) do
|
||||||
|
schema
|
||||||
|
|> cast(prepare_attrs(params), [
|
||||||
|
:followers,
|
||||||
|
:follows,
|
||||||
|
:non_follows,
|
||||||
|
:non_followers,
|
||||||
|
:privacy_option
|
||||||
|
])
|
||||||
|
end
|
||||||
|
|
||||||
|
defp prepare_attrs(params) do
|
||||||
|
Enum.reduce(params, %{}, fn
|
||||||
|
{k, v}, acc when is_binary(v) ->
|
||||||
|
Map.put(acc, k, String.downcase(v))
|
||||||
|
|
||||||
|
{k, v}, acc ->
|
||||||
|
Map.put(acc, k, v)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -45,6 +45,7 @@ defmodule Pleroma.User.Search do
|
||||||
for_user
|
for_user
|
||||||
|> base_query(following)
|
|> base_query(following)
|
||||||
|> filter_blocked_user(for_user)
|
|> filter_blocked_user(for_user)
|
||||||
|
|> filter_invisible_users()
|
||||||
|> filter_blocked_domains(for_user)
|
|> filter_blocked_domains(for_user)
|
||||||
|> fts_search(query_string)
|
|> fts_search(query_string)
|
||||||
|> trigram_rank(query_string)
|
|> trigram_rank(query_string)
|
||||||
|
|
@ -54,15 +55,7 @@ defmodule Pleroma.User.Search do
|
||||||
|> maybe_restrict_local(for_user)
|
|> maybe_restrict_local(for_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
@nickname_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~\-@]+$/
|
|
||||||
defp fts_search(query, query_string) do
|
defp fts_search(query, query_string) do
|
||||||
{nickname_weight, name_weight} =
|
|
||||||
if String.match?(query_string, @nickname_regex) do
|
|
||||||
{"A", "B"}
|
|
||||||
else
|
|
||||||
{"B", "A"}
|
|
||||||
end
|
|
||||||
|
|
||||||
query_string = to_tsquery(query_string)
|
query_string = to_tsquery(query_string)
|
||||||
|
|
||||||
from(
|
from(
|
||||||
|
|
@ -70,12 +63,10 @@ defmodule Pleroma.User.Search do
|
||||||
where:
|
where:
|
||||||
fragment(
|
fragment(
|
||||||
"""
|
"""
|
||||||
(setweight(to_tsvector('simple', ?), ?) || setweight(to_tsvector('simple', ?), ?)) @@ to_tsquery('simple', ?)
|
(to_tsvector('simple', ?) || to_tsvector('simple', ?)) @@ to_tsquery('simple', ?)
|
||||||
""",
|
""",
|
||||||
u.name,
|
u.name,
|
||||||
^name_weight,
|
|
||||||
u.nickname,
|
u.nickname,
|
||||||
^nickname_weight,
|
|
||||||
^query_string
|
^query_string
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -108,9 +99,17 @@ defmodule Pleroma.User.Search do
|
||||||
defp base_query(_user, false), do: User
|
defp base_query(_user, false), do: User
|
||||||
defp base_query(user, true), do: User.get_followers_query(user)
|
defp base_query(user, true), do: User.get_followers_query(user)
|
||||||
|
|
||||||
defp filter_blocked_user(query, %User{blocks: blocks})
|
defp filter_invisible_users(query) do
|
||||||
when length(blocks) > 0 do
|
from(q in query, where: q.invisible == false)
|
||||||
from(q in query, where: not (q.ap_id in ^blocks))
|
end
|
||||||
|
|
||||||
|
defp filter_blocked_user(query, %User{} = blocker) do
|
||||||
|
query
|
||||||
|
|> join(:left, [u], b in Pleroma.UserRelationship,
|
||||||
|
as: :blocks,
|
||||||
|
on: b.relationship_type == ^:block and b.source_id == ^blocker.id and u.id == b.target_id
|
||||||
|
)
|
||||||
|
|> where([blocks: b], is_nil(b.target_id))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp filter_blocked_user(query, _), do: query
|
defp filter_blocked_user(query, _), do: query
|
||||||
|
|
|
||||||
92
lib/pleroma/user_relationship.ex
Normal file
92
lib/pleroma/user_relationship.ex
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.UserRelationship do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.UserRelationship
|
||||||
|
|
||||||
|
schema "user_relationships" do
|
||||||
|
belongs_to(:source, User, type: FlakeId.Ecto.CompatType)
|
||||||
|
belongs_to(:target, User, type: FlakeId.Ecto.CompatType)
|
||||||
|
field(:relationship_type, UserRelationshipTypeEnum)
|
||||||
|
|
||||||
|
timestamps(updated_at: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
for relationship_type <- Keyword.keys(UserRelationshipTypeEnum.__enum_map__()) do
|
||||||
|
# Definitions of `create_block/2`, `create_mute/2` etc.
|
||||||
|
def unquote(:"create_#{relationship_type}")(source, target),
|
||||||
|
do: create(unquote(relationship_type), source, target)
|
||||||
|
|
||||||
|
# Definitions of `delete_block/2`, `delete_mute/2` etc.
|
||||||
|
def unquote(:"delete_#{relationship_type}")(source, target),
|
||||||
|
do: delete(unquote(relationship_type), source, target)
|
||||||
|
|
||||||
|
# Definitions of `block_exists?/2`, `mute_exists?/2` etc.
|
||||||
|
def unquote(:"#{relationship_type}_exists?")(source, target),
|
||||||
|
do: exists?(unquote(relationship_type), source, target)
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(%UserRelationship{} = user_relationship, params \\ %{}) do
|
||||||
|
user_relationship
|
||||||
|
|> cast(params, [:relationship_type, :source_id, :target_id])
|
||||||
|
|> validate_required([:relationship_type, :source_id, :target_id])
|
||||||
|
|> unique_constraint(:relationship_type,
|
||||||
|
name: :user_relationships_source_id_relationship_type_target_id_index
|
||||||
|
)
|
||||||
|
|> validate_not_self_relationship()
|
||||||
|
end
|
||||||
|
|
||||||
|
def exists?(relationship_type, %User{} = source, %User{} = target) do
|
||||||
|
UserRelationship
|
||||||
|
|> where(relationship_type: ^relationship_type, source_id: ^source.id, target_id: ^target.id)
|
||||||
|
|> Repo.exists?()
|
||||||
|
end
|
||||||
|
|
||||||
|
def create(relationship_type, %User{} = source, %User{} = target) do
|
||||||
|
%UserRelationship{}
|
||||||
|
|> changeset(%{
|
||||||
|
relationship_type: relationship_type,
|
||||||
|
source_id: source.id,
|
||||||
|
target_id: target.id
|
||||||
|
})
|
||||||
|
|> Repo.insert(
|
||||||
|
on_conflict: {:replace_all_except, [:id]},
|
||||||
|
conflict_target: [:source_id, :relationship_type, :target_id]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(relationship_type, %User{} = source, %User{} = target) do
|
||||||
|
attrs = %{relationship_type: relationship_type, source_id: source.id, target_id: target.id}
|
||||||
|
|
||||||
|
case Repo.get_by(UserRelationship, attrs) do
|
||||||
|
%UserRelationship{} = existing_record -> Repo.delete(existing_record)
|
||||||
|
nil -> {:ok, nil}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do
|
||||||
|
changeset
|
||||||
|
|> validate_change(:target_id, fn _, target_id ->
|
||||||
|
if target_id == get_field(changeset, :source_id) do
|
||||||
|
[target_id: "can't be equal to source_id"]
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|> validate_change(:source_id, fn _, source_id ->
|
||||||
|
if source_id == get_field(changeset, :target_id) do
|
||||||
|
[source_id: "can't be equal to target_id"]
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
12
lib/pleroma/utils.ex
Normal file
12
lib/pleroma/utils.ex
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Utils do
|
||||||
|
def compile_dir(dir) when is_binary(dir) do
|
||||||
|
dir
|
||||||
|
|> File.ls!()
|
||||||
|
|> Enum.map(&Path.join(dir, &1))
|
||||||
|
|> Kernel.ParallelCompiler.compile()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -322,6 +322,32 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def react_with_emoji(user, object, emoji, options \\ []) do
|
||||||
|
with local <- Keyword.get(options, :local, true),
|
||||||
|
activity_id <- Keyword.get(options, :activity_id, nil),
|
||||||
|
Pleroma.Emoji.is_unicode_emoji?(emoji),
|
||||||
|
reaction_data <- make_emoji_reaction_data(user, object, emoji, activity_id),
|
||||||
|
{:ok, activity} <- insert(reaction_data, local),
|
||||||
|
{:ok, object} <- add_emoji_reaction_to_object(activity, object),
|
||||||
|
:ok <- maybe_federate(activity) do
|
||||||
|
{:ok, activity, object}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def unreact_with_emoji(user, reaction_id, options \\ []) do
|
||||||
|
with local <- Keyword.get(options, :local, true),
|
||||||
|
activity_id <- Keyword.get(options, :activity_id, nil),
|
||||||
|
user_ap_id <- user.ap_id,
|
||||||
|
%Activity{actor: ^user_ap_id} = reaction_activity <- Activity.get_by_ap_id(reaction_id),
|
||||||
|
object <- Object.normalize(reaction_activity),
|
||||||
|
unreact_data <- make_undo_data(user, reaction_activity, activity_id),
|
||||||
|
{:ok, activity} <- insert(unreact_data, local),
|
||||||
|
{:ok, object} <- remove_emoji_reaction_from_object(reaction_activity, object),
|
||||||
|
:ok <- maybe_federate(activity) do
|
||||||
|
{:ok, activity, object}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# TODO: This is weird, maybe we shouldn't check here if we can make the activity.
|
# TODO: This is weird, maybe we shouldn't check here if we can make the activity.
|
||||||
def like(
|
def like(
|
||||||
%User{ap_id: ap_id} = user,
|
%User{ap_id: ap_id} = user,
|
||||||
|
|
@ -430,17 +456,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
user = User.get_cached_by_ap_id(actor)
|
user = User.get_cached_by_ap_id(actor)
|
||||||
to = (object.data["to"] || []) ++ (object.data["cc"] || [])
|
to = (object.data["to"] || []) ++ (object.data["cc"] || [])
|
||||||
|
|
||||||
with {:ok, object, activity} <- Object.delete(object),
|
with create_activity <- Activity.get_create_by_object_ap_id(id),
|
||||||
data <-
|
data <-
|
||||||
%{
|
%{
|
||||||
"type" => "Delete",
|
"type" => "Delete",
|
||||||
"actor" => actor,
|
"actor" => actor,
|
||||||
"object" => id,
|
"object" => id,
|
||||||
"to" => to,
|
"to" => to,
|
||||||
"deleted_activity_id" => activity && activity.id
|
"deleted_activity_id" => create_activity && create_activity.id
|
||||||
}
|
}
|
||||||
|> maybe_put("id", activity_id),
|
|> maybe_put("id", activity_id),
|
||||||
{:ok, activity} <- insert(data, local, false),
|
{:ok, activity} <- insert(data, local, false),
|
||||||
|
{:ok, object, _create_activity} <- Object.delete(object),
|
||||||
stream_out_participations(object, user),
|
stream_out_participations(object, user),
|
||||||
_ <- decrease_replies_count_if_reply(object),
|
_ <- decrease_replies_count_if_reply(object),
|
||||||
{:ok, _actor} <- decrease_note_count_if_public(user, object),
|
{:ok, _actor} <- decrease_note_count_if_public(user, object),
|
||||||
|
|
@ -503,7 +530,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
|
|
||||||
with flag_data <- make_flag_data(params, additional),
|
with flag_data <- make_flag_data(params, additional),
|
||||||
{:ok, activity} <- insert(flag_data, local),
|
{:ok, activity} <- insert(flag_data, local),
|
||||||
:ok <- maybe_federate(activity) do
|
{:ok, stripped_activity} <- strip_report_status_data(activity),
|
||||||
|
:ok <- maybe_federate(stripped_activity) do
|
||||||
Enum.each(User.all_superusers(), fn superuser ->
|
Enum.each(User.all_superusers(), fn superuser ->
|
||||||
superuser
|
superuser
|
||||||
|> Pleroma.Emails.AdminEmail.report(actor, account, statuses, content)
|
|> Pleroma.Emails.AdminEmail.report(actor, account, statuses, content)
|
||||||
|
|
@ -514,6 +542,30 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def move(%User{} = origin, %User{} = target, local \\ true) do
|
||||||
|
params = %{
|
||||||
|
"type" => "Move",
|
||||||
|
"actor" => origin.ap_id,
|
||||||
|
"object" => origin.ap_id,
|
||||||
|
"target" => target.ap_id
|
||||||
|
}
|
||||||
|
|
||||||
|
with true <- origin.ap_id in target.also_known_as,
|
||||||
|
{:ok, activity} <- insert(params, local) do
|
||||||
|
maybe_federate(activity)
|
||||||
|
|
||||||
|
BackgroundWorker.enqueue("move_following", %{
|
||||||
|
"origin_id" => origin.id,
|
||||||
|
"target_id" => target.id
|
||||||
|
})
|
||||||
|
|
||||||
|
{:ok, activity}
|
||||||
|
else
|
||||||
|
false -> {:error, "Target account must have the origin in `alsoKnownAs`"}
|
||||||
|
err -> err
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp fetch_activities_for_context_query(context, opts) do
|
defp fetch_activities_for_context_query(context, opts) do
|
||||||
public = [Pleroma.Constants.as_public()]
|
public = [Pleroma.Constants.as_public()]
|
||||||
|
|
||||||
|
|
@ -567,7 +619,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
|> fetch_activities_query(opts)
|
|> fetch_activities_query(opts)
|
||||||
|> restrict_unlisted()
|
|> restrict_unlisted()
|
||||||
|> Pagination.fetch_paginated(opts, pagination)
|
|> Pagination.fetch_paginated(opts, pagination)
|
||||||
|> Enum.reverse()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@valid_visibilities ~w[direct unlisted public private]
|
@valid_visibilities ~w[direct unlisted public private]
|
||||||
|
|
@ -677,7 +728,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
params
|
params
|
||||||
|> Map.put("user", reading_user)
|
|> Map.put("user", reading_user)
|
||||||
|> Map.put("actor_id", user.ap_id)
|
|> Map.put("actor_id", user.ap_id)
|
||||||
|> Map.put("whole_db", true)
|
|
||||||
|
|
||||||
recipients =
|
recipients =
|
||||||
user_activities_recipients(%{
|
user_activities_recipients(%{
|
||||||
|
|
@ -695,9 +745,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
|> Map.put("type", ["Create", "Announce"])
|
|> Map.put("type", ["Create", "Announce"])
|
||||||
|> Map.put("user", reading_user)
|
|> Map.put("user", reading_user)
|
||||||
|> Map.put("actor_id", user.ap_id)
|
|> Map.put("actor_id", user.ap_id)
|
||||||
|> Map.put("whole_db", true)
|
|
||||||
|> Map.put("pinned_activity_ids", user.pinned_activities)
|
|> Map.put("pinned_activity_ids", user.pinned_activities)
|
||||||
|
|
||||||
|
params =
|
||||||
|
if User.blocks?(reading_user, user) do
|
||||||
|
params
|
||||||
|
else
|
||||||
|
params
|
||||||
|
|> Map.put("blocking_user", reading_user)
|
||||||
|
|> Map.put("muting_user", reading_user)
|
||||||
|
end
|
||||||
|
|
||||||
recipients =
|
recipients =
|
||||||
user_activities_recipients(%{
|
user_activities_recipients(%{
|
||||||
"godmode" => params["godmode"],
|
"godmode" => params["godmode"],
|
||||||
|
|
@ -708,6 +766,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
|> Enum.reverse()
|
|> Enum.reverse()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def fetch_instance_activities(params) do
|
||||||
|
params =
|
||||||
|
params
|
||||||
|
|> Map.put("type", ["Create", "Announce"])
|
||||||
|
|> Map.put("instance", params["instance"])
|
||||||
|
|
||||||
|
fetch_activities([Pleroma.Constants.as_public()], params, :offset)
|
||||||
|
|> Enum.reverse()
|
||||||
|
end
|
||||||
|
|
||||||
defp user_activities_recipients(%{"godmode" => true}) do
|
defp user_activities_recipients(%{"godmode" => true}) do
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
|
|
@ -858,7 +926,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
defp restrict_muted(query, %{"with_muted" => val}) when val in [true, "true", "1"], do: query
|
defp restrict_muted(query, %{"with_muted" => val}) when val in [true, "true", "1"], do: query
|
||||||
|
|
||||||
defp restrict_muted(query, %{"muting_user" => %User{} = user} = opts) do
|
defp restrict_muted(query, %{"muting_user" => %User{} = user} = opts) do
|
||||||
mutes = user.mutes
|
mutes = opts["muted_users_ap_ids"] || User.muted_users_ap_ids(user)
|
||||||
|
|
||||||
query =
|
query =
|
||||||
from([activity] in query,
|
from([activity] in query,
|
||||||
|
|
@ -875,26 +943,42 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
|
|
||||||
defp restrict_muted(query, _), do: query
|
defp restrict_muted(query, _), do: query
|
||||||
|
|
||||||
defp restrict_blocked(query, %{"blocking_user" => %User{} = user}) do
|
defp restrict_blocked(query, %{"blocking_user" => %User{} = user} = opts) do
|
||||||
blocks = user.blocks || []
|
blocked_ap_ids = opts["blocked_users_ap_ids"] || User.blocked_users_ap_ids(user)
|
||||||
domain_blocks = user.domain_blocks || []
|
domain_blocks = user.domain_blocks || []
|
||||||
|
|
||||||
|
following_ap_ids = User.get_friends_ap_ids(user)
|
||||||
|
|
||||||
query =
|
query =
|
||||||
if has_named_binding?(query, :object), do: query, else: Activity.with_joined_object(query)
|
if has_named_binding?(query, :object), do: query, else: Activity.with_joined_object(query)
|
||||||
|
|
||||||
from(
|
from(
|
||||||
[activity, object: o] in query,
|
[activity, object: o] in query,
|
||||||
where: fragment("not (? = ANY(?))", activity.actor, ^blocks),
|
where: fragment("not (? = ANY(?))", activity.actor, ^blocked_ap_ids),
|
||||||
where: fragment("not (? && ?)", activity.recipients, ^blocks),
|
where: fragment("not (? && ?)", activity.recipients, ^blocked_ap_ids),
|
||||||
where:
|
where:
|
||||||
fragment(
|
fragment(
|
||||||
"not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
|
"not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
|
||||||
activity.data,
|
activity.data,
|
||||||
activity.data,
|
activity.data,
|
||||||
^blocks
|
^blocked_ap_ids
|
||||||
),
|
),
|
||||||
where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks),
|
where:
|
||||||
where: fragment("not (split_part(?->>'actor', '/', 3) = ANY(?))", o.data, ^domain_blocks)
|
fragment(
|
||||||
|
"(not (split_part(?, '/', 3) = ANY(?))) or ? = ANY(?)",
|
||||||
|
activity.actor,
|
||||||
|
^domain_blocks,
|
||||||
|
activity.actor,
|
||||||
|
^following_ap_ids
|
||||||
|
),
|
||||||
|
where:
|
||||||
|
fragment(
|
||||||
|
"(not (split_part(?->>'actor', '/', 3) = ANY(?))) or (?->>'actor') = ANY(?)",
|
||||||
|
o.data,
|
||||||
|
^domain_blocks,
|
||||||
|
o.data,
|
||||||
|
^following_ap_ids
|
||||||
|
)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -918,8 +1002,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
|
|
||||||
defp restrict_pinned(query, _), do: query
|
defp restrict_pinned(query, _), do: query
|
||||||
|
|
||||||
defp restrict_muted_reblogs(query, %{"muting_user" => %User{} = user}) do
|
defp restrict_muted_reblogs(query, %{"muting_user" => %User{} = user} = opts) do
|
||||||
muted_reblogs = user.muted_reblogs || []
|
muted_reblogs = opts["reblog_muted_users_ap_ids"] || User.reblog_muted_users_ap_ids(user)
|
||||||
|
|
||||||
from(
|
from(
|
||||||
activity in query,
|
activity in query,
|
||||||
|
|
@ -935,6 +1019,20 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
|
|
||||||
defp restrict_muted_reblogs(query, _), do: query
|
defp restrict_muted_reblogs(query, _), do: query
|
||||||
|
|
||||||
|
defp restrict_instance(query, %{"instance" => instance}) do
|
||||||
|
users =
|
||||||
|
from(
|
||||||
|
u in User,
|
||||||
|
select: u.ap_id,
|
||||||
|
where: fragment("? LIKE ?", u.nickname, ^"%@#{instance}")
|
||||||
|
)
|
||||||
|
|> Repo.all()
|
||||||
|
|
||||||
|
from(activity in query, where: activity.actor in ^users)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_instance(query, _), do: query
|
||||||
|
|
||||||
defp exclude_poll_votes(query, %{"include_poll_votes" => true}), do: query
|
defp exclude_poll_votes(query, %{"include_poll_votes" => true}), do: query
|
||||||
|
|
||||||
defp exclude_poll_votes(query, _) do
|
defp exclude_poll_votes(query, _) do
|
||||||
|
|
@ -967,6 +1065,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
|> Activity.with_preloaded_bookmark(opts["user"])
|
|> Activity.with_preloaded_bookmark(opts["user"])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp maybe_preload_report_notes(query, %{"preload_report_notes" => true}) do
|
||||||
|
query
|
||||||
|
|> Activity.with_preloaded_report_notes()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_preload_report_notes(query, _), do: query
|
||||||
|
|
||||||
defp maybe_set_thread_muted_field(query, %{"skip_preload" => true}), do: query
|
defp maybe_set_thread_muted_field(query, %{"skip_preload" => true}), do: query
|
||||||
|
|
||||||
defp maybe_set_thread_muted_field(query, opts) do
|
defp maybe_set_thread_muted_field(query, opts) do
|
||||||
|
|
@ -986,7 +1091,33 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
|
|
||||||
defp maybe_order(query, _), do: query
|
defp maybe_order(query, _), do: query
|
||||||
|
|
||||||
|
defp fetch_activities_query_ap_ids_ops(opts) do
|
||||||
|
source_user = opts["muting_user"]
|
||||||
|
ap_id_relations = if source_user, do: [:mute, :reblog_mute], else: []
|
||||||
|
|
||||||
|
ap_id_relations =
|
||||||
|
ap_id_relations ++
|
||||||
|
if opts["blocking_user"] && opts["blocking_user"] == source_user do
|
||||||
|
[:block]
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
|
preloaded_ap_ids = User.outgoing_relations_ap_ids(source_user, ap_id_relations)
|
||||||
|
|
||||||
|
restrict_blocked_opts = Map.merge(%{"blocked_users_ap_ids" => preloaded_ap_ids[:block]}, opts)
|
||||||
|
restrict_muted_opts = Map.merge(%{"muted_users_ap_ids" => preloaded_ap_ids[:mute]}, opts)
|
||||||
|
|
||||||
|
restrict_muted_reblogs_opts =
|
||||||
|
Map.merge(%{"reblog_muted_users_ap_ids" => preloaded_ap_ids[:reblog_mute]}, opts)
|
||||||
|
|
||||||
|
{restrict_blocked_opts, restrict_muted_opts, restrict_muted_reblogs_opts}
|
||||||
|
end
|
||||||
|
|
||||||
def fetch_activities_query(recipients, opts \\ %{}) do
|
def fetch_activities_query(recipients, opts \\ %{}) do
|
||||||
|
{restrict_blocked_opts, restrict_muted_opts, restrict_muted_reblogs_opts} =
|
||||||
|
fetch_activities_query_ap_ids_ops(opts)
|
||||||
|
|
||||||
config = %{
|
config = %{
|
||||||
skip_thread_containment: Config.get([:instance, :skip_thread_containment])
|
skip_thread_containment: Config.get([:instance, :skip_thread_containment])
|
||||||
}
|
}
|
||||||
|
|
@ -994,6 +1125,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
Activity
|
Activity
|
||||||
|> maybe_preload_objects(opts)
|
|> maybe_preload_objects(opts)
|
||||||
|> maybe_preload_bookmarks(opts)
|
|> maybe_preload_bookmarks(opts)
|
||||||
|
|> maybe_preload_report_notes(opts)
|
||||||
|> maybe_set_thread_muted_field(opts)
|
|> maybe_set_thread_muted_field(opts)
|
||||||
|> maybe_order(opts)
|
|> maybe_order(opts)
|
||||||
|> restrict_recipients(recipients, opts["user"])
|
|> restrict_recipients(recipients, opts["user"])
|
||||||
|
|
@ -1006,15 +1138,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
|> restrict_type(opts)
|
|> restrict_type(opts)
|
||||||
|> restrict_state(opts)
|
|> restrict_state(opts)
|
||||||
|> restrict_favorited_by(opts)
|
|> restrict_favorited_by(opts)
|
||||||
|> restrict_blocked(opts)
|
|> restrict_blocked(restrict_blocked_opts)
|
||||||
|> restrict_muted(opts)
|
|> restrict_muted(restrict_muted_opts)
|
||||||
|> restrict_media(opts)
|
|> restrict_media(opts)
|
||||||
|> restrict_visibility(opts)
|
|> restrict_visibility(opts)
|
||||||
|> restrict_thread_visibility(opts, config)
|
|> restrict_thread_visibility(opts, config)
|
||||||
|> restrict_replies(opts)
|
|> restrict_replies(opts)
|
||||||
|> restrict_reblogs(opts)
|
|> restrict_reblogs(opts)
|
||||||
|> restrict_pinned(opts)
|
|> restrict_pinned(opts)
|
||||||
|> restrict_muted_reblogs(opts)
|
|> restrict_muted_reblogs(restrict_muted_reblogs_opts)
|
||||||
|
|> restrict_instance(opts)
|
||||||
|> Activity.restrict_deactivated_users()
|
|> Activity.restrict_deactivated_users()
|
||||||
|> exclude_poll_votes(opts)
|
|> exclude_poll_votes(opts)
|
||||||
|> exclude_visibility(opts)
|
|> exclude_visibility(opts)
|
||||||
|
|
@ -1029,6 +1162,25 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
|> maybe_update_cc(list_memberships, opts["user"])
|
|> maybe_update_cc(list_memberships, opts["user"])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Fetch favorites activities of user with order by sort adds to favorites
|
||||||
|
"""
|
||||||
|
@spec fetch_favourites(User.t(), map(), atom()) :: list(Activity.t())
|
||||||
|
def fetch_favourites(user, params \\ %{}, pagination \\ :keyset) do
|
||||||
|
user.ap_id
|
||||||
|
|> Activity.Queries.by_actor()
|
||||||
|
|> Activity.Queries.by_type("Like")
|
||||||
|
|> Activity.with_joined_object()
|
||||||
|
|> Object.with_joined_activity()
|
||||||
|
|> select([_like, object, activity], %{activity | object: object})
|
||||||
|
|> order_by([like, _, _], desc: like.id)
|
||||||
|
|> Pagination.fetch_paginated(
|
||||||
|
Map.merge(params, %{"skip_order" => true}),
|
||||||
|
pagination,
|
||||||
|
:object_activity
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
defp maybe_update_cc(activities, list_memberships, %User{ap_id: user_ap_id})
|
defp maybe_update_cc(activities, list_memberships, %User{ap_id: user_ap_id})
|
||||||
when is_list(list_memberships) and length(list_memberships) > 0 do
|
when is_list(list_memberships) and length(list_memberships) > 0 do
|
||||||
Enum.map(activities, fn
|
Enum.map(activities, fn
|
||||||
|
|
@ -1105,6 +1257,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
data = Transmogrifier.maybe_fix_user_object(data)
|
data = Transmogrifier.maybe_fix_user_object(data)
|
||||||
discoverable = data["discoverable"] || false
|
discoverable = data["discoverable"] || false
|
||||||
invisible = data["invisible"] || false
|
invisible = data["invisible"] || false
|
||||||
|
actor_type = data["type"] || "Person"
|
||||||
|
|
||||||
user_data = %{
|
user_data = %{
|
||||||
ap_id: data["id"],
|
ap_id: data["id"],
|
||||||
|
|
@ -1119,7 +1272,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
name: data["name"],
|
name: data["name"],
|
||||||
follower_address: data["followers"],
|
follower_address: data["followers"],
|
||||||
following_address: data["following"],
|
following_address: data["following"],
|
||||||
bio: data["summary"]
|
bio: data["summary"],
|
||||||
|
actor_type: actor_type,
|
||||||
|
also_known_as: Map.get(data, "alsoKnownAs", [])
|
||||||
}
|
}
|
||||||
|
|
||||||
# nickname can be nil because of virtual actors
|
# nickname can be nil because of virtual actors
|
||||||
|
|
@ -1140,27 +1295,25 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
def fetch_follow_information_for_user(user) do
|
def fetch_follow_information_for_user(user) do
|
||||||
with {:ok, following_data} <-
|
with {:ok, following_data} <-
|
||||||
Fetcher.fetch_and_contain_remote_object_from_id(user.following_address),
|
Fetcher.fetch_and_contain_remote_object_from_id(user.following_address),
|
||||||
following_count when is_integer(following_count) <- following_data["totalItems"],
|
|
||||||
{:ok, hide_follows} <- collection_private(following_data),
|
{:ok, hide_follows} <- collection_private(following_data),
|
||||||
{:ok, followers_data} <-
|
{:ok, followers_data} <-
|
||||||
Fetcher.fetch_and_contain_remote_object_from_id(user.follower_address),
|
Fetcher.fetch_and_contain_remote_object_from_id(user.follower_address),
|
||||||
followers_count when is_integer(followers_count) <- followers_data["totalItems"],
|
|
||||||
{:ok, hide_followers} <- collection_private(followers_data) do
|
{:ok, hide_followers} <- collection_private(followers_data) do
|
||||||
{:ok,
|
{:ok,
|
||||||
%{
|
%{
|
||||||
hide_follows: hide_follows,
|
hide_follows: hide_follows,
|
||||||
follower_count: followers_count,
|
follower_count: normalize_counter(followers_data["totalItems"]),
|
||||||
following_count: following_count,
|
following_count: normalize_counter(following_data["totalItems"]),
|
||||||
hide_followers: hide_followers
|
hide_followers: hide_followers
|
||||||
}}
|
}}
|
||||||
else
|
else
|
||||||
{:error, _} = e ->
|
{:error, _} = e -> e
|
||||||
e
|
e -> {:error, e}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
e ->
|
defp normalize_counter(counter) when is_integer(counter), do: counter
|
||||||
{:error, e}
|
defp normalize_counter(_), do: 0
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp maybe_update_follow_information(data) do
|
defp maybe_update_follow_information(data) do
|
||||||
with {:enabled, true} <-
|
with {:enabled, true} <-
|
||||||
|
|
@ -1181,26 +1334,22 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp collection_private(data) do
|
defp collection_private(%{"first" => %{"type" => type}})
|
||||||
if is_map(data["first"]) and
|
when type in ["CollectionPage", "OrderedCollectionPage"],
|
||||||
data["first"]["type"] in ["CollectionPage", "OrderedCollectionPage"] do
|
do: {:ok, false}
|
||||||
{:ok, false}
|
|
||||||
else
|
defp collection_private(%{"first" => first}) do
|
||||||
with {:ok, %{"type" => type}} when type in ["CollectionPage", "OrderedCollectionPage"] <-
|
with {:ok, %{"type" => type}} when type in ["CollectionPage", "OrderedCollectionPage"] <-
|
||||||
Fetcher.fetch_and_contain_remote_object_from_id(data["first"]) do
|
Fetcher.fetch_and_contain_remote_object_from_id(first) do
|
||||||
{:ok, false}
|
{:ok, false}
|
||||||
else
|
else
|
||||||
{:error, {:ok, %{status: code}}} when code in [401, 403] ->
|
{:error, {:ok, %{status: code}}} when code in [401, 403] -> {:ok, true}
|
||||||
{:ok, true}
|
{:error, _} = e -> e
|
||||||
|
e -> {:error, e}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
{:error, _} = e ->
|
defp collection_private(_data), do: {:ok, true}
|
||||||
e
|
|
||||||
|
|
||||||
e ->
|
|
||||||
{:error, e}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def user_data_from_user_object(data) do
|
def user_data_from_user_object(data) do
|
||||||
with {:ok, data} <- MRF.filter(data),
|
with {:ok, data} <- MRF.filter(data),
|
||||||
|
|
@ -1217,6 +1366,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
data <- maybe_update_follow_information(data) do
|
data <- maybe_update_follow_information(data) do
|
||||||
{:ok, data}
|
{:ok, data}
|
||||||
else
|
else
|
||||||
|
{:error, "Object has been deleted"} = e ->
|
||||||
|
Logger.debug("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
|
||||||
|
{:error, e}
|
||||||
|
|
||||||
e ->
|
e ->
|
||||||
Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
|
Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
|
||||||
{:error, e}
|
{:error, e}
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
end
|
end
|
||||||
|
|
||||||
def user(conn, %{"nickname" => nickname}) do
|
def user(conn, %{"nickname" => nickname}) do
|
||||||
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
with %User{local: true} = user <- User.get_cached_by_nickname(nickname),
|
||||||
{:ok, user} <- User.ensure_keys_present(user) do
|
{:ok, user} <- User.ensure_keys_present(user) do
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/activity+json")
|
|> put_resp_content_type("application/activity+json")
|
||||||
|
|
@ -53,6 +53,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
|> render("user.json", %{user: user})
|
|> render("user.json", %{user: user})
|
||||||
else
|
else
|
||||||
nil -> {:error, :not_found}
|
nil -> {:error, :not_found}
|
||||||
|
%{local: false} -> {:error, :not_found}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -256,7 +257,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
|
|
||||||
# only accept relayed Creates
|
# only accept relayed Creates
|
||||||
def inbox(conn, %{"type" => "Create"} = params) do
|
def inbox(conn, %{"type" => "Create"} = params) do
|
||||||
Logger.info(
|
Logger.debug(
|
||||||
"Signature missing or not from author, relayed Create message, fetching object from source"
|
"Signature missing or not from author, relayed Create message, fetching object from source"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -269,11 +270,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
headers = Enum.into(conn.req_headers, %{})
|
headers = Enum.into(conn.req_headers, %{})
|
||||||
|
|
||||||
if String.contains?(headers["signature"], params["actor"]) do
|
if String.contains?(headers["signature"], params["actor"]) do
|
||||||
Logger.info(
|
Logger.debug(
|
||||||
"Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
|
"Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
|
||||||
)
|
)
|
||||||
|
|
||||||
Logger.info(inspect(conn.req_headers))
|
Logger.debug(inspect(conn.req_headers))
|
||||||
end
|
end
|
||||||
|
|
||||||
json(conn, dgettext("errors", "error"))
|
json(conn, dgettext("errors", "error"))
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.DropPolicy do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def filter(object) do
|
def filter(object) do
|
||||||
Logger.info("REJECTING #{inspect(object)}")
|
Logger.debug("REJECTING #{inspect(object)}")
|
||||||
{:reject, object}
|
{:reject, object}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
|
||||||
]
|
]
|
||||||
|
|
||||||
def perform(:prefetch, url) do
|
def perform(:prefetch, url) do
|
||||||
Logger.info("Prefetching #{inspect(url)}")
|
Logger.debug("Prefetching #{inspect(url)}")
|
||||||
|
|
||||||
url
|
url
|
||||||
|> MediaProxy.url()
|
|> MediaProxy.url()
|
||||||
101
lib/pleroma/web/activity_pub/mrf/object_age_policy.ex
Normal file
101
lib/pleroma/web/activity_pub/mrf/object_age_policy.ex
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.MRF
|
||||||
|
|
||||||
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
@moduledoc "Filter activities depending on their age"
|
||||||
|
@behaviour MRF
|
||||||
|
|
||||||
|
defp check_date(%{"published" => published} = message) do
|
||||||
|
with %DateTime{} = now <- DateTime.utc_now(),
|
||||||
|
{:ok, %DateTime{} = then, _} <- DateTime.from_iso8601(published),
|
||||||
|
max_ttl <- Config.get([:mrf_object_age, :threshold]),
|
||||||
|
{:ttl, false} <- {:ttl, DateTime.diff(now, then) > max_ttl} do
|
||||||
|
{:ok, message}
|
||||||
|
else
|
||||||
|
{:ttl, true} ->
|
||||||
|
{:reject, nil}
|
||||||
|
|
||||||
|
e ->
|
||||||
|
{:error, e}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp check_reject(message, actions) do
|
||||||
|
if :reject in actions do
|
||||||
|
{:reject, nil}
|
||||||
|
else
|
||||||
|
{:ok, message}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp check_delist(message, actions) do
|
||||||
|
if :delist in actions do
|
||||||
|
with %User{} = user <- User.get_cached_by_ap_id(message["actor"]) do
|
||||||
|
to = List.delete(message["to"], Pleroma.Constants.as_public()) ++ [user.follower_address]
|
||||||
|
cc = List.delete(message["cc"], user.follower_address) ++ [Pleroma.Constants.as_public()]
|
||||||
|
|
||||||
|
message =
|
||||||
|
message
|
||||||
|
|> Map.put("to", to)
|
||||||
|
|> Map.put("cc", cc)
|
||||||
|
|
||||||
|
{:ok, message}
|
||||||
|
else
|
||||||
|
# Unhandleable error: somebody is messing around, just drop the message.
|
||||||
|
_e ->
|
||||||
|
{:reject, nil}
|
||||||
|
end
|
||||||
|
else
|
||||||
|
{:ok, message}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp check_strip_followers(message, actions) do
|
||||||
|
if :strip_followers in actions do
|
||||||
|
with %User{} = user <- User.get_cached_by_ap_id(message["actor"]) do
|
||||||
|
to = List.delete(message["to"], user.follower_address)
|
||||||
|
cc = List.delete(message["cc"], user.follower_address)
|
||||||
|
|
||||||
|
message =
|
||||||
|
message
|
||||||
|
|> Map.put("to", to)
|
||||||
|
|> Map.put("cc", cc)
|
||||||
|
|
||||||
|
{:ok, message}
|
||||||
|
else
|
||||||
|
# Unhandleable error: somebody is messing around, just drop the message.
|
||||||
|
_e ->
|
||||||
|
{:reject, nil}
|
||||||
|
end
|
||||||
|
else
|
||||||
|
{:ok, message}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(%{"type" => "Create", "published" => _} = message) do
|
||||||
|
with actions <- Config.get([:mrf_object_age, :actions]),
|
||||||
|
{:reject, _} <- check_date(message),
|
||||||
|
{:ok, message} <- check_reject(message, actions),
|
||||||
|
{:ok, message} <- check_delist(message, actions),
|
||||||
|
{:ok, message} <- check_strip_followers(message, actions) do
|
||||||
|
{:ok, message}
|
||||||
|
else
|
||||||
|
# check_date() is allowed to short-circuit the pipeline
|
||||||
|
e -> e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(message), do: {:ok, message}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe, do: {:ok, %{}}
|
||||||
|
end
|
||||||
|
|
@ -20,7 +20,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do
|
||||||
with accepted_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :accept]),
|
with accepted_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :accept]),
|
||||||
rejected_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :reject]),
|
rejected_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :reject]),
|
||||||
true <-
|
true <-
|
||||||
length(accepted_vocabulary) == 0 || Enum.member?(accepted_vocabulary, message_type),
|
Enum.empty?(accepted_vocabulary) || Enum.member?(accepted_vocabulary, message_type),
|
||||||
false <-
|
false <-
|
||||||
length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type),
|
length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type),
|
||||||
{:ok, _} <- filter(message["object"]) do
|
{:ok, _} <- filter(message["object"]) do
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue