forked from YokaiRick/akkoma
Merge remote-tracking branch 'remotes/upstream/develop' into 1427-oauth-admin-scopes
# Conflicts: # CHANGELOG.md
This commit is contained in:
commit
835ac2157c
87 changed files with 2178 additions and 835 deletions
|
@ -1,3 +1,3 @@
|
|||
[
|
||||
inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}", "priv/repo/migrations/*.exs"]
|
||||
inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}", "priv/repo/migrations/*.exs", "priv/scrubbers/*.ex"]
|
||||
]
|
||||
|
|
|
@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- OStatus: Extract RSS functionality
|
||||
- Deprecated `User.Info` embedded schema (fields moved to `User`)
|
||||
- Store status data inside Flag activity
|
||||
- Deprecated (reorganized as `UserRelationship` entity) User fields with user AP IDs (`blocks`, `mutes`, `muted_reblogs`, `muted_notifications`, `subscribers`).
|
||||
<details>
|
||||
<summary>API Changes</summary>
|
||||
|
||||
|
@ -35,9 +36,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Mastodon API: `pleroma.thread_muted` to the Status entity
|
||||
- Mastodon API: Mark the direct conversation as read for the author when they send a new direct message
|
||||
- Mastodon API, streaming: Add `pleroma.direct_conversation_id` to the `conversation` stream event payload.
|
||||
- Admin API: Render whole status in grouped reports
|
||||
- Mastodon API: User timelines will now respect blocks, unless you are getting the user timeline of somebody you blocked (which would be empty otherwise).
|
||||
</details>
|
||||
|
||||
### Added
|
||||
- `:chat_limit` option to limit chat characters.
|
||||
- Refreshing poll results for remote polls
|
||||
- Authentication: Added rate limit for password-authorized actions / login existence checks
|
||||
- Static Frontend: Add the ability to render user profiles and notices server-side without requiring JS app.
|
||||
|
@ -45,6 +49,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Mix task to list all users (`mix pleroma.user list`)
|
||||
- Support for `X-Forwarded-For` and similar HTTP headers which used by reverse proxies to pass a real user IP address to the backend. Must not be enabled unless your instance is behind at least one reverse proxy (such as Nginx, Apache HTTPD or Varnish Cache).
|
||||
- MRF: New module which handles incoming posts based on their age. By default, all incoming posts that are older than 2 days will be unlisted and not shown to their followers.
|
||||
- User notification settings: Add `privacy_option` option.
|
||||
- OAuth: admin scopes support (relevant setting: `[:auth, :enforce_oauth_admin_scope_usage]`).
|
||||
<details>
|
||||
<summary>API Changes</summary>
|
||||
|
@ -79,11 +84,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
### Fixed
|
||||
- Report emails now include functional links to profiles of remote user accounts
|
||||
- Not being able to log in to some third-party apps when logged in to MastoFE
|
||||
- MRF: `Delete` activities being exempt from MRF policies
|
||||
- OTP releases: Not being able to configure OAuth expired token cleanup interval
|
||||
- OTP releases: Not being able to configure HTML sanitization policy
|
||||
<details>
|
||||
<summary>API Changes</summary>
|
||||
|
||||
- Mastodon API: Fix private and direct statuses not being filtered out from the public timeline for an authenticated user (`GET /api/v1/timelines/public`)
|
||||
- Mastodon API: Inability to get some local users by nickname in `/api/v1/accounts/:id_or_nickname`
|
||||
- AdminAPI: If some status received reports both in the "new" format and "old" format it was considered reports on two different statuses (in the context of grouped reports)
|
||||
- Admin API: Error when trying to update reports in the "old" format
|
||||
</details>
|
||||
|
||||
|
|
|
@ -225,6 +225,7 @@
|
|||
notify_email: "noreply@example.com",
|
||||
description: "A Pleroma instance, an alternative fediverse server",
|
||||
limit: 5_000,
|
||||
chat_limit: 5_000,
|
||||
remote_limit: 100_000,
|
||||
upload_limit: 16_000_000,
|
||||
avatar_upload_limit: 2_000_000,
|
||||
|
|
|
@ -103,6 +103,7 @@ The `type` value is `move`. Has an additional field:
|
|||
Accepts additional parameters:
|
||||
|
||||
- `exclude_visibilities`: will exclude the notifications for activities with the given visibilities. The parameter accepts an array of visibility types (`public`, `unlisted`, `private`, `direct`). Usage example: `GET /api/v1/notifications?exclude_visibilities[]=direct&exclude_visibilities[]=private`.
|
||||
- `with_move`: boolean, when set to `true` will include Move notifications. `false` by default.
|
||||
|
||||
## POST `/api/v1/statuses`
|
||||
|
||||
|
|
|
@ -302,6 +302,7 @@ See [Admin-API](admin_api.md)
|
|||
* `follows`: BOOLEAN field, receives notifications from people the user follows
|
||||
* `remote`: BOOLEAN field, receives notifications from people on remote instances
|
||||
* `local`: BOOLEAN field, receives notifications from people on the local instance
|
||||
* `privacy_option`: BOOLEAN field. When set to true, it removes the contents of a message from the push notification.
|
||||
* Response: JSON. Returns `{"status": "success"}` if the update was successful, otherwise returns `{"error": "error_msg"}`
|
||||
|
||||
## `/api/pleroma/healthcheck`
|
||||
|
|
|
@ -3,17 +3,26 @@
|
|||
!!! danger
|
||||
This is a Work In Progress, not usable just yet.
|
||||
|
||||
Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl config` and in case of source installs it's
|
||||
`mix pleroma.config`.
|
||||
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
|
||||
|
||||
## Transfer config from file to DB.
|
||||
|
||||
```sh
|
||||
$PREFIX migrate_to_db
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl config migrate_to_db
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.config migrate_to_db
|
||||
```
|
||||
|
||||
|
||||
## Transfer config from DB to `config/env.exported_from_db.secret.exs`
|
||||
|
||||
```sh
|
||||
$PREFIX migrate_from_db <env>
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl config migrate_from_db <env>
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.config migrate_from_db <env>
|
||||
```
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Database maintenance tasks
|
||||
|
||||
Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl database` and in case of source installs it's `mix pleroma.database`.
|
||||
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
|
||||
|
||||
!!! danger
|
||||
These mix tasks can take a long time to complete. Many of them were written to address specific database issues that happened because of bugs in migrations or other specific scenarios. Do not run these tasks "just in case" if everything is fine your instance.
|
||||
|
@ -9,8 +9,12 @@ Every command should be ran with a prefix, in case of OTP releases it is `./bin/
|
|||
|
||||
Replaces embedded objects with references to them in the `objects` table. Only needs to be ran once if the instance was created before Pleroma 1.0.5. The reason why this is not a migration is because it could significantly increase the database size after being ran, however after this `VACUUM FULL` will be able to reclaim about 20% (really depends on what is in the database, your mileage may vary) of the db size before the migration.
|
||||
|
||||
```sh
|
||||
$PREFIX remove_embedded_objects [<options>]
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl database remove_embedded_objects [<options>]
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.database remove_embedded_objects [<options>]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
@ -20,11 +24,15 @@ $PREFIX remove_embedded_objects [<options>]
|
|||
|
||||
This will prune remote posts older than 90 days (configurable with [`config :pleroma, :instance, remote_post_retention_days`](../../configuration/cheatsheet.md#instance)) from the database, they will be refetched from source when accessed.
|
||||
|
||||
!!! note
|
||||
The disk space will only be reclaimed after `VACUUM FULL`
|
||||
!!! danger
|
||||
The disk space will only be reclaimed after `VACUUM FULL`. You may run out of disk space during the execution of the task or vacuuming if you don't have about 1/3rds of the database size free.
|
||||
|
||||
```sh
|
||||
$PREFIX pleroma.database prune_objects [<options>]
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl database prune_objects [<options>]
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.database prune_objects [<options>]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
@ -34,18 +42,30 @@ $PREFIX pleroma.database prune_objects [<options>]
|
|||
|
||||
Can be safely re-run
|
||||
|
||||
```sh
|
||||
$PREFIX bump_all_conversations
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl database bump_all_conversations
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.database bump_all_conversations
|
||||
```
|
||||
|
||||
## Remove duplicated items from following and update followers count for all users
|
||||
|
||||
```sh
|
||||
$PREFIX update_users_following_followers_counts
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl database update_users_following_followers_counts
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.database update_users_following_followers_counts
|
||||
```
|
||||
|
||||
## Fix the pre-existing "likes" collections for all objects
|
||||
|
||||
```sh
|
||||
$PREFIX fix_likes_collections
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl database fix_likes_collections
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.database fix_likes_collections
|
||||
```
|
||||
|
|
|
@ -1,13 +1,24 @@
|
|||
# Managing digest emails
|
||||
Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl digest` and in case of source installs it's `mix pleroma.digest`.
|
||||
|
||||
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
|
||||
|
||||
## Send digest email since given date (user registration date by default) ignoring user activity status.
|
||||
|
||||
```sh
|
||||
$PREFIX test <nickname> [<since_date>]
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl digest test <nickname> [<since_date>]
|
||||
```
|
||||
|
||||
Example:
|
||||
```sh
|
||||
$PREFIX test donaldtheduck 2019-05-20
|
||||
```sh tab="From Source"
|
||||
mix pleroma.digest test <nickname> [<since_date>]
|
||||
```
|
||||
|
||||
|
||||
Example:
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl digest test donaldtheduck 2019-05-20
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.digest test donaldtheduck 2019-05-20
|
||||
```
|
||||
|
||||
|
|
|
@ -1,28 +1,44 @@
|
|||
# Managing emoji packs
|
||||
|
||||
Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl emoji` and in case of source installs it's `mix pleroma.emoji`.
|
||||
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
|
||||
|
||||
## Lists emoji packs and metadata specified in the manifest
|
||||
|
||||
```sh
|
||||
$PREFIX ls-packs [<options>]
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl emoji ls-packs [<options>]
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.emoji ls-packs [<options>]
|
||||
```
|
||||
|
||||
|
||||
### Options
|
||||
- `-m, --manifest PATH/URL` - path to a custom manifest, it can either be an URL starting with `http`, in that case the manifest will be fetched from that address, or a local path
|
||||
|
||||
## Fetch, verify and install the specified packs from the manifest into `STATIC-DIR/emoji/PACK-NAME`
|
||||
```sh
|
||||
$PREFIX get-packs [<options>] <packs>
|
||||
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl emoji get-packs [<options>] <packs>
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.emoji get-packs [<options>] <packs>
|
||||
```
|
||||
|
||||
### Options
|
||||
- `-m, --manifest PATH/URL` - same as [`ls-packs`](#ls-packs)
|
||||
|
||||
## Create a new manifest entry and a file list from the specified remote pack file
|
||||
```sh
|
||||
$PREFIX gen-pack PACK-URL
|
||||
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl emoji gen-pack PACK-URL
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.emoji gen-pack PACK-URL
|
||||
```
|
||||
|
||||
Currently, only .zip archives are recognized as remote pack files and packs are therefore assumed to be zip archives. This command is intended to run interactively and will first ask you some basic questions about the pack, then download the remote file and generate an SHA256 checksum for it, then generate an emoji file list for you.
|
||||
|
||||
The manifest entry will either be written to a newly created `index.json` file or appended to the existing one, *replacing* the old pack with the same name if it was in the file previously.
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
Every command should be ran as the `pleroma` user from it's home directory. For example if you are superuser, you would have to wrap the command in `su pleroma -s $SHELL -lc "$COMMAND"`.
|
||||
|
||||
??? note "From source note about `MIX_ENV`"
|
||||
|
||||
The `mix` command should be prefixed with the name of environment your Pleroma server is running in, usually it's `MIX_ENV=prod`
|
|
@ -1,12 +1,17 @@
|
|||
# Managing instance configuration
|
||||
|
||||
Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl instance` and in case of source installs it's `mix pleroma.instance`.
|
||||
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
|
||||
|
||||
## Generate a new configuration file
|
||||
```sh
|
||||
$PREFIX gen [<options>]
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl instance gen [<options>]
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.instance gen [<options>]
|
||||
```
|
||||
|
||||
|
||||
If any of the options are left unspecified, you will be prompted interactively.
|
||||
|
||||
### Options
|
||||
|
|
|
@ -1,30 +1,33 @@
|
|||
# Managing relays
|
||||
|
||||
Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl relay` and in case of source installs it's `mix pleroma.relay`.
|
||||
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
|
||||
|
||||
## Follow a relay
|
||||
```sh
|
||||
$PREFIX follow <relay_url>
|
||||
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl relay follow <relay_url>
|
||||
```
|
||||
|
||||
Example:
|
||||
```sh
|
||||
$PREFIX follow https://example.org/relay
|
||||
```sh tab="From Source"
|
||||
mix pleroma.relay follow <relay_url>
|
||||
```
|
||||
|
||||
## Unfollow a remote relay
|
||||
|
||||
```sh
|
||||
$PREFIX unfollow <relay_url>
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl relay unfollow <relay_url>
|
||||
```
|
||||
|
||||
Example:
|
||||
```sh
|
||||
$PREFIX unfollow https://example.org/relay
|
||||
```sh tab="From Source"
|
||||
mix pleroma.relay unfollow <relay_url>
|
||||
```
|
||||
|
||||
## List relay subscriptions
|
||||
|
||||
```sh
|
||||
$PREFIX list
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl relay list
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.relay list
|
||||
```
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
# Managing uploads
|
||||
|
||||
Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl uploads` and in case of source installs it's `mix pleroma.uploads`.
|
||||
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
|
||||
|
||||
## Migrate uploads from local to remote storage
|
||||
```sh
|
||||
$PREFIX migrate_local <target_uploader> [<options>]
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl uploads migrate_local <target_uploader> [<options>]
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.uploads migrate_local <target_uploader> [<options>]
|
||||
```
|
||||
|
||||
### Options
|
||||
- `--delete` - delete local uploads after migrating them to the target uploader
|
||||
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
# Managing users
|
||||
|
||||
Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl user` and in case of source installs it's `mix pleroma.user`.
|
||||
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
|
||||
|
||||
## Create a user
|
||||
```sh
|
||||
$PREFIX new <nickname> <email> [<options>]
|
||||
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl user new <email> [<options>]
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.user new <email> [<options>]
|
||||
```
|
||||
|
||||
|
||||
### Options
|
||||
- `--name <name>` - the user's display name
|
||||
- `--bio <bio>` - the user's bio
|
||||
|
@ -16,84 +22,159 @@ $PREFIX new <nickname> <email> [<options>]
|
|||
- `-y`, `--assume-yes`/`--no-assume-yes` - whether to assume yes to all questions
|
||||
|
||||
## List local users
|
||||
```sh
|
||||
$PREFIX list
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl user list
|
||||
```
|
||||
|
||||
## Generate an invite link
|
||||
```sh
|
||||
$PREFIX invite [<options>]
|
||||
```sh tab="From Source"
|
||||
mix pleroma.user list
|
||||
```
|
||||
|
||||
|
||||
## Generate an invite link
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl user invite [<options>]
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.user invite [<options>]
|
||||
```
|
||||
|
||||
|
||||
### Options
|
||||
- `--expires-at DATE` - last day on which token is active (e.g. "2019-04-05")
|
||||
- `--max-use NUMBER` - maximum numbers of token uses
|
||||
|
||||
## List generated invites
|
||||
```sh
|
||||
$PREFIX invites
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl user invites
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.user invites
|
||||
```
|
||||
|
||||
|
||||
## Revoke invite
|
||||
```sh
|
||||
$PREFIX revoke_invite <token_or_id>
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl user revoke_invite <token_or_id>
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.user revoke_invite <token_or_id>
|
||||
```
|
||||
|
||||
|
||||
## Delete a user
|
||||
```sh
|
||||
$PREFIX rm <nickname>
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl user rm <nickname>
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.user rm <nickname>
|
||||
```
|
||||
|
||||
|
||||
## Delete user's posts and interactions
|
||||
```sh
|
||||
$PREFIX delete_activities <nickname>
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl user delete_activities <nickname>
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.user delete_activities <nickname>
|
||||
```
|
||||
|
||||
|
||||
## Sign user out from all applications (delete user's OAuth tokens and authorizations)
|
||||
```sh
|
||||
$PREFIX sign_out <nickname>
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl user sign_out <nickname>
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.user sign_out <nickname>
|
||||
```
|
||||
|
||||
|
||||
## Deactivate or activate a user
|
||||
```sh
|
||||
$PREFIX toggle_activated <nickname>
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl user toggle_activated <nickname>
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.user toggle_activated <nickname>
|
||||
```
|
||||
|
||||
|
||||
## Unsubscribe local users from a user and deactivate the user
|
||||
```sh
|
||||
$PREFIX unsubscribe NICKNAME
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl user unsubscribe NICKNAME
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.user unsubscribe NICKNAME
|
||||
```
|
||||
|
||||
|
||||
## Unsubscribe local users from an instance and deactivate all accounts on it
|
||||
```sh
|
||||
$PREFIX unsubscribe_all_from_instance <instance>
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl user unsubscribe_all_from_instance <instance>
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.user unsubscribe_all_from_instance <instance>
|
||||
```
|
||||
|
||||
|
||||
## Create a password reset link for user
|
||||
```sh
|
||||
$PREFIX reset_password <nickname>
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl user reset_password <nickname>
|
||||
```
|
||||
|
||||
## Set the value of the given user's settings
|
||||
```sh
|
||||
$PREFIX set <nickname> [<options>]
|
||||
```sh tab="From Source"
|
||||
mix pleroma.user reset_password <nickname>
|
||||
```
|
||||
|
||||
|
||||
## Set the value of the given user's settings
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl user set <nickname> [<options>]
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.user set <nickname> [<options>]
|
||||
```
|
||||
|
||||
### Options
|
||||
- `--locked`/`--no-locked` - whether the user should be locked
|
||||
- `--moderator`/`--no-moderator` - whether the user should be a moderator
|
||||
- `--admin`/`--no-admin` - whether the user should be an admin
|
||||
|
||||
## Add tags to a user
|
||||
```sh
|
||||
$PREFIX tag <nickname> <tags>
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl user tag <nickname> <tags>
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.user tag <nickname> <tags>
|
||||
```
|
||||
|
||||
|
||||
## Delete tags from a user
|
||||
```sh
|
||||
$PREFIX untag <nickname> <tags>
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl user untag <nickname> <tags>
|
||||
```
|
||||
|
||||
## Toggle confirmation status of the user
|
||||
```sh
|
||||
$PREFIX toggle_confirmed <nickname>
|
||||
```sh tab="From Source"
|
||||
mix pleroma.user untag <nickname> <tags>
|
||||
```
|
||||
|
||||
|
||||
## Toggle confirmation status of the user
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl user toggle_confirmed <nickname>
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.user toggle_confirmed <nickname>
|
||||
```
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ You shouldn't edit the base config directly to avoid breakages and merge conflic
|
|||
* `notify_email`: Email used for notifications.
|
||||
* `description`: The instance’s description, can be seen in nodeinfo and ``/api/v1/instance``.
|
||||
* `limit`: Posts character limit (CW/Subject included in the counter).
|
||||
* `chat_limit`: Character limit of the instance chat messages.
|
||||
* `remote_limit`: Hard character limit beyond which remote posts will be dropped.
|
||||
* `upload_limit`: File size limit of uploads (except for avatar, background, banner).
|
||||
* `avatar_upload_limit`: File size limit of user’s profile avatars.
|
||||
|
|
|
@ -52,7 +52,9 @@ def run(["migrate_from_db", env, delete?]) do
|
|||
|> Enum.each(fn config ->
|
||||
IO.write(
|
||||
file,
|
||||
"config :#{config.group}, #{config.key}, #{inspect(Config.from_binary(config.value))}\r\n\r\n"
|
||||
"config :#{config.group}, #{config.key}, #{
|
||||
inspect(Config.from_binary(config.value), limit: :infinity)
|
||||
}\r\n\r\n"
|
||||
)
|
||||
|
||||
if delete? do
|
||||
|
|
83
lib/mix/tasks/pleroma/notification_settings.ex
Normal file
83
lib/mix/tasks/pleroma/notification_settings.ex
Normal file
|
@ -0,0 +1,83 @@
|
|||
defmodule Mix.Tasks.Pleroma.NotificationSettings do
|
||||
@shortdoc "Enable&Disable privacy option for push notifications"
|
||||
@moduledoc """
|
||||
Example:
|
||||
|
||||
> mix pleroma.notification_settings --privacy-option=false --nickname-users="parallel588" # set false only for parallel588 user
|
||||
> mix pleroma.notification_settings --privacy-option=true # set true for all users
|
||||
|
||||
"""
|
||||
|
||||
use Mix.Task
|
||||
import Mix.Pleroma
|
||||
import Ecto.Query
|
||||
|
||||
def run(args) do
|
||||
start_pleroma()
|
||||
|
||||
{options, _, _} =
|
||||
OptionParser.parse(
|
||||
args,
|
||||
strict: [
|
||||
privacy_option: :boolean,
|
||||
email_users: :string,
|
||||
nickname_users: :string
|
||||
]
|
||||
)
|
||||
|
||||
privacy_option = Keyword.get(options, :privacy_option)
|
||||
|
||||
if not is_nil(privacy_option) do
|
||||
privacy_option
|
||||
|> build_query(options)
|
||||
|> Pleroma.Repo.update_all([])
|
||||
end
|
||||
|
||||
shell_info("Done")
|
||||
end
|
||||
|
||||
defp build_query(privacy_option, options) do
|
||||
query =
|
||||
from(u in Pleroma.User,
|
||||
update: [
|
||||
set: [
|
||||
notification_settings:
|
||||
fragment(
|
||||
"jsonb_set(notification_settings, '{privacy_option}', ?)",
|
||||
^privacy_option
|
||||
)
|
||||
]
|
||||
]
|
||||
)
|
||||
|
||||
user_emails =
|
||||
options
|
||||
|> Keyword.get(:email_users, "")
|
||||
|> String.split(",")
|
||||
|> Enum.map(&String.trim(&1))
|
||||
|> Enum.reject(&(&1 == ""))
|
||||
|
||||
query =
|
||||
if length(user_emails) > 0 do
|
||||
where(query, [u], u.email in ^user_emails)
|
||||
else
|
||||
query
|
||||
end
|
||||
|
||||
user_nicknames =
|
||||
options
|
||||
|> Keyword.get(:nickname_users, "")
|
||||
|> String.split(",")
|
||||
|> Enum.map(&String.trim(&1))
|
||||
|> Enum.reject(&(&1 == ""))
|
||||
|
||||
query =
|
||||
if length(user_nicknames) > 0 do
|
||||
where(query, [u], u.nickname in ^user_nicknames)
|
||||
else
|
||||
query
|
||||
end
|
||||
|
||||
query
|
||||
end
|
||||
end
|
|
@ -371,9 +371,9 @@ def run(["list"]) do
|
|||
users
|
||||
|> Enum.each(fn user ->
|
||||
shell_info(
|
||||
"#{user.nickname} moderator: #{user.info.is_moderator}, admin: #{user.info.is_admin}, locked: #{
|
||||
user.info.locked
|
||||
}, deactivated: #{user.info.deactivated}"
|
||||
"#{user.nickname} moderator: #{user.is_moderator}, admin: #{user.is_admin}, locked: #{
|
||||
user.locked
|
||||
}, deactivated: #{user.deactivated}"
|
||||
)
|
||||
end)
|
||||
end)
|
||||
|
|
|
@ -241,9 +241,10 @@ def normalize(obj) when is_map(obj), do: get_by_ap_id_with_object(obj["id"])
|
|||
def normalize(ap_id) when is_binary(ap_id), do: get_by_ap_id_with_object(ap_id)
|
||||
def normalize(_), do: nil
|
||||
|
||||
def delete_by_ap_id(id) when is_binary(id) do
|
||||
def delete_all_by_object_ap_id(id) when is_binary(id) do
|
||||
id
|
||||
|> Queries.by_object_id()
|
||||
|> Queries.exclude_type("Delete")
|
||||
|> select([u], u)
|
||||
|> Repo.delete_all()
|
||||
|> elem(1)
|
||||
|
@ -255,7 +256,7 @@ def delete_by_ap_id(id) when is_binary(id) do
|
|||
|> purge_web_resp_cache()
|
||||
end
|
||||
|
||||
def delete_by_ap_id(_), do: nil
|
||||
def delete_all_by_object_ap_id(_), do: nil
|
||||
|
||||
defp purge_web_resp_cache(%Activity{} = activity) do
|
||||
%{path: path} = URI.parse(activity.data["id"])
|
||||
|
|
|
@ -64,4 +64,12 @@ def by_type(query \\ Activity, activity_type) do
|
|||
where: fragment("(?)->>'type' = ?", activity.data, ^activity_type)
|
||||
)
|
||||
end
|
||||
|
||||
@spec exclude_type(query, String.t()) :: query
|
||||
def exclude_type(query \\ Activity, activity_type) do
|
||||
from(
|
||||
activity in query,
|
||||
where: fragment("(?)->>'type' != ?", activity.data, ^activity_type)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -86,7 +86,7 @@ defp maybe_fetch(activities, user, search_query) do
|
|||
{:ok, object} <- Fetcher.fetch_object_from_id(search_query),
|
||||
%Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
|
||||
true <- Visibility.visible_for_user?(activity, user) do
|
||||
activities ++ [activity]
|
||||
[activity | activities]
|
||||
else
|
||||
_ -> activities
|
||||
end
|
||||
|
|
|
@ -30,6 +30,7 @@ def user_agent do
|
|||
# See http://elixir-lang.org/docs/stable/elixir/Application.html
|
||||
# for more information on OTP Applications
|
||||
def start(_type, _args) do
|
||||
Pleroma.HTML.compile_scrubbers()
|
||||
Pleroma.Config.DeprecationWarnings.warn()
|
||||
setup_instrumenters()
|
||||
|
||||
|
@ -147,8 +148,6 @@ defp oauth_cleanup_child(true),
|
|||
|
||||
defp oauth_cleanup_child(_), do: []
|
||||
|
||||
defp chat_child(:test, _), do: []
|
||||
|
||||
defp chat_child(_env, true) do
|
||||
[Pleroma.Web.ChatChannel.ChatChannelState]
|
||||
end
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
defmodule Pleroma.Clippy do
|
||||
@moduledoc false
|
||||
|
||||
# No software is complete until they have a Clippy implementation.
|
||||
# A ballmer peak _may_ be required to change this module.
|
||||
|
||||
|
|
13
lib/pleroma/ecto_enums.ex
Normal file
13
lib/pleroma/ecto_enums.ex
Normal file
|
@ -0,0 +1,13 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import EctoEnum
|
||||
|
||||
defenum(UserRelationshipTypeEnum,
|
||||
block: 1,
|
||||
mute: 2,
|
||||
reblog_mute: 3,
|
||||
notification_mute: 4,
|
||||
inverse_subscription: 5
|
||||
)
|
|
@ -121,8 +121,12 @@ def move_following(origin, target) do
|
|||
Pleroma.Web.CommonAPI.follow(following_relationship.follower, target)
|
||||
end)
|
||||
|> case do
|
||||
[] -> :ok
|
||||
_ -> move_following(origin, target)
|
||||
[] ->
|
||||
User.update_follower_count(origin)
|
||||
:ok
|
||||
|
||||
_ ->
|
||||
move_following(origin, target)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,6 +3,25 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.HTML do
|
||||
# Scrubbers are compiled on boot so they can be configured in OTP releases
|
||||
# @on_load :compile_scrubbers
|
||||
|
||||
def compile_scrubbers do
|
||||
dir = Path.join(:code.priv_dir(:pleroma), "scrubbers")
|
||||
|
||||
dir
|
||||
|> File.ls!()
|
||||
|> Enum.map(&Path.join(dir, &1))
|
||||
|> Kernel.ParallelCompiler.compile()
|
||||
|> case do
|
||||
{:error, _errors, _warnings} ->
|
||||
raise "Compiling scrubbers failed"
|
||||
|
||||
{:ok, _modules, _warnings} ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp get_scrubbers(scrubber) when is_atom(scrubber), do: [scrubber]
|
||||
defp get_scrubbers(scrubbers) when is_list(scrubbers), do: scrubbers
|
||||
defp get_scrubbers(_), do: [Pleroma.HTML.Scrubber.Default]
|
||||
|
@ -99,215 +118,3 @@ def extract_first_external_url(object, content) do
|
|||
end)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Pleroma.HTML.Scrubber.TwitterText do
|
||||
@moduledoc """
|
||||
An HTML scrubbing policy which limits to twitter-style text. Only
|
||||
paragraphs, breaks and links are allowed through the filter.
|
||||
"""
|
||||
|
||||
@valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
|
||||
|
||||
require FastSanitize.Sanitizer.Meta
|
||||
alias FastSanitize.Sanitizer.Meta
|
||||
|
||||
Meta.strip_comments()
|
||||
|
||||
# links
|
||||
Meta.allow_tag_with_uri_attributes(:a, ["href", "data-user", "data-tag"], @valid_schemes)
|
||||
|
||||
Meta.allow_tag_with_this_attribute_values(:a, "class", [
|
||||
"hashtag",
|
||||
"u-url",
|
||||
"mention",
|
||||
"u-url mention",
|
||||
"mention u-url"
|
||||
])
|
||||
|
||||
Meta.allow_tag_with_this_attribute_values(:a, "rel", [
|
||||
"tag",
|
||||
"nofollow",
|
||||
"noopener",
|
||||
"noreferrer"
|
||||
])
|
||||
|
||||
Meta.allow_tag_with_these_attributes(:a, ["name", "title"])
|
||||
|
||||
# paragraphs and linebreaks
|
||||
Meta.allow_tag_with_these_attributes(:br, [])
|
||||
Meta.allow_tag_with_these_attributes(:p, [])
|
||||
|
||||
# microformats
|
||||
Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card"])
|
||||
Meta.allow_tag_with_these_attributes(:span, [])
|
||||
|
||||
# allow inline images for custom emoji
|
||||
if Pleroma.Config.get([:markup, :allow_inline_images]) do
|
||||
# restrict img tags to http/https only, because of MediaProxy.
|
||||
Meta.allow_tag_with_uri_attributes(:img, ["src"], ["http", "https"])
|
||||
|
||||
Meta.allow_tag_with_these_attributes(:img, [
|
||||
"width",
|
||||
"height",
|
||||
"class",
|
||||
"title",
|
||||
"alt"
|
||||
])
|
||||
end
|
||||
|
||||
Meta.strip_everything_not_covered()
|
||||
end
|
||||
|
||||
defmodule Pleroma.HTML.Scrubber.Default do
|
||||
@doc "The default HTML scrubbing policy: no "
|
||||
|
||||
require FastSanitize.Sanitizer.Meta
|
||||
alias FastSanitize.Sanitizer.Meta
|
||||
# credo:disable-for-previous-line
|
||||
# No idea how to fix this one…
|
||||
|
||||
@valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
|
||||
|
||||
Meta.strip_comments()
|
||||
|
||||
Meta.allow_tag_with_uri_attributes(:a, ["href", "data-user", "data-tag"], @valid_schemes)
|
||||
|
||||
Meta.allow_tag_with_this_attribute_values(:a, "class", [
|
||||
"hashtag",
|
||||
"u-url",
|
||||
"mention",
|
||||
"u-url mention",
|
||||
"mention u-url"
|
||||
])
|
||||
|
||||
Meta.allow_tag_with_this_attribute_values(:a, "rel", [
|
||||
"tag",
|
||||
"nofollow",
|
||||
"noopener",
|
||||
"noreferrer",
|
||||
"ugc"
|
||||
])
|
||||
|
||||
Meta.allow_tag_with_these_attributes(:a, ["name", "title"])
|
||||
|
||||
Meta.allow_tag_with_these_attributes(:abbr, ["title"])
|
||||
|
||||
Meta.allow_tag_with_these_attributes(:b, [])
|
||||
Meta.allow_tag_with_these_attributes(:blockquote, [])
|
||||
Meta.allow_tag_with_these_attributes(:br, [])
|
||||
Meta.allow_tag_with_these_attributes(:code, [])
|
||||
Meta.allow_tag_with_these_attributes(:del, [])
|
||||
Meta.allow_tag_with_these_attributes(:em, [])
|
||||
Meta.allow_tag_with_these_attributes(:i, [])
|
||||
Meta.allow_tag_with_these_attributes(:li, [])
|
||||
Meta.allow_tag_with_these_attributes(:ol, [])
|
||||
Meta.allow_tag_with_these_attributes(:p, [])
|
||||
Meta.allow_tag_with_these_attributes(:pre, [])
|
||||
Meta.allow_tag_with_these_attributes(:strong, [])
|
||||
Meta.allow_tag_with_these_attributes(:sub, [])
|
||||
Meta.allow_tag_with_these_attributes(:sup, [])
|
||||
Meta.allow_tag_with_these_attributes(:u, [])
|
||||
Meta.allow_tag_with_these_attributes(:ul, [])
|
||||
|
||||
Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card"])
|
||||
Meta.allow_tag_with_these_attributes(:span, [])
|
||||
|
||||
@allow_inline_images Pleroma.Config.get([:markup, :allow_inline_images])
|
||||
|
||||
if @allow_inline_images do
|
||||
# restrict img tags to http/https only, because of MediaProxy.
|
||||
Meta.allow_tag_with_uri_attributes(:img, ["src"], ["http", "https"])
|
||||
|
||||
Meta.allow_tag_with_these_attributes(:img, [
|
||||
"width",
|
||||
"height",
|
||||
"class",
|
||||
"title",
|
||||
"alt"
|
||||
])
|
||||
end
|
||||
|
||||
if Pleroma.Config.get([:markup, :allow_tables]) do
|
||||
Meta.allow_tag_with_these_attributes(:table, [])
|
||||
Meta.allow_tag_with_these_attributes(:tbody, [])
|
||||
Meta.allow_tag_with_these_attributes(:td, [])
|
||||
Meta.allow_tag_with_these_attributes(:th, [])
|
||||
Meta.allow_tag_with_these_attributes(:thead, [])
|
||||
Meta.allow_tag_with_these_attributes(:tr, [])
|
||||
end
|
||||
|
||||
if Pleroma.Config.get([:markup, :allow_headings]) do
|
||||
Meta.allow_tag_with_these_attributes(:h1, [])
|
||||
Meta.allow_tag_with_these_attributes(:h2, [])
|
||||
Meta.allow_tag_with_these_attributes(:h3, [])
|
||||
Meta.allow_tag_with_these_attributes(:h4, [])
|
||||
Meta.allow_tag_with_these_attributes(:h5, [])
|
||||
end
|
||||
|
||||
if Pleroma.Config.get([:markup, :allow_fonts]) do
|
||||
Meta.allow_tag_with_these_attributes(:font, ["face"])
|
||||
end
|
||||
|
||||
Meta.strip_everything_not_covered()
|
||||
end
|
||||
|
||||
defmodule Pleroma.HTML.Transform.MediaProxy do
|
||||
@moduledoc "Transforms inline image URIs to use MediaProxy."
|
||||
|
||||
alias Pleroma.Web.MediaProxy
|
||||
|
||||
def before_scrub(html), do: html
|
||||
|
||||
def scrub_attribute(:img, {"src", "http" <> target}) do
|
||||
media_url =
|
||||
("http" <> target)
|
||||
|> MediaProxy.url()
|
||||
|
||||
{"src", media_url}
|
||||
end
|
||||
|
||||
def scrub_attribute(_tag, attribute), do: attribute
|
||||
|
||||
def scrub({:img, attributes, children}) do
|
||||
attributes =
|
||||
attributes
|
||||
|> Enum.map(fn attr -> scrub_attribute(:img, attr) end)
|
||||
|> Enum.reject(&is_nil(&1))
|
||||
|
||||
{:img, attributes, children}
|
||||
end
|
||||
|
||||
def scrub({:comment, _text, _children}), do: ""
|
||||
|
||||
def scrub({tag, attributes, children}), do: {tag, attributes, children}
|
||||
def scrub({_tag, children}), do: children
|
||||
def scrub(text), do: text
|
||||
end
|
||||
|
||||
defmodule Pleroma.HTML.Scrubber.LinksOnly do
|
||||
@moduledoc """
|
||||
An HTML scrubbing policy which limits to links only.
|
||||
"""
|
||||
|
||||
@valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
|
||||
|
||||
require FastSanitize.Sanitizer.Meta
|
||||
alias FastSanitize.Sanitizer.Meta
|
||||
|
||||
Meta.strip_comments()
|
||||
|
||||
# links
|
||||
Meta.allow_tag_with_uri_attributes(:a, ["href"], @valid_schemes)
|
||||
|
||||
Meta.allow_tag_with_this_attribute_values(:a, "rel", [
|
||||
"tag",
|
||||
"nofollow",
|
||||
"noopener",
|
||||
"noreferrer",
|
||||
"me",
|
||||
"ugc"
|
||||
])
|
||||
|
||||
Meta.allow_tag_with_these_attributes(:a, ["name", "title"])
|
||||
Meta.strip_everything_not_covered()
|
||||
end
|
||||
|
|
|
@ -21,6 +21,8 @@ defmodule Pleroma.Notification do
|
|||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
@include_muted_option :with_muted
|
||||
|
||||
schema "notifications" do
|
||||
field(:seen, :boolean, default: false)
|
||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||
|
@ -34,7 +36,25 @@ def changeset(%Notification{} = notification, attrs) do
|
|||
|> cast(attrs, [:seen])
|
||||
end
|
||||
|
||||
def for_user_query(user, opts \\ []) do
|
||||
defp for_user_query_ap_id_opts(user, opts) do
|
||||
ap_id_relations =
|
||||
[:block] ++
|
||||
if opts[@include_muted_option], do: [], else: [:notification_mute]
|
||||
|
||||
preloaded_ap_ids = User.outgoing_relations_ap_ids(user, ap_id_relations)
|
||||
|
||||
exclude_blocked_opts = Map.merge(%{blocked_users_ap_ids: preloaded_ap_ids[:block]}, opts)
|
||||
|
||||
exclude_notification_muted_opts =
|
||||
Map.merge(%{notification_muted_users_ap_ids: preloaded_ap_ids[:notification_mute]}, opts)
|
||||
|
||||
{exclude_blocked_opts, exclude_notification_muted_opts}
|
||||
end
|
||||
|
||||
def for_user_query(user, opts \\ %{}) do
|
||||
{exclude_blocked_opts, exclude_notification_muted_opts} =
|
||||
for_user_query_ap_id_opts(user, opts)
|
||||
|
||||
Notification
|
||||
|> where(user_id: ^user.id)
|
||||
|> where(
|
||||
|
@ -54,43 +74,75 @@ def for_user_query(user, opts \\ []) do
|
|||
)
|
||||
)
|
||||
|> preload([n, a, o], activity: {a, object: o})
|
||||
|> exclude_muted(user, opts)
|
||||
|> exclude_blocked(user)
|
||||
|> exclude_notification_muted(user, exclude_notification_muted_opts)
|
||||
|> exclude_blocked(user, exclude_blocked_opts)
|
||||
|> exclude_visibility(opts)
|
||||
|> exclude_move(opts)
|
||||
end
|
||||
|
||||
defp exclude_blocked(query, user) do
|
||||
defp exclude_blocked(query, user, opts) do
|
||||
blocked_ap_ids = opts[:blocked_users_ap_ids] || User.blocked_users_ap_ids(user)
|
||||
|
||||
query
|
||||
|> where([n, a], a.actor not in ^user.blocks)
|
||||
|> where([n, a], a.actor not in ^blocked_ap_ids)
|
||||
|> where(
|
||||
[n, a],
|
||||
fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.domain_blocks
|
||||
)
|
||||
end
|
||||
|
||||
defp exclude_muted(query, _, %{with_muted: true}) do
|
||||
defp exclude_notification_muted(query, _, %{@include_muted_option => true}) do
|
||||
query
|
||||
end
|
||||
|
||||
defp exclude_muted(query, user, _opts) do
|
||||
defp exclude_notification_muted(query, user, opts) do
|
||||
notification_muted_ap_ids =
|
||||
opts[:notification_muted_users_ap_ids] || User.notification_muted_users_ap_ids(user)
|
||||
|
||||
query
|
||||
|> where([n, a], a.actor not in ^user.muted_notifications)
|
||||
|> where([n, a], a.actor not in ^notification_muted_ap_ids)
|
||||
|> join(:left, [n, a], tm in Pleroma.ThreadMute,
|
||||
on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data)
|
||||
)
|
||||
|> where([n, a, o, tm], is_nil(tm.user_id))
|
||||
end
|
||||
|
||||
defp exclude_move(query, %{with_move: true}) do
|
||||
query
|
||||
end
|
||||
|
||||
defp exclude_move(query, _opts) do
|
||||
where(query, [n, a], fragment("?->>'type' != 'Move'", a.data))
|
||||
end
|
||||
|
||||
@valid_visibilities ~w[direct unlisted public private]
|
||||
|
||||
defp exclude_visibility(query, %{exclude_visibilities: visibility})
|
||||
when is_list(visibility) do
|
||||
if Enum.all?(visibility, &(&1 in @valid_visibilities)) do
|
||||
query
|
||||
|> join(:left, [n, a], mutated_activity in Pleroma.Activity,
|
||||
on:
|
||||
fragment("?->>'context'", a.data) ==
|
||||
fragment("?->>'context'", mutated_activity.data) and
|
||||
fragment("(?->>'type' = 'Like' or ?->>'type' = 'Announce')", a.data, a.data) and
|
||||
fragment("?->>'type'", mutated_activity.data) == "Create",
|
||||
as: :mutated_activity
|
||||
)
|
||||
|> where(
|
||||
[n, a],
|
||||
[n, a, mutated_activity: mutated_activity],
|
||||
not fragment(
|
||||
"activity_visibility(?, ?, ?) = ANY (?)",
|
||||
"""
|
||||
CASE WHEN (?->>'type') = 'Like' or (?->>'type') = 'Announce'
|
||||
THEN (activity_visibility(?, ?, ?) = ANY (?))
|
||||
ELSE (activity_visibility(?, ?, ?) = ANY (?)) END
|
||||
""",
|
||||
a.data,
|
||||
a.data,
|
||||
mutated_activity.actor,
|
||||
mutated_activity.recipients,
|
||||
mutated_activity.data,
|
||||
^visibility,
|
||||
a.actor,
|
||||
a.recipients,
|
||||
a.data,
|
||||
|
@ -105,17 +157,7 @@ defp exclude_visibility(query, %{exclude_visibilities: visibility})
|
|||
|
||||
defp exclude_visibility(query, %{exclude_visibilities: visibility})
|
||||
when visibility in @valid_visibilities do
|
||||
query
|
||||
|> where(
|
||||
[n, a],
|
||||
not fragment(
|
||||
"activity_visibility(?, ?, ?) = (?)",
|
||||
a.actor,
|
||||
a.recipients,
|
||||
a.data,
|
||||
^visibility
|
||||
)
|
||||
)
|
||||
exclude_visibility(query, [visibility])
|
||||
end
|
||||
|
||||
defp exclude_visibility(query, %{exclude_visibilities: visibility})
|
||||
|
@ -313,7 +355,7 @@ def skip?(:self, activity, user) do
|
|||
def skip?(
|
||||
:followers,
|
||||
activity,
|
||||
%{notification_settings: %{"followers" => false}} = user
|
||||
%{notification_settings: %{followers: false}} = user
|
||||
) do
|
||||
actor = activity.data["actor"]
|
||||
follower = User.get_cached_by_ap_id(actor)
|
||||
|
@ -323,14 +365,14 @@ def skip?(
|
|||
def skip?(
|
||||
:non_followers,
|
||||
activity,
|
||||
%{notification_settings: %{"non_followers" => false}} = user
|
||||
%{notification_settings: %{non_followers: false}} = user
|
||||
) do
|
||||
actor = activity.data["actor"]
|
||||
follower = User.get_cached_by_ap_id(actor)
|
||||
!User.following?(follower, user)
|
||||
end
|
||||
|
||||
def skip?(:follows, activity, %{notification_settings: %{"follows" => false}} = user) do
|
||||
def skip?(:follows, activity, %{notification_settings: %{follows: false}} = user) do
|
||||
actor = activity.data["actor"]
|
||||
followed = User.get_cached_by_ap_id(actor)
|
||||
User.following?(user, followed)
|
||||
|
@ -339,7 +381,7 @@ def skip?(:follows, activity, %{notification_settings: %{"follows" => false}} =
|
|||
def skip?(
|
||||
:non_follows,
|
||||
activity,
|
||||
%{notification_settings: %{"non_follows" => false}} = user
|
||||
%{notification_settings: %{non_follows: false}} = user
|
||||
) do
|
||||
actor = activity.data["actor"]
|
||||
followed = User.get_cached_by_ap_id(actor)
|
||||
|
|
|
@ -147,7 +147,7 @@ def swap_object_with_tombstone(object) do
|
|||
|
||||
def delete(%Object{data: %{"id" => id}} = object) do
|
||||
with {:ok, _obj} = swap_object_with_tombstone(object),
|
||||
deleted_activity = Activity.delete_by_ap_id(id),
|
||||
deleted_activity = Activity.delete_all_by_object_ap_id(id),
|
||||
{:ok, true} <- Cachex.del(:object_cache, "object:#{id}"),
|
||||
{:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path) do
|
||||
{:ok, object, deleted_activity}
|
||||
|
|
21
lib/pleroma/plugs/parsers_plug.ex
Normal file
21
lib/pleroma/plugs/parsers_plug.ex
Normal file
|
@ -0,0 +1,21 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Plugs.Parsers do
|
||||
@moduledoc "Initializes Plug.Parsers with upload limit set at boot time"
|
||||
|
||||
@behaviour Plug
|
||||
|
||||
def init(_opts) do
|
||||
Plug.Parsers.init(
|
||||
parsers: [:urlencoded, :multipart, :json],
|
||||
pass: ["*/*"],
|
||||
json_decoder: Jason,
|
||||
length: Pleroma.Config.get([:instance, :upload_limit]),
|
||||
body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []}
|
||||
)
|
||||
end
|
||||
|
||||
defdelegate call(conn, opts), to: Plug.Parsers
|
||||
end
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.User do
|
|||
|
||||
import Ecto.Changeset
|
||||
import Ecto.Query
|
||||
import Ecto, only: [assoc: 2]
|
||||
|
||||
alias Comeonin.Pbkdf2
|
||||
alias Ecto.Multi
|
||||
|
@ -21,6 +22,7 @@ defmodule Pleroma.User do
|
|||
alias Pleroma.Repo
|
||||
alias Pleroma.RepoStreamer
|
||||
alias Pleroma.User
|
||||
alias Pleroma.UserRelationship
|
||||
alias Pleroma.Web
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
|
@ -42,6 +44,32 @@ defmodule Pleroma.User do
|
|||
@strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
|
||||
@extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
|
||||
|
||||
# AP ID user relationships (blocks, mutes etc.)
|
||||
# Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
|
||||
@user_relationships_config [
|
||||
block: [
|
||||
blocker_blocks: :blocked_users,
|
||||
blockee_blocks: :blocker_users
|
||||
],
|
||||
mute: [
|
||||
muter_mutes: :muted_users,
|
||||
mutee_mutes: :muter_users
|
||||
],
|
||||
reblog_mute: [
|
||||
reblog_muter_mutes: :reblog_muted_users,
|
||||
reblog_mutee_mutes: :reblog_muter_users
|
||||
],
|
||||
notification_mute: [
|
||||
notification_muter_mutes: :notification_muted_users,
|
||||
notification_mutee_mutes: :notification_muter_users
|
||||
],
|
||||
# Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
|
||||
inverse_subscription: [
|
||||
subscribee_subscriptions: :subscriber_users,
|
||||
subscriber_subscriptions: :subscribee_users
|
||||
]
|
||||
]
|
||||
|
||||
schema "users" do
|
||||
field(:bio, :string)
|
||||
field(:email, :string)
|
||||
|
@ -61,7 +89,6 @@ defmodule Pleroma.User do
|
|||
field(:tags, {:array, :string}, default: [])
|
||||
field(:last_refreshed_at, :naive_datetime_usec)
|
||||
field(:last_digest_emailed_at, :naive_datetime)
|
||||
|
||||
field(:banner, :map, default: %{})
|
||||
field(:background, :map, default: %{})
|
||||
field(:source_data, :map, default: %{})
|
||||
|
@ -73,12 +100,7 @@ defmodule Pleroma.User do
|
|||
field(:password_reset_pending, :boolean, default: false)
|
||||
field(:confirmation_token, :string, default: nil)
|
||||
field(:default_scope, :string, default: "public")
|
||||
field(:blocks, {:array, :string}, default: [])
|
||||
field(:domain_blocks, {:array, :string}, default: [])
|
||||
field(:mutes, {:array, :string}, default: [])
|
||||
field(:muted_reblogs, {:array, :string}, default: [])
|
||||
field(:muted_notifications, {:array, :string}, default: [])
|
||||
field(:subscribers, {:array, :string}, default: [])
|
||||
field(:deactivated, :boolean, default: false)
|
||||
field(:no_rich_text, :boolean, default: false)
|
||||
field(:ap_enabled, :boolean, default: false)
|
||||
|
@ -107,22 +129,92 @@ defmodule Pleroma.User do
|
|||
field(:skip_thread_containment, :boolean, default: false)
|
||||
field(:also_known_as, {:array, :string}, default: [])
|
||||
|
||||
field(:notification_settings, :map,
|
||||
default: %{
|
||||
"followers" => true,
|
||||
"follows" => true,
|
||||
"non_follows" => true,
|
||||
"non_followers" => true
|
||||
}
|
||||
embeds_one(
|
||||
:notification_settings,
|
||||
Pleroma.User.NotificationSetting,
|
||||
on_replace: :update
|
||||
)
|
||||
|
||||
has_many(:notifications, Notification)
|
||||
has_many(:registrations, Registration)
|
||||
has_many(:deliveries, Delivery)
|
||||
|
||||
has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
|
||||
has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
|
||||
|
||||
for {relationship_type,
|
||||
[
|
||||
{outgoing_relation, outgoing_relation_target},
|
||||
{incoming_relation, incoming_relation_source}
|
||||
]} <- @user_relationships_config do
|
||||
# Definitions of `has_many :blocker_blocks`, `has_many :muter_mutes` etc.
|
||||
has_many(outgoing_relation, UserRelationship,
|
||||
foreign_key: :source_id,
|
||||
where: [relationship_type: relationship_type]
|
||||
)
|
||||
|
||||
# Definitions of `has_many :blockee_blocks`, `has_many :mutee_mutes` etc.
|
||||
has_many(incoming_relation, UserRelationship,
|
||||
foreign_key: :target_id,
|
||||
where: [relationship_type: relationship_type]
|
||||
)
|
||||
|
||||
# Definitions of `has_many :blocked_users`, `has_many :muted_users` etc.
|
||||
has_many(outgoing_relation_target, through: [outgoing_relation, :target])
|
||||
|
||||
# Definitions of `has_many :blocker_users`, `has_many :muter_users` etc.
|
||||
has_many(incoming_relation_source, through: [incoming_relation, :source])
|
||||
end
|
||||
|
||||
# `:blocks` is deprecated (replaced with `blocked_users` relation)
|
||||
field(:blocks, {:array, :string}, default: [])
|
||||
# `:mutes` is deprecated (replaced with `muted_users` relation)
|
||||
field(:mutes, {:array, :string}, default: [])
|
||||
# `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
|
||||
field(:muted_reblogs, {:array, :string}, default: [])
|
||||
# `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
|
||||
field(:muted_notifications, {:array, :string}, default: [])
|
||||
# `:subscribers` is deprecated (replaced with `subscriber_users` relation)
|
||||
field(:subscribers, {:array, :string}, default: [])
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
|
||||
@user_relationships_config do
|
||||
# Definitions of `blocked_users_relation/1`, `muted_users_relation/1`, etc.
|
||||
def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
|
||||
target_users_query = assoc(user, unquote(outgoing_relation_target))
|
||||
|
||||
if restrict_deactivated? do
|
||||
restrict_deactivated(target_users_query)
|
||||
else
|
||||
target_users_query
|
||||
end
|
||||
end
|
||||
|
||||
# Definitions of `blocked_users/1`, `muted_users/1`, etc.
|
||||
def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
|
||||
__MODULE__
|
||||
|> apply(unquote(:"#{outgoing_relation_target}_relation"), [
|
||||
user,
|
||||
restrict_deactivated?
|
||||
])
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
# Definitions of `blocked_users_ap_ids/1`, `muted_users_ap_ids/1`, etc.
|
||||
def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
|
||||
__MODULE__
|
||||
|> apply(unquote(:"#{outgoing_relation_target}_relation"), [
|
||||
user,
|
||||
restrict_deactivated?
|
||||
])
|
||||
|> select([u], u.ap_id)
|
||||
|> Repo.all()
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Returns if the user should be allowed to authenticate"
|
||||
def auth_active?(%User{deactivated: true}), do: false
|
||||
|
||||
|
@ -946,34 +1038,45 @@ def get_recipients_from_activity(%Activity{recipients: to}) do
|
|||
|> Repo.all()
|
||||
end
|
||||
|
||||
@spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
|
||||
def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
|
||||
add_to_mutes(muter, ap_id, notifications?)
|
||||
@spec mute(User.t(), User.t(), boolean()) ::
|
||||
{:ok, list(UserRelationship.t())} | {:error, String.t()}
|
||||
def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
|
||||
add_to_mutes(muter, mutee, notifications?)
|
||||
end
|
||||
|
||||
def unmute(muter, %{ap_id: ap_id}) do
|
||||
remove_from_mutes(muter, ap_id)
|
||||
def unmute(%User{} = muter, %User{} = mutee) do
|
||||
remove_from_mutes(muter, mutee)
|
||||
end
|
||||
|
||||
def subscribe(subscriber, %{ap_id: ap_id}) do
|
||||
with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
|
||||
deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
|
||||
def subscribe(%User{} = subscriber, %User{} = target) do
|
||||
deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
|
||||
|
||||
if blocks?(subscribed, subscriber) and deny_follow_blocked do
|
||||
{:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
|
||||
else
|
||||
User.add_to_subscribers(subscribed, subscriber.ap_id)
|
||||
end
|
||||
if blocks?(target, subscriber) and deny_follow_blocked do
|
||||
{:error, "Could not subscribe: #{target.nickname} is blocking you"}
|
||||
else
|
||||
# Note: the relationship is inverse: subscriber acts as relationship target
|
||||
UserRelationship.create_inverse_subscription(target, subscriber)
|
||||
end
|
||||
end
|
||||
|
||||
def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
|
||||
def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
|
||||
with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
|
||||
subscribe(subscriber, subscribee)
|
||||
end
|
||||
end
|
||||
|
||||
def unsubscribe(%User{} = unsubscriber, %User{} = target) do
|
||||
# Note: the relationship is inverse: subscriber acts as relationship target
|
||||
UserRelationship.delete_inverse_subscription(target, unsubscriber)
|
||||
end
|
||||
|
||||
def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
|
||||
with %User{} = user <- get_cached_by_ap_id(ap_id) do
|
||||
User.remove_from_subscribers(user, unsubscriber.ap_id)
|
||||
unsubscribe(unsubscriber, user)
|
||||
end
|
||||
end
|
||||
|
||||
def block(blocker, %User{ap_id: ap_id} = blocked) do
|
||||
def block(%User{} = blocker, %User{} = blocked) do
|
||||
# sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
|
||||
blocker =
|
||||
if following?(blocker, blocked) do
|
||||
|
@ -990,50 +1093,53 @@ def block(blocker, %User{ap_id: ap_id} = blocked) do
|
|||
nil -> blocked
|
||||
end
|
||||
|
||||
blocker =
|
||||
if subscribed_to?(blocked, blocker) do
|
||||
{:ok, blocker} = unsubscribe(blocked, blocker)
|
||||
blocker
|
||||
else
|
||||
blocker
|
||||
end
|
||||
unsubscribe(blocked, blocker)
|
||||
|
||||
if following?(blocked, blocker), do: unfollow(blocked, blocker)
|
||||
|
||||
{:ok, blocker} = update_follower_count(blocker)
|
||||
{:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
|
||||
add_to_block(blocker, ap_id)
|
||||
add_to_block(blocker, blocked)
|
||||
end
|
||||
|
||||
# helper to handle the block given only an actor's AP id
|
||||
def block(blocker, %{ap_id: ap_id}) do
|
||||
def block(%User{} = blocker, %{ap_id: ap_id}) do
|
||||
block(blocker, get_cached_by_ap_id(ap_id))
|
||||
end
|
||||
|
||||
def unblock(blocker, %{ap_id: ap_id}) do
|
||||
remove_from_block(blocker, ap_id)
|
||||
def unblock(%User{} = blocker, %User{} = blocked) do
|
||||
remove_from_block(blocker, blocked)
|
||||
end
|
||||
|
||||
# helper to handle the block given only an actor's AP id
|
||||
def unblock(%User{} = blocker, %{ap_id: ap_id}) do
|
||||
unblock(blocker, get_cached_by_ap_id(ap_id))
|
||||
end
|
||||
|
||||
def mutes?(nil, _), do: false
|
||||
def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id)
|
||||
def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
|
||||
|
||||
def mutes_user?(%User{} = user, %User{} = target) do
|
||||
UserRelationship.mute_exists?(user, target)
|
||||
end
|
||||
|
||||
@spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
|
||||
def muted_notifications?(nil, _), do: false
|
||||
|
||||
def muted_notifications?(user, %{ap_id: ap_id}),
|
||||
do: Enum.member?(user.muted_notifications, ap_id)
|
||||
|
||||
def blocks?(%User{} = user, %User{} = target) do
|
||||
blocks_ap_id?(user, target) || blocks_domain?(user, target)
|
||||
end
|
||||
def muted_notifications?(%User{} = user, %User{} = target),
|
||||
do: UserRelationship.notification_mute_exists?(user, target)
|
||||
|
||||
def blocks?(nil, _), do: false
|
||||
|
||||
def blocks_ap_id?(%User{} = user, %User{} = target) do
|
||||
Enum.member?(user.blocks, target.ap_id)
|
||||
def blocks?(%User{} = user, %User{} = target) do
|
||||
blocks_user?(user, target) || blocks_domain?(user, target)
|
||||
end
|
||||
|
||||
def blocks_ap_id?(_, _), do: false
|
||||
def blocks_user?(%User{} = user, %User{} = target) do
|
||||
UserRelationship.block_exists?(user, target)
|
||||
end
|
||||
|
||||
def blocks_user?(_, _), do: false
|
||||
|
||||
def blocks_domain?(%User{} = user, %User{} = target) do
|
||||
domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
|
||||
|
@ -1043,28 +1149,41 @@ def blocks_domain?(%User{} = user, %User{} = target) do
|
|||
|
||||
def blocks_domain?(_, _), do: false
|
||||
|
||||
def subscribed_to?(user, %{ap_id: ap_id}) do
|
||||
def subscribed_to?(%User{} = user, %User{} = target) do
|
||||
# Note: the relationship is inverse: subscriber acts as relationship target
|
||||
UserRelationship.inverse_subscription_exists?(target, user)
|
||||
end
|
||||
|
||||
def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
|
||||
with %User{} = target <- get_cached_by_ap_id(ap_id) do
|
||||
Enum.member?(target.subscribers, user.ap_id)
|
||||
subscribed_to?(user, target)
|
||||
end
|
||||
end
|
||||
|
||||
@spec muted_users(User.t()) :: [User.t()]
|
||||
def muted_users(user) do
|
||||
User.Query.build(%{ap_id: user.mutes, deactivated: false})
|
||||
|> Repo.all()
|
||||
end
|
||||
@doc """
|
||||
Returns map of outgoing (blocked, muted etc.) relations' user AP IDs by relation type.
|
||||
E.g. `outgoing_relations_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
|
||||
"""
|
||||
@spec outgoing_relations_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
|
||||
def outgoing_relations_ap_ids(_, []), do: %{}
|
||||
|
||||
@spec blocked_users(User.t()) :: [User.t()]
|
||||
def blocked_users(user) do
|
||||
User.Query.build(%{ap_id: user.blocks, deactivated: false})
|
||||
|> Repo.all()
|
||||
end
|
||||
def outgoing_relations_ap_ids(%User{} = user, relationship_types)
|
||||
when is_list(relationship_types) do
|
||||
db_result =
|
||||
user
|
||||
|> assoc(:outgoing_relationships)
|
||||
|> join(:inner, [user_rel], u in assoc(user_rel, :target))
|
||||
|> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
|
||||
|> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
|
||||
|> group_by([user_rel, u], user_rel.relationship_type)
|
||||
|> Repo.all()
|
||||
|> Enum.into(%{}, fn [k, v] -> {k, v} end)
|
||||
|
||||
@spec subscribers(User.t()) :: [User.t()]
|
||||
def subscribers(user) do
|
||||
User.Query.build(%{ap_id: user.subscribers, deactivated: false})
|
||||
|> Repo.all()
|
||||
Enum.into(
|
||||
relationship_types,
|
||||
%{},
|
||||
fn rel_type -> {rel_type, db_result[rel_type] || []} end
|
||||
)
|
||||
end
|
||||
|
||||
def deactivate_async(user, status \\ true) do
|
||||
|
@ -1099,20 +1218,9 @@ def deactivate(%User{} = user, status) do
|
|||
end
|
||||
|
||||
def update_notification_settings(%User{} = user, settings) do
|
||||
settings =
|
||||
settings
|
||||
|> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
|
||||
|> Map.new()
|
||||
|
||||
notification_settings =
|
||||
user.notification_settings
|
||||
|> Map.merge(settings)
|
||||
|> Map.take(["followers", "follows", "non_follows", "non_followers"])
|
||||
|
||||
params = %{notification_settings: notification_settings}
|
||||
|
||||
user
|
||||
|> cast(params, [:notification_settings])
|
||||
|> cast(%{notification_settings: settings}, [])
|
||||
|> cast_embed(:notification_settings)
|
||||
|> validate_required([:notification_settings])
|
||||
|> update_and_set_cache()
|
||||
end
|
||||
|
@ -1171,7 +1279,7 @@ def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
|
|||
blocked_identifiers,
|
||||
fn blocked_identifier ->
|
||||
with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
|
||||
{:ok, blocker} <- block(blocker, blocked),
|
||||
{:ok, _user_block} <- block(blocker, blocked),
|
||||
{:ok, _} <- ActivityPub.block(blocker, blocked) do
|
||||
blocked
|
||||
else
|
||||
|
@ -1485,7 +1593,7 @@ def all_superusers do
|
|||
end
|
||||
|
||||
def showing_reblogs?(%User{} = user, %User{} = target) do
|
||||
target.ap_id not in user.muted_reblogs
|
||||
not UserRelationship.reblog_mute_exists?(user, target)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
@ -1823,23 +1931,6 @@ def update_email_notifications(user, settings) do
|
|||
|> update_and_set_cache()
|
||||
end
|
||||
|
||||
defp set_subscribers(user, subscribers) do
|
||||
params = %{subscribers: subscribers}
|
||||
|
||||
user
|
||||
|> cast(params, [:subscribers])
|
||||
|> validate_required([:subscribers])
|
||||
|> update_and_set_cache()
|
||||
end
|
||||
|
||||
def add_to_subscribers(user, subscribed) do
|
||||
set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
|
||||
end
|
||||
|
||||
def remove_from_subscribers(user, subscribed) do
|
||||
set_subscribers(user, List.delete(user.subscribers, subscribed))
|
||||
end
|
||||
|
||||
defp set_domain_blocks(user, domain_blocks) do
|
||||
params = %{domain_blocks: domain_blocks}
|
||||
|
||||
|
@ -1857,81 +1948,35 @@ def unblock_domain(user, domain_blocked) do
|
|||
set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
|
||||
end
|
||||
|
||||
defp set_blocks(user, blocks) do
|
||||
params = %{blocks: blocks}
|
||||
|
||||
user
|
||||
|> cast(params, [:blocks])
|
||||
|> validate_required([:blocks])
|
||||
|> update_and_set_cache()
|
||||
@spec add_to_block(User.t(), User.t()) ::
|
||||
{:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
|
||||
defp add_to_block(%User{} = user, %User{} = blocked) do
|
||||
UserRelationship.create_block(user, blocked)
|
||||
end
|
||||
|
||||
def add_to_block(user, blocked) do
|
||||
set_blocks(user, Enum.uniq([blocked | user.blocks]))
|
||||
@spec add_to_block(User.t(), User.t()) ::
|
||||
{:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
|
||||
defp remove_from_block(%User{} = user, %User{} = blocked) do
|
||||
UserRelationship.delete_block(user, blocked)
|
||||
end
|
||||
|
||||
def remove_from_block(user, blocked) do
|
||||
set_blocks(user, List.delete(user.blocks, blocked))
|
||||
end
|
||||
|
||||
defp set_mutes(user, mutes) do
|
||||
params = %{mutes: mutes}
|
||||
|
||||
user
|
||||
|> cast(params, [:mutes])
|
||||
|> validate_required([:mutes])
|
||||
|> update_and_set_cache()
|
||||
end
|
||||
|
||||
def add_to_mutes(user, muted, notifications?) do
|
||||
with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
|
||||
set_notification_mutes(
|
||||
user,
|
||||
Enum.uniq([muted | user.muted_notifications]),
|
||||
notifications?
|
||||
)
|
||||
defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
|
||||
with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
|
||||
{:ok, user_notification_mute} <-
|
||||
(notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
|
||||
{:ok, nil} do
|
||||
{:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
|
||||
end
|
||||
end
|
||||
|
||||
def remove_from_mutes(user, muted) do
|
||||
with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
|
||||
set_notification_mutes(
|
||||
user,
|
||||
List.delete(user.muted_notifications, muted),
|
||||
true
|
||||
)
|
||||
defp remove_from_mutes(user, %User{} = muted_user) do
|
||||
with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
|
||||
{:ok, user_notification_mute} <-
|
||||
UserRelationship.delete_notification_mute(user, muted_user) do
|
||||
{:ok, [user_mute, user_notification_mute]}
|
||||
end
|
||||
end
|
||||
|
||||
defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
|
||||
{:ok, user}
|
||||
end
|
||||
|
||||
defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
|
||||
params = %{muted_notifications: muted_notifications}
|
||||
|
||||
user
|
||||
|> cast(params, [:muted_notifications])
|
||||
|> validate_required([:muted_notifications])
|
||||
|> update_and_set_cache()
|
||||
end
|
||||
|
||||
def add_reblog_mute(user, ap_id) do
|
||||
params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
|
||||
|
||||
user
|
||||
|> cast(params, [:muted_reblogs])
|
||||
|> update_and_set_cache()
|
||||
end
|
||||
|
||||
def remove_reblog_mute(user, ap_id) do
|
||||
params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
|
||||
|
||||
user
|
||||
|> cast(params, [:muted_reblogs])
|
||||
|> update_and_set_cache()
|
||||
end
|
||||
|
||||
def set_invisible(user, invisible) do
|
||||
params = %{invisible: invisible}
|
||||
|
||||
|
|
40
lib/pleroma/user/notification_setting.ex
Normal file
40
lib/pleroma/user/notification_setting.ex
Normal file
|
@ -0,0 +1,40 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.User.NotificationSetting do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@derive Jason.Encoder
|
||||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:followers, :boolean, default: true)
|
||||
field(:follows, :boolean, default: true)
|
||||
field(:non_follows, :boolean, default: true)
|
||||
field(:non_followers, :boolean, default: true)
|
||||
field(:privacy_option, :boolean, default: false)
|
||||
end
|
||||
|
||||
def changeset(schema, params) do
|
||||
schema
|
||||
|> cast(prepare_attrs(params), [
|
||||
:followers,
|
||||
:follows,
|
||||
:non_follows,
|
||||
:non_followers,
|
||||
:privacy_option
|
||||
])
|
||||
end
|
||||
|
||||
defp prepare_attrs(params) do
|
||||
Enum.reduce(params, %{}, fn
|
||||
{k, v}, acc when is_binary(v) ->
|
||||
Map.put(acc, k, String.downcase(v))
|
||||
|
||||
{k, v}, acc ->
|
||||
Map.put(acc, k, v)
|
||||
end)
|
||||
end
|
||||
end
|
|
@ -103,9 +103,13 @@ defp filter_invisible_users(query) do
|
|||
from(q in query, where: q.invisible == false)
|
||||
end
|
||||
|
||||
defp filter_blocked_user(query, %User{blocks: blocks})
|
||||
when length(blocks) > 0 do
|
||||
from(q in query, where: not (q.ap_id in ^blocks))
|
||||
defp filter_blocked_user(query, %User{} = blocker) do
|
||||
query
|
||||
|> join(:left, [u], b in Pleroma.UserRelationship,
|
||||
as: :blocks,
|
||||
on: b.relationship_type == ^:block and b.source_id == ^blocker.id and u.id == b.target_id
|
||||
)
|
||||
|> where([blocks: b], is_nil(b.target_id))
|
||||
end
|
||||
|
||||
defp filter_blocked_user(query, _), do: query
|
||||
|
|
92
lib/pleroma/user_relationship.ex
Normal file
92
lib/pleroma/user_relationship.ex
Normal file
|
@ -0,0 +1,92 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.UserRelationship do
|
||||
use Ecto.Schema
|
||||
|
||||
import Ecto.Changeset
|
||||
import Ecto.Query
|
||||
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.UserRelationship
|
||||
|
||||
schema "user_relationships" do
|
||||
belongs_to(:source, User, type: FlakeId.Ecto.CompatType)
|
||||
belongs_to(:target, User, type: FlakeId.Ecto.CompatType)
|
||||
field(:relationship_type, UserRelationshipTypeEnum)
|
||||
|
||||
timestamps(updated_at: false)
|
||||
end
|
||||
|
||||
for relationship_type <- Keyword.keys(UserRelationshipTypeEnum.__enum_map__()) do
|
||||
# Definitions of `create_block/2`, `create_mute/2` etc.
|
||||
def unquote(:"create_#{relationship_type}")(source, target),
|
||||
do: create(unquote(relationship_type), source, target)
|
||||
|
||||
# Definitions of `delete_block/2`, `delete_mute/2` etc.
|
||||
def unquote(:"delete_#{relationship_type}")(source, target),
|
||||
do: delete(unquote(relationship_type), source, target)
|
||||
|
||||
# Definitions of `block_exists?/2`, `mute_exists?/2` etc.
|
||||
def unquote(:"#{relationship_type}_exists?")(source, target),
|
||||
do: exists?(unquote(relationship_type), source, target)
|
||||
end
|
||||
|
||||
def changeset(%UserRelationship{} = user_relationship, params \\ %{}) do
|
||||
user_relationship
|
||||
|> cast(params, [:relationship_type, :source_id, :target_id])
|
||||
|> validate_required([:relationship_type, :source_id, :target_id])
|
||||
|> unique_constraint(:relationship_type,
|
||||
name: :user_relationships_source_id_relationship_type_target_id_index
|
||||
)
|
||||
|> validate_not_self_relationship()
|
||||
end
|
||||
|
||||
def exists?(relationship_type, %User{} = source, %User{} = target) do
|
||||
UserRelationship
|
||||
|> where(relationship_type: ^relationship_type, source_id: ^source.id, target_id: ^target.id)
|
||||
|> Repo.exists?()
|
||||
end
|
||||
|
||||
def create(relationship_type, %User{} = source, %User{} = target) do
|
||||
%UserRelationship{}
|
||||
|> changeset(%{
|
||||
relationship_type: relationship_type,
|
||||
source_id: source.id,
|
||||
target_id: target.id
|
||||
})
|
||||
|> Repo.insert(
|
||||
on_conflict: :replace_all_except_primary_key,
|
||||
conflict_target: [:source_id, :relationship_type, :target_id]
|
||||
)
|
||||
end
|
||||
|
||||
def delete(relationship_type, %User{} = source, %User{} = target) do
|
||||
attrs = %{relationship_type: relationship_type, source_id: source.id, target_id: target.id}
|
||||
|
||||
case Repo.get_by(UserRelationship, attrs) do
|
||||
%UserRelationship{} = existing_record -> Repo.delete(existing_record)
|
||||
nil -> {:ok, nil}
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do
|
||||
changeset
|
||||
|> validate_change(:target_id, fn _, target_id ->
|
||||
if target_id == get_field(changeset, :source_id) do
|
||||
[target_id: "can't be equal to source_id"]
|
||||
else
|
||||
[]
|
||||
end
|
||||
end)
|
||||
|> validate_change(:source_id, fn _, source_id ->
|
||||
if source_id == get_field(changeset, :target_id) do
|
||||
[source_id: "can't be equal to target_id"]
|
||||
else
|
||||
[]
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
|
@ -456,17 +456,18 @@ def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, options \\ [
|
|||
user = User.get_cached_by_ap_id(actor)
|
||||
to = (object.data["to"] || []) ++ (object.data["cc"] || [])
|
||||
|
||||
with {:ok, object, activity} <- Object.delete(object),
|
||||
with create_activity <- Activity.get_create_by_object_ap_id(id),
|
||||
data <-
|
||||
%{
|
||||
"type" => "Delete",
|
||||
"actor" => actor,
|
||||
"object" => id,
|
||||
"to" => to,
|
||||
"deleted_activity_id" => activity && activity.id
|
||||
"deleted_activity_id" => create_activity && create_activity.id
|
||||
}
|
||||
|> maybe_put("id", activity_id),
|
||||
{:ok, activity} <- insert(data, local, false),
|
||||
{:ok, object, _create_activity} <- Object.delete(object),
|
||||
stream_out_participations(object, user),
|
||||
_ <- decrease_replies_count_if_reply(object),
|
||||
{:ok, _actor} <- decrease_note_count_if_public(user, object),
|
||||
|
@ -748,6 +749,15 @@ def fetch_user_activities(user, reading_user, params \\ %{}) do
|
|||
|> Map.put("whole_db", true)
|
||||
|> Map.put("pinned_activity_ids", user.pinned_activities)
|
||||
|
||||
params =
|
||||
if User.blocks?(reading_user, user) do
|
||||
params
|
||||
else
|
||||
params
|
||||
|> Map.put("blocking_user", reading_user)
|
||||
|> Map.put("muting_user", reading_user)
|
||||
end
|
||||
|
||||
recipients =
|
||||
user_activities_recipients(%{
|
||||
"godmode" => params["godmode"],
|
||||
|
@ -919,7 +929,7 @@ defp restrict_reblogs(query, _), do: query
|
|||
defp restrict_muted(query, %{"with_muted" => val}) when val in [true, "true", "1"], do: query
|
||||
|
||||
defp restrict_muted(query, %{"muting_user" => %User{} = user} = opts) do
|
||||
mutes = user.mutes
|
||||
mutes = opts["muted_users_ap_ids"] || User.muted_users_ap_ids(user)
|
||||
|
||||
query =
|
||||
from([activity] in query,
|
||||
|
@ -936,8 +946,8 @@ defp restrict_muted(query, %{"muting_user" => %User{} = user} = opts) do
|
|||
|
||||
defp restrict_muted(query, _), do: query
|
||||
|
||||
defp restrict_blocked(query, %{"blocking_user" => %User{} = user}) do
|
||||
blocks = user.blocks || []
|
||||
defp restrict_blocked(query, %{"blocking_user" => %User{} = user} = opts) do
|
||||
blocked_ap_ids = opts["blocked_users_ap_ids"] || User.blocked_users_ap_ids(user)
|
||||
domain_blocks = user.domain_blocks || []
|
||||
|
||||
query =
|
||||
|
@ -945,14 +955,14 @@ defp restrict_blocked(query, %{"blocking_user" => %User{} = user}) do
|
|||
|
||||
from(
|
||||
[activity, object: o] in query,
|
||||
where: fragment("not (? = ANY(?))", activity.actor, ^blocks),
|
||||
where: fragment("not (? && ?)", activity.recipients, ^blocks),
|
||||
where: fragment("not (? = ANY(?))", activity.actor, ^blocked_ap_ids),
|
||||
where: fragment("not (? && ?)", activity.recipients, ^blocked_ap_ids),
|
||||
where:
|
||||
fragment(
|
||||
"not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
|
||||
activity.data,
|
||||
activity.data,
|
||||
^blocks
|
||||
^blocked_ap_ids
|
||||
),
|
||||
where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks),
|
||||
where: fragment("not (split_part(?->>'actor', '/', 3) = ANY(?))", o.data, ^domain_blocks)
|
||||
|
@ -979,8 +989,8 @@ defp restrict_pinned(query, %{"pinned" => "true", "pinned_activity_ids" => ids})
|
|||
|
||||
defp restrict_pinned(query, _), do: query
|
||||
|
||||
defp restrict_muted_reblogs(query, %{"muting_user" => %User{} = user}) do
|
||||
muted_reblogs = user.muted_reblogs || []
|
||||
defp restrict_muted_reblogs(query, %{"muting_user" => %User{} = user} = opts) do
|
||||
muted_reblogs = opts["reblog_muted_users_ap_ids"] || User.reblog_muted_users_ap_ids(user)
|
||||
|
||||
from(
|
||||
activity in query,
|
||||
|
@ -1061,7 +1071,33 @@ defp maybe_order(query, %{order: :asc}) do
|
|||
|
||||
defp maybe_order(query, _), do: query
|
||||
|
||||
defp fetch_activities_query_ap_ids_ops(opts) do
|
||||
source_user = opts["muting_user"]
|
||||
ap_id_relations = if source_user, do: [:mute, :reblog_mute], else: []
|
||||
|
||||
ap_id_relations =
|
||||
ap_id_relations ++
|
||||
if opts["blocking_user"] && opts["blocking_user"] == source_user do
|
||||
[:block]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
preloaded_ap_ids = User.outgoing_relations_ap_ids(source_user, ap_id_relations)
|
||||
|
||||
restrict_blocked_opts = Map.merge(%{"blocked_users_ap_ids" => preloaded_ap_ids[:block]}, opts)
|
||||
restrict_muted_opts = Map.merge(%{"muted_users_ap_ids" => preloaded_ap_ids[:mute]}, opts)
|
||||
|
||||
restrict_muted_reblogs_opts =
|
||||
Map.merge(%{"reblog_muted_users_ap_ids" => preloaded_ap_ids[:reblog_mute]}, opts)
|
||||
|
||||
{restrict_blocked_opts, restrict_muted_opts, restrict_muted_reblogs_opts}
|
||||
end
|
||||
|
||||
def fetch_activities_query(recipients, opts \\ %{}) do
|
||||
{restrict_blocked_opts, restrict_muted_opts, restrict_muted_reblogs_opts} =
|
||||
fetch_activities_query_ap_ids_ops(opts)
|
||||
|
||||
config = %{
|
||||
skip_thread_containment: Config.get([:instance, :skip_thread_containment])
|
||||
}
|
||||
|
@ -1081,15 +1117,15 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|
|||
|> restrict_type(opts)
|
||||
|> restrict_state(opts)
|
||||
|> restrict_favorited_by(opts)
|
||||
|> restrict_blocked(opts)
|
||||
|> restrict_muted(opts)
|
||||
|> restrict_blocked(restrict_blocked_opts)
|
||||
|> restrict_muted(restrict_muted_opts)
|
||||
|> restrict_media(opts)
|
||||
|> restrict_visibility(opts)
|
||||
|> restrict_thread_visibility(opts, config)
|
||||
|> restrict_replies(opts)
|
||||
|> restrict_reblogs(opts)
|
||||
|> restrict_pinned(opts)
|
||||
|> restrict_muted_reblogs(opts)
|
||||
|> restrict_muted_reblogs(restrict_muted_reblogs_opts)
|
||||
|> restrict_instance(opts)
|
||||
|> Activity.restrict_deactivated_users()
|
||||
|> exclude_poll_votes(opts)
|
||||
|
|
|
@ -387,7 +387,7 @@ def handle_incoming(%{"type" => "Flag", "object" => objects, "actor" => actor} =
|
|||
def handle_incoming(%{"id" => nil}, _options), do: :error
|
||||
def handle_incoming(%{"id" => ""}, _options), do: :error
|
||||
# length of https:// = 8, should validate better, but good enough for now.
|
||||
def handle_incoming(%{"id" => id}, _options) when not (is_binary(id) and length(id) > 8),
|
||||
def handle_incoming(%{"id" => id}, _options) when is_binary(id) and byte_size(id) < 8,
|
||||
do: :error
|
||||
|
||||
# TODO: validate those with a Ecto scheme
|
||||
|
|
|
@ -722,16 +722,22 @@ defp build_flag_object(act) when is_map(act) or is_binary(act) do
|
|||
act when is_binary(act) -> act
|
||||
end
|
||||
|
||||
activity = Activity.get_by_ap_id_with_object(id)
|
||||
actor = User.get_by_ap_id(activity.object.data["actor"])
|
||||
case Activity.get_by_ap_id_with_object(id) do
|
||||
%Activity{} = activity ->
|
||||
%{
|
||||
"type" => "Note",
|
||||
"id" => activity.data["id"],
|
||||
"content" => activity.object.data["content"],
|
||||
"published" => activity.object.data["published"],
|
||||
"actor" =>
|
||||
AccountView.render("show.json", %{
|
||||
user: User.get_by_ap_id(activity.object.data["actor"])
|
||||
})
|
||||
}
|
||||
|
||||
%{
|
||||
"type" => "Note",
|
||||
"id" => activity.data["id"],
|
||||
"content" => activity.object.data["content"],
|
||||
"published" => activity.object.data["published"],
|
||||
"actor" => AccountView.render("show.json", %{user: actor})
|
||||
}
|
||||
_ ->
|
||||
%{"id" => id, "deleted" => true}
|
||||
end
|
||||
end
|
||||
|
||||
defp build_flag_object(_), do: []
|
||||
|
@ -788,7 +794,52 @@ def get_reports(params, page, page_size) do
|
|||
ActivityPub.fetch_activities([], params, :offset)
|
||||
end
|
||||
|
||||
@spec get_reports_grouped_by_status(%{required(:activity) => String.t()}) :: %{
|
||||
def parse_report_group(activity) do
|
||||
reports = get_reports_by_status_id(activity["id"])
|
||||
max_date = Enum.max_by(reports, &NaiveDateTime.from_iso8601!(&1.data["published"]))
|
||||
actors = Enum.map(reports, & &1.user_actor)
|
||||
[%{data: %{"object" => [account_id | _]}} | _] = reports
|
||||
|
||||
account =
|
||||
AccountView.render("show.json", %{
|
||||
user: User.get_by_ap_id(account_id)
|
||||
})
|
||||
|
||||
status = get_status_data(activity)
|
||||
|
||||
%{
|
||||
date: max_date.data["published"],
|
||||
account: account,
|
||||
status: status,
|
||||
actors: Enum.uniq(actors),
|
||||
reports: reports
|
||||
}
|
||||
end
|
||||
|
||||
defp get_status_data(status) do
|
||||
case status["deleted"] do
|
||||
true ->
|
||||
%{
|
||||
"id" => status["id"],
|
||||
"deleted" => true
|
||||
}
|
||||
|
||||
_ ->
|
||||
Activity.get_by_ap_id(status["id"])
|
||||
end
|
||||
end
|
||||
|
||||
def get_reports_by_status_id(ap_id) do
|
||||
from(a in Activity,
|
||||
where: fragment("(?)->>'type' = 'Flag'", a.data),
|
||||
where: fragment("(?)->'object' @> ?", a.data, ^[%{id: ap_id}]),
|
||||
or_where: fragment("(?)->'object' @> ?", a.data, ^[ap_id])
|
||||
)
|
||||
|> Activity.with_preloaded_user_actor()
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@spec get_reports_grouped_by_status([String.t()]) :: %{
|
||||
required(:groups) => [
|
||||
%{
|
||||
required(:date) => String.t(),
|
||||
|
@ -797,20 +848,15 @@ def get_reports(params, page, page_size) do
|
|||
required(:actors) => [%User{}],
|
||||
required(:reports) => [%Activity{}]
|
||||
}
|
||||
],
|
||||
required(:total) => integer
|
||||
]
|
||||
}
|
||||
def get_reports_grouped_by_status(groups) do
|
||||
def get_reports_grouped_by_status(activity_ids) do
|
||||
parsed_groups =
|
||||
groups
|
||||
|> Enum.map(fn entry ->
|
||||
activity =
|
||||
case Jason.decode(entry.activity) do
|
||||
{:ok, activity} -> activity
|
||||
_ -> build_flag_object(entry.activity)
|
||||
end
|
||||
|
||||
parse_report_group(activity)
|
||||
activity_ids
|
||||
|> Enum.map(fn id ->
|
||||
id
|
||||
|> build_flag_object()
|
||||
|> parse_report_group()
|
||||
end)
|
||||
|
||||
%{
|
||||
|
@ -818,33 +864,6 @@ def get_reports_grouped_by_status(groups) do
|
|||
}
|
||||
end
|
||||
|
||||
def parse_report_group(activity) do
|
||||
reports = get_reports_by_status_id(activity["id"])
|
||||
max_date = Enum.max_by(reports, &NaiveDateTime.from_iso8601!(&1.data["published"]))
|
||||
actors = Enum.map(reports, & &1.user_actor)
|
||||
|
||||
%{
|
||||
date: max_date.data["published"],
|
||||
account: activity["actor"],
|
||||
status: %{
|
||||
id: activity["id"],
|
||||
content: activity["content"],
|
||||
published: activity["published"]
|
||||
},
|
||||
actors: Enum.uniq(actors),
|
||||
reports: reports
|
||||
}
|
||||
end
|
||||
|
||||
def get_reports_by_status_id(ap_id) do
|
||||
from(a in Activity,
|
||||
where: fragment("(?)->>'type' = 'Flag'", a.data),
|
||||
where: fragment("(?)->'object' @> ?", a.data, ^[%{id: ap_id}])
|
||||
)
|
||||
|> Activity.with_preloaded_user_actor()
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@spec get_reported_activities() :: [
|
||||
%{
|
||||
required(:activity) => String.t(),
|
||||
|
@ -852,17 +871,23 @@ def get_reports_by_status_id(ap_id) do
|
|||
}
|
||||
]
|
||||
def get_reported_activities do
|
||||
from(a in Activity,
|
||||
where: fragment("(?)->>'type' = 'Flag'", a.data),
|
||||
reported_activities_query =
|
||||
from(a in Activity,
|
||||
where: fragment("(?)->>'type' = 'Flag'", a.data),
|
||||
select: %{
|
||||
activity: fragment("jsonb_array_elements((? #- '{object,0}')->'object')", a.data)
|
||||
},
|
||||
group_by: fragment("activity")
|
||||
)
|
||||
|
||||
from(a in subquery(reported_activities_query),
|
||||
distinct: true,
|
||||
select: %{
|
||||
date: fragment("max(?->>'published') date", a.data),
|
||||
activity:
|
||||
fragment("jsonb_array_elements_text((? #- '{object,0}')->'object') activity", a.data)
|
||||
},
|
||||
group_by: fragment("activity"),
|
||||
order_by: fragment("date DESC")
|
||||
id: fragment("COALESCE(?->>'id'::text, ? #>> '{}')", a.activity, a.activity)
|
||||
}
|
||||
)
|
||||
|> Repo.all()
|
||||
|> Enum.map(& &1.id)
|
||||
end
|
||||
|
||||
def update_report_state(%Activity{} = activity, state)
|
||||
|
|
|
@ -649,11 +649,11 @@ def list_reports(conn, params) do
|
|||
end
|
||||
|
||||
def list_grouped_reports(conn, _params) do
|
||||
reports = Utils.get_reported_activities()
|
||||
statuses = Utils.get_reported_activities()
|
||||
|
||||
conn
|
||||
|> put_view(ReportView)
|
||||
|> render("index_grouped.json", Utils.get_reports_grouped_by_status(reports))
|
||||
|> render("index_grouped.json", Utils.get_reports_grouped_by_status(statuses))
|
||||
end
|
||||
|
||||
def report_show(conn, %{"id" => id}) do
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
defmodule Pleroma.Web.AdminAPI.ReportView do
|
||||
use Pleroma.Web, :view
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.HTML
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.AdminAPI.Report
|
||||
|
@ -45,10 +46,16 @@ def render("show.json", %{report: report, user: user, account: account, statuses
|
|||
def render("index_grouped.json", %{groups: groups}) do
|
||||
reports =
|
||||
Enum.map(groups, fn group ->
|
||||
status =
|
||||
case group.status do
|
||||
%Activity{} = activity -> StatusView.render("show.json", %{activity: activity})
|
||||
_ -> group.status
|
||||
end
|
||||
|
||||
%{
|
||||
date: group[:date],
|
||||
account: group[:account],
|
||||
status: group[:status],
|
||||
status: Map.put_new(status, "deleted", false),
|
||||
actors: Enum.map(group[:actors], &merge_account_views/1),
|
||||
reports:
|
||||
group[:reports]
|
||||
|
|
|
@ -20,7 +20,7 @@ def handle_info(:after_join, socket) do
|
|||
def handle_in("new_msg", %{"text" => text}, %{assigns: %{user_name: user_name}} = socket) do
|
||||
text = String.trim(text)
|
||||
|
||||
if String.length(text) > 0 do
|
||||
if String.length(text) in 1..Pleroma.Config.get([:instance, :chat_limit]) do
|
||||
author = User.get_cached_by_nickname(user_name)
|
||||
author = Pleroma.Web.MastodonAPI.AccountView.render("show.json", user: author)
|
||||
message = ChatChannelState.add_message(%{text: text, author: author})
|
||||
|
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
alias Pleroma.Object
|
||||
alias Pleroma.ThreadMute
|
||||
alias Pleroma.User
|
||||
alias Pleroma.UserRelationship
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
|
@ -32,7 +33,7 @@ def follow(follower, followed) do
|
|||
def unfollow(follower, unfollowed) do
|
||||
with {:ok, follower, _follow_activity} <- User.unfollow(follower, unfollowed),
|
||||
{:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed),
|
||||
{:ok, _unfollowed} <- User.unsubscribe(follower, unfollowed) do
|
||||
{:ok, _subscription} <- User.unsubscribe(follower, unfollowed) do
|
||||
{:ok, follower}
|
||||
end
|
||||
end
|
||||
|
@ -420,15 +421,11 @@ defp set_visibility(activity, %{"visibility" => visibility}) do
|
|||
|
||||
defp set_visibility(activity, _), do: {:ok, activity}
|
||||
|
||||
def hide_reblogs(user, %{ap_id: ap_id} = _muted) do
|
||||
if ap_id not in user.muted_reblogs do
|
||||
User.add_reblog_mute(user, ap_id)
|
||||
end
|
||||
def hide_reblogs(%User{} = user, %User{} = target) do
|
||||
UserRelationship.create_reblog_mute(user, target)
|
||||
end
|
||||
|
||||
def show_reblogs(user, %{ap_id: ap_id} = _muted) do
|
||||
if ap_id in user.muted_reblogs do
|
||||
User.remove_reblog_mute(user, ap_id)
|
||||
end
|
||||
def show_reblogs(%User{} = user, %User{} = target) do
|
||||
UserRelationship.delete_reblog_mute(user, target)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -494,7 +494,7 @@ def maybe_notify_subscribers(
|
|||
with %User{} = user <- User.get_cached_by_ap_id(actor) do
|
||||
subscriber_ids =
|
||||
user
|
||||
|> User.subscribers()
|
||||
|> User.subscriber_users()
|
||||
|> Enum.filter(&Visibility.visible_for_user?(activity, &1))
|
||||
|> Enum.map(& &1.ap_id)
|
||||
|
||||
|
|
|
@ -61,14 +61,7 @@ defmodule Pleroma.Web.Endpoint do
|
|||
plug(Plug.RequestId)
|
||||
plug(Plug.Logger)
|
||||
|
||||
plug(
|
||||
Plug.Parsers,
|
||||
parsers: [:urlencoded, :multipart, :json],
|
||||
pass: ["*/*"],
|
||||
json_decoder: Jason,
|
||||
length: Pleroma.Config.get([:instance, :upload_limit]),
|
||||
body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []}
|
||||
)
|
||||
plug(Pleroma.Plugs.Parsers)
|
||||
|
||||
plug(Plug.MethodOverride)
|
||||
plug(Plug.Head)
|
||||
|
|
|
@ -249,7 +249,11 @@ def show(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do
|
|||
@doc "GET /api/v1/accounts/:id/statuses"
|
||||
def statuses(%{assigns: %{user: reading_user}} = conn, params) do
|
||||
with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user) do
|
||||
params = Map.put(params, "tag", params["tagged"])
|
||||
params =
|
||||
params
|
||||
|> Map.put("tag", params["tagged"])
|
||||
|> Map.delete("godmode")
|
||||
|
||||
activities = ActivityPub.fetch_user_activities(user, reading_user, params)
|
||||
|
||||
conn
|
||||
|
@ -324,7 +328,7 @@ def unfollow(%{assigns: %{user: follower, account: followed}} = conn, _params) d
|
|||
def mute(%{assigns: %{user: muter, account: muted}} = conn, params) do
|
||||
notifications? = params |> Map.get("notifications", true) |> truthy_param?()
|
||||
|
||||
with {:ok, muter} <- User.mute(muter, muted, notifications?) do
|
||||
with {:ok, _user_relationships} <- User.mute(muter, muted, notifications?) do
|
||||
render(conn, "relationship.json", user: muter, target: muted)
|
||||
else
|
||||
{:error, message} -> json_response(conn, :forbidden, %{error: message})
|
||||
|
@ -333,7 +337,7 @@ def mute(%{assigns: %{user: muter, account: muted}} = conn, params) do
|
|||
|
||||
@doc "POST /api/v1/accounts/:id/unmute"
|
||||
def unmute(%{assigns: %{user: muter, account: muted}} = conn, _params) do
|
||||
with {:ok, muter} <- User.unmute(muter, muted) do
|
||||
with {:ok, _user_relationships} <- User.unmute(muter, muted) do
|
||||
render(conn, "relationship.json", user: muter, target: muted)
|
||||
else
|
||||
{:error, message} -> json_response(conn, :forbidden, %{error: message})
|
||||
|
@ -342,7 +346,7 @@ def unmute(%{assigns: %{user: muter, account: muted}} = conn, _params) do
|
|||
|
||||
@doc "POST /api/v1/accounts/:id/block"
|
||||
def block(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
|
||||
with {:ok, blocker} <- User.block(blocker, blocked),
|
||||
with {:ok, _user_block} <- User.block(blocker, blocked),
|
||||
{:ok, _activity} <- ActivityPub.block(blocker, blocked) do
|
||||
render(conn, "relationship.json", user: blocker, target: blocked)
|
||||
else
|
||||
|
@ -352,7 +356,7 @@ def block(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
|
|||
|
||||
@doc "POST /api/v1/accounts/:id/unblock"
|
||||
def unblock(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
|
||||
with {:ok, blocker} <- User.unblock(blocker, blocked),
|
||||
with {:ok, _user_block} <- User.unblock(blocker, blocked),
|
||||
{:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do
|
||||
render(conn, "relationship.json", user: blocker, target: blocked)
|
||||
else
|
||||
|
@ -374,12 +378,14 @@ def follows(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
|
|||
|
||||
@doc "GET /api/v1/mutes"
|
||||
def mutes(%{assigns: %{user: user}} = conn, _) do
|
||||
render(conn, "index.json", users: User.muted_users(user), for: user, as: :user)
|
||||
users = User.muted_users(user, _restrict_deactivated = true)
|
||||
render(conn, "index.json", users: users, for: user, as: :user)
|
||||
end
|
||||
|
||||
@doc "GET /api/v1/blocks"
|
||||
def blocks(%{assigns: %{user: user}} = conn, _) do
|
||||
render(conn, "index.json", users: User.blocked_users(user), for: user, as: :user)
|
||||
users = User.blocked_users(user, _restrict_deactivated = true)
|
||||
render(conn, "index.json", users: users, for: user, as: :user)
|
||||
end
|
||||
|
||||
@doc "GET /api/v1/endorsements"
|
||||
|
|
|
@ -24,19 +24,16 @@ def follow(follower, followed, params \\ %{}) do
|
|||
|
||||
with {:ok, follower, _followed, _} <- result do
|
||||
options = cast_params(params)
|
||||
|
||||
case reblogs_visibility(options[:reblogs], result) do
|
||||
{:ok, follower} -> {:ok, follower}
|
||||
_ -> {:ok, follower}
|
||||
end
|
||||
set_reblogs_visibility(options[:reblogs], result)
|
||||
{:ok, follower}
|
||||
end
|
||||
end
|
||||
|
||||
defp reblogs_visibility(false, {:ok, follower, followed, _}) do
|
||||
defp set_reblogs_visibility(false, {:ok, follower, followed, _}) do
|
||||
CommonAPI.hide_reblogs(follower, followed)
|
||||
end
|
||||
|
||||
defp reblogs_visibility(_, {:ok, follower, followed, _}) do
|
||||
defp set_reblogs_visibility(_, {:ok, follower, followed, _}) do
|
||||
CommonAPI.show_reblogs(follower, followed)
|
||||
end
|
||||
|
||||
|
@ -73,7 +70,8 @@ defp cast_params(params) do
|
|||
exclude_types: {:array, :string},
|
||||
exclude_visibilities: {:array, :string},
|
||||
reblogs: :boolean,
|
||||
with_muted: :boolean
|
||||
with_muted: :boolean,
|
||||
with_move: :boolean
|
||||
}
|
||||
|
||||
changeset = cast({%{}, param_types}, params, Map.keys(param_types))
|
||||
|
|
|
@ -50,8 +50,8 @@ def render("relationship.json", %{user: %User{} = user, target: %User{} = target
|
|||
id: to_string(target.id),
|
||||
following: User.following?(user, target),
|
||||
followed_by: User.following?(target, user),
|
||||
blocking: User.blocks_ap_id?(user, target),
|
||||
blocked_by: User.blocks_ap_id?(target, user),
|
||||
blocking: User.blocks_user?(user, target),
|
||||
blocked_by: User.blocks_user?(target, user),
|
||||
muting: User.mutes?(user, target),
|
||||
muting_notifications: User.muted_notifications?(user, target),
|
||||
subscribing: User.subscribed_to?(user, target),
|
||||
|
|
|
@ -11,11 +11,6 @@ defmodule Pleroma.Web.OAuth.Token.CleanWorker do
|
|||
@ten_seconds 10_000
|
||||
@one_day 86_400_000
|
||||
|
||||
@interval Pleroma.Config.get(
|
||||
[:oauth2, :clean_expired_tokens_interval],
|
||||
@one_day
|
||||
)
|
||||
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
alias Pleroma.Workers.BackgroundWorker
|
||||
|
||||
|
@ -29,8 +24,9 @@ def init(_) do
|
|||
@doc false
|
||||
def handle_info(:perform, state) do
|
||||
BackgroundWorker.enqueue("clean_expired_tokens", %{})
|
||||
interval = Pleroma.Config.get([:oauth2, :clean_expired_tokens_interval], @one_day)
|
||||
|
||||
Process.send_after(self(), :perform, @interval)
|
||||
Process.send_after(self(), :perform, interval)
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
|
|
|
@ -144,7 +144,7 @@ def favourites(%{assigns: %{user: for_user, account: user}} = conn, params) do
|
|||
|
||||
@doc "POST /api/v1/pleroma/accounts/:id/subscribe"
|
||||
def subscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do
|
||||
with {:ok, subscription_target} <- User.subscribe(user, subscription_target) do
|
||||
with {:ok, _subscription} <- User.subscribe(user, subscription_target) do
|
||||
render(conn, "relationship.json", user: user, target: subscription_target)
|
||||
else
|
||||
{:error, message} -> json_response(conn, :forbidden, %{error: message})
|
||||
|
@ -153,7 +153,7 @@ def subscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _p
|
|||
|
||||
@doc "POST /api/v1/pleroma/accounts/:id/unsubscribe"
|
||||
def unsubscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do
|
||||
with {:ok, subscription_target} <- User.unsubscribe(user, subscription_target) do
|
||||
with {:ok, _subscription} <- User.unsubscribe(user, subscription_target) do
|
||||
render(conn, "relationship.json", user: user, target: subscription_target)
|
||||
else
|
||||
{:error, message} -> json_response(conn, :forbidden, %{error: message})
|
||||
|
|
|
@ -22,8 +22,8 @@ defmodule Pleroma.Web.Push.Impl do
|
|||
@spec perform(Notification.t()) :: list(any) | :error
|
||||
def perform(
|
||||
%{
|
||||
activity: %{data: %{"type" => activity_type}, id: activity_id} = activity,
|
||||
user_id: user_id
|
||||
activity: %{data: %{"type" => activity_type}} = activity,
|
||||
user: %User{id: user_id}
|
||||
} = notif
|
||||
)
|
||||
when activity_type in @types do
|
||||
|
@ -39,18 +39,17 @@ def perform(
|
|||
for subscription <- fetch_subsriptions(user_id),
|
||||
get_in(subscription.data, ["alerts", type]) do
|
||||
%{
|
||||
title: format_title(notif),
|
||||
access_token: subscription.token.token,
|
||||
body: format_body(notif, actor, object),
|
||||
notification_id: notif.id,
|
||||
notification_type: type,
|
||||
icon: avatar_url,
|
||||
preferred_locale: "en",
|
||||
pleroma: %{
|
||||
activity_id: activity_id,
|
||||
activity_id: notif.activity.id,
|
||||
direct_conversation_id: direct_conversation_id
|
||||
}
|
||||
}
|
||||
|> Map.merge(build_content(notif, actor, object))
|
||||
|> Jason.encode!()
|
||||
|> push_message(build_sub(subscription), gcm_api_key, subscription)
|
||||
end
|
||||
|
@ -100,6 +99,24 @@ def build_sub(subscription) do
|
|||
}
|
||||
end
|
||||
|
||||
def build_content(
|
||||
%{
|
||||
activity: %{data: %{"directMessage" => true}},
|
||||
user: %{notification_settings: %{privacy_option: true}}
|
||||
},
|
||||
actor,
|
||||
_
|
||||
) do
|
||||
%{title: "New Direct Message", body: "@#{actor.nickname}"}
|
||||
end
|
||||
|
||||
def build_content(notif, actor, object) do
|
||||
%{
|
||||
title: format_title(notif),
|
||||
body: format_body(notif, actor, object)
|
||||
}
|
||||
end
|
||||
|
||||
def format_body(
|
||||
%{activity: %{data: %{"type" => "Create"}}},
|
||||
actor,
|
||||
|
|
|
@ -129,16 +129,17 @@ defp do_stream(%{topic: topic, item: item}) do
|
|||
end
|
||||
|
||||
defp should_send?(%User{} = user, %Activity{} = item) do
|
||||
blocks = user.blocks || []
|
||||
mutes = user.mutes || []
|
||||
reblog_mutes = user.muted_reblogs || []
|
||||
recipient_blocks = MapSet.new(blocks ++ mutes)
|
||||
%{block: blocked_ap_ids, mute: muted_ap_ids, reblog_mute: reblog_muted_ap_ids} =
|
||||
User.outgoing_relations_ap_ids(user, [:block, :mute, :reblog_mute])
|
||||
|
||||
recipient_blocks = MapSet.new(blocked_ap_ids ++ muted_ap_ids)
|
||||
recipients = MapSet.new(item.recipients)
|
||||
domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
|
||||
|
||||
with parent <- Object.normalize(item) || item,
|
||||
true <- Enum.all?([blocks, mutes, reblog_mutes], &(item.actor not in &1)),
|
||||
true <- Enum.all?([blocks, mutes], &(parent.data["actor"] not in &1)),
|
||||
true <-
|
||||
Enum.all?([blocked_ap_ids, muted_ap_ids, reblog_muted_ap_ids], &(item.actor not in &1)),
|
||||
true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(parent.data["actor"] not in &1)),
|
||||
true <- MapSet.disjoint?(recipients, recipient_blocks),
|
||||
%{host: item_host} <- URI.parse(item.actor),
|
||||
%{host: parent_host} <- URI.parse(parent.data["actor"]),
|
||||
|
|
|
@ -13,7 +13,7 @@ def perform(%{"op" => "web_push", "notification_id" => notification_id}, _job) d
|
|||
notification =
|
||||
Notification
|
||||
|> Repo.get(notification_id)
|
||||
|> Repo.preload([:activity])
|
||||
|> Repo.preload([:activity, :user])
|
||||
|
||||
Pleroma.Web.Push.Impl.perform(notification)
|
||||
end
|
||||
|
|
1
mix.exs
1
mix.exs
|
@ -100,6 +100,7 @@ defp deps do
|
|||
{:plug_cowboy, "~> 2.0"},
|
||||
{:phoenix_pubsub, "~> 1.1"},
|
||||
{:phoenix_ecto, "~> 4.0"},
|
||||
{:ecto_enum, "~> 1.4"},
|
||||
{:ecto_sql, "~> 3.2"},
|
||||
{:postgrex, ">= 0.13.5"},
|
||||
{:oban, "~> 0.12.0"},
|
||||
|
|
1
mix.lock
1
mix.lock
|
@ -24,6 +24,7 @@
|
|||
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm"},
|
||||
"earmark": {:hex, :earmark, "1.4.2", "3aa0bd23bc4c61cf2f1e5d752d1bb470560a6f8539974f767a38923bb20e1d7f", [:mix], [], "hexpm"},
|
||||
"ecto": {:hex, :ecto, "3.2.5", "76c864b77948a479e18e69cc1d0f0f4ee7cced1148ffe6a093ff91eba644f0b5", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"ecto_sql": {:hex, :ecto_sql, "3.2.2", "d10845bc147b9f61ef485cbf0973c0a337237199bd9bd30dd9542db00aadc26b", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.2.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.2.0 or ~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm"},
|
||||
"eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm"},
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
defmodule Pleroma.Repo.Migrations.CreateUserRelationships do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create_if_not_exists table(:user_relationships) do
|
||||
add(:source_id, references(:users, type: :uuid, on_delete: :delete_all))
|
||||
add(:target_id, references(:users, type: :uuid, on_delete: :delete_all))
|
||||
add(:relationship_type, :integer, null: false)
|
||||
|
||||
timestamps(updated_at: false)
|
||||
end
|
||||
|
||||
create_if_not_exists(
|
||||
unique_index(:user_relationships, [:source_id, :relationship_type, :target_id])
|
||||
)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,68 @@
|
|||
defmodule Pleroma.Repo.Migrations.DataMigrationPopulateUserRelationships do
|
||||
use Ecto.Migration
|
||||
|
||||
alias Ecto.Adapters.SQL
|
||||
alias Pleroma.Repo
|
||||
|
||||
require Logger
|
||||
|
||||
def up do
|
||||
Enum.each(
|
||||
[blocks: 1, mutes: 2, muted_reblogs: 3, muted_notifications: 4, subscribers: 5],
|
||||
fn {field, relationship_type_code} ->
|
||||
migrate(field, relationship_type_code)
|
||||
|
||||
if field == :subscribers do
|
||||
drop_if_exists(index(:users, [:subscribers]))
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
def down, do: :noop
|
||||
|
||||
defp migrate(field, relationship_type_code) do
|
||||
Logger.info("Processing users.#{field}...")
|
||||
|
||||
{:ok, %{rows: field_rows}} =
|
||||
SQL.query(Repo, "SELECT id, #{field} FROM users WHERE #{field} != '{}'")
|
||||
|
||||
target_ap_ids =
|
||||
Enum.flat_map(
|
||||
field_rows,
|
||||
fn [_, ap_ids] -> ap_ids end
|
||||
)
|
||||
|> Enum.uniq()
|
||||
|
||||
# Selecting ids of all targets at once in order to reduce the number of SELECT queries
|
||||
{:ok, %{rows: target_ap_id_id}} =
|
||||
SQL.query(Repo, "SELECT ap_id, id FROM users WHERE ap_id = ANY($1)", [target_ap_ids])
|
||||
|
||||
target_id_by_ap_id = Enum.into(target_ap_id_id, %{}, fn [k, v] -> {k, v} end)
|
||||
|
||||
Enum.each(
|
||||
field_rows,
|
||||
fn [source_id, target_ap_ids] ->
|
||||
source_uuid = Ecto.UUID.cast!(source_id)
|
||||
|
||||
for target_ap_id <- target_ap_ids do
|
||||
target_id = target_id_by_ap_id[target_ap_id]
|
||||
|
||||
with {:ok, target_uuid} <- target_id && Ecto.UUID.cast(target_id) do
|
||||
execute("""
|
||||
INSERT INTO user_relationships(
|
||||
source_id, target_id, relationship_type, inserted_at
|
||||
)
|
||||
VALUES(
|
||||
'#{source_uuid}'::uuid, '#{target_uuid}'::uuid, #{relationship_type_code}, now()
|
||||
)
|
||||
ON CONFLICT (source_id, relationship_type, target_id) DO NOTHING
|
||||
""")
|
||||
else
|
||||
_ -> Logger.warn("Unresolved #{field} reference: (#{source_uuid}, #{target_id})")
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
end
|
93
priv/scrubbers/default.ex
Normal file
93
priv/scrubbers/default.ex
Normal file
|
@ -0,0 +1,93 @@
|
|||
defmodule Pleroma.HTML.Scrubber.Default do
|
||||
@doc "The default HTML scrubbing policy: no "
|
||||
|
||||
require FastSanitize.Sanitizer.Meta
|
||||
alias FastSanitize.Sanitizer.Meta
|
||||
|
||||
# credo:disable-for-previous-line
|
||||
# No idea how to fix this one…
|
||||
|
||||
@valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
|
||||
|
||||
Meta.strip_comments()
|
||||
|
||||
Meta.allow_tag_with_uri_attributes(:a, ["href", "data-user", "data-tag"], @valid_schemes)
|
||||
|
||||
Meta.allow_tag_with_this_attribute_values(:a, "class", [
|
||||
"hashtag",
|
||||
"u-url",
|
||||
"mention",
|
||||
"u-url mention",
|
||||
"mention u-url"
|
||||
])
|
||||
|
||||
Meta.allow_tag_with_this_attribute_values(:a, "rel", [
|
||||
"tag",
|
||||
"nofollow",
|
||||
"noopener",
|
||||
"noreferrer",
|
||||
"ugc"
|
||||
])
|
||||
|
||||
Meta.allow_tag_with_these_attributes(:a, ["name", "title"])
|
||||
|
||||
Meta.allow_tag_with_these_attributes(:abbr, ["title"])
|
||||
|
||||
Meta.allow_tag_with_these_attributes(:b, [])
|
||||
Meta.allow_tag_with_these_attributes(:blockquote, [])
|
||||
Meta.allow_tag_with_these_attributes(:br, [])
|
||||
Meta.allow_tag_with_these_attributes(:code, [])
|
||||
Meta.allow_tag_with_these_attributes(:del, [])
|
||||
Meta.allow_tag_with_these_attributes(:em, [])
|
||||
Meta.allow_tag_with_these_attributes(:i, [])
|
||||
Meta.allow_tag_with_these_attributes(:li, [])
|
||||
Meta.allow_tag_with_these_attributes(:ol, [])
|
||||
Meta.allow_tag_with_these_attributes(:p, [])
|
||||
Meta.allow_tag_with_these_attributes(:pre, [])
|
||||
Meta.allow_tag_with_these_attributes(:strong, [])
|
||||
Meta.allow_tag_with_these_attributes(:sub, [])
|
||||
Meta.allow_tag_with_these_attributes(:sup, [])
|
||||
Meta.allow_tag_with_these_attributes(:u, [])
|
||||
Meta.allow_tag_with_these_attributes(:ul, [])
|
||||
|
||||
Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card"])
|
||||
Meta.allow_tag_with_these_attributes(:span, [])
|
||||
|
||||
@allow_inline_images Pleroma.Config.get([:markup, :allow_inline_images])
|
||||
|
||||
if @allow_inline_images do
|
||||
# restrict img tags to http/https only, because of MediaProxy.
|
||||
Meta.allow_tag_with_uri_attributes(:img, ["src"], ["http", "https"])
|
||||
|
||||
Meta.allow_tag_with_these_attributes(:img, [
|
||||
"width",
|
||||
"height",
|
||||
"class",
|
||||
"title",
|
||||
"alt"
|
||||
])
|
||||
end
|
||||
|
||||
if Pleroma.Config.get([:markup, :allow_tables]) do
|
||||
Meta.allow_tag_with_these_attributes(:table, [])
|
||||
Meta.allow_tag_with_these_attributes(:tbody, [])
|
||||
Meta.allow_tag_with_these_attributes(:td, [])
|
||||
Meta.allow_tag_with_these_attributes(:th, [])
|
||||
Meta.allow_tag_with_these_attributes(:thead, [])
|
||||
Meta.allow_tag_with_these_attributes(:tr, [])
|
||||
end
|
||||
|
||||
if Pleroma.Config.get([:markup, :allow_headings]) do
|
||||
Meta.allow_tag_with_these_attributes(:h1, [])
|
||||
Meta.allow_tag_with_these_attributes(:h2, [])
|
||||
Meta.allow_tag_with_these_attributes(:h3, [])
|
||||
Meta.allow_tag_with_these_attributes(:h4, [])
|
||||
Meta.allow_tag_with_these_attributes(:h5, [])
|
||||
end
|
||||
|
||||
if Pleroma.Config.get([:markup, :allow_fonts]) do
|
||||
Meta.allow_tag_with_these_attributes(:font, ["face"])
|
||||
end
|
||||
|
||||
Meta.strip_everything_not_covered()
|
||||
end
|
27
priv/scrubbers/links_only.ex
Normal file
27
priv/scrubbers/links_only.ex
Normal file
|
@ -0,0 +1,27 @@
|
|||
defmodule Pleroma.HTML.Scrubber.LinksOnly do
|
||||
@moduledoc """
|
||||
An HTML scrubbing policy which limits to links only.
|
||||
"""
|
||||
|
||||
@valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
|
||||
|
||||
require FastSanitize.Sanitizer.Meta
|
||||
alias FastSanitize.Sanitizer.Meta
|
||||
|
||||
Meta.strip_comments()
|
||||
|
||||
# links
|
||||
Meta.allow_tag_with_uri_attributes(:a, ["href"], @valid_schemes)
|
||||
|
||||
Meta.allow_tag_with_this_attribute_values(:a, "rel", [
|
||||
"tag",
|
||||
"nofollow",
|
||||
"noopener",
|
||||
"noreferrer",
|
||||
"me",
|
||||
"ugc"
|
||||
])
|
||||
|
||||
Meta.allow_tag_with_these_attributes(:a, ["name", "title"])
|
||||
Meta.strip_everything_not_covered()
|
||||
end
|
32
priv/scrubbers/media_proxy.ex
Normal file
32
priv/scrubbers/media_proxy.ex
Normal file
|
@ -0,0 +1,32 @@
|
|||
defmodule Pleroma.HTML.Transform.MediaProxy do
|
||||
@moduledoc "Transforms inline image URIs to use MediaProxy."
|
||||
|
||||
alias Pleroma.Web.MediaProxy
|
||||
|
||||
def before_scrub(html), do: html
|
||||
|
||||
def scrub_attribute(:img, {"src", "http" <> target}) do
|
||||
media_url =
|
||||
("http" <> target)
|
||||
|> MediaProxy.url()
|
||||
|
||||
{"src", media_url}
|
||||
end
|
||||
|
||||
def scrub_attribute(_tag, attribute), do: attribute
|
||||
|
||||
def scrub({:img, attributes, children}) do
|
||||
attributes =
|
||||
attributes
|
||||
|> Enum.map(fn attr -> scrub_attribute(:img, attr) end)
|
||||
|> Enum.reject(&is_nil(&1))
|
||||
|
||||
{:img, attributes, children}
|
||||
end
|
||||
|
||||
def scrub({:comment, _text, _children}), do: ""
|
||||
|
||||
def scrub({tag, attributes, children}), do: {tag, attributes, children}
|
||||
def scrub({_tag, children}), do: children
|
||||
def scrub(text), do: text
|
||||
end
|
57
priv/scrubbers/twitter_text.ex
Normal file
57
priv/scrubbers/twitter_text.ex
Normal file
|
@ -0,0 +1,57 @@
|
|||
defmodule Pleroma.HTML.Scrubber.TwitterText do
|
||||
@moduledoc """
|
||||
An HTML scrubbing policy which limits to twitter-style text. Only
|
||||
paragraphs, breaks and links are allowed through the filter.
|
||||
"""
|
||||
|
||||
@valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
|
||||
|
||||
require FastSanitize.Sanitizer.Meta
|
||||
alias FastSanitize.Sanitizer.Meta
|
||||
|
||||
Meta.strip_comments()
|
||||
|
||||
# links
|
||||
Meta.allow_tag_with_uri_attributes(:a, ["href", "data-user", "data-tag"], @valid_schemes)
|
||||
|
||||
Meta.allow_tag_with_this_attribute_values(:a, "class", [
|
||||
"hashtag",
|
||||
"u-url",
|
||||
"mention",
|
||||
"u-url mention",
|
||||
"mention u-url"
|
||||
])
|
||||
|
||||
Meta.allow_tag_with_this_attribute_values(:a, "rel", [
|
||||
"tag",
|
||||
"nofollow",
|
||||
"noopener",
|
||||
"noreferrer"
|
||||
])
|
||||
|
||||
Meta.allow_tag_with_these_attributes(:a, ["name", "title"])
|
||||
|
||||
# paragraphs and linebreaks
|
||||
Meta.allow_tag_with_these_attributes(:br, [])
|
||||
Meta.allow_tag_with_these_attributes(:p, [])
|
||||
|
||||
# microformats
|
||||
Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card"])
|
||||
Meta.allow_tag_with_these_attributes(:span, [])
|
||||
|
||||
# allow inline images for custom emoji
|
||||
if Pleroma.Config.get([:markup, :allow_inline_images]) do
|
||||
# restrict img tags to http/https only, because of MediaProxy.
|
||||
Meta.allow_tag_with_uri_attributes(:img, ["src"], ["http", "https"])
|
||||
|
||||
Meta.allow_tag_with_these_attributes(:img, [
|
||||
"width",
|
||||
"height",
|
||||
"class",
|
||||
"title",
|
||||
"alt"
|
||||
])
|
||||
end
|
||||
|
||||
Meta.strip_everything_not_covered()
|
||||
end
|
|
@ -252,7 +252,7 @@ test "when the user blocks a recipient, the existing conversations with them are
|
|||
|
||||
assert User.get_cached_by_id(blocker.id).unread_conversation_count == 4
|
||||
|
||||
{:ok, blocker} = User.block(blocker, blocked)
|
||||
{:ok, _user_relationship} = User.block(blocker, blocked)
|
||||
|
||||
# The conversations with the blocked user are marked as read
|
||||
assert [%{read: true}, %{read: true}, %{read: true}, %{read: false}] =
|
||||
|
@ -274,7 +274,7 @@ test "the new conversation with the blocked user is not marked as unread " do
|
|||
blocked = insert(:user)
|
||||
third_user = insert(:user)
|
||||
|
||||
{:ok, blocker} = User.block(blocker, blocked)
|
||||
{:ok, _user_relationship} = User.block(blocker, blocked)
|
||||
|
||||
# When the blocked user is the author
|
||||
{:ok, _direct1} =
|
||||
|
@ -311,7 +311,7 @@ test "the conversation with the blocked user is not marked as unread on a reply"
|
|||
"visibility" => "direct"
|
||||
})
|
||||
|
||||
{:ok, blocker} = User.block(blocker, blocked)
|
||||
{:ok, _user_relationship} = User.block(blocker, blocked)
|
||||
assert [%{read: true}] = Participation.for_user(blocker)
|
||||
assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@ test "it creates a notification for user if the user blocks the activity author"
|
|||
activity = insert(:note_activity)
|
||||
author = User.get_cached_by_ap_id(activity.data["actor"])
|
||||
user = insert(:user)
|
||||
{:ok, user} = User.block(user, author)
|
||||
{:ok, _user_relationship} = User.block(user, author)
|
||||
|
||||
assert Notification.create_notification(activity, user)
|
||||
end
|
||||
|
@ -112,7 +112,7 @@ test "notification created if user is muted without notifications" do
|
|||
muter = insert(:user)
|
||||
muted = insert(:user)
|
||||
|
||||
{:ok, muter} = User.mute(muter, muted, false)
|
||||
{:ok, _user_relationships} = User.mute(muter, muted, false)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(muted, %{"status" => "Hi @#{muter.nickname}"})
|
||||
|
||||
|
@ -136,7 +136,10 @@ test "it creates a notification for an activity from a muted thread" do
|
|||
|
||||
test "it disables notifications from followers" do
|
||||
follower = insert(:user)
|
||||
followed = insert(:user, notification_settings: %{"followers" => false})
|
||||
|
||||
followed =
|
||||
insert(:user, notification_settings: %Pleroma.User.NotificationSetting{followers: false})
|
||||
|
||||
User.follow(follower, followed)
|
||||
{:ok, activity} = CommonAPI.post(follower, %{"status" => "hey @#{followed.nickname}"})
|
||||
refute Notification.create_notification(activity, followed)
|
||||
|
@ -144,13 +147,20 @@ test "it disables notifications from followers" do
|
|||
|
||||
test "it disables notifications from non-followers" do
|
||||
follower = insert(:user)
|
||||
followed = insert(:user, notification_settings: %{"non_followers" => false})
|
||||
|
||||
followed =
|
||||
insert(:user,
|
||||
notification_settings: %Pleroma.User.NotificationSetting{non_followers: false}
|
||||
)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(follower, %{"status" => "hey @#{followed.nickname}"})
|
||||
refute Notification.create_notification(activity, followed)
|
||||
end
|
||||
|
||||
test "it disables notifications from people the user follows" do
|
||||
follower = insert(:user, notification_settings: %{"follows" => false})
|
||||
follower =
|
||||
insert(:user, notification_settings: %Pleroma.User.NotificationSetting{follows: false})
|
||||
|
||||
followed = insert(:user)
|
||||
User.follow(follower, followed)
|
||||
follower = Repo.get(User, follower.id)
|
||||
|
@ -159,7 +169,9 @@ test "it disables notifications from people the user follows" do
|
|||
end
|
||||
|
||||
test "it disables notifications from people the user does not follow" do
|
||||
follower = insert(:user, notification_settings: %{"non_follows" => false})
|
||||
follower =
|
||||
insert(:user, notification_settings: %Pleroma.User.NotificationSetting{non_follows: false})
|
||||
|
||||
followed = insert(:user)
|
||||
{:ok, activity} = CommonAPI.post(followed, %{"status" => "hey @#{follower.nickname}"})
|
||||
refute Notification.create_notification(activity, follower)
|
||||
|
@ -643,13 +655,7 @@ test "move activity generates a notification" do
|
|||
Pleroma.Web.ActivityPub.ActivityPub.move(old_user, new_user)
|
||||
ObanHelpers.perform_all()
|
||||
|
||||
assert [
|
||||
%{
|
||||
activity: %{
|
||||
data: %{"type" => "Move", "actor" => ^old_ap_id, "target" => ^new_ap_id}
|
||||
}
|
||||
}
|
||||
] = Notification.for_user(follower)
|
||||
assert [] = Notification.for_user(follower)
|
||||
|
||||
assert [
|
||||
%{
|
||||
|
@ -657,7 +663,17 @@ test "move activity generates a notification" do
|
|||
data: %{"type" => "Move", "actor" => ^old_ap_id, "target" => ^new_ap_id}
|
||||
}
|
||||
}
|
||||
] = Notification.for_user(other_follower)
|
||||
] = Notification.for_user(follower, %{with_move: true})
|
||||
|
||||
assert [] = Notification.for_user(other_follower)
|
||||
|
||||
assert [
|
||||
%{
|
||||
activity: %{
|
||||
data: %{"type" => "Move", "actor" => ^old_ap_id, "target" => ^new_ap_id}
|
||||
}
|
||||
}
|
||||
] = Notification.for_user(other_follower, %{with_move: true})
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -665,7 +681,7 @@ test "move activity generates a notification" do
|
|||
test "it returns notifications for muted user without notifications" do
|
||||
user = insert(:user)
|
||||
muted = insert(:user)
|
||||
{:ok, user} = User.mute(user, muted, false)
|
||||
{:ok, _user_relationships} = User.mute(user, muted, false)
|
||||
|
||||
{:ok, _activity} = CommonAPI.post(muted, %{"status" => "hey @#{user.nickname}"})
|
||||
|
||||
|
@ -675,7 +691,7 @@ test "it returns notifications for muted user without notifications" do
|
|||
test "it doesn't return notifications for muted user with notifications" do
|
||||
user = insert(:user)
|
||||
muted = insert(:user)
|
||||
{:ok, user} = User.mute(user, muted)
|
||||
{:ok, _user_relationships} = User.mute(user, muted)
|
||||
|
||||
{:ok, _activity} = CommonAPI.post(muted, %{"status" => "hey @#{user.nickname}"})
|
||||
|
||||
|
@ -685,7 +701,7 @@ test "it doesn't return notifications for muted user with notifications" do
|
|||
test "it doesn't return notifications for blocked user" do
|
||||
user = insert(:user)
|
||||
blocked = insert(:user)
|
||||
{:ok, user} = User.block(user, blocked)
|
||||
{:ok, _user_relationship} = User.block(user, blocked)
|
||||
|
||||
{:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"})
|
||||
|
||||
|
@ -715,7 +731,7 @@ test "it doesn't return notifications for muted thread" do
|
|||
test "it returns notifications from a muted user when with_muted is set" do
|
||||
user = insert(:user)
|
||||
muted = insert(:user)
|
||||
{:ok, user} = User.mute(user, muted)
|
||||
{:ok, _user_relationships} = User.mute(user, muted)
|
||||
|
||||
{:ok, _activity} = CommonAPI.post(muted, %{"status" => "hey @#{user.nickname}"})
|
||||
|
||||
|
@ -725,7 +741,7 @@ test "it returns notifications from a muted user when with_muted is set" do
|
|||
test "it doesn't return notifications from a blocked user when with_muted is set" do
|
||||
user = insert(:user)
|
||||
blocked = insert(:user)
|
||||
{:ok, user} = User.block(user, blocked)
|
||||
{:ok, _user_relationship} = User.block(user, blocked)
|
||||
|
||||
{:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"})
|
||||
|
||||
|
|
|
@ -10,7 +10,8 @@ def build(data \\ %{}) do
|
|||
password_hash: Comeonin.Pbkdf2.hashpwsalt("test"),
|
||||
bio: "A tester.",
|
||||
ap_id: "some id",
|
||||
last_digest_emailed_at: NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
|
||||
last_digest_emailed_at: NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second),
|
||||
notification_settings: %Pleroma.User.NotificationSetting{}
|
||||
}
|
||||
|
||||
Map.merge(user, data)
|
||||
|
|
|
@ -23,6 +23,7 @@ defmodule Pleroma.Web.ChannelCase do
|
|||
quote do
|
||||
# Import conveniences for testing with channels
|
||||
use Phoenix.ChannelTest
|
||||
use Pleroma.Tests.Helpers
|
||||
|
||||
# The default endpoint for testing
|
||||
@endpoint Pleroma.Web.Endpoint
|
||||
|
|
|
@ -31,7 +31,8 @@ def user_factory do
|
|||
nickname: sequence(:nickname, &"nick#{&1}"),
|
||||
password_hash: Comeonin.Pbkdf2.hashpwsalt("test"),
|
||||
bio: sequence(:bio, &"Tester Number #{&1}"),
|
||||
last_digest_emailed_at: NaiveDateTime.utc_now()
|
||||
last_digest_emailed_at: NaiveDateTime.utc_now(),
|
||||
notification_settings: %Pleroma.User.NotificationSetting{}
|
||||
}
|
||||
|
||||
%{
|
||||
|
@ -42,6 +43,18 @@ def user_factory do
|
|||
}
|
||||
end
|
||||
|
||||
def user_relationship_factory(attrs \\ %{}) do
|
||||
source = attrs[:source] || insert(:user)
|
||||
target = attrs[:target] || insert(:user)
|
||||
relationship_type = attrs[:relationship_type] || :block
|
||||
|
||||
%Pleroma.UserRelationship{
|
||||
source_id: source.id,
|
||||
target_id: target.id,
|
||||
relationship_type: relationship_type
|
||||
}
|
||||
end
|
||||
|
||||
def note_factory(attrs \\ %{}) do
|
||||
text = sequence(:text, &"This is :moominmamma: note #{&1}")
|
||||
|
||||
|
|
|
@ -75,6 +75,23 @@ def render_json(view, template, assigns) do
|
|||
|> Poison.decode!()
|
||||
end
|
||||
|
||||
def stringify_keys(nil), do: nil
|
||||
|
||||
def stringify_keys(key) when key in [true, false], do: key
|
||||
def stringify_keys(key) when is_atom(key), do: Atom.to_string(key)
|
||||
|
||||
def stringify_keys(map) when is_map(map) do
|
||||
map
|
||||
|> Enum.map(fn {k, v} -> {stringify_keys(k), stringify_keys(v)} end)
|
||||
|> Enum.into(%{})
|
||||
end
|
||||
|
||||
def stringify_keys([head | rest] = list) when is_list(list) do
|
||||
[stringify_keys(head) | stringify_keys(rest)]
|
||||
end
|
||||
|
||||
def stringify_keys(key), do: key
|
||||
|
||||
defmacro guards_config(config_path) do
|
||||
quote do
|
||||
initial_setting = Pleroma.Config.get(config_path)
|
||||
|
|
|
@ -63,4 +63,84 @@ test "settings are migrated to file and deleted from db", %{temp_file: temp_file
|
|||
assert file =~ "config :pleroma, :setting_first,"
|
||||
assert file =~ "config :pleroma, :setting_second,"
|
||||
end
|
||||
|
||||
test "load a settings with large values and pass to file", %{temp_file: temp_file} do
|
||||
Config.create(%{
|
||||
group: "pleroma",
|
||||
key: ":instance",
|
||||
value: [
|
||||
name: "Pleroma",
|
||||
email: "example@example.com",
|
||||
notify_email: "noreply@example.com",
|
||||
description: "A Pleroma instance, an alternative fediverse server",
|
||||
limit: 5_000,
|
||||
chat_limit: 5_000,
|
||||
remote_limit: 100_000,
|
||||
upload_limit: 16_000_000,
|
||||
avatar_upload_limit: 2_000_000,
|
||||
background_upload_limit: 4_000_000,
|
||||
banner_upload_limit: 4_000_000,
|
||||
poll_limits: %{
|
||||
max_options: 20,
|
||||
max_option_chars: 200,
|
||||
min_expiration: 0,
|
||||
max_expiration: 365 * 24 * 60 * 60
|
||||
},
|
||||
registrations_open: true,
|
||||
federating: true,
|
||||
federation_incoming_replies_max_depth: 100,
|
||||
federation_reachability_timeout_days: 7,
|
||||
federation_publisher_modules: [Pleroma.Web.ActivityPub.Publisher],
|
||||
allow_relay: true,
|
||||
rewrite_policy: Pleroma.Web.ActivityPub.MRF.NoOpPolicy,
|
||||
public: true,
|
||||
quarantined_instances: [],
|
||||
managed_config: true,
|
||||
static_dir: "instance/static/",
|
||||
allowed_post_formats: ["text/plain", "text/html", "text/markdown", "text/bbcode"],
|
||||
mrf_transparency: true,
|
||||
mrf_transparency_exclusions: [],
|
||||
autofollowed_nicknames: [],
|
||||
max_pinned_statuses: 1,
|
||||
no_attachment_links: true,
|
||||
welcome_user_nickname: nil,
|
||||
welcome_message: nil,
|
||||
max_report_comment_size: 1000,
|
||||
safe_dm_mentions: false,
|
||||
healthcheck: false,
|
||||
remote_post_retention_days: 90,
|
||||
skip_thread_containment: true,
|
||||
limit_to_local_content: :unauthenticated,
|
||||
dynamic_configuration: false,
|
||||
user_bio_length: 5000,
|
||||
user_name_length: 100,
|
||||
max_account_fields: 10,
|
||||
max_remote_account_fields: 20,
|
||||
account_field_name_length: 512,
|
||||
account_field_value_length: 2048,
|
||||
external_user_synchronization: true,
|
||||
extended_nickname_format: true,
|
||||
multi_factor_authentication: [
|
||||
totp: [
|
||||
# digits 6 or 8
|
||||
digits: 6,
|
||||
period: 30
|
||||
],
|
||||
backup_codes: [
|
||||
number: 2,
|
||||
length: 6
|
||||
]
|
||||
]
|
||||
]
|
||||
})
|
||||
|
||||
Mix.Tasks.Pleroma.Config.run(["migrate_from_db", "temp", "true"])
|
||||
|
||||
assert Repo.all(Config) == []
|
||||
assert File.exists?(temp_file)
|
||||
{:ok, file} = File.read(temp_file)
|
||||
|
||||
assert file ==
|
||||
"use Mix.Config\n\nconfig :pleroma, :instance,\n name: \"Pleroma\",\n email: \"example@example.com\",\n notify_email: \"noreply@example.com\",\n description: \"A Pleroma instance, an alternative fediverse server\",\n limit: 5000,\n chat_limit: 5000,\n remote_limit: 100_000,\n upload_limit: 16_000_000,\n avatar_upload_limit: 2_000_000,\n background_upload_limit: 4_000_000,\n banner_upload_limit: 4_000_000,\n poll_limits: %{\n max_expiration: 31_536_000,\n max_option_chars: 200,\n max_options: 20,\n min_expiration: 0\n },\n registrations_open: true,\n federating: true,\n federation_incoming_replies_max_depth: 100,\n federation_reachability_timeout_days: 7,\n federation_publisher_modules: [Pleroma.Web.ActivityPub.Publisher],\n allow_relay: true,\n rewrite_policy: Pleroma.Web.ActivityPub.MRF.NoOpPolicy,\n public: true,\n quarantined_instances: [],\n managed_config: true,\n static_dir: \"instance/static/\",\n allowed_post_formats: [\"text/plain\", \"text/html\", \"text/markdown\", \"text/bbcode\"],\n mrf_transparency: true,\n mrf_transparency_exclusions: [],\n autofollowed_nicknames: [],\n max_pinned_statuses: 1,\n no_attachment_links: true,\n welcome_user_nickname: nil,\n welcome_message: nil,\n max_report_comment_size: 1000,\n safe_dm_mentions: false,\n healthcheck: false,\n remote_post_retention_days: 90,\n skip_thread_containment: true,\n limit_to_local_content: :unauthenticated,\n dynamic_configuration: false,\n user_bio_length: 5000,\n user_name_length: 100,\n max_account_fields: 10,\n max_remote_account_fields: 20,\n account_field_name_length: 512,\n account_field_value_length: 2048,\n external_user_synchronization: true,\n extended_nickname_format: true,\n multi_factor_authentication: [\n totp: [digits: 6, period: 30],\n backup_codes: [number: 2, length: 6]\n ]\n"
|
||||
end
|
||||
end
|
||||
|
|
21
test/user/notification_setting_test.exs
Normal file
21
test/user/notification_setting_test.exs
Normal file
|
@ -0,0 +1,21 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.User.NotificationSettingTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.User.NotificationSetting
|
||||
|
||||
describe "changeset/2" do
|
||||
test "sets valid privacy option" do
|
||||
changeset =
|
||||
NotificationSetting.changeset(
|
||||
%NotificationSetting{},
|
||||
%{"privacy_option" => true}
|
||||
)
|
||||
|
||||
assert %Ecto.Changeset{valid?: true} = changeset
|
||||
end
|
||||
end
|
||||
end
|
130
test/user_relationship_test.exs
Normal file
130
test/user_relationship_test.exs
Normal file
|
@ -0,0 +1,130 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.UserRelationshipTest do
|
||||
alias Pleroma.UserRelationship
|
||||
|
||||
use Pleroma.DataCase
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
describe "*_exists?/2" do
|
||||
setup do
|
||||
{:ok, users: insert_list(2, :user)}
|
||||
end
|
||||
|
||||
test "returns false if record doesn't exist", %{users: [user1, user2]} do
|
||||
refute UserRelationship.block_exists?(user1, user2)
|
||||
refute UserRelationship.mute_exists?(user1, user2)
|
||||
refute UserRelationship.notification_mute_exists?(user1, user2)
|
||||
refute UserRelationship.reblog_mute_exists?(user1, user2)
|
||||
refute UserRelationship.inverse_subscription_exists?(user1, user2)
|
||||
end
|
||||
|
||||
test "returns true if record exists", %{users: [user1, user2]} do
|
||||
for relationship_type <- [
|
||||
:block,
|
||||
:mute,
|
||||
:notification_mute,
|
||||
:reblog_mute,
|
||||
:inverse_subscription
|
||||
] do
|
||||
insert(:user_relationship,
|
||||
source: user1,
|
||||
target: user2,
|
||||
relationship_type: relationship_type
|
||||
)
|
||||
end
|
||||
|
||||
assert UserRelationship.block_exists?(user1, user2)
|
||||
assert UserRelationship.mute_exists?(user1, user2)
|
||||
assert UserRelationship.notification_mute_exists?(user1, user2)
|
||||
assert UserRelationship.reblog_mute_exists?(user1, user2)
|
||||
assert UserRelationship.inverse_subscription_exists?(user1, user2)
|
||||
end
|
||||
end
|
||||
|
||||
describe "create_*/2" do
|
||||
setup do
|
||||
{:ok, users: insert_list(2, :user)}
|
||||
end
|
||||
|
||||
test "creates user relationship record if it doesn't exist", %{users: [user1, user2]} do
|
||||
for relationship_type <- [
|
||||
:block,
|
||||
:mute,
|
||||
:notification_mute,
|
||||
:reblog_mute,
|
||||
:inverse_subscription
|
||||
] do
|
||||
insert(:user_relationship,
|
||||
source: user1,
|
||||
target: user2,
|
||||
relationship_type: relationship_type
|
||||
)
|
||||
end
|
||||
|
||||
UserRelationship.create_block(user1, user2)
|
||||
UserRelationship.create_mute(user1, user2)
|
||||
UserRelationship.create_notification_mute(user1, user2)
|
||||
UserRelationship.create_reblog_mute(user1, user2)
|
||||
UserRelationship.create_inverse_subscription(user1, user2)
|
||||
|
||||
assert UserRelationship.block_exists?(user1, user2)
|
||||
assert UserRelationship.mute_exists?(user1, user2)
|
||||
assert UserRelationship.notification_mute_exists?(user1, user2)
|
||||
assert UserRelationship.reblog_mute_exists?(user1, user2)
|
||||
assert UserRelationship.inverse_subscription_exists?(user1, user2)
|
||||
end
|
||||
|
||||
test "if record already exists, returns it", %{users: [user1, user2]} do
|
||||
user_block = UserRelationship.create_block(user1, user2)
|
||||
assert user_block == UserRelationship.create_block(user1, user2)
|
||||
end
|
||||
end
|
||||
|
||||
describe "delete_*/2" do
|
||||
setup do
|
||||
{:ok, users: insert_list(2, :user)}
|
||||
end
|
||||
|
||||
test "deletes user relationship record if it exists", %{users: [user1, user2]} do
|
||||
for relationship_type <- [
|
||||
:block,
|
||||
:mute,
|
||||
:notification_mute,
|
||||
:reblog_mute,
|
||||
:inverse_subscription
|
||||
] do
|
||||
insert(:user_relationship,
|
||||
source: user1,
|
||||
target: user2,
|
||||
relationship_type: relationship_type
|
||||
)
|
||||
end
|
||||
|
||||
assert {:ok, %UserRelationship{}} = UserRelationship.delete_block(user1, user2)
|
||||
assert {:ok, %UserRelationship{}} = UserRelationship.delete_mute(user1, user2)
|
||||
assert {:ok, %UserRelationship{}} = UserRelationship.delete_notification_mute(user1, user2)
|
||||
assert {:ok, %UserRelationship{}} = UserRelationship.delete_reblog_mute(user1, user2)
|
||||
|
||||
assert {:ok, %UserRelationship{}} =
|
||||
UserRelationship.delete_inverse_subscription(user1, user2)
|
||||
|
||||
refute UserRelationship.block_exists?(user1, user2)
|
||||
refute UserRelationship.mute_exists?(user1, user2)
|
||||
refute UserRelationship.notification_mute_exists?(user1, user2)
|
||||
refute UserRelationship.reblog_mute_exists?(user1, user2)
|
||||
refute UserRelationship.inverse_subscription_exists?(user1, user2)
|
||||
end
|
||||
|
||||
test "if record does not exist, returns {:ok, nil}", %{users: [user1, user2]} do
|
||||
assert {:ok, nil} = UserRelationship.delete_block(user1, user2)
|
||||
assert {:ok, nil} = UserRelationship.delete_mute(user1, user2)
|
||||
assert {:ok, nil} = UserRelationship.delete_notification_mute(user1, user2)
|
||||
assert {:ok, nil} = UserRelationship.delete_reblog_mute(user1, user2)
|
||||
assert {:ok, nil} = UserRelationship.delete_inverse_subscription(user1, user2)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -174,6 +174,7 @@ test "works with URIs" do
|
|||
|> Map.put(:search_rank, nil)
|
||||
|> Map.put(:search_type, nil)
|
||||
|> Map.put(:last_digest_emailed_at, nil)
|
||||
|> Map.put(:notification_settings, nil)
|
||||
|
||||
assert user == expected
|
||||
end
|
||||
|
|
|
@ -44,6 +44,56 @@ test "returns invisible actor" do
|
|||
end
|
||||
end
|
||||
|
||||
describe "AP ID user relationships" do
|
||||
setup do
|
||||
{:ok, user: insert(:user)}
|
||||
end
|
||||
|
||||
test "outgoing_relations_ap_ids/1", %{user: user} do
|
||||
rel_types = [:block, :mute, :notification_mute, :reblog_mute, :inverse_subscription]
|
||||
|
||||
ap_ids_by_rel =
|
||||
Enum.into(
|
||||
rel_types,
|
||||
%{},
|
||||
fn rel_type ->
|
||||
rel_records =
|
||||
insert_list(2, :user_relationship, %{source: user, relationship_type: rel_type})
|
||||
|
||||
ap_ids = Enum.map(rel_records, fn rr -> Repo.preload(rr, :target).target.ap_id end)
|
||||
{rel_type, Enum.sort(ap_ids)}
|
||||
end
|
||||
)
|
||||
|
||||
assert ap_ids_by_rel[:block] == Enum.sort(User.blocked_users_ap_ids(user))
|
||||
assert ap_ids_by_rel[:block] == Enum.sort(Enum.map(User.blocked_users(user), & &1.ap_id))
|
||||
|
||||
assert ap_ids_by_rel[:mute] == Enum.sort(User.muted_users_ap_ids(user))
|
||||
assert ap_ids_by_rel[:mute] == Enum.sort(Enum.map(User.muted_users(user), & &1.ap_id))
|
||||
|
||||
assert ap_ids_by_rel[:notification_mute] ==
|
||||
Enum.sort(User.notification_muted_users_ap_ids(user))
|
||||
|
||||
assert ap_ids_by_rel[:notification_mute] ==
|
||||
Enum.sort(Enum.map(User.notification_muted_users(user), & &1.ap_id))
|
||||
|
||||
assert ap_ids_by_rel[:reblog_mute] == Enum.sort(User.reblog_muted_users_ap_ids(user))
|
||||
|
||||
assert ap_ids_by_rel[:reblog_mute] ==
|
||||
Enum.sort(Enum.map(User.reblog_muted_users(user), & &1.ap_id))
|
||||
|
||||
assert ap_ids_by_rel[:inverse_subscription] == Enum.sort(User.subscriber_users_ap_ids(user))
|
||||
|
||||
assert ap_ids_by_rel[:inverse_subscription] ==
|
||||
Enum.sort(Enum.map(User.subscriber_users(user), & &1.ap_id))
|
||||
|
||||
outgoing_relations_ap_ids = User.outgoing_relations_ap_ids(user, rel_types)
|
||||
|
||||
assert ap_ids_by_rel ==
|
||||
Enum.into(outgoing_relations_ap_ids, %{}, fn {k, v} -> {k, Enum.sort(v)} end)
|
||||
end
|
||||
end
|
||||
|
||||
describe "when tags are nil" do
|
||||
test "tagging a user" do
|
||||
user = insert(:user, %{tags: nil})
|
||||
|
@ -119,7 +169,7 @@ test "clears follow requests when requester is blocked" do
|
|||
CommonAPI.follow(follower, followed)
|
||||
assert [_activity] = User.get_follow_requests(followed)
|
||||
|
||||
{:ok, _follower} = User.block(followed, follower)
|
||||
{:ok, _user_relationship} = User.block(followed, follower)
|
||||
assert [] = User.get_follow_requests(followed)
|
||||
end
|
||||
|
||||
|
@ -132,8 +182,8 @@ test "follow_all follows mutliple users" do
|
|||
not_followed = insert(:user)
|
||||
reverse_blocked = insert(:user)
|
||||
|
||||
{:ok, user} = User.block(user, blocked)
|
||||
{:ok, reverse_blocked} = User.block(reverse_blocked, user)
|
||||
{:ok, _user_relationship} = User.block(user, blocked)
|
||||
{:ok, _user_relationship} = User.block(reverse_blocked, user)
|
||||
|
||||
{:ok, user} = User.follow(user, followed_zero)
|
||||
|
||||
|
@ -186,7 +236,7 @@ test "can't follow a user who blocked us" do
|
|||
blocker = insert(:user)
|
||||
blockee = insert(:user)
|
||||
|
||||
{:ok, blocker} = User.block(blocker, blockee)
|
||||
{:ok, _user_relationship} = User.block(blocker, blockee)
|
||||
|
||||
{:error, _} = User.follow(blockee, blocker)
|
||||
end
|
||||
|
@ -195,7 +245,7 @@ test "can't subscribe to a user who blocked us" do
|
|||
blocker = insert(:user)
|
||||
blocked = insert(:user)
|
||||
|
||||
{:ok, blocker} = User.block(blocker, blocked)
|
||||
{:ok, _user_relationship} = User.block(blocker, blocked)
|
||||
|
||||
{:error, _} = User.subscribe(blocked, blocker)
|
||||
end
|
||||
|
@ -678,7 +728,7 @@ test "it mutes people" do
|
|||
refute User.mutes?(user, muted_user)
|
||||
refute User.muted_notifications?(user, muted_user)
|
||||
|
||||
{:ok, user} = User.mute(user, muted_user)
|
||||
{:ok, _user_relationships} = User.mute(user, muted_user)
|
||||
|
||||
assert User.mutes?(user, muted_user)
|
||||
assert User.muted_notifications?(user, muted_user)
|
||||
|
@ -688,8 +738,8 @@ test "it unmutes users" do
|
|||
user = insert(:user)
|
||||
muted_user = insert(:user)
|
||||
|
||||
{:ok, user} = User.mute(user, muted_user)
|
||||
{:ok, user} = User.unmute(user, muted_user)
|
||||
{:ok, _user_relationships} = User.mute(user, muted_user)
|
||||
{:ok, _user_mute} = User.unmute(user, muted_user)
|
||||
|
||||
refute User.mutes?(user, muted_user)
|
||||
refute User.muted_notifications?(user, muted_user)
|
||||
|
@ -702,7 +752,7 @@ test "it mutes user without notifications" do
|
|||
refute User.mutes?(user, muted_user)
|
||||
refute User.muted_notifications?(user, muted_user)
|
||||
|
||||
{:ok, user} = User.mute(user, muted_user, false)
|
||||
{:ok, _user_relationships} = User.mute(user, muted_user, false)
|
||||
|
||||
assert User.mutes?(user, muted_user)
|
||||
refute User.muted_notifications?(user, muted_user)
|
||||
|
@ -716,7 +766,7 @@ test "it blocks people" do
|
|||
|
||||
refute User.blocks?(user, blocked_user)
|
||||
|
||||
{:ok, user} = User.block(user, blocked_user)
|
||||
{:ok, _user_relationship} = User.block(user, blocked_user)
|
||||
|
||||
assert User.blocks?(user, blocked_user)
|
||||
end
|
||||
|
@ -725,8 +775,8 @@ test "it unblocks users" do
|
|||
user = insert(:user)
|
||||
blocked_user = insert(:user)
|
||||
|
||||
{:ok, user} = User.block(user, blocked_user)
|
||||
{:ok, user} = User.unblock(user, blocked_user)
|
||||
{:ok, _user_relationship} = User.block(user, blocked_user)
|
||||
{:ok, _user_block} = User.unblock(user, blocked_user)
|
||||
|
||||
refute User.blocks?(user, blocked_user)
|
||||
end
|
||||
|
@ -741,7 +791,7 @@ test "blocks tear down cyclical follow relationships" do
|
|||
assert User.following?(blocker, blocked)
|
||||
assert User.following?(blocked, blocker)
|
||||
|
||||
{:ok, blocker} = User.block(blocker, blocked)
|
||||
{:ok, _user_relationship} = User.block(blocker, blocked)
|
||||
blocked = User.get_cached_by_id(blocked.id)
|
||||
|
||||
assert User.blocks?(blocker, blocked)
|
||||
|
@ -759,7 +809,7 @@ test "blocks tear down blocker->blocked follow relationships" do
|
|||
assert User.following?(blocker, blocked)
|
||||
refute User.following?(blocked, blocker)
|
||||
|
||||
{:ok, blocker} = User.block(blocker, blocked)
|
||||
{:ok, _user_relationship} = User.block(blocker, blocked)
|
||||
blocked = User.get_cached_by_id(blocked.id)
|
||||
|
||||
assert User.blocks?(blocker, blocked)
|
||||
|
@ -777,7 +827,7 @@ test "blocks tear down blocked->blocker follow relationships" do
|
|||
refute User.following?(blocker, blocked)
|
||||
assert User.following?(blocked, blocker)
|
||||
|
||||
{:ok, blocker} = User.block(blocker, blocked)
|
||||
{:ok, _user_relationship} = User.block(blocker, blocked)
|
||||
blocked = User.get_cached_by_id(blocked.id)
|
||||
|
||||
assert User.blocks?(blocker, blocked)
|
||||
|
@ -790,12 +840,12 @@ test "blocks tear down blocked->blocker subscription relationships" do
|
|||
blocker = insert(:user)
|
||||
blocked = insert(:user)
|
||||
|
||||
{:ok, blocker} = User.subscribe(blocked, blocker)
|
||||
{:ok, _subscription} = User.subscribe(blocked, blocker)
|
||||
|
||||
assert User.subscribed_to?(blocked, blocker)
|
||||
refute User.subscribed_to?(blocker, blocked)
|
||||
|
||||
{:ok, blocker} = User.block(blocker, blocked)
|
||||
{:ok, _user_relationship} = User.block(blocker, blocked)
|
||||
|
||||
assert User.blocks?(blocker, blocked)
|
||||
refute User.subscribed_to?(blocker, blocked)
|
||||
|
@ -1324,7 +1374,8 @@ test "follower count is updated when a follower is blocked" do
|
|||
{:ok, _follower2} = User.follow(follower2, user)
|
||||
{:ok, _follower3} = User.follow(follower3, user)
|
||||
|
||||
{:ok, user} = User.block(user, follower)
|
||||
{:ok, _user_relationship} = User.block(user, follower)
|
||||
user = refresh_record(user)
|
||||
|
||||
assert user.follower_count == 2
|
||||
end
|
||||
|
|
|
@ -298,7 +298,7 @@ test "cached purged after activity deletion", %{conn: conn} do
|
|||
assert json_response(conn1, :ok)
|
||||
assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
|
||||
|
||||
Activity.delete_by_ap_id(activity.object.data["id"])
|
||||
Activity.delete_all_by_object_ap_id(activity.object.data["id"])
|
||||
|
||||
conn2 =
|
||||
conn
|
||||
|
|
|
@ -487,7 +487,7 @@ test "retrieves activities that have a given context" do
|
|||
activity_five = insert(:note_activity)
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, user} = User.block(user, %{ap_id: activity_five.data["actor"]})
|
||||
{:ok, _user_relationship} = User.block(user, %{ap_id: activity_five.data["actor"]})
|
||||
|
||||
activities = ActivityPub.fetch_activities_for_context("2hu", %{"blocking_user" => user})
|
||||
assert activities == [activity_two, activity]
|
||||
|
@ -500,7 +500,7 @@ test "doesn't return blocked activities" do
|
|||
activity_three = insert(:note_activity)
|
||||
user = insert(:user)
|
||||
booster = insert(:user)
|
||||
{:ok, user} = User.block(user, %{ap_id: activity_one.data["actor"]})
|
||||
{:ok, _user_relationship} = User.block(user, %{ap_id: activity_one.data["actor"]})
|
||||
|
||||
activities =
|
||||
ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
|
||||
|
@ -509,7 +509,7 @@ test "doesn't return blocked activities" do
|
|||
assert Enum.member?(activities, activity_three)
|
||||
refute Enum.member?(activities, activity_one)
|
||||
|
||||
{:ok, user} = User.unblock(user, %{ap_id: activity_one.data["actor"]})
|
||||
{:ok, _user_block} = User.unblock(user, %{ap_id: activity_one.data["actor"]})
|
||||
|
||||
activities =
|
||||
ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
|
||||
|
@ -518,7 +518,7 @@ test "doesn't return blocked activities" do
|
|||
assert Enum.member?(activities, activity_three)
|
||||
assert Enum.member?(activities, activity_one)
|
||||
|
||||
{:ok, user} = User.block(user, %{ap_id: activity_three.data["actor"]})
|
||||
{:ok, _user_relationship} = User.block(user, %{ap_id: activity_three.data["actor"]})
|
||||
{:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
|
||||
%Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
|
||||
activity_three = Activity.get_by_id(activity_three.id)
|
||||
|
@ -545,7 +545,7 @@ test "doesn't return transitive interactions concerning blocked users" do
|
|||
blockee = insert(:user)
|
||||
friend = insert(:user)
|
||||
|
||||
{:ok, blocker} = User.block(blocker, blockee)
|
||||
{:ok, _user_relationship} = User.block(blocker, blockee)
|
||||
|
||||
{:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey!"})
|
||||
|
||||
|
@ -568,7 +568,7 @@ test "doesn't return announce activities concerning blocked users" do
|
|||
blockee = insert(:user)
|
||||
friend = insert(:user)
|
||||
|
||||
{:ok, blocker} = User.block(blocker, blockee)
|
||||
{:ok, _user_relationship} = User.block(blocker, blockee)
|
||||
|
||||
{:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey!"})
|
||||
|
||||
|
@ -614,7 +614,9 @@ test "doesn't return muted activities" do
|
|||
activity_three = insert(:note_activity)
|
||||
user = insert(:user)
|
||||
booster = insert(:user)
|
||||
{:ok, user} = User.mute(user, %User{ap_id: activity_one.data["actor"]})
|
||||
|
||||
activity_one_actor = User.get_by_ap_id(activity_one.data["actor"])
|
||||
{:ok, _user_relationships} = User.mute(user, activity_one_actor)
|
||||
|
||||
activities =
|
||||
ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
|
||||
|
@ -635,7 +637,7 @@ test "doesn't return muted activities" do
|
|||
assert Enum.member?(activities, activity_three)
|
||||
assert Enum.member?(activities, activity_one)
|
||||
|
||||
{:ok, user} = User.unmute(user, %User{ap_id: activity_one.data["actor"]})
|
||||
{:ok, _user_mute} = User.unmute(user, activity_one_actor)
|
||||
|
||||
activities =
|
||||
ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
|
||||
|
@ -644,7 +646,8 @@ test "doesn't return muted activities" do
|
|||
assert Enum.member?(activities, activity_three)
|
||||
assert Enum.member?(activities, activity_one)
|
||||
|
||||
{:ok, user} = User.mute(user, %User{ap_id: activity_three.data["actor"]})
|
||||
activity_three_actor = User.get_by_ap_id(activity_three.data["actor"])
|
||||
{:ok, _user_relationships} = User.mute(user, activity_three_actor)
|
||||
{:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
|
||||
%Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
|
||||
activity_three = Activity.get_by_id(activity_three.id)
|
||||
|
@ -791,7 +794,7 @@ test "doesn't return reblogs for users for whom reblogs have been muted" do
|
|||
activity = insert(:note_activity)
|
||||
user = insert(:user)
|
||||
booster = insert(:user)
|
||||
{:ok, user} = CommonAPI.hide_reblogs(user, booster)
|
||||
{:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster)
|
||||
|
||||
{:ok, activity, _} = CommonAPI.repeat(activity.id, booster)
|
||||
|
||||
|
@ -804,8 +807,8 @@ test "returns reblogs for users for whom reblogs have not been muted" do
|
|||
activity = insert(:note_activity)
|
||||
user = insert(:user)
|
||||
booster = insert(:user)
|
||||
{:ok, user} = CommonAPI.hide_reblogs(user, booster)
|
||||
{:ok, user} = CommonAPI.show_reblogs(user, booster)
|
||||
{:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster)
|
||||
{:ok, _reblog_mute} = CommonAPI.show_reblogs(user, booster)
|
||||
|
||||
{:ok, activity, _} = CommonAPI.repeat(activity.id, booster)
|
||||
|
||||
|
@ -1256,6 +1259,21 @@ test "decreases reply count" do
|
|||
assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
|
||||
assert object.data["repliesCount"] == 0
|
||||
end
|
||||
|
||||
test "it passes delete activity through MRF before deleting the object" do
|
||||
rewrite_policy = Pleroma.Config.get([:instance, :rewrite_policy])
|
||||
Pleroma.Config.put([:instance, :rewrite_policy], Pleroma.Web.ActivityPub.MRF.DropPolicy)
|
||||
|
||||
on_exit(fn -> Pleroma.Config.put([:instance, :rewrite_policy], rewrite_policy) end)
|
||||
|
||||
note = insert(:note_activity)
|
||||
object = Object.normalize(note)
|
||||
|
||||
{:error, {:reject, _}} = ActivityPub.delete(object)
|
||||
|
||||
assert Activity.get_by_id(note.id)
|
||||
assert Repo.get(Object, object.id).data["type"] == object.data["type"]
|
||||
end
|
||||
end
|
||||
|
||||
describe "timeline post-processing" do
|
||||
|
@ -1619,10 +1637,10 @@ test "create" do
|
|||
activity = %Activity{activity | object: nil}
|
||||
|
||||
assert [%Notification{activity: ^activity}] =
|
||||
Notification.for_user_since(follower, ~N[2019-04-13 11:22:33])
|
||||
Notification.for_user(follower, %{with_move: true})
|
||||
|
||||
assert [%Notification{activity: ^activity}] =
|
||||
Notification.for_user_since(follower_move_opted_out, ~N[2019-04-13 11:22:33])
|
||||
Notification.for_user(follower_move_opted_out, %{with_move: true})
|
||||
end
|
||||
|
||||
test "old user must be in the new user's `also_known_as` list" do
|
||||
|
|
|
@ -128,7 +128,7 @@ test "it rejects incoming follow requests from blocked users when deny_follow_bl
|
|||
user = insert(:user)
|
||||
{:ok, target} = User.get_or_fetch("http://mastodon.example.org/users/admin")
|
||||
|
||||
{:ok, user} = User.block(user, target)
|
||||
{:ok, _user_relationship} = User.block(user, target)
|
||||
|
||||
data =
|
||||
File.read!("test/fixtures/mastodon-follow-activity.json")
|
||||
|
|
|
@ -636,47 +636,4 @@ test "removes actor from announcements" do
|
|||
assert updated_object.data["announcement_count"] == 1
|
||||
end
|
||||
end
|
||||
|
||||
describe "get_reports_grouped_by_status/1" do
|
||||
setup do
|
||||
[reporter, target_user] = insert_pair(:user)
|
||||
first_status = insert(:note_activity, user: target_user)
|
||||
second_status = insert(:note_activity, user: target_user)
|
||||
|
||||
CommonAPI.report(reporter, %{
|
||||
"account_id" => target_user.id,
|
||||
"comment" => "I feel offended",
|
||||
"status_ids" => [first_status.id]
|
||||
})
|
||||
|
||||
CommonAPI.report(reporter, %{
|
||||
"account_id" => target_user.id,
|
||||
"comment" => "I feel offended2",
|
||||
"status_ids" => [second_status.id]
|
||||
})
|
||||
|
||||
data = [%{activity: first_status.data["id"]}, %{activity: second_status.data["id"]}]
|
||||
|
||||
{:ok,
|
||||
%{
|
||||
first_status: first_status,
|
||||
second_status: second_status,
|
||||
data: data
|
||||
}}
|
||||
end
|
||||
|
||||
test "works for deprecated reports format", %{
|
||||
first_status: first_status,
|
||||
second_status: second_status,
|
||||
data: data
|
||||
} do
|
||||
groups = Utils.get_reports_grouped_by_status(data).groups
|
||||
|
||||
first_group = Enum.find(groups, &(&1.status.id == first_status.data["id"]))
|
||||
second_group = Enum.find(groups, &(&1.status.id == second_status.data["id"]))
|
||||
|
||||
assert first_group.status.id == first_status.data["id"]
|
||||
assert second_group.status.id == second_status.data["id"]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,6 +15,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
|
|||
alias Pleroma.UserInviteToken
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
alias Pleroma.Web.MediaProxy
|
||||
import Pleroma.Factory
|
||||
|
||||
|
@ -1667,6 +1668,7 @@ test "returns 403 when requested by anonymous" do
|
|||
first_status: Activity.get_by_ap_id_with_object(first_status.data["id"]),
|
||||
second_status: Activity.get_by_ap_id_with_object(second_status.data["id"]),
|
||||
third_status: Activity.get_by_ap_id_with_object(third_status.data["id"]),
|
||||
first_report: first_report,
|
||||
first_status_reports: [first_report, second_report, third_report],
|
||||
second_status_reports: [first_report, second_report],
|
||||
third_status_reports: [first_report],
|
||||
|
@ -1693,14 +1695,11 @@ test "returns reports grouped by status", %{
|
|||
|
||||
assert length(response["reports"]) == 3
|
||||
|
||||
first_group =
|
||||
Enum.find(response["reports"], &(&1["status"]["id"] == first_status.data["id"]))
|
||||
first_group = Enum.find(response["reports"], &(&1["status"]["id"] == first_status.id))
|
||||
|
||||
second_group =
|
||||
Enum.find(response["reports"], &(&1["status"]["id"] == second_status.data["id"]))
|
||||
second_group = Enum.find(response["reports"], &(&1["status"]["id"] == second_status.id))
|
||||
|
||||
third_group =
|
||||
Enum.find(response["reports"], &(&1["status"]["id"] == third_status.data["id"]))
|
||||
third_group = Enum.find(response["reports"], &(&1["status"]["id"] == third_status.id))
|
||||
|
||||
assert length(first_group["reports"]) == 3
|
||||
assert length(second_group["reports"]) == 2
|
||||
|
@ -1711,13 +1710,14 @@ test "returns reports grouped by status", %{
|
|||
NaiveDateTime.from_iso8601!(act.data["published"])
|
||||
end).data["published"]
|
||||
|
||||
assert first_group["status"] == %{
|
||||
"id" => first_status.data["id"],
|
||||
"content" => first_status.object.data["content"],
|
||||
"published" => first_status.object.data["published"]
|
||||
}
|
||||
assert first_group["status"] ==
|
||||
Map.put(
|
||||
stringify_keys(StatusView.render("show.json", %{activity: first_status})),
|
||||
"deleted",
|
||||
false
|
||||
)
|
||||
|
||||
assert first_group["account"]["id"] == target_user.id
|
||||
assert(first_group["account"]["id"] == target_user.id)
|
||||
|
||||
assert length(first_group["actors"]) == 1
|
||||
assert hd(first_group["actors"])["id"] == reporter.id
|
||||
|
@ -1730,11 +1730,12 @@ test "returns reports grouped by status", %{
|
|||
NaiveDateTime.from_iso8601!(act.data["published"])
|
||||
end).data["published"]
|
||||
|
||||
assert second_group["status"] == %{
|
||||
"id" => second_status.data["id"],
|
||||
"content" => second_status.object.data["content"],
|
||||
"published" => second_status.object.data["published"]
|
||||
}
|
||||
assert second_group["status"] ==
|
||||
Map.put(
|
||||
stringify_keys(StatusView.render("show.json", %{activity: second_status})),
|
||||
"deleted",
|
||||
false
|
||||
)
|
||||
|
||||
assert second_group["account"]["id"] == target_user.id
|
||||
|
||||
|
@ -1749,11 +1750,12 @@ test "returns reports grouped by status", %{
|
|||
NaiveDateTime.from_iso8601!(act.data["published"])
|
||||
end).data["published"]
|
||||
|
||||
assert third_group["status"] == %{
|
||||
"id" => third_status.data["id"],
|
||||
"content" => third_status.object.data["content"],
|
||||
"published" => third_status.object.data["published"]
|
||||
}
|
||||
assert third_group["status"] ==
|
||||
Map.put(
|
||||
stringify_keys(StatusView.render("show.json", %{activity: third_status})),
|
||||
"deleted",
|
||||
false
|
||||
)
|
||||
|
||||
assert third_group["account"]["id"] == target_user.id
|
||||
|
||||
|
@ -1763,6 +1765,70 @@ test "returns reports grouped by status", %{
|
|||
assert Enum.map(third_group["reports"], & &1["id"]) --
|
||||
Enum.map(third_status_reports, & &1.id) == []
|
||||
end
|
||||
|
||||
test "reopened report renders status data", %{
|
||||
conn: conn,
|
||||
first_report: first_report,
|
||||
first_status: first_status
|
||||
} do
|
||||
{:ok, _} = CommonAPI.update_report_state(first_report.id, "resolved")
|
||||
|
||||
response =
|
||||
conn
|
||||
|> get("/api/pleroma/admin/grouped_reports")
|
||||
|> json_response(:ok)
|
||||
|
||||
first_group = Enum.find(response["reports"], &(&1["status"]["id"] == first_status.id))
|
||||
|
||||
assert first_group["status"] ==
|
||||
Map.put(
|
||||
stringify_keys(StatusView.render("show.json", %{activity: first_status})),
|
||||
"deleted",
|
||||
false
|
||||
)
|
||||
end
|
||||
|
||||
test "reopened report does not render status data if status has been deleted", %{
|
||||
conn: conn,
|
||||
first_report: first_report,
|
||||
first_status: first_status,
|
||||
target_user: target_user
|
||||
} do
|
||||
{:ok, _} = CommonAPI.update_report_state(first_report.id, "resolved")
|
||||
{:ok, _} = CommonAPI.delete(first_status.id, target_user)
|
||||
|
||||
refute Activity.get_by_ap_id(first_status.id)
|
||||
|
||||
response =
|
||||
conn
|
||||
|> get("/api/pleroma/admin/grouped_reports")
|
||||
|> json_response(:ok)
|
||||
|
||||
assert Enum.find(response["reports"], &(&1["status"]["deleted"] == true))["status"][
|
||||
"deleted"
|
||||
] == true
|
||||
|
||||
assert length(Enum.filter(response["reports"], &(&1["status"]["deleted"] == false))) == 2
|
||||
end
|
||||
|
||||
test "account not empty if status was deleted", %{
|
||||
conn: conn,
|
||||
first_report: first_report,
|
||||
first_status: first_status,
|
||||
target_user: target_user
|
||||
} do
|
||||
{:ok, _} = CommonAPI.update_report_state(first_report.id, "resolved")
|
||||
{:ok, _} = CommonAPI.delete(first_status.id, target_user)
|
||||
|
||||
refute Activity.get_by_ap_id(first_status.id)
|
||||
|
||||
response =
|
||||
conn
|
||||
|> get("/api/pleroma/admin/grouped_reports")
|
||||
|> json_response(:ok)
|
||||
|
||||
assert Enum.find(response["reports"], &(&1["status"]["deleted"] == true))["account"]
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /api/pleroma/admin/reports/:id/respond" do
|
||||
|
|
37
test/web/chat_channel_test.exs
Normal file
37
test/web/chat_channel_test.exs
Normal file
|
@ -0,0 +1,37 @@
|
|||
defmodule Pleroma.Web.ChatChannelTest do
|
||||
use Pleroma.Web.ChannelCase
|
||||
alias Pleroma.Web.ChatChannel
|
||||
alias Pleroma.Web.UserSocket
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
setup do
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, _, socket} =
|
||||
socket(UserSocket, "", %{user_name: user.nickname})
|
||||
|> subscribe_and_join(ChatChannel, "chat:public")
|
||||
|
||||
{:ok, socket: socket}
|
||||
end
|
||||
|
||||
test "it broadcasts a message", %{socket: socket} do
|
||||
push(socket, "new_msg", %{"text" => "why is tenshi eating a corndog so cute?"})
|
||||
assert_broadcast("new_msg", %{text: "why is tenshi eating a corndog so cute?"})
|
||||
end
|
||||
|
||||
describe "message lengths" do
|
||||
clear_config([:instance, :chat_limit])
|
||||
|
||||
test "it ignores messages of length zero", %{socket: socket} do
|
||||
push(socket, "new_msg", %{"text" => ""})
|
||||
refute_broadcast("new_msg", %{text: ""})
|
||||
end
|
||||
|
||||
test "it ignores messages above a certain length", %{socket: socket} do
|
||||
Pleroma.Config.put([:instance, :chat_limit], 2)
|
||||
push(socket, "new_msg", %{"text" => "123"})
|
||||
refute_broadcast("new_msg", %{text: "123"})
|
||||
end
|
||||
end
|
||||
end
|
|
@ -509,14 +509,14 @@ test "updates state of multiple reports" do
|
|||
end
|
||||
|
||||
test "add a reblog mute", %{muter: muter, muted: muted} do
|
||||
{:ok, muter} = CommonAPI.hide_reblogs(muter, muted)
|
||||
{:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
|
||||
|
||||
assert User.showing_reblogs?(muter, muted) == false
|
||||
end
|
||||
|
||||
test "remove a reblog mute", %{muter: muter, muted: muted} do
|
||||
{:ok, muter} = CommonAPI.hide_reblogs(muter, muted)
|
||||
{:ok, muter} = CommonAPI.show_reblogs(muter, muted)
|
||||
{:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
|
||||
{:ok, _reblog_mute} = CommonAPI.show_reblogs(muter, muted)
|
||||
|
||||
assert User.showing_reblogs?(muter, muted) == true
|
||||
end
|
||||
|
@ -526,7 +526,7 @@ test "remove a reblog mute", %{muter: muter, muted: muted} do
|
|||
test "also unsubscribes a user" do
|
||||
[follower, followed] = insert_pair(:user)
|
||||
{:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
|
||||
{:ok, followed} = User.subscribe(follower, followed)
|
||||
{:ok, _subscription} = User.subscribe(follower, followed)
|
||||
|
||||
assert User.subscribed_to?(follower, followed)
|
||||
|
||||
|
|
|
@ -144,6 +144,50 @@ test "returns 404 for internal.fetch actor", %{conn: conn} do
|
|||
end
|
||||
|
||||
describe "user timelines" do
|
||||
test "respects blocks", %{conn: conn} do
|
||||
user_one = insert(:user)
|
||||
user_two = insert(:user)
|
||||
user_three = insert(:user)
|
||||
|
||||
User.block(user_one, user_two)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user_two, %{"status" => "User one sux0rz"})
|
||||
{:ok, repeat, _} = CommonAPI.repeat(activity.id, user_three)
|
||||
|
||||
resp =
|
||||
conn
|
||||
|> get("/api/v1/accounts/#{user_two.id}/statuses")
|
||||
|
||||
assert [%{"id" => id}] = json_response(resp, 200)
|
||||
assert id == activity.id
|
||||
|
||||
# Even a blocked user will deliver the full user timeline, there would be
|
||||
# no point in looking at a blocked users timeline otherwise
|
||||
resp =
|
||||
conn
|
||||
|> assign(:user, user_one)
|
||||
|> get("/api/v1/accounts/#{user_two.id}/statuses")
|
||||
|
||||
assert [%{"id" => id}] = json_response(resp, 200)
|
||||
assert id == activity.id
|
||||
|
||||
resp =
|
||||
conn
|
||||
|> get("/api/v1/accounts/#{user_three.id}/statuses")
|
||||
|
||||
assert [%{"id" => id}] = json_response(resp, 200)
|
||||
assert id == repeat.id
|
||||
|
||||
# When viewing a third user's timeline, the blocked users will NOT be
|
||||
# shown.
|
||||
resp =
|
||||
conn
|
||||
|> assign(:user, user_one)
|
||||
|> get("/api/v1/accounts/#{user_three.id}/statuses")
|
||||
|
||||
assert [] = json_response(resp, 200)
|
||||
end
|
||||
|
||||
test "gets a users statuses", %{conn: conn} do
|
||||
user_one = insert(:user)
|
||||
user_two = insert(:user)
|
||||
|
@ -891,7 +935,7 @@ test "getting a list of mutes", %{conn: conn} do
|
|||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, user} = User.mute(user, other_user)
|
||||
{:ok, _user_relationships} = User.mute(user, other_user)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|
@ -906,7 +950,7 @@ test "getting a list of blocks", %{conn: conn} do
|
|||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, user} = User.block(user, other_user)
|
||||
{:ok, _user_relationship} = User.block(user, other_user)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|
|
|
@ -137,55 +137,151 @@ test "paginates notifications using min_id, since_id, max_id, and limit", %{conn
|
|||
assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
|
||||
end
|
||||
|
||||
test "filters notifications using exclude_visibilities", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
describe "exclude_visibilities" do
|
||||
test "filters notifications for mentions", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, public_activity} =
|
||||
CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "public"})
|
||||
{:ok, public_activity} =
|
||||
CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "public"})
|
||||
|
||||
{:ok, direct_activity} =
|
||||
CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "direct"})
|
||||
{:ok, direct_activity} =
|
||||
CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "direct"})
|
||||
|
||||
{:ok, unlisted_activity} =
|
||||
CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "unlisted"})
|
||||
{:ok, unlisted_activity} =
|
||||
CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "unlisted"})
|
||||
|
||||
{:ok, private_activity} =
|
||||
CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "private"})
|
||||
{:ok, private_activity} =
|
||||
CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "private"})
|
||||
|
||||
conn = assign(conn, :user, user)
|
||||
conn = assign(conn, :user, user)
|
||||
|
||||
conn_res =
|
||||
get(conn, "/api/v1/notifications", %{
|
||||
exclude_visibilities: ["public", "unlisted", "private"]
|
||||
})
|
||||
conn_res =
|
||||
get(conn, "/api/v1/notifications", %{
|
||||
exclude_visibilities: ["public", "unlisted", "private"]
|
||||
})
|
||||
|
||||
assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
|
||||
assert id == direct_activity.id
|
||||
assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
|
||||
assert id == direct_activity.id
|
||||
|
||||
conn_res =
|
||||
get(conn, "/api/v1/notifications", %{
|
||||
exclude_visibilities: ["public", "unlisted", "direct"]
|
||||
})
|
||||
conn_res =
|
||||
get(conn, "/api/v1/notifications", %{
|
||||
exclude_visibilities: ["public", "unlisted", "direct"]
|
||||
})
|
||||
|
||||
assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
|
||||
assert id == private_activity.id
|
||||
assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
|
||||
assert id == private_activity.id
|
||||
|
||||
conn_res =
|
||||
get(conn, "/api/v1/notifications", %{
|
||||
exclude_visibilities: ["public", "private", "direct"]
|
||||
})
|
||||
conn_res =
|
||||
get(conn, "/api/v1/notifications", %{
|
||||
exclude_visibilities: ["public", "private", "direct"]
|
||||
})
|
||||
|
||||
assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
|
||||
assert id == unlisted_activity.id
|
||||
assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
|
||||
assert id == unlisted_activity.id
|
||||
|
||||
conn_res =
|
||||
get(conn, "/api/v1/notifications", %{
|
||||
exclude_visibilities: ["unlisted", "private", "direct"]
|
||||
})
|
||||
conn_res =
|
||||
get(conn, "/api/v1/notifications", %{
|
||||
exclude_visibilities: ["unlisted", "private", "direct"]
|
||||
})
|
||||
|
||||
assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
|
||||
assert id == public_activity.id
|
||||
assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
|
||||
assert id == public_activity.id
|
||||
end
|
||||
|
||||
test "filters notifications for Like activities", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, public_activity} =
|
||||
CommonAPI.post(other_user, %{"status" => ".", "visibility" => "public"})
|
||||
|
||||
{:ok, direct_activity} =
|
||||
CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "direct"})
|
||||
|
||||
{:ok, unlisted_activity} =
|
||||
CommonAPI.post(other_user, %{"status" => ".", "visibility" => "unlisted"})
|
||||
|
||||
{:ok, private_activity} =
|
||||
CommonAPI.post(other_user, %{"status" => ".", "visibility" => "private"})
|
||||
|
||||
{:ok, _, _} = CommonAPI.favorite(public_activity.id, user)
|
||||
{:ok, _, _} = CommonAPI.favorite(direct_activity.id, user)
|
||||
{:ok, _, _} = CommonAPI.favorite(unlisted_activity.id, user)
|
||||
{:ok, _, _} = CommonAPI.favorite(private_activity.id, user)
|
||||
|
||||
activity_ids =
|
||||
conn
|
||||
|> assign(:user, other_user)
|
||||
|> get("/api/v1/notifications", %{exclude_visibilities: ["direct"]})
|
||||
|> json_response(200)
|
||||
|> Enum.map(& &1["status"]["id"])
|
||||
|
||||
assert public_activity.id in activity_ids
|
||||
assert unlisted_activity.id in activity_ids
|
||||
assert private_activity.id in activity_ids
|
||||
refute direct_activity.id in activity_ids
|
||||
|
||||
activity_ids =
|
||||
conn
|
||||
|> assign(:user, other_user)
|
||||
|> get("/api/v1/notifications", %{exclude_visibilities: ["unlisted"]})
|
||||
|> json_response(200)
|
||||
|> Enum.map(& &1["status"]["id"])
|
||||
|
||||
assert public_activity.id in activity_ids
|
||||
refute unlisted_activity.id in activity_ids
|
||||
assert private_activity.id in activity_ids
|
||||
assert direct_activity.id in activity_ids
|
||||
|
||||
activity_ids =
|
||||
conn
|
||||
|> assign(:user, other_user)
|
||||
|> get("/api/v1/notifications", %{exclude_visibilities: ["private"]})
|
||||
|> json_response(200)
|
||||
|> Enum.map(& &1["status"]["id"])
|
||||
|
||||
assert public_activity.id in activity_ids
|
||||
assert unlisted_activity.id in activity_ids
|
||||
refute private_activity.id in activity_ids
|
||||
assert direct_activity.id in activity_ids
|
||||
|
||||
activity_ids =
|
||||
conn
|
||||
|> assign(:user, other_user)
|
||||
|> get("/api/v1/notifications", %{exclude_visibilities: ["public"]})
|
||||
|> json_response(200)
|
||||
|> Enum.map(& &1["status"]["id"])
|
||||
|
||||
refute public_activity.id in activity_ids
|
||||
assert unlisted_activity.id in activity_ids
|
||||
assert private_activity.id in activity_ids
|
||||
assert direct_activity.id in activity_ids
|
||||
end
|
||||
|
||||
test "filters notifications for Announce activities", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, public_activity} =
|
||||
CommonAPI.post(other_user, %{"status" => ".", "visibility" => "public"})
|
||||
|
||||
{:ok, unlisted_activity} =
|
||||
CommonAPI.post(other_user, %{"status" => ".", "visibility" => "unlisted"})
|
||||
|
||||
{:ok, _, _} = CommonAPI.repeat(public_activity.id, user)
|
||||
{:ok, _, _} = CommonAPI.repeat(unlisted_activity.id, user)
|
||||
|
||||
activity_ids =
|
||||
conn
|
||||
|> assign(:user, other_user)
|
||||
|> get("/api/v1/notifications", %{exclude_visibilities: ["unlisted"]})
|
||||
|> json_response(200)
|
||||
|> Enum.map(& &1["status"]["id"])
|
||||
|
||||
assert public_activity.id in activity_ids
|
||||
refute unlisted_activity.id in activity_ids
|
||||
end
|
||||
end
|
||||
|
||||
test "filters notifications using exclude_types", %{conn: conn} do
|
||||
|
@ -289,7 +385,7 @@ test "doesn't see notifications after muting user with notifications", %{conn: c
|
|||
|
||||
assert length(json_response(conn, 200)) == 1
|
||||
|
||||
{:ok, user} = User.mute(user, user2)
|
||||
{:ok, _user_relationships} = User.mute(user, user2)
|
||||
|
||||
conn = assign(build_conn(), :user, user)
|
||||
conn = get(conn, "/api/v1/notifications")
|
||||
|
@ -310,7 +406,7 @@ test "see notifications after muting user without notifications", %{conn: conn}
|
|||
|
||||
assert length(json_response(conn, 200)) == 1
|
||||
|
||||
{:ok, user} = User.mute(user, user2, false)
|
||||
{:ok, _user_relationships} = User.mute(user, user2, false)
|
||||
|
||||
conn = assign(build_conn(), :user, user)
|
||||
conn = get(conn, "/api/v1/notifications")
|
||||
|
@ -333,7 +429,7 @@ test "see notifications after muting user with notifications and with_muted para
|
|||
|
||||
assert length(json_response(conn, 200)) == 1
|
||||
|
||||
{:ok, user} = User.mute(user, user2)
|
||||
{:ok, _user_relationships} = User.mute(user, user2)
|
||||
|
||||
conn = assign(build_conn(), :user, user)
|
||||
conn = get(conn, "/api/v1/notifications", %{"with_muted" => "true"})
|
||||
|
@ -341,6 +437,32 @@ test "see notifications after muting user with notifications and with_muted para
|
|||
assert length(json_response(conn, 200)) == 1
|
||||
end
|
||||
|
||||
test "see move notifications with `with_move` parameter", %{
|
||||
conn: conn
|
||||
} do
|
||||
old_user = insert(:user)
|
||||
new_user = insert(:user, also_known_as: [old_user.ap_id])
|
||||
follower = insert(:user)
|
||||
|
||||
User.follow(follower, old_user)
|
||||
Pleroma.Web.ActivityPub.ActivityPub.move(old_user, new_user)
|
||||
Pleroma.Tests.ObanHelpers.perform_all()
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, follower)
|
||||
|> get("/api/v1/notifications")
|
||||
|
||||
assert json_response(conn, 200) == []
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, follower)
|
||||
|> get("/api/v1/notifications", %{"with_move" => "true"})
|
||||
|
||||
assert length(json_response(conn, 200)) == 1
|
||||
end
|
||||
|
||||
defp get_notification_id_by_activity(%{id: id}) do
|
||||
Notification
|
||||
|> Repo.get_by(activity_id: id)
|
||||
|
|
|
@ -165,15 +165,20 @@ test "search", %{conn: conn} do
|
|||
assert status["id"] == to_string(activity.id)
|
||||
end
|
||||
|
||||
test "search fetches remote statuses", %{conn: conn} do
|
||||
test "search fetches remote statuses and prefers them over other results", %{conn: conn} do
|
||||
capture_log(fn ->
|
||||
{:ok, %{id: activity_id}} =
|
||||
CommonAPI.post(insert(:user), %{
|
||||
"status" => "check out https://shitposter.club/notice/2827873"
|
||||
})
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> get("/api/v1/search", %{"q" => "https://shitposter.club/notice/2827873"})
|
||||
|
||||
assert results = json_response(conn, 200)
|
||||
|
||||
[status] = results["statuses"]
|
||||
[status, %{"id" => ^activity_id}] = results["statuses"]
|
||||
|
||||
assert status["uri"] ==
|
||||
"tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
|
||||
|
|
|
@ -1108,7 +1108,7 @@ test "does not return users who have favorited the status but are blocked", %{
|
|||
activity: activity
|
||||
} do
|
||||
other_user = insert(:user)
|
||||
{:ok, user} = User.block(user, other_user)
|
||||
{:ok, _user_relationship} = User.block(user, other_user)
|
||||
|
||||
{:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
|
||||
|
||||
|
@ -1205,7 +1205,7 @@ test "does not return users who have reblogged the status but are blocked", %{
|
|||
activity: activity
|
||||
} do
|
||||
other_user = insert(:user)
|
||||
{:ok, user} = User.block(user, other_user)
|
||||
{:ok, _user_relationship} = User.block(user, other_user)
|
||||
|
||||
{:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
|
||||
|
||||
|
|
|
@ -194,7 +194,7 @@ test "doesn't include DMs from blocked users", %{conn: conn} do
|
|||
blocker = insert(:user)
|
||||
blocked = insert(:user)
|
||||
user = insert(:user)
|
||||
{:ok, blocker} = User.block(blocker, blocked)
|
||||
{:ok, _user_relationship} = User.block(blocker, blocked)
|
||||
|
||||
{:ok, _blocked_direct} =
|
||||
CommonAPI.post(blocked, %{
|
||||
|
|
|
@ -92,13 +92,7 @@ test "Represent a user account" do
|
|||
test "Represent the user account for the account owner" do
|
||||
user = insert(:user)
|
||||
|
||||
notification_settings = %{
|
||||
"followers" => true,
|
||||
"follows" => true,
|
||||
"non_follows" => true,
|
||||
"non_followers" => true
|
||||
}
|
||||
|
||||
notification_settings = %Pleroma.User.NotificationSetting{}
|
||||
privacy = user.default_scope
|
||||
|
||||
assert %{
|
||||
|
@ -190,9 +184,9 @@ test "represent a relationship for the following and followed user" do
|
|||
|
||||
{:ok, user} = User.follow(user, other_user)
|
||||
{:ok, other_user} = User.follow(other_user, user)
|
||||
{:ok, other_user} = User.subscribe(user, other_user)
|
||||
{:ok, user} = User.mute(user, other_user, true)
|
||||
{:ok, user} = CommonAPI.hide_reblogs(user, other_user)
|
||||
{:ok, _subscription} = User.subscribe(user, other_user)
|
||||
{:ok, _user_relationships} = User.mute(user, other_user, true)
|
||||
{:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, other_user)
|
||||
|
||||
expected = %{
|
||||
id: to_string(other_user.id),
|
||||
|
@ -218,9 +212,9 @@ test "represent a relationship for the blocking and blocked user" do
|
|||
other_user = insert(:user)
|
||||
|
||||
{:ok, user} = User.follow(user, other_user)
|
||||
{:ok, other_user} = User.subscribe(user, other_user)
|
||||
{:ok, user} = User.block(user, other_user)
|
||||
{:ok, other_user} = User.block(other_user, user)
|
||||
{:ok, _subscription} = User.subscribe(user, other_user)
|
||||
{:ok, _user_relationship} = User.block(user, other_user)
|
||||
{:ok, _user_relationship} = User.block(other_user, user)
|
||||
|
||||
expected = %{
|
||||
id: to_string(other_user.id),
|
||||
|
@ -291,7 +285,7 @@ test "represent an embedded relationship" do
|
|||
|
||||
other_user = insert(:user)
|
||||
{:ok, other_user} = User.follow(other_user, user)
|
||||
{:ok, other_user} = User.block(other_user, user)
|
||||
{:ok, _user_relationship} = User.block(other_user, user)
|
||||
{:ok, _} = User.follow(insert(:user), user)
|
||||
|
||||
expected = %{
|
||||
|
|
|
@ -109,8 +109,8 @@ test "Follow notification" do
|
|||
end
|
||||
|
||||
test "Move notification" do
|
||||
%{ap_id: old_ap_id} = old_user = insert(:user)
|
||||
%{ap_id: _new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id])
|
||||
old_user = insert(:user)
|
||||
new_user = insert(:user, also_known_as: [old_user.ap_id])
|
||||
follower = insert(:user)
|
||||
|
||||
User.follow(follower, old_user)
|
||||
|
@ -120,7 +120,7 @@ test "Move notification" do
|
|||
old_user = refresh_record(old_user)
|
||||
new_user = refresh_record(new_user)
|
||||
|
||||
[notification] = Notification.for_user(follower)
|
||||
[notification] = Notification.for_user(follower, %{with_move: true})
|
||||
|
||||
expected = %{
|
||||
id: to_string(notification.id),
|
||||
|
|
|
@ -183,7 +183,7 @@ test "tells if the message is muted for some reason" do
|
|||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, user} = User.mute(user, other_user)
|
||||
{:ok, _user_relationships} = User.mute(user, other_user)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"})
|
||||
status = StatusView.render("show.json", %{activity: activity})
|
||||
|
@ -199,7 +199,7 @@ test "tells if the message is thread muted" do
|
|||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, user} = User.mute(user, other_user)
|
||||
{:ok, _user_relationships} = User.mute(user, other_user)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"})
|
||||
status = StatusView.render("show.json", %{activity: activity, for: user})
|
||||
|
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.Push.ImplTest do
|
|||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.Push.Impl
|
||||
alias Pleroma.Web.Push.Subscription
|
||||
|
@ -182,4 +183,50 @@ test "renders title for create activity with direct visibility" do
|
|||
assert Impl.format_title(%{activity: activity}) ==
|
||||
"New Direct Message"
|
||||
end
|
||||
|
||||
describe "build_content/3" do
|
||||
test "returns info content for direct message with enabled privacy option" do
|
||||
user = insert(:user, nickname: "Bob")
|
||||
user2 = insert(:user, nickname: "Rob", notification_settings: %{privacy_option: true})
|
||||
|
||||
{:ok, activity} =
|
||||
CommonAPI.post(user, %{
|
||||
"visibility" => "direct",
|
||||
"status" => "<Lorem ipsum dolor sit amet."
|
||||
})
|
||||
|
||||
notif = insert(:notification, user: user2, activity: activity)
|
||||
|
||||
actor = User.get_cached_by_ap_id(notif.activity.data["actor"])
|
||||
object = Object.normalize(activity)
|
||||
|
||||
assert Impl.build_content(notif, actor, object) == %{
|
||||
body: "@Bob",
|
||||
title: "New Direct Message"
|
||||
}
|
||||
end
|
||||
|
||||
test "returns regular content for direct message with disabled privacy option" do
|
||||
user = insert(:user, nickname: "Bob")
|
||||
user2 = insert(:user, nickname: "Rob", notification_settings: %{privacy_option: false})
|
||||
|
||||
{:ok, activity} =
|
||||
CommonAPI.post(user, %{
|
||||
"visibility" => "direct",
|
||||
"status" =>
|
||||
"<span>Lorem ipsum dolor sit amet</span>, consectetur :firefox: adipiscing elit. Fusce sagittis finibus turpis."
|
||||
})
|
||||
|
||||
notif = insert(:notification, user: user2, activity: activity)
|
||||
|
||||
actor = User.get_cached_by_ap_id(notif.activity.data["actor"])
|
||||
object = Object.normalize(activity)
|
||||
|
||||
assert Impl.build_content(notif, actor, object) == %{
|
||||
body:
|
||||
"@Bob: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sagittis fini...",
|
||||
title: "New Direct Message"
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -59,7 +59,7 @@ test "it doesn't send notify to the 'user:notification' stream when a user is bl
|
|||
user: user
|
||||
} do
|
||||
blocked = insert(:user)
|
||||
{:ok, user} = User.block(user, blocked)
|
||||
{:ok, _user_relationship} = User.block(user, blocked)
|
||||
|
||||
task = Task.async(fn -> refute_receive {:text, _}, 4_000 end)
|
||||
|
||||
|
@ -259,7 +259,7 @@ test "it sends message if recipients invalid and thread containment is enabled b
|
|||
test "it doesn't send messages involving blocked users" do
|
||||
user = insert(:user)
|
||||
blocked_user = insert(:user)
|
||||
{:ok, user} = User.block(user, blocked_user)
|
||||
{:ok, _user_relationship} = User.block(user, blocked_user)
|
||||
|
||||
task =
|
||||
Task.async(fn ->
|
||||
|
@ -301,7 +301,7 @@ test "it doesn't send messages transitively involving blocked users" do
|
|||
"public" => [fake_socket]
|
||||
}
|
||||
|
||||
{:ok, blocker} = User.block(blocker, blockee)
|
||||
{:ok, _user_relationship} = User.block(blocker, blockee)
|
||||
|
||||
{:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey! @#{blockee.nickname}"})
|
||||
|
||||
|
|
|
@ -159,11 +159,31 @@ test "it updates notification settings", %{conn: conn} do
|
|||
|
||||
user = Repo.get(User, user.id)
|
||||
|
||||
assert %{
|
||||
"followers" => false,
|
||||
"follows" => true,
|
||||
"non_follows" => true,
|
||||
"non_followers" => true
|
||||
assert %Pleroma.User.NotificationSetting{
|
||||
followers: false,
|
||||
follows: true,
|
||||
non_follows: true,
|
||||
non_followers: true,
|
||||
privacy_option: false
|
||||
} == user.notification_settings
|
||||
end
|
||||
|
||||
test "it update notificatin privacy option", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> put("/api/pleroma/notification_settings", %{"privacy_option" => "1"})
|
||||
|> json_response(:ok)
|
||||
|
||||
user = refresh_record(user)
|
||||
|
||||
assert %Pleroma.User.NotificationSetting{
|
||||
followers: true,
|
||||
follows: true,
|
||||
non_follows: true,
|
||||
non_followers: true,
|
||||
privacy_option: true
|
||||
} == user.notification_settings
|
||||
end
|
||||
end
|
||||
|
@ -387,7 +407,7 @@ test "returns error when user is blocked", %{conn: conn} do
|
|||
user = insert(:user)
|
||||
user2 = insert(:user)
|
||||
|
||||
{:ok, _user} = Pleroma.User.block(user2, user)
|
||||
{:ok, _user_block} = Pleroma.User.block(user2, user)
|
||||
|
||||
response =
|
||||
conn
|
||||
|
@ -485,7 +505,7 @@ test "returns error when user is blocked", %{conn: conn} do
|
|||
Pleroma.Config.put([:user, :deny_follow_blocked], true)
|
||||
user = insert(:user)
|
||||
user2 = insert(:user)
|
||||
{:ok, _user} = Pleroma.User.block(user2, user)
|
||||
{:ok, _user_block} = Pleroma.User.block(user2, user)
|
||||
|
||||
response =
|
||||
conn
|
||||
|
|
Loading…
Reference in a new issue