Merge remote-tracking branch 'origin/develop' into shigusegubu-new
This commit is contained in:
commit
db7bca8945
203 changed files with 2904 additions and 1090 deletions
25
.forgejo/issue_template/bug.yaml
Normal file
25
.forgejo/issue_template/bug.yaml
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
name: 'Bug report'
|
||||||
|
about: 'Report a bug in Pleroma'
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
### Precheck
|
||||||
|
|
||||||
|
* For support use https://git.pleroma.social/pleroma/pleroma-support or [community channels](https://git.pleroma.social/pleroma/pleroma#community-channels).
|
||||||
|
* Please do a quick search to ensure no similar bug has been reported before. If the bug has not been addressed after 2 weeks, it's fine to bump it.
|
||||||
|
* Try to ensure that the bug is actually related to the Pleroma backend. For example, if a bug happens in Pleroma-FE but not in Mastodon-FE or mobile clients, it's likely that the bug should be filed in [Pleroma-FE](https://git.pleroma.social/pleroma/pleroma-fe/issues/new) repository.
|
||||||
|
- type: textarea
|
||||||
|
id: environment
|
||||||
|
attributes:
|
||||||
|
label: Environment
|
||||||
|
value: |
|
||||||
|
* Installation type (OTP or From Source):
|
||||||
|
* Pleroma version (could be found in the "Version" tab of settings in Pleroma-FE):
|
||||||
|
* Elixir version (`elixir -v` for from source installations, N/A for OTP):
|
||||||
|
* Operating system:
|
||||||
|
* PostgreSQL version (`psql -V`):
|
||||||
|
- type: textarea
|
||||||
|
id: bug-description
|
||||||
|
attributes:
|
||||||
|
label: Bug description
|
||||||
13
.forgejo/pull_request_template.md
Normal file
13
.forgejo/pull_request_template.md
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
### Checklist
|
||||||
|
|
||||||
|
- [ ] Adding a changelog: In the `changelog.d` directory, create a file named `<code>.<type>`.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
`<code>` can be anything, but we recommend using a more or less unique identifier to avoid collisions, such as the branch name.
|
||||||
|
|
||||||
|
`<type>` can be `add`, `change`, `remove`, `fix`, `security` or `skip`. `skip` is only used if there is no user-visible change in the PR (for example, only editing comments in the code). Otherwise, choose a type that corresponds to your change.
|
||||||
|
|
||||||
|
In the file, write the changelog entry. For example, if a PR adds group functionality, we can create a file named `group.add` and write `Add group functionality` in it.
|
||||||
|
|
||||||
|
If one changelog entry is not enough, you may add more. But that might mean you can split it into two PRs. Only use more than one changelog entry if you really need to (for example, when one change in the code fix two different bugs, or when refactoring).
|
||||||
|
-->
|
||||||
9
.woodpecker/changelog.yaml
Normal file
9
.woodpecker/changelog.yaml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
when:
|
||||||
|
- event: pull_request
|
||||||
|
|
||||||
|
steps:
|
||||||
|
check-changelog:
|
||||||
|
image: docker.io/alpine:3.23
|
||||||
|
commands:
|
||||||
|
- apk add --no-cache git
|
||||||
|
- sh ./tools/check-changelog
|
||||||
65
.woodpecker/lint.yaml
Normal file
65
.woodpecker/lint.yaml
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
when:
|
||||||
|
- event: pull_request
|
||||||
|
path: [ "*.ex", "*.eex", "*.exs", "mix.lock", ".woodpecker/**" ]
|
||||||
|
- event: push
|
||||||
|
branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
path: [ "*.ex", "*.eex", "*.exs", "mix.lock", ".woodpecker/**" ]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
mix-format:
|
||||||
|
image: &elixir-image
|
||||||
|
docker.io/elixir:1.15-alpine
|
||||||
|
failure: ignore
|
||||||
|
commands:
|
||||||
|
- |
|
||||||
|
if ! mix format --check-formatted; then
|
||||||
|
touch fail.stamp
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
credo:
|
||||||
|
image: *elixir-image
|
||||||
|
failure: ignore
|
||||||
|
environment:
|
||||||
|
MIX_ENV: test
|
||||||
|
commands:
|
||||||
|
- apk add --no-cache build-base cmake exiftool ffmpeg file-dev git openssl
|
||||||
|
- adduser -D -h /home/testuser testuser
|
||||||
|
- mkdir -p /home/testuser/.mix /home/testuser/.hex
|
||||||
|
- chown -R testuser:testuser . /home/testuser
|
||||||
|
- su testuser -c "HOME=/home/testuser mix local.hex --force"
|
||||||
|
- su testuser -c "HOME=/home/testuser mix local.rebar --force"
|
||||||
|
- su testuser -c "HOME=/home/testuser mix deps.get"
|
||||||
|
- |
|
||||||
|
if ! su testuser -c "HOME=/home/testuser mix analyze"; then
|
||||||
|
touch fail.stamp
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# cycles:
|
||||||
|
# image: *elixir-image
|
||||||
|
# failure: ignore
|
||||||
|
# commands:
|
||||||
|
# - apk add --no-cache build-base cmake exiftool ffmpeg file-dev git openssl
|
||||||
|
# - adduser -D -h /home/testuser testuser
|
||||||
|
# - mkdir -p /home/testuser/.mix /home/testuser/.hex
|
||||||
|
# - chown -R testuser:testuser . /home/testuser
|
||||||
|
# - su testuser -c "HOME=/home/testuser mix local.hex --force"
|
||||||
|
# - su testuser -c "HOME=/home/testuser mix local.rebar --force"
|
||||||
|
# - su testuser -c "HOME=/home/testuser mix compile"
|
||||||
|
# - |
|
||||||
|
# if ! su testuser -c "HOME=/home/testuser mix xref graph --format cycles --label compile | awk '{print $0} END{exit ($0 != \"No cycles found\")}'"; then
|
||||||
|
# touch fail.stamp
|
||||||
|
# exit 1
|
||||||
|
# fi
|
||||||
|
|
||||||
|
ensure-status:
|
||||||
|
image: *elixir-image
|
||||||
|
commands: |
|
||||||
|
if test -f fail.stamp; then
|
||||||
|
echo "One or more previous steps fails. Failing workflow..."
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "All steps passed"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
34
.woodpecker/unit-testing-elixir-1.15.yaml
Normal file
34
.woodpecker/unit-testing-elixir-1.15.yaml
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
when:
|
||||||
|
- event: pull_request
|
||||||
|
path: [ "*.ex", "*.eex", "*.exs", "mix.lock", ".woodpecker/**" ]
|
||||||
|
- event: push
|
||||||
|
branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
path: [ "*.ex", "*.eex", "*.exs", "mix.lock", ".woodpecker/**" ]
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- lint
|
||||||
|
|
||||||
|
steps:
|
||||||
|
unit-testing-elixir-1.15:
|
||||||
|
image: elixir:1.15-alpine
|
||||||
|
environment:
|
||||||
|
MIX_ENV: test
|
||||||
|
DB_HOST: postgres
|
||||||
|
DB_PORT: 5432
|
||||||
|
commands:
|
||||||
|
- apk add --no-cache build-base cmake exiftool ffmpeg file-dev git openssl
|
||||||
|
- adduser -D -h /home/testuser testuser
|
||||||
|
- mkdir -p /home/testuser/.mix /home/testuser/.hex
|
||||||
|
- chown -R testuser:testuser . /home/testuser
|
||||||
|
- su testuser -c "HOME=/home/testuser mix local.hex --force"
|
||||||
|
- su testuser -c "HOME=/home/testuser mix local.rebar --force"
|
||||||
|
- su testuser -c "HOME=/home/testuser mix deps.get"
|
||||||
|
- su testuser -c "HOME=/home/testuser mix pleroma.test_runner --preload-modules"
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:13-alpine
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: pleroma_test
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
34
.woodpecker/unit-testing-elixir-1.18.yaml
Normal file
34
.woodpecker/unit-testing-elixir-1.18.yaml
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
when:
|
||||||
|
- event: pull_request
|
||||||
|
path: [ "*.ex", "*.eex", "*.exs", "mix.lock", ".woodpecker/**" ]
|
||||||
|
- event: push
|
||||||
|
branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
path: [ "*.ex", "*.eex", "*.exs", "mix.lock", ".woodpecker/**" ]
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- lint
|
||||||
|
|
||||||
|
steps:
|
||||||
|
unit-testing-elixir-1.18:
|
||||||
|
image: elixir:1.18-otp-27-alpine
|
||||||
|
environment:
|
||||||
|
MIX_ENV: test
|
||||||
|
DB_HOST: postgres
|
||||||
|
DB_PORT: 5432
|
||||||
|
commands:
|
||||||
|
- apk add --no-cache build-base cmake exiftool ffmpeg file-dev git openssl
|
||||||
|
- adduser -D -h /home/testuser testuser
|
||||||
|
- mkdir -p /home/testuser/.mix /home/testuser/.hex
|
||||||
|
- chown -R testuser:testuser . /home/testuser
|
||||||
|
- su testuser -c "HOME=/home/testuser mix local.hex --force"
|
||||||
|
- su testuser -c "HOME=/home/testuser mix local.rebar --force"
|
||||||
|
- su testuser -c "HOME=/home/testuser mix deps.get"
|
||||||
|
- su testuser -c "HOME=/home/testuser mix pleroma.test_runner --preload-modules"
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:13-alpine
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: pleroma_test
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<img src="https://git.pleroma.social/pleroma/pleroma/uploads/8cec84f5a084d887339f57deeb8a293e/pleroma-banner-vector-nopad-notext.svg" width="300px" />
|
<img src="https://git.pleroma.social/attachments/06a95f5a-7cac-42ad-8b1d-1483f1739f38" width="300px" />
|
||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
|
|
|
||||||
1
changelog.d/assign-users.add
Normal file
1
changelog.d/assign-users.add
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Allow assigning users to reports
|
||||||
1
changelog.d/avatar-description-mastodon-api.change
Normal file
1
changelog.d/avatar-description-mastodon-api.change
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Move avatar_description and header_description fields to the account object
|
||||||
1
changelog.d/bandit.change
Normal file
1
changelog.d/bandit.change
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Update Bandit to 1.10.4
|
||||||
1
changelog.d/bookmark-folders.ignore
Normal file
1
changelog.d/bookmark-folders.ignore
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Various bookmark folders-related improvements
|
||||||
1
changelog.d/boost-visibilities.add
Normal file
1
changelog.d/boost-visibilities.add
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Allow fine-grained announce visibilities
|
||||||
1
changelog.d/cache-control-immutable.add
Normal file
1
changelog.d/cache-control-immutable.add
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Add immutable tag on cache-control header for several endpoints that's serving the same exact things.
|
||||||
0
changelog.d/credo-aliases-sort-fixes.skip
Normal file
0
changelog.d/credo-aliases-sort-fixes.skip
Normal file
1
changelog.d/database-config-whitelist.add
Normal file
1
changelog.d/database-config-whitelist.add
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Add reasonable defaults for :database_config_whitelist
|
||||||
1
changelog.d/elixir-1.19-cherrypicks.change
Normal file
1
changelog.d/elixir-1.19-cherrypicks.change
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
No-op code correctness improvements detected by Elixir 1.19 compiler
|
||||||
1
changelog.d/email_digest.fix
Normal file
1
changelog.d/email_digest.fix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Fix the daily email digest job which was not executing
|
||||||
1
changelog.d/exclusive-lists.add
Normal file
1
changelog.d/exclusive-lists.add
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Support lists `exclusive` param
|
||||||
1
changelog.d/gopher-genserver-crash-on-boot.fix
Normal file
1
changelog.d/gopher-genserver-crash-on-boot.fix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Gopher: Fix Ranch listener not being stopped properly on Pleroma restart when database configuration is enabled
|
||||||
1
changelog.d/hackney-downgrade.change
Normal file
1
changelog.d/hackney-downgrade.change
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Downgrade Hackney to 1.20.1
|
||||||
1
changelog.d/hubzilla-alsoknownas.fix
Normal file
1
changelog.d/hubzilla-alsoknownas.fix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Fix fetching Hubzilla Actors with alsoKnownAs as string
|
||||||
1
changelog.d/instance-profile-fields.add
Normal file
1
changelog.d/instance-profile-fields.add
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Add /api/v2/instance profile fields limits info used by Mastodon
|
||||||
0
changelog.d/lint-warnings.skip
Normal file
0
changelog.d/lint-warnings.skip
Normal file
1
changelog.d/live-dashboard-redirect.fix
Normal file
1
changelog.d/live-dashboard-redirect.fix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Fix /phoenix/live_dashboard redirect not working when user added a path segment
|
||||||
0
changelog.d/map-side-effects.skip
Normal file
0
changelog.d/map-side-effects.skip
Normal file
1
changelog.d/missing-static-file.fix
Normal file
1
changelog.d/missing-static-file.fix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Fix 404 error codes for missing static files
|
||||||
0
changelog.d/mix-exs-fix.skip
Normal file
0
changelog.d/mix-exs-fix.skip
Normal file
0
changelog.d/mix-exs-update.skip
Normal file
0
changelog.d/mix-exs-update.skip
Normal file
1
changelog.d/old-migrations.fix
Normal file
1
changelog.d/old-migrations.fix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Correct old migrations for expiring activities and user access tokens.
|
||||||
0
changelog.d/plug-test-typo.skip
Normal file
0
changelog.d/plug-test-typo.skip
Normal file
1
changelog.d/poll-voters-count.fix
Normal file
1
changelog.d/poll-voters-count.fix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Federate `votersCount` correctly
|
||||||
1
changelog.d/prune-hashtag-follow-3376.fix
Normal file
1
changelog.d/prune-hashtag-follow-3376.fix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
DB prune: Check if user follows hashtag with no objects before deletion
|
||||||
1
changelog.d/rate-limiter-hardening.fix
Normal file
1
changelog.d/rate-limiter-hardening.fix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Stop the rate limiter from crashing when run with wrong settings.
|
||||||
1
changelog.d/relationship-expires-at.change
Normal file
1
changelog.d/relationship-expires-at.change
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Add mute/block expiry to the relationship object
|
||||||
1
changelog.d/restore-embeds.fix
Normal file
1
changelog.d/restore-embeds.fix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Restore embeds route
|
||||||
1
changelog.d/reverseproxy-recursive-redirect.fix
Normal file
1
changelog.d/reverseproxy-recursive-redirect.fix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
ReverseProxy: Recursively follow redirects until redirect_limit is reached
|
||||||
1
changelog.d/search-indexing.change
Normal file
1
changelog.d/search-indexing.change
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Filter indexable activities before inserting indexing jobs into the queue.
|
||||||
0
changelog.d/search-indexing.skip
Normal file
0
changelog.d/search-indexing.skip
Normal file
0
changelog.d/twitter-api.skip
Normal file
0
changelog.d/twitter-api.skip
Normal file
1
changelog.d/update-comment.ignore
Normal file
1
changelog.d/update-comment.ignore
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Update comment for prepare_object, rename prepare_outgoing
|
||||||
1
changelog.d/user-view.ignore
Normal file
1
changelog.d/user-view.ignore
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Avoid code duplication in UserView
|
||||||
0
changelog.d/woodpecker-pr-pipeline.skip
Normal file
0
changelog.d/woodpecker-pr-pipeline.skip
Normal file
|
|
@ -960,6 +960,15 @@ config :pleroma, Pleroma.Search.QdrantSearch,
|
||||||
vectors: %{size: 384, distance: "Cosine"}
|
vectors: %{size: 384, distance: "Cosine"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config :pleroma, :database_config_whitelist, [
|
||||||
|
{:pleroma},
|
||||||
|
{:cors_plug},
|
||||||
|
{:ex_aws, :s3},
|
||||||
|
{:mime},
|
||||||
|
{:prometheus, Pleroma.Web.Endpoint.MetricsExporter},
|
||||||
|
{:web_push_encryption, :vapid_details}
|
||||||
|
]
|
||||||
|
|
||||||
# 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"
|
||||||
|
|
|
||||||
|
|
@ -169,4 +169,18 @@ This forcibly removes any enabled MRF that does not exist and will fix the abili
|
||||||
=== "From Source"
|
=== "From Source"
|
||||||
```sh
|
```sh
|
||||||
mix pleroma.config fix_mrf_policies
|
mix pleroma.config fix_mrf_policies
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Remove non-whitelisted configs from the database
|
||||||
|
|
||||||
|
This removes any configuration value that is not explicitly whitelisted by `:pleroma, :database_config_whitelist`. Might be useful after updating the whitelist.
|
||||||
|
|
||||||
|
=== "OTP"
|
||||||
|
```sh
|
||||||
|
./bin/pleroma_ctl config filter_whitelisted
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "From Source"
|
||||||
|
```sh
|
||||||
|
mix pleroma.config filter_whitelisted
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -1132,8 +1132,9 @@ Boolean, enables/disables in-database configuration. Read [Transferring the conf
|
||||||
|
|
||||||
List of valid configuration sections which are allowed to be configured from the
|
List of valid configuration sections which are allowed to be configured from the
|
||||||
database. Settings stored in the database before the whitelist is configured are
|
database. Settings stored in the database before the whitelist is configured are
|
||||||
still applied, so it is suggested to only use the whitelist on instances that
|
still applied. Consider running the `mix pleroma.config filter_whitelisted` task
|
||||||
have not migrated the config to the database.
|
after updating the whitelist. Read [Remove non-whitelisted configs from the database](../administration/CLI_tasks/config.md#remove-non-whitelisted-configs-from-the-database)
|
||||||
|
for more information.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
```elixir
|
```elixir
|
||||||
|
|
|
||||||
|
|
@ -665,6 +665,7 @@ Status: 404
|
||||||
- *optional* `limit`: **integer** the number of records to retrieve
|
- *optional* `limit`: **integer** the number of records to retrieve
|
||||||
- *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`)
|
||||||
|
- *optional* `assigned_account`: **string** assigned account ID
|
||||||
- Response:
|
- Response:
|
||||||
- On failure: 403 Forbidden error `{"error": "error_msg"}` when requested by anonymous or non-admin
|
- On failure: 403 Forbidden error `{"error": "error_msg"}` when requested by anonymous or non-admin
|
||||||
- On success: JSON, returns a list of reports, where:
|
- On success: JSON, returns a list of reports, where:
|
||||||
|
|
@ -749,6 +750,7 @@ Status: 404
|
||||||
"url": "https://pleroma.example.org/users/lain",
|
"url": "https://pleroma.example.org/users/lain",
|
||||||
"username": "lain"
|
"username": "lain"
|
||||||
},
|
},
|
||||||
|
"assigned_account": null,
|
||||||
"content": "Please delete it",
|
"content": "Please delete it",
|
||||||
"created_at": "2019-04-29T19:48:15.000Z",
|
"created_at": "2019-04-29T19:48:15.000Z",
|
||||||
"id": "9iJGOv1j8hxuw19bcm",
|
"id": "9iJGOv1j8hxuw19bcm",
|
||||||
|
|
@ -868,6 +870,37 @@ Status: 404
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- Response:
|
||||||
|
- On failure:
|
||||||
|
- 400 Bad Request, JSON:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
`id`, // report id
|
||||||
|
`error` // error message
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
- On success: `204`, empty response
|
||||||
|
|
||||||
|
## `POST /api/v1/pleroma/admin/reports/assign_account`
|
||||||
|
|
||||||
|
### Assign account to one or multiple reports
|
||||||
|
|
||||||
|
- Params:
|
||||||
|
|
||||||
|
```json
|
||||||
|
`reports`: [
|
||||||
|
{
|
||||||
|
`id`, // required, report id
|
||||||
|
`nickname` // account nickname, use null to unassign account
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
- Response:
|
- Response:
|
||||||
- On failure:
|
- On failure:
|
||||||
- 400 Bad Request, JSON:
|
- 400 Bad Request, JSON:
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ Pleroma does not process remote images and therefore cannot include fields such
|
||||||
|
|
||||||
The `GET /api/v1/bookmarks` endpoint accepts optional parameter `folder_id` for bookmark folder ID.
|
The `GET /api/v1/bookmarks` endpoint accepts optional parameter `folder_id` for bookmark folder ID.
|
||||||
|
|
||||||
The `POST /api/v1/statuses/:id/bookmark` endpoint accepts optional parameter `folder_id` for bookmark folder ID.
|
The `POST /api/v1/statuses/:id/bookmark` endpoint accepts optional parameter `folder_id` for bookmark folder ID. Bookmarking an already bookmarked post will update the folder association, or remove it if `folder_id` is omitted or `null`.
|
||||||
|
|
||||||
## Accounts
|
## Accounts
|
||||||
|
|
||||||
|
|
@ -127,8 +127,6 @@ Has these additional fields under the `pleroma` object:
|
||||||
- `notification_settings`: object, can be absent. See `/api/v1/pleroma/notification_settings` for the parameters/keys returned.
|
- `notification_settings`: object, can be absent. See `/api/v1/pleroma/notification_settings` for the parameters/keys returned.
|
||||||
- `accepts_chat_messages`: boolean, but can be null if we don't have that information about a user
|
- `accepts_chat_messages`: boolean, but can be null if we don't have that information about a user
|
||||||
- `favicon`: nullable URL string, Favicon image of the user's instance
|
- `favicon`: nullable URL string, Favicon image of the user's instance
|
||||||
- `avatar_description`: string, image description for user avatar, defaults to empty string
|
|
||||||
- `header_description`: string, image description for user banner, defaults to empty string
|
|
||||||
|
|
||||||
### Source
|
### Source
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -690,6 +690,7 @@ Audio scrobbling in Pleroma is **deprecated**.
|
||||||
* `album`: the album of the media playing [optional]
|
* `album`: the album of the media playing [optional]
|
||||||
* `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]
|
||||||
|
* `external_link`: a URL referencing 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
|
||||||
|
|
|
||||||
|
|
@ -234,6 +234,61 @@ defmodule Mix.Tasks.Pleroma.Config do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Removes non-whitelisted configuration sections
|
||||||
|
def run(["filter_whitelisted" | rest]) do
|
||||||
|
{options, [], []} =
|
||||||
|
OptionParser.parse(
|
||||||
|
rest,
|
||||||
|
strict: [force: :boolean],
|
||||||
|
aliases: [f: :force]
|
||||||
|
)
|
||||||
|
|
||||||
|
force = Keyword.get(options, :force, false)
|
||||||
|
|
||||||
|
start_pleroma()
|
||||||
|
|
||||||
|
whitelisted_configs = Pleroma.Config.get(:database_config_whitelist)
|
||||||
|
|
||||||
|
if whitelisted_configs in [nil, false] do
|
||||||
|
shell_error("No unwanted settings in ConfigDB. No changes made.")
|
||||||
|
else
|
||||||
|
whitelisted_groups =
|
||||||
|
whitelisted_configs
|
||||||
|
|> Enum.filter(fn
|
||||||
|
{_group} -> true
|
||||||
|
_ -> false
|
||||||
|
end)
|
||||||
|
|> Enum.map(fn {group} -> group end)
|
||||||
|
|
||||||
|
whitelisted_keys =
|
||||||
|
whitelisted_configs
|
||||||
|
|> Enum.filter(fn
|
||||||
|
{_group, _key} -> true
|
||||||
|
_ -> false
|
||||||
|
end)
|
||||||
|
|
||||||
|
filtered =
|
||||||
|
from(c in ConfigDB)
|
||||||
|
|> Repo.all()
|
||||||
|
|> Enum.filter(¬_whitelisted?(&1, whitelisted_groups, whitelisted_keys))
|
||||||
|
|
||||||
|
if not Enum.empty?(filtered) do
|
||||||
|
shell_info("The following settings will be removed from ConfigDB:\n")
|
||||||
|
Enum.each(filtered, &dump(&1))
|
||||||
|
|
||||||
|
if force or shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
|
||||||
|
filtered_ids = Enum.map(filtered, fn %{id: id} -> id end)
|
||||||
|
|
||||||
|
Repo.delete_all(from(c in ConfigDB, where: c.id in ^filtered_ids))
|
||||||
|
else
|
||||||
|
shell_error("No changes made.")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
shell_error("No unwanted settings in ConfigDB. No changes made.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@spec migrate_to_db(Path.t() | nil) :: any()
|
@spec migrate_to_db(Path.t() | nil) :: any()
|
||||||
def migrate_to_db(file_path \\ nil) do
|
def migrate_to_db(file_path \\ nil) do
|
||||||
with :ok <- Pleroma.Config.DeprecationWarnings.warn() do
|
with :ok <- Pleroma.Config.DeprecationWarnings.warn() do
|
||||||
|
|
@ -434,4 +489,9 @@ defmodule Mix.Tasks.Pleroma.Config do
|
||||||
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;")
|
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;")
|
||||||
Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;")
|
Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp not_whitelisted?(%{group: group, key: key}, whitelisted_groups, whitelisted_keys) do
|
||||||
|
not Enum.member?(whitelisted_groups, group) and
|
||||||
|
not Enum.member?(whitelisted_keys, {group, key})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -226,7 +226,12 @@ defmodule Mix.Tasks.Pleroma.Database do
|
||||||
DELETE FROM hashtags AS ht
|
DELETE FROM hashtags AS ht
|
||||||
WHERE NOT EXISTS (
|
WHERE NOT EXISTS (
|
||||||
SELECT 1 FROM hashtags_objects hto
|
SELECT 1 FROM hashtags_objects hto
|
||||||
WHERE ht.id = hto.hashtag_id)
|
WHERE ht.id = hto.hashtag_id
|
||||||
|
)
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM user_follows_hashtag ufh
|
||||||
|
WHERE ht.id = ufh.hashtag_id
|
||||||
|
)
|
||||||
"""
|
"""
|
||||||
|> Repo.query()
|
|> Repo.query()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ defmodule Mix.Tasks.Pleroma.OpenapiSpec do
|
||||||
else
|
else
|
||||||
{_, errors} ->
|
{_, errors} ->
|
||||||
IO.puts(IO.ANSI.format([:red, :bright, "Spec check failed, errors:"]))
|
IO.puts(IO.ANSI.format([:red, :bright, "Spec check failed, errors:"]))
|
||||||
Enum.map(errors, &IO.puts/1)
|
Enum.each(errors, &IO.puts/1)
|
||||||
|
|
||||||
raise "Spec check failed"
|
raise "Spec check failed"
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do
|
||||||
query,
|
query,
|
||||||
timeout: :infinity
|
timeout: :infinity
|
||||||
)
|
)
|
||||||
|> Stream.map(&Pleroma.Search.Meilisearch.object_to_search_data/1)
|
|> Stream.map(&Pleroma.Search.object_to_search_data/1)
|
||||||
|> Stream.filter(fn o -> not is_nil(o) end)
|
|> Stream.filter(fn o -> not is_nil(o) end)
|
||||||
|> Stream.chunk_every(chunk_size)
|
|> Stream.chunk_every(chunk_size)
|
||||||
|> Stream.transform(0, fn objects, acc ->
|
|> Stream.transform(0, fn objects, acc ->
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ defmodule Pleroma.Activity.HTML do
|
||||||
|
|
||||||
def invalidate_cache_for(activity_id) do
|
def invalidate_cache_for(activity_id) do
|
||||||
keys = get_cache_keys_for(activity_id)
|
keys = get_cache_keys_for(activity_id)
|
||||||
Enum.map(keys, &@cachex.del(:scrubber_cache, &1))
|
Enum.each(keys, &@cachex.del(:scrubber_cache, &1))
|
||||||
@cachex.del(:scrubber_management_cache, activity_id)
|
@cachex.del(:scrubber_management_cache, activity_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ defmodule Pleroma.Bookmark do
|
||||||
schema "bookmarks" do
|
schema "bookmarks" do
|
||||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||||
belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
|
belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
|
||||||
belongs_to(:folder, BookmarkFolder, type: FlakeId.Ecto.CompatType)
|
belongs_to(:folder, BookmarkFolder, type: FlakeId.Ecto.Type)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
@ -38,7 +38,7 @@ defmodule Pleroma.Bookmark do
|
||||||
|> validate_required([:user_id, :activity_id])
|
|> validate_required([:user_id, :activity_id])
|
||||||
|> unique_constraint(:activity_id, name: :bookmarks_user_id_activity_id_index)
|
|> unique_constraint(:activity_id, name: :bookmarks_user_id_activity_id_index)
|
||||||
|> Repo.insert(
|
|> Repo.insert(
|
||||||
on_conflict: [set: [folder_id: folder_id]],
|
on_conflict: [set: [folder_id: folder_id, updated_at: NaiveDateTime.utc_now()]],
|
||||||
conflict_target: [:user_id, :activity_id]
|
conflict_target: [:user_id, :activity_id]
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
@ -76,11 +76,4 @@ defmodule Pleroma.Bookmark do
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
|> Repo.delete()
|
|> Repo.delete()
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_folder(bookmark, folder_id) do
|
|
||||||
bookmark
|
|
||||||
|> cast(%{folder_id: folder_id}, [:folder_id])
|
|
||||||
|> validate_required([:folder_id])
|
|
||||||
|> Repo.update()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ defmodule Pleroma.BookmarkFolder do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
@type t :: %__MODULE__{}
|
@type t :: %__MODULE__{}
|
||||||
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
|
@primary_key {:id, FlakeId.Ecto.Type, autogenerate: true}
|
||||||
|
|
||||||
schema "bookmark_folders" do
|
schema "bookmark_folders" do
|
||||||
field(:name, :string)
|
field(:name, :string)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.ConfigDB do
|
||||||
import Pleroma.Web.Gettext
|
import Pleroma.Web.Gettext
|
||||||
|
|
||||||
alias __MODULE__
|
alias __MODULE__
|
||||||
|
alias Pleroma.EctoType.Config.RateLimit
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
|
||||||
@type t :: %__MODULE__{}
|
@type t :: %__MODULE__{}
|
||||||
|
|
@ -60,8 +61,59 @@ defmodule Pleroma.ConfigDB do
|
||||||
|> cast(params, [:key, :group, :value])
|
|> cast(params, [:key, :group, :value])
|
||||||
|> validate_required([:key, :group, :value])
|
|> validate_required([:key, :group, :value])
|
||||||
|> unique_constraint(:key, name: :config_group_key_index)
|
|> unique_constraint(:key, name: :config_group_key_index)
|
||||||
|
|> validate_rate_limit()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp validate_rate_limit(changeset) do
|
||||||
|
group = get_field(changeset, :group)
|
||||||
|
key = get_field(changeset, :key)
|
||||||
|
|
||||||
|
if group == :pleroma and key == :rate_limit do
|
||||||
|
value = get_field(changeset, :value)
|
||||||
|
|
||||||
|
case normalize_rate_limit(value) do
|
||||||
|
{:ok, normalized_value} ->
|
||||||
|
put_change(changeset, :value, normalized_value)
|
||||||
|
|
||||||
|
{:error, {limiter_name, reason}} ->
|
||||||
|
add_error(
|
||||||
|
changeset,
|
||||||
|
:value,
|
||||||
|
"invalid :rate_limit value for #{inspect(limiter_name)}: #{reason}"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
changeset
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp normalize_rate_limit(nil), do: {:ok, nil}
|
||||||
|
|
||||||
|
defp normalize_rate_limit(%{} = value), do: normalize_rate_limit(Map.to_list(value))
|
||||||
|
|
||||||
|
defp normalize_rate_limit(value) when is_list(value) do
|
||||||
|
if Keyword.keyword?(value) do
|
||||||
|
value
|
||||||
|
|> Enum.reduce_while({:ok, []}, fn {limiter_name, limiter_value}, {:ok, acc} ->
|
||||||
|
case RateLimit.cast_with_error(limiter_value) do
|
||||||
|
{:ok, normalized_limiter_value} ->
|
||||||
|
{:cont, {:ok, [{limiter_name, normalized_limiter_value} | acc]}}
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
{:halt, {:error, {limiter_name, reason}}}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|> case do
|
||||||
|
{:ok, acc} -> {:ok, Enum.reverse(acc)}
|
||||||
|
{:error, _} = error -> error
|
||||||
|
end
|
||||||
|
else
|
||||||
|
{:error, {:rate_limit, "must be a keyword list"}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp normalize_rate_limit(_), do: {:error, {:rate_limit, "must be a keyword list"}}
|
||||||
|
|
||||||
defp create(params) do
|
defp create(params) do
|
||||||
%ConfigDB{}
|
%ConfigDB{}
|
||||||
|> changeset(params)
|
|> changeset(params)
|
||||||
|
|
|
||||||
|
|
@ -22,13 +22,14 @@ defmodule Pleroma.Constants do
|
||||||
"generator",
|
"generator",
|
||||||
"rules",
|
"rules",
|
||||||
"language",
|
"language",
|
||||||
"voters"
|
"voters",
|
||||||
|
"assigned_account"
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
const(static_only_files,
|
const(static_only_files,
|
||||||
do:
|
do:
|
||||||
~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc embed.js embed.css)
|
~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js schemas doc embed.js embed.css)
|
||||||
)
|
)
|
||||||
|
|
||||||
const(status_updatable_fields,
|
const(status_updatable_fields,
|
||||||
|
|
|
||||||
71
lib/pleroma/ecto_type/config/rate_limit.ex
Normal file
71
lib/pleroma/ecto_type/config/rate_limit.ex
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.EctoType.Config.RateLimit do
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
use Ecto.Type
|
||||||
|
|
||||||
|
@type t ::
|
||||||
|
nil
|
||||||
|
| {non_neg_integer(), non_neg_integer()}
|
||||||
|
| [{non_neg_integer(), non_neg_integer()}]
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def type, do: :term
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def cast(value) do
|
||||||
|
case cast_with_error(value) do
|
||||||
|
{:ok, normalized} -> {:ok, normalized}
|
||||||
|
{:error, _reason} -> :error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def load(value), do: cast(value)
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def dump(value), do: cast(value)
|
||||||
|
|
||||||
|
@spec cast_with_error(term()) :: {:ok, t()} | {:error, String.t()}
|
||||||
|
def cast_with_error(nil), do: {:ok, nil}
|
||||||
|
|
||||||
|
def cast_with_error({scale, limit}) do
|
||||||
|
with {:ok, scale} <- parse_integer(scale, "scale"),
|
||||||
|
{:ok, limit} <- parse_integer(limit, "limit"),
|
||||||
|
true <- scale >= 1 and limit >= 1 do
|
||||||
|
{:ok, {scale, limit}}
|
||||||
|
else
|
||||||
|
false -> {:error, "scale and limit must be >= 1"}
|
||||||
|
{:error, reason} -> {:error, reason}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_with_error([{_, _} = unauth, {_, _} = auth]) do
|
||||||
|
with {:ok, unauth} <- cast_with_error(unauth),
|
||||||
|
{:ok, auth} <- cast_with_error(auth) do
|
||||||
|
{:ok, [unauth, auth]}
|
||||||
|
else
|
||||||
|
{:error, reason} -> {:error, reason}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_with_error(_),
|
||||||
|
do:
|
||||||
|
{:error, "must be a {scale, limit} tuple, a [{scale, limit}, {scale, limit}] list, or nil"}
|
||||||
|
|
||||||
|
defp parse_integer(value, _label) when is_integer(value), do: {:ok, value}
|
||||||
|
|
||||||
|
defp parse_integer(value, label) when is_binary(value) do
|
||||||
|
value = String.trim(value)
|
||||||
|
|
||||||
|
case Integer.parse(value) do
|
||||||
|
{number, ""} -> {:ok, number}
|
||||||
|
_ -> {:error, "#{label} must be an integer"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp parse_integer(_value, label), do: {:error, "#{label} must be an integer"}
|
||||||
|
end
|
||||||
|
|
@ -21,10 +21,13 @@ defmodule Pleroma.Gopher.Server do
|
||||||
|
|
||||||
def init([ip, port]) do
|
def init([ip, port]) do
|
||||||
Logger.info("Starting gopher server on #{port}")
|
Logger.info("Starting gopher server on #{port}")
|
||||||
|
Process.flag(:trap_exit, true)
|
||||||
|
|
||||||
|
listener = :gopher
|
||||||
|
|
||||||
{:ok, _pid} =
|
{:ok, _pid} =
|
||||||
:ranch.start_listener(
|
:ranch.start_listener(
|
||||||
:gopher,
|
listener,
|
||||||
:ranch_tcp,
|
:ranch_tcp,
|
||||||
%{
|
%{
|
||||||
num_acceptors: 100,
|
num_acceptors: 100,
|
||||||
|
|
@ -35,7 +38,11 @@ defmodule Pleroma.Gopher.Server do
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
{:ok, %{ip: ip, port: port}}
|
{:ok, %{ip: ip, port: port, listener: listener}}
|
||||||
|
end
|
||||||
|
|
||||||
|
def terminate(_reason, state) do
|
||||||
|
:ranch.stop_listener(state.listener)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,14 @@ defmodule Pleroma.List do
|
||||||
field(:title, :string)
|
field(:title, :string)
|
||||||
field(:following, {:array, :string}, default: [])
|
field(:following, {:array, :string}, default: [])
|
||||||
field(:ap_id, :string)
|
field(:ap_id, :string)
|
||||||
|
field(:exclusive, :boolean, default: false)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
def title_changeset(list, attrs \\ %{}) do
|
def update_changeset(list, attrs \\ %{}) do
|
||||||
list
|
list
|
||||||
|> cast(attrs, [:title])
|
|> cast(attrs, [:title, :exclusive])
|
||||||
|> validate_required([:title])
|
|> validate_required([:title])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -91,14 +92,14 @@ defmodule Pleroma.List do
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
def rename(%Pleroma.List{} = list, title) do
|
def update(%Pleroma.List{} = list, params) do
|
||||||
list
|
list
|
||||||
|> title_changeset(%{title: title})
|
|> update_changeset(params)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
end
|
||||||
|
|
||||||
def create(title, %User{} = creator) do
|
def create(params, %User{} = creator) do
|
||||||
changeset = title_changeset(%Pleroma.List{user_id: creator.id}, %{title: title})
|
changeset = update_changeset(%Pleroma.List{user_id: creator.id}, params)
|
||||||
|
|
||||||
if changeset.valid? do
|
if changeset.valid? do
|
||||||
Repo.transaction(fn ->
|
Repo.transaction(fn ->
|
||||||
|
|
@ -149,4 +150,14 @@ defmodule Pleroma.List do
|
||||||
end
|
end
|
||||||
|
|
||||||
def member?(_, _), do: false
|
def member?(_, _), do: false
|
||||||
|
|
||||||
|
def get_exclusive_list_members(%User{id: user_id}) do
|
||||||
|
Pleroma.List
|
||||||
|
|> where([l], l.user_id == ^user_id)
|
||||||
|
|> where([l], l.exclusive == true)
|
||||||
|
|> select([l], l.following)
|
||||||
|
|> Repo.all()
|
||||||
|
|> List.flatten()
|
||||||
|
|> Enum.uniq()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ defmodule Pleroma.Marker do
|
||||||
|
|
||||||
defp get_marker(user, timeline) do
|
defp get_marker(user, timeline) do
|
||||||
case Repo.find_resource(get_query(user, timeline)) do
|
case Repo.find_resource(get_query(user, timeline)) do
|
||||||
{:ok, marker} -> %__MODULE__{marker | user: user}
|
{:ok, %__MODULE__{} = marker} -> %{marker | user: user}
|
||||||
_ -> %__MODULE__{timeline: timeline, user_id: user.id}
|
_ -> %__MODULE__{timeline: timeline, user_id: user.id}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@ defmodule Pleroma.MFA.Changeset do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
def disable(%Ecto.Changeset{} = changeset, force \\ false) do
|
def disable(%Ecto.Changeset{} = changeset, force \\ false) do
|
||||||
settings =
|
%Settings{} =
|
||||||
|
settings =
|
||||||
changeset
|
changeset
|
||||||
|> Ecto.Changeset.apply_changes()
|
|> Ecto.Changeset.apply_changes()
|
||||||
|> MFA.fetch_settings()
|
|> MFA.fetch_settings()
|
||||||
|
|
@ -20,20 +21,20 @@ defmodule Pleroma.MFA.Changeset do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def disable_totp(%User{multi_factor_authentication_settings: settings} = user) do
|
def disable_totp(%User{multi_factor_authentication_settings: %Settings{} = settings} = user) do
|
||||||
user
|
user
|
||||||
|> put_change(%Settings{settings | totp: %Settings.TOTP{}})
|
|> put_change(%Settings{settings | totp: %Settings.TOTP{}})
|
||||||
end
|
end
|
||||||
|
|
||||||
def confirm_totp(%User{multi_factor_authentication_settings: settings} = user) do
|
def confirm_totp(%User{multi_factor_authentication_settings: %Settings{} = settings} = user) do
|
||||||
totp_settings = %Settings.TOTP{settings.totp | confirmed: true}
|
totp_settings = %Settings.TOTP{(%Settings.TOTP{} = settings.totp) | confirmed: true}
|
||||||
|
|
||||||
user
|
user
|
||||||
|> put_change(%Settings{settings | totp: totp_settings, enabled: true})
|
|> put_change(%Settings{settings | totp: totp_settings, enabled: true})
|
||||||
end
|
end
|
||||||
|
|
||||||
def setup_totp(%User{} = user, attrs) do
|
def setup_totp(%User{} = user, attrs) do
|
||||||
mfa_settings = MFA.fetch_settings(user)
|
%Settings{} = mfa_settings = MFA.fetch_settings(user)
|
||||||
|
|
||||||
totp_settings =
|
totp_settings =
|
||||||
%Settings.TOTP{}
|
%Settings.TOTP{}
|
||||||
|
|
@ -46,7 +47,7 @@ defmodule Pleroma.MFA.Changeset do
|
||||||
def cast_backup_codes(%User{} = user, codes) do
|
def cast_backup_codes(%User{} = user, codes) do
|
||||||
user
|
user
|
||||||
|> put_change(%Settings{
|
|> put_change(%Settings{
|
||||||
user.multi_factor_authentication_settings
|
(%Settings{} = user.multi_factor_authentication_settings)
|
||||||
| backup_codes: codes
|
| backup_codes: codes
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -132,11 +132,18 @@ defmodule Pleroma.ModerationLog do
|
||||||
end
|
end
|
||||||
|
|
||||||
def insert_log(%{actor: %User{}, action: action, subject: %Activity{} = subject} = attrs)
|
def insert_log(%{actor: %User{}, action: action, subject: %Activity{} = subject} = attrs)
|
||||||
when action in ["report_note_delete", "report_update", "report_note"] do
|
when action in [
|
||||||
|
"report_note_delete",
|
||||||
|
"report_update",
|
||||||
|
"report_note",
|
||||||
|
"report_unassigned",
|
||||||
|
"report_assigned"
|
||||||
|
] do
|
||||||
data =
|
data =
|
||||||
attrs
|
attrs
|
||||||
|> prepare_log_data
|
|> prepare_log_data
|
||||||
|> Pleroma.Maps.put_if_present("text", attrs[:text])
|
|> Pleroma.Maps.put_if_present("text", attrs[:text])
|
||||||
|
|> Pleroma.Maps.put_if_present("assigned_account", attrs[:assigned_account])
|
||||||
|> Map.merge(%{"subject" => report_to_map(subject)})
|
|> Map.merge(%{"subject" => report_to_map(subject)})
|
||||||
|
|
||||||
insert_log_entry_with_message(%ModerationLog{data: data})
|
insert_log_entry_with_message(%ModerationLog{data: data})
|
||||||
|
|
@ -441,6 +448,35 @@ defmodule Pleroma.ModerationLog do
|
||||||
" with '#{state}' state"
|
" with '#{state}' state"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_log_entry_message(
|
||||||
|
%ModerationLog{
|
||||||
|
data: %{
|
||||||
|
"actor" => %{"nickname" => actor_nickname},
|
||||||
|
"action" => "report_assigned",
|
||||||
|
"subject" => %{"id" => subject_id, "type" => "report"},
|
||||||
|
"assigned_account" => assigned_account
|
||||||
|
}
|
||||||
|
} = log
|
||||||
|
) do
|
||||||
|
"@#{actor_nickname} assigned report ##{subject_id}" <>
|
||||||
|
subject_actor_nickname(log, " (on user ", ")") <>
|
||||||
|
" to user #{assigned_account}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_log_entry_message(
|
||||||
|
%ModerationLog{
|
||||||
|
data: %{
|
||||||
|
"actor" => %{"nickname" => actor_nickname},
|
||||||
|
"action" => "report_unassigned",
|
||||||
|
"subject" => %{"id" => subject_id, "type" => "report"}
|
||||||
|
}
|
||||||
|
} = log
|
||||||
|
) do
|
||||||
|
"@#{actor_nickname} unassigned report ##{subject_id}" <>
|
||||||
|
subject_actor_nickname(log, " (on user ", ")") <>
|
||||||
|
" from a user"
|
||||||
|
end
|
||||||
|
|
||||||
def get_log_entry_message(
|
def get_log_entry_message(
|
||||||
%ModerationLog{
|
%ModerationLog{
|
||||||
data: %{
|
data: %{
|
||||||
|
|
|
||||||
|
|
@ -374,10 +374,18 @@ defmodule Pleroma.Object do
|
||||||
|
|
||||||
voters = [actor | object.data["voters"] || []] |> Enum.uniq()
|
voters = [actor | object.data["voters"] || []] |> Enum.uniq()
|
||||||
|
|
||||||
|
voters_count =
|
||||||
|
if Map.has_key?(object.data, "votersCount") do
|
||||||
|
object.data["votersCount"] + 1
|
||||||
|
else
|
||||||
|
length(voters)
|
||||||
|
end
|
||||||
|
|
||||||
data =
|
data =
|
||||||
object.data
|
object.data
|
||||||
|> Map.put(key, options)
|
|> Map.put(key, options)
|
||||||
|> Map.put("voters", voters)
|
|> Map.put("voters", voters)
|
||||||
|
|> Map.put("votersCount", voters_count)
|
||||||
|
|
||||||
object
|
object
|
||||||
|> Object.change(%{data: data})
|
|> Object.change(%{data: data})
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ defmodule Pleroma.ReverseProxy do
|
||||||
@keep_resp_headers @resp_cache_headers ++
|
@keep_resp_headers @resp_cache_headers ++
|
||||||
~w(content-length content-type content-disposition content-encoding) ++
|
~w(content-length content-type content-disposition content-encoding) ++
|
||||||
~w(content-range accept-ranges vary)
|
~w(content-range accept-ranges vary)
|
||||||
@default_cache_control_header "public, max-age=1209600"
|
@default_cache_control_header "public, max-age=1209600, immutable"
|
||||||
@valid_resp_codes [200, 206, 304]
|
@valid_resp_codes [200, 206, 304]
|
||||||
@max_read_duration :timer.seconds(30)
|
@max_read_duration :timer.seconds(30)
|
||||||
@max_body_length :infinity
|
@max_body_length :infinity
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@
|
||||||
|
|
||||||
defmodule Pleroma.ReverseProxy.Client.Hackney do
|
defmodule Pleroma.ReverseProxy.Client.Hackney do
|
||||||
@behaviour Pleroma.ReverseProxy.Client
|
@behaviour Pleroma.ReverseProxy.Client
|
||||||
|
@redirect_limit 5
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
# In-app redirect handler to avoid Hackney redirect bugs:
|
# In-app redirect handler to avoid Hackney redirect bugs:
|
||||||
# - https://github.com/benoitc/hackney/issues/527 (relative/protocol-less redirects can crash Hackney)
|
# - https://github.com/benoitc/hackney/issues/527 (relative/protocol-less redirects can crash Hackney)
|
||||||
|
|
@ -31,26 +34,15 @@ defmodule Pleroma.ReverseProxy.Client.Hackney do
|
||||||
|
|
||||||
if opts[:follow_redirect] != false do
|
if opts[:follow_redirect] != false do
|
||||||
{_state, req_opts} = Access.get_and_update(opts, :follow_redirect, fn a -> {a, false} end)
|
{_state, req_opts} = Access.get_and_update(opts, :follow_redirect, fn a -> {a, false} end)
|
||||||
|
env = %{method: method, headers: headers, body: body, req_opts: req_opts}
|
||||||
res = :hackney.request(method, url, headers, body, req_opts)
|
res = :hackney.request(method, url, headers, body, req_opts)
|
||||||
|
|
||||||
case res do
|
case res do
|
||||||
{:ok, code, resp_headers, _client} when code in @redirect_statuses ->
|
{:ok, code, resp_headers, _client} when code in @redirect_statuses ->
|
||||||
:hackney.request(
|
redirect(url, resp_headers, env, @redirect_limit)
|
||||||
method,
|
|
||||||
absolute_redirect_url(url, resp_headers),
|
|
||||||
headers,
|
|
||||||
body,
|
|
||||||
req_opts
|
|
||||||
)
|
|
||||||
|
|
||||||
{:ok, code, resp_headers} when code in @redirect_statuses ->
|
{:ok, code, resp_headers} when code in @redirect_statuses ->
|
||||||
:hackney.request(
|
redirect(url, resp_headers, env, @redirect_limit)
|
||||||
method,
|
|
||||||
absolute_redirect_url(url, resp_headers),
|
|
||||||
headers,
|
|
||||||
body,
|
|
||||||
req_opts
|
|
||||||
)
|
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
res
|
res
|
||||||
|
|
@ -71,4 +63,32 @@ defmodule Pleroma.ReverseProxy.Client.Hackney do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def close(ref), do: :hackney.close(ref)
|
def close(ref), do: :hackney.close(ref)
|
||||||
|
|
||||||
|
defp redirect(url, resp_headers, env, limit) when limit == 0 do
|
||||||
|
new_url = absolute_redirect_url(url, resp_headers)
|
||||||
|
|
||||||
|
Logger.debug(
|
||||||
|
"#{__MODULE__}: Handling redirect #{url} -> #{new_url}; redirect limit was reached - returning response after final redirect"
|
||||||
|
)
|
||||||
|
|
||||||
|
:hackney.request(env.method, new_url, env.headers, env.body, env.req_opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp redirect(url, resp_headers, env, limit) do
|
||||||
|
new_url = absolute_redirect_url(url, resp_headers)
|
||||||
|
Logger.debug("#{__MODULE__}: handling redirect #{url} -> #{new_url}; limit = #{limit}")
|
||||||
|
|
||||||
|
res = :hackney.request(env.method, new_url, env.headers, env.body, env.req_opts)
|
||||||
|
|
||||||
|
case res do
|
||||||
|
{:ok, code, new_resp_headers, _client} when code in @redirect_statuses ->
|
||||||
|
redirect(new_url, new_resp_headers, env, limit - 1)
|
||||||
|
|
||||||
|
{:ok, code, new_resp_headers} when code in @redirect_statuses ->
|
||||||
|
redirect(new_url, new_resp_headers, env, limit - 1)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
res
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,28 @@
|
||||||
defmodule Pleroma.Search do
|
defmodule Pleroma.Search do
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
alias Pleroma.Workers.SearchIndexingWorker
|
alias Pleroma.Workers.SearchIndexingWorker
|
||||||
|
|
||||||
def add_to_index(%Pleroma.Activity{id: activity_id}) do
|
@spec add_to_index(Activity.t()) :: {:ok, Oban.Job.t() | :noop} | {:error, Oban.Job.changeset()}
|
||||||
SearchIndexingWorker.new(%{"op" => "add_to_index", "activity" => activity_id})
|
def add_to_index(%Activity{id: activity_id, object: %Object{} = object} = activity) do
|
||||||
|> Oban.insert()
|
with {_, true} <- {:indexable, indexable?(activity)},
|
||||||
|
{_, "public"} <- {:visibility, Visibility.get_visibility(object)} do
|
||||||
|
SearchIndexingWorker.new(%{"op" => "add_to_index", "activity" => activity_id})
|
||||||
|
|> Oban.insert()
|
||||||
|
else
|
||||||
|
_ -> {:ok, :noop}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def add_to_index(%Activity{id: activity_id}) do
|
||||||
|
case Activity.get_by_id_with_object(activity_id) do
|
||||||
|
%Activity{} = preloaded -> add_to_index(preloaded)
|
||||||
|
_ -> {:ok, :noop}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec remove_from_index(Object.t()) :: {:ok, Oban.Job.t()} | {:error, Oban.Job.changeset()}
|
||||||
def remove_from_index(%Pleroma.Object{id: object_id}) do
|
def remove_from_index(%Pleroma.Object{id: object_id}) do
|
||||||
SearchIndexingWorker.new(%{"op" => "remove_from_index", "object" => object_id})
|
SearchIndexingWorker.new(%{"op" => "remove_from_index", "object" => object_id})
|
||||||
|> Oban.insert()
|
|> Oban.insert()
|
||||||
|
|
@ -20,4 +37,44 @@ defmodule Pleroma.Search do
|
||||||
search_module = Pleroma.Config.get([Pleroma.Search, :module])
|
search_module = Pleroma.Config.get([Pleroma.Search, :module])
|
||||||
search_module.healthcheck_endpoints()
|
search_module.healthcheck_endpoints()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def object_to_search_data(%Object{} = object) do
|
||||||
|
data = object.data
|
||||||
|
|
||||||
|
content_str =
|
||||||
|
case data["content"] do
|
||||||
|
[nil | rest] -> to_string(rest)
|
||||||
|
str -> str
|
||||||
|
end
|
||||||
|
|
||||||
|
content =
|
||||||
|
with {:ok, scrubbed} <-
|
||||||
|
FastSanitize.Sanitizer.scrub(content_str, Pleroma.HTML.Scrubber.SearchIndexing),
|
||||||
|
trimmed <- String.trim(scrubbed) do
|
||||||
|
trimmed
|
||||||
|
end
|
||||||
|
|
||||||
|
# Make sure we have a non-empty string
|
||||||
|
if content != "" do
|
||||||
|
{:ok, published, _} = DateTime.from_iso8601(data["published"])
|
||||||
|
|
||||||
|
%{
|
||||||
|
id: object.id,
|
||||||
|
content: content,
|
||||||
|
ap: data["id"],
|
||||||
|
published: published |> DateTime.to_unix()
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp indexable?(%Activity{
|
||||||
|
data: %{"type" => "Create"},
|
||||||
|
object: %Object{
|
||||||
|
data: %{"content" => content, "published" => published, "type" => "Note"}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
when not is_nil(content) and content not in ["", "."] and not is_nil(published),
|
||||||
|
do: true
|
||||||
|
|
||||||
|
defp indexable?(_), do: false
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ defmodule Pleroma.Search.Meilisearch do
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Config.Getting, as: Config
|
alias Pleroma.Config.Getting, as: Config
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Search
|
||||||
|
|
||||||
import Pleroma.Search.DatabaseSearch
|
import Pleroma.Search.DatabaseSearch
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
@ -118,66 +120,24 @@ defmodule Pleroma.Search.Meilisearch do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def object_to_search_data(object) do
|
|
||||||
# Only index public or unlisted Notes
|
|
||||||
if not is_nil(object) and object.data["type"] == "Note" and
|
|
||||||
not is_nil(object.data["content"]) and
|
|
||||||
not is_nil(object.data["published"]) and
|
|
||||||
(Pleroma.Constants.as_public() in object.data["to"] or
|
|
||||||
Pleroma.Constants.as_public() in object.data["cc"]) and
|
|
||||||
object.data["content"] not in ["", "."] do
|
|
||||||
data = object.data
|
|
||||||
|
|
||||||
content_str =
|
|
||||||
case data["content"] do
|
|
||||||
[nil | rest] -> to_string(rest)
|
|
||||||
str -> str
|
|
||||||
end
|
|
||||||
|
|
||||||
content =
|
|
||||||
with {:ok, scrubbed} <-
|
|
||||||
FastSanitize.Sanitizer.scrub(content_str, Pleroma.HTML.Scrubber.SearchIndexing),
|
|
||||||
trimmed <- String.trim(scrubbed) do
|
|
||||||
trimmed
|
|
||||||
end
|
|
||||||
|
|
||||||
# Make sure we have a non-empty string
|
|
||||||
if content != "" do
|
|
||||||
{:ok, published, _} = DateTime.from_iso8601(data["published"])
|
|
||||||
|
|
||||||
%{
|
|
||||||
id: object.id,
|
|
||||||
content: content,
|
|
||||||
ap: data["id"],
|
|
||||||
published: published |> DateTime.to_unix()
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def add_to_index(activity) do
|
def add_to_index(%Activity{object: %Object{} = object} = activity) do
|
||||||
maybe_search_data = object_to_search_data(activity.object)
|
search_data = Search.object_to_search_data(object)
|
||||||
|
|
||||||
if activity.data["type"] == "Create" and maybe_search_data do
|
result =
|
||||||
result =
|
meili_put(
|
||||||
meili_put(
|
"/indexes/objects/documents",
|
||||||
"/indexes/objects/documents",
|
[search_data]
|
||||||
[maybe_search_data]
|
)
|
||||||
)
|
|
||||||
|
|
||||||
with {:ok, %{"status" => "enqueued"}} <- result do
|
with {:ok, %{"status" => "enqueued"}} <- result do
|
||||||
# Added successfully
|
# Added successfully
|
||||||
:ok
|
|
||||||
else
|
|
||||||
_ ->
|
|
||||||
# There was an error, report it
|
|
||||||
Logger.error("Failed to add activity #{activity.id} to index: #{inspect(result)}")
|
|
||||||
{:error, result}
|
|
||||||
end
|
|
||||||
else
|
|
||||||
# The post isn't something we can search, that's ok
|
|
||||||
:ok
|
:ok
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
# There was an error, report it
|
||||||
|
Logger.error("Failed to add activity #{activity.id} to index: #{inspect(result)}")
|
||||||
|
{:error, result}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,12 @@ defmodule Pleroma.Search.QdrantSearch do
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Config.Getting, as: Config
|
alias Pleroma.Config.Getting, as: Config
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Search
|
||||||
|
|
||||||
alias __MODULE__.OpenAIClient
|
alias __MODULE__.OpenAIClient
|
||||||
alias __MODULE__.QdrantClient
|
alias __MODULE__.QdrantClient
|
||||||
|
|
||||||
import Pleroma.Search.Meilisearch, only: [object_to_search_data: 1]
|
|
||||||
import Pleroma.Search.DatabaseSearch, only: [maybe_fetch: 3]
|
import Pleroma.Search.DatabaseSearch, only: [maybe_fetch: 3]
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
|
|
@ -82,23 +83,18 @@ defmodule Pleroma.Search.QdrantSearch do
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def add_to_index(activity) do
|
def add_to_index(%Activity{object: %Object{} = object} = activity) do
|
||||||
# This will only index public or unlisted notes
|
search_data = Search.object_to_search_data(object)
|
||||||
maybe_search_data = object_to_search_data(activity.object)
|
|
||||||
|
|
||||||
if activity.data["type"] == "Create" and maybe_search_data do
|
with {:ok, embedding} <- get_embedding(search_data.content),
|
||||||
with {:ok, embedding} <- get_embedding(maybe_search_data.content),
|
{:ok, %{status: 200}} <-
|
||||||
{:ok, %{status: 200}} <-
|
QdrantClient.put(
|
||||||
QdrantClient.put(
|
"/collections/posts/points",
|
||||||
"/collections/posts/points",
|
build_index_payload(activity, embedding)
|
||||||
build_index_payload(activity, embedding)
|
) do
|
||||||
) do
|
|
||||||
:ok
|
|
||||||
else
|
|
||||||
e -> {:error, e}
|
|
||||||
end
|
|
||||||
else
|
|
||||||
:ok
|
:ok
|
||||||
|
else
|
||||||
|
e -> {:error, e}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ defmodule Pleroma.Upload do
|
||||||
def store(upload, opts \\ []) do
|
def store(upload, opts \\ []) do
|
||||||
opts = get_opts(opts)
|
opts = get_opts(opts)
|
||||||
|
|
||||||
with {:ok, upload} <- prepare_upload(upload, opts),
|
with {:ok, %__MODULE__{} = upload} <- prepare_upload(upload, opts),
|
||||||
upload = %__MODULE__{upload | path: upload.path || "#{upload.id}/#{upload.name}"},
|
upload = %__MODULE__{upload | path: upload.path || "#{upload.id}/#{upload.name}"},
|
||||||
{:ok, upload} <- Pleroma.Upload.Filter.filter(opts.filters, upload),
|
{:ok, upload} <- Pleroma.Upload.Filter.filter(opts.filters, upload),
|
||||||
description = get_description(upload),
|
description = get_description(upload),
|
||||||
|
|
|
||||||
|
|
@ -342,7 +342,7 @@ defmodule Pleroma.User.Backup do
|
||||||
dir,
|
dir,
|
||||||
"outbox",
|
"outbox",
|
||||||
fn a ->
|
fn a ->
|
||||||
with {:ok, activity} <- Transmogrifier.prepare_outgoing(a.data) do
|
with {:ok, activity} <- Transmogrifier.prepare_activity(a.data) do
|
||||||
{:ok, Map.delete(activity, "@context")}
|
{:ok, Map.delete(activity, "@context")}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ defmodule Pleroma.UserRelationship do
|
||||||
do: exists?(unquote(relationship_type), source, target)
|
do: exists?(unquote(relationship_type), source, target)
|
||||||
|
|
||||||
# `def get_block_expire_date/2`, `def get_mute_expire_date/2`,
|
# `def get_block_expire_date/2`, `def get_mute_expire_date/2`,
|
||||||
# `def get_reblog_mute_expire_date/2`, `def get_notification_mute_exists?/2`,
|
# `def get_reblog_mute_expire_date/2`, `def get_notification_mute_expire_date/2`,
|
||||||
# `def get_inverse_subscription_expire_date/2`, `def get_inverse_endorsement_expire_date/2`
|
# `def get_inverse_subscription_expire_date/2`, `def get_inverse_endorsement_expire_date/2`
|
||||||
def unquote(:"get_#{relationship_type}_expire_date")(source, target),
|
def unquote(:"get_#{relationship_type}_expire_date")(source, target),
|
||||||
do: get_expire_date(unquote(relationship_type), source, target)
|
do: get_expire_date(unquote(relationship_type), source, target)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ defmodule Pleroma.Utils do
|
||||||
dir
|
dir
|
||||||
|> File.ls!()
|
|> File.ls!()
|
||||||
|> Enum.map(&Path.join(dir, &1))
|
|> Enum.map(&Path.join(dir, &1))
|
||||||
|> Kernel.ParallelCompiler.compile()
|
|> Kernel.ParallelCompiler.compile(return_diagnostics: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
|
||||||
|
|
@ -1003,6 +1003,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
|
|
||||||
defp restrict_state(query, _), do: query
|
defp restrict_state(query, _), do: query
|
||||||
|
|
||||||
|
defp restrict_assigned_account(query, %{assigned_account: assigned_account}) do
|
||||||
|
from(activity in query,
|
||||||
|
where: fragment("?->>'assigned_account' = ?", activity.data, ^assigned_account)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_assigned_account(query, _), do: query
|
||||||
|
|
||||||
defp restrict_favorited_by(query, %{favorited_by: ap_id}) do
|
defp restrict_favorited_by(query, %{favorited_by: ap_id}) do
|
||||||
from(
|
from(
|
||||||
[_activity, object] in query,
|
[_activity, object] in query,
|
||||||
|
|
@ -1471,6 +1479,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
|> restrict_actor(opts)
|
|> restrict_actor(opts)
|
||||||
|> restrict_type(opts)
|
|> restrict_type(opts)
|
||||||
|> restrict_state(opts)
|
|> restrict_state(opts)
|
||||||
|
|> restrict_assigned_account(opts)
|
||||||
|> restrict_favorited_by(opts)
|
|> restrict_favorited_by(opts)
|
||||||
|> restrict_blocked(restrict_blocked_opts)
|
|> restrict_blocked(restrict_blocked_opts)
|
||||||
|> restrict_blockers_visibility(opts)
|
|> restrict_blockers_visibility(opts)
|
||||||
|
|
@ -1609,6 +1618,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
defp normalize_image(urls) when is_list(urls), do: urls |> List.first() |> normalize_image()
|
defp normalize_image(urls) when is_list(urls), do: urls |> List.first() |> normalize_image()
|
||||||
defp normalize_image(_), do: nil
|
defp normalize_image(_), do: nil
|
||||||
|
|
||||||
|
defp normalize_also_known_as(urls) when is_list(urls), do: urls
|
||||||
|
defp normalize_also_known_as(url) when is_binary(url), do: [url]
|
||||||
|
defp normalize_also_known_as(nil), do: []
|
||||||
|
|
||||||
defp maybe_put_description(map, %{"name" => description}) when is_binary(description) do
|
defp maybe_put_description(map, %{"name" => description}) when is_binary(description) do
|
||||||
Map.put(map, "name", description)
|
Map.put(map, "name", description)
|
||||||
end
|
end
|
||||||
|
|
@ -1684,7 +1697,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
featured_address: featured_address,
|
featured_address: featured_address,
|
||||||
bio: data["summary"] || "",
|
bio: data["summary"] || "",
|
||||||
actor_type: actor_type,
|
actor_type: actor_type,
|
||||||
also_known_as: Map.get(data, "alsoKnownAs", []),
|
also_known_as: normalize_also_known_as(data["alsoKnownAs"]),
|
||||||
public_key: public_key,
|
public_key: public_key,
|
||||||
inbox: data["inbox"],
|
inbox: data["inbox"],
|
||||||
shared_inbox: shared_inbox,
|
shared_inbox: shared_inbox,
|
||||||
|
|
|
||||||
|
|
@ -332,21 +332,18 @@ defmodule Pleroma.Web.ActivityPub.Builder do
|
||||||
|
|
||||||
@spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
|
@spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
|
||||||
def announce(actor, object, options \\ []) do
|
def announce(actor, object, options \\ []) do
|
||||||
public? = Keyword.get(options, :public, false)
|
visibility = Keyword.get(options, :visibility, "public")
|
||||||
|
|
||||||
to =
|
{to, cc} =
|
||||||
cond do
|
if actor.ap_id == Relay.ap_id() do
|
||||||
actor.ap_id == Relay.ap_id() ->
|
{[actor.follower_address], []}
|
||||||
[actor.follower_address]
|
else
|
||||||
|
Pleroma.Web.CommonAPI.Utils.get_to_and_cc_for_visibility(
|
||||||
public? and Visibility.local_public?(object) ->
|
visibility,
|
||||||
[actor.follower_address, object.data["actor"], Utils.as_local_public()]
|
actor.follower_address,
|
||||||
|
nil,
|
||||||
public? ->
|
[object.data["actor"]]
|
||||||
[actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()]
|
)
|
||||||
|
|
||||||
true ->
|
|
||||||
[actor.follower_address, object.data["actor"]]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
{:ok,
|
{:ok,
|
||||||
|
|
@ -355,6 +352,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
|
||||||
"actor" => actor.ap_id,
|
"actor" => actor.ap_id,
|
||||||
"object" => object.data["id"],
|
"object" => object.data["id"],
|
||||||
"to" => to,
|
"to" => to,
|
||||||
|
"cc" => cc,
|
||||||
"context" => object.data["context"],
|
"context" => object.data["context"],
|
||||||
"type" => "Announce",
|
"type" => "Announce",
|
||||||
"published" => Utils.make_date()
|
"published" => Utils.make_date()
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
|
||||||
end
|
end
|
||||||
|
|
||||||
field(:closed, ObjectValidators.DateTime)
|
field(:closed, ObjectValidators.DateTime)
|
||||||
|
field(:votersCount, :integer)
|
||||||
field(:voters, {:array, ObjectValidators.ObjectID}, default: [])
|
field(:voters, {:array, ObjectValidators.ObjectID}, default: [])
|
||||||
field(:nonAnonymous, :boolean)
|
field(:nonAnonymous, :boolean)
|
||||||
embeds_many(:anyOf, QuestionOptionsValidator)
|
embeds_many(:anyOf, QuestionOptionsValidator)
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
||||||
Determine if an activity can be represented by running it through Transmogrifier.
|
Determine if an activity can be represented by running it through Transmogrifier.
|
||||||
"""
|
"""
|
||||||
def representable?(%Activity{} = activity) do
|
def representable?(%Activity{} = activity) do
|
||||||
with {:ok, _data} <- @transmogrifier_impl.prepare_outgoing(activity.data) do
|
with {:ok, _data} <- @transmogrifier_impl.prepare_activity(activity.data) do
|
||||||
true
|
true
|
||||||
else
|
else
|
||||||
_e ->
|
_e ->
|
||||||
|
|
@ -102,14 +102,14 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
||||||
Logger.debug("Federating #{ap_id} to #{inbox}")
|
Logger.debug("Federating #{ap_id} to #{inbox}")
|
||||||
uri = %{path: path} = URI.parse(inbox)
|
uri = %{path: path} = URI.parse(inbox)
|
||||||
|
|
||||||
{:ok, data} = @transmogrifier_impl.prepare_outgoing(activity.data)
|
{:ok, data} = @transmogrifier_impl.prepare_activity(activity.data)
|
||||||
|
|
||||||
{actor, data} =
|
{actor, data} =
|
||||||
with {_, false} <- {:actor_changed?, data["actor"] != activity.data["actor"]} do
|
with {_, false} <- {:actor_changed?, data["actor"] != activity.data["actor"]} do
|
||||||
{actor, data}
|
{actor, data}
|
||||||
else
|
else
|
||||||
{:actor_changed?, true} ->
|
{:actor_changed?, true} ->
|
||||||
# If prepare_outgoing changes the actor, re-get it from the db
|
# If prepare_activity changes the actor, re-get it from the db
|
||||||
new_actor = User.get_cached_by_ap_id(data["actor"])
|
new_actor = User.get_cached_by_ap_id(data["actor"])
|
||||||
{new_actor, data}
|
{new_actor, data}
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -783,7 +783,13 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
|
|
||||||
def set_replies(obj_data), do: obj_data
|
def set_replies(obj_data), do: obj_data
|
||||||
|
|
||||||
# Prepares the object of an outgoing create activity.
|
defp set_voters_count(%{"voters" => [_ | _] = voters} = obj) do
|
||||||
|
Map.merge(obj, %{"votersCount" => length(voters)})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp set_voters_count(obj), do: obj
|
||||||
|
|
||||||
|
# Prepares and sanitizes the object for federation.
|
||||||
def prepare_object(object) do
|
def prepare_object(object) do
|
||||||
object
|
object
|
||||||
|> add_hashtags
|
|> add_hashtags
|
||||||
|
|
@ -795,6 +801,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
|> set_reply_to_uri
|
|> set_reply_to_uri
|
||||||
|> set_quote_url
|
|> set_quote_url
|
||||||
|> set_replies
|
|> set_replies
|
||||||
|
|> set_voters_count
|
||||||
|> CommonFixes.maybe_add_content_map()
|
|> CommonFixes.maybe_add_content_map()
|
||||||
|> strip_internal_fields
|
|> strip_internal_fields
|
||||||
|> strip_internal_tags
|
|> strip_internal_tags
|
||||||
|
|
@ -824,7 +831,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
# internal -> Mastodon
|
# internal -> Mastodon
|
||||||
# """
|
# """
|
||||||
|
|
||||||
def prepare_outgoing(%{"type" => activity_type, "object" => object_id} = data)
|
def prepare_activity(%{"type" => activity_type, "object" => object_id} = data)
|
||||||
when activity_type in ["Create", "Listen"] do
|
when activity_type in ["Create", "Listen"] do
|
||||||
object =
|
object =
|
||||||
object_id
|
object_id
|
||||||
|
|
@ -840,7 +847,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
{:ok, data}
|
{:ok, data}
|
||||||
end
|
end
|
||||||
|
|
||||||
def prepare_outgoing(%{"type" => "Update", "object" => %{"type" => objtype} = object} = data)
|
def prepare_activity(%{"type" => "Update", "object" => %{"type" => objtype} = object} = data)
|
||||||
when objtype in Pleroma.Constants.updatable_object_types() do
|
when objtype in Pleroma.Constants.updatable_object_types() do
|
||||||
data =
|
data =
|
||||||
data
|
data
|
||||||
|
|
@ -851,7 +858,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
{:ok, data}
|
{:ok, data}
|
||||||
end
|
end
|
||||||
|
|
||||||
def prepare_outgoing(%{"type" => "Update", "object" => %{"type" => objtype} = object} = data)
|
def prepare_activity(%{"type" => "Update", "object" => %{"type" => objtype} = object} = data)
|
||||||
when objtype in Pleroma.Constants.actor_types() do
|
when objtype in Pleroma.Constants.actor_types() do
|
||||||
object =
|
object =
|
||||||
object
|
object
|
||||||
|
|
@ -868,11 +875,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
{:ok, data}
|
{:ok, data}
|
||||||
end
|
end
|
||||||
|
|
||||||
def prepare_outgoing(%{"type" => "Update", "object" => %{}} = data) do
|
def prepare_activity(%{"type" => "Update", "object" => %{}} = data) do
|
||||||
raise "Requested to serve an Update for non-updateable object type: #{inspect(data)}"
|
raise "Requested to serve an Update for non-updateable object type: #{inspect(data)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def prepare_outgoing(%{"type" => "Announce", "actor" => ap_id, "object" => object_id} = data) do
|
def prepare_activity(%{"type" => "Announce", "actor" => ap_id, "object" => object_id} = data) do
|
||||||
object =
|
object =
|
||||||
object_id
|
object_id
|
||||||
|> Object.normalize(fetch: false)
|
|> Object.normalize(fetch: false)
|
||||||
|
|
@ -895,7 +902,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
|
|
||||||
# Mastodon Accept/Reject requires a non-normalized object containing the actor URIs,
|
# Mastodon Accept/Reject requires a non-normalized object containing the actor URIs,
|
||||||
# because of course it does.
|
# because of course it does.
|
||||||
def prepare_outgoing(%{"type" => "Accept"} = data) do
|
def prepare_activity(%{"type" => "Accept"} = data) do
|
||||||
with follow_activity <- Activity.normalize(data["object"]) do
|
with follow_activity <- Activity.normalize(data["object"]) do
|
||||||
object = %{
|
object = %{
|
||||||
"actor" => follow_activity.actor,
|
"actor" => follow_activity.actor,
|
||||||
|
|
@ -913,7 +920,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def prepare_outgoing(%{"type" => "Reject"} = data) do
|
def prepare_activity(%{"type" => "Reject"} = data) do
|
||||||
with follow_activity <- Activity.normalize(data["object"]) do
|
with follow_activity <- Activity.normalize(data["object"]) do
|
||||||
object = %{
|
object = %{
|
||||||
"actor" => follow_activity.actor,
|
"actor" => follow_activity.actor,
|
||||||
|
|
@ -931,7 +938,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def prepare_outgoing(%{"type" => "Flag"} = data) do
|
def prepare_activity(%{"type" => "Flag"} = data) do
|
||||||
with {:ok, stripped_activity} <- Utils.strip_report_status_data(data),
|
with {:ok, stripped_activity} <- Utils.strip_report_status_data(data),
|
||||||
stripped_activity <- Utils.maybe_anonymize_reporter(stripped_activity),
|
stripped_activity <- Utils.maybe_anonymize_reporter(stripped_activity),
|
||||||
stripped_activity <- Map.merge(stripped_activity, Utils.make_json_ld_header()) do
|
stripped_activity <- Map.merge(stripped_activity, Utils.make_json_ld_header()) do
|
||||||
|
|
@ -939,7 +946,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def prepare_outgoing(%{"type" => _type} = data) do
|
def prepare_activity(%{"type" => _type} = data) do
|
||||||
data =
|
data =
|
||||||
data
|
data
|
||||||
|> strip_internal_fields
|
|> strip_internal_fields
|
||||||
|
|
|
||||||
|
|
@ -7,5 +7,5 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.API do
|
||||||
Behaviour for the subset of Transmogrifier used by Publisher.
|
Behaviour for the subset of Transmogrifier used by Publisher.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@callback prepare_outgoing(map()) :: {:ok, map()} | {:error, term()}
|
@callback prepare_activity(map()) :: {:ok, map()} | {:error, term()}
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -863,6 +863,34 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
||||||
|
|
||||||
def update_report_state(_, _), do: {:error, "Unsupported state"}
|
def update_report_state(_, _), do: {:error, "Unsupported state"}
|
||||||
|
|
||||||
|
def assign_report_to_account(%Activity{} = activity, nil = _account) do
|
||||||
|
new_data = Map.delete(activity.data, "assigned_account")
|
||||||
|
|
||||||
|
activity
|
||||||
|
|> Changeset.change(data: new_data)
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
|
||||||
|
def assign_report_to_account(%Activity{} = activity, account) do
|
||||||
|
new_data = Map.put(activity.data, "assigned_account", account)
|
||||||
|
|
||||||
|
activity
|
||||||
|
|> Changeset.change(data: new_data)
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
|
||||||
|
def assign_report_to_account(activity_ids, account) do
|
||||||
|
activities_num = length(activity_ids)
|
||||||
|
|
||||||
|
from(a in Activity, where: a.id in ^activity_ids)
|
||||||
|
|> update(set: [data: fragment("jsonb_set(data, '{assigned_account}', ?)", ^account)])
|
||||||
|
|> Repo.update_all([])
|
||||||
|
|> case do
|
||||||
|
{^activities_num, _} -> :ok
|
||||||
|
_ -> {:error, activity_ids}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def strip_report_status_data(%Activity{} = activity) do
|
def strip_report_status_data(%Activity{} = activity) do
|
||||||
with {:ok, new_data} <- strip_report_status_data(activity.data) do
|
with {:ok, new_data} <- strip_report_status_data(activity.data) do
|
||||||
{:ok, %{activity | data: new_data}}
|
{:ok, %{activity | data: new_data}}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("object.json", %{object: %Activity{} = activity}) do
|
def render("object.json", %{object: %Activity{} = activity}) do
|
||||||
{:ok, ap_data} = Transmogrifier.prepare_outgoing(activity.data)
|
{:ok, ap_data} = Transmogrifier.prepare_activity(activity.data)
|
||||||
ap_data
|
ap_data
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,32 +35,14 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
||||||
def render("endpoints.json", _), do: %{}
|
def render("endpoints.json", _), do: %{}
|
||||||
|
|
||||||
def render("service.json", %{user: user}) do
|
def render("service.json", %{user: user}) do
|
||||||
{:ok, _, public_key} = Keys.keys_from_pem(user.keys)
|
Map.merge(common_actor_fields(user), %{
|
||||||
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
|
|
||||||
public_key = :public_key.pem_encode([public_key])
|
|
||||||
|
|
||||||
endpoints = render("endpoints.json", %{user: user})
|
|
||||||
|
|
||||||
%{
|
|
||||||
"id" => user.ap_id,
|
|
||||||
"type" => "Application",
|
"type" => "Application",
|
||||||
"following" => "#{user.ap_id}/following",
|
|
||||||
"followers" => "#{user.ap_id}/followers",
|
|
||||||
"inbox" => "#{user.ap_id}/inbox",
|
|
||||||
"outbox" => "#{user.ap_id}/outbox",
|
|
||||||
"name" => "Pleroma",
|
"name" => "Pleroma",
|
||||||
"summary" =>
|
"summary" =>
|
||||||
"An internal service actor for this Pleroma instance. No user-serviceable parts inside.",
|
"An internal service actor for this Pleroma instance. No user-serviceable parts inside.",
|
||||||
"url" => user.ap_id,
|
|
||||||
"manuallyApprovesFollowers" => false,
|
"manuallyApprovesFollowers" => false,
|
||||||
"publicKey" => %{
|
|
||||||
"id" => "#{user.ap_id}#main-key",
|
|
||||||
"owner" => user.ap_id,
|
|
||||||
"publicKeyPem" => public_key
|
|
||||||
},
|
|
||||||
"endpoints" => endpoints,
|
|
||||||
"invisible" => User.invisible?(user)
|
"invisible" => User.invisible?(user)
|
||||||
}
|
})
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -77,13 +59,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("user.json", %{user: user}) do
|
def render("user.json", %{user: user}) do
|
||||||
{:ok, _, public_key} = Keys.keys_from_pem(user.keys)
|
|
||||||
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
|
|
||||||
public_key = :public_key.pem_encode([public_key])
|
|
||||||
user = User.sanitize_html(user)
|
user = User.sanitize_html(user)
|
||||||
|
|
||||||
endpoints = render("endpoints.json", %{user: user})
|
|
||||||
|
|
||||||
emoji_tags = Transmogrifier.take_emoji_tags(user)
|
emoji_tags = Transmogrifier.take_emoji_tags(user)
|
||||||
|
|
||||||
fields = Enum.map(user.fields, &Map.put(&1, "type", "PropertyValue"))
|
fields = Enum.map(user.fields, &Map.put(&1, "type", "PropertyValue"))
|
||||||
|
|
@ -102,25 +79,9 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
||||||
do: Date.to_iso8601(user.birthday),
|
do: Date.to_iso8601(user.birthday),
|
||||||
else: nil
|
else: nil
|
||||||
|
|
||||||
%{
|
Map.merge(common_actor_fields(user), %{
|
||||||
"id" => user.ap_id,
|
|
||||||
"type" => user.actor_type,
|
|
||||||
"following" => "#{user.ap_id}/following",
|
|
||||||
"followers" => "#{user.ap_id}/followers",
|
|
||||||
"inbox" => "#{user.ap_id}/inbox",
|
|
||||||
"outbox" => "#{user.ap_id}/outbox",
|
|
||||||
"featured" => "#{user.ap_id}/collections/featured",
|
"featured" => "#{user.ap_id}/collections/featured",
|
||||||
"preferredUsername" => user.nickname,
|
"preferredUsername" => user.nickname,
|
||||||
"name" => user.name,
|
|
||||||
"summary" => user.bio,
|
|
||||||
"url" => user.ap_id,
|
|
||||||
"manuallyApprovesFollowers" => user.is_locked,
|
|
||||||
"publicKey" => %{
|
|
||||||
"id" => "#{user.ap_id}#main-key",
|
|
||||||
"owner" => user.ap_id,
|
|
||||||
"publicKeyPem" => public_key
|
|
||||||
},
|
|
||||||
"endpoints" => endpoints,
|
|
||||||
"attachment" => fields,
|
"attachment" => fields,
|
||||||
"tag" => emoji_tags,
|
"tag" => emoji_tags,
|
||||||
# Note: key name is indeed "discoverable" (not an error)
|
# Note: key name is indeed "discoverable" (not an error)
|
||||||
|
|
@ -130,7 +91,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
||||||
"vcard:bday" => birthday,
|
"vcard:bday" => birthday,
|
||||||
"webfinger" => "acct:#{User.full_nickname(user)}",
|
"webfinger" => "acct:#{User.full_nickname(user)}",
|
||||||
"published" => Pleroma.Web.CommonAPI.Utils.to_masto_date(user.inserted_at)
|
"published" => Pleroma.Web.CommonAPI.Utils.to_masto_date(user.inserted_at)
|
||||||
}
|
})
|
||||||
|> Map.merge(
|
|> Map.merge(
|
||||||
maybe_make_image(
|
maybe_make_image(
|
||||||
&User.avatar_url/2,
|
&User.avatar_url/2,
|
||||||
|
|
@ -283,7 +244,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
||||||
}) do
|
}) do
|
||||||
collection =
|
collection =
|
||||||
Enum.map(activities, fn activity ->
|
Enum.map(activities, fn activity ->
|
||||||
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
|
{:ok, data} = Transmogrifier.prepare_activity(activity.data)
|
||||||
data
|
data
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
@ -309,6 +270,33 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp common_actor_fields(%User{} = user) do
|
||||||
|
endpoints = render("endpoints.json", %{user: user})
|
||||||
|
|
||||||
|
{:ok, _, public_key} = Keys.keys_from_pem(user.keys)
|
||||||
|
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
|
||||||
|
public_key = :public_key.pem_encode([public_key])
|
||||||
|
|
||||||
|
%{
|
||||||
|
"id" => user.ap_id,
|
||||||
|
"type" => user.actor_type,
|
||||||
|
"following" => "#{user.ap_id}/following",
|
||||||
|
"followers" => "#{user.ap_id}/followers",
|
||||||
|
"inbox" => "#{user.ap_id}/inbox",
|
||||||
|
"outbox" => "#{user.ap_id}/outbox",
|
||||||
|
"name" => user.name,
|
||||||
|
"summary" => user.bio,
|
||||||
|
"url" => user.ap_id,
|
||||||
|
"manuallyApprovesFollowers" => user.is_locked,
|
||||||
|
"endpoints" => endpoints,
|
||||||
|
"publicKey" => %{
|
||||||
|
"id" => "#{user.ap_id}#main-key",
|
||||||
|
"owner" => user.ap_id,
|
||||||
|
"publicKeyPem" => public_key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
defp maybe_put_total_items(map, false, _total), do: map
|
defp maybe_put_total_items(map, false, _total), do: map
|
||||||
|
|
||||||
defp maybe_put_total_items(map, true, total) do
|
defp maybe_put_total_items(map, true, total) do
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,8 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp whitelisted_config?(":pleroma", ":database_config_whitelist"), do: false
|
||||||
|
|
||||||
defp whitelisted_config?(group, key) do
|
defp whitelisted_config?(group, key) do
|
||||||
if whitelisted_configs = Config.get(:database_config_whitelist) do
|
if whitelisted_configs = Config.get(:database_config_whitelist) do
|
||||||
Enum.any?(whitelisted_configs, fn
|
Enum.any?(whitelisted_configs, fn
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Web.AdminAPI.ReportController do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.ModerationLog
|
alias Pleroma.ModerationLog
|
||||||
alias Pleroma.ReportNote
|
alias Pleroma.ReportNote
|
||||||
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.AdminAPI
|
alias Pleroma.Web.AdminAPI
|
||||||
alias Pleroma.Web.AdminAPI.Report
|
alias Pleroma.Web.AdminAPI.Report
|
||||||
|
|
@ -24,7 +25,7 @@ defmodule Pleroma.Web.AdminAPI.ReportController do
|
||||||
plug(
|
plug(
|
||||||
OAuthScopesPlug,
|
OAuthScopesPlug,
|
||||||
%{scopes: ["admin:write:reports"]}
|
%{scopes: ["admin:write:reports"]}
|
||||||
when action in [:update, :notes_create, :notes_delete]
|
when action in [:update, :assign_account, :notes_create, :notes_delete]
|
||||||
)
|
)
|
||||||
|
|
||||||
action_fallback(AdminAPI.FallbackController)
|
action_fallback(AdminAPI.FallbackController)
|
||||||
|
|
@ -79,6 +80,22 @@ defmodule Pleroma.Web.AdminAPI.ReportController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def assign_account(
|
||||||
|
%{
|
||||||
|
assigns: %{user: admin},
|
||||||
|
private: %{open_api_spex: %{body_params: %{reports: reports}}}
|
||||||
|
} = conn,
|
||||||
|
_
|
||||||
|
) do
|
||||||
|
result = Enum.map(reports, &do_assign_account(&1, admin))
|
||||||
|
|
||||||
|
if Enum.any?(result, &Map.has_key?(&1, :error)) do
|
||||||
|
json_response(conn, :bad_request, result)
|
||||||
|
else
|
||||||
|
json_response(conn, :no_content, "")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def notes_create(
|
def notes_create(
|
||||||
%{
|
%{
|
||||||
assigns: %{user: user},
|
assigns: %{user: user},
|
||||||
|
|
@ -131,4 +148,40 @@ defmodule Pleroma.Web.AdminAPI.ReportController do
|
||||||
_ -> json_response(conn, :bad_request, "")
|
_ -> json_response(conn, :bad_request, "")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp do_assign_account(%{assigned_account: nil, id: id}, admin) do
|
||||||
|
with {:ok, activity} <- CommonAPI.assign_report_to_account(id, nil),
|
||||||
|
report <- Activity.get_by_id_with_user_actor(activity.id) do
|
||||||
|
ModerationLog.insert_log(%{
|
||||||
|
action: "report_unassigned",
|
||||||
|
actor: admin,
|
||||||
|
subject: activity,
|
||||||
|
subject_actor: report.user_actor
|
||||||
|
})
|
||||||
|
|
||||||
|
activity
|
||||||
|
else
|
||||||
|
{:error, message} ->
|
||||||
|
%{id: id, error: message}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_assign_account(%{assigned_account: assigned_account, id: id}, admin) do
|
||||||
|
with %User{id: account} = user <- User.get_cached_by_nickname(assigned_account),
|
||||||
|
{:ok, activity} <- CommonAPI.assign_report_to_account(id, account),
|
||||||
|
report <- Activity.get_by_id_with_user_actor(activity.id) do
|
||||||
|
ModerationLog.insert_log(%{
|
||||||
|
action: "report_assigned",
|
||||||
|
actor: admin,
|
||||||
|
subject: activity,
|
||||||
|
subject_actor: report.user_actor,
|
||||||
|
assigned_account: user.nickname
|
||||||
|
})
|
||||||
|
|
||||||
|
activity
|
||||||
|
else
|
||||||
|
{:error, message} ->
|
||||||
|
%{id: id, error: message}
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,11 @@ defmodule Pleroma.Web.AdminAPI.Report do
|
||||||
user = User.get_cached_by_ap_id(actor)
|
user = User.get_cached_by_ap_id(actor)
|
||||||
account = User.get_cached_by_ap_id(account_ap_id)
|
account = User.get_cached_by_ap_id(account_ap_id)
|
||||||
|
|
||||||
|
assigned_account =
|
||||||
|
if Map.has_key?(report.data, "assigned_account") do
|
||||||
|
User.get_cached_by_id(report.data["assigned_account"])
|
||||||
|
end
|
||||||
|
|
||||||
statuses =
|
statuses =
|
||||||
status_ap_ids
|
status_ap_ids
|
||||||
|> Enum.reject(&is_nil(&1))
|
|> Enum.reject(&is_nil(&1))
|
||||||
|
|
@ -26,7 +31,13 @@ defmodule Pleroma.Web.AdminAPI.Report do
|
||||||
Activity.get_by_ap_id_with_object(act)
|
Activity.get_by_ap_id_with_object(act)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
%{report: report, user: user, account: account, statuses: statuses}
|
%{
|
||||||
|
report: report,
|
||||||
|
user: user,
|
||||||
|
account: account,
|
||||||
|
statuses: statuses,
|
||||||
|
assigned_account: assigned_account
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp make_fake_activity(act, user) do
|
defp make_fake_activity(act, user) do
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,13 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("show.json", %{report: report, user: user, account: account, statuses: statuses}) do
|
def render("show.json", %{
|
||||||
|
report: report,
|
||||||
|
user: user,
|
||||||
|
account: account,
|
||||||
|
statuses: statuses,
|
||||||
|
assigned_account: assigned_account
|
||||||
|
}) do
|
||||||
created_at = Utils.to_masto_date(report.data["published"])
|
created_at = Utils.to_masto_date(report.data["published"])
|
||||||
|
|
||||||
content =
|
content =
|
||||||
|
|
@ -36,6 +42,11 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
assigned_account =
|
||||||
|
if assigned_account do
|
||||||
|
merge_account_views(assigned_account)
|
||||||
|
end
|
||||||
|
|
||||||
%{
|
%{
|
||||||
id: report.id,
|
id: report.id,
|
||||||
account: merge_account_views(account),
|
account: merge_account_views(account),
|
||||||
|
|
@ -49,7 +60,8 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
|
||||||
}),
|
}),
|
||||||
state: report.data["state"],
|
state: report.data["state"],
|
||||||
notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes}),
|
notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes}),
|
||||||
rules: rules(Map.get(report.data, "rules", nil))
|
rules: rules(Map.get(report.data, "rules", nil)),
|
||||||
|
assigned_account: assigned_account
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,14 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do
|
||||||
OpenApiSpex.cast_and_validate(spec, operation, conn, content_type, cast_opts)
|
OpenApiSpex.cast_and_validate(spec, operation, conn, content_type, cast_opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp cast_and_validate(spec, operation, conn, content_type, false = _strict, cast_opts) do
|
defp cast_and_validate(
|
||||||
|
spec,
|
||||||
|
operation,
|
||||||
|
%Conn{} = conn,
|
||||||
|
content_type,
|
||||||
|
false = _strict,
|
||||||
|
cast_opts
|
||||||
|
) do
|
||||||
case OpenApiSpex.cast_and_validate(spec, operation, conn, content_type) do
|
case OpenApiSpex.cast_and_validate(spec, operation, conn, content_type) do
|
||||||
{:ok, conn} ->
|
{:ok, conn} ->
|
||||||
{:ok, conn}
|
{:ok, conn}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,12 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
|
||||||
:query,
|
:query,
|
||||||
%Schema{type: :integer, default: 50},
|
%Schema{type: :integer, default: 50},
|
||||||
"Number number of log entries per page"
|
"Number number of log entries per page"
|
||||||
|
),
|
||||||
|
Operation.parameter(
|
||||||
|
:assigned_account,
|
||||||
|
:query,
|
||||||
|
%Schema{type: :string},
|
||||||
|
"Filter by assigned account ID"
|
||||||
)
|
)
|
||||||
| admin_api_params()
|
| admin_api_params()
|
||||||
],
|
],
|
||||||
|
|
@ -103,6 +109,22 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def assign_account_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Report management"],
|
||||||
|
summary: "Assign account to specified reports",
|
||||||
|
operationId: "AdminAPI.ReportController.assign_account",
|
||||||
|
security: [%{"oAuth" => ["admin:write:reports"]}],
|
||||||
|
parameters: admin_api_params(),
|
||||||
|
requestBody: request_body("Parameters", assign_account_request(), required: true),
|
||||||
|
responses: %{
|
||||||
|
204 => no_content_response(),
|
||||||
|
400 => Operation.response("Bad Request", "application/json", update_400_response()),
|
||||||
|
403 => Operation.response("Forbidden", "application/json", ApiError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def notes_create_operation do
|
def notes_create_operation do
|
||||||
%Operation{
|
%Operation{
|
||||||
tags: ["Report management"],
|
tags: ["Report management"],
|
||||||
|
|
@ -186,7 +208,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
|
||||||
hint: %Schema{type: :string, nullable: true}
|
hint: %Schema{type: :string, nullable: true}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
assigned_account:
|
||||||
|
account_admin()
|
||||||
|
|> Map.put(:nullable, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
@ -242,6 +267,34 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp assign_account_request do
|
||||||
|
%Schema{
|
||||||
|
type: :object,
|
||||||
|
required: [:reports],
|
||||||
|
properties: %{
|
||||||
|
reports: %Schema{
|
||||||
|
type: :array,
|
||||||
|
items: %Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
id: %Schema{allOf: [FlakeID], description: "Required, report ID"},
|
||||||
|
assigned_account: %Schema{
|
||||||
|
type: :string,
|
||||||
|
description: "User nickname",
|
||||||
|
nullable: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
example: %{
|
||||||
|
"reports" => [
|
||||||
|
%{"id" => "123", "assigned_account" => "pleroma"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
defp update_400_response do
|
defp update_400_response do
|
||||||
%Schema{
|
%Schema{
|
||||||
type: :array,
|
type: :array,
|
||||||
|
|
|
||||||
|
|
@ -342,6 +342,18 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do
|
||||||
max_pinned_statuses: %Schema{
|
max_pinned_statuses: %Schema{
|
||||||
type: :integer,
|
type: :integer,
|
||||||
description: "The maximum number of pinned statuses for each account."
|
description: "The maximum number of pinned statuses for each account."
|
||||||
|
},
|
||||||
|
max_profile_fields: %Schema{
|
||||||
|
type: :integer,
|
||||||
|
description: "The maximum number of custom profile fields allowed to be set."
|
||||||
|
},
|
||||||
|
profile_field_name_limit: %Schema{
|
||||||
|
type: :integer,
|
||||||
|
description: "The maximum size of a profile field name, in characters."
|
||||||
|
},
|
||||||
|
profile_field_value_limit: %Schema{
|
||||||
|
type: :integer,
|
||||||
|
description: "The maximum size of a profile field value, in characters."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do
|
||||||
summary: "Create a list",
|
summary: "Create a list",
|
||||||
description: "Fetch the list with the given ID. Used for verifying the title of a list.",
|
description: "Fetch the list with the given ID. Used for verifying the title of a list.",
|
||||||
operationId: "ListController.create",
|
operationId: "ListController.create",
|
||||||
requestBody: create_update_request(),
|
requestBody: create_request(),
|
||||||
security: [%{"oAuth" => ["write:lists"]}],
|
security: [%{"oAuth" => ["write:lists"]}],
|
||||||
responses: %{
|
responses: %{
|
||||||
200 => Operation.response("List", "application/json", List),
|
200 => Operation.response("List", "application/json", List),
|
||||||
|
|
@ -68,7 +68,7 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do
|
||||||
description: "Change the title of a list",
|
description: "Change the title of a list",
|
||||||
operationId: "ListController.update",
|
operationId: "ListController.update",
|
||||||
parameters: [id_param()],
|
parameters: [id_param()],
|
||||||
requestBody: create_update_request(),
|
requestBody: update_request(),
|
||||||
security: [%{"oAuth" => ["write:lists"]}],
|
security: [%{"oAuth" => ["write:lists"]}],
|
||||||
responses: %{
|
responses: %{
|
||||||
200 => Operation.response("List", "application/json", List),
|
200 => Operation.response("List", "application/json", List),
|
||||||
|
|
@ -164,14 +164,18 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp create_update_request do
|
defp create_request do
|
||||||
request_body(
|
request_body(
|
||||||
"Parameters",
|
"Parameters",
|
||||||
%Schema{
|
%Schema{
|
||||||
description: "POST body for creating or updating a List",
|
description: "POST body for creating a List",
|
||||||
type: :object,
|
type: :object,
|
||||||
properties: %{
|
properties: %{
|
||||||
title: %Schema{type: :string, description: "List title"}
|
title: %Schema{type: :string, description: "List title"},
|
||||||
|
exclusive: %Schema{
|
||||||
|
type: :boolean,
|
||||||
|
description: "Whether members of the list should be removed from the “Home” feed"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
required: [:title]
|
required: [:title]
|
||||||
},
|
},
|
||||||
|
|
@ -179,6 +183,24 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp update_request do
|
||||||
|
request_body(
|
||||||
|
"Parameters",
|
||||||
|
%Schema{
|
||||||
|
description: "PUT body for updating a List",
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
title: %Schema{type: :string, description: "List title"},
|
||||||
|
exclusive: %Schema{
|
||||||
|
type: :boolean,
|
||||||
|
description: "Whether members of the list should be removed from the “Home” feed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: true
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
defp add_remove_accounts_request(required) when is_boolean(required) do
|
defp add_remove_accounts_request(required) when is_boolean(required) do
|
||||||
request_body(
|
request_body(
|
||||||
"Parameters",
|
"Parameters",
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
|
defmodule Pleroma.Web.ApiSpec.PleromaUtilOperation do
|
||||||
alias OpenApiSpex.Operation
|
alias OpenApiSpex.Operation
|
||||||
alias OpenApiSpex.Schema
|
alias OpenApiSpex.Schema
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||||
|
|
@ -19,7 +19,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
|
||||||
%Operation{
|
%Operation{
|
||||||
tags: ["Custom emojis"],
|
tags: ["Custom emojis"],
|
||||||
summary: "List all custom emojis",
|
summary: "List all custom emojis",
|
||||||
operationId: "UtilController.emoji",
|
operationId: "PleromaAPI.UtilController.emoji",
|
||||||
parameters: [],
|
parameters: [],
|
||||||
responses: %{
|
responses: %{
|
||||||
200 =>
|
200 =>
|
||||||
|
|
@ -48,7 +48,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
|
||||||
%Operation{
|
%Operation{
|
||||||
tags: ["Others"],
|
tags: ["Others"],
|
||||||
summary: "Dump frontend configurations",
|
summary: "Dump frontend configurations",
|
||||||
operationId: "UtilController.frontend_configurations",
|
operationId: "PleromaAPI.UtilController.frontend_configurations",
|
||||||
parameters: [],
|
parameters: [],
|
||||||
responses: %{
|
responses: %{
|
||||||
200 =>
|
200 =>
|
||||||
|
|
@ -70,7 +70,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
|
||||||
tags: ["Account credentials"],
|
tags: ["Account credentials"],
|
||||||
summary: "Change account password",
|
summary: "Change account password",
|
||||||
security: [%{"oAuth" => ["write:accounts"]}],
|
security: [%{"oAuth" => ["write:accounts"]}],
|
||||||
operationId: "UtilController.change_password",
|
operationId: "PleromaAPI.UtilController.change_password",
|
||||||
requestBody: request_body("Parameters", change_password_request(), required: true),
|
requestBody: request_body("Parameters", change_password_request(), required: true),
|
||||||
responses: %{
|
responses: %{
|
||||||
200 =>
|
200 =>
|
||||||
|
|
@ -106,7 +106,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
|
||||||
tags: ["Account credentials"],
|
tags: ["Account credentials"],
|
||||||
summary: "Change account email",
|
summary: "Change account email",
|
||||||
security: [%{"oAuth" => ["write:accounts"]}],
|
security: [%{"oAuth" => ["write:accounts"]}],
|
||||||
operationId: "UtilController.change_email",
|
operationId: "PleromaAPI.UtilController.change_email",
|
||||||
requestBody: request_body("Parameters", change_email_request(), required: true),
|
requestBody: request_body("Parameters", change_email_request(), required: true),
|
||||||
responses: %{
|
responses: %{
|
||||||
200 =>
|
200 =>
|
||||||
|
|
@ -141,7 +141,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
|
||||||
tags: ["Settings"],
|
tags: ["Settings"],
|
||||||
summary: "Update Notification Settings",
|
summary: "Update Notification Settings",
|
||||||
security: [%{"oAuth" => ["write:accounts"]}],
|
security: [%{"oAuth" => ["write:accounts"]}],
|
||||||
operationId: "UtilController.update_notification_settings",
|
operationId: "PleromaAPI.UtilController.update_notification_settings",
|
||||||
parameters: [
|
parameters: [
|
||||||
Operation.parameter(
|
Operation.parameter(
|
||||||
:block_from_strangers,
|
:block_from_strangers,
|
||||||
|
|
@ -173,7 +173,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
|
||||||
tags: ["Account credentials"],
|
tags: ["Account credentials"],
|
||||||
summary: "Disable Account",
|
summary: "Disable Account",
|
||||||
security: [%{"oAuth" => ["write:accounts"]}],
|
security: [%{"oAuth" => ["write:accounts"]}],
|
||||||
operationId: "UtilController.disable_account",
|
operationId: "PleromaAPI.UtilController.disable_account",
|
||||||
parameters: [
|
parameters: [
|
||||||
Operation.parameter(:password, :query, :string, "Password")
|
Operation.parameter(:password, :query, :string, "Password")
|
||||||
],
|
],
|
||||||
|
|
@ -193,7 +193,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
|
||||||
tags: ["Account credentials"],
|
tags: ["Account credentials"],
|
||||||
summary: "Delete Account",
|
summary: "Delete Account",
|
||||||
security: [%{"oAuth" => ["write:accounts"]}],
|
security: [%{"oAuth" => ["write:accounts"]}],
|
||||||
operationId: "UtilController.delete_account",
|
operationId: "PleromaAPI.UtilController.delete_account",
|
||||||
parameters: [
|
parameters: [
|
||||||
Operation.parameter(:password, :query, :string, "Password")
|
Operation.parameter(:password, :query, :string, "Password")
|
||||||
],
|
],
|
||||||
|
|
@ -212,7 +212,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
|
||||||
def captcha_operation do
|
def captcha_operation do
|
||||||
%Operation{
|
%Operation{
|
||||||
summary: "Get a captcha",
|
summary: "Get a captcha",
|
||||||
operationId: "UtilController.captcha",
|
operationId: "PleromaAPI.UtilController.captcha",
|
||||||
tags: ["Others"],
|
tags: ["Others"],
|
||||||
parameters: [],
|
parameters: [],
|
||||||
responses: %{
|
responses: %{
|
||||||
|
|
@ -226,7 +226,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
|
||||||
tags: ["Account credentials"],
|
tags: ["Account credentials"],
|
||||||
summary: "Move account",
|
summary: "Move account",
|
||||||
security: [%{"oAuth" => ["write:accounts"]}],
|
security: [%{"oAuth" => ["write:accounts"]}],
|
||||||
operationId: "UtilController.move_account",
|
operationId: "PleromaAPI.UtilController.move_account",
|
||||||
requestBody: request_body("Parameters", move_account_request(), required: true),
|
requestBody: request_body("Parameters", move_account_request(), required: true),
|
||||||
responses: %{
|
responses: %{
|
||||||
200 =>
|
200 =>
|
||||||
|
|
@ -262,7 +262,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
|
||||||
tags: ["Account credentials"],
|
tags: ["Account credentials"],
|
||||||
summary: "List account aliases",
|
summary: "List account aliases",
|
||||||
security: [%{"oAuth" => ["read:accounts"]}],
|
security: [%{"oAuth" => ["read:accounts"]}],
|
||||||
operationId: "UtilController.list_aliases",
|
operationId: "PleromaAPI.UtilController.list_aliases",
|
||||||
responses: %{
|
responses: %{
|
||||||
200 =>
|
200 =>
|
||||||
Operation.response("Success", "application/json", %Schema{
|
Operation.response("Success", "application/json", %Schema{
|
||||||
|
|
@ -286,7 +286,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
|
||||||
tags: ["Account credentials"],
|
tags: ["Account credentials"],
|
||||||
summary: "Add an alias to this account",
|
summary: "Add an alias to this account",
|
||||||
security: [%{"oAuth" => ["write:accounts"]}],
|
security: [%{"oAuth" => ["write:accounts"]}],
|
||||||
operationId: "UtilController.add_alias",
|
operationId: "PleromaAPI.UtilController.add_alias",
|
||||||
requestBody: request_body("Parameters", add_alias_request(), required: true),
|
requestBody: request_body("Parameters", add_alias_request(), required: true),
|
||||||
responses: %{
|
responses: %{
|
||||||
200 =>
|
200 =>
|
||||||
|
|
@ -326,7 +326,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
|
||||||
tags: ["Account credentials"],
|
tags: ["Account credentials"],
|
||||||
summary: "Delete an alias from this account",
|
summary: "Delete an alias from this account",
|
||||||
security: [%{"oAuth" => ["write:accounts"]}],
|
security: [%{"oAuth" => ["write:accounts"]}],
|
||||||
operationId: "UtilController.delete_alias",
|
operationId: "PleromaAPI.UtilController.delete_alias",
|
||||||
requestBody: request_body("Parameters", delete_alias_request(), required: true),
|
requestBody: request_body("Parameters", delete_alias_request(), required: true),
|
||||||
responses: %{
|
responses: %{
|
||||||
200 =>
|
200 =>
|
||||||
|
|
@ -366,7 +366,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
|
||||||
tags: ["Others"],
|
tags: ["Others"],
|
||||||
summary: "Quick status check on the instance",
|
summary: "Quick status check on the instance",
|
||||||
security: [%{"oAuth" => ["write:accounts"]}],
|
security: [%{"oAuth" => ["write:accounts"]}],
|
||||||
operationId: "UtilController.healthcheck",
|
operationId: "PleromaAPI.UtilController.healthcheck",
|
||||||
parameters: [],
|
parameters: [],
|
||||||
responses: %{
|
responses: %{
|
||||||
200 => Operation.response("Healthy", "application/json", %Schema{type: :object}),
|
200 => Operation.response("Healthy", "application/json", %Schema{type: :object}),
|
||||||
|
|
@ -376,52 +376,6 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def remote_subscribe_operation do
|
|
||||||
%Operation{
|
|
||||||
tags: ["Remote interaction"],
|
|
||||||
summary: "Remote Subscribe",
|
|
||||||
operationId: "UtilController.remote_subscribe",
|
|
||||||
parameters: [],
|
|
||||||
responses: %{200 => Operation.response("Web Page", "test/html", %Schema{type: :string})}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def remote_interaction_operation do
|
|
||||||
%Operation{
|
|
||||||
tags: ["Remote interaction"],
|
|
||||||
summary: "Remote interaction",
|
|
||||||
operationId: "UtilController.remote_interaction",
|
|
||||||
requestBody: request_body("Parameters", remote_interaction_request(), required: true),
|
|
||||||
responses: %{
|
|
||||||
200 =>
|
|
||||||
Operation.response("Remote interaction URL", "application/json", %Schema{type: :object})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp remote_interaction_request do
|
|
||||||
%Schema{
|
|
||||||
title: "RemoteInteractionRequest",
|
|
||||||
description: "POST body for remote interaction",
|
|
||||||
type: :object,
|
|
||||||
required: [:ap_id, :profile],
|
|
||||||
properties: %{
|
|
||||||
ap_id: %Schema{type: :string, description: "Profile or status ActivityPub ID"},
|
|
||||||
profile: %Schema{type: :string, description: "Remote profile webfinger"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def show_subscribe_form_operation do
|
|
||||||
%Operation{
|
|
||||||
tags: ["Remote interaction"],
|
|
||||||
summary: "Show remote subscribe form",
|
|
||||||
operationId: "UtilController.show_subscribe_form",
|
|
||||||
parameters: [],
|
|
||||||
responses: %{200 => Operation.response("Web Page", "test/html", %Schema{type: :string})}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp delete_account_request do
|
defp delete_account_request do
|
||||||
%Schema{
|
%Schema{
|
||||||
title: "AccountDeleteRequest",
|
title: "AccountDeleteRequest",
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ApiSpec.RemoteInteractionOperation do
|
||||||
|
alias OpenApiSpex.Operation
|
||||||
|
alias OpenApiSpex.Schema
|
||||||
|
|
||||||
|
import Pleroma.Web.ApiSpec.Helpers
|
||||||
|
|
||||||
|
def open_api_operation(action) do
|
||||||
|
operation = String.to_existing_atom("#{action}_operation")
|
||||||
|
apply(__MODULE__, operation, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def remote_interaction_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Remote interaction"],
|
||||||
|
summary: "Remote interaction",
|
||||||
|
operationId: "RemoteInteractionController.remote_interaction",
|
||||||
|
requestBody: request_body("Parameters", remote_interaction_request(), required: true),
|
||||||
|
responses: %{
|
||||||
|
200 =>
|
||||||
|
Operation.response("Remote interaction URL", "application/json", %Schema{type: :object})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp remote_interaction_request do
|
||||||
|
%Schema{
|
||||||
|
title: "RemoteInteractionRequest",
|
||||||
|
description: "POST body for remote interaction",
|
||||||
|
type: :object,
|
||||||
|
required: [:ap_id, :profile],
|
||||||
|
properties: %{
|
||||||
|
ap_id: %Schema{type: :string, description: "Profile or status ActivityPub ID"},
|
||||||
|
profile: %Schema{type: :string, description: "Remote profile webfinger"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def follow_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Remote interaction"],
|
||||||
|
summary: "Display follow form",
|
||||||
|
operationId: "RemoteInteractionController.follow",
|
||||||
|
parameters: [],
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("Web Page", "text/html", %Schema{type: :string}),
|
||||||
|
302 => Operation.response("Redirect to the status page", nil, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def do_follow_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Remote interaction"],
|
||||||
|
summary: "Perform follow activity",
|
||||||
|
operationId: "RemoteInteractionController.do_follow",
|
||||||
|
parameters: [],
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("Web page", "text/html", %Schema{type: :string}),
|
||||||
|
302 => Operation.response("Redirect to the account page", nil, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorize_interaction_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Remote interaction"],
|
||||||
|
summary: "Authorize remote interaction",
|
||||||
|
operationId: "RemoteInteractionController.authorize_interaction",
|
||||||
|
parameters: [],
|
||||||
|
responses: %{
|
||||||
|
302 => Operation.response("Redirect to remote_interaction path", nil, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def show_subscribe_form_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Remote interaction"],
|
||||||
|
summary: "Show remote subscribe form",
|
||||||
|
operationId: "RemoteInteractionController.show_subscribe_form",
|
||||||
|
parameters: [],
|
||||||
|
responses: %{200 => Operation.response("Web Page", "text/html", %Schema{type: :string})}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def remote_subscribe_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Remote interaction"],
|
||||||
|
summary: "Remote Subscribe",
|
||||||
|
operationId: "RemoteInteractionController.remote_subscribe",
|
||||||
|
parameters: [],
|
||||||
|
responses: %{200 => Operation.response("Web Page", "text/html", %Schema{type: :string})}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -17,11 +17,11 @@ defmodule Pleroma.Web.ApiSpec.RenderError do
|
||||||
def call(conn, errors) do
|
def call(conn, errors) do
|
||||||
errors =
|
errors =
|
||||||
Enum.map(errors, fn
|
Enum.map(errors, fn
|
||||||
%{name: nil, reason: :invalid_enum} = err ->
|
%OpenApiSpex.Cast.Error{name: nil, reason: :invalid_enum} = err ->
|
||||||
%OpenApiSpex.Cast.Error{err | name: err.value}
|
%{err | name: err.value}
|
||||||
|
|
||||||
%{name: nil} = err ->
|
%OpenApiSpex.Cast.Error{name: nil} = err ->
|
||||||
%OpenApiSpex.Cast.Error{err | name: List.last(err.path)}
|
%{err | name: List.last(err.path)}
|
||||||
|
|
||||||
err ->
|
err ->
|
||||||
err
|
err
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
||||||
acct: %Schema{type: :string},
|
acct: %Schema{type: :string},
|
||||||
avatar_static: %Schema{type: :string, format: :uri},
|
avatar_static: %Schema{type: :string, format: :uri},
|
||||||
avatar: %Schema{type: :string, format: :uri},
|
avatar: %Schema{type: :string, format: :uri},
|
||||||
|
avatar_description: %Schema{type: :string},
|
||||||
bot: %Schema{type: :boolean},
|
bot: %Schema{type: :boolean},
|
||||||
created_at: %Schema{type: :string, format: "date-time"},
|
created_at: %Schema{type: :string, format: "date-time"},
|
||||||
display_name: %Schema{type: :string},
|
display_name: %Schema{type: :string},
|
||||||
|
|
@ -31,6 +32,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
||||||
following_count: %Schema{type: :integer},
|
following_count: %Schema{type: :integer},
|
||||||
header_static: %Schema{type: :string, format: :uri},
|
header_static: %Schema{type: :string, format: :uri},
|
||||||
header: %Schema{type: :string, format: :uri},
|
header: %Schema{type: :string, format: :uri},
|
||||||
|
header_description: %Schema{type: :string},
|
||||||
id: FlakeID,
|
id: FlakeID,
|
||||||
locked: %Schema{type: :boolean},
|
locked: %Schema{type: :boolean},
|
||||||
note: %Schema{type: :string, format: :html},
|
note: %Schema{type: :string, format: :html},
|
||||||
|
|
@ -111,8 +113,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
||||||
nullable: true,
|
nullable: true,
|
||||||
description: "Favicon image of the user's instance"
|
description: "Favicon image of the user's instance"
|
||||||
},
|
},
|
||||||
avatar_description: %Schema{type: :string},
|
avatar_description: %Schema{type: :string, deprecated: true},
|
||||||
header_description: %Schema{type: :string}
|
header_description: %Schema{type: :string, deprecated: true}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
source: %Schema{
|
source: %Schema{
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do
|
||||||
requested: %Schema{type: :boolean},
|
requested: %Schema{type: :boolean},
|
||||||
showing_reblogs: %Schema{type: :boolean},
|
showing_reblogs: %Schema{type: :boolean},
|
||||||
subscribing: %Schema{type: :boolean},
|
subscribing: %Schema{type: :boolean},
|
||||||
notifying: %Schema{type: :boolean}
|
notifying: %Schema{type: :boolean},
|
||||||
|
mute_expires_at: %Schema{type: :string, format: "date-time", nullable: true},
|
||||||
|
block_expires_at: %Schema{type: :string, format: "date-time", nullable: true}
|
||||||
},
|
},
|
||||||
example: %{
|
example: %{
|
||||||
"blocked_by" => false,
|
"blocked_by" => false,
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,18 @@ defmodule Pleroma.Web.ApiSpec.Schemas.BookmarkFolder do
|
||||||
properties: %{
|
properties: %{
|
||||||
id: FlakeID,
|
id: FlakeID,
|
||||||
name: %Schema{type: :string, description: "Folder name"},
|
name: %Schema{type: :string, description: "Folder name"},
|
||||||
emoji: %Schema{type: :string, description: "Folder emoji", nullable: true}
|
emoji: %Schema{type: :string, description: "Folder emoji", nullable: true},
|
||||||
|
emoji_url: %Schema{
|
||||||
|
type: :string,
|
||||||
|
description: "URL of the folder emoji if it's a custom emoji, null otherwise",
|
||||||
|
nullable: true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
example: %{
|
example: %{
|
||||||
"id" => "9toJCu5YZW7O7gfvH6",
|
"id" => "9toJCu5YZW7O7gfvH6",
|
||||||
"name" => "Read later",
|
"name" => "Read later",
|
||||||
"emoji" => nil
|
"emoji" => nil,
|
||||||
|
"emoji_url" => nil
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.List do
|
||||||
type: :object,
|
type: :object,
|
||||||
properties: %{
|
properties: %{
|
||||||
id: %Schema{type: :string, description: "The internal database ID of the list"},
|
id: %Schema{type: :string, description: "The internal database ID of the list"},
|
||||||
title: %Schema{type: :string, description: "The user-defined title of the list"}
|
title: %Schema{type: :string, description: "The user-defined title of the list"},
|
||||||
|
exclusive: %Schema{
|
||||||
|
type: :boolean,
|
||||||
|
description: "Whether members of the list should be removed from the “Home” feed"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
example: %{
|
example: %{
|
||||||
"id" => "12249",
|
"id" => "12249",
|
||||||
|
|
|
||||||
|
|
@ -222,8 +222,8 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id),
|
with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id),
|
||||||
object = %Object{} <- Object.normalize(activity, fetch: false),
|
object = %Object{} <- Object.normalize(activity, fetch: false),
|
||||||
{_, nil} <- {:existing_announce, Utils.get_existing_announce(user.ap_id, object)},
|
{_, nil} <- {:existing_announce, Utils.get_existing_announce(user.ap_id, object)},
|
||||||
public = public_announce?(object, params),
|
visibility = announce_visibility(object, params),
|
||||||
{:ok, announce, _} <- Builder.announce(user, object, public: public),
|
{:ok, announce, _} <- Builder.announce(user, object, visibility: visibility),
|
||||||
{:ok, activity, _} <- Pipeline.common_pipeline(announce, local: true) do
|
{:ok, activity, _} <- Pipeline.common_pipeline(announce, local: true) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
|
|
@ -407,13 +407,11 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp public_announce?(_, %{visibility: visibility})
|
def announce_visibility(_, %{visibility: visibility})
|
||||||
when visibility in ~w{public unlisted private direct},
|
when visibility in ~w{public unlisted private direct local},
|
||||||
do: visibility in ~w(public unlisted)
|
do: visibility
|
||||||
|
|
||||||
defp public_announce?(object, _) do
|
def announce_visibility(object, _), do: Visibility.get_visibility(object)
|
||||||
Visibility.public?(object)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec get_visibility(map(), map() | nil, Participation.t() | nil) ::
|
@spec get_visibility(map(), map() | nil, Participation.t() | nil) ::
|
||||||
{String.t() | nil, String.t() | nil}
|
{String.t() | nil, String.t() | nil}
|
||||||
|
|
@ -709,6 +707,22 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def assign_report_to_account(activity_ids, user) when is_list(activity_ids) do
|
||||||
|
case Utils.assign_report_to_account(activity_ids, user) do
|
||||||
|
:ok -> {:ok, activity_ids}
|
||||||
|
_ -> {:error, dgettext("errors", "Could not assign account")}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def assign_report_to_account(activity_id, user) do
|
||||||
|
with %Activity{} = activity <- Activity.get_by_id(activity_id) do
|
||||||
|
Utils.assign_report_to_account(activity, user)
|
||||||
|
else
|
||||||
|
nil -> {:error, :not_found}
|
||||||
|
_ -> {:error, dgettext("errors", "Could not assign account")}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@spec update_activity_scope(String.t(), map()) :: {:ok, any()} | {:error, any()}
|
@spec update_activity_scope(String.t(), map()) :: {:ok, any()} | {:error, any()}
|
||||||
def update_activity_scope(activity_id, opts \\ %{}) do
|
def update_activity_scope(activity_id, opts \\ %{}) do
|
||||||
with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
|
with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
||||||
|> validate()
|
|> validate()
|
||||||
end
|
end
|
||||||
|
|
||||||
defp listen_object(draft) do
|
defp listen_object(%__MODULE__{} = draft) do
|
||||||
object =
|
object =
|
||||||
draft.params
|
draft.params
|
||||||
|> Map.take([:album, :artist, :title, :length])
|
|> Map.take([:album, :artist, :title, :length])
|
||||||
|
|
@ -99,34 +99,34 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
||||||
|> Map.put("cc", draft.cc)
|
|> Map.put("cc", draft.cc)
|
||||||
|> Map.put("actor", draft.user.ap_id)
|
|> Map.put("actor", draft.user.ap_id)
|
||||||
|
|
||||||
%__MODULE__{draft | object: object}
|
%{draft | object: object}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp put_params(draft, params) do
|
defp put_params(%__MODULE__{} = draft, params) do
|
||||||
params = Map.put_new(params, :in_reply_to_status_id, params[:in_reply_to_id])
|
params = Map.put_new(params, :in_reply_to_status_id, params[:in_reply_to_id])
|
||||||
%__MODULE__{draft | params: params}
|
%{draft | params: params}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp status(%{params: %{status: status}} = draft) do
|
defp status(%__MODULE__{params: %{status: status}} = draft) do
|
||||||
%__MODULE__{draft | status: String.trim(status)}
|
%{draft | status: String.trim(status)}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp summary(%{params: params} = draft) do
|
defp summary(%__MODULE__{params: params} = draft) do
|
||||||
%__MODULE__{draft | summary: Map.get(params, :spoiler_text, "")}
|
%{draft | summary: Map.get(params, :spoiler_text, "")}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp full_payload(%{status: status, summary: summary} = draft) do
|
defp full_payload(%__MODULE__{status: status, summary: summary} = draft) do
|
||||||
full_payload = String.trim(status <> summary)
|
full_payload = String.trim(status <> summary)
|
||||||
|
|
||||||
case Utils.validate_character_limit(full_payload, draft.attachments) do
|
case Utils.validate_character_limit(full_payload, draft.attachments) do
|
||||||
:ok -> %__MODULE__{draft | full_payload: full_payload}
|
:ok -> %{draft | full_payload: full_payload}
|
||||||
{:error, message} -> add_error(draft, message)
|
{:error, message} -> add_error(draft, message)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp attachments(%{params: params} = draft) do
|
defp attachments(%__MODULE__{params: params} = draft) do
|
||||||
attachments = Utils.attachments_from_ids(params, draft.user)
|
attachments = Utils.attachments_from_ids(params, draft.user)
|
||||||
draft = %__MODULE__{draft | attachments: attachments}
|
draft = %{draft | attachments: attachments}
|
||||||
|
|
||||||
case Utils.validate_attachments_count(attachments) do
|
case Utils.validate_attachments_count(attachments) do
|
||||||
:ok -> draft
|
:ok -> draft
|
||||||
|
|
@ -134,9 +134,10 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp in_reply_to(%{params: %{in_reply_to_status_id: ""}} = draft), do: draft
|
defp in_reply_to(%__MODULE__{params: %{in_reply_to_status_id: ""}} = draft), do: draft
|
||||||
|
|
||||||
defp in_reply_to(%{params: %{in_reply_to_status_id: id}} = draft) when is_binary(id) do
|
defp in_reply_to(%__MODULE__{params: %{in_reply_to_status_id: id}} = draft)
|
||||||
|
when is_binary(id) do
|
||||||
# If a post was deleted all its activities (except the newly added Delete) are purged too,
|
# If a post was deleted all its activities (except the newly added Delete) are purged too,
|
||||||
# thus lookup by Create db ID will yield nil just as if it never existed in the first place.
|
# thus lookup by Create db ID will yield nil just as if it never existed in the first place.
|
||||||
#
|
#
|
||||||
|
|
@ -148,7 +149,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
||||||
with %Activity{} = activity <- Activity.get_by_id(id),
|
with %Activity{} = activity <- Activity.get_by_id(id),
|
||||||
true <- Visibility.visible_for_user?(activity, draft.user),
|
true <- Visibility.visible_for_user?(activity, draft.user),
|
||||||
{_, type} when type in ["Create", "Announce"] <- {:type, activity.data["type"]} do
|
{_, type} when type in ["Create", "Announce"] <- {:type, activity.data["type"]} do
|
||||||
%__MODULE__{draft | in_reply_to: activity}
|
%{draft | in_reply_to: activity}
|
||||||
else
|
else
|
||||||
nil ->
|
nil ->
|
||||||
add_error(draft, dgettext("errors", "Cannot reply to a deleted status"))
|
add_error(draft, dgettext("errors", "Cannot reply to a deleted status"))
|
||||||
|
|
@ -166,40 +167,43 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp in_reply_to(%{params: %{in_reply_to_status_id: %Activity{} = in_reply_to}} = draft) do
|
defp in_reply_to(
|
||||||
%__MODULE__{draft | in_reply_to: in_reply_to}
|
%__MODULE__{params: %{in_reply_to_status_id: %Activity{} = in_reply_to}} = draft
|
||||||
|
) do
|
||||||
|
%{draft | in_reply_to: in_reply_to}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp in_reply_to(draft), do: draft
|
defp in_reply_to(draft), do: draft
|
||||||
|
|
||||||
defp quote_post(%{params: %{quoted_status_id: id}} = draft) when not_empty_string(id) do
|
defp quote_post(%__MODULE__{params: %{quoted_status_id: id}} = draft)
|
||||||
|
when not_empty_string(id) do
|
||||||
case Activity.get_by_id_with_object(id) do
|
case Activity.get_by_id_with_object(id) do
|
||||||
%Activity{} = activity ->
|
%Activity{} = activity ->
|
||||||
%__MODULE__{draft | quote_post: activity}
|
%{draft | quote_post: activity}
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
draft
|
draft
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp quote_post(%{params: %{quote_id: id}} = draft) when not_empty_string(id) do
|
defp quote_post(%__MODULE__{params: %{quote_id: id}} = draft) when not_empty_string(id) do
|
||||||
quote_post(%{draft | params: Map.put(draft.params, :quoted_status_id, id)})
|
quote_post(%{draft | params: Map.put(draft.params, :quoted_status_id, id)})
|
||||||
end
|
end
|
||||||
|
|
||||||
defp quote_post(draft), do: draft
|
defp quote_post(draft), do: draft
|
||||||
|
|
||||||
defp in_reply_to_conversation(draft) do
|
defp in_reply_to_conversation(%__MODULE__{} = draft) do
|
||||||
in_reply_to_conversation = Participation.get(draft.params[:in_reply_to_conversation_id])
|
in_reply_to_conversation = Participation.get(draft.params[:in_reply_to_conversation_id])
|
||||||
%__MODULE__{draft | in_reply_to_conversation: in_reply_to_conversation}
|
%{draft | in_reply_to_conversation: in_reply_to_conversation}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp visibility(%{params: params} = draft) do
|
defp visibility(%__MODULE__{params: params} = draft) do
|
||||||
case CommonAPI.get_visibility(params, draft.in_reply_to, draft.in_reply_to_conversation) do
|
case CommonAPI.get_visibility(params, draft.in_reply_to, draft.in_reply_to_conversation) do
|
||||||
{visibility, "direct"} when visibility != "direct" ->
|
{visibility, "direct"} when visibility != "direct" ->
|
||||||
add_error(draft, dgettext("errors", "The message visibility must be direct"))
|
add_error(draft, dgettext("errors", "The message visibility must be direct"))
|
||||||
|
|
||||||
{visibility, _} ->
|
{visibility, _} ->
|
||||||
%__MODULE__{draft | visibility: visibility}
|
%{draft | visibility: visibility}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -215,7 +219,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
defp quoting_visibility(%{quote_post: %Activity{}} = draft) do
|
defp quoting_visibility(%__MODULE__{quote_post: %Activity{}} = draft) do
|
||||||
with %Object{} = object <- Object.normalize(draft.quote_post, fetch: false),
|
with %Object{} = object <- Object.normalize(draft.quote_post, fetch: false),
|
||||||
true <- can_quote?(draft, object, Visibility.get_visibility(object)) do
|
true <- can_quote?(draft, object, Visibility.get_visibility(object)) do
|
||||||
draft
|
draft
|
||||||
|
|
@ -226,24 +230,24 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
||||||
|
|
||||||
defp quoting_visibility(draft), do: draft
|
defp quoting_visibility(draft), do: draft
|
||||||
|
|
||||||
defp expires_at(draft) do
|
defp expires_at(%__MODULE__{} = draft) do
|
||||||
case CommonAPI.check_expiry_date(draft.params[:expires_in]) do
|
case CommonAPI.check_expiry_date(draft.params[:expires_in]) do
|
||||||
{:ok, expires_at} -> %__MODULE__{draft | expires_at: expires_at}
|
{:ok, expires_at} -> %{draft | expires_at: expires_at}
|
||||||
{:error, message} -> add_error(draft, message)
|
{:error, message} -> add_error(draft, message)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp poll(draft) do
|
defp poll(%__MODULE__{} = draft) do
|
||||||
case Utils.make_poll_data(draft.params) do
|
case Utils.make_poll_data(draft.params) do
|
||||||
{:ok, {poll, poll_emoji}} ->
|
{:ok, {poll, poll_emoji}} ->
|
||||||
%__MODULE__{draft | extra: poll, emoji: Map.merge(draft.emoji, poll_emoji)}
|
%{draft | extra: poll, emoji: Map.merge(draft.emoji, poll_emoji)}
|
||||||
|
|
||||||
{:error, message} ->
|
{:error, message} ->
|
||||||
add_error(draft, message)
|
add_error(draft, message)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp content(%{mentions: mentions} = draft) do
|
defp content(%__MODULE__{mentions: mentions} = draft) do
|
||||||
{content_html, mentioned_users, tags} = Utils.make_content_html(draft)
|
{content_html, mentioned_users, tags} = Utils.make_content_html(draft)
|
||||||
|
|
||||||
mentioned_ap_ids =
|
mentioned_ap_ids =
|
||||||
|
|
@ -254,25 +258,25 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
||||||
|> Kernel.++(mentioned_ap_ids)
|
|> Kernel.++(mentioned_ap_ids)
|
||||||
|> Utils.get_addressed_users(draft.params[:to])
|
|> Utils.get_addressed_users(draft.params[:to])
|
||||||
|
|
||||||
%__MODULE__{draft | content_html: content_html, mentions: mentions, tags: tags}
|
%{draft | content_html: content_html, mentions: mentions, tags: tags}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp to_and_cc(draft) do
|
defp to_and_cc(%__MODULE__{} = draft) do
|
||||||
{to, cc} = Utils.get_to_and_cc(draft)
|
{to, cc} = Utils.get_to_and_cc(draft)
|
||||||
%__MODULE__{draft | to: to, cc: cc}
|
%{draft | to: to, cc: cc}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp context(draft) do
|
defp context(%__MODULE__{} = draft) do
|
||||||
context = Utils.make_context(draft.in_reply_to, draft.in_reply_to_conversation)
|
context = Utils.make_context(draft.in_reply_to, draft.in_reply_to_conversation)
|
||||||
%__MODULE__{draft | context: context}
|
%{draft | context: context}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp sensitive(draft) do
|
defp sensitive(%__MODULE__{} = draft) do
|
||||||
sensitive = draft.params[:sensitive]
|
sensitive = draft.params[:sensitive]
|
||||||
%__MODULE__{draft | sensitive: sensitive}
|
%{draft | sensitive: sensitive}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp language(draft) do
|
defp language(%__MODULE__{} = draft) do
|
||||||
language =
|
language =
|
||||||
with language <- draft.params[:language],
|
with language <- draft.params[:language],
|
||||||
true <- good_locale_code?(language) do
|
true <- good_locale_code?(language) do
|
||||||
|
|
@ -281,10 +285,10 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
||||||
_ -> LanguageDetector.detect(draft.content_html <> " " <> draft.summary)
|
_ -> LanguageDetector.detect(draft.content_html <> " " <> draft.summary)
|
||||||
end
|
end
|
||||||
|
|
||||||
%__MODULE__{draft | language: language}
|
%{draft | language: language}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp object(draft) do
|
defp object(%__MODULE__{} = draft) do
|
||||||
emoji = Map.merge(Pleroma.Emoji.Formatter.get_emoji_map(draft.full_payload), draft.emoji)
|
emoji = Map.merge(Pleroma.Emoji.Formatter.get_emoji_map(draft.full_payload), draft.emoji)
|
||||||
|
|
||||||
# Sometimes people create posts with subject containing emoji,
|
# Sometimes people create posts with subject containing emoji,
|
||||||
|
|
@ -325,15 +329,15 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
||||||
|> Map.put("generator", draft.params[:generator])
|
|> Map.put("generator", draft.params[:generator])
|
||||||
|> Map.put("language", draft.language)
|
|> Map.put("language", draft.language)
|
||||||
|
|
||||||
%__MODULE__{draft | object: object}
|
%{draft | object: object}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp preview?(draft) do
|
defp preview?(%__MODULE__{} = draft) do
|
||||||
preview? = Pleroma.Web.Utils.Params.truthy_param?(draft.params[:preview])
|
preview? = Pleroma.Web.Utils.Params.truthy_param?(draft.params[:preview])
|
||||||
%__MODULE__{draft | preview?: preview?}
|
%{draft | preview?: preview?}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp changes(draft) do
|
defp changes(%__MODULE__{} = draft) do
|
||||||
direct? = draft.visibility == "direct"
|
direct? = draft.visibility == "direct"
|
||||||
additional = %{"cc" => draft.cc, "directMessage" => direct?}
|
additional = %{"cc" => draft.cc, "directMessage" => direct?}
|
||||||
|
|
||||||
|
|
@ -353,14 +357,14 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
||||||
}
|
}
|
||||||
|> Utils.maybe_add_list_data(draft.user, draft.visibility)
|
|> Utils.maybe_add_list_data(draft.user, draft.visibility)
|
||||||
|
|
||||||
%__MODULE__{draft | changes: changes}
|
%{draft | changes: changes}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp with_valid(%{valid?: true} = draft, func), do: func.(draft)
|
defp with_valid(%{valid?: true} = draft, func), do: func.(draft)
|
||||||
defp with_valid(draft, _func), do: draft
|
defp with_valid(draft, _func), do: draft
|
||||||
|
|
||||||
defp add_error(draft, message) do
|
defp add_error(%__MODULE__{} = draft, message) do
|
||||||
%__MODULE__{draft | valid?: false, errors: [message | draft.errors]}
|
%{draft | valid?: false, errors: [message | draft.errors]}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp validate(%{valid?: true} = draft), do: {:ok, draft}
|
defp validate(%{valid?: true} = draft), do: {:ok, draft}
|
||||||
|
|
|
||||||
|
|
@ -75,48 +75,70 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
||||||
{Enum.map(participation.recipients, & &1.ap_id), []}
|
{Enum.map(participation.recipients, & &1.ap_id), []}
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_to_and_cc(%{visibility: visibility} = draft) when visibility in ["public", "local"] do
|
def get_to_and_cc(%{visibility: visibility} = draft) do
|
||||||
to =
|
# If the OP is a DM already, add the implicit actor
|
||||||
case visibility do
|
mentions =
|
||||||
"public" -> [Pleroma.Constants.as_public() | draft.mentions]
|
if visibility == "direct" && draft.in_reply_to && Visibility.direct?(draft.in_reply_to) do
|
||||||
"local" -> [Utils.as_local_public() | draft.mentions]
|
Enum.uniq([draft.in_reply_to.data["actor"] | draft.mentions])
|
||||||
|
else
|
||||||
|
draft.mentions
|
||||||
end
|
end
|
||||||
|
|
||||||
cc = [draft.user.follower_address]
|
get_to_and_cc_for_visibility(
|
||||||
|
visibility,
|
||||||
if draft.in_reply_to do
|
draft.user.follower_address,
|
||||||
{Enum.uniq([draft.in_reply_to.data["actor"] | to]), cc}
|
draft.in_reply_to && draft.in_reply_to.data["actor"],
|
||||||
else
|
mentions
|
||||||
{to, cc}
|
)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_to_and_cc(%{visibility: "unlisted"} = draft) do
|
def get_to_and_cc_for_visibility("public", follower_collection, parent_actor, mentions) do
|
||||||
to = [draft.user.follower_address | draft.mentions]
|
scope_addr = Pleroma.Constants.as_public()
|
||||||
cc = [Pleroma.Constants.as_public()]
|
|
||||||
|
|
||||||
if draft.in_reply_to do
|
to =
|
||||||
{Enum.uniq([draft.in_reply_to.data["actor"] | to]), cc}
|
if parent_actor,
|
||||||
else
|
do: Enum.uniq([parent_actor, scope_addr | mentions]),
|
||||||
{to, cc}
|
else: [scope_addr | mentions]
|
||||||
end
|
|
||||||
|
{to, [follower_collection]}
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_to_and_cc(%{visibility: "private"} = draft) do
|
def get_to_and_cc_for_visibility("local", follower_collection, parent_actor, mentions) do
|
||||||
{to, cc} = get_to_and_cc(struct(draft, visibility: "direct"))
|
recipients =
|
||||||
{[draft.user.follower_address | to], cc}
|
if parent_actor,
|
||||||
|
do: Enum.uniq([parent_actor | mentions]),
|
||||||
|
else: mentions
|
||||||
|
|
||||||
|
to = [
|
||||||
|
Utils.as_local_public()
|
||||||
|
| Enum.filter(recipients, fn addr ->
|
||||||
|
String.starts_with?(addr, Pleroma.Web.Endpoint.url() <> "/")
|
||||||
|
end)
|
||||||
|
]
|
||||||
|
|
||||||
|
{to, [follower_collection]}
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_to_and_cc(%{visibility: "direct"} = draft) do
|
def get_to_and_cc_for_visibility("unlisted", follower_collection, parent_actor, mentions) do
|
||||||
# If the OP is a DM already, add the implicit actor.
|
to =
|
||||||
if draft.in_reply_to && Visibility.direct?(draft.in_reply_to) do
|
if parent_actor,
|
||||||
{Enum.uniq([draft.in_reply_to.data["actor"] | draft.mentions]), []}
|
do: Enum.uniq([parent_actor, follower_collection | mentions]),
|
||||||
else
|
else: [follower_collection | mentions]
|
||||||
{draft.mentions, []}
|
|
||||||
end
|
{to, [Pleroma.Constants.as_public()]}
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_to_and_cc(%{visibility: {:list, _}, mentions: mentions}), do: {mentions, []}
|
def get_to_and_cc_for_visibility("private", follower_collection, _, mentions) do
|
||||||
|
{[follower_collection | mentions], []}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_to_and_cc_for_visibility("direct", _, _, mentions) do
|
||||||
|
{mentions, []}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_to_and_cc_for_visibility({:list, _}, _, _, mentions) do
|
||||||
|
{mentions, []}
|
||||||
|
end
|
||||||
|
|
||||||
def get_addressed_users(_, to) when is_list(to) do
|
def get_addressed_users(_, to) when is_list(to) do
|
||||||
User.get_ap_ids_by_nicknames(to)
|
User.get_ap_ids_by_nicknames(to)
|
||||||
|
|
|
||||||
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