forked from AkkomaGang/akkoma
Merge branch 'develop' into stable
This commit is contained in:
commit
f614bf2725
93 changed files with 2170 additions and 1176 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -78,3 +78,4 @@ docs/venv
|
||||||
# docker stuff
|
# docker stuff
|
||||||
docker-db
|
docker-db
|
||||||
*.iml
|
*.iml
|
||||||
|
docker-compose.override.yml
|
||||||
|
|
|
@ -7,6 +7,7 @@ matrix:
|
||||||
ELIXIR_VERSION:
|
ELIXIR_VERSION:
|
||||||
- 1.14
|
- 1.14
|
||||||
- 1.15
|
- 1.15
|
||||||
|
- 1.16
|
||||||
OTP_VERSION:
|
OTP_VERSION:
|
||||||
- 25
|
- 25
|
||||||
- 26
|
- 26
|
||||||
|
@ -17,6 +18,8 @@ matrix:
|
||||||
OTP_VERSION: 25
|
OTP_VERSION: 25
|
||||||
- ELIXIR_VERSION: 1.15
|
- ELIXIR_VERSION: 1.15
|
||||||
OTP_VERSION: 26
|
OTP_VERSION: 26
|
||||||
|
- ELIXIR_VERSION: 1.16
|
||||||
|
OTP_VERSION: 26
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
- &scw-secrets
|
- &scw-secrets
|
||||||
|
|
38
CHANGELOG.md
38
CHANGELOG.md
|
@ -4,6 +4,44 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 2024.04
|
||||||
|
|
||||||
|
## Added
|
||||||
|
- Support for [FEP-fffd](https://codeberg.org/fediverse/fep/src/branch/main/fep/fffd/fep-fffd.md) (proxy objects)
|
||||||
|
- Verified support for elixir 1.16
|
||||||
|
- Uploadfilter `Pleroma.Upload.Filter.Exiftool.ReadDescription` returns description values to the FE so they can pre fill the image description field
|
||||||
|
NOTE: this filter MUST be placed before `Exiftool.StripMetadata` to work
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
- Inbound pipeline error handing was modified somewhat, which should lead to less incomprehensible log spam. Hopefully.
|
||||||
|
- Uploadfilter `Pleroma.Upload.Filter.Exiftool` was replaced by `Pleroma.Upload.Filter.Exiftool.StripMetadata`;
|
||||||
|
the latter strips all non-essential metadata by default but can be configured.
|
||||||
|
To regain the old behaviour of only stripping GPS data set `purge: ["gps:all"]`.
|
||||||
|
- Uploadfilter `Pleroma.Upload.Filter.Exiftool` has been renamed to `Pleroma.Upload.Filter.Exiftool.StripMetadata`
|
||||||
|
- MRF.InlineQuotePolicy now prefers to insert display URLs instead of ActivityPub IDs
|
||||||
|
- Old accounts are no longer listed in WebFinger as aliases; this was breaking spec
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
- Issue preventing fetching anything from IPv6-only instances
|
||||||
|
- Issue allowing post content to leak via opengraph tags despite :estrict\_unauthenticated being set
|
||||||
|
- Move activities no longer operate on stale user data
|
||||||
|
- Missing definitions in our JSON-LD context
|
||||||
|
- Issue mangling newlines in code blocks for RSS/Atom feeds
|
||||||
|
- static\_fe squeezing non-square avatars and emoji
|
||||||
|
- Issue leading to properly JSON-LD compacted emoji reactions being rejected
|
||||||
|
- We now use a standard-compliant Accept header when fetching ActivityPub objects
|
||||||
|
- /api/pleroma/notification\_settings was rejecting body parameters;
|
||||||
|
this also broke changing this setting via akkoma-fe
|
||||||
|
- Issue leading to Mastodon bot accounts being rejected
|
||||||
|
- Scope misdetection of remote posts resulting from not recognising
|
||||||
|
JSON-LD-compacted forms of public scope; affected e.g. federation with bovine
|
||||||
|
|
||||||
|
## Removed
|
||||||
|
- ActivityPub Client-To-Server write API endpoints have been disabled;
|
||||||
|
read endpoints are planned to be removed next release unless a clear need is demonstrated
|
||||||
|
|
||||||
## 2024.03
|
## 2024.03
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
|
|
2
Procfile
2
Procfile
|
@ -1,2 +0,0 @@
|
||||||
web: mix phx.server
|
|
||||||
release: mix ecto.migrate
|
|
|
@ -222,6 +222,26 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
%{
|
||||||
|
group: :pleroma,
|
||||||
|
key: Pleroma.Upload.Filter.Exiftool.StripMetadata,
|
||||||
|
type: :group,
|
||||||
|
description: "Strip specified metadata from image uploads",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :purge,
|
||||||
|
description: "Metadata fields or groups to strip",
|
||||||
|
type: {:list, :string},
|
||||||
|
suggestions: ["all", "CommonIFD0"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :preserve,
|
||||||
|
description: "Metadata fields or groups to preserve (takes precedence over stripping)",
|
||||||
|
type: {:list, :string},
|
||||||
|
suggestions: ["ColorSpaces", "Orientation"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: Pleroma.Emails.Mailer,
|
key: Pleroma.Emails.Mailer,
|
||||||
|
|
|
@ -26,6 +26,8 @@
|
||||||
filters: [],
|
filters: [],
|
||||||
link_name: false
|
link_name: false
|
||||||
|
|
||||||
|
config :pleroma, :media_proxy, base_url: "http://localhost:4001"
|
||||||
|
|
||||||
config :pleroma, Pleroma.Uploaders.Local, uploads: "test/uploads"
|
config :pleroma, Pleroma.Uploaders.Local, uploads: "test/uploads"
|
||||||
|
|
||||||
config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Test, enabled: true
|
config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Test, enabled: true
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"skip_files": [
|
|
||||||
"test/support",
|
|
||||||
"lib/mix/tasks/pleroma/benchmark.ex",
|
|
||||||
"lib/credo/check/consistency/file_location.ex"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -46,7 +46,7 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- .:/opt/akkoma
|
- .:/opt/akkoma
|
||||||
|
|
||||||
# Uncomment the following if you want to use a reverse proxy
|
# Copy this into docker-compose.override.yml and uncomment there if you want to use a reverse proxy
|
||||||
#proxy:
|
#proxy:
|
||||||
# image: caddy:2-alpine
|
# image: caddy:2-alpine
|
||||||
# restart: unless-stopped
|
# restart: unless-stopped
|
||||||
|
|
|
@ -37,7 +37,8 @@ If any of the options are left unspecified, you will be prompted interactively.
|
||||||
- `--static-dir <path>` - the directory custom public files should be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)
|
- `--static-dir <path>` - the directory custom public files should be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)
|
||||||
- `--listen-ip <ip>` - the ip the app should listen to, defaults to 127.0.0.1
|
- `--listen-ip <ip>` - the ip the app should listen to, defaults to 127.0.0.1
|
||||||
- `--listen-port <port>` - the port the app should listen to, defaults to 4000
|
- `--listen-port <port>` - the port the app should listen to, defaults to 4000
|
||||||
- `--strip-uploads <Y|N>` - use ExifTool to strip uploads of sensitive location data
|
- `--strip-uploads-metadata <Y|N>` - use ExifTool to strip uploads of metadata when possible
|
||||||
|
- `--read-uploads-description <Y|N>` - use ExifTool to read image descriptions from uploads
|
||||||
- `--anonymize-uploads <Y|N>` - randomize uploaded filenames
|
- `--anonymize-uploads <Y|N>` - randomize uploaded filenames
|
||||||
- `--dedupe-uploads <Y|N>` - store files based on their hash to reduce data storage requirements if duplicates are uploaded with different filenames
|
- `--dedupe-uploads <Y|N>` - store files based on their hash to reduce data storage requirements if duplicates are uploaded with different filenames
|
||||||
- `--skip-release-env` - skip generation the release environment file
|
- `--skip-release-env` - skip generation the release environment file
|
||||||
|
|
|
@ -654,9 +654,17 @@ This filter replaces the declared filename (not the path) of an upload.
|
||||||
|
|
||||||
* `text`: Text to replace filenames in links. If empty, `{random}.extension` will be used. You can get the original filename extension by using `{extension}`, for example `custom-file-name.{extension}`.
|
* `text`: Text to replace filenames in links. If empty, `{random}.extension` will be used. You can get the original filename extension by using `{extension}`, for example `custom-file-name.{extension}`.
|
||||||
|
|
||||||
#### Pleroma.Upload.Filter.Exiftool
|
#### Pleroma.Upload.Filter.Exiftool.StripMetadata
|
||||||
|
|
||||||
This filter only strips the GPS and location metadata with Exiftool leaving color profiles and attributes intact.
|
This filter strips metadata with Exiftool leaving color profiles and orientation intact.
|
||||||
|
|
||||||
|
* `purge`: List of Exiftool tag names or tag group names to purge
|
||||||
|
* `preserve`: List of Exiftool tag names or tag group names to preserve even if they occur in the purge list
|
||||||
|
|
||||||
|
|
||||||
|
#### Pleroma.Upload.Filter.Exiftool.ReadDescription
|
||||||
|
|
||||||
|
This filter reads the ImageDescription and iptc:Caption-Abstract fields with Exiftool so clients can prefill the media description field.
|
||||||
|
|
||||||
No specific configuration.
|
No specific configuration.
|
||||||
|
|
||||||
|
|
|
@ -145,47 +145,13 @@ If you want to open your newly installed instance to the world, you should run n
|
||||||
doas apk add nginx
|
doas apk add nginx
|
||||||
```
|
```
|
||||||
|
|
||||||
* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
doas apk add certbot
|
|
||||||
```
|
|
||||||
|
|
||||||
and then set it up:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
doas mkdir -p /var/lib/letsencrypt/
|
|
||||||
doas certbot certonly --email <your@emailaddress> -d <yourdomain> --standalone
|
|
||||||
```
|
|
||||||
|
|
||||||
If that doesn’t work, make sure, that nginx is not already running. If it still doesn’t work, try setting up nginx first (change ssl “on” to “off” and try again).
|
|
||||||
|
|
||||||
* Copy the example nginx configuration to the nginx folder
|
* Copy the example nginx configuration to the nginx folder
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
doas cp /opt/akkoma/installation/nginx/akkoma.nginx /etc/nginx/conf.d/akkoma.conf
|
doas cp /opt/akkoma/installation/nginx/akkoma.nginx /etc/nginx/conf.d/akkoma.conf
|
||||||
```
|
```
|
||||||
|
|
||||||
* Before starting nginx edit the configuration and change it to your needs. You must change change `server_name` and the paths to the certificates. You can use `nano` (install with `apk add nano` if missing).
|
* Before starting nginx edit the configuration and change it to your needs. You must change change `server_name`. You can use `nano` (install with `apk add nano` if missing).
|
||||||
|
|
||||||
```
|
|
||||||
server {
|
|
||||||
server_name your.domain;
|
|
||||||
listen 80;
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
server_name your.domain;
|
|
||||||
listen 443 ssl http2;
|
|
||||||
...
|
|
||||||
ssl_trusted_certificate /etc/letsencrypt/live/your.domain/chain.pem;
|
|
||||||
ssl_certificate /etc/letsencrypt/live/your.domain/fullchain.pem;
|
|
||||||
ssl_certificate_key /etc/letsencrypt/live/your.domain/privkey.pem;
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
* Enable and start nginx:
|
* Enable and start nginx:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
@ -193,10 +159,37 @@ doas rc-update add nginx
|
||||||
doas rc-service nginx start
|
doas rc-service nginx start
|
||||||
```
|
```
|
||||||
|
|
||||||
If you need to renew the certificate in the future, uncomment the relevant location block in the nginx config and run:
|
* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
doas certbot certonly --email <your@emailaddress> -d <yourdomain> --webroot -w /var/lib/letsencrypt/
|
doas apk add certbot certbot-nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
and then set it up:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
doas mkdir -p /var/lib/letsencrypt/
|
||||||
|
doas certbot --email <your@emailaddress> -d <yourdomain> -d <media_domain> --nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
If that doesn't work the first time, add `--dry-run` to further attempts to avoid being ratelimited as you identify the issue, and do not remove it until the dry run succeeds. A common source of problems are nginx config syntax errors; this can be checked for by running `nginx -t`.
|
||||||
|
|
||||||
|
To automatically renew, set up a cron job like so:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Enable the crond service
|
||||||
|
doas rc-update add crond
|
||||||
|
doas rc-service crond start
|
||||||
|
|
||||||
|
# Test that renewals work
|
||||||
|
doas certbot renew --cert-name yourinstance.tld --nginx --dry-run
|
||||||
|
|
||||||
|
# Add the renewal task to cron
|
||||||
|
echo '#!/bin/sh
|
||||||
|
certbot renew --cert-name yourinstance.tld --nginx
|
||||||
|
' | doas tee /etc/periodic/daily/renew-akkoma-cert
|
||||||
|
doas chmod +x /etc/periodic/daily/renew-akkoma-cert
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### OpenRC service
|
#### OpenRC service
|
||||||
|
|
|
@ -136,16 +136,17 @@ If you want to open your newly installed instance to the world, you should run n
|
||||||
sudo pacman -S nginx
|
sudo pacman -S nginx
|
||||||
```
|
```
|
||||||
|
|
||||||
* Create directories for available and enabled sites:
|
* Copy the example nginx configuration:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo mkdir -p /etc/nginx/sites-{available,enabled}
|
sudo cp /opt/akkoma/installation/nginx/akkoma.nginx /etc/nginx/conf.d/akkoma.conf
|
||||||
```
|
```
|
||||||
|
|
||||||
* Append the following line at the end of the `http` block in `/etc/nginx/nginx.conf`:
|
* Before starting nginx edit the configuration and change it to your needs (e.g. change servername, change cert paths)
|
||||||
|
* Enable and start nginx:
|
||||||
|
|
||||||
```Nginx
|
```shell
|
||||||
include sites-enabled/*;
|
sudo systemctl enable --now nginx.service
|
||||||
```
|
```
|
||||||
|
|
||||||
* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
|
* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
|
||||||
|
@ -158,32 +159,18 @@ and then set it up:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo mkdir -p /var/lib/letsencrypt/
|
sudo mkdir -p /var/lib/letsencrypt/
|
||||||
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --standalone
|
sudo certbot --email <your@emailaddress> -d <yourdomain> -d <media_domain> --nginx
|
||||||
```
|
```
|
||||||
|
|
||||||
If that doesn’t work, make sure, that nginx is not already running. If it still doesn’t work, try setting up nginx first (change ssl “on” to “off” and try again).
|
If that doesn't work the first time, add `--dry-run` to further attempts to avoid being ratelimited as you identify the issue, and do not remove it until the dry run succeeds. A common source of problems are nginx config syntax errors; this can be checked for by running `nginx -t`.
|
||||||
|
|
||||||
---
|
To make sure renewals work, enable the appropriate systemd timer:
|
||||||
|
|
||||||
* Copy the example nginx configuration and activate it:
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo cp /opt/akkoma/installation/nginx/akkoma.nginx /etc/nginx/sites-available/akkoma.nginx
|
sudo systemctl enable --now certbot-renew.timer
|
||||||
sudo ln -s /etc/nginx/sites-available/akkoma.nginx /etc/nginx/sites-enabled/akkoma.nginx
|
|
||||||
```
|
```
|
||||||
|
|
||||||
* Before starting nginx edit the configuration and change it to your needs (e.g. change servername, change cert paths)
|
Certificate renewal should be handled automatically by Certbot from now on.
|
||||||
* Enable and start nginx:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo systemctl enable --now nginx.service
|
|
||||||
```
|
|
||||||
|
|
||||||
If you need to renew the certificate in the future, uncomment the relevant location block in the nginx config and run:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --webroot -w /var/lib/letsencrypt/
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Other webserver/proxies
|
#### Other webserver/proxies
|
||||||
|
|
||||||
|
|
|
@ -155,23 +155,6 @@ If you want to open your newly installed instance to the world, you should run n
|
||||||
sudo apt install nginx
|
sudo apt install nginx
|
||||||
```
|
```
|
||||||
|
|
||||||
* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo apt install certbot
|
|
||||||
```
|
|
||||||
|
|
||||||
and then set it up:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo mkdir -p /var/lib/letsencrypt/
|
|
||||||
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --standalone
|
|
||||||
```
|
|
||||||
|
|
||||||
If that doesn’t work, make sure, that nginx is not already running. If it still doesn’t work, try setting up nginx first (change ssl “on” to “off” and try again).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
* Copy the example nginx configuration and activate it:
|
* Copy the example nginx configuration and activate it:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
@ -186,12 +169,23 @@ sudo ln -s /etc/nginx/sites-available/akkoma.nginx /etc/nginx/sites-enabled/akko
|
||||||
sudo systemctl enable --now nginx.service
|
sudo systemctl enable --now nginx.service
|
||||||
```
|
```
|
||||||
|
|
||||||
If you need to renew the certificate in the future, uncomment the relevant location block in the nginx config and run:
|
* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --webroot -w /var/lib/letsencrypt/
|
sudo apt install certbot python3-certbot-nginx
|
||||||
```
|
```
|
||||||
|
|
||||||
|
and then set it up:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo mkdir -p /var/lib/letsencrypt/
|
||||||
|
sudo certbot --email <your@emailaddress> -d <yourdomain> -d <media_domain> --nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
If that doesn't work the first time, add `--dry-run` to further attempts to avoid being ratelimited as you identify the issue, and do not remove it until the dry run succeeds. A common source of problems are nginx config syntax errors; this can be checked for by running `nginx -t`.
|
||||||
|
|
||||||
|
Certificate renewal should be handled automatically by Certbot from now on.
|
||||||
|
|
||||||
#### Other webserver/proxies
|
#### Other webserver/proxies
|
||||||
|
|
||||||
You can find example configurations for them in `/opt/akkoma/installation/`.
|
You can find example configurations for them in `/opt/akkoma/installation/`.
|
||||||
|
|
|
@ -125,7 +125,26 @@ cp docker-resources/Caddyfile.example docker-resources/Caddyfile
|
||||||
|
|
||||||
Then edit the TLD in your caddyfile to the domain you're serving on.
|
Then edit the TLD in your caddyfile to the domain you're serving on.
|
||||||
|
|
||||||
Uncomment the `caddy` section in the docker compose file,
|
Copy the commented out `caddy` section in `docker-compose.yml` into a new file called `docker-compose.override.yml` like so:
|
||||||
|
```yaml
|
||||||
|
version: "3.7"
|
||||||
|
|
||||||
|
services:
|
||||||
|
proxy:
|
||||||
|
image: caddy:2-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
links:
|
||||||
|
- akkoma
|
||||||
|
ports: [
|
||||||
|
"443:443",
|
||||||
|
"80:80"
|
||||||
|
]
|
||||||
|
volumes:
|
||||||
|
- ./docker-resources/Caddyfile:/etc/caddy/Caddyfile
|
||||||
|
- ./caddy-data:/data
|
||||||
|
- ./caddy-config:/config
|
||||||
|
```
|
||||||
|
|
||||||
then run `docker compose up -d` again.
|
then run `docker compose up -d` again.
|
||||||
|
|
||||||
#### Running a reverse proxy on the host
|
#### Running a reverse proxy on the host
|
||||||
|
@ -155,6 +174,12 @@ git pull
|
||||||
docker compose restart akkoma db
|
docker compose restart akkoma db
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Modifying the Docker services
|
||||||
|
If you want to modify the services defined in the docker compose file, you can
|
||||||
|
create a new file called `docker-compose.override.yml`. There you can add any
|
||||||
|
overrides or additional services without worrying about git conflicts when a
|
||||||
|
new release comes out.
|
||||||
|
|
||||||
#### Further reading
|
#### Further reading
|
||||||
|
|
||||||
{! installation/further_reading.include !}
|
{! installation/further_reading.include !}
|
||||||
|
|
|
@ -135,23 +135,6 @@ If you want to open your newly installed instance to the world, you should run n
|
||||||
sudo dnf install nginx
|
sudo dnf install nginx
|
||||||
```
|
```
|
||||||
|
|
||||||
* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo dnf install certbot
|
|
||||||
```
|
|
||||||
|
|
||||||
and then set it up:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo mkdir -p /var/lib/letsencrypt/
|
|
||||||
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --standalone
|
|
||||||
```
|
|
||||||
|
|
||||||
If that doesn’t work, make sure, that nginx is not already running. If it still doesn’t work, try setting up nginx first (change ssl “on” to “off” and try again).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
* Copy the example nginx configuration and activate it:
|
* Copy the example nginx configuration and activate it:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
@ -165,12 +148,23 @@ sudo cp /opt/akkoma/installation/nginx/akkoma.nginx /etc/nginx/conf.d/akkoma.con
|
||||||
sudo systemctl enable --now nginx.service
|
sudo systemctl enable --now nginx.service
|
||||||
```
|
```
|
||||||
|
|
||||||
If you need to renew the certificate in the future, uncomment the relevant location block in the nginx config and run:
|
* Setup your SSL cert, using your method of choice or certbot. If using certbot, first install it:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --webroot -w /var/lib/letsencrypt/
|
sudo dnf install certbot python3-certbot-nginx
|
||||||
```
|
```
|
||||||
|
|
||||||
|
and then set it up:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo certbot --email <your@emailaddress> -d <yourdomain> -d <media_domain> --nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
If that doesn't work the first time, add `--dry-run` to further attempts to avoid being ratelimited as you identify the issue, and do not remove it until the dry run succeeds. A common source of problems are nginx config syntax errors; this can be checked for by running `nginx -t`.
|
||||||
|
|
||||||
|
Certificate renewal should be handled automatically by Certbot from now on.
|
||||||
|
|
||||||
|
|
||||||
#### Other webserver/proxies
|
#### Other webserver/proxies
|
||||||
|
|
||||||
You can find example configurations for them in `/opt/akkoma/installation/`.
|
You can find example configurations for them in `/opt/akkoma/installation/`.
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
## Required dependencies
|
## Required dependencies
|
||||||
|
|
||||||
* PostgreSQL 9.6+
|
* PostgreSQL 9.6+
|
||||||
* Elixir 1.14+
|
* Elixir 1.14+ (currently tested up to 1.16)
|
||||||
* Erlang OTP 25+
|
* Erlang OTP 25+ (currently tested up to OTP26)
|
||||||
* git
|
* git
|
||||||
* file / libmagic
|
* file / libmagic
|
||||||
* gcc (clang might also work)
|
* gcc (clang might also work)
|
||||||
|
|
|
@ -201,25 +201,6 @@ Assuming you want to open your newly installed federated social network to, well
|
||||||
include sites-enabled/*;
|
include sites-enabled/*;
|
||||||
```
|
```
|
||||||
|
|
||||||
* Setup your SSL cert, using your method of choice or certbot. If using certbot, install it if you haven't already:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
# emerge --ask app-crypt/certbot app-crypt/certbot-nginx
|
|
||||||
```
|
|
||||||
|
|
||||||
and then set it up:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
# mkdir -p /var/lib/letsencrypt/
|
|
||||||
# certbot certonly --email <your@emailaddress> -d <yourdomain> --standalone
|
|
||||||
```
|
|
||||||
|
|
||||||
If that doesn't work the first time, add `--dry-run` to further attempts to avoid being ratelimited as you identify the issue, and do not remove it until the dry run succeeds. If that doesn’t work, make sure, that nginx is not already running. If it still doesn’t work, try setting up nginx first (change ssl “on” to “off” and try again). Often the answer to issues with certbot is to use the `--nginx` flag once you have nginx up and running.
|
|
||||||
|
|
||||||
If you are using any additional subdomains, such as for a media proxy, you can re-run the same command with the subdomain in question. When it comes time to renew later, you will not need to run multiple times for each domain, one renew will handle it.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
* Copy the example nginx configuration and activate it:
|
* Copy the example nginx configuration and activate it:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
@ -237,9 +218,24 @@ Pay special attention to the line that begins with `ssl_ecdh_curve`. It is stong
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# rc-update add nginx default
|
# rc-update add nginx default
|
||||||
# /etc/init.d/nginx start
|
# rc-service nginx start
|
||||||
```
|
```
|
||||||
|
|
||||||
|
* Setup your SSL cert, using your method of choice or certbot. If using certbot, install it if you haven't already:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# emerge --ask app-crypt/certbot app-crypt/certbot-nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
and then set it up:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# mkdir -p /var/lib/letsencrypt/
|
||||||
|
# certbot --email <your@emailaddress> -d <yourdomain> -d <media_domain> --nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
If that doesn't work the first time, add `--dry-run` to further attempts to avoid being ratelimited as you identify the issue, and do not remove it until the dry run succeeds. A common source of problems are nginx config syntax errors; this can be checked for by running `nginx -t`.
|
||||||
|
|
||||||
If you are using certbot, it is HIGHLY recommend you set up a cron job that renews your certificate, and that you install the suggested `certbot-nginx` plugin. If you don't do these things, you only have yourself to blame when your instance breaks suddenly because you forgot about it.
|
If you are using certbot, it is HIGHLY recommend you set up a cron job that renews your certificate, and that you install the suggested `certbot-nginx` plugin. If you don't do these things, you only have yourself to blame when your instance breaks suddenly because you forgot about it.
|
||||||
|
|
||||||
First, ensure that the command you will be installing into your crontab works.
|
First, ensure that the command you will be installing into your crontab works.
|
||||||
|
|
|
@ -14,7 +14,7 @@ Note: the packages are not required with the current default settings of Akkoma.
|
||||||
`ImageMagick` is a set of tools to create, edit, compose, or convert bitmap images.
|
`ImageMagick` is a set of tools to create, edit, compose, or convert bitmap images.
|
||||||
|
|
||||||
It is required for the following Akkoma features:
|
It is required for the following Akkoma features:
|
||||||
* `Pleroma.Upload.Filters.Mogrify`, `Pleroma.Upload.Filters.Mogrifun` upload filters (related config: `Plaroma.Upload/filters` in `config/config.exs`)
|
* `Pleroma.Upload.Filters.Mogrify`, `Pleroma.Upload.Filters.Mogrifun` upload filters (related config: `Pleroma.Upload/filters` in `config/config.exs`)
|
||||||
* Media preview proxy for still images (related config: `media_preview_proxy/enabled` in `config/config.exs`)
|
* Media preview proxy for still images (related config: `media_preview_proxy/enabled` in `config/config.exs`)
|
||||||
|
|
||||||
## `ffmpeg`
|
## `ffmpeg`
|
||||||
|
@ -29,4 +29,5 @@ It is required for the following Akkoma features:
|
||||||
`exiftool` is media files metadata reader/writer.
|
`exiftool` is media files metadata reader/writer.
|
||||||
|
|
||||||
It is required for the following Akkoma features:
|
It is required for the following Akkoma features:
|
||||||
* `Pleroma.Upload.Filters.Exiftool` upload filter (related config: `Plaroma.Upload/filters` in `config/config.exs`)
|
* `Pleroma.Upload.Filters.Exiftool.StripMetadata` upload filter (related config: `Pleroma.Upload/filters` in `config/config.exs`)
|
||||||
|
* `Pleroma.Upload.Filters.Exiftool.ReadDescription` upload filter (related config: `Pleroma.Upload/filters` in `config/config.exs`)
|
||||||
|
|
|
@ -9,7 +9,7 @@ This guide covers a installation using an OTP release. To install Akkoma from so
|
||||||
* For installing OTP releases on RedHat-based distros like Fedora and Centos Stream, please follow [this guide](./otp_redhat_en.md) instead.
|
* For installing OTP releases on RedHat-based distros like Fedora and Centos Stream, please follow [this guide](./otp_redhat_en.md) instead.
|
||||||
* A (sub)domain pointed to the machine
|
* A (sub)domain pointed to the machine
|
||||||
|
|
||||||
You will be running commands as root. If you aren't root already, please elevate your priviledges by executing `sudo su`/`su`.
|
You will be running commands as root. If you aren't root already, please elevate your priviledges by executing `sudo -i`/`su`.
|
||||||
|
|
||||||
While in theory OTP releases are possbile to install on any compatible machine, for the sake of simplicity this guide focuses only on Debian/Ubuntu and Alpine.
|
While in theory OTP releases are possbile to install on any compatible machine, for the sake of simplicity this guide focuses only on Debian/Ubuntu and Alpine.
|
||||||
|
|
||||||
|
@ -176,11 +176,6 @@ su akkoma -s $SHELL -lc "./bin/pleroma stop"
|
||||||
|
|
||||||
### Setting up nginx and getting Let's Encrypt SSL certificaties
|
### Setting up nginx and getting Let's Encrypt SSL certificaties
|
||||||
|
|
||||||
#### Get a Let's Encrypt certificate
|
|
||||||
```sh
|
|
||||||
certbot certonly --standalone --preferred-challenges http -d yourinstance.tld
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Copy Akkoma nginx configuration to the nginx folder
|
#### Copy Akkoma nginx configuration to the nginx folder
|
||||||
|
|
||||||
The location of nginx configs is dependent on the distro
|
The location of nginx configs is dependent on the distro
|
||||||
|
@ -209,6 +204,14 @@ $EDITOR path-to-nginx-config
|
||||||
# Verify that the config is valid
|
# Verify that the config is valid
|
||||||
nginx -t
|
nginx -t
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Get a Let's Encrypt certificate
|
||||||
|
```sh
|
||||||
|
certbot --nginx -d yourinstance.tld -d media.yourinstance.tld
|
||||||
|
```
|
||||||
|
|
||||||
|
If that doesn't work the first time, add `--dry-run` to further attempts to avoid being ratelimited as you identify the issue, and do not remove it until the dry run succeeds. A common source of problems are nginx config syntax errors; this can be checked for by running `nginx -t`.
|
||||||
|
|
||||||
#### Start nginx
|
#### Start nginx
|
||||||
|
|
||||||
=== "Alpine"
|
=== "Alpine"
|
||||||
|
@ -252,32 +255,19 @@ If everything worked, you should see Akkoma-FE when visiting your domain. If tha
|
||||||
## Post installation
|
## Post installation
|
||||||
|
|
||||||
### Setting up auto-renew of the Let's Encrypt certificate
|
### Setting up auto-renew of the Let's Encrypt certificate
|
||||||
```sh
|
|
||||||
# Create the directory for webroot challenges
|
|
||||||
mkdir -p /var/lib/letsencrypt
|
|
||||||
|
|
||||||
# Uncomment the webroot method
|
|
||||||
$EDITOR path-to-nginx-config
|
|
||||||
|
|
||||||
# Verify that the config is valid
|
|
||||||
nginx -t
|
|
||||||
```
|
|
||||||
|
|
||||||
=== "Alpine"
|
=== "Alpine"
|
||||||
```
|
```
|
||||||
# Restart nginx
|
|
||||||
rc-service nginx restart
|
|
||||||
|
|
||||||
# Start the cron daemon and make it start on boot
|
# Start the cron daemon and make it start on boot
|
||||||
rc-service crond start
|
rc-service crond start
|
||||||
rc-update add crond
|
rc-update add crond
|
||||||
|
|
||||||
# Ensure the webroot menthod and post hook is working
|
# Ensure the webroot menthod and post hook is working
|
||||||
certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --dry-run --post-hook 'rc-service nginx reload'
|
certbot renew --cert-name yourinstance.tld --nginx --dry-run
|
||||||
|
|
||||||
# Add it to the daily cron
|
# Add it to the daily cron
|
||||||
echo '#!/bin/sh
|
echo '#!/bin/sh
|
||||||
certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --post-hook "rc-service nginx reload"
|
certbot renew --cert-name yourinstance.tld --nginx
|
||||||
' > /etc/periodic/daily/renew-akkoma-cert
|
' > /etc/periodic/daily/renew-akkoma-cert
|
||||||
chmod +x /etc/periodic/daily/renew-akkoma-cert
|
chmod +x /etc/periodic/daily/renew-akkoma-cert
|
||||||
|
|
||||||
|
@ -286,22 +276,7 @@ nginx -t
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "Debian/Ubuntu"
|
=== "Debian/Ubuntu"
|
||||||
```
|
This should be automatically enabled with the `certbot-renew.timer` systemd unit.
|
||||||
# Restart nginx
|
|
||||||
systemctl restart nginx
|
|
||||||
|
|
||||||
# Ensure the webroot menthod and post hook is working
|
|
||||||
certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --dry-run --post-hook 'systemctl reload nginx'
|
|
||||||
|
|
||||||
# Add it to the daily cron
|
|
||||||
echo '#!/bin/sh
|
|
||||||
certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --post-hook "systemctl reload nginx"
|
|
||||||
' > /etc/cron.daily/renew-akkoma-cert
|
|
||||||
chmod +x /etc/cron.daily/renew-akkoma-cert
|
|
||||||
|
|
||||||
# If everything worked the output should contain /etc/cron.daily/renew-akkoma-cert
|
|
||||||
run-parts --test /etc/cron.daily
|
|
||||||
```
|
|
||||||
|
|
||||||
## Create your first user and set as admin
|
## Create your first user and set as admin
|
||||||
```sh
|
```sh
|
||||||
|
|
|
@ -82,6 +82,7 @@ Other than things bundled in the OTP release Akkoma depends on:
|
||||||
* PostgreSQL (also utilizes extensions in postgresql-contrib)
|
* PostgreSQL (also utilizes extensions in postgresql-contrib)
|
||||||
* nginx (could be swapped with another reverse proxy but this guide covers only it)
|
* nginx (could be swapped with another reverse proxy but this guide covers only it)
|
||||||
* certbot (for Let's Encrypt certificates, could be swapped with another ACME client, but this guide covers only it)
|
* certbot (for Let's Encrypt certificates, could be swapped with another ACME client, but this guide covers only it)
|
||||||
|
* If you are using certbot, also install the `python3-certbot-nginx` package for the nginx plugin
|
||||||
* libmagic/file
|
* libmagic/file
|
||||||
|
|
||||||
First, update your system, if not already done:
|
First, update your system, if not already done:
|
||||||
|
@ -169,12 +170,6 @@ sudo -Hu akkoma ./bin/pleroma stop
|
||||||
|
|
||||||
### Setting up nginx and getting Let's Encrypt SSL certificaties
|
### Setting up nginx and getting Let's Encrypt SSL certificaties
|
||||||
|
|
||||||
#### Get a Let's Encrypt certificate
|
|
||||||
|
|
||||||
```shell
|
|
||||||
certbot certonly --standalone --preferred-challenges http -d yourinstance.tld
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Copy Akkoma nginx configuration to the nginx folder
|
#### Copy Akkoma nginx configuration to the nginx folder
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
@ -195,8 +190,15 @@ sudo nginx -t
|
||||||
sudo systemctl start nginx
|
sudo systemctl start nginx
|
||||||
```
|
```
|
||||||
|
|
||||||
At this point if you open your (sub)domain in a browser you should see a 502 error, that's because Akkoma is not started yet.
|
#### Get a Let's Encrypt certificate
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo certbot --email <your@emailaddress> -d <yourdomain> -d <media_domain> --nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
If that doesn't work the first time, add `--dry-run` to further attempts to avoid being ratelimited as you identify the issue, and do not remove it until the dry run succeeds. A common source of problems are nginx config syntax errors; this can be checked for by running `nginx -t`.
|
||||||
|
|
||||||
|
If you're successful with obtaining the certificates, opening your (sub)domain in a browser will result in a 502 error, since Akkoma hasn't been started yet.
|
||||||
|
|
||||||
### Setting up a system service
|
### Setting up a system service
|
||||||
|
|
||||||
|
@ -239,19 +241,11 @@ sudo nginx -t
|
||||||
# Restart nginx
|
# Restart nginx
|
||||||
sudo systemctl restart nginx
|
sudo systemctl restart nginx
|
||||||
|
|
||||||
# Ensure the webroot menthod and post hook is working
|
# Test that renewals work properly
|
||||||
sudo certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --dry-run --post-hook 'systemctl reload nginx'
|
sudo certbot renew --cert-name yourinstance.tld --nginx --dry-run
|
||||||
|
|
||||||
# Add it to the daily cron
|
|
||||||
echo '#!/bin/sh
|
|
||||||
certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --post-hook "systemctl reload nginx"
|
|
||||||
' > /etc/cron.daily/renew-akkoma-cert
|
|
||||||
sudo chmod +x /etc/cron.daily/renew-akkoma-cert
|
|
||||||
|
|
||||||
# If everything worked the output should contain /etc/cron.daily/renew-akkoma-cert
|
|
||||||
sudo run-parts --test /etc/cron.daily
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Assuming the commands were run successfully, certbot should be able to renew your certificates automatically via the `certbot-renew.timer` systemd unit.
|
||||||
|
|
||||||
## Create your first user and set as admin
|
## Create your first user and set as admin
|
||||||
```shell
|
```shell
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
elixir_version=1.14.3
|
|
||||||
erlang_version=25.3
|
|
|
@ -1,12 +1,9 @@
|
||||||
# default nginx site config for Akkoma
|
# default nginx site config for Akkoma
|
||||||
#
|
#
|
||||||
# Simple installation instructions:
|
# See the documentation at docs.akkoma.dev for your particular distro/OS for
|
||||||
# 1. Install your TLS certificate, possibly using Let's Encrypt.
|
# installation instructions.
|
||||||
# 2. Replace 'example.tld' with your instance's domain wherever it appears.
|
|
||||||
# 3. Copy this file to /etc/nginx/sites-available/ and then add a symlink to it
|
|
||||||
# in /etc/nginx/sites-enabled/ and run 'nginx -s reload' or restart nginx.
|
|
||||||
|
|
||||||
proxy_cache_path /tmp/akkoma-media-cache levels=1:2 keys_zone=akkoma_media_cache:10m max_size=10g
|
proxy_cache_path /tmp/akkoma-media-cache levels=1:2 keys_zone=akkoma_media_cache:10m max_size=1g
|
||||||
inactive=720m use_temp_path=off;
|
inactive=720m use_temp_path=off;
|
||||||
|
|
||||||
# this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only
|
# this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only
|
||||||
|
@ -15,25 +12,19 @@ upstream phoenix {
|
||||||
server 127.0.0.1:4000 max_fails=5 fail_timeout=60s;
|
server 127.0.0.1:4000 max_fails=5 fail_timeout=60s;
|
||||||
}
|
}
|
||||||
|
|
||||||
server {
|
# If you are setting up TLS certificates without certbot, uncomment the
|
||||||
server_name example.tld;
|
# following to enable HTTP -> HTTPS redirects. Certbot users don't need to do
|
||||||
|
# this as it will automatically do this for you.
|
||||||
listen 80;
|
# server {
|
||||||
listen [::]:80;
|
# server_name example.tld media.example.tld;
|
||||||
|
#
|
||||||
# Uncomment this if you need to use the 'webroot' method with certbot. Make sure
|
# listen 80;
|
||||||
# that the directory exists and that it is accessible by the webserver. If you followed
|
# listen [::]:80;
|
||||||
# the guide, you already ran 'mkdir -p /var/lib/letsencrypt' to create the folder.
|
#
|
||||||
# You may need to load this file with the ssl server block commented out, run certbot
|
# location / {
|
||||||
# to get the certificate, and then uncomment it.
|
# return 301 https://$server_name$request_uri;
|
||||||
#
|
# }
|
||||||
# location ~ /\.well-known/acme-challenge {
|
# }
|
||||||
# root /var/lib/letsencrypt/;
|
|
||||||
# }
|
|
||||||
location / {
|
|
||||||
return 301 https://$server_name$request_uri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Enable SSL session caching for improved performance
|
# Enable SSL session caching for improved performance
|
||||||
ssl_session_cache shared:ssl_session_cache:10m;
|
ssl_session_cache shared:ssl_session_cache:10m;
|
||||||
|
@ -41,22 +32,29 @@ ssl_session_cache shared:ssl_session_cache:10m;
|
||||||
server {
|
server {
|
||||||
server_name example.tld;
|
server_name example.tld;
|
||||||
|
|
||||||
listen 443 ssl http2;
|
# Once certbot is set up, this will automatically be updated to listen to
|
||||||
listen [::]:443 ssl http2;
|
# port 443 with TLS alongside a redirect from plaintext HTTP.
|
||||||
ssl_session_timeout 1d;
|
listen 80;
|
||||||
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
|
listen [::]:80;
|
||||||
ssl_session_tickets off;
|
|
||||||
|
|
||||||
ssl_trusted_certificate /etc/letsencrypt/live/example.tld/chain.pem;
|
# If you are not using Certbot, comment out the above and uncomment/edit the following
|
||||||
ssl_certificate /etc/letsencrypt/live/example.tld/fullchain.pem;
|
# listen 443 ssl http2;
|
||||||
ssl_certificate_key /etc/letsencrypt/live/example.tld/privkey.pem;
|
# listen [::]:443 ssl http2;
|
||||||
|
# ssl_session_timeout 1d;
|
||||||
|
# ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
|
||||||
|
# ssl_session_tickets off;
|
||||||
|
#
|
||||||
|
# ssl_trusted_certificate /etc/letsencrypt/live/example.tld/chain.pem;
|
||||||
|
# ssl_certificate /etc/letsencrypt/live/example.tld/fullchain.pem;
|
||||||
|
# ssl_certificate_key /etc/letsencrypt/live/example.tld/privkey.pem;
|
||||||
|
#
|
||||||
|
# ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
# ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
|
||||||
|
# ssl_prefer_server_ciphers off;
|
||||||
|
# ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1;
|
||||||
|
# ssl_stapling on;
|
||||||
|
# ssl_stapling_verify on;
|
||||||
|
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
|
||||||
ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
|
|
||||||
ssl_prefer_server_ciphers off;
|
|
||||||
ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1;
|
|
||||||
ssl_stapling on;
|
|
||||||
ssl_stapling_verify on;
|
|
||||||
|
|
||||||
gzip_vary on;
|
gzip_vary on;
|
||||||
gzip_proxied any;
|
gzip_proxied any;
|
||||||
|
@ -86,27 +84,22 @@ server {
|
||||||
|
|
||||||
# Upload and MediaProxy Subdomain
|
# Upload and MediaProxy Subdomain
|
||||||
# (see main domain setup for more details)
|
# (see main domain setup for more details)
|
||||||
server {
|
|
||||||
server_name media.example.tld;
|
|
||||||
|
|
||||||
listen 80;
|
|
||||||
listen [::]:80;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
return 301 https://$server_name$request_uri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
server {
|
||||||
server_name media.example.tld;
|
server_name media.example.tld;
|
||||||
|
|
||||||
listen 443 ssl http2;
|
# Same as above, will be updated to HTTPS once certbot is set up.
|
||||||
listen [::]:443 ssl http2;
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
|
||||||
ssl_trusted_certificate /etc/letsencrypt/live/media.example.tld/chain.pem;
|
# If you are not using certbot, comment the above and copy all the ssl
|
||||||
ssl_certificate /etc/letsencrypt/live/media.example.tld/fullchain.pem;
|
# stuff from above into here.
|
||||||
ssl_certificate_key /etc/letsencrypt/live/media.example.tld/privkey.pem;
|
|
||||||
# .. copy all other the ssl_* and gzip_* stuff from main domain
|
gzip_vary on;
|
||||||
|
gzip_proxied any;
|
||||||
|
gzip_comp_level 6;
|
||||||
|
gzip_buffers 16 8k;
|
||||||
|
gzip_http_version 1.1;
|
||||||
|
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript application/activity+json application/atom+xml;
|
||||||
|
|
||||||
# the nginx default is 1m, not enough for large media uploads
|
# the nginx default is 1m, not enough for large media uploads
|
||||||
client_max_body_size 16m;
|
client_max_body_size 16m;
|
||||||
|
|
|
@ -35,7 +35,8 @@ def run(["gen" | rest]) do
|
||||||
static_dir: :string,
|
static_dir: :string,
|
||||||
listen_ip: :string,
|
listen_ip: :string,
|
||||||
listen_port: :string,
|
listen_port: :string,
|
||||||
strip_uploads: :string,
|
strip_uploads_metadata: :string,
|
||||||
|
read_uploads_description: :string,
|
||||||
anonymize_uploads: :string
|
anonymize_uploads: :string
|
||||||
],
|
],
|
||||||
aliases: [
|
aliases: [
|
||||||
|
@ -169,21 +170,38 @@ def run(["gen" | rest]) do
|
||||||
)
|
)
|
||||||
|> Path.expand()
|
|> Path.expand()
|
||||||
|
|
||||||
{strip_uploads_message, strip_uploads_default} =
|
{strip_uploads_metadata_message, strip_uploads_metadata_default} =
|
||||||
if Pleroma.Utils.command_available?("exiftool") do
|
if Pleroma.Utils.command_available?("exiftool") do
|
||||||
{"Do you want to strip location (GPS) data from uploaded images? This requires exiftool, it was detected as installed. (y/n)",
|
{"Do you want to strip metadata from uploaded images? This requires exiftool, it was detected as installed. (y/n)",
|
||||||
"y"}
|
"y"}
|
||||||
else
|
else
|
||||||
{"Do you want to strip location (GPS) data from uploaded images? This requires exiftool, it was detected as not installed, please install it if you answer yes. (y/n)",
|
{"Do you want to strip metadata from uploaded images? This requires exiftool, it was detected as not installed, please install it if you answer yes. (y/n)",
|
||||||
"n"}
|
"n"}
|
||||||
end
|
end
|
||||||
|
|
||||||
strip_uploads =
|
strip_uploads_metadata =
|
||||||
get_option(
|
get_option(
|
||||||
options,
|
options,
|
||||||
:strip_uploads,
|
:strip_uploads_metadata,
|
||||||
strip_uploads_message,
|
strip_uploads_metadata_message,
|
||||||
strip_uploads_default
|
strip_uploads_metadata_default
|
||||||
|
) === "y"
|
||||||
|
|
||||||
|
{read_uploads_description_message, read_uploads_description_default} =
|
||||||
|
if Pleroma.Utils.command_available?("exiftool") do
|
||||||
|
{"Do you want to read data from uploaded files so clients can use it to prefill fields like image description? This requires exiftool, it was detected as installed. (y/n)",
|
||||||
|
"y"}
|
||||||
|
else
|
||||||
|
{"Do you want to read data from uploaded files so clients can use it to prefill fields like image description? This requires exiftool, it was detected as not installed, please install it if you answer yes. (y/n)",
|
||||||
|
"n"}
|
||||||
|
end
|
||||||
|
|
||||||
|
read_uploads_description =
|
||||||
|
get_option(
|
||||||
|
options,
|
||||||
|
:read_uploads_description,
|
||||||
|
read_uploads_description_message,
|
||||||
|
read_uploads_description_default
|
||||||
) === "y"
|
) === "y"
|
||||||
|
|
||||||
anonymize_uploads =
|
anonymize_uploads =
|
||||||
|
@ -230,7 +248,8 @@ def run(["gen" | rest]) do
|
||||||
listen_port: listen_port,
|
listen_port: listen_port,
|
||||||
upload_filters:
|
upload_filters:
|
||||||
upload_filters(%{
|
upload_filters(%{
|
||||||
strip: strip_uploads,
|
strip_metadata: strip_uploads_metadata,
|
||||||
|
read_description: read_uploads_description,
|
||||||
anonymize: anonymize_uploads
|
anonymize: anonymize_uploads
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -305,11 +324,20 @@ defp write_robots_txt(static_dir, indexable, template_dir) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp upload_filters(filters) when is_map(filters) do
|
defp upload_filters(filters) when is_map(filters) do
|
||||||
|
enabled_filters = []
|
||||||
|
|
||||||
enabled_filters =
|
enabled_filters =
|
||||||
if filters.strip do
|
if filters.read_description do
|
||||||
[Pleroma.Upload.Filter.Exiftool]
|
enabled_filters ++ [Pleroma.Upload.Filter.Exiftool.ReadDescription]
|
||||||
else
|
else
|
||||||
[]
|
enabled_filters
|
||||||
|
end
|
||||||
|
|
||||||
|
enabled_filters =
|
||||||
|
if filters.strip_metadata do
|
||||||
|
enabled_filters ++ [Pleroma.Upload.Filter.Exiftool.StripMetadata]
|
||||||
|
else
|
||||||
|
enabled_filters
|
||||||
end
|
end
|
||||||
|
|
||||||
enabled_filters =
|
enabled_filters =
|
||||||
|
|
|
@ -288,6 +288,7 @@ defp http_children do
|
||||||
|> Config.get([])
|
|> Config.get([])
|
||||||
|> Pleroma.HTTP.AdapterHelper.add_pool_size(pool_size)
|
|> Pleroma.HTTP.AdapterHelper.add_pool_size(pool_size)
|
||||||
|> Pleroma.HTTP.AdapterHelper.maybe_add_proxy_pool(proxy)
|
|> Pleroma.HTTP.AdapterHelper.maybe_add_proxy_pool(proxy)
|
||||||
|
|> Pleroma.HTTP.AdapterHelper.ensure_ipv6()
|
||||||
|> Keyword.put(:name, MyFinch)
|
|> Keyword.put(:name, MyFinch)
|
||||||
|
|
||||||
[{Finch, config}]
|
[{Finch, config}]
|
||||||
|
|
|
@ -164,7 +164,8 @@ defp do_check_rum!(setting, migrate) do
|
||||||
|
|
||||||
defp check_system_commands!(:ok) do
|
defp check_system_commands!(:ok) do
|
||||||
filter_commands_statuses = [
|
filter_commands_statuses = [
|
||||||
check_filter(Pleroma.Upload.Filter.Exiftool, "exiftool"),
|
check_filter(Pleroma.Upload.Filter.Exiftool.StripMetadata, "exiftool"),
|
||||||
|
check_filter(Pleroma.Upload.Filter.Exiftool.ReadDescription, "exiftool"),
|
||||||
check_filter(Pleroma.Upload.Filter.Mogrify, "mogrify"),
|
check_filter(Pleroma.Upload.Filter.Mogrify, "mogrify"),
|
||||||
check_filter(Pleroma.Upload.Filter.Mogrifun, "mogrify"),
|
check_filter(Pleroma.Upload.Filter.Mogrifun, "mogrify"),
|
||||||
check_filter(Pleroma.Upload.Filter.AnalyzeMetadata, "mogrify"),
|
check_filter(Pleroma.Upload.Filter.AnalyzeMetadata, "mogrify"),
|
||||||
|
|
|
@ -68,7 +68,10 @@ defp fetch_page_items(id, items \\ []) do
|
||||||
items
|
items
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
{:error, {"Object has been deleted", _, _}} ->
|
{:error, :not_found} ->
|
||||||
|
items
|
||||||
|
|
||||||
|
{:error, :forbidden} ->
|
||||||
items
|
items
|
||||||
|
|
||||||
{:error, error} ->
|
{:error, error} ->
|
||||||
|
|
|
@ -22,6 +22,43 @@ defmodule Pleroma.Config.DeprecationWarnings do
|
||||||
"\n* `config :pleroma, :instance, :quarantined_instances` is now covered by `:pleroma, :mrf_simple, :reject`"}
|
"\n* `config :pleroma, :instance, :quarantined_instances` is now covered by `:pleroma, :mrf_simple, :reject`"}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def check_exiftool_filter do
|
||||||
|
filters = Config.get([Pleroma.Upload]) |> Keyword.get(:filters, [])
|
||||||
|
|
||||||
|
if Pleroma.Upload.Filter.Exiftool in filters do
|
||||||
|
Logger.warning("""
|
||||||
|
!!!DEPRECATION WARNING!!!
|
||||||
|
Your config is using Exiftool as a filter instead of Exiftool.StripMetadata. This should work for now, but you are advised to change to the new configuration to prevent possible issues later:
|
||||||
|
|
||||||
|
```
|
||||||
|
config :pleroma, Pleroma.Upload,
|
||||||
|
filters: [Pleroma.Upload.Filter.Exiftool]
|
||||||
|
```
|
||||||
|
|
||||||
|
Is now
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
config :pleroma, Pleroma.Upload,
|
||||||
|
filters: [Pleroma.Upload.Filter.Exiftool.StripMetadata]
|
||||||
|
```
|
||||||
|
""")
|
||||||
|
|
||||||
|
new_config =
|
||||||
|
filters
|
||||||
|
|> Enum.map(fn
|
||||||
|
Pleroma.Upload.Filter.Exiftool -> Pleroma.Upload.Filter.Exiftool.StripMetadata
|
||||||
|
filter -> filter
|
||||||
|
end)
|
||||||
|
|
||||||
|
Config.put([Pleroma.Upload, :filters], new_config)
|
||||||
|
|
||||||
|
:error
|
||||||
|
else
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def check_simple_policy_tuples do
|
def check_simple_policy_tuples do
|
||||||
has_strings =
|
has_strings =
|
||||||
Config.get([:mrf_simple])
|
Config.get([:mrf_simple])
|
||||||
|
@ -184,7 +221,8 @@ def warn do
|
||||||
check_simple_policy_tuples(),
|
check_simple_policy_tuples(),
|
||||||
check_http_adapter(),
|
check_http_adapter(),
|
||||||
check_uploader_base_url_set(),
|
check_uploader_base_url_set(),
|
||||||
check_uploader_base_url_is_not_base_domain()
|
check_uploader_base_url_is_not_base_domain(),
|
||||||
|
check_exiftool_filter()
|
||||||
]
|
]
|
||||||
|> Enum.reduce(:ok, fn
|
|> Enum.reduce(:ok, fn
|
||||||
:ok, :ok -> :ok
|
:ok, :ok -> :ok
|
||||||
|
|
|
@ -65,6 +65,15 @@ def add_pool_size(opts, pool_size) do
|
||||||
|> put_in([:pools, :default, :size], pool_size)
|
|> put_in([:pools, :default, :size], pool_size)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def ensure_ipv6(opts) do
|
||||||
|
# Default transport opts already enable IPv6, so just ensure they're loaded
|
||||||
|
opts
|
||||||
|
|> maybe_add_pools()
|
||||||
|
|> maybe_add_default_pool()
|
||||||
|
|> maybe_add_conn_opts()
|
||||||
|
|> maybe_add_transport_opts()
|
||||||
|
end
|
||||||
|
|
||||||
defp maybe_add_pools(opts) do
|
defp maybe_add_pools(opts) do
|
||||||
if Keyword.has_key?(opts, :pools) do
|
if Keyword.has_key?(opts, :pools) do
|
||||||
opts
|
opts
|
||||||
|
@ -96,11 +105,15 @@ defp maybe_add_conn_opts(opts) do
|
||||||
defp maybe_add_transport_opts(opts) do
|
defp maybe_add_transport_opts(opts) do
|
||||||
transport_opts = get_in(opts, [:pools, :default, :conn_opts, :transport_opts])
|
transport_opts = get_in(opts, [:pools, :default, :conn_opts, :transport_opts])
|
||||||
|
|
||||||
unless is_nil(transport_opts) do
|
opts =
|
||||||
opts
|
unless is_nil(transport_opts) do
|
||||||
else
|
opts
|
||||||
put_in(opts, [:pools, :default, :conn_opts, :transport_opts], [])
|
else
|
||||||
end
|
put_in(opts, [:pools, :default, :conn_opts, :transport_opts], [])
|
||||||
|
end
|
||||||
|
|
||||||
|
# IPv6 is disabled and IPv4 enabled by default; ensure we can use both
|
||||||
|
put_in(opts, [:pools, :default, :conn_opts, :transport_opts, :inet6], true)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
|
@ -178,7 +178,10 @@ def normalize(ap_id, options) when is_binary(ap_id) do
|
||||||
ap_id
|
ap_id
|
||||||
|
|
||||||
Keyword.get(options, :fetch) ->
|
Keyword.get(options, :fetch) ->
|
||||||
Fetcher.fetch_object_from_id!(ap_id, options)
|
case Fetcher.fetch_object_from_id(ap_id, options) do
|
||||||
|
{:ok, object} -> object
|
||||||
|
_ -> nil
|
||||||
|
end
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
get_cached_by_ap_id(ap_id)
|
get_cached_by_ap_id(ap_id)
|
||||||
|
|
|
@ -122,7 +122,7 @@ def refetch_object(%Object{data: %{"id" => id}} = object) do
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
else
|
else
|
||||||
{:local, true} -> {:ok, object}
|
{:local, true} -> {:ok, object}
|
||||||
{:id, false} -> {:error, "Object id changed on refetch"}
|
{:id, false} -> {:error, :id_mismatch}
|
||||||
e -> {:error, e}
|
e -> {:error, e}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -136,10 +136,13 @@ def refetch_object(%Object{data: %{"id" => id}} = object) do
|
||||||
def fetch_object_from_id(id, options \\ []) do
|
def fetch_object_from_id(id, options \\ []) do
|
||||||
with %URI{} = uri <- URI.parse(id),
|
with %URI{} = uri <- URI.parse(id),
|
||||||
# let's check the URI is even vaguely valid first
|
# let's check the URI is even vaguely valid first
|
||||||
{:scheme, true} <- {:scheme, uri.scheme == "http" or uri.scheme == "https"},
|
{:valid_uri_scheme, true} <-
|
||||||
|
{:valid_uri_scheme, uri.scheme == "http" or uri.scheme == "https"},
|
||||||
# If we have instance restrictions, apply them here to prevent fetching from unwanted instances
|
# If we have instance restrictions, apply them here to prevent fetching from unwanted instances
|
||||||
{:ok, nil} <- Pleroma.Web.ActivityPub.MRF.SimplePolicy.check_reject(uri),
|
{:mrf_reject_check, {:ok, nil}} <-
|
||||||
{:ok, _} <- Pleroma.Web.ActivityPub.MRF.SimplePolicy.check_accept(uri),
|
{:mrf_reject_check, Pleroma.Web.ActivityPub.MRF.SimplePolicy.check_reject(uri)},
|
||||||
|
{:mrf_accept_check, {:ok, _}} <-
|
||||||
|
{:mrf_accept_check, Pleroma.Web.ActivityPub.MRF.SimplePolicy.check_accept(uri)},
|
||||||
{_, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)},
|
{_, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)},
|
||||||
{_, true} <- {:allowed_depth, Federator.allowed_thread_distance?(options[:depth])},
|
{_, true} <- {:allowed_depth, Federator.allowed_thread_distance?(options[:depth])},
|
||||||
{_, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)},
|
{_, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)},
|
||||||
|
@ -151,20 +154,37 @@ def fetch_object_from_id(id, options \\ []) do
|
||||||
{:object, data, Object.normalize(activity, fetch: false)} do
|
{:object, data, Object.normalize(activity, fetch: false)} do
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
else
|
else
|
||||||
{:allowed_depth, false} ->
|
{:allowed_depth, false} = e ->
|
||||||
{:error, "Max thread distance exceeded."}
|
log_fetch_error(id, e)
|
||||||
|
{:error, :allowed_depth}
|
||||||
|
|
||||||
{:scheme, false} ->
|
{:valid_uri_scheme, _} = e ->
|
||||||
{:error, "URI Scheme Invalid"}
|
log_fetch_error(id, e)
|
||||||
|
{:error, :invalid_uri_scheme}
|
||||||
|
|
||||||
{:transmogrifier, {:error, {:reject, e}}} ->
|
{:mrf_reject_check, _} = e ->
|
||||||
{:reject, e}
|
log_fetch_error(id, e)
|
||||||
|
{:reject, :mrf}
|
||||||
|
|
||||||
{:transmogrifier, {:reject, e}} ->
|
{:mrf_accept_check, _} = e ->
|
||||||
{:reject, e}
|
log_fetch_error(id, e)
|
||||||
|
{:reject, :mrf}
|
||||||
|
|
||||||
{:transmogrifier, _} = e ->
|
{:containment, reason} = e ->
|
||||||
{:error, e}
|
log_fetch_error(id, e)
|
||||||
|
{:error, reason}
|
||||||
|
|
||||||
|
{:transmogrifier, {:error, {:reject, reason}}} = e ->
|
||||||
|
log_fetch_error(id, e)
|
||||||
|
{:reject, reason}
|
||||||
|
|
||||||
|
{:transmogrifier, {:reject, reason}} = e ->
|
||||||
|
log_fetch_error(id, e)
|
||||||
|
{:reject, reason}
|
||||||
|
|
||||||
|
{:transmogrifier, reason} = e ->
|
||||||
|
log_fetch_error(id, e)
|
||||||
|
{:error, reason}
|
||||||
|
|
||||||
{:object, data, nil} ->
|
{:object, data, nil} ->
|
||||||
reinject_object(%Object{}, data)
|
reinject_object(%Object{}, data)
|
||||||
|
@ -175,17 +195,21 @@ def fetch_object_from_id(id, options \\ []) do
|
||||||
{:fetch_object, %Object{} = object} ->
|
{:fetch_object, %Object{} = object} ->
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
|
|
||||||
{:fetch, {:error, error}} ->
|
{:fetch, {:error, reason}} = e ->
|
||||||
{:error, error}
|
log_fetch_error(id, e)
|
||||||
|
{:error, reason}
|
||||||
{:reject, reason} ->
|
|
||||||
{:reject, reason}
|
|
||||||
|
|
||||||
e ->
|
e ->
|
||||||
e
|
log_fetch_error(id, e)
|
||||||
|
{:error, e}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp log_fetch_error(id, error) do
|
||||||
|
Logger.metadata(object: id)
|
||||||
|
Logger.error("Object rejected while fetching #{id} #{inspect(error)}")
|
||||||
|
end
|
||||||
|
|
||||||
defp prepare_activity_params(data) do
|
defp prepare_activity_params(data) do
|
||||||
%{
|
%{
|
||||||
"type" => "Create",
|
"type" => "Create",
|
||||||
|
@ -199,27 +223,6 @@ defp prepare_activity_params(data) do
|
||||||
|> Maps.put_if_present("bcc", data["bcc"])
|
|> Maps.put_if_present("bcc", data["bcc"])
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "Identical to `fetch_object_from_id/2` but just directly returns the object or on error `nil`"
|
|
||||||
def fetch_object_from_id!(id, options \\ []) do
|
|
||||||
with {:ok, object} <- fetch_object_from_id(id, options) do
|
|
||||||
object
|
|
||||||
else
|
|
||||||
{:error, %Tesla.Mock.Error{}} ->
|
|
||||||
nil
|
|
||||||
|
|
||||||
{:error, {"Object has been deleted", _id, _code}} ->
|
|
||||||
nil
|
|
||||||
|
|
||||||
{:reject, reason} ->
|
|
||||||
Logger.debug("Rejected #{id} while fetching: #{inspect(reason)}")
|
|
||||||
nil
|
|
||||||
|
|
||||||
e ->
|
|
||||||
Logger.error("Error while fetching #{id}: #{inspect(e)}")
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp make_signature(id, date) do
|
defp make_signature(id, date) do
|
||||||
uri = URI.parse(id)
|
uri = URI.parse(id)
|
||||||
|
|
||||||
|
@ -259,8 +262,13 @@ def fetch_and_contain_remote_object_from_id(%{"id" => id}),
|
||||||
def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
|
def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
|
||||||
Logger.debug("Fetching object #{id} via AP")
|
Logger.debug("Fetching object #{id} via AP")
|
||||||
|
|
||||||
with {:scheme, true} <- {:scheme, String.starts_with?(id, "http")},
|
with {:valid_uri_scheme, true} <- {:valid_uri_scheme, String.starts_with?(id, "http")},
|
||||||
{_, :ok} <- {:local_fetch, Containment.contain_local_fetch(id)},
|
%URI{} = uri <- URI.parse(id),
|
||||||
|
{:mrf_reject_check, {:ok, nil}} <-
|
||||||
|
{:mrf_reject_check, Pleroma.Web.ActivityPub.MRF.SimplePolicy.check_reject(uri)},
|
||||||
|
{:mrf_accept_check, {:ok, _}} <-
|
||||||
|
{:mrf_accept_check, Pleroma.Web.ActivityPub.MRF.SimplePolicy.check_accept(uri)},
|
||||||
|
{:local_fetch, :ok} <- {:local_fetch, Containment.contain_local_fetch(id)},
|
||||||
{:ok, final_id, body} <- get_object(id),
|
{:ok, final_id, body} <- get_object(id),
|
||||||
{:ok, data} <- safe_json_decode(body),
|
{:ok, data} <- safe_json_decode(body),
|
||||||
{_, :ok} <- {:strict_id, Containment.contain_id_to_fetch(final_id, data)},
|
{_, :ok} <- {:strict_id, Containment.contain_id_to_fetch(final_id, data)},
|
||||||
|
@ -271,17 +279,29 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
|
||||||
|
|
||||||
{:ok, data}
|
{:ok, data}
|
||||||
else
|
else
|
||||||
{:strict_id, _} ->
|
{:strict_id, _} = e ->
|
||||||
{:error, "Object's ActivityPub id/url does not match final fetch URL"}
|
log_fetch_error(id, e)
|
||||||
|
{:error, :id_mismatch}
|
||||||
|
|
||||||
{:scheme, _} ->
|
{:mrf_reject_check, _} = e ->
|
||||||
{:error, "Unsupported URI scheme"}
|
log_fetch_error(id, e)
|
||||||
|
{:reject, :mrf}
|
||||||
|
|
||||||
{:local_fetch, _} ->
|
{:mrf_accept_check, _} = e ->
|
||||||
{:error, "Trying to fetch local resource"}
|
log_fetch_error(id, e)
|
||||||
|
{:reject, :mrf}
|
||||||
|
|
||||||
{:containment, _} ->
|
{:valid_uri_scheme, _} = e ->
|
||||||
{:error, "Object containment failed."}
|
log_fetch_error(id, e)
|
||||||
|
{:error, :invalid_uri_scheme}
|
||||||
|
|
||||||
|
{:local_fetch, _} = e ->
|
||||||
|
log_fetch_error(id, e)
|
||||||
|
{:error, :local_resource}
|
||||||
|
|
||||||
|
{:containment, reason} ->
|
||||||
|
log_fetch_error(id, reason)
|
||||||
|
{:error, reason}
|
||||||
|
|
||||||
{:error, e} ->
|
{:error, e} ->
|
||||||
{:error, e}
|
{:error, e}
|
||||||
|
@ -292,7 +312,7 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_and_contain_remote_object_from_id(_id),
|
def fetch_and_contain_remote_object_from_id(_id),
|
||||||
do: {:error, "id must be a string"}
|
do: {:error, :invalid_id}
|
||||||
|
|
||||||
defp check_crossdomain_redirect(final_host, original_url)
|
defp check_crossdomain_redirect(final_host, original_url)
|
||||||
|
|
||||||
|
@ -324,7 +344,11 @@ def get_object(id) do
|
||||||
date = Pleroma.Signature.signed_date()
|
date = Pleroma.Signature.signed_date()
|
||||||
|
|
||||||
headers =
|
headers =
|
||||||
[{"accept", "application/activity+json"}]
|
[
|
||||||
|
# The first is required by spec, the second provided as a fallback for buggy implementations
|
||||||
|
{"accept", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""},
|
||||||
|
{"accept", "application/activity+json"}
|
||||||
|
]
|
||||||
|> maybe_date_fetch(date)
|
|> maybe_date_fetch(date)
|
||||||
|> sign_fetch(id, date)
|
|> sign_fetch(id, date)
|
||||||
|
|
||||||
|
@ -352,8 +376,11 @@ def get_object(id) do
|
||||||
{:error, {:content_type, content_type}}
|
{:error, {:content_type, content_type}}
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
{:ok, %{status: code}} when code in [401, 403] ->
|
||||||
|
{:error, :forbidden}
|
||||||
|
|
||||||
{:ok, %{status: code}} when code in [404, 410] ->
|
{:ok, %{status: code}} when code in [404, 410] ->
|
||||||
{:error, {"Object has been deleted", id, code}}
|
{:error, :not_found}
|
||||||
|
|
||||||
{:error, e} ->
|
{:error, e} ->
|
||||||
{:error, e}
|
{:error, e}
|
||||||
|
|
|
@ -39,8 +39,6 @@ defmodule Pleroma.Upload do
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@mix_env Mix.env()
|
|
||||||
|
|
||||||
@type source ::
|
@type source ::
|
||||||
Plug.Upload.t()
|
Plug.Upload.t()
|
||||||
| (data_uri_string :: String.t())
|
| (data_uri_string :: String.t())
|
||||||
|
@ -63,12 +61,23 @@ defmodule Pleroma.Upload do
|
||||||
width: integer(),
|
width: integer(),
|
||||||
height: integer(),
|
height: integer(),
|
||||||
blurhash: String.t(),
|
blurhash: String.t(),
|
||||||
|
description: String.t(),
|
||||||
path: String.t()
|
path: String.t()
|
||||||
}
|
}
|
||||||
|
|
||||||
@always_enabled_filters [Pleroma.Upload.Filter.Dedupe]
|
@always_enabled_filters [Pleroma.Upload.Filter.Dedupe]
|
||||||
|
|
||||||
defstruct [:id, :name, :tempfile, :content_type, :width, :height, :blurhash, :path]
|
defstruct [
|
||||||
|
:id,
|
||||||
|
:name,
|
||||||
|
:tempfile,
|
||||||
|
:content_type,
|
||||||
|
:width,
|
||||||
|
:height,
|
||||||
|
:blurhash,
|
||||||
|
:description,
|
||||||
|
:path
|
||||||
|
]
|
||||||
|
|
||||||
@spec store(source, options :: [option()]) :: {:ok, Map.t()} | {:error, any()}
|
@spec store(source, options :: [option()]) :: {:ok, Map.t()} | {:error, any()}
|
||||||
@doc "Store a file. If using a `Plug.Upload{}` as the source, be sure to use `Majic.Plug` to ensure its content_type and filename is correct."
|
@doc "Store a file. If using a `Plug.Upload{}` as the source, be sure to use `Majic.Plug` to ensure its content_type and filename is correct."
|
||||||
|
@ -78,7 +87,7 @@ def store(upload, opts \\ []) do
|
||||||
with {:ok, upload} <- prepare_upload(upload, opts),
|
with {:ok, upload} <- prepare_upload(upload, opts),
|
||||||
upload = %__MODULE__{upload | path: upload.path || "#{upload.id}/#{upload.name}"},
|
upload = %__MODULE__{upload | path: upload.path || "#{upload.id}/#{upload.name}"},
|
||||||
{:ok, upload} <- Pleroma.Upload.Filter.filter(opts.filters, upload),
|
{:ok, upload} <- Pleroma.Upload.Filter.filter(opts.filters, upload),
|
||||||
description = Map.get(opts, :description) || "",
|
description = Map.get(upload, :description) || "",
|
||||||
{_, true} <-
|
{_, true} <-
|
||||||
{:description_limit,
|
{:description_limit,
|
||||||
String.length(description) <= Pleroma.Config.get([:instance, :description_limit])},
|
String.length(description) <= Pleroma.Config.get([:instance, :description_limit])},
|
||||||
|
@ -154,7 +163,8 @@ defp prepare_upload(%Plug.Upload{} = file, opts) do
|
||||||
id: UUID.generate(),
|
id: UUID.generate(),
|
||||||
name: file.filename,
|
name: file.filename,
|
||||||
tempfile: file.path,
|
tempfile: file.path,
|
||||||
content_type: file.content_type
|
content_type: file.content_type,
|
||||||
|
description: opts.description
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -174,7 +184,8 @@ defp prepare_upload(%{img: "data:image/" <> image_data}, opts) do
|
||||||
id: UUID.generate(),
|
id: UUID.generate(),
|
||||||
name: hash <> "." <> ext,
|
name: hash <> "." <> ext,
|
||||||
tempfile: tmp_path,
|
tempfile: tmp_path,
|
||||||
content_type: content_type
|
content_type: content_type,
|
||||||
|
description: opts.description
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -230,13 +241,6 @@ defp url_from_spec(%__MODULE__{name: name}, base_url, {:file, path}) do
|
||||||
|
|
||||||
defp url_from_spec(_upload, _base_url, {:url, url}), do: url
|
defp url_from_spec(_upload, _base_url, {:url, url}), do: url
|
||||||
|
|
||||||
if @mix_env == :test do
|
|
||||||
defp choose_base_url(prim, sec \\ nil),
|
|
||||||
do: prim || sec || Pleroma.Web.Endpoint.url() <> "/media/"
|
|
||||||
else
|
|
||||||
defp choose_base_url(prim, sec \\ nil), do: prim || sec
|
|
||||||
end
|
|
||||||
|
|
||||||
def base_url do
|
def base_url do
|
||||||
uploader = Config.get([Pleroma.Upload, :uploader])
|
uploader = Config.get([Pleroma.Upload, :uploader])
|
||||||
upload_base_url = Config.get([Pleroma.Upload, :base_url])
|
upload_base_url = Config.get([Pleroma.Upload, :base_url])
|
||||||
|
@ -244,7 +248,7 @@ def base_url do
|
||||||
|
|
||||||
case uploader do
|
case uploader do
|
||||||
Pleroma.Uploaders.Local ->
|
Pleroma.Uploaders.Local ->
|
||||||
choose_base_url(upload_base_url)
|
upload_base_url
|
||||||
|
|
||||||
Pleroma.Uploaders.S3 ->
|
Pleroma.Uploaders.S3 ->
|
||||||
bucket = Config.get([Pleroma.Uploaders.S3, :bucket])
|
bucket = Config.get([Pleroma.Uploaders.S3, :bucket])
|
||||||
|
@ -270,7 +274,7 @@ def base_url do
|
||||||
end
|
end
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
choose_base_url(public_endpoint, upload_base_url)
|
public_endpoint || upload_base_url
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
52
lib/pleroma/upload/filter/exiftool/read_description.ex
Normal file
52
lib/pleroma/upload/filter/exiftool/read_description.ex
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Upload.Filter.Exiftool.ReadDescription do
|
||||||
|
@moduledoc """
|
||||||
|
Gets a valid description from the related EXIF tags and provides them in the response if no description is provided yet.
|
||||||
|
It will first check ImageDescription, when that doesn't probide a valid description, it will check iptc:Caption-Abstract.
|
||||||
|
A valid description means the fields are filled in and not too long (see `:instance, :description_limit`).
|
||||||
|
"""
|
||||||
|
@behaviour Pleroma.Upload.Filter
|
||||||
|
|
||||||
|
@spec filter(Pleroma.Upload.t()) :: {:ok, any()} | {:error, String.t()}
|
||||||
|
|
||||||
|
def filter(%Pleroma.Upload{description: description})
|
||||||
|
when is_binary(description),
|
||||||
|
do: {:ok, :noop}
|
||||||
|
|
||||||
|
def filter(%Pleroma.Upload{tempfile: file} = upload),
|
||||||
|
do: {:ok, :filtered, upload |> Map.put(:description, read_description_from_exif_data(file))}
|
||||||
|
|
||||||
|
def filter(_, _), do: {:ok, :noop}
|
||||||
|
|
||||||
|
defp read_description_from_exif_data(file) do
|
||||||
|
nil
|
||||||
|
|> read_when_empty(file, "-ImageDescription")
|
||||||
|
|> read_when_empty(file, "-iptc:Caption-Abstract")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp read_when_empty(current_description, _, _) when is_binary(current_description),
|
||||||
|
do: current_description
|
||||||
|
|
||||||
|
defp read_when_empty(_, file, tag) do
|
||||||
|
try do
|
||||||
|
{tag_content, 0} =
|
||||||
|
System.cmd("exiftool", ["-b", "-s3", tag, file],
|
||||||
|
stderr_to_stdout: true,
|
||||||
|
parallelism: true
|
||||||
|
)
|
||||||
|
|
||||||
|
tag_content = String.trim(tag_content)
|
||||||
|
|
||||||
|
if tag_content != "" and
|
||||||
|
String.length(tag_content) <=
|
||||||
|
Pleroma.Config.get([:instance, :description_limit]),
|
||||||
|
do: tag_content,
|
||||||
|
else: nil
|
||||||
|
rescue
|
||||||
|
_ in ErlangError -> nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -2,24 +2,42 @@
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Upload.Filter.Exiftool do
|
defmodule Pleroma.Upload.Filter.Exiftool.StripMetadata do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Strips GPS related EXIF tags and overwrites the file in place.
|
Tries to strip all image metadata but colorspace and orientation overwriting the file in place.
|
||||||
Also strips or replaces filesystem metadata e.g., timestamps.
|
Also strips or replaces filesystem metadata e.g., timestamps.
|
||||||
"""
|
"""
|
||||||
@behaviour Pleroma.Upload.Filter
|
@behaviour Pleroma.Upload.Filter
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
|
||||||
|
@purge_default ["all", "CommonIFD0"]
|
||||||
|
@preserve_default ["ColorSpaceTags", "Orientation"]
|
||||||
|
|
||||||
@spec filter(Pleroma.Upload.t()) :: {:ok, :noop} | {:ok, :filtered} | {:error, String.t()}
|
@spec filter(Pleroma.Upload.t()) :: {:ok, :noop} | {:ok, :filtered} | {:error, String.t()}
|
||||||
|
|
||||||
# Formats not compatible with exiftool at this time
|
# Formats not compatible with exiftool at this time
|
||||||
def filter(%Pleroma.Upload{content_type: "image/heic"}), do: {:ok, :noop}
|
|
||||||
def filter(%Pleroma.Upload{content_type: "image/webp"}), do: {:ok, :noop}
|
def filter(%Pleroma.Upload{content_type: "image/webp"}), do: {:ok, :noop}
|
||||||
def filter(%Pleroma.Upload{content_type: "image/svg+xml"}), do: {:ok, :noop}
|
def filter(%Pleroma.Upload{content_type: "image/svg+xml"}), do: {:ok, :noop}
|
||||||
def filter(%Pleroma.Upload{content_type: "image/jxl"}), do: {:ok, :noop}
|
|
||||||
|
|
||||||
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
|
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
|
||||||
|
purge_args =
|
||||||
|
Config.get([__MODULE__, :purge], @purge_default)
|
||||||
|
|> Enum.map(fn mgroup -> "-" <> mgroup <> "=" end)
|
||||||
|
|
||||||
|
preserve_args =
|
||||||
|
Config.get([__MODULE__, :preserve], @preserve_default)
|
||||||
|
|> Enum.map(fn mgroup -> "-" <> mgroup end)
|
||||||
|
|> then(fn
|
||||||
|
# If -TagsFromFile is not followed by tag selectors, it will copy most available tags
|
||||||
|
[] -> []
|
||||||
|
args -> ["-TagsFromFile", "@" | args]
|
||||||
|
end)
|
||||||
|
|
||||||
|
args = ["-ignoreMinorErrors", "-overwrite_original" | purge_args] ++ preserve_args ++ [file]
|
||||||
|
|
||||||
try do
|
try do
|
||||||
case System.cmd("exiftool", ["-overwrite_original", "-gps:all=", file], parallelism: true) do
|
case System.cmd("exiftool", args, parallelism: true) do
|
||||||
{_response, 0} -> {:ok, :filtered}
|
{_response, 0} -> {:ok, :filtered}
|
||||||
{error, 1} -> {:error, error}
|
{error, 1} -> {:error, error}
|
||||||
end
|
end
|
|
@ -969,15 +969,16 @@ defp maybe_send_registration_email(%User{email: email} = user) when is_binary(em
|
||||||
|
|
||||||
defp maybe_send_registration_email(_), do: {:ok, :noop}
|
defp maybe_send_registration_email(_), do: {:ok, :noop}
|
||||||
|
|
||||||
def needs_update?(%User{local: true}), do: false
|
def needs_update?(user, options \\ [])
|
||||||
|
def needs_update?(%User{local: true}, _options), do: false
|
||||||
|
def needs_update?(%User{local: false, last_refreshed_at: nil}, _options), do: true
|
||||||
|
|
||||||
def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
|
def needs_update?(%User{local: false} = user, options) do
|
||||||
|
NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >=
|
||||||
def needs_update?(%User{local: false} = user) do
|
Keyword.get(options, :maximum_age, 86_400)
|
||||||
NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def needs_update?(_), do: true
|
def needs_update?(_, _options), do: true
|
||||||
|
|
||||||
# "Locked" (self-locked) users demand explicit authorization of follow requests
|
# "Locked" (self-locked) users demand explicit authorization of follow requests
|
||||||
@spec can_direct_follow_local(User.t(), User.t()) :: true | false
|
@spec can_direct_follow_local(User.t(), User.t()) :: true | false
|
||||||
|
@ -1980,10 +1981,10 @@ def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])
|
||||||
|
|
||||||
def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
|
def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
|
||||||
|
|
||||||
def get_or_fetch_by_ap_id(ap_id) do
|
def get_or_fetch_by_ap_id(ap_id, options \\ []) do
|
||||||
cached_user = get_cached_by_ap_id(ap_id)
|
cached_user = get_cached_by_ap_id(ap_id)
|
||||||
|
|
||||||
maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id)
|
maybe_fetched_user = needs_update?(cached_user, options) && fetch_by_ap_id(ap_id)
|
||||||
|
|
||||||
case {cached_user, maybe_fetched_user} do
|
case {cached_user, maybe_fetched_user} do
|
||||||
{_, {:ok, %User{} = user}} ->
|
{_, {:ok, %User{} = user}} ->
|
||||||
|
|
|
@ -1705,9 +1705,7 @@ defp collection_private(%{"first" => first}) do
|
||||||
Fetcher.fetch_and_contain_remote_object_from_id(first) do
|
Fetcher.fetch_and_contain_remote_object_from_id(first) do
|
||||||
{:ok, false}
|
{:ok, false}
|
||||||
else
|
else
|
||||||
{:error, {:ok, %{status: code}}} when code in [401, 403] -> {:ok, true}
|
{:error, _} -> {:ok, true}
|
||||||
{:error, _} = e -> e
|
|
||||||
e -> {:error, e}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1732,7 +1730,7 @@ def fetch_and_prepare_user_from_ap_id(ap_id, additional \\ []) do
|
||||||
Logger.debug("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
|
Logger.debug("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
|
||||||
{:error, e}
|
{:error, e}
|
||||||
|
|
||||||
{:error, {:reject, reason} = e} ->
|
{:reject, reason} = e ->
|
||||||
Logger.debug("Rejected user #{ap_id}: #{inspect(reason)}")
|
Logger.debug("Rejected user #{ap_id}: #{inspect(reason)}")
|
||||||
{:error, e}
|
{:error, e}
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.InternalFetchActor
|
alias Pleroma.Web.ActivityPub.InternalFetchActor
|
||||||
alias Pleroma.Web.ActivityPub.ObjectView
|
alias Pleroma.Web.ActivityPub.ObjectView
|
||||||
alias Pleroma.Web.ActivityPub.Pipeline
|
|
||||||
alias Pleroma.Web.ActivityPub.Relay
|
alias Pleroma.Web.ActivityPub.Relay
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
|
||||||
alias Pleroma.Web.ActivityPub.UserView
|
alias Pleroma.Web.ActivityPub.UserView
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
|
@ -40,11 +38,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
# Note: :following and :followers must be served even without authentication (as via :api)
|
# Note: :following and :followers must be served even without authentication (as via :api)
|
||||||
plug(
|
plug(
|
||||||
EnsureAuthenticatedPlug
|
EnsureAuthenticatedPlug
|
||||||
when action in [:read_inbox, :update_outbox, :whoami, :upload_media]
|
when action in [:read_inbox]
|
||||||
)
|
)
|
||||||
|
|
||||||
plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:upload_media])
|
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
Pleroma.Web.Plugs.Cache,
|
Pleroma.Web.Plugs.Cache,
|
||||||
[query_params: false, tracking_fun: &__MODULE__.track_object_fetch/2]
|
[query_params: false, tracking_fun: &__MODULE__.track_object_fetch/2]
|
||||||
|
@ -160,7 +156,9 @@ def maybe_skip_cache(conn, user) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# GET /relay/following
|
@doc """
|
||||||
|
GET /relay/following
|
||||||
|
"""
|
||||||
def relay_following(conn, _params) do
|
def relay_following(conn, _params) do
|
||||||
with %{halted: false} = conn <- FederatingPlug.call(conn, []) do
|
with %{halted: false} = conn <- FederatingPlug.call(conn, []) do
|
||||||
conn
|
conn
|
||||||
|
@ -197,7 +195,9 @@ def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) d
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# GET /relay/followers
|
@doc """
|
||||||
|
GET /relay/followers
|
||||||
|
"""
|
||||||
def relay_followers(conn, _params) do
|
def relay_followers(conn, _params) do
|
||||||
with %{halted: false} = conn <- FederatingPlug.call(conn, []) do
|
with %{halted: false} = conn <- FederatingPlug.call(conn, []) do
|
||||||
conn
|
conn
|
||||||
|
@ -317,14 +317,6 @@ def internal_fetch(conn, _params) do
|
||||||
|> represent_service_actor(conn)
|
|> represent_service_actor(conn)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "Returns the authenticated user's ActivityPub User object or a 404 Not Found if non-authenticated"
|
|
||||||
def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
|
|
||||||
conn
|
|
||||||
|> put_resp_content_type("application/activity+json")
|
|
||||||
|> put_view(UserView)
|
|
||||||
|> render("user.json", %{user: user})
|
|
||||||
end
|
|
||||||
|
|
||||||
def read_inbox(
|
def read_inbox(
|
||||||
%{assigns: %{user: %User{nickname: nickname} = user}} = conn,
|
%{assigns: %{user: %User{nickname: nickname} = user}} = conn,
|
||||||
%{"nickname" => nickname, "page" => page?} = params
|
%{"nickname" => nickname, "page" => page?} = params
|
||||||
|
@ -375,105 +367,6 @@ def read_inbox(%{assigns: %{user: %User{nickname: as_nickname}}} = conn, %{
|
||||||
|> json(err)
|
|> json(err)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp fix_user_message(%User{ap_id: actor}, %{"type" => "Create", "object" => object} = activity)
|
|
||||||
when is_map(object) do
|
|
||||||
length =
|
|
||||||
[object["content"], object["summary"], object["name"]]
|
|
||||||
|> Enum.filter(&is_binary(&1))
|
|
||||||
|> Enum.join("")
|
|
||||||
|> String.length()
|
|
||||||
|
|
||||||
limit = Pleroma.Config.get([:instance, :limit])
|
|
||||||
|
|
||||||
if length < limit do
|
|
||||||
object =
|
|
||||||
object
|
|
||||||
|> Transmogrifier.strip_internal_fields()
|
|
||||||
|> Map.put("attributedTo", actor)
|
|
||||||
|> Map.put("actor", actor)
|
|
||||||
|> Map.put("id", Utils.generate_object_id())
|
|
||||||
|
|
||||||
{:ok, Map.put(activity, "object", object)}
|
|
||||||
else
|
|
||||||
{:error,
|
|
||||||
dgettext(
|
|
||||||
"errors",
|
|
||||||
"Character limit (%{limit} characters) exceeded, contains %{length} characters",
|
|
||||||
limit: limit,
|
|
||||||
length: length
|
|
||||||
)}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp fix_user_message(
|
|
||||||
%User{ap_id: actor} = user,
|
|
||||||
%{"type" => "Delete", "object" => object} = activity
|
|
||||||
) do
|
|
||||||
with {_, %Object{data: object_data}} <- {:normalize, Object.normalize(object, fetch: false)},
|
|
||||||
{_, true} <- {:permission, user.is_moderator || actor == object_data["actor"]} do
|
|
||||||
{:ok, activity}
|
|
||||||
else
|
|
||||||
{:normalize, _} ->
|
|
||||||
{:error, "No such object found"}
|
|
||||||
|
|
||||||
{:permission, _} ->
|
|
||||||
{:forbidden, "You can't delete this object"}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp fix_user_message(%User{}, activity) do
|
|
||||||
{:ok, activity}
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_outbox(
|
|
||||||
%{assigns: %{user: %User{nickname: nickname, ap_id: actor} = user}} = conn,
|
|
||||||
%{"nickname" => nickname} = params
|
|
||||||
) do
|
|
||||||
params =
|
|
||||||
params
|
|
||||||
|> Map.drop(["nickname"])
|
|
||||||
|> Map.put("id", Utils.generate_activity_id())
|
|
||||||
|> Map.put("actor", actor)
|
|
||||||
|
|
||||||
with {:ok, params} <- fix_user_message(user, params),
|
|
||||||
{:ok, activity, _} <- Pipeline.common_pipeline(params, local: true),
|
|
||||||
%Activity{data: activity_data} <- Activity.normalize(activity) do
|
|
||||||
conn
|
|
||||||
|> put_status(:created)
|
|
||||||
|> put_resp_header("location", activity_data["id"])
|
|
||||||
|> json(activity_data)
|
|
||||||
else
|
|
||||||
{:forbidden, message} ->
|
|
||||||
conn
|
|
||||||
|> put_status(:forbidden)
|
|
||||||
|> json(message)
|
|
||||||
|
|
||||||
{:error, message} ->
|
|
||||||
conn
|
|
||||||
|> put_status(:bad_request)
|
|
||||||
|> json(message)
|
|
||||||
|
|
||||||
e ->
|
|
||||||
Logger.warning(fn -> "AP C2S: #{inspect(e)}" end)
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> put_status(:bad_request)
|
|
||||||
|> json("Bad Request")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_outbox(%{assigns: %{user: %User{} = user}} = conn, %{"nickname" => nickname}) do
|
|
||||||
err =
|
|
||||||
dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
|
|
||||||
nickname: nickname,
|
|
||||||
as_nickname: user.nickname
|
|
||||||
)
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> put_status(:forbidden)
|
|
||||||
|> json(err)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp errors(conn, {:error, :not_found}) do
|
defp errors(conn, {:error, :not_found}) do
|
||||||
conn
|
conn
|
||||||
|> put_status(:not_found)
|
|> put_status(:not_found)
|
||||||
|
@ -495,21 +388,6 @@ defp set_requester_reachable(%Plug.Conn{} = conn, _) do
|
||||||
conn
|
conn
|
||||||
end
|
end
|
||||||
|
|
||||||
def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do
|
|
||||||
with {:ok, object} <-
|
|
||||||
ActivityPub.upload(
|
|
||||||
file,
|
|
||||||
actor: User.ap_id(user),
|
|
||||||
description: Map.get(data, "description")
|
|
||||||
) do
|
|
||||||
Logger.debug(inspect(object))
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> put_status(:created)
|
|
||||||
|> json(object.data)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def pinned(conn, %{"nickname" => nickname}) do
|
def pinned(conn, %{"nickname" => nickname}) do
|
||||||
with %User{} = user <- User.get_cached_by_nickname(nickname) do
|
with %User{} = user <- User.get_cached_by_nickname(nickname) do
|
||||||
conn
|
conn
|
||||||
|
|
|
@ -6,14 +6,29 @@ defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy do
|
||||||
@moduledoc "Force a quote line into the message content."
|
@moduledoc "Force a quote line into the message content."
|
||||||
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
|
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
|
||||||
|
|
||||||
|
alias Pleroma.Object
|
||||||
|
|
||||||
defp build_inline_quote(prefix, url) do
|
defp build_inline_quote(prefix, url) do
|
||||||
"<span class=\"quote-inline\"><br/><br/>#{prefix}: <a href=\"#{url}\">#{url}</a></span>"
|
"<span class=\"quote-inline\"><br/><br/>#{prefix}: <a href=\"#{url}\">#{url}</a></span>"
|
||||||
end
|
end
|
||||||
|
|
||||||
defp has_inline_quote?(content, quote_url) do
|
defp resolve_urls(quote_url) do
|
||||||
|
# Fetching here can cause infinite recursion as we run this logic on inbound objects too
|
||||||
|
# This is probably not a problem - its an exceptional corner case for a local user to quote
|
||||||
|
# a post which doesn't exist
|
||||||
|
with %Object{} = obj <- Object.normalize(quote_url, fetch: false) do
|
||||||
|
id = obj.data["id"]
|
||||||
|
url = Map.get(obj.data, "url", id)
|
||||||
|
{id, url, [id, url, quote_url]}
|
||||||
|
else
|
||||||
|
_ -> {quote_url, quote_url, [quote_url]}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp has_inline_quote?(content, urls) do
|
||||||
cond do
|
cond do
|
||||||
# Does the quote URL exist in the content?
|
# Does the quote URL exist in the content?
|
||||||
content =~ quote_url -> true
|
Enum.any?(urls, fn url -> content =~ url end) -> true
|
||||||
# Does the content already have a .quote-inline span?
|
# Does the content already have a .quote-inline span?
|
||||||
content =~ "<span class=\"quote-inline\">" -> true
|
content =~ "<span class=\"quote-inline\">" -> true
|
||||||
# No inline quote found
|
# No inline quote found
|
||||||
|
@ -22,18 +37,22 @@ defp has_inline_quote?(content, quote_url) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp filter_object(%{"quoteUri" => quote_url} = object) do
|
defp filter_object(%{"quoteUri" => quote_url} = object) do
|
||||||
|
{id, preferred_url, all_urls} = resolve_urls(quote_url)
|
||||||
|
object = Map.put(object, "quoteUri", id)
|
||||||
|
|
||||||
content = object["content"] || ""
|
content = object["content"] || ""
|
||||||
|
|
||||||
if has_inline_quote?(content, quote_url) do
|
if has_inline_quote?(content, all_urls) do
|
||||||
object
|
object
|
||||||
else
|
else
|
||||||
prefix = Pleroma.Config.get([:mrf_inline_quote, :prefix])
|
prefix = Pleroma.Config.get([:mrf_inline_quote, :prefix])
|
||||||
|
|
||||||
content =
|
content =
|
||||||
if String.ends_with?(content, "</p>") do
|
if String.ends_with?(content, "</p>") do
|
||||||
String.trim_trailing(content, "</p>") <> build_inline_quote(prefix, quote_url) <> "</p>"
|
String.trim_trailing(content, "</p>") <>
|
||||||
|
build_inline_quote(prefix, preferred_url) <> "</p>"
|
||||||
else
|
else
|
||||||
content <> build_inline_quote(prefix, quote_url)
|
content <> build_inline_quote(prefix, preferred_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
Map.put(object, "content", content)
|
Map.put(object, "content", content)
|
||||||
|
|
|
@ -53,6 +53,13 @@ def cast_data(data) do
|
||||||
|
|
||||||
defp fix_url(%{"url" => url} = data) when is_bitstring(url), do: data
|
defp fix_url(%{"url" => url} = data) when is_bitstring(url), do: data
|
||||||
defp fix_url(%{"url" => url} = data) when is_map(url), do: Map.put(data, "url", url["href"])
|
defp fix_url(%{"url" => url} = data) when is_map(url), do: Map.put(data, "url", url["href"])
|
||||||
|
|
||||||
|
defp fix_url(%{"url" => url} = data) when is_list(url) do
|
||||||
|
data
|
||||||
|
|> Map.put("url", List.first(url))
|
||||||
|
|> fix_url()
|
||||||
|
end
|
||||||
|
|
||||||
defp fix_url(data), do: data
|
defp fix_url(data), do: data
|
||||||
|
|
||||||
defp fix_tag(%{"tag" => tag} = data) when is_list(tag) do
|
defp fix_tag(%{"tag" => tag} = data) when is_list(tag) do
|
||||||
|
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
|
||||||
alias Pleroma.Emoji
|
alias Pleroma.Emoji
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
||||||
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
|
@ -52,6 +53,7 @@ def changeset(struct, data) do
|
||||||
defp fix(data) do
|
defp fix(data) do
|
||||||
data =
|
data =
|
||||||
data
|
data
|
||||||
|
|> Transmogrifier.fix_tag()
|
||||||
|> fix_emoji_qualification()
|
|> fix_emoji_qualification()
|
||||||
|> CommonFixes.fix_actor()
|
|> CommonFixes.fix_actor()
|
||||||
|> CommonFixes.fix_activity_addressing()
|
|> CommonFixes.fix_activity_addressing()
|
||||||
|
|
|
@ -16,11 +16,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UserValidator do
|
||||||
alias Pleroma.Object.Containment
|
alias Pleroma.Object.Containment
|
||||||
alias Pleroma.Signature
|
alias Pleroma.Signature
|
||||||
|
|
||||||
|
require Pleroma.Constants
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def validate(object, meta)
|
def validate(object, meta)
|
||||||
|
|
||||||
def validate(%{"type" => type, "id" => _id} = data, meta)
|
def validate(%{"type" => type, "id" => _id} = data, meta)
|
||||||
when type in ["Person", "Organization", "Group", "Application"] do
|
when type in Pleroma.Constants.actor_types() do
|
||||||
with :ok <- validate_pubkey(data),
|
with :ok <- validate_pubkey(data),
|
||||||
:ok <- validate_inbox(data),
|
:ok <- validate_inbox(data),
|
||||||
:ok <- contain_collection_origin(data) do
|
:ok <- contain_collection_origin(data) do
|
||||||
|
|
|
@ -25,8 +25,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
|
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
require Logger
|
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
|
require Logger
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Modifies an incoming AP object (mastodon format) to our internal format.
|
Modifies an incoming AP object (mastodon format) to our internal format.
|
||||||
|
@ -58,21 +58,48 @@ def fix_summary(%{"summary" => _} = object) do
|
||||||
|
|
||||||
def fix_summary(object), do: Map.put(object, "summary", "")
|
def fix_summary(object), do: Map.put(object, "summary", "")
|
||||||
|
|
||||||
def fix_addressing_list(map, field) do
|
defp fix_addressing_list(addrs) do
|
||||||
addrs = map[field]
|
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
is_list(addrs) ->
|
is_list(addrs) -> Enum.filter(addrs, &is_binary/1)
|
||||||
Map.put(map, field, Enum.filter(addrs, &is_binary/1))
|
is_binary(addrs) -> [addrs]
|
||||||
|
true -> []
|
||||||
is_binary(addrs) ->
|
|
||||||
Map.put(map, field, [addrs])
|
|
||||||
|
|
||||||
true ->
|
|
||||||
Map.put(map, field, [])
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Due to JSON-LD simply "Public" and "as:Public" are equivalent to the full URI
|
||||||
|
# but to simplify later checks we only want to deal with one reperesentation internally
|
||||||
|
defp normalise_addressing_public_list(map, all_fields)
|
||||||
|
|
||||||
|
defp normalise_addressing_public_list(%{} = map, [field | fields]) do
|
||||||
|
full_uri = Pleroma.Constants.as_public()
|
||||||
|
|
||||||
|
map =
|
||||||
|
if map[field] != nil do
|
||||||
|
new_fval =
|
||||||
|
map[field]
|
||||||
|
|> fix_addressing_list()
|
||||||
|
|> Enum.map(fn
|
||||||
|
"Public" -> full_uri
|
||||||
|
"as:Public" -> full_uri
|
||||||
|
x -> x
|
||||||
|
end)
|
||||||
|
|
||||||
|
Map.put(map, field, new_fval)
|
||||||
|
else
|
||||||
|
map
|
||||||
|
end
|
||||||
|
|
||||||
|
normalise_addressing_public_list(map, fields)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp normalise_addressing_public_list(map, _) do
|
||||||
|
map
|
||||||
|
end
|
||||||
|
|
||||||
|
defp normalise_addressing_public(map) do
|
||||||
|
normalise_addressing_public_list(map, ["to", "cc", "bto", "bcc"])
|
||||||
|
end
|
||||||
|
|
||||||
# if directMessage flag is set to true, leave the addressing alone
|
# if directMessage flag is set to true, leave the addressing alone
|
||||||
def fix_explicit_addressing(%{"directMessage" => true} = object, _follower_collection),
|
def fix_explicit_addressing(%{"directMessage" => true} = object, _follower_collection),
|
||||||
do: object
|
do: object
|
||||||
|
@ -96,6 +123,10 @@ def fix_explicit_addressing(%{"to" => to, "cc" => cc} = object, follower_collect
|
||||||
|> Map.put("cc", final_cc)
|
|> Map.put("cc", final_cc)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def fix_addressing_list_key(map, field) do
|
||||||
|
Map.put(map, field, fix_addressing_list(map[field]))
|
||||||
|
end
|
||||||
|
|
||||||
def fix_addressing(object) do
|
def fix_addressing(object) do
|
||||||
{:ok, %User{follower_address: follower_collection}} =
|
{:ok, %User{follower_address: follower_collection}} =
|
||||||
object
|
object
|
||||||
|
@ -103,10 +134,10 @@ def fix_addressing(object) do
|
||||||
|> User.get_or_fetch_by_ap_id()
|
|> User.get_or_fetch_by_ap_id()
|
||||||
|
|
||||||
object
|
object
|
||||||
|> fix_addressing_list("to")
|
|> fix_addressing_list_key("to")
|
||||||
|> fix_addressing_list("cc")
|
|> fix_addressing_list_key("cc")
|
||||||
|> fix_addressing_list("bto")
|
|> fix_addressing_list_key("bto")
|
||||||
|> fix_addressing_list("bcc")
|
|> fix_addressing_list_key("bcc")
|
||||||
|> fix_explicit_addressing(follower_collection)
|
|> fix_explicit_addressing(follower_collection)
|
||||||
|> CommonFixes.fix_implicit_addressing(follower_collection)
|
|> CommonFixes.fix_implicit_addressing(follower_collection)
|
||||||
end
|
end
|
||||||
|
@ -135,8 +166,7 @@ def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
|
||||||
|> Map.put("context", replied_object.data["context"] || object["conversation"])
|
|> Map.put("context", replied_object.data["context"] || object["conversation"])
|
||||||
|> Map.drop(["conversation", "inReplyToAtomUri"])
|
|> Map.drop(["conversation", "inReplyToAtomUri"])
|
||||||
else
|
else
|
||||||
e ->
|
_ ->
|
||||||
Logger.warning("Couldn't fetch reply@#{inspect(in_reply_to_id)}, error: #{inspect(e)}")
|
|
||||||
object
|
object
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
@ -384,11 +414,28 @@ defp get_reported(objects) do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(data, options \\ [])
|
def handle_incoming(data, options \\ []) do
|
||||||
|
data = normalise_addressing_public(data)
|
||||||
|
|
||||||
|
data =
|
||||||
|
if data["object"] != nil do
|
||||||
|
object = normalise_addressing_public(data["object"])
|
||||||
|
Map.put(data, "object", object)
|
||||||
|
else
|
||||||
|
data
|
||||||
|
end
|
||||||
|
|
||||||
|
handle_incoming_normalised(data, options)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp handle_incoming_normalised(data, options)
|
||||||
|
|
||||||
# Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them
|
# Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them
|
||||||
# with nil ID.
|
# with nil ID.
|
||||||
def handle_incoming(%{"type" => "Flag", "object" => objects, "actor" => actor} = data, _options) do
|
defp handle_incoming_normalised(
|
||||||
|
%{"type" => "Flag", "object" => objects, "actor" => actor} = data,
|
||||||
|
_options
|
||||||
|
) do
|
||||||
with context <- data["context"] || Utils.generate_context_id(),
|
with context <- data["context"] || Utils.generate_context_id(),
|
||||||
content <- data["content"] || "",
|
content <- data["content"] || "",
|
||||||
%User{} = actor <- User.get_cached_by_ap_id(actor),
|
%User{} = actor <- User.get_cached_by_ap_id(actor),
|
||||||
|
@ -409,20 +456,21 @@ def handle_incoming(%{"type" => "Flag", "object" => objects, "actor" => actor} =
|
||||||
end
|
end
|
||||||
|
|
||||||
# disallow objects with bogus IDs
|
# disallow objects with bogus IDs
|
||||||
def handle_incoming(%{"id" => nil}, _options), do: :error
|
defp handle_incoming_normalised(%{"id" => nil}, _options), do: :error
|
||||||
def handle_incoming(%{"id" => ""}, _options), do: :error
|
defp handle_incoming_normalised(%{"id" => ""}, _options), do: :error
|
||||||
# length of https:// = 8, should validate better, but good enough for now.
|
# length of https:// = 8, should validate better, but good enough for now.
|
||||||
def handle_incoming(%{"id" => id}, _options) when is_binary(id) and byte_size(id) < 8,
|
defp handle_incoming_normalised(%{"id" => id}, _options)
|
||||||
do: :error
|
when is_binary(id) and byte_size(id) < 8,
|
||||||
|
do: :error
|
||||||
|
|
||||||
@doc "Rewrite misskey likes into EmojiReacts"
|
# Rewrite misskey likes into EmojiReacts
|
||||||
def handle_incoming(
|
defp handle_incoming_normalised(
|
||||||
%{
|
%{
|
||||||
"type" => "Like",
|
"type" => "Like",
|
||||||
"content" => reaction
|
"content" => reaction
|
||||||
} = data,
|
} = data,
|
||||||
options
|
options
|
||||||
) do
|
) do
|
||||||
if Pleroma.Emoji.is_unicode_emoji?(reaction) || Pleroma.Emoji.matches_shortcode?(reaction) do
|
if Pleroma.Emoji.is_unicode_emoji?(reaction) || Pleroma.Emoji.matches_shortcode?(reaction) do
|
||||||
data
|
data
|
||||||
|> Map.put("type", "EmojiReact")
|
|> Map.put("type", "EmojiReact")
|
||||||
|
@ -434,11 +482,11 @@ def handle_incoming(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(
|
defp handle_incoming_normalised(
|
||||||
%{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,
|
%{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,
|
||||||
options
|
options
|
||||||
)
|
)
|
||||||
when objtype in ~w{Question Answer Audio Video Event Article Note Page} do
|
when objtype in ~w{Question Answer Audio Video Event Article Note Page} do
|
||||||
fetch_options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
|
fetch_options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
|
||||||
|
|
||||||
object =
|
object =
|
||||||
|
@ -470,8 +518,8 @@ def handle_incoming(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(%{"type" => type} = data, _options)
|
defp handle_incoming_normalised(%{"type" => type} = data, _options)
|
||||||
when type in ~w{Like EmojiReact Announce Add Remove} do
|
when type in ~w{Like EmojiReact Announce Add Remove} do
|
||||||
with :ok <- ObjectValidator.fetch_actor_and_object(data),
|
with :ok <- ObjectValidator.fetch_actor_and_object(data),
|
||||||
{:ok, activity, _meta} <- Pipeline.common_pipeline(data, local: false) do
|
{:ok, activity, _meta} <- Pipeline.common_pipeline(data, local: false) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
|
@ -481,11 +529,11 @@ def handle_incoming(%{"type" => type} = data, _options)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(
|
defp handle_incoming_normalised(
|
||||||
%{"type" => type} = data,
|
%{"type" => type} = data,
|
||||||
_options
|
_options
|
||||||
)
|
)
|
||||||
when type in ~w{Update Block Follow Accept Reject} do
|
when type in ~w{Update Block Follow Accept Reject} do
|
||||||
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
|
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
|
||||||
{:ok, activity, _} <-
|
{:ok, activity, _} <-
|
||||||
Pipeline.common_pipeline(data, local: false) do
|
Pipeline.common_pipeline(data, local: false) do
|
||||||
|
@ -493,10 +541,10 @@ def handle_incoming(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(
|
defp handle_incoming_normalised(
|
||||||
%{"type" => "Delete"} = data,
|
%{"type" => "Delete"} = data,
|
||||||
_options
|
_options
|
||||||
) do
|
) do
|
||||||
with {:ok, activity, _} <-
|
with {:ok, activity, _} <-
|
||||||
Pipeline.common_pipeline(data, local: false) do
|
Pipeline.common_pipeline(data, local: false) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
|
@ -516,15 +564,15 @@ def handle_incoming(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(
|
defp handle_incoming_normalised(
|
||||||
%{
|
%{
|
||||||
"type" => "Undo",
|
"type" => "Undo",
|
||||||
"object" => %{"type" => "Follow", "object" => followed},
|
"object" => %{"type" => "Follow", "object" => followed},
|
||||||
"actor" => follower,
|
"actor" => follower,
|
||||||
"id" => id
|
"id" => id
|
||||||
} = _data,
|
} = _data,
|
||||||
_options
|
_options
|
||||||
) do
|
) do
|
||||||
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
|
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
|
||||||
{:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower),
|
{:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower),
|
||||||
{:ok, activity} <- ActivityPub.unfollow(follower, followed, id, false) do
|
{:ok, activity} <- ActivityPub.unfollow(follower, followed, id, false) do
|
||||||
|
@ -535,28 +583,28 @@ def handle_incoming(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(
|
defp handle_incoming_normalised(
|
||||||
%{
|
%{
|
||||||
"type" => "Undo",
|
"type" => "Undo",
|
||||||
"object" => %{"type" => type}
|
"object" => %{"type" => type}
|
||||||
} = data,
|
} = data,
|
||||||
_options
|
_options
|
||||||
)
|
)
|
||||||
when type in ["Like", "EmojiReact", "Announce", "Block"] do
|
when type in ["Like", "EmojiReact", "Announce", "Block"] do
|
||||||
with {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
|
with {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# For Undos that don't have the complete object attached, try to find it in our database.
|
# For Undos that don't have the complete object attached, try to find it in our database.
|
||||||
def handle_incoming(
|
defp handle_incoming_normalised(
|
||||||
%{
|
%{
|
||||||
"type" => "Undo",
|
"type" => "Undo",
|
||||||
"object" => object
|
"object" => object
|
||||||
} = activity,
|
} = activity,
|
||||||
options
|
options
|
||||||
)
|
)
|
||||||
when is_binary(object) do
|
when is_binary(object) do
|
||||||
with %Activity{data: data} <- Activity.get_by_ap_id(object) do
|
with %Activity{data: data} <- Activity.get_by_ap_id(object) do
|
||||||
activity
|
activity
|
||||||
|> Map.put("object", data)
|
|> Map.put("object", data)
|
||||||
|
@ -566,17 +614,22 @@ def handle_incoming(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(
|
defp handle_incoming_normalised(
|
||||||
%{
|
%{
|
||||||
"type" => "Move",
|
"type" => "Move",
|
||||||
"actor" => origin_actor,
|
"actor" => origin_actor,
|
||||||
"object" => origin_actor,
|
"object" => origin_actor,
|
||||||
"target" => target_actor
|
"target" => target_actor
|
||||||
},
|
},
|
||||||
_options
|
_options
|
||||||
) do
|
) do
|
||||||
with %User{} = origin_user <- User.get_cached_by_ap_id(origin_actor),
|
with %User{} = origin_user <- User.get_cached_by_ap_id(origin_actor),
|
||||||
{:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_actor),
|
# Use a dramatically shortened maximum age before refresh here because it is reasonable
|
||||||
|
# for a user to
|
||||||
|
# 1. Add the alias to their new account and then
|
||||||
|
# 2. Press the button on their new account
|
||||||
|
# within a very short period of time and expect it to work
|
||||||
|
{:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_actor, maximum_age: 5),
|
||||||
true <- origin_actor in target_user.also_known_as do
|
true <- origin_actor in target_user.also_known_as do
|
||||||
ActivityPub.move(origin_user, target_user, false)
|
ActivityPub.move(origin_user, target_user, false)
|
||||||
else
|
else
|
||||||
|
@ -584,7 +637,7 @@ def handle_incoming(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(_, _), do: :error
|
defp handle_incoming_normalised(_, _), do: :error
|
||||||
|
|
||||||
@spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil
|
@spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil
|
||||||
def get_obj_helper(id, options \\ []) do
|
def get_obj_helper(id, options \\ []) do
|
||||||
|
@ -828,8 +881,7 @@ def maybe_fix_object_url(%{"object" => object} = data) when is_binary(object) do
|
||||||
relative_object do
|
relative_object do
|
||||||
Map.put(data, "object", external_url)
|
Map.put(data, "object", external_url)
|
||||||
else
|
else
|
||||||
{:fetch, e} ->
|
{:fetch, _} ->
|
||||||
Logger.error("Couldn't fetch fixed_object@#{object} #{inspect(e)}")
|
|
||||||
data
|
data
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
|
|
|
@ -26,8 +26,7 @@ def render("endpoints.json", %{user: %User{local: true} = _user}) do
|
||||||
"oauthAuthorizationEndpoint" => url(~p"/oauth/authorize"),
|
"oauthAuthorizationEndpoint" => url(~p"/oauth/authorize"),
|
||||||
"oauthRegistrationEndpoint" => url(~p"/api/v1/apps"),
|
"oauthRegistrationEndpoint" => url(~p"/api/v1/apps"),
|
||||||
"oauthTokenEndpoint" => url(~p"/oauth/token"),
|
"oauthTokenEndpoint" => url(~p"/oauth/token"),
|
||||||
"sharedInbox" => url(~p"/inbox"),
|
"sharedInbox" => url(~p"/inbox")
|
||||||
"uploadMedia" => url(~p"/api/ap/upload_media")
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -150,7 +150,7 @@ def update_notificaton_settings_operation do
|
||||||
"removes the contents of a message from the push notification"
|
"removes the contents of a message from the push notification"
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
requestBody: nil,
|
requestBody: request_body("Parameters", update_notification_settings_request()),
|
||||||
responses: %{
|
responses: %{
|
||||||
200 =>
|
200 =>
|
||||||
Operation.response("Success", "application/json", %Schema{
|
Operation.response("Success", "application/json", %Schema{
|
||||||
|
@ -432,4 +432,22 @@ defp delete_account_request do
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp update_notification_settings_request do
|
||||||
|
%Schema{
|
||||||
|
title: "UpdateNotificationSettings",
|
||||||
|
description: "PUT paramenters (query, form or JSON) for updating notification settings",
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
block_from_strangers: %Schema{
|
||||||
|
type: :boolean,
|
||||||
|
description: "blocks notifications from accounts you do not follow"
|
||||||
|
},
|
||||||
|
hide_notification_contents: %Schema{
|
||||||
|
type: :boolean,
|
||||||
|
description: "removes the contents of a message from the push notification"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -78,9 +78,7 @@ def activity_title(%{"content" => content}, opts \\ %{}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def activity_content(%{"content" => content}) do
|
def activity_content(%{"content" => content}) do
|
||||||
content
|
escape(content)
|
||||||
|> String.replace(~r/[\n\r]/, "")
|
|
||||||
|> escape()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def activity_content(_), do: ""
|
def activity_content(_), do: ""
|
||||||
|
|
|
@ -14,8 +14,6 @@ defmodule Pleroma.Web.MediaProxy do
|
||||||
|
|
||||||
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
|
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
|
||||||
|
|
||||||
@mix_env Mix.env()
|
|
||||||
|
|
||||||
def cache_table, do: @cache_table
|
def cache_table, do: @cache_table
|
||||||
|
|
||||||
@spec in_banned_urls(String.t()) :: boolean()
|
@spec in_banned_urls(String.t()) :: boolean()
|
||||||
|
@ -146,14 +144,8 @@ def filename(url_or_path) do
|
||||||
if path = URI.parse(url_or_path).path, do: Path.basename(path)
|
if path = URI.parse(url_or_path).path, do: Path.basename(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
if @mix_env == :test do
|
def base_url do
|
||||||
def base_url do
|
Config.get!([:media_proxy, :base_url])
|
||||||
Config.get([:media_proxy, :base_url], Endpoint.url())
|
|
||||||
end
|
|
||||||
else
|
|
||||||
def base_url do
|
|
||||||
Config.get!([:media_proxy, :base_url])
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp proxy_url(path, sig_base64, url_base64, filename) do
|
defp proxy_url(path, sig_base64, url_base64, filename) do
|
||||||
|
|
|
@ -12,14 +12,38 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
|
||||||
@behaviour Provider
|
@behaviour Provider
|
||||||
@media_types ["image", "audio", "video"]
|
@media_types ["image", "audio", "video"]
|
||||||
|
|
||||||
|
defp user_avatar_tags(user) do
|
||||||
|
if Utils.visible?(user) do
|
||||||
|
[
|
||||||
|
{:meta, [property: "og:image", content: MediaProxy.preview_url(User.avatar_url(user))],
|
||||||
|
[]},
|
||||||
|
{:meta, [property: "og:image:width", content: 150], []},
|
||||||
|
{:meta, [property: "og:image:height", content: 150], []}
|
||||||
|
]
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@impl Provider
|
@impl Provider
|
||||||
def build_tags(%{
|
def build_tags(%{
|
||||||
object: object,
|
object: object,
|
||||||
url: url,
|
url: url,
|
||||||
user: user
|
user: user
|
||||||
}) do
|
}) do
|
||||||
attachments = build_attachments(object)
|
attachments =
|
||||||
scrubbed_content = Utils.scrub_html_and_truncate(object)
|
if Utils.visible?(object) do
|
||||||
|
build_attachments(object)
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
|
scrubbed_content =
|
||||||
|
if Utils.visible?(object) do
|
||||||
|
Utils.scrub_html_and_truncate(object)
|
||||||
|
else
|
||||||
|
"Content cannot be displayed."
|
||||||
|
end
|
||||||
|
|
||||||
[
|
[
|
||||||
{:meta,
|
{:meta,
|
||||||
|
@ -36,12 +60,7 @@ def build_tags(%{
|
||||||
{:meta, [property: "og:type", content: "article"], []}
|
{:meta, [property: "og:type", content: "article"], []}
|
||||||
] ++
|
] ++
|
||||||
if attachments == [] or Metadata.activity_nsfw?(object) do
|
if attachments == [] or Metadata.activity_nsfw?(object) do
|
||||||
[
|
user_avatar_tags(user)
|
||||||
{:meta, [property: "og:image", content: MediaProxy.preview_url(User.avatar_url(user))],
|
|
||||||
[]},
|
|
||||||
{:meta, [property: "og:image:width", content: 150], []},
|
|
||||||
{:meta, [property: "og:image:height", content: 150], []}
|
|
||||||
]
|
|
||||||
else
|
else
|
||||||
attachments
|
attachments
|
||||||
end
|
end
|
||||||
|
@ -49,7 +68,9 @@ def build_tags(%{
|
||||||
|
|
||||||
@impl Provider
|
@impl Provider
|
||||||
def build_tags(%{user: user}) do
|
def build_tags(%{user: user}) do
|
||||||
with truncated_bio = Utils.scrub_html_and_truncate(user.bio) do
|
if Utils.visible?(user) do
|
||||||
|
truncated_bio = Utils.scrub_html_and_truncate(user.bio)
|
||||||
|
|
||||||
[
|
[
|
||||||
{:meta,
|
{:meta,
|
||||||
[
|
[
|
||||||
|
@ -58,12 +79,10 @@ def build_tags(%{user: user}) do
|
||||||
], []},
|
], []},
|
||||||
{:meta, [property: "og:url", content: user.uri || user.ap_id], []},
|
{:meta, [property: "og:url", content: user.uri || user.ap_id], []},
|
||||||
{:meta, [property: "og:description", content: truncated_bio], []},
|
{:meta, [property: "og:description", content: truncated_bio], []},
|
||||||
{:meta, [property: "og:type", content: "article"], []},
|
{:meta, [property: "og:type", content: "article"], []}
|
||||||
{:meta, [property: "og:image", content: MediaProxy.preview_url(User.avatar_url(user))],
|
] ++ user_avatar_tags(user)
|
||||||
[]},
|
else
|
||||||
{:meta, [property: "og:image:width", content: 150], []},
|
[]
|
||||||
{:meta, [property: "og:image:height", content: 150], []}
|
|
||||||
]
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,19 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
|
||||||
|
|
||||||
@impl Provider
|
@impl Provider
|
||||||
def build_tags(%{activity_id: id, object: object, user: user}) do
|
def build_tags(%{activity_id: id, object: object, user: user}) do
|
||||||
attachments = build_attachments(id, object)
|
attachments =
|
||||||
scrubbed_content = Utils.scrub_html_and_truncate(object)
|
if Utils.visible?(object) do
|
||||||
|
build_attachments(id, object)
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
|
scrubbed_content =
|
||||||
|
if Utils.visible?(object) do
|
||||||
|
Utils.scrub_html_and_truncate(object)
|
||||||
|
else
|
||||||
|
"Content cannot be displayed."
|
||||||
|
end
|
||||||
|
|
||||||
[
|
[
|
||||||
title_tag(user),
|
title_tag(user),
|
||||||
|
@ -36,13 +47,17 @@ def build_tags(%{activity_id: id, object: object, user: user}) do
|
||||||
|
|
||||||
@impl Provider
|
@impl Provider
|
||||||
def build_tags(%{user: user}) do
|
def build_tags(%{user: user}) do
|
||||||
with truncated_bio = Utils.scrub_html_and_truncate(user.bio) do
|
if Utils.visible?(user) do
|
||||||
[
|
with truncated_bio = Utils.scrub_html_and_truncate(user.bio) do
|
||||||
title_tag(user),
|
[
|
||||||
{:meta, [name: "twitter:description", content: truncated_bio], []},
|
title_tag(user),
|
||||||
image_tag(user),
|
{:meta, [name: "twitter:description", content: truncated_bio], []},
|
||||||
{:meta, [name: "twitter:card", content: "summary"], []}
|
image_tag(user),
|
||||||
]
|
{:meta, [name: "twitter:card", content: "summary"], []}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
else
|
||||||
|
[]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -51,7 +66,11 @@ defp title_tag(user) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def image_tag(user) do
|
def image_tag(user) do
|
||||||
{:meta, [name: "twitter:image", content: MediaProxy.preview_url(User.avatar_url(user))], []}
|
if Utils.visible?(user) do
|
||||||
|
{:meta, [name: "twitter:image", content: MediaProxy.preview_url(User.avatar_url(user))], []}
|
||||||
|
else
|
||||||
|
{:meta, [name: "twitter:image", content: ""], []}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
|
defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
|
||||||
|
|
|
@ -7,6 +7,15 @@ defmodule Pleroma.Web.Metadata.Utils do
|
||||||
alias Pleroma.Emoji
|
alias Pleroma.Emoji
|
||||||
alias Pleroma.Formatter
|
alias Pleroma.Formatter
|
||||||
alias Pleroma.HTML
|
alias Pleroma.HTML
|
||||||
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
|
|
||||||
|
def visible?(%Pleroma.User{} = object) do
|
||||||
|
Visibility.restrict_unauthenticated_access?(object) == :visible
|
||||||
|
end
|
||||||
|
|
||||||
|
def visible?(object) do
|
||||||
|
Visibility.visible_for_user?(object, nil)
|
||||||
|
end
|
||||||
|
|
||||||
defp scrub_html_and_truncate_object_field(field, object) do
|
defp scrub_html_and_truncate_object_field(field, object) do
|
||||||
field
|
field
|
||||||
|
|
|
@ -800,13 +800,9 @@ defmodule Pleroma.Web.Router do
|
||||||
scope "/", Pleroma.Web.ActivityPub do
|
scope "/", Pleroma.Web.ActivityPub do
|
||||||
pipe_through([:activitypub_client])
|
pipe_through([:activitypub_client])
|
||||||
|
|
||||||
get("/api/ap/whoami", ActivityPubController, :whoami)
|
|
||||||
get("/users/:nickname/inbox", ActivityPubController, :read_inbox)
|
get("/users/:nickname/inbox", ActivityPubController, :read_inbox)
|
||||||
|
|
||||||
get("/users/:nickname/outbox", ActivityPubController, :outbox)
|
get("/users/:nickname/outbox", ActivityPubController, :outbox)
|
||||||
post("/users/:nickname/outbox", ActivityPubController, :update_outbox)
|
|
||||||
post("/api/ap/upload_media", ActivityPubController, :upload_media)
|
|
||||||
|
|
||||||
get("/users/:nickname/collections/featured", ActivityPubController, :pinned)
|
get("/users/:nickname/collections/featured", ActivityPubController, :pinned)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -151,41 +151,40 @@ defp summary_fallback_metrics(byte_unit \\ :byte) do
|
||||||
# phoenix.router_dispatch.stop.duration
|
# phoenix.router_dispatch.stop.duration
|
||||||
# pleroma.repo.query.total_time
|
# pleroma.repo.query.total_time
|
||||||
# pleroma.repo.query.queue_time
|
# pleroma.repo.query.queue_time
|
||||||
dist_metrics =
|
dist_metrics = [
|
||||||
[
|
distribution("phoenix.endpoint.stop.duration.fdist",
|
||||||
distribution("phoenix.endpoint.stop.duration.fdist",
|
event_name: [:phoenix, :endpoint, :stop],
|
||||||
event_name: [:phoenix, :endpoint, :stop],
|
measurement: :duration,
|
||||||
measurement: :duration,
|
unit: {:native, :millisecond},
|
||||||
unit: {:native, :millisecond},
|
reporter_options: [
|
||||||
reporter_options: [
|
buckets: simple_buckets
|
||||||
buckets: simple_buckets
|
]
|
||||||
]
|
),
|
||||||
),
|
distribution("pleroma.repo.query.decode_time.fdist",
|
||||||
distribution("pleroma.repo.query.decode_time.fdist",
|
event_name: [:pleroma, :repo, :query],
|
||||||
event_name: [:pleroma, :repo, :query],
|
measurement: :decode_time,
|
||||||
measurement: :decode_time,
|
unit: {:native, :millisecond},
|
||||||
unit: {:native, :millisecond},
|
reporter_options: [
|
||||||
reporter_options: [
|
buckets: simple_buckets_quick
|
||||||
buckets: simple_buckets_quick
|
]
|
||||||
]
|
),
|
||||||
),
|
distribution("pleroma.repo.query.query_time.fdist",
|
||||||
distribution("pleroma.repo.query.query_time.fdist",
|
event_name: [:pleroma, :repo, :query],
|
||||||
event_name: [:pleroma, :repo, :query],
|
measurement: :query_time,
|
||||||
measurement: :query_time,
|
unit: {:native, :millisecond},
|
||||||
unit: {:native, :millisecond},
|
reporter_options: [
|
||||||
reporter_options: [
|
buckets: simple_buckets
|
||||||
buckets: simple_buckets
|
]
|
||||||
]
|
),
|
||||||
),
|
distribution("pleroma.repo.query.idle_time.fdist",
|
||||||
distribution("pleroma.repo.query.idle_time.fdist",
|
event_name: [:pleroma, :repo, :query],
|
||||||
event_name: [:pleroma, :repo, :query],
|
measurement: :idle_time,
|
||||||
measurement: :idle_time,
|
unit: {:native, :millisecond},
|
||||||
unit: {:native, :millisecond},
|
reporter_options: [
|
||||||
reporter_options: [
|
buckets: simple_buckets
|
||||||
buckets: simple_buckets
|
]
|
||||||
]
|
)
|
||||||
)
|
]
|
||||||
]
|
|
||||||
|
|
||||||
vm_metrics =
|
vm_metrics =
|
||||||
sum_counter_pair("vm.memory.total",
|
sum_counter_pair("vm.memory.total",
|
||||||
|
|
|
@ -184,7 +184,13 @@ def emoji(conn, _params) do
|
||||||
json(conn, emoji)
|
json(conn, emoji)
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_notificaton_settings(%{assigns: %{user: user}} = conn, params) do
|
def update_notificaton_settings(
|
||||||
|
%{assigns: %{user: user}, body_params: body_params} = conn,
|
||||||
|
params
|
||||||
|
) do
|
||||||
|
# OpenApiSpex 3.x prevents Plug's usual parameter premerging
|
||||||
|
params = Map.merge(params, body_params)
|
||||||
|
|
||||||
with {:ok, _} <- User.update_notification_settings(user, params) do
|
with {:ok, _} <- User.update_notification_settings(user, params) do
|
||||||
json(conn, %{status: "success"})
|
json(conn, %{status: "success"})
|
||||||
end
|
end
|
||||||
|
|
|
@ -65,7 +65,7 @@ defp gather_links(%User{} = user) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp gather_aliases(%User{} = user) do
|
defp gather_aliases(%User{} = user) do
|
||||||
[user.ap_id | user.also_known_as]
|
[user.ap_id]
|
||||||
end
|
end
|
||||||
|
|
||||||
def represent_user(user, "JSON") do
|
def represent_user(user, "JSON") do
|
||||||
|
|
|
@ -26,7 +26,7 @@ def string_from_xpath(xpath, doc) do
|
||||||
|
|
||||||
def parse_document(text) do
|
def parse_document(text) do
|
||||||
try do
|
try do
|
||||||
doc = SweetXml.parse(text, dtd: :none)
|
doc = SweetXml.parse(text, dtd: :none, quiet: true)
|
||||||
|
|
||||||
{:ok, doc}
|
{:ok, doc}
|
||||||
rescue
|
rescue
|
||||||
|
|
|
@ -14,7 +14,8 @@ def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do
|
||||||
else
|
else
|
||||||
{:error, :origin_containment_failed} -> {:discard, :origin_containment_failed}
|
{:error, :origin_containment_failed} -> {:discard, :origin_containment_failed}
|
||||||
{:error, {:reject, reason}} -> {:discard, reason}
|
{:error, {:reject, reason}} -> {:discard, reason}
|
||||||
e -> e
|
{:error, _} = e -> e
|
||||||
|
e -> {:error, e}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,10 +5,42 @@
|
||||||
defmodule Pleroma.Workers.RemoteFetcherWorker do
|
defmodule Pleroma.Workers.RemoteFetcherWorker do
|
||||||
alias Pleroma.Object.Fetcher
|
alias Pleroma.Object.Fetcher
|
||||||
|
|
||||||
use Pleroma.Workers.WorkerHelper, queue: "remote_fetcher"
|
use Pleroma.Workers.WorkerHelper,
|
||||||
|
queue: "remote_fetcher",
|
||||||
|
unique: [period: 300, states: Oban.Job.states(), keys: [:op, :id]]
|
||||||
|
|
||||||
@impl Oban.Worker
|
@impl Oban.Worker
|
||||||
def perform(%Job{args: %{"op" => "fetch_remote", "id" => id} = args}) do
|
def perform(%Job{args: %{"op" => "fetch_remote", "id" => id} = args}) do
|
||||||
{:ok, _object} = Fetcher.fetch_object_from_id(id, depth: args["depth"])
|
case Fetcher.fetch_object_from_id(id, depth: args["depth"]) do
|
||||||
|
{:ok, _object} ->
|
||||||
|
:ok
|
||||||
|
|
||||||
|
{:error, :forbidden} ->
|
||||||
|
{:discard, :forbidden}
|
||||||
|
|
||||||
|
{:error, :not_found} ->
|
||||||
|
{:discard, :not_found}
|
||||||
|
|
||||||
|
{:error, :allowed_depth} ->
|
||||||
|
{:discard, :allowed_depth}
|
||||||
|
|
||||||
|
{:error, :invalid_uri_scheme} ->
|
||||||
|
{:discard, :invalid_uri_scheme}
|
||||||
|
|
||||||
|
{:error, :local_resource} ->
|
||||||
|
{:discard, :local_resource}
|
||||||
|
|
||||||
|
{:reject, _} ->
|
||||||
|
{:discard, :reject}
|
||||||
|
|
||||||
|
{:error, :id_mismatch} ->
|
||||||
|
{:discard, :id_mismatch}
|
||||||
|
|
||||||
|
{:error, _} = e ->
|
||||||
|
e
|
||||||
|
|
||||||
|
e ->
|
||||||
|
{:error, e}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,12 +25,16 @@ def sidekiq_backoff(attempt, pow \\ 4, base_backoff \\ 15) do
|
||||||
defmacro __using__(opts) do
|
defmacro __using__(opts) do
|
||||||
caller_module = __CALLER__.module
|
caller_module = __CALLER__.module
|
||||||
queue = Keyword.fetch!(opts, :queue)
|
queue = Keyword.fetch!(opts, :queue)
|
||||||
|
# by default just stop unintended duplicates - this can and should be overridden
|
||||||
|
# if you want to have a more complex uniqueness constraint
|
||||||
|
uniqueness = Keyword.get(opts, :unique, period: 1)
|
||||||
|
|
||||||
quote do
|
quote do
|
||||||
# Note: `max_attempts` is intended to be overridden in `new/2` call
|
# Note: `max_attempts` is intended to be overridden in `new/2` call
|
||||||
use Oban.Worker,
|
use Oban.Worker,
|
||||||
queue: unquote(queue),
|
queue: unquote(queue),
|
||||||
max_attempts: 1
|
max_attempts: 1,
|
||||||
|
unique: unquote(uniqueness)
|
||||||
|
|
||||||
alias Oban.Job
|
alias Oban.Job
|
||||||
|
|
||||||
|
|
4
mix.exs
4
mix.exs
|
@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do
|
||||||
def project do
|
def project do
|
||||||
[
|
[
|
||||||
app: :pleroma,
|
app: :pleroma,
|
||||||
version: version("3.12.2"),
|
version: version("3.13.0"),
|
||||||
elixir: "~> 1.14",
|
elixir: "~> 1.14",
|
||||||
elixirc_paths: elixirc_paths(Mix.env()),
|
elixirc_paths: elixirc_paths(Mix.env()),
|
||||||
compilers: Mix.compilers(),
|
compilers: Mix.compilers(),
|
||||||
|
@ -125,7 +125,7 @@ defp deps do
|
||||||
{:ecto_enum, "~> 1.4"},
|
{:ecto_enum, "~> 1.4"},
|
||||||
{:ecto_sql, "~> 3.10.0"},
|
{:ecto_sql, "~> 3.10.0"},
|
||||||
{:postgrex, "~> 0.17.2"},
|
{:postgrex, "~> 0.17.2"},
|
||||||
{:oban, "~> 2.15.2"},
|
{:oban, "~> 2.17.8"},
|
||||||
{:gettext, "~> 0.22.3"},
|
{:gettext, "~> 0.22.3"},
|
||||||
{:bcrypt_elixir, "~> 3.0.1"},
|
{:bcrypt_elixir, "~> 3.0.1"},
|
||||||
{:fast_sanitize, "~> 0.2.3"},
|
{:fast_sanitize, "~> 0.2.3"},
|
||||||
|
|
78
mix.lock
78
mix.lock
|
@ -3,55 +3,55 @@
|
||||||
"base62": {:hex, :base62, "1.2.2", "85c6627eb609317b70f555294045895ffaaeb1758666ab9ef9ca38865b11e629", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "d41336bda8eaa5be197f1e4592400513ee60518e5b9f4dcf38f4b4dae6f377bb"},
|
"base62": {:hex, :base62, "1.2.2", "85c6627eb609317b70f555294045895ffaaeb1758666ab9ef9ca38865b11e629", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "d41336bda8eaa5be197f1e4592400513ee60518e5b9f4dcf38f4b4dae6f377bb"},
|
||||||
"bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"},
|
"bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"},
|
||||||
"bcrypt_elixir": {:hex, :bcrypt_elixir, "3.0.1", "9be815469e6bfefec40fa74658ecbbe6897acfb57614df1416eeccd4903f602c", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "486bb95efb645d1efc6794c1ddd776a186a9a713abf06f45708a6ce324fb96cf"},
|
"bcrypt_elixir": {:hex, :bcrypt_elixir, "3.0.1", "9be815469e6bfefec40fa74658ecbbe6897acfb57614df1416eeccd4903f602c", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "486bb95efb645d1efc6794c1ddd776a186a9a713abf06f45708a6ce324fb96cf"},
|
||||||
"benchee": {:hex, :benchee, "1.2.0", "afd2f0caec06ce3a70d9c91c514c0b58114636db9d83c2dc6bfd416656618353", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "ee729e53217898b8fd30aaad3cce61973dab61574ae6f48229fe7ff42d5e4457"},
|
"benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"},
|
||||||
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
|
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
|
||||||
"cachex": {:hex, :cachex, "3.6.0", "14a1bfbeee060dd9bec25a5b6f4e4691e3670ebda28c8ba2884b12fe30b36bf8", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "ebf24e373883bc8e0c8d894a63bbe102ae13d918f790121f5cfe6e485cc8e2e2"},
|
"cachex": {:hex, :cachex, "3.6.0", "14a1bfbeee060dd9bec25a5b6f4e4691e3670ebda28c8ba2884b12fe30b36bf8", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "ebf24e373883bc8e0c8d894a63bbe102ae13d918f790121f5cfe6e485cc8e2e2"},
|
||||||
"calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.1.201603 or ~> 0.5.20 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"},
|
"calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.1.201603 or ~> 0.5.20 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"},
|
||||||
"captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "90f6ce7672f70f56708792a98d98bd05176c9176", [ref: "90f6ce7672f70f56708792a98d98bd05176c9176"]},
|
"captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "90f6ce7672f70f56708792a98d98bd05176c9176", [ref: "90f6ce7672f70f56708792a98d98bd05176c9176"]},
|
||||||
"castore": {:hex, :castore, "1.0.5", "9eeebb394cc9a0f3ae56b813459f990abb0a3dedee1be6b27fdb50301930502f", [:mix], [], "hexpm", "8d7c597c3e4a64c395980882d4bca3cebb8d74197c590dc272cfd3b6a6310578"},
|
"castore": {:hex, :castore, "1.0.6", "ffc42f110ebfdafab0ea159cd43d31365fa0af0ce4a02ecebf1707ae619ee727", [:mix], [], "hexpm", "374c6e7ca752296be3d6780a6d5b922854ffcc74123da90f2f328996b962d33a"},
|
||||||
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
|
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
|
||||||
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
|
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
|
||||||
"comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"},
|
"comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"},
|
||||||
"concurrent_limiter": {:git, "https://akkoma.dev/AkkomaGang/concurrent-limiter.git", "a9e0b3d64574bdba761f429bb4fba0cf687b3338", [ref: "a9e0b3d64574bdba761f429bb4fba0cf687b3338"]},
|
"concurrent_limiter": {:git, "https://akkoma.dev/AkkomaGang/concurrent-limiter.git", "a9e0b3d64574bdba761f429bb4fba0cf687b3338", [ref: "a9e0b3d64574bdba761f429bb4fba0cf687b3338"]},
|
||||||
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
|
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
|
||||||
"cors_plug": {:hex, :cors_plug, "3.0.3", "7c3ac52b39624bc616db2e937c282f3f623f25f8d550068b6710e58d04a0e330", [:mix], [{:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3f2d759e8c272ed3835fab2ef11b46bddab8c1ab9528167bd463b6452edf830d"},
|
"cors_plug": {:hex, :cors_plug, "3.0.3", "7c3ac52b39624bc616db2e937c282f3f623f25f8d550068b6710e58d04a0e330", [:mix], [{:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3f2d759e8c272ed3835fab2ef11b46bddab8c1ab9528167bd463b6452edf830d"},
|
||||||
"cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"},
|
"cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"},
|
||||||
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
|
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
|
||||||
"cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"},
|
"cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"},
|
||||||
"credo": {:hex, :credo, "1.7.1", "6e26bbcc9e22eefbff7e43188e69924e78818e2fe6282487d0703652bc20fd62", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e9871c6095a4c0381c89b6aa98bc6260a8ba6addccf7f6a53da8849c748a58a2"},
|
"credo": {:hex, :credo, "1.7.5", "643213503b1c766ec0496d828c90c424471ea54da77c8a168c725686377b9545", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "f799e9b5cd1891577d8c773d245668aa74a2fcd15eb277f51a0131690ebfb3fd"},
|
||||||
"custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},
|
"custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},
|
||||||
"db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"},
|
"db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"},
|
||||||
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
|
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
|
||||||
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
|
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
|
||||||
"dialyxir": {:hex, :dialyxir, "1.4.2", "764a6e8e7a354f0ba95d58418178d486065ead1f69ad89782817c296d0d746a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "516603d8067b2fd585319e4b13d3674ad4f314a5902ba8130cd97dc902ce6bbd"},
|
"dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"},
|
||||||
"earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"},
|
"earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"},
|
||||||
"earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"},
|
"earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"},
|
||||||
"eblurhash": {:hex, :eblurhash, "1.2.2", "7da4255aaea984b31bb71155f673257353b0e0554d0d30dcf859547e74602582", [:rebar3], [], "hexpm", "8c20ca00904de023a835a9dcb7b7762fed32264c85a80c3cafa85288e405044c"},
|
"eblurhash": {:hex, :eblurhash, "1.2.2", "7da4255aaea984b31bb71155f673257353b0e0554d0d30dcf859547e74602582", [:rebar3], [], "hexpm", "8c20ca00904de023a835a9dcb7b7762fed32264c85a80c3cafa85288e405044c"},
|
||||||
"ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"},
|
"ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"},
|
||||||
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
|
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
|
||||||
"ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.14", "7a20cfe913b0476542b43870e67386461258734896035e3f284039fd18bd4c4c", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "22f5f98592dd597db9416fcef00effae0787669fdcb6faf447e982b553798e98"},
|
"ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.15", "0fc29dbae0e444a29bd6abeee4cf3c4c037e692a272478a234a1cc765077dbb1", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "b6127f3a5c6fc3d84895e4768cc7c199f22b48b67d6c99b13fbf4a374e73f039"},
|
||||||
"ecto_sql": {:hex, :ecto_sql, "3.10.2", "6b98b46534b5c2f8b8b5f03f126e75e2a73c64f3c071149d32987a5378b0fdbd", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007"},
|
"ecto_sql": {:hex, :ecto_sql, "3.10.2", "6b98b46534b5c2f8b8b5f03f126e75e2a73c64f3c071149d32987a5378b0fdbd", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007"},
|
||||||
"elasticsearch": {:git, "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", "6cd946f75f6ab9042521a009d1d32d29a90113ca", [ref: "main"]},
|
"elasticsearch": {:git, "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", "6cd946f75f6ab9042521a009d1d32d29a90113ca", [ref: "main"]},
|
||||||
"elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"},
|
"elixir_make": {:hex, :elixir_make, "0.8.3", "d38d7ee1578d722d89b4d452a3e36bcfdc644c618f0d063b874661876e708683", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "5c99a18571a756d4af7a4d89ca75c28ac899e6103af6f223982f09ce44942cc9"},
|
||||||
"elixir_xml_to_map": {:hex, :elixir_xml_to_map, "3.0.0", "67dcff30ecf72aed37ab08525133e4420717a749436e22bfece431e7dddeea7e", [:mix], [{:erlsom, "~> 1.4", [hex: :erlsom, repo: "hexpm", optional: false]}], "hexpm", "11222dd7f029f8db7a6662b41c992dbdb0e1c6e4fdea6a42056f9d27c847efbb"},
|
"elixir_xml_to_map": {:hex, :elixir_xml_to_map, "3.1.0", "4d6260486a8cce59e4bf3575fe2dd2a24766546ceeef9f93fcec6f7c62a2827a", [:mix], [{:erlsom, "~> 1.4", [hex: :erlsom, repo: "hexpm", optional: false]}], "hexpm", "8fe5f2e75f90bab07ee2161120c2dc038ebcae8135554f5582990f1c8c21f911"},
|
||||||
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
|
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
|
||||||
"erlsom": {:hex, :erlsom, "1.5.1", "c8fe2babd33ff0846403f6522328b8ab676f896b793634cfe7ef181c05316c03", [:rebar3], [], "hexpm", "7965485494c5844dd127656ac40f141aadfa174839ec1be1074e7edf5b4239eb"},
|
"erlsom": {:hex, :erlsom, "1.5.1", "c8fe2babd33ff0846403f6522328b8ab676f896b793634cfe7ef181c05316c03", [:rebar3], [], "hexpm", "7965485494c5844dd127656ac40f141aadfa174839ec1be1074e7edf5b4239eb"},
|
||||||
"eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"},
|
"eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"},
|
||||||
"ex_aws": {:hex, :ex_aws, "2.5.0", "1785e69350b16514c1049330537c7da10039b1a53e1d253bbd703b135174aec3", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "971b86e5495fc0ae1c318e35e23f389e74cf322f2c02d34037c6fc6d405006f1"},
|
"ex_aws": {:hex, :ex_aws, "2.5.3", "9c2d05ba0c057395b12c7b5ca6267d14cdaec1d8e65bdf6481fe1fd245accfb4", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "67115f1d399d7ec4d191812ee565c6106cb4b1bbf19a9d4db06f265fd87da97e"},
|
||||||
"ex_aws_s3": {:hex, :ex_aws_s3, "2.5.2", "cee302b8e9ee198cc0d89f1de2a7d6a8921e1a556574476cf5590d2156590fe3", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "cc5bd945a22a99eece4721d734ae2452d3717e81c357a781c8574663254df4a1"},
|
"ex_aws_s3": {:hex, :ex_aws_s3, "2.5.3", "422468e5c3e1a4da5298e66c3468b465cfd354b842e512cb1f6fbbe4e2f5bdaf", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "4f09dd372cc386550e484808c5ac5027766c8d0cd8271ccc578b82ee6ef4f3b8"},
|
||||||
"ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"},
|
"ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"},
|
||||||
"ex_doc": {:hex, :ex_doc, "0.31.0", "06eb1dfd787445d9cab9a45088405593dd3bb7fe99e097eaa71f37ba80c7a676", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "5350cafa6b7f77bdd107aa2199fe277acf29d739aba5aee7e865fc680c62a110"},
|
"ex_doc": {:hex, :ex_doc, "0.32.0", "896afb57b1e00030f6ec8b2e19d3ca99a197afb23858d49d94aea673dc222f12", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "ed2c3e42c558f49bda3ff37e05713432006e1719a6c4a3320c7e4735787374e7"},
|
||||||
"ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"},
|
"ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"},
|
||||||
"ex_syslogger": {:hex, :ex_syslogger, "2.0.0", "de6de5c5472a9c4fdafb28fa6610e381ae79ebc17da6490b81d785d68bd124c9", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "a52b2fe71764e9e6ecd149ab66635812f68e39279cbeee27c52c0e35e8b8019e"},
|
"ex_syslogger": {:hex, :ex_syslogger, "2.0.0", "de6de5c5472a9c4fdafb28fa6610e381ae79ebc17da6490b81d785d68bd124c9", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "a52b2fe71764e9e6ecd149ab66635812f68e39279cbeee27c52c0e35e8b8019e"},
|
||||||
"excoveralls": {:hex, :excoveralls, "0.16.1", "0bd42ed05c7d2f4d180331a20113ec537be509da31fed5c8f7047ce59ee5a7c5", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dae763468e2008cf7075a64cb1249c97cb4bc71e236c5c2b5e5cdf1cfa2bf138"},
|
"excoveralls": {:hex, :excoveralls, "0.16.1", "0bd42ed05c7d2f4d180331a20113ec537be509da31fed5c8f7047ce59ee5a7c5", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dae763468e2008cf7075a64cb1249c97cb4bc71e236c5c2b5e5cdf1cfa2bf138"},
|
||||||
"expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"},
|
"expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"},
|
||||||
"fast_html": {:hex, :fast_html, "2.2.0", "6c5ef1be087a4ed613b0379c13f815c4d11742b36b67bb52cee7859847c84520", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "064c4f23b4a6168f9187dac8984b056f2c531bb0787f559fd6a8b34b38aefbae"},
|
"fast_html": {:hex, :fast_html, "2.3.0", "08c1d8ead840dd3060ba02c761bed9f37f456a1ddfe30bcdcfee8f651cec06a6", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "f18e3c7668f82d3ae0b15f48d48feeb257e28aa5ab1b0dbf781c7312e5da029d"},
|
||||||
"fast_sanitize": {:hex, :fast_sanitize, "0.2.3", "67b93dfb34e302bef49fec3aaab74951e0f0602fd9fa99085987af05bd91c7a5", [:mix], [{:fast_html, "~> 2.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "e8ad286d10d0386e15d67d0ee125245ebcfbc7d7290b08712ba9013c8c5e56e2"},
|
"fast_sanitize": {:hex, :fast_sanitize, "0.2.3", "67b93dfb34e302bef49fec3aaab74951e0f0602fd9fa99085987af05bd91c7a5", [:mix], [{:fast_html, "~> 2.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "e8ad286d10d0386e15d67d0ee125245ebcfbc7d7290b08712ba9013c8c5e56e2"},
|
||||||
"file_ex": {:git, "https://akkoma.dev/AkkomaGang/file_ex.git", "cc7067c7d446c2526e9ecf91d40896b088851569", [ref: "cc7067c7d446c2526e9ecf91d40896b088851569"]},
|
"file_ex": {:git, "https://akkoma.dev/AkkomaGang/file_ex.git", "cc7067c7d446c2526e9ecf91d40896b088851569", [ref: "cc7067c7d446c2526e9ecf91d40896b088851569"]},
|
||||||
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
|
"file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"},
|
||||||
"finch": {:hex, :finch, "0.16.0", "40733f02c89f94a112518071c0a91fe86069560f5dbdb39f9150042f44dcfb1a", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f660174c4d519e5fec629016054d60edd822cdfe2b7270836739ac2f97735ec5"},
|
"finch": {:hex, :finch, "0.16.0", "40733f02c89f94a112518071c0a91fe86069560f5dbdb39f9150042f44dcfb1a", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f660174c4d519e5fec629016054d60edd822cdfe2b7270836739ac2f97735ec5"},
|
||||||
"flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"},
|
"flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"},
|
||||||
"floki": {:hex, :floki, "0.35.2", "87f8c75ed8654b9635b311774308b2760b47e9a579dabf2e4d5f1e1d42c39e0b", [:mix], [], "hexpm", "6b05289a8e9eac475f644f09c2e4ba7e19201fd002b89c28c1293e7bd16773d9"},
|
"floki": {:hex, :floki, "0.36.1", "712b7f2ba19a4d5a47dfe3e74d81876c95bbcbee44fe551f0af3d2a388abb3da", [:mix], [], "hexpm", "21ba57abb8204bcc70c439b423fc0dd9f0286de67dc82773a14b0200ada0995f"},
|
||||||
"gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"},
|
"gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"},
|
||||||
"gettext": {:hex, :gettext, "0.22.3", "c8273e78db4a0bb6fba7e9f0fd881112f349a3117f7f7c598fa18c66c888e524", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "935f23447713954a6866f1bb28c3a878c4c011e802bcd68a726f5e558e4b64bd"},
|
"gettext": {:hex, :gettext, "0.22.3", "c8273e78db4a0bb6fba7e9f0fd881112f349a3117f7f7c598fa18c66c888e524", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "935f23447713954a6866f1bb28c3a878c4c011e802bcd68a726f5e558e4b64bd"},
|
||||||
"hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"},
|
"hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"},
|
||||||
|
@ -60,17 +60,17 @@
|
||||||
"http_signatures": {:git, "https://akkoma.dev/AkkomaGang/http_signatures.git", "6640ce7d24c783ac2ef56e27d00d12e8dc85f396", [ref: "6640ce7d24c783ac2ef56e27d00d12e8dc85f396"]},
|
"http_signatures": {:git, "https://akkoma.dev/AkkomaGang/http_signatures.git", "6640ce7d24c783ac2ef56e27d00d12e8dc85f396", [ref: "6640ce7d24c783ac2ef56e27d00d12e8dc85f396"]},
|
||||||
"httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"},
|
"httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"},
|
||||||
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
|
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
|
||||||
"inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"},
|
"inet_cidr": {:hex, :inet_cidr, "1.0.8", "d26bb7bdbdf21ae401ead2092bf2bb4bf57fe44a62f5eaa5025280720ace8a40", [:mix], [], "hexpm", "d5b26da66603bb56c933c65214c72152f0de9a6ea53618b56d63302a68f6a90e"},
|
||||||
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
|
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
|
||||||
"joken": {:hex, :joken, "2.6.0", "b9dd9b6d52e3e6fcb6c65e151ad38bf4bc286382b5b6f97079c47ade6b1bcc6a", [:mix], [{:jose, "~> 1.11.5", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5a95b05a71cd0b54abd35378aeb1d487a23a52c324fa7efdffc512b655b5aaa7"},
|
"joken": {:hex, :joken, "2.6.1", "2ca3d8d7f83bf7196296a3d9b2ecda421a404634bfc618159981a960020480a1", [:mix], [{:jose, "~> 1.11.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "ab26122c400b3d254ce7d86ed066d6afad27e70416df947cdcb01e13a7382e68"},
|
||||||
"jose": {:hex, :jose, "1.11.6", "613fda82552128aa6fb804682e3a616f4bc15565a048dabd05b1ebd5827ed965", [:mix, :rebar3], [], "hexpm", "6275cb75504f9c1e60eeacb771adfeee4905a9e182103aa59b53fed651ff9738"},
|
"jose": {:hex, :jose, "1.11.9", "c861eb99d9e9f62acd071dc5a49ffbeab9014e44490cd85ea3e49e3d36184777", [:mix, :rebar3], [], "hexpm", "b5ccc3749d2e1638c26bed806259df5bc9e438797fe60dc71e9fa0716133899b"},
|
||||||
"jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"},
|
"jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"},
|
||||||
"linkify": {:git, "https://akkoma.dev/AkkomaGang/linkify.git", "2567e2c1073fa371fd26fd66dfa5bc77b6919c16", []},
|
"linkify": {:git, "https://akkoma.dev/AkkomaGang/linkify.git", "2567e2c1073fa371fd26fd66dfa5bc77b6919c16", []},
|
||||||
"mail": {:hex, :mail, "0.3.1", "cb0a14e4ed8904e4e5a08214e686ccf6f9099346885db17d8c309381f865cc5c", [:mix], [], "hexpm", "1db701e89865c1d5fa296b2b57b1cd587587cca8d8a1a22892b35ef5a8e352a6"},
|
"mail": {:hex, :mail, "0.3.1", "cb0a14e4ed8904e4e5a08214e686ccf6f9099346885db17d8c309381f865cc5c", [:mix], [], "hexpm", "1db701e89865c1d5fa296b2b57b1cd587587cca8d8a1a22892b35ef5a8e352a6"},
|
||||||
"majic": {:git, "https://akkoma.dev/AkkomaGang/majic.git", "80540b36939ec83f48e76c61e5000e0fd67706f0", [ref: "80540b36939ec83f48e76c61e5000e0fd67706f0"]},
|
"majic": {:git, "https://akkoma.dev/AkkomaGang/majic.git", "80540b36939ec83f48e76c61e5000e0fd67706f0", [ref: "80540b36939ec83f48e76c61e5000e0fd67706f0"]},
|
||||||
"makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"},
|
"makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"},
|
||||||
"makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"},
|
"makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"},
|
||||||
"makeup_erlang": {:hex, :makeup_erlang, "0.1.3", "d684f4bac8690e70b06eb52dad65d26de2eefa44cd19d64a8095e1417df7c8fd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "b78dc853d2e670ff6390b605d807263bf606da3c82be37f9d7f68635bd886fc9"},
|
"makeup_erlang": {:hex, :makeup_erlang, "0.1.5", "e0ff5a7c708dda34311f7522a8758e23bfcd7d8d8068dc312b5eb41c6fd76eba", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "94d2e986428585a21516d7d7149781480013c56e30c6a233534bedf38867a59a"},
|
||||||
"meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"},
|
"meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"},
|
||||||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
|
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
|
||||||
"mfm_parser": {:git, "https://akkoma.dev/AkkomaGang/mfm-parser.git", "b21ab7754024af096f2d14247574f55f0063295b", [ref: "b21ab7754024af096f2d14247574f55f0063295b"]},
|
"mfm_parser": {:git, "https://akkoma.dev/AkkomaGang/mfm-parser.git", "b21ab7754024af096f2d14247574f55f0063295b", [ref: "b21ab7754024af096f2d14247574f55f0063295b"]},
|
||||||
|
@ -82,54 +82,54 @@
|
||||||
"mox": {:hex, :mox, "1.1.0", "0f5e399649ce9ab7602f72e718305c0f9cdc351190f72844599545e4996af73c", [:mix], [], "hexpm", "d44474c50be02d5b72131070281a5d3895c0e7a95c780e90bc0cfe712f633a13"},
|
"mox": {:hex, :mox, "1.1.0", "0f5e399649ce9ab7602f72e718305c0f9cdc351190f72844599545e4996af73c", [:mix], [], "hexpm", "d44474c50be02d5b72131070281a5d3895c0e7a95c780e90bc0cfe712f633a13"},
|
||||||
"nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"},
|
"nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"},
|
||||||
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
|
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
|
||||||
"nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"},
|
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
|
||||||
"oban": {:hex, :oban, "2.15.4", "d49ab4ffb7153010e32f80fe9e56f592706238149ec579eb50f8a4e41d218856", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5fce611fdfffb13e9148df883116e5201adf1e731eb302cc88cde0588510079c"},
|
"oban": {:hex, :oban, "2.17.8", "7fd7c8e82c7819afc1b5b5ed8d6d92bf0ecdd7ba170328fb043301eb06d32521", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a2165bf93843b7bcb68182c82725ddd4cb43c0c3719f114e7aa3b6c99c4b6129"},
|
||||||
"open_api_spex": {:hex, :open_api_spex, "3.18.0", "f9952b6bc8a1bf14168f3754981b7c8d72d015112bfedf2588471dd602e1e715", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "37849887ab67efab052376401fac28c0974b273ffaecd98f4532455ca0886464"},
|
"open_api_spex": {:hex, :open_api_spex, "3.18.3", "fefb84fe323cacfc92afdd0ecb9e89bc0261ae00b7e3167ffc2028ce3944de42", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "c0cfc31570199ce7e7520b494a591027da609af45f6bf9adce51e2469b1609fb"},
|
||||||
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
|
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
|
||||||
"phoenix": {:hex, :phoenix, "1.7.10", "02189140a61b2ce85bb633a9b6fd02dff705a5f1596869547aeb2b2b95edd729", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "cf784932e010fd736d656d7fead6a584a4498efefe5b8227e9f383bf15bb79d0"},
|
"phoenix": {:hex, :phoenix, "1.7.12", "1cc589e0eab99f593a8aa38ec45f15d25297dd6187ee801c8de8947090b5a9d3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "d646192fbade9f485b01bc9920c139bfdd19d0f8df3d73fd8eaf2dfbe0d2837c"},
|
||||||
"phoenix_ecto": {:hex, :phoenix_ecto, "4.4.3", "86e9878f833829c3f66da03d75254c155d91d72a201eb56ae83482328dc7ca93", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d36c401206f3011fefd63d04e8ef626ec8791975d9d107f9a0817d426f61ac07"},
|
"phoenix_ecto": {:hex, :phoenix_ecto, "4.5.1", "6fdbc334ea53620e71655664df6f33f670747b3a7a6c4041cdda3e2c32df6257", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ebe43aa580db129e54408e719fb9659b7f9e0d52b965c5be26cdca416ecead28"},
|
||||||
"phoenix_html": {:hex, :phoenix_html, "3.3.3", "380b8fb45912b5638d2f1d925a3771b4516b9a78587249cabe394e0a5d579dc9", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "923ebe6fec6e2e3b3e569dfbdc6560de932cd54b000ada0208b5f45024bdd76c"},
|
"phoenix_html": {:hex, :phoenix_html, "3.3.3", "380b8fb45912b5638d2f1d925a3771b4516b9a78587249cabe394e0a5d579dc9", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "923ebe6fec6e2e3b3e569dfbdc6560de932cd54b000ada0208b5f45024bdd76c"},
|
||||||
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"},
|
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"},
|
||||||
"phoenix_live_view": {:hex, :phoenix_live_view, "0.18.18", "1f38fbd7c363723f19aad1a04b5490ff3a178e37daaf6999594d5f34796c47fc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214"},
|
"phoenix_live_view": {:hex, :phoenix_live_view, "0.18.18", "1f38fbd7c363723f19aad1a04b5490ff3a178e37daaf6999594d5f34796c47fc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214"},
|
||||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
|
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
|
||||||
"phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.0", "a544d83fde4a767efb78f45404a74c9e37b2a9c5ea3339692e65a6966731f935", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "e88d117251e89a16b92222415a6d87b99a96747ddf674fc5c7631de734811dba"},
|
"phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.1", "b74ccaa8046fbc388a62134360ee7d9742d5a8ae74063f34eb050279de7a99e1", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "4000eeba3f9d7d1a6bf56d2bd56733d5cadf41a7f0d8ffe5bb67e7d667e204a2"},
|
||||||
"phoenix_template": {:hex, :phoenix_template, "1.0.3", "32de561eefcefa951aead30a1f94f1b5f0379bc9e340bb5c667f65f1edfa4326", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "16f4b6588a4152f3cc057b9d0c0ba7e82ee23afa65543da535313ad8d25d8e2c"},
|
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
|
||||||
"phoenix_view": {:hex, :phoenix_view, "2.0.3", "4d32c4817fce933693741deeb99ef1392619f942633dde834a5163124813aad3", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "cd34049af41be2c627df99cd4eaa71fc52a328c0c3d8e7d4aa28f880c30e7f64"},
|
"phoenix_view": {:hex, :phoenix_view, "2.0.3", "4d32c4817fce933693741deeb99ef1392619f942633dde834a5163124813aad3", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "cd34049af41be2c627df99cd4eaa71fc52a328c0c3d8e7d4aa28f880c30e7f64"},
|
||||||
"plug": {:hex, :plug, "1.15.2", "94cf1fa375526f30ff8770837cb804798e0045fd97185f0bb9e5fcd858c792a3", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02731fa0c2dcb03d8d21a1d941bdbbe99c2946c0db098eee31008e04c6283615"},
|
"plug": {:hex, :plug, "1.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"},
|
||||||
"plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"},
|
"plug_cowboy": {:hex, :plug_cowboy, "2.7.1", "87677ffe3b765bc96a89be7960f81703223fe2e21efa42c125fcd0127dd9d6b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "02dbd5f9ab571b864ae39418db7811618506256f6d13b4a45037e5fe78dc5de3"},
|
||||||
"plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"},
|
"plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"},
|
||||||
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"},
|
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"},
|
||||||
"poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"},
|
"poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"},
|
||||||
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
|
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
|
||||||
"postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"},
|
"postgrex": {:hex, :postgrex, "0.17.5", "0483d054938a8dc069b21bdd636bf56c487404c241ce6c319c1f43588246b281", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "50b8b11afbb2c4095a3ba675b4f055c416d0f3d7de6633a595fc131a828a67eb"},
|
||||||
"pot": {:hex, :pot, "1.0.2", "13abb849139fdc04ab8154986abbcb63bdee5de6ed2ba7e1713527e33df923dd", [:rebar3], [], "hexpm", "78fe127f5a4f5f919d6ea5a2a671827bd53eb9d37e5b4128c0ad3df99856c2e0"},
|
"pot": {:hex, :pot, "1.0.2", "13abb849139fdc04ab8154986abbcb63bdee5de6ed2ba7e1713527e33df923dd", [:rebar3], [], "hexpm", "78fe127f5a4f5f919d6ea5a2a671827bd53eb9d37e5b4128c0ad3df99856c2e0"},
|
||||||
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
|
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
|
||||||
"recon": {:hex, :recon, "2.5.4", "05dd52a119ee4059fa9daa1ab7ce81bc7a8161a2f12e9d42e9d551ffd2ba901c", [:mix, :rebar3], [], "hexpm", "e9ab01ac7fc8572e41eb59385efeb3fb0ff5bf02103816535bacaedf327d0263"},
|
"recon": {:hex, :recon, "2.5.5", "c108a4c406fa301a529151a3bb53158cadc4064ec0c5f99b03ddb8c0e4281bdf", [:mix, :rebar3], [], "hexpm", "632a6f447df7ccc1a4a10bdcfce71514412b16660fe59deca0fcf0aa3c054404"},
|
||||||
"remote_ip": {:hex, :remote_ip, "1.1.0", "cb308841595d15df3f9073b7c39243a1dd6ca56e5020295cb012c76fbec50f2d", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "616ffdf66aaad6a72fc546dabf42eed87e2a99e97b09cbd92b10cc180d02ed74"},
|
"remote_ip": {:hex, :remote_ip, "1.1.0", "cb308841595d15df3f9073b7c39243a1dd6ca56e5020295cb012c76fbec50f2d", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "616ffdf66aaad6a72fc546dabf42eed87e2a99e97b09cbd92b10cc180d02ed74"},
|
||||||
"search_parser": {:git, "https://github.com/FloatingGhost/pleroma-contrib-search-parser.git", "08971a81e68686f9ac465cfb6661d51c5e4e1e7f", [ref: "08971a81e68686f9ac465cfb6661d51c5e4e1e7f"]},
|
"search_parser": {:git, "https://github.com/FloatingGhost/pleroma-contrib-search-parser.git", "08971a81e68686f9ac465cfb6661d51c5e4e1e7f", [ref: "08971a81e68686f9ac465cfb6661d51c5e4e1e7f"]},
|
||||||
"sleeplocks": {:hex, :sleeplocks, "1.1.2", "d45aa1c5513da48c888715e3381211c859af34bee9b8290490e10c90bb6ff0ca", [:rebar3], [], "hexpm", "9fe5d048c5b781d6305c1a3a0f40bb3dfc06f49bf40571f3d2d0c57eaa7f59a5"},
|
"sleeplocks": {:hex, :sleeplocks, "1.1.2", "d45aa1c5513da48c888715e3381211c859af34bee9b8290490e10c90bb6ff0ca", [:rebar3], [], "hexpm", "9fe5d048c5b781d6305c1a3a0f40bb3dfc06f49bf40571f3d2d0c57eaa7f59a5"},
|
||||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
|
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
|
||||||
"statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},
|
"statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},
|
||||||
"sweet_xml": {:hex, :sweet_xml, "0.7.4", "a8b7e1ce7ecd775c7e8a65d501bc2cd933bff3a9c41ab763f5105688ef485d08", [:mix], [], "hexpm", "e7c4b0bdbf460c928234951def54fe87edf1a170f6896675443279e2dbeba167"},
|
"sweet_xml": {:hex, :sweet_xml, "0.7.4", "a8b7e1ce7ecd775c7e8a65d501bc2cd933bff3a9c41ab763f5105688ef485d08", [:mix], [], "hexpm", "e7c4b0bdbf460c928234951def54fe87edf1a170f6896675443279e2dbeba167"},
|
||||||
"swoosh": {:hex, :swoosh, "1.14.2", "cf686f92ad3b21e6651b20c50eeb1781f581dc7097ef6251b4d322a9f1d19339", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.4 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "01d8fae72930a0b5c1bb9725df0408602ed8c5c3d59dc6e7a39c57b723cd1065"},
|
"swoosh": {:hex, :swoosh, "1.14.4", "94e9dba91f7695a10f49b0172c4a4cb658ef24abef7e8140394521b7f3bbb2d4", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.4 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "081c5a590e4ba85cc89baddf7b2beecf6c13f7f84a958f1cd969290815f0f026"},
|
||||||
"syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
|
"syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
|
||||||
"table_rex": {:hex, :table_rex, "3.1.1", "0c67164d1714b5e806d5067c1e96ff098ba7ae79413cc075973e17c38a587caa", [:mix], [], "hexpm", "678a23aba4d670419c23c17790f9dcd635a4a89022040df7d5d772cb21012490"},
|
"table_rex": {:hex, :table_rex, "4.0.0", "3c613a68ebdc6d4d1e731bc973c233500974ec3993c99fcdabb210407b90959b", [:mix], [], "hexpm", "c35c4d5612ca49ebb0344ea10387da4d2afe278387d4019e4d8111e815df8f55"},
|
||||||
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
|
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
|
||||||
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"},
|
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"},
|
||||||
"telemetry_metrics_prometheus": {:hex, :telemetry_metrics_prometheus, "1.1.0", "1cc23e932c1ef9aa3b91db257ead31ea58d53229d407e059b29bb962c1505a13", [:mix], [{:plug_cowboy, "~> 2.1", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.0", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}], "hexpm", "d43b3659b3244da44fe0275b717701542365d4519b79d9ce895b9719c1ce4d26"},
|
"telemetry_metrics_prometheus": {:hex, :telemetry_metrics_prometheus, "1.1.0", "1cc23e932c1ef9aa3b91db257ead31ea58d53229d407e059b29bb962c1505a13", [:mix], [{:plug_cowboy, "~> 2.1", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.0", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}], "hexpm", "d43b3659b3244da44fe0275b717701542365d4519b79d9ce895b9719c1ce4d26"},
|
||||||
"telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.1.0", "4e15f6d7dbedb3a4e3aed2262b7e1407f166fcb9c30ca3f96635dfbbef99965c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0dd10e7fe8070095df063798f82709b0a1224c31b8baf6278b423898d591a069"},
|
"telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.1.0", "4e15f6d7dbedb3a4e3aed2262b7e1407f166fcb9c30ca3f96635dfbbef99965c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0dd10e7fe8070095df063798f82709b0a1224c31b8baf6278b423898d591a069"},
|
||||||
"telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"},
|
"telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"},
|
||||||
"temple": {:git, "https://akkoma.dev/AkkomaGang/temple.git", "066a699ade472d8fa42a9d730b29a61af9bc8b59", [ref: "066a699ade472d8fa42a9d730b29a61af9bc8b59"]},
|
"temple": {:git, "https://akkoma.dev/AkkomaGang/temple.git", "066a699ade472d8fa42a9d730b29a61af9bc8b59", [ref: "066a699ade472d8fa42a9d730b29a61af9bc8b59"]},
|
||||||
"tesla": {:hex, :tesla, "1.8.0", "d511a4f5c5e42538d97eef7c40ec4f3e44effdc5068206f42ed859e09e51d1fd", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "10501f360cd926a309501287470372af1a6e1cbed0f43949203a4c13300bc79f"},
|
"tesla": {:hex, :tesla, "1.9.0", "8c22db6a826e56a087eeb8cdef56889731287f53feeb3f361dec5d4c8efb6f14", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "7c240c67e855f7e63e795bf16d6b3f5115a81d1f44b7fe4eadbf656bae0fef8a"},
|
||||||
"timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"},
|
"timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"},
|
||||||
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
|
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
|
||||||
"tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"},
|
"tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"},
|
||||||
"ueberauth": {:hex, :ueberauth, "0.10.5", "806adb703df87e55b5615cf365e809f84c20c68aa8c08ff8a416a5a6644c4b02", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3efd1f31d490a125c7ed453b926f7c31d78b97b8a854c755f5c40064bf3ac9e1"},
|
"ueberauth": {:hex, :ueberauth, "0.10.5", "806adb703df87e55b5615cf365e809f84c20c68aa8c08ff8a416a5a6644c4b02", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3efd1f31d490a125c7ed453b926f7c31d78b97b8a854c755f5c40064bf3ac9e1"},
|
||||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
|
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
|
||||||
"unsafe": {:hex, :unsafe, "1.0.2", "23c6be12f6c1605364801f4b47007c0c159497d0446ad378b5cf05f1855c0581", [:mix], [], "hexpm", "b485231683c3ab01a9cd44cb4a79f152c6f3bb87358439c6f68791b85c2df675"},
|
"unsafe": {:hex, :unsafe, "1.0.2", "23c6be12f6c1605364801f4b47007c0c159497d0446ad378b5cf05f1855c0581", [:mix], [], "hexpm", "b485231683c3ab01a9cd44cb4a79f152c6f3bb87358439c6f68791b85c2df675"},
|
||||||
"vex": {:hex, :vex, "0.9.1", "cb65348ebd1c4002861b65bef36e524c29d9a879c90119b2d0e674e323124277", [:mix], [], "hexpm", "a0f9f3959d127ad6a6a617c3f607ecfb1bc6f3c59f9c3614a901a46d1765bafe"},
|
"vex": {:hex, :vex, "0.9.2", "fe061acc9e0907d983d46b51bf35d58176f0fe6eb7ba3b33c9336401bf42b6d1", [:mix], [], "hexpm", "76e709a9762e98c6b462dfce92e9b5dfbf712839227f2da8add6dd11549b12cb"},
|
||||||
"web_push_encryption": {:hex, :web_push_encryption, "0.3.1", "76d0e7375142dfee67391e7690e89f92578889cbcf2879377900b5620ee4708d", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.11.1", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "4f82b2e57622fb9337559058e8797cb0df7e7c9790793bdc4e40bc895f70e2a2"},
|
"web_push_encryption": {:hex, :web_push_encryption, "0.3.1", "76d0e7375142dfee67391e7690e89f92578889cbcf2879377900b5620ee4708d", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.11.1", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "4f82b2e57622fb9337559058e8797cb0df7e7c9790793bdc4e40bc895f70e2a2"},
|
||||||
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
|
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
|
||||||
"websock_adapter": {:hex, :websock_adapter, "0.5.5", "9dfeee8269b27e958a65b3e235b7e447769f66b5b5925385f5a569269164a210", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "4b977ba4a01918acbf77045ff88de7f6972c2a009213c515a445c48f224ffce9"},
|
"websock_adapter": {:hex, :websock_adapter, "0.5.6", "0437fe56e093fd4ac422de33bf8fc89f7bc1416a3f2d732d8b2c8fd54792fe60", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "e04378d26b0af627817ae84c92083b7e97aca3121196679b73c73b99d0d133ea"},
|
||||||
"websockex": {:hex, :websockex, "0.4.3", "92b7905769c79c6480c02daacaca2ddd49de936d912976a4d3c923723b647bf0", [:mix], [], "hexpm", "95f2e7072b85a3a4cc385602d42115b73ce0b74a9121d0d6dbbf557645ac53e4"},
|
"websockex": {:hex, :websockex, "0.4.3", "92b7905769c79c6480c02daacaca2ddd49de936d912976a4d3c923723b647bf0", [:mix], [], "hexpm", "95f2e7072b85a3a4cc385602d42115b73ce0b74a9121d0d6dbbf557645ac53e4"},
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.UploadFilterExiftoolToExiftoolStripMetadata do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
alias Pleroma.ConfigDB
|
||||||
|
|
||||||
|
def up,
|
||||||
|
do:
|
||||||
|
ConfigDB.get_by_params(%{group: :pleroma, key: Pleroma.Upload})
|
||||||
|
|> update_filtername(
|
||||||
|
Pleroma.Upload.Filter.Exiftool,
|
||||||
|
Pleroma.Upload.Filter.Exiftool.StripMetadata
|
||||||
|
)
|
||||||
|
|
||||||
|
def down,
|
||||||
|
do:
|
||||||
|
ConfigDB.get_by_params(%{group: :pleroma, key: Pleroma.Upload})
|
||||||
|
|> update_filtername(
|
||||||
|
Pleroma.Upload.Filter.Exiftool.StripMetadata,
|
||||||
|
Pleroma.Upload.Filter.Exiftool
|
||||||
|
)
|
||||||
|
|
||||||
|
defp update_filtername(%{value: value}, from_filtername, to_filtername) do
|
||||||
|
new_value =
|
||||||
|
value
|
||||||
|
|> Keyword.update(:filters, [], fn filters ->
|
||||||
|
filters
|
||||||
|
|> Enum.map(fn
|
||||||
|
^from_filtername -> to_filtername
|
||||||
|
filter -> filter
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
ConfigDB.update_or_create(%{group: :pleroma, key: Pleroma.Upload, value: new_value})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp update_filtername(_, _, _), do: nil
|
||||||
|
end
|
|
@ -19,6 +19,7 @@
|
||||||
"toot": "http://joinmastodon.org/ns#",
|
"toot": "http://joinmastodon.org/ns#",
|
||||||
"misskey": "https://misskey-hub.net/ns#",
|
"misskey": "https://misskey-hub.net/ns#",
|
||||||
"fedibird": "http://fedibird.com/ns#",
|
"fedibird": "http://fedibird.com/ns#",
|
||||||
|
"sharkey": "https://joinsharkey.org/ns#",
|
||||||
"value": "schema:value",
|
"value": "schema:value",
|
||||||
"sensitive": "as:sensitive",
|
"sensitive": "as:sensitive",
|
||||||
"litepub": "http://litepub.social/ns#",
|
"litepub": "http://litepub.social/ns#",
|
||||||
|
@ -45,6 +46,14 @@
|
||||||
"contentMap": {
|
"contentMap": {
|
||||||
"@id": "as:content",
|
"@id": "as:content",
|
||||||
"@container": "@language"
|
"@container": "@language"
|
||||||
|
},
|
||||||
|
"featured": {
|
||||||
|
"@id": "toot:featured",
|
||||||
|
"@type": "@id"
|
||||||
|
},
|
||||||
|
"backgroundUrl": {
|
||||||
|
"@id": "sharkey:backgroundUrl",
|
||||||
|
"@type": "@id"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -117,6 +117,7 @@ .inner-nav a {
|
||||||
|
|
||||||
.inner-nav img {
|
.inner-nav img {
|
||||||
height: 28px;
|
height: 28px;
|
||||||
|
width: auto;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
padding-right: 5px
|
padding-right: 5px
|
||||||
}
|
}
|
||||||
|
@ -440,6 +441,7 @@ .user-info .avatar img {
|
||||||
.avatar img {
|
.avatar img {
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
box-shadow: var(--avatarShadow);
|
box-shadow: var(--avatarShadow);
|
||||||
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-summary {
|
.user-summary {
|
||||||
|
@ -642,13 +644,13 @@ @media (max-width: 800px) {
|
||||||
}
|
}
|
||||||
|
|
||||||
img:not(.u-photo, .fa-icon) {
|
img:not(.u-photo, .fa-icon) {
|
||||||
width: 32px;
|
width: auto;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.username img:not(.u-photo) {
|
.username img:not(.u-photo) {
|
||||||
width: 16px;
|
width: auto;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
}
|
}
|
BIN
test/fixtures/image_with_caption-abstract.jpg
vendored
Normal file
BIN
test/fixtures/image_with_caption-abstract.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 697 B |
BIN
test/fixtures/image_with_imagedescription_and_caption-abstract.jpg
vendored
Normal file
BIN
test/fixtures/image_with_imagedescription_and_caption-abstract.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 823 B |
BIN
test/fixtures/image_with_imagedescription_and_caption-abstract_whitespaces.jpg
vendored
Normal file
BIN
test/fixtures/image_with_imagedescription_and_caption-abstract_whitespaces.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 785 B |
BIN
test/fixtures/image_with_no_description.jpg
vendored
Normal file
BIN
test/fixtures/image_with_no_description.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 631 B |
117
test/fixtures/mastodon/service_actor.json
vendored
Normal file
117
test/fixtures/mastodon/service_actor.json
vendored
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://w3id.org/security/v1",
|
||||||
|
{
|
||||||
|
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||||
|
"toot": "http://joinmastodon.org/ns#",
|
||||||
|
"featured": {
|
||||||
|
"@id": "toot:featured",
|
||||||
|
"@type": "@id"
|
||||||
|
},
|
||||||
|
"featuredTags": {
|
||||||
|
"@id": "toot:featuredTags",
|
||||||
|
"@type": "@id"
|
||||||
|
},
|
||||||
|
"alsoKnownAs": {
|
||||||
|
"@id": "as:alsoKnownAs",
|
||||||
|
"@type": "@id"
|
||||||
|
},
|
||||||
|
"movedTo": {
|
||||||
|
"@id": "as:movedTo",
|
||||||
|
"@type": "@id"
|
||||||
|
},
|
||||||
|
"schema": "http://schema.org#",
|
||||||
|
"PropertyValue": "schema:PropertyValue",
|
||||||
|
"value": "schema:value",
|
||||||
|
"discoverable": "toot:discoverable",
|
||||||
|
"Device": "toot:Device",
|
||||||
|
"Ed25519Signature": "toot:Ed25519Signature",
|
||||||
|
"Ed25519Key": "toot:Ed25519Key",
|
||||||
|
"Curve25519Key": "toot:Curve25519Key",
|
||||||
|
"EncryptedMessage": "toot:EncryptedMessage",
|
||||||
|
"publicKeyBase64": "toot:publicKeyBase64",
|
||||||
|
"deviceId": "toot:deviceId",
|
||||||
|
"claim": {
|
||||||
|
"@type": "@id",
|
||||||
|
"@id": "toot:claim"
|
||||||
|
},
|
||||||
|
"fingerprintKey": {
|
||||||
|
"@type": "@id",
|
||||||
|
"@id": "toot:fingerprintKey"
|
||||||
|
},
|
||||||
|
"identityKey": {
|
||||||
|
"@type": "@id",
|
||||||
|
"@id": "toot:identityKey"
|
||||||
|
},
|
||||||
|
"devices": {
|
||||||
|
"@type": "@id",
|
||||||
|
"@id": "toot:devices"
|
||||||
|
},
|
||||||
|
"messageFranking": "toot:messageFranking",
|
||||||
|
"messageType": "toot:messageType",
|
||||||
|
"cipherText": "toot:cipherText",
|
||||||
|
"suspended": "toot:suspended",
|
||||||
|
"memorial": "toot:memorial",
|
||||||
|
"indexable": "toot:indexable",
|
||||||
|
"focalPoint": {
|
||||||
|
"@container": "@list",
|
||||||
|
"@id": "toot:focalPoint"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "https://mastodont.cat/users/fediverse",
|
||||||
|
"type": "Service",
|
||||||
|
"following": "https://mastodont.cat/users/fediverse/following",
|
||||||
|
"followers": "https://mastodont.cat/users/fediverse/followers",
|
||||||
|
"inbox": "https://mastodont.cat/users/fediverse/inbox",
|
||||||
|
"outbox": "https://mastodont.cat/users/fediverse/outbox",
|
||||||
|
"featured": "https://mastodont.cat/users/fediverse/collections/featured",
|
||||||
|
"featuredTags": "https://mastodont.cat/users/fediverse/collections/tags",
|
||||||
|
"preferredUsername": "fediverse",
|
||||||
|
"name": "fediverse's stats",
|
||||||
|
"summary": "<p>All fediverse alive servers stats. New refactored code!</p><p>Ask server info:</p><p><span class=\"h-card\" translate=\"no\"><a href=\"https://mastodont.cat/@fediverse\" class=\"u-url mention\">@<span>fediverse</span></a></span> server example.server</p><p>Ask software info:</p><p><span class=\"h-card\" translate=\"no\"><a href=\"https://mastodont.cat/@fediverse\" class=\"u-url mention\">@<span>fediverse</span></a></span> soft mastodon</p>",
|
||||||
|
"url": "https://mastodont.cat/@fediverse",
|
||||||
|
"manuallyApprovesFollowers": false,
|
||||||
|
"discoverable": true,
|
||||||
|
"indexable": false,
|
||||||
|
"published": "2020-05-13T00:00:00Z",
|
||||||
|
"memorial": false,
|
||||||
|
"devices": "https://mastodont.cat/users/fediverse/collections/devices",
|
||||||
|
"publicKey": {
|
||||||
|
"id": "https://mastodont.cat/users/fediverse#main-key",
|
||||||
|
"owner": "https://mastodont.cat/users/fediverse",
|
||||||
|
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu2X8LqAR/6j95UUTG02T\nWG+PmNRWnfOl+zjDts3OctyJK7at5AwA+T0be1faHpf+oLREl/dkWXc8VQY2UJzY\n8QTuXXnIkwHAeA7WADB6kPvQhVpfGPgKD0dpAgBz9WHFquMSXcnuyt7q1CDn5wId\nRoUtkCAcg1rOX+lIAoeic5hT0O0sXLJdtaSCTZmGqkF2Cf+/16q8XhRevMRh73vP\nX2PefCr63Iy/Zh5rnVhPluQMyQ6FGxXgd5dEKJRa2kxrhIsrm0TzMX892Ev45AwI\ndppYQOQ+nLOgMYrpFNYdOmizJsn635l18K1r/tyDDAegPp6Kfa8v+BaZdOmNTFKr\n/wIDAQAB\n-----END PUBLIC KEY-----\n"
|
||||||
|
},
|
||||||
|
"tag": [],
|
||||||
|
"attachment": [
|
||||||
|
{
|
||||||
|
"type": "PropertyValue",
|
||||||
|
"name": "code",
|
||||||
|
"value": "<a href=\"https://codeberg.org/spla/stats\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"\">codeberg.org/spla/stats</span><span class=\"invisible\"></span></a>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "PropertyValue",
|
||||||
|
"name": "my user-agent",
|
||||||
|
"value": ""fediverse's stats (fediverse@mastodont.cat)""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "PropertyValue",
|
||||||
|
"name": "coded by",
|
||||||
|
"value": "<span class=\"h-card\" translate=\"no\"><a href=\"https://mastodont.cat/@spla\" class=\"u-url mention\">@<span>spla</span></a></span>"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"endpoints": {
|
||||||
|
"sharedInbox": "https://mastodont.cat/inbox"
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"type": "Image",
|
||||||
|
"mediaType": "image/png",
|
||||||
|
"url": "https://mastodont.cat/system/accounts/avatars/000/149/323/original/33201dbeb139a24a.png"
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"type": "Image",
|
||||||
|
"mediaType": "image/jpeg",
|
||||||
|
"url": "https://mastodont.cat/system/accounts/headers/000/149/323/original/75c861d59e5a8860.jpeg"
|
||||||
|
}
|
||||||
|
}
|
72
test/fixtures/peertube/actor-videochannel.json
vendored
Normal file
72
test/fixtures/peertube/actor-videochannel.json
vendored
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://w3id.org/security/v1",
|
||||||
|
{
|
||||||
|
"RsaSignature2017": "https://w3id.org/security#RsaSignature2017"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pt": "https://joinpeertube.org/ns#",
|
||||||
|
"sc": "http://schema.org/",
|
||||||
|
"playlists": {
|
||||||
|
"@id": "pt:playlists",
|
||||||
|
"@type": "@id"
|
||||||
|
},
|
||||||
|
"support": {
|
||||||
|
"@type": "sc:Text",
|
||||||
|
"@id": "pt:support"
|
||||||
|
},
|
||||||
|
"icons": "as:icon"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Group",
|
||||||
|
"id": "https://spectra.video/video-channels/fediforum_demos",
|
||||||
|
"following": "https://spectra.video/video-channels/fediforum_demos/following",
|
||||||
|
"followers": "https://spectra.video/video-channels/fediforum_demos/followers",
|
||||||
|
"playlists": "https://spectra.video/video-channels/fediforum_demos/playlists",
|
||||||
|
"inbox": "https://spectra.video/video-channels/fediforum_demos/inbox",
|
||||||
|
"outbox": "https://spectra.video/video-channels/fediforum_demos/outbox",
|
||||||
|
"preferredUsername": "fediforum_demos",
|
||||||
|
"url": "https://spectra.video/video-channels/fediforum_demos",
|
||||||
|
"name": "FediForum Demos",
|
||||||
|
"endpoints": {
|
||||||
|
"sharedInbox": "https://spectra.video/inbox"
|
||||||
|
},
|
||||||
|
"publicKey": {
|
||||||
|
"id": "https://spectra.video/video-channels/fediforum_demos#main-key",
|
||||||
|
"owner": "https://spectra.video/video-channels/fediforum_demos",
|
||||||
|
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxFVESAz0Z28zhXVJafzg\nKXVWS6yuZdQ4vOuA+k//ioSpNls53pI9vwQyixNa+QLdnXxm51dy//Py49wZbzAV\n2nC2FEnzcCM/EZvA4gzy7wekcjnGIz3equbdLOj3IAJJTSwCvZpW2f0poAa1CUmQ\nDRV5p3t3bjtUX5B9RnhiuDitN8qCzEeEbD9SHoyMDIACl8wXer8eyi5v98CMTHwh\nJYUJZJmS7/SSlJO2aqThEBaAYCUzVxlcXOecF1N1RWjjtwqi9xXxmlJ+teivYyST\nYfCeLmY/zZPY7OjoBxoVcVa/Yj3Wg6Nt+A5co9NATpsXmud7GWx4CvQ00uH/fa7e\nvQIDAQAB\n-----END PUBLIC KEY-----\n"
|
||||||
|
},
|
||||||
|
"published": "2024-03-26T19:34:06.073Z",
|
||||||
|
"icon": [
|
||||||
|
{
|
||||||
|
"type": "Image",
|
||||||
|
"mediaType": "image/png",
|
||||||
|
"height": 48,
|
||||||
|
"width": 48,
|
||||||
|
"url": "https://spectra.video/lazy-static/avatars/b13e5038-0169-420e-a6bc-4f5e0666fae6.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Image",
|
||||||
|
"mediaType": "image/png",
|
||||||
|
"height": 120,
|
||||||
|
"width": 120,
|
||||||
|
"url": "https://spectra.video/lazy-static/avatars/559b141a-96ec-4161-8889-1111b71abca0.png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"image": {
|
||||||
|
"type": "Image",
|
||||||
|
"mediaType": "image/png",
|
||||||
|
"height": 317,
|
||||||
|
"width": 1920,
|
||||||
|
"url": "https://spectra.video/lazy-static/banners/bbe18e2c-79ef-4640-9193-cdd743c964dd.png"
|
||||||
|
},
|
||||||
|
"summary": "Demos from the the FediForum Unconference. For the sake of simplicity, demos are [broken out into playlists](https://spectra.video/c/fediforum_demos/video-playlists) representing each FediForum Event.",
|
||||||
|
"support": "Check out our site: https://fediforum.org/\nFollow us on Mastodon: https://mastodon.social/@fediforum",
|
||||||
|
"attributedTo": [
|
||||||
|
{
|
||||||
|
"type": "Person",
|
||||||
|
"id": "https://spectra.video/accounts/fediforum"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
64
test/fixtures/tesla_mock/aimu@misskey.io.json
vendored
Normal file
64
test/fixtures/tesla_mock/aimu@misskey.io.json
vendored
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://w3id.org/security/v1",
|
||||||
|
{
|
||||||
|
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||||
|
"sensitive": "as:sensitive",
|
||||||
|
"Hashtag": "as:Hashtag",
|
||||||
|
"quoteUrl": "as:quoteUrl",
|
||||||
|
"toot": "http://joinmastodon.org/ns#",
|
||||||
|
"Emoji": "toot:Emoji",
|
||||||
|
"featured": "toot:featured",
|
||||||
|
"discoverable": "toot:discoverable",
|
||||||
|
"schema": "http://schema.org#",
|
||||||
|
"PropertyValue": "schema:PropertyValue",
|
||||||
|
"value": "schema:value",
|
||||||
|
"misskey": "https://misskey.io/ns#",
|
||||||
|
"_misskey_content": "misskey:_misskey_content",
|
||||||
|
"_misskey_quote": "misskey:_misskey_quote",
|
||||||
|
"_misskey_reaction": "misskey:_misskey_reaction",
|
||||||
|
"_misskey_votes": "misskey:_misskey_votes",
|
||||||
|
"_misskey_talk": "misskey:_misskey_talk",
|
||||||
|
"isCat": "misskey:isCat",
|
||||||
|
"vcard": "http://www.w3.org/2006/vcard/ns#"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Person",
|
||||||
|
"id": "https://misskey.io/users/83ssedkv53",
|
||||||
|
"inbox": "https://misskey.io/users/83ssedkv53/inbox",
|
||||||
|
"outbox": "https://misskey.io/users/83ssedkv53/outbox",
|
||||||
|
"followers": "https://misskey.io/users/83ssedkv53/followers",
|
||||||
|
"following": "https://misskey.io/users/83ssedkv53/following",
|
||||||
|
"sharedInbox": "https://misskey.io/inbox",
|
||||||
|
"endpoints": {
|
||||||
|
"sharedInbox": "https://misskey.io/inbox"
|
||||||
|
},
|
||||||
|
"url": "https://misskey.io/@aimu",
|
||||||
|
"preferredUsername": "aimu",
|
||||||
|
"name": "あいむ",
|
||||||
|
"summary": "<p><span>わずかな作曲要素 巣穴で独り言<br>Twitter </span><a href=\"https://twitter.com/aimu_53\">https://twitter.com/aimu_53</a><span><br>Soundcloud </span><a href=\"https://soundcloud.com/aimu-53\">https://soundcloud.com/aimu-53</a></p>",
|
||||||
|
"icon": {
|
||||||
|
"type": "Image",
|
||||||
|
"url": "https://s3.arkjp.net/misskey/webpublic-3f7e93c0-34f5-443c-acc0-f415cb2342b4.jpg",
|
||||||
|
"sensitive": false,
|
||||||
|
"name": null
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"type": "Image",
|
||||||
|
"url": "https://s3.arkjp.net/misskey/webpublic-2db63d1d-490b-488b-ab62-c93c285f26b6.png",
|
||||||
|
"sensitive": false,
|
||||||
|
"name": null
|
||||||
|
},
|
||||||
|
"tag": [],
|
||||||
|
"manuallyApprovesFollowers": false,
|
||||||
|
"discoverable": true,
|
||||||
|
"publicKey": {
|
||||||
|
"id": "https://misskey.io/users/83ssedkv53#main-key",
|
||||||
|
"type": "Key",
|
||||||
|
"owner": "https://misskey.io/users/83ssedkv53",
|
||||||
|
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1ylhePJ6qGHmwHSBP17b\nIosxGaiFKvgDBgZdm8vzvKeRSqJV9uLHfZL3pO/Zt02EwaZd2GohZAtBZEF8DbMA\n3s93WAesvyGF9mjGrYYKlhp/glwyrrrbf+RdD0DLtyDwRRlrxp3pS2lLmv5Tp1Zl\npH+UKpOnNrpQqjHI5P+lEc9bnflzbRrX+UiyLNsVAP80v4wt7SZfT/telrU6mDru\n998UdfhUo7bDKeDsHG1PfLpyhhtfdoZub4kBpkyacHiwAd+CdCjR54Eu7FDwVK3p\nY3JcrT2q5stgMqN1m4QgSL4XAADIotWwDYttTJejM1n9dr+6VWv5bs0F2Q/6gxOp\nu5DQZLk4Q+64U4LWNox6jCMOq3fYe0g7QalJIHnanYQQo+XjoH6S1Aw64gQ3Ip2Y\nZBmZREAOR7GMFVDPFnVnsbCHnIAv16TdgtLgQBAihkWEUuPqITLi8PMu6kMr3uyq\nYkObEfH0TNTcqaiVpoXv791GZLEUV5ROl0FSUANLNkHZZv29xZ5JDOBOR1rNBLyH\ngVtW8rpszYqOXwzX23hh4WsVXfB7YgNvIijwjiaWbzsecleaENGEnLNMiVKVumTj\nmtyTeFJpH0+OaSrUYpemRRJizmqIjklKsNwUEwUb2WcUUg92o56T2obrBkooabZe\nwgSXSKTOcjsR/ju7+AuIyvkCAwEAAQ==\n-----END PUBLIC KEY-----\n"
|
||||||
|
},
|
||||||
|
"isCat": true,
|
||||||
|
"vcard:bday": "5353-05-03"
|
||||||
|
}
|
44
test/fixtures/tesla_mock/misskey.io_8vs6wxufd0.json
vendored
Normal file
44
test/fixtures/tesla_mock/misskey.io_8vs6wxufd0.json
vendored
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://w3id.org/security/v1",
|
||||||
|
{
|
||||||
|
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||||
|
"sensitive": "as:sensitive",
|
||||||
|
"Hashtag": "as:Hashtag",
|
||||||
|
"quoteUrl": "as:quoteUrl",
|
||||||
|
"toot": "http://joinmastodon.org/ns#",
|
||||||
|
"Emoji": "toot:Emoji",
|
||||||
|
"featured": "toot:featured",
|
||||||
|
"discoverable": "toot:discoverable",
|
||||||
|
"schema": "http://schema.org#",
|
||||||
|
"PropertyValue": "schema:PropertyValue",
|
||||||
|
"value": "schema:value",
|
||||||
|
"misskey": "https://misskey.io/ns#",
|
||||||
|
"_misskey_content": "misskey:_misskey_content",
|
||||||
|
"_misskey_quote": "misskey:_misskey_quote",
|
||||||
|
"_misskey_reaction": "misskey:_misskey_reaction",
|
||||||
|
"_misskey_votes": "misskey:_misskey_votes",
|
||||||
|
"_misskey_talk": "misskey:_misskey_talk",
|
||||||
|
"isCat": "misskey:isCat",
|
||||||
|
"vcard": "http://www.w3.org/2006/vcard/ns#"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "https://misskey.io/notes/8vs6wxufd0",
|
||||||
|
"type": "Note",
|
||||||
|
"attributedTo": "https://misskey.io/users/83ssedkv53",
|
||||||
|
"summary": null,
|
||||||
|
"content": "<p><span>Fantiaこれできないように過去のやつは従量課金だった気がする</span></p>",
|
||||||
|
"_misskey_content": "Fantiaこれできないように過去のやつは従量課金だった気がする",
|
||||||
|
"published": "2022-01-21T16:37:12.663Z",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [
|
||||||
|
"https://misskey.io/users/83ssedkv53/followers"
|
||||||
|
],
|
||||||
|
"inReplyTo": null,
|
||||||
|
"attachment": [],
|
||||||
|
"sensitive": false,
|
||||||
|
"tag": []
|
||||||
|
}
|
|
@ -69,7 +69,9 @@ test "running gen" do
|
||||||
"test/uploads",
|
"test/uploads",
|
||||||
"--static-dir",
|
"--static-dir",
|
||||||
"./test/../test/instance/static/",
|
"./test/../test/instance/static/",
|
||||||
"--strip-uploads",
|
"--strip-uploads-metadata",
|
||||||
|
"y",
|
||||||
|
"--read-uploads-description",
|
||||||
"y",
|
"y",
|
||||||
"--anonymize-uploads",
|
"--anonymize-uploads",
|
||||||
"n"
|
"n"
|
||||||
|
@ -91,7 +93,10 @@ test "running gen" do
|
||||||
assert generated_config =~ "password: \"dbpass\""
|
assert generated_config =~ "password: \"dbpass\""
|
||||||
assert generated_config =~ "configurable_from_database: true"
|
assert generated_config =~ "configurable_from_database: true"
|
||||||
assert generated_config =~ "http: [ip: {127, 0, 0, 1}, port: 4000]"
|
assert generated_config =~ "http: [ip: {127, 0, 0, 1}, port: 4000]"
|
||||||
assert generated_config =~ "filters: [Pleroma.Upload.Filter.Exiftool]"
|
|
||||||
|
assert generated_config =~
|
||||||
|
"filters: [Pleroma.Upload.Filter.Exiftool.ReadDescription, Pleroma.Upload.Filter.Exiftool.StripMetadata]"
|
||||||
|
|
||||||
assert generated_config =~ "base_url: \"https://media.pleroma.social/media\""
|
assert generated_config =~ "base_url: \"https://media.pleroma.social/media\""
|
||||||
assert File.read!(tmp_path() <> "setup.psql") == generated_setup_psql()
|
assert File.read!(tmp_path() <> "setup.psql") == generated_setup_psql()
|
||||||
assert File.exists?(Path.expand("./test/instance/static/robots.txt"))
|
assert File.exists?(Path.expand("./test/instance/static/robots.txt"))
|
||||||
|
|
|
@ -11,6 +11,62 @@ defmodule Pleroma.Config.DeprecationWarningsTest do
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
alias Pleroma.Config.DeprecationWarnings
|
alias Pleroma.Config.DeprecationWarnings
|
||||||
|
|
||||||
|
describe "filter exiftool" do
|
||||||
|
test "gives warning when still used" do
|
||||||
|
clear_config(
|
||||||
|
[Pleroma.Upload, :filters],
|
||||||
|
[Pleroma.Upload.Filter.Exiftool]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert capture_log(fn -> DeprecationWarnings.check_exiftool_filter() end) =~
|
||||||
|
"""
|
||||||
|
!!!DEPRECATION WARNING!!!
|
||||||
|
Your config is using Exiftool as a filter instead of Exiftool.StripMetadata. This should work for now, but you are advised to change to the new configuration to prevent possible issues later:
|
||||||
|
|
||||||
|
```
|
||||||
|
config :pleroma, Pleroma.Upload,
|
||||||
|
filters: [Pleroma.Upload.Filter.Exiftool]
|
||||||
|
```
|
||||||
|
|
||||||
|
Is now
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
config :pleroma, Pleroma.Upload,
|
||||||
|
filters: [Pleroma.Upload.Filter.Exiftool.StripMetadata]
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
test "changes setting to exiftool strip metadata" do
|
||||||
|
clear_config(
|
||||||
|
[Pleroma.Upload, :filters],
|
||||||
|
[Pleroma.Upload.Filter.Exiftool, Pleroma.Upload.Filter.Exiftool.ReadDescription]
|
||||||
|
)
|
||||||
|
|
||||||
|
expected_config = [
|
||||||
|
Pleroma.Upload.Filter.Exiftool.StripMetadata,
|
||||||
|
Pleroma.Upload.Filter.Exiftool.ReadDescription
|
||||||
|
]
|
||||||
|
|
||||||
|
capture_log(fn -> DeprecationWarnings.warn() end)
|
||||||
|
|
||||||
|
assert Config.get([Pleroma.Upload]) |> Keyword.get(:filters, []) == expected_config
|
||||||
|
end
|
||||||
|
|
||||||
|
test "doesn't give a warning with correct config" do
|
||||||
|
clear_config(
|
||||||
|
[Pleroma.Upload, :filters],
|
||||||
|
[
|
||||||
|
Pleroma.Upload.Filter.Exiftool.StripMetadata,
|
||||||
|
Pleroma.Upload.Filter.Exiftool.ReadDescription
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert capture_log(fn -> DeprecationWarnings.check_exiftool_filter() end) == ""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "simple policy tuples" do
|
describe "simple policy tuples" do
|
||||||
test "gives warning when there are still strings" do
|
test "gives warning when there are still strings" do
|
||||||
clear_config([:mrf_simple],
|
clear_config([:mrf_simple],
|
||||||
|
|
|
@ -57,6 +57,9 @@ defp spoofed_object_with_ids(
|
||||||
body: spoofed_object_with_ids("https://patch.cx/objects/spoof_content_type")
|
body: spoofed_object_with_ids("https://patch.cx/objects/spoof_content_type")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
%{method: :get, url: "https://octodon.social/users/cwebber/statuses/111647596861000656"} ->
|
||||||
|
%Tesla.Env{status: 403}
|
||||||
|
|
||||||
# Spoof: mismatching ids
|
# Spoof: mismatching ids
|
||||||
# Variant 1: Non-exisitng fake id
|
# Variant 1: Non-exisitng fake id
|
||||||
%{
|
%{
|
||||||
|
@ -203,8 +206,7 @@ test "it works when fetching the OP actor errors out" do
|
||||||
test "it returns thread depth exceeded error if thread depth is exceeded" do
|
test "it returns thread depth exceeded error if thread depth is exceeded" do
|
||||||
clear_config([:instance, :federation_incoming_replies_max_depth], 0)
|
clear_config([:instance, :federation_incoming_replies_max_depth], 0)
|
||||||
|
|
||||||
assert {:error, "Max thread distance exceeded."} =
|
assert {:error, :allowed_depth} = Fetcher.fetch_object_from_id(@ap_id, depth: 1)
|
||||||
Fetcher.fetch_object_from_id(@ap_id, depth: 1)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it fetches object if max thread depth is restricted to 0 and depth is not specified" do
|
test "it fetches object if max thread depth is restricted to 0 and depth is not specified" do
|
||||||
|
@ -250,12 +252,12 @@ test "it does not fetch a spoofed object with wrong content type" do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it does not fetch a spoofed object with id different from URL" do
|
test "it does not fetch a spoofed object with id different from URL" do
|
||||||
assert {:error, "Object's ActivityPub id/url does not match final fetch URL"} =
|
assert {:error, :id_mismatch} =
|
||||||
Fetcher.fetch_and_contain_remote_object_from_id(
|
Fetcher.fetch_and_contain_remote_object_from_id(
|
||||||
"https://patch.cx/media/03ca3c8b4ac3ddd08bf0f84be7885f2f88de0f709112131a22d83650819e36c2.json"
|
"https://patch.cx/media/03ca3c8b4ac3ddd08bf0f84be7885f2f88de0f709112131a22d83650819e36c2.json"
|
||||||
)
|
)
|
||||||
|
|
||||||
assert {:error, "Object's ActivityPub id/url does not match final fetch URL"} =
|
assert {:error, :id_mismatch} =
|
||||||
Fetcher.fetch_and_contain_remote_object_from_id(
|
Fetcher.fetch_and_contain_remote_object_from_id(
|
||||||
"https://patch.cx/media/spoof_stage1.json"
|
"https://patch.cx/media/spoof_stage1.json"
|
||||||
)
|
)
|
||||||
|
@ -285,14 +287,14 @@ test "it accepts same-domain redirects" do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it does not fetch a spoofed object with a foreign actor" do
|
test "it does not fetch a spoofed object with a foreign actor" do
|
||||||
assert {:error, "Object containment failed."} =
|
assert {:error, _} =
|
||||||
Fetcher.fetch_and_contain_remote_object_from_id(
|
Fetcher.fetch_and_contain_remote_object_from_id(
|
||||||
"https://patch.cx/objects/spoof_foreign_actor"
|
"https://patch.cx/objects/spoof_foreign_actor"
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it does not fetch from localhost" do
|
test "it does not fetch from localhost" do
|
||||||
assert {:error, "Trying to fetch local resource"} =
|
assert {:error, :local_resource} =
|
||||||
Fetcher.fetch_and_contain_remote_object_from_id(
|
Fetcher.fetch_and_contain_remote_object_from_id(
|
||||||
Pleroma.Web.Endpoint.url() <> "/spoof_local"
|
Pleroma.Web.Endpoint.url() <> "/spoof_local"
|
||||||
)
|
)
|
||||||
|
@ -402,16 +404,14 @@ test "all objects with fake directions are rejected by the object fetcher" do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "handle HTTP 410 Gone response" do
|
test "handle HTTP 410 Gone response" do
|
||||||
assert {:error,
|
assert {:error, :not_found} ==
|
||||||
{"Object has been deleted", "https://mastodon.example.org/users/userisgone", 410}} ==
|
|
||||||
Fetcher.fetch_and_contain_remote_object_from_id(
|
Fetcher.fetch_and_contain_remote_object_from_id(
|
||||||
"https://mastodon.example.org/users/userisgone"
|
"https://mastodon.example.org/users/userisgone"
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "handle HTTP 404 response" do
|
test "handle HTTP 404 response" do
|
||||||
assert {:error,
|
assert {:error, :not_found} ==
|
||||||
{"Object has been deleted", "https://mastodon.example.org/users/userisgone404", 404}} ==
|
|
||||||
Fetcher.fetch_and_contain_remote_object_from_id(
|
Fetcher.fetch_and_contain_remote_object_from_id(
|
||||||
"https://mastodon.example.org/users/userisgone404"
|
"https://mastodon.example.org/users/userisgone404"
|
||||||
)
|
)
|
||||||
|
|
117
test/pleroma/upload/filter/exiftool/read_description_test.exs
Normal file
117
test/pleroma/upload/filter/exiftool/read_description_test.exs
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Upload.Filter.Exiftool.ReadDescriptionTest do
|
||||||
|
use Pleroma.DataCase, async: true
|
||||||
|
alias Pleroma.Upload.Filter
|
||||||
|
|
||||||
|
@uploads %Pleroma.Upload{
|
||||||
|
name: "image_with_imagedescription_and_caption-abstract.jpg",
|
||||||
|
content_type: "image/jpeg",
|
||||||
|
path: Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
|
||||||
|
tempfile: Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
|
||||||
|
description: nil
|
||||||
|
}
|
||||||
|
|
||||||
|
test "keeps description when not empty" do
|
||||||
|
uploads = %Pleroma.Upload{
|
||||||
|
name: "image_with_imagedescription_and_caption-abstract.jpg",
|
||||||
|
content_type: "image/jpeg",
|
||||||
|
path: Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
|
||||||
|
tempfile:
|
||||||
|
Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
|
||||||
|
description: "Some description"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert Filter.Exiftool.ReadDescription.filter(uploads) ==
|
||||||
|
{:ok, :noop}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "otherwise returns ImageDescription when present" do
|
||||||
|
uploads_after = %Pleroma.Upload{
|
||||||
|
name: "image_with_imagedescription_and_caption-abstract.jpg",
|
||||||
|
content_type: "image/jpeg",
|
||||||
|
path: Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
|
||||||
|
tempfile:
|
||||||
|
Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
|
||||||
|
description: "a descriptive white pixel"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert Filter.Exiftool.ReadDescription.filter(@uploads) ==
|
||||||
|
{:ok, :filtered, uploads_after}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "otherwise returns iptc:Caption-Abstract when present" do
|
||||||
|
upload = %Pleroma.Upload{
|
||||||
|
name: "image_with_caption-abstract.jpg",
|
||||||
|
content_type: "image/jpeg",
|
||||||
|
path: Path.absname("test/fixtures/image_with_caption-abstract.jpg"),
|
||||||
|
tempfile: Path.absname("test/fixtures/image_with_caption-abstract.jpg"),
|
||||||
|
description: nil
|
||||||
|
}
|
||||||
|
|
||||||
|
upload_after = %Pleroma.Upload{
|
||||||
|
name: "image_with_caption-abstract.jpg",
|
||||||
|
content_type: "image/jpeg",
|
||||||
|
path: Path.absname("test/fixtures/image_with_caption-abstract.jpg"),
|
||||||
|
tempfile: Path.absname("test/fixtures/image_with_caption-abstract.jpg"),
|
||||||
|
description: "an abstract white pixel"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert Filter.Exiftool.ReadDescription.filter(upload) ==
|
||||||
|
{:ok, :filtered, upload_after}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "otherwise returns nil" do
|
||||||
|
uploads = %Pleroma.Upload{
|
||||||
|
name: "image_with_no_description.jpg",
|
||||||
|
content_type: "image/jpeg",
|
||||||
|
path: Path.absname("test/fixtures/image_with_no_description.jpg"),
|
||||||
|
tempfile: Path.absname("test/fixtures/image_with_no_description.jpg"),
|
||||||
|
description: nil
|
||||||
|
}
|
||||||
|
|
||||||
|
assert Filter.Exiftool.ReadDescription.filter(uploads) ==
|
||||||
|
{:ok, :filtered, uploads}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Return nil when image description from EXIF data exceeds the maximum length" do
|
||||||
|
clear_config([:instance, :description_limit], 5)
|
||||||
|
|
||||||
|
assert Filter.Exiftool.ReadDescription.filter(@uploads) ==
|
||||||
|
{:ok, :filtered, @uploads}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Ignores content with only whitespace" do
|
||||||
|
uploads = %Pleroma.Upload{
|
||||||
|
name: "non-existant.jpg",
|
||||||
|
content_type: "image/jpeg",
|
||||||
|
path:
|
||||||
|
Path.absname(
|
||||||
|
"test/fixtures/image_with_imagedescription_and_caption-abstract_whitespaces.jpg"
|
||||||
|
),
|
||||||
|
tempfile:
|
||||||
|
Path.absname(
|
||||||
|
"test/fixtures/image_with_imagedescription_and_caption-abstract_whitespaces.jpg"
|
||||||
|
),
|
||||||
|
description: nil
|
||||||
|
}
|
||||||
|
|
||||||
|
assert Filter.Exiftool.ReadDescription.filter(uploads) ==
|
||||||
|
{:ok, :filtered, uploads}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Return nil when image description from EXIF data can't be read" do
|
||||||
|
uploads = %Pleroma.Upload{
|
||||||
|
name: "non-existant.jpg",
|
||||||
|
content_type: "image/jpeg",
|
||||||
|
path: Path.absname("test/fixtures/non-existant.jpg"),
|
||||||
|
tempfile: Path.absname("test/fixtures/non-existant_tmp.jpg"),
|
||||||
|
description: nil
|
||||||
|
}
|
||||||
|
|
||||||
|
assert Filter.Exiftool.ReadDescription.filter(uploads) ==
|
||||||
|
{:ok, :filtered, uploads}
|
||||||
|
end
|
||||||
|
end
|
148
test/pleroma/upload/filter/exiftool/strip_location_test.exs
Normal file
148
test/pleroma/upload/filter/exiftool/strip_location_test.exs
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Upload.Filter.Exiftool.StripMetadataTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
alias Pleroma.Upload.Filter
|
||||||
|
|
||||||
|
@tag :tmp_dir
|
||||||
|
test "exiftool strip metadata strips GPS etc but preserves Orientation and ColorSpace by default",
|
||||||
|
%{tmp_dir: tmp_dir} do
|
||||||
|
assert Pleroma.Utils.command_available?("exiftool")
|
||||||
|
|
||||||
|
tmpfile = Path.join(tmp_dir, "tmp.jpg")
|
||||||
|
|
||||||
|
File.cp!(
|
||||||
|
"test/fixtures/DSCN0010.jpg",
|
||||||
|
tmpfile
|
||||||
|
)
|
||||||
|
|
||||||
|
upload = %Pleroma.Upload{
|
||||||
|
name: "image_with_GPS_data.jpg",
|
||||||
|
content_type: "image/jpeg",
|
||||||
|
path: Path.absname("test/fixtures/DSCN0010.jpg"),
|
||||||
|
tempfile: Path.absname(tmpfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert Filter.Exiftool.StripMetadata.filter(upload) == {:ok, :filtered}
|
||||||
|
|
||||||
|
exif_original = read_exif("test/fixtures/DSCN0010.jpg")
|
||||||
|
exif_filtered = read_exif(tmpfile)
|
||||||
|
|
||||||
|
refute exif_original == exif_filtered
|
||||||
|
assert String.match?(exif_original, ~r/GPS/)
|
||||||
|
refute String.match?(exif_filtered, ~r/GPS/)
|
||||||
|
assert String.match?(exif_original, ~r/Camera Model Name/)
|
||||||
|
refute String.match?(exif_filtered, ~r/Camera Model Name/)
|
||||||
|
assert String.match?(exif_original, ~r/Orientation/)
|
||||||
|
assert String.match?(exif_filtered, ~r/Orientation/)
|
||||||
|
assert String.match?(exif_original, ~r/Color Space/)
|
||||||
|
assert String.match?(exif_filtered, ~r/Color Space/)
|
||||||
|
end
|
||||||
|
|
||||||
|
# this is a nonsensical configuration, but it shouldn't explode
|
||||||
|
@tag :tmp_dir
|
||||||
|
test "exiftool strip metadata is a noop with empty purge list", %{tmp_dir: tmp_dir} do
|
||||||
|
assert Pleroma.Utils.command_available?("exiftool")
|
||||||
|
clear_config([Pleroma.Upload.Filter.Exiftool.StripMetadata, :purge], [])
|
||||||
|
|
||||||
|
tmpfile = Path.join(tmp_dir, "tmp.jpg")
|
||||||
|
|
||||||
|
File.cp!(
|
||||||
|
"test/fixtures/DSCN0010.jpg",
|
||||||
|
tmpfile
|
||||||
|
)
|
||||||
|
|
||||||
|
upload = %Pleroma.Upload{
|
||||||
|
name: "image_with_GPS_data.jpg",
|
||||||
|
content_type: "image/jpeg",
|
||||||
|
path: Path.absname("test/fixtures/DSCN0010.jpg"),
|
||||||
|
tempfile: Path.absname(tmpfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert Filter.Exiftool.StripMetadata.filter(upload) == {:ok, :filtered}
|
||||||
|
|
||||||
|
exif_original = read_exif("test/fixtures/DSCN0010.jpg")
|
||||||
|
exif_filtered = read_exif(tmpfile)
|
||||||
|
|
||||||
|
assert exif_original == exif_filtered
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag :tmp_dir
|
||||||
|
test "exiftool strip metadata works with empty preserve list", %{tmp_dir: tmp_dir} do
|
||||||
|
assert Pleroma.Utils.command_available?("exiftool")
|
||||||
|
clear_config([Pleroma.Upload.Filter.Exiftool.StripMetadata, :preserve], [])
|
||||||
|
|
||||||
|
tmpfile = Path.join(tmp_dir, "tmp.jpg")
|
||||||
|
|
||||||
|
File.cp!(
|
||||||
|
"test/fixtures/DSCN0010.jpg",
|
||||||
|
tmpfile
|
||||||
|
)
|
||||||
|
|
||||||
|
upload = %Pleroma.Upload{
|
||||||
|
name: "image_with_GPS_data.jpg",
|
||||||
|
content_type: "image/jpeg",
|
||||||
|
path: Path.absname("test/fixtures/DSCN0010.jpg"),
|
||||||
|
tempfile: Path.absname(tmpfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
write_exif(["-ImageDescription=Trees and Houses", "-Orientation=1", tmpfile])
|
||||||
|
exif_extended = read_exif(tmpfile)
|
||||||
|
assert String.match?(exif_extended, ~r/Image Description[ \t]*:[ \t]*Trees and Houses/)
|
||||||
|
assert String.match?(exif_extended, ~r/Orientation/)
|
||||||
|
|
||||||
|
assert Filter.Exiftool.StripMetadata.filter(upload) == {:ok, :filtered}
|
||||||
|
|
||||||
|
exif_original = read_exif("test/fixtures/DSCN0010.jpg")
|
||||||
|
exif_filtered = read_exif(tmpfile)
|
||||||
|
|
||||||
|
refute exif_original == exif_filtered
|
||||||
|
refute exif_extended == exif_filtered
|
||||||
|
assert String.match?(exif_original, ~r/GPS/)
|
||||||
|
refute String.match?(exif_filtered, ~r/GPS/)
|
||||||
|
refute String.match?(exif_filtered, ~r/Image Description/)
|
||||||
|
refute String.match?(exif_filtered, ~r/Orientation/)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "verify webp files are skipped" do
|
||||||
|
upload = %Pleroma.Upload{
|
||||||
|
name: "sample.webp",
|
||||||
|
content_type: "image/webp"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert Filter.Exiftool.StripMetadata.filter(upload) == {:ok, :noop}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "verify svg files are skipped" do
|
||||||
|
upload = %Pleroma.Upload{
|
||||||
|
name: "sample.svg",
|
||||||
|
content_type: "image/svg+xml"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert Filter.Exiftool.StripMetadata.filter(upload) == {:ok, :noop}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp read_exif(file) do
|
||||||
|
# time and file path tags cause mismatches even for byte-identical files
|
||||||
|
{exif_data, 0} =
|
||||||
|
System.cmd("exiftool", [
|
||||||
|
"-x",
|
||||||
|
"Time:All",
|
||||||
|
"-x",
|
||||||
|
"Directory",
|
||||||
|
"-x",
|
||||||
|
"FileName",
|
||||||
|
"-x",
|
||||||
|
"FileSize",
|
||||||
|
file
|
||||||
|
])
|
||||||
|
|
||||||
|
exif_data
|
||||||
|
end
|
||||||
|
|
||||||
|
defp write_exif(args) do
|
||||||
|
{_response, 0} = System.cmd("exiftool", ["-ignoreMinorErrors", "-overwrite_original" | args])
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,42 +0,0 @@
|
||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Upload.Filter.ExiftoolTest do
|
|
||||||
use Pleroma.DataCase
|
|
||||||
alias Pleroma.Upload.Filter
|
|
||||||
|
|
||||||
test "apply exiftool filter" do
|
|
||||||
assert Pleroma.Utils.command_available?("exiftool")
|
|
||||||
|
|
||||||
File.cp!(
|
|
||||||
"test/fixtures/DSCN0010.jpg",
|
|
||||||
"test/fixtures/DSCN0010_tmp.jpg"
|
|
||||||
)
|
|
||||||
|
|
||||||
upload = %Pleroma.Upload{
|
|
||||||
name: "image_with_GPS_data.jpg",
|
|
||||||
content_type: "image/jpeg",
|
|
||||||
path: Path.absname("test/fixtures/DSCN0010.jpg"),
|
|
||||||
tempfile: Path.absname("test/fixtures/DSCN0010_tmp.jpg")
|
|
||||||
}
|
|
||||||
|
|
||||||
assert Filter.Exiftool.filter(upload) == {:ok, :filtered}
|
|
||||||
|
|
||||||
{exif_original, 0} = System.cmd("exiftool", ["test/fixtures/DSCN0010.jpg"])
|
|
||||||
{exif_filtered, 0} = System.cmd("exiftool", ["test/fixtures/DSCN0010_tmp.jpg"])
|
|
||||||
|
|
||||||
refute exif_original == exif_filtered
|
|
||||||
assert String.match?(exif_original, ~r/GPS/)
|
|
||||||
refute String.match?(exif_filtered, ~r/GPS/)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "verify webp files are skipped" do
|
|
||||||
upload = %Pleroma.Upload{
|
|
||||||
name: "sample.webp",
|
|
||||||
content_type: "image/webp"
|
|
||||||
}
|
|
||||||
|
|
||||||
assert Filter.Exiftool.filter(upload) == {:ok, :noop}
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1418,244 +1418,6 @@ test "It returns poll Answers when authenticated", %{conn: conn} do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "POST /users/:nickname/outbox (C2S)" do
|
|
||||||
setup do: clear_config([:instance, :limit])
|
|
||||||
|
|
||||||
setup do
|
|
||||||
[
|
|
||||||
activity: %{
|
|
||||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
|
||||||
"type" => "Create",
|
|
||||||
"object" => %{
|
|
||||||
"type" => "Note",
|
|
||||||
"content" => "AP C2S test",
|
|
||||||
"to" => "https://www.w3.org/ns/activitystreams#Public",
|
|
||||||
"cc" => []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it rejects posts from other users / unauthenticated users", %{
|
|
||||||
conn: conn,
|
|
||||||
activity: activity
|
|
||||||
} do
|
|
||||||
user = insert(:user)
|
|
||||||
other_user = insert(:user)
|
|
||||||
conn = put_req_header(conn, "content-type", "application/activity+json")
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> post("/users/#{user.nickname}/outbox", activity)
|
|
||||||
|> json_response(403)
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> assign(:user, other_user)
|
|
||||||
|> post("/users/#{user.nickname}/outbox", activity)
|
|
||||||
|> json_response(403)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it inserts an incoming create activity into the database", %{
|
|
||||||
conn: conn,
|
|
||||||
activity: activity
|
|
||||||
} do
|
|
||||||
user = insert(:user)
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|
||||||
|> post("/users/#{user.nickname}/outbox", activity)
|
|
||||||
|> json_response(201)
|
|
||||||
|
|
||||||
assert Activity.get_by_ap_id(result["id"])
|
|
||||||
assert result["object"]
|
|
||||||
assert %Object{data: object} = Object.normalize(result["object"], fetch: false)
|
|
||||||
assert object["content"] == activity["object"]["content"]
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
|
|
||||||
user = insert(:user)
|
|
||||||
|
|
||||||
activity =
|
|
||||||
activity
|
|
||||||
|> put_in(["object", "type"], "Benis")
|
|
||||||
|
|
||||||
_result =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|
||||||
|> post("/users/#{user.nickname}/outbox", activity)
|
|
||||||
|> json_response(400)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it inserts an incoming sensitive activity into the database", %{
|
|
||||||
conn: conn,
|
|
||||||
activity: activity
|
|
||||||
} do
|
|
||||||
user = insert(:user)
|
|
||||||
conn = assign(conn, :user, user)
|
|
||||||
object = Map.put(activity["object"], "sensitive", true)
|
|
||||||
activity = Map.put(activity, "object", object)
|
|
||||||
|
|
||||||
response =
|
|
||||||
conn
|
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|
||||||
|> post("/users/#{user.nickname}/outbox", activity)
|
|
||||||
|> json_response(201)
|
|
||||||
|
|
||||||
assert Activity.get_by_ap_id(response["id"])
|
|
||||||
assert response["object"]
|
|
||||||
assert %Object{data: response_object} = Object.normalize(response["object"], fetch: false)
|
|
||||||
assert response_object["sensitive"] == true
|
|
||||||
assert response_object["content"] == activity["object"]["content"]
|
|
||||||
|
|
||||||
representation =
|
|
||||||
conn
|
|
||||||
|> put_req_header("accept", "application/activity+json")
|
|
||||||
|> get(response["id"])
|
|
||||||
|> json_response(200)
|
|
||||||
|
|
||||||
assert representation["object"]["sensitive"] == true
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
|
|
||||||
user = insert(:user)
|
|
||||||
activity = Map.put(activity, "type", "BadType")
|
|
||||||
|
|
||||||
conn =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|
||||||
|> post("/users/#{user.nickname}/outbox", activity)
|
|
||||||
|
|
||||||
assert json_response(conn, 400)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
|
|
||||||
note_activity = insert(:note_activity)
|
|
||||||
note_object = Object.normalize(note_activity, fetch: false)
|
|
||||||
user = User.get_cached_by_ap_id(note_activity.data["actor"])
|
|
||||||
|
|
||||||
data = %{
|
|
||||||
"type" => "Delete",
|
|
||||||
"object" => %{
|
|
||||||
"id" => note_object.data["id"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|
||||||
|> post("/users/#{user.nickname}/outbox", data)
|
|
||||||
|> json_response(201)
|
|
||||||
|
|
||||||
assert Activity.get_by_ap_id(result["id"])
|
|
||||||
|
|
||||||
assert object = Object.get_by_ap_id(note_object.data["id"])
|
|
||||||
assert object.data["type"] == "Tombstone"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it rejects delete activity of object from other actor", %{conn: conn} do
|
|
||||||
note_activity = insert(:note_activity)
|
|
||||||
note_object = Object.normalize(note_activity, fetch: false)
|
|
||||||
user = insert(:user)
|
|
||||||
|
|
||||||
data = %{
|
|
||||||
type: "Delete",
|
|
||||||
object: %{
|
|
||||||
id: note_object.data["id"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
conn =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|
||||||
|> post("/users/#{user.nickname}/outbox", data)
|
|
||||||
|
|
||||||
assert json_response(conn, 403)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it increases like count when receiving a like action", %{conn: conn} do
|
|
||||||
note_activity = insert(:note_activity)
|
|
||||||
note_object = Object.normalize(note_activity, fetch: false)
|
|
||||||
user = User.get_cached_by_ap_id(note_activity.data["actor"])
|
|
||||||
|
|
||||||
data = %{
|
|
||||||
type: "Like",
|
|
||||||
object: %{
|
|
||||||
id: note_object.data["id"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
conn =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|
||||||
|> post("/users/#{user.nickname}/outbox", data)
|
|
||||||
|
|
||||||
result = json_response(conn, 201)
|
|
||||||
assert Activity.get_by_ap_id(result["id"])
|
|
||||||
|
|
||||||
assert object = Object.get_by_ap_id(note_object.data["id"])
|
|
||||||
assert object.data["like_count"] == 1
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it doesn't spreads faulty attributedTo or actor fields", %{
|
|
||||||
conn: conn,
|
|
||||||
activity: activity
|
|
||||||
} do
|
|
||||||
reimu = insert(:user, nickname: "reimu")
|
|
||||||
cirno = insert(:user, nickname: "cirno")
|
|
||||||
|
|
||||||
assert reimu.ap_id
|
|
||||||
assert cirno.ap_id
|
|
||||||
|
|
||||||
activity =
|
|
||||||
activity
|
|
||||||
|> put_in(["object", "actor"], reimu.ap_id)
|
|
||||||
|> put_in(["object", "attributedTo"], reimu.ap_id)
|
|
||||||
|> put_in(["actor"], reimu.ap_id)
|
|
||||||
|> put_in(["attributedTo"], reimu.ap_id)
|
|
||||||
|
|
||||||
_reimu_outbox =
|
|
||||||
conn
|
|
||||||
|> assign(:user, cirno)
|
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|
||||||
|> post("/users/#{reimu.nickname}/outbox", activity)
|
|
||||||
|> json_response(403)
|
|
||||||
|
|
||||||
cirno_outbox =
|
|
||||||
conn
|
|
||||||
|> assign(:user, cirno)
|
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|
||||||
|> post("/users/#{cirno.nickname}/outbox", activity)
|
|
||||||
|> json_response(201)
|
|
||||||
|
|
||||||
assert cirno_outbox["attributedTo"] == nil
|
|
||||||
assert cirno_outbox["actor"] == cirno.ap_id
|
|
||||||
|
|
||||||
assert cirno_object = Object.normalize(cirno_outbox["object"], fetch: false)
|
|
||||||
assert cirno_object.data["actor"] == cirno.ap_id
|
|
||||||
assert cirno_object.data["attributedTo"] == cirno.ap_id
|
|
||||||
end
|
|
||||||
|
|
||||||
test "Character limitation", %{conn: conn, activity: activity} do
|
|
||||||
clear_config([:instance, :limit], 5)
|
|
||||||
user = insert(:user)
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|
||||||
|> post("/users/#{user.nickname}/outbox", activity)
|
|
||||||
|> json_response(400)
|
|
||||||
|
|
||||||
assert result == "Character limit (5 characters) exceeded, contains 11 characters"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "/relay/followers" do
|
describe "/relay/followers" do
|
||||||
test "it returns relay followers", %{conn: conn} do
|
test "it returns relay followers", %{conn: conn} do
|
||||||
relay_actor = Relay.get_actor()
|
relay_actor = Relay.get_actor()
|
||||||
|
@ -1977,95 +1739,6 @@ test "it tracks a signed activity fetch when the json is cached", %{conn: conn}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "Additional ActivityPub C2S endpoints" do
|
|
||||||
test "GET /api/ap/whoami", %{conn: conn} do
|
|
||||||
user = insert(:user)
|
|
||||||
|
|
||||||
conn =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> get("/api/ap/whoami")
|
|
||||||
|
|
||||||
user = User.get_cached_by_id(user.id)
|
|
||||||
|
|
||||||
assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> get("/api/ap/whoami")
|
|
||||||
|> json_response(403)
|
|
||||||
end
|
|
||||||
|
|
||||||
setup do: clear_config([:media_proxy])
|
|
||||||
setup do: clear_config([Pleroma.Upload])
|
|
||||||
|
|
||||||
test "POST /api/ap/upload_media", %{conn: conn} do
|
|
||||||
user = insert(:user)
|
|
||||||
|
|
||||||
desc = "Description of the image"
|
|
||||||
|
|
||||||
image = %Plug.Upload{
|
|
||||||
content_type: "image/jpeg",
|
|
||||||
path: Path.absname("test/fixtures/image.jpg"),
|
|
||||||
filename: "an_image.jpg"
|
|
||||||
}
|
|
||||||
|
|
||||||
object =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
|
|
||||||
|> json_response(:created)
|
|
||||||
|
|
||||||
assert object["name"] == desc
|
|
||||||
assert object["type"] == "Document"
|
|
||||||
assert object["actor"] == user.ap_id
|
|
||||||
assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
|
|
||||||
assert is_binary(object_href)
|
|
||||||
assert object_mediatype == "image/jpeg"
|
|
||||||
assert String.ends_with?(object_href, ".jpg")
|
|
||||||
|
|
||||||
activity_request = %{
|
|
||||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
|
||||||
"type" => "Create",
|
|
||||||
"object" => %{
|
|
||||||
"type" => "Note",
|
|
||||||
"content" => "AP C2S test, attachment",
|
|
||||||
"attachment" => [object],
|
|
||||||
"to" => "https://www.w3.org/ns/activitystreams#Public",
|
|
||||||
"cc" => []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
activity_response =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> post("/users/#{user.nickname}/outbox", activity_request)
|
|
||||||
|> json_response(:created)
|
|
||||||
|
|
||||||
assert activity_response["id"]
|
|
||||||
assert activity_response["object"]
|
|
||||||
assert activity_response["actor"] == user.ap_id
|
|
||||||
|
|
||||||
assert %Object{data: %{"attachment" => [attachment]}} =
|
|
||||||
Object.normalize(activity_response["object"], fetch: false)
|
|
||||||
|
|
||||||
assert attachment["type"] == "Document"
|
|
||||||
assert attachment["name"] == desc
|
|
||||||
|
|
||||||
assert [
|
|
||||||
%{
|
|
||||||
"href" => ^object_href,
|
|
||||||
"type" => "Link",
|
|
||||||
"mediaType" => ^object_mediatype
|
|
||||||
}
|
|
||||||
] = attachment["url"]
|
|
||||||
|
|
||||||
# Fails if unauthenticated
|
|
||||||
conn
|
|
||||||
|> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
|
|
||||||
|> json_response(403)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "pinned collection", %{conn: conn} do
|
test "pinned collection", %{conn: conn} do
|
||||||
clear_config([:instance, :max_pinned_statuses], 2)
|
clear_config([:instance, :max_pinned_statuses], 2)
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
|
@ -4,10 +4,16 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicyTest do
|
defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicyTest do
|
||||||
alias Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy
|
alias Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy
|
||||||
|
alias Pleroma.Object
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
setup_all do
|
||||||
|
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
test "adds quote URL to post content" do
|
test "adds quote URL to post content" do
|
||||||
quote_url = "https://example.com/objects/1234"
|
quote_url = "https://mastodon.social/users/emelie/statuses/101849165031453009"
|
||||||
|
|
||||||
activity = %{
|
activity = %{
|
||||||
"type" => "Create",
|
"type" => "Create",
|
||||||
|
@ -19,10 +25,13 @@ test "adds quote URL to post content" do
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Prefetch the quoted post
|
||||||
|
%Object{} = Object.normalize(quote_url, fetch: true)
|
||||||
|
|
||||||
{:ok, %{"object" => %{"content" => filtered}}} = InlineQuotePolicy.filter(activity)
|
{:ok, %{"object" => %{"content" => filtered}}} = InlineQuotePolicy.filter(activity)
|
||||||
|
|
||||||
assert filtered ==
|
assert filtered ==
|
||||||
"<p>Nice post<span class=\"quote-inline\"><br/><br/>RE: <a href=\"https://example.com/objects/1234\">https://example.com/objects/1234</a></span></p>"
|
"<p>Nice post<span class=\"quote-inline\"><br/><br/>RE: <a href=\"https://mastodon.social/@emelie/101849165031453009\">https://mastodon.social/@emelie/101849165031453009</a></span></p>"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "ignores Misskey quote posts" do
|
test "ignores Misskey quote posts" do
|
||||||
|
|
|
@ -39,6 +39,28 @@ test "a basic note validates", %{note: note} do
|
||||||
%{valid?: true} = ArticleNotePageValidator.cast_and_validate(note)
|
%{valid?: true} = ArticleNotePageValidator.cast_and_validate(note)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "note with url validates", %{note: note} do
|
||||||
|
note = Map.put(note, "url", "https://remote.example/link")
|
||||||
|
%{valid?: true} = ArticleNotePageValidator.cast_and_validate(note)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "note with url array validates", %{note: note} do
|
||||||
|
note = Map.put(note, "url", ["https://remote.example/link"])
|
||||||
|
%{valid?: true} = ArticleNotePageValidator.cast_and_validate(note)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "note with url array validates if contains a link object", %{note: note} do
|
||||||
|
note =
|
||||||
|
Map.put(note, "url", [
|
||||||
|
%{
|
||||||
|
"type" => "Link",
|
||||||
|
"href" => "https://remote.example/link"
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
%{valid?: true} = ArticleNotePageValidator.cast_and_validate(note)
|
||||||
|
end
|
||||||
|
|
||||||
test "a note with a language validates" do
|
test "a note with a language validates" do
|
||||||
insert(:user, %{ap_id: "https://mastodon.social/users/akkoma_ap_integration_tester"})
|
insert(:user, %{ap_id: "https://mastodon.social/users/akkoma_ap_integration_tester"})
|
||||||
note = File.read!("test/fixtures/mastodon/note_with_language.json") |> Jason.decode!()
|
note = File.read!("test/fixtures/mastodon/note_with_language.json") |> Jason.decode!()
|
||||||
|
|
|
@ -12,14 +12,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidatorTest do
|
||||||
|
|
||||||
describe "attachments" do
|
describe "attachments" do
|
||||||
test "works with apng" do
|
test "works with apng" do
|
||||||
attachment =
|
attachment = %{
|
||||||
%{
|
"mediaType" => "image/apng",
|
||||||
"mediaType" => "image/apng",
|
"name" => "",
|
||||||
"name" => "",
|
"type" => "Document",
|
||||||
"type" => "Document",
|
"url" =>
|
||||||
"url" =>
|
"https://media.misskeyusercontent.com/io/2859c26e-cd43-4550-848b-b6243bc3fe28.apng"
|
||||||
"https://media.misskeyusercontent.com/io/2859c26e-cd43-4550-848b-b6243bc3fe28.apng"
|
}
|
||||||
}
|
|
||||||
|
|
||||||
assert {:ok, attachment} =
|
assert {:ok, attachment} =
|
||||||
AttachmentValidator.cast_and_validate(attachment)
|
AttachmentValidator.cast_and_validate(attachment)
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
# Akkoma: Magically expressive social media
|
||||||
|
# Copyright © 2024 Akkoma Authors <https://akkoma.dev/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.UserValidatorTest do
|
||||||
|
use Pleroma.DataCase, async: true
|
||||||
|
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.UserValidator
|
||||||
|
|
||||||
|
# all standard actor types are listed here:
|
||||||
|
# https://www.w3.org/TR/activitystreams-vocabulary/#actor-types
|
||||||
|
describe "accepts standard type" do
|
||||||
|
test "Application" do
|
||||||
|
validates_file!("test/fixtures/mastodon/application_actor.json")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Group" do
|
||||||
|
validates_file!("test/fixtures/peertube/actor-videochannel.json")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Organization" do
|
||||||
|
validates_file!("test/fixtures/tesla_mock/wedistribute-user.json")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Person" do
|
||||||
|
validates_file!("test/fixtures/bridgy/actor.json")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Service" do
|
||||||
|
validates_file!("test/fixtures/mastodon/service_actor.json")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp validates_file!(path) do
|
||||||
|
user_data = Jason.decode!(File.read!(path))
|
||||||
|
{:ok, _validated_data, _meta} = UserValidator.validate(user_data, [])
|
||||||
|
end
|
||||||
|
end
|
|
@ -37,7 +37,80 @@ test "it works for incoming emoji reactions" do
|
||||||
assert match?([["👌", _, nil]], object.data["reactions"])
|
assert match?([["👌", _, nil]], object.data["reactions"])
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it works for incoming custom emoji reactions" do
|
test "it works for incoming custom emoji with nil id" do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user, local: false)
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{status: "hello"})
|
||||||
|
|
||||||
|
shortcode = "blobcatgoogly"
|
||||||
|
emoji = emoji_object(shortcode)
|
||||||
|
data = react_with_custom(activity.data["object"], other_user.ap_id, emoji)
|
||||||
|
|
||||||
|
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
|
assert data["actor"] == other_user.ap_id
|
||||||
|
assert data["type"] == "EmojiReact"
|
||||||
|
assert data["object"] == activity.data["object"]
|
||||||
|
assert data["content"] == ":" <> shortcode <> ":"
|
||||||
|
[%{}] = data["tag"]
|
||||||
|
|
||||||
|
object = Object.get_by_ap_id(data["object"])
|
||||||
|
|
||||||
|
assert object.data["reaction_count"] == 1
|
||||||
|
assert match?([[^shortcode, _, _]], object.data["reactions"])
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it works for incoming custom emoji with image url as id" do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user, local: false)
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{status: "hello"})
|
||||||
|
|
||||||
|
shortcode = "blobcatgoogly"
|
||||||
|
imgurl = "https://example.org/emoji/a.png"
|
||||||
|
emoji = emoji_object(shortcode, imgurl, imgurl)
|
||||||
|
data = react_with_custom(activity.data["object"], other_user.ap_id, emoji)
|
||||||
|
|
||||||
|
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
|
assert data["actor"] == other_user.ap_id
|
||||||
|
assert data["type"] == "EmojiReact"
|
||||||
|
assert data["object"] == activity.data["object"]
|
||||||
|
assert data["content"] == ":" <> shortcode <> ":"
|
||||||
|
assert [%{}] = data["tag"]
|
||||||
|
|
||||||
|
object = Object.get_by_ap_id(data["object"])
|
||||||
|
|
||||||
|
assert object.data["reaction_count"] == 1
|
||||||
|
assert match?([[^shortcode, _, ^imgurl]], object.data["reactions"])
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it works for incoming custom emoji without tag array" do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user, local: false)
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{status: "hello"})
|
||||||
|
|
||||||
|
shortcode = "blobcatgoogly"
|
||||||
|
imgurl = "https://example.org/emoji/b.png"
|
||||||
|
emoji = emoji_object(shortcode, imgurl, imgurl)
|
||||||
|
data = react_with_custom(activity.data["object"], other_user.ap_id, emoji, false)
|
||||||
|
|
||||||
|
assert %{} = data["tag"]
|
||||||
|
|
||||||
|
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
|
assert data["actor"] == other_user.ap_id
|
||||||
|
assert data["type"] == "EmojiReact"
|
||||||
|
assert data["object"] == activity.data["object"]
|
||||||
|
assert data["content"] == ":" <> shortcode <> ":"
|
||||||
|
assert [%{}] = data["tag"]
|
||||||
|
|
||||||
|
object = Object.get_by_ap_id(data["object"])
|
||||||
|
|
||||||
|
assert object.data["reaction_count"] == 1
|
||||||
|
assert match?([[^shortcode, _, _]], object.data["reactions"])
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it works for incoming custom emoji reactions from Misskey" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
other_user = insert(:user, local: false)
|
other_user = insert(:user, local: false)
|
||||||
{:ok, activity} = CommonAPI.post(user, %{status: "hello"})
|
{:ok, activity} = CommonAPI.post(user, %{status: "hello"})
|
||||||
|
@ -138,4 +211,27 @@ test "it reject invalid emoji reactions" do
|
||||||
|
|
||||||
assert {:error, _} = Transmogrifier.handle_incoming(data)
|
assert {:error, _} = Transmogrifier.handle_incoming(data)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp emoji_object(shortcode, id \\ nil, url \\ "https://example.org/emoji.png") do
|
||||||
|
%{
|
||||||
|
"type" => "Emoji",
|
||||||
|
"id" => id,
|
||||||
|
"name" => shortcode |> String.replace_prefix(":", "") |> String.replace_suffix(":", ""),
|
||||||
|
"icon" => %{
|
||||||
|
"type" => "Image",
|
||||||
|
"url" => url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp react_with_custom(object_id, as_actor, emoji, tag_array \\ true) do
|
||||||
|
tag = if tag_array, do: [emoji], else: emoji
|
||||||
|
|
||||||
|
File.read!("test/fixtures/emoji-reaction.json")
|
||||||
|
|> Jason.decode!()
|
||||||
|
|> Map.put("object", object_id)
|
||||||
|
|> Map.put("actor", as_actor)
|
||||||
|
|> Map.put("content", ":" <> emoji["name"] <> ":")
|
||||||
|
|> Map.put("tag", tag)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -124,6 +124,28 @@ test "it fixes both the Create and object contexts in a reply" do
|
||||||
|
|
||||||
assert activity.data["context"] == object.data["context"]
|
assert activity.data["context"] == object.data["context"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it accepts quote posts" do
|
||||||
|
insert(:user, ap_id: "https://misskey.io/users/7rkrarq81i")
|
||||||
|
|
||||||
|
object = File.read!("test/fixtures/quote_post/misskey_quote_post.json") |> Jason.decode!()
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||||
|
"type" => "Create",
|
||||||
|
"actor" => "https://misskey.io/users/7rkrarq81i",
|
||||||
|
"object" => object
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:ok, activity} = Transmogrifier.handle_incoming(message)
|
||||||
|
|
||||||
|
# Object was created in the database
|
||||||
|
object = Object.normalize(activity)
|
||||||
|
assert object.data["quoteUri"] == "https://misskey.io/notes/8vs6wxufd0"
|
||||||
|
|
||||||
|
# It fetched the quoted post
|
||||||
|
assert Object.normalize("https://misskey.io/notes/8vs6wxufd0")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "prepare outgoing" do
|
describe "prepare outgoing" do
|
||||||
|
@ -413,7 +435,7 @@ test "it rejects activities which reference objects with bogus origins" do
|
||||||
|
|
||||||
assert capture_log(fn ->
|
assert capture_log(fn ->
|
||||||
{:error, _} = Transmogrifier.handle_incoming(data)
|
{:error, _} = Transmogrifier.handle_incoming(data)
|
||||||
end) =~ "Object containment failed"
|
end) =~ "Object rejected while fetching"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
|
test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
|
||||||
|
@ -428,7 +450,7 @@ test "it rejects activities which reference objects that have an incorrect attri
|
||||||
|
|
||||||
assert capture_log(fn ->
|
assert capture_log(fn ->
|
||||||
{:error, _} = Transmogrifier.handle_incoming(data)
|
{:error, _} = Transmogrifier.handle_incoming(data)
|
||||||
end) =~ "Object containment failed"
|
end) =~ "Object rejected while fetching"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
|
test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
|
||||||
|
@ -443,7 +465,7 @@ test "it rejects activities which reference objects that have an incorrect attri
|
||||||
|
|
||||||
assert capture_log(fn ->
|
assert capture_log(fn ->
|
||||||
{:error, _} = Transmogrifier.handle_incoming(data)
|
{:error, _} = Transmogrifier.handle_incoming(data)
|
||||||
end) =~ "Object containment failed"
|
end) =~ "Object rejected while fetching"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -536,7 +558,7 @@ test "returns non-modified object" do
|
||||||
test "returns nil when cannot normalize object" do
|
test "returns nil when cannot normalize object" do
|
||||||
assert capture_log(fn ->
|
assert capture_log(fn ->
|
||||||
refute Transmogrifier.get_obj_helper("test-obj-id")
|
refute Transmogrifier.get_obj_helper("test-obj-id")
|
||||||
end) =~ "URI Scheme Invalid"
|
end) =~ ":valid_uri_scheme"
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag capture_log: true
|
@tag capture_log: true
|
||||||
|
|
|
@ -137,6 +137,37 @@ test "successfully processes incoming AP docs with correct origin" do
|
||||||
assert {:error, :already_present} = ObanHelpers.perform(job)
|
assert {:error, :already_present} = ObanHelpers.perform(job)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "successfully normalises public scope descriptors" do
|
||||||
|
params = %{
|
||||||
|
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||||
|
"actor" => "http://mastodon.example.org/users/admin",
|
||||||
|
"type" => "Create",
|
||||||
|
"id" => "http://mastodon.example.org/users/admin/activities/1",
|
||||||
|
"object" => %{
|
||||||
|
"type" => "Note",
|
||||||
|
"content" => "hi world!",
|
||||||
|
"id" => "http://mastodon.example.org/users/admin/objects/1",
|
||||||
|
"attributedTo" => "http://mastodon.example.org/users/admin",
|
||||||
|
"to" => ["Public"]
|
||||||
|
},
|
||||||
|
"to" => ["as:Public"]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:ok, job} = Federator.incoming_ap_doc(params)
|
||||||
|
assert {:ok, activity} = ObanHelpers.perform(job)
|
||||||
|
assert activity.data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
|
||||||
|
|
||||||
|
object =
|
||||||
|
from(
|
||||||
|
object in Pleroma.Object,
|
||||||
|
where: fragment("(?)->>'id' = ?", object.data, ^activity.data["object"]),
|
||||||
|
limit: 1
|
||||||
|
)
|
||||||
|
|> Repo.one()
|
||||||
|
|
||||||
|
assert object.data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
|
||||||
|
end
|
||||||
|
|
||||||
test "rejects incoming AP docs with incorrect origin" do
|
test "rejects incoming AP docs with incorrect origin" do
|
||||||
params = %{
|
params = %{
|
||||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||||
|
|
|
@ -111,7 +111,7 @@ test "posting a status", %{conn: conn} do
|
||||||
# 2 hours
|
# 2 hours
|
||||||
expires_in = 2 * 60 * 60
|
expires_in = 2 * 60 * 60
|
||||||
|
|
||||||
expires_at = DateTime.add(DateTime.utc_now(), expires_in)
|
expires_at1 = DateTime.add(DateTime.utc_now(), expires_in)
|
||||||
|
|
||||||
conn_four =
|
conn_four =
|
||||||
conn
|
conn
|
||||||
|
@ -123,12 +123,16 @@ test "posting a status", %{conn: conn} do
|
||||||
|
|
||||||
assert %{"id" => fourth_id} = json_response_and_validate_schema(conn_four, 200)
|
assert %{"id" => fourth_id} = json_response_and_validate_schema(conn_four, 200)
|
||||||
|
|
||||||
assert Activity.get_by_id(fourth_id)
|
activity = Activity.get_by_id(fourth_id)
|
||||||
|
assert activity
|
||||||
|
|
||||||
|
{:ok, expires_at2, _} = DateTime.from_iso8601(activity.data["expires_at"])
|
||||||
|
assert Timex.compare(expires_at1, expires_at2, :minutes) == 0
|
||||||
|
|
||||||
assert_enqueued(
|
assert_enqueued(
|
||||||
worker: Pleroma.Workers.PurgeExpiredActivity,
|
worker: Pleroma.Workers.PurgeExpiredActivity,
|
||||||
args: %{activity_id: fourth_id},
|
args: %{activity_id: fourth_id},
|
||||||
scheduled_at: expires_at
|
scheduled_at: expires_at2
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -148,16 +152,13 @@ test "automatically setting a post expiry if status_ttl_days is set" do
|
||||||
activity = Activity.get_by_id_with_object(id)
|
activity = Activity.get_by_id_with_object(id)
|
||||||
{:ok, expires_at, _} = DateTime.from_iso8601(activity.data["expires_at"])
|
{:ok, expires_at, _} = DateTime.from_iso8601(activity.data["expires_at"])
|
||||||
|
|
||||||
assert Timex.diff(
|
expiry_delay = Timex.diff(expires_at, DateTime.utc_now(), :hours)
|
||||||
expires_at,
|
assert(expiry_delay in [23, 24])
|
||||||
DateTime.utc_now(),
|
|
||||||
:hours
|
|
||||||
) == 23
|
|
||||||
|
|
||||||
assert_enqueued(
|
assert_enqueued(
|
||||||
worker: Pleroma.Workers.PurgeExpiredActivity,
|
worker: Pleroma.Workers.PurgeExpiredActivity,
|
||||||
args: %{activity_id: id},
|
args: %{activity_id: id},
|
||||||
scheduled_at: DateTime.add(DateTime.utc_now(), 1 * 60 * 60 * 24)
|
scheduled_at: expires_at
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1405,7 +1406,7 @@ test "on pin removes deletion job, on unpin reschedule deletion" do
|
||||||
%{conn: conn} = oauth_access(["write:accounts", "write:statuses"])
|
%{conn: conn} = oauth_access(["write:accounts", "write:statuses"])
|
||||||
expires_in = 2 * 60 * 60
|
expires_in = 2 * 60 * 60
|
||||||
|
|
||||||
expires_at = DateTime.add(DateTime.utc_now(), expires_in)
|
expires_at1 = DateTime.add(DateTime.utc_now(), expires_in)
|
||||||
|
|
||||||
assert %{"id" => id} =
|
assert %{"id" => id} =
|
||||||
conn
|
conn
|
||||||
|
@ -1416,10 +1417,15 @@ test "on pin removes deletion job, on unpin reschedule deletion" do
|
||||||
})
|
})
|
||||||
|> json_response_and_validate_schema(200)
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
activity = Activity.get_by_id(id)
|
||||||
|
{:ok, expires_at2, _} = DateTime.from_iso8601(activity.data["expires_at"])
|
||||||
|
|
||||||
|
assert Timex.compare(expires_at1, expires_at2, :minutes) == 0
|
||||||
|
|
||||||
assert_enqueued(
|
assert_enqueued(
|
||||||
worker: Pleroma.Workers.PurgeExpiredActivity,
|
worker: Pleroma.Workers.PurgeExpiredActivity,
|
||||||
args: %{activity_id: id},
|
args: %{activity_id: id},
|
||||||
scheduled_at: expires_at
|
scheduled_at: expires_at2
|
||||||
)
|
)
|
||||||
|
|
||||||
assert %{"id" => ^id, "pinned" => true} =
|
assert %{"id" => ^id, "pinned" => true} =
|
||||||
|
@ -1431,7 +1437,7 @@ test "on pin removes deletion job, on unpin reschedule deletion" do
|
||||||
refute_enqueued(
|
refute_enqueued(
|
||||||
worker: Pleroma.Workers.PurgeExpiredActivity,
|
worker: Pleroma.Workers.PurgeExpiredActivity,
|
||||||
args: %{activity_id: id},
|
args: %{activity_id: id},
|
||||||
scheduled_at: expires_at
|
scheduled_at: expires_at2
|
||||||
)
|
)
|
||||||
|
|
||||||
assert %{"id" => ^id, "pinned" => false} =
|
assert %{"id" => ^id, "pinned" => false} =
|
||||||
|
@ -1443,7 +1449,7 @@ test "on pin removes deletion job, on unpin reschedule deletion" do
|
||||||
assert_enqueued(
|
assert_enqueued(
|
||||||
worker: Pleroma.Workers.PurgeExpiredActivity,
|
worker: Pleroma.Workers.PurgeExpiredActivity,
|
||||||
args: %{activity_id: id},
|
args: %{activity_id: id},
|
||||||
scheduled_at: expires_at
|
scheduled_at: expires_at2
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1944,7 +1950,7 @@ test "expires_at is nil for another user" do
|
||||||
|> json_response_and_validate_schema(:ok)
|
|> json_response_and_validate_schema(:ok)
|
||||||
|
|
||||||
{:ok, a_expires_at, 0} = DateTime.from_iso8601(a_expires_at)
|
{:ok, a_expires_at, 0} = DateTime.from_iso8601(a_expires_at)
|
||||||
assert DateTime.diff(expires_at, a_expires_at) == 0
|
assert Timex.compare(expires_at, a_expires_at, :minutes) == 0
|
||||||
|
|
||||||
%{conn: conn} = oauth_access(["read:statuses"])
|
%{conn: conn} = oauth_access(["read:statuses"])
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,8 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraphTest do
|
||||||
|
|
||||||
setup do: clear_config([Pleroma.Web.Metadata, :unfurl_nsfw])
|
setup do: clear_config([Pleroma.Web.Metadata, :unfurl_nsfw])
|
||||||
setup do: clear_config([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
|
setup do: clear_config([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
|
||||||
|
setup do: clear_config([:restrict_unauthenticated, :profiles, :local])
|
||||||
|
setup do: clear_config([:restrict_unauthenticated, :activities, :local])
|
||||||
|
|
||||||
test "it renders all supported types of attachments and skips unknown types" do
|
test "it renders all supported types of attachments and skips unknown types" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
@ -188,4 +190,24 @@ test "video attachments have no image thumbnail with Preview Proxy disabled" do
|
||||||
"http://localhost:4001/proxy/preview/LzAnlke-l5oZbNzWsrHfprX1rGw/aHR0cHM6Ly9wbGVyb21hLmdvdi9hYm91dC9qdWNoZS53ZWJt/juche.webm"
|
"http://localhost:4001/proxy/preview/LzAnlke-l5oZbNzWsrHfprX1rGw/aHR0cHM6Ly9wbGVyb21hLmdvdi9hYm91dC9qdWNoZS53ZWJt/juche.webm"
|
||||||
], []} in result
|
], []} in result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it does not render users if profiles are marked as restricted" do
|
||||||
|
clear_config([:restrict_unauthenticated, :profiles, :local], true)
|
||||||
|
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
result = OpenGraph.build_tags(%{user: user})
|
||||||
|
assert Enum.empty?(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it does not activities users if they are marked as restricted" do
|
||||||
|
clear_config([:restrict_unauthenticated, :activities, :local], true)
|
||||||
|
|
||||||
|
user = insert(:user)
|
||||||
|
note = insert(:note, data: %{"actor" => user.ap_id})
|
||||||
|
|
||||||
|
result = OpenGraph.build_tags(%{object: note, url: note.data["id"], user: user})
|
||||||
|
|
||||||
|
assert {:meta, [property: "og:description", content: "Content cannot be displayed."], []} in result
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,6 +14,8 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCardTest do
|
||||||
alias Pleroma.Web.Metadata.Utils
|
alias Pleroma.Web.Metadata.Utils
|
||||||
|
|
||||||
setup do: clear_config([Pleroma.Web.Metadata, :unfurl_nsfw])
|
setup do: clear_config([Pleroma.Web.Metadata, :unfurl_nsfw])
|
||||||
|
setup do: clear_config([:restrict_unauthenticated, :profiles, :local])
|
||||||
|
setup do: clear_config([:restrict_unauthenticated, :activities, :local])
|
||||||
|
|
||||||
test "it renders twitter card for user info" do
|
test "it renders twitter card for user info" do
|
||||||
user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994")
|
user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994")
|
||||||
|
@ -28,6 +30,14 @@ test "it renders twitter card for user info" do
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it does not render twitter card for user info if it is restricted" do
|
||||||
|
clear_config([:restrict_unauthenticated, :profiles, :local], true)
|
||||||
|
user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994")
|
||||||
|
res = TwitterCard.build_tags(%{user: user})
|
||||||
|
|
||||||
|
assert Enum.empty?(res)
|
||||||
|
end
|
||||||
|
|
||||||
test "it uses summary twittercard if post has no attachment" do
|
test "it uses summary twittercard if post has no attachment" do
|
||||||
user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994")
|
user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994")
|
||||||
{:ok, activity} = CommonAPI.post(user, %{status: "HI"})
|
{:ok, activity} = CommonAPI.post(user, %{status: "HI"})
|
||||||
|
@ -54,6 +64,16 @@ test "it uses summary twittercard if post has no attachment" do
|
||||||
] == result
|
] == result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it does not summarise activities if they are marked as restricted" do
|
||||||
|
clear_config([:restrict_unauthenticated, :activities, :local], true)
|
||||||
|
user = insert(:user)
|
||||||
|
note = insert(:note, data: %{"actor" => user.ap_id})
|
||||||
|
|
||||||
|
result = TwitterCard.build_tags(%{object: note, activity_id: note.data["id"], user: user})
|
||||||
|
|
||||||
|
assert {:meta, [name: "twitter:description", content: "Content cannot be displayed."], []} in result
|
||||||
|
end
|
||||||
|
|
||||||
test "it uses summary as description if post has one" do
|
test "it uses summary as description if post has one" do
|
||||||
user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994")
|
user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994")
|
||||||
{:ok, activity} = CommonAPI.post(user, %{status: "HI"})
|
{:ok, activity} = CommonAPI.post(user, %{status: "HI"})
|
||||||
|
|
|
@ -7,8 +7,6 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlugTest do
|
||||||
|
|
||||||
alias Plug.Conn
|
alias Plug.Conn
|
||||||
|
|
||||||
setup_all do: clear_config([Pleroma.Upload, :base_url], nil)
|
|
||||||
|
|
||||||
describe "http security enabled" do
|
describe "http security enabled" do
|
||||||
setup do: clear_config([:http_security, :enabled], true)
|
setup do: clear_config([:http_security, :enabled], true)
|
||||||
|
|
||||||
|
@ -98,51 +96,68 @@ test "it sets the Service-Worker-Allowed header", %{conn: conn} do
|
||||||
test "media_proxy with base_url", %{conn: conn} do
|
test "media_proxy with base_url", %{conn: conn} do
|
||||||
url = "https://example.com"
|
url = "https://example.com"
|
||||||
clear_config([:media_proxy, :base_url], url)
|
clear_config([:media_proxy, :base_url], url)
|
||||||
assert_media_img_src(conn, url)
|
assert_media_img_src(conn, proxy: url)
|
||||||
assert_connect_src(conn, url)
|
assert_connect_src(conn, url)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "upload with base url", %{conn: conn} do
|
test "upload with base url", %{conn: conn} do
|
||||||
url = "https://example2.com"
|
url = "https://example2.com"
|
||||||
clear_config([Pleroma.Upload, :base_url], url)
|
clear_config([Pleroma.Upload, :base_url], url)
|
||||||
assert_media_img_src(conn, url)
|
assert_media_img_src(conn, upload: url)
|
||||||
assert_connect_src(conn, url)
|
assert_connect_src(conn, url)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "with S3 public endpoint", %{conn: conn} do
|
test "with S3 public endpoint", %{conn: conn} do
|
||||||
url = "https://example3.com"
|
url = "https://example3.com"
|
||||||
clear_config([Pleroma.Uploaders.S3, :public_endpoint], url)
|
clear_config([Pleroma.Uploaders.S3, :public_endpoint], url)
|
||||||
assert_media_img_src(conn, url)
|
assert_media_img_src(conn, s3: url)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "with captcha endpoint", %{conn: conn} do
|
test "with captcha endpoint", %{conn: conn} do
|
||||||
clear_config([Pleroma.Captcha.Mock, :endpoint], "https://captcha.com")
|
clear_config([Pleroma.Captcha.Mock, :endpoint], "https://captcha.com")
|
||||||
assert_media_img_src(conn, "https://captcha.com")
|
assert_media_img_src(conn, captcha: "https://captcha.com")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "with media_proxy whitelist", %{conn: conn} do
|
test "with media_proxy whitelist", %{conn: conn} do
|
||||||
clear_config([:media_proxy, :whitelist], ["https://example6.com", "https://example7.com"])
|
clear_config([:media_proxy, :whitelist], ["https://example6.com", "https://example7.com"])
|
||||||
assert_media_img_src(conn, "https://example7.com https://example6.com")
|
assert_media_img_src(conn, proxy_whitelist: "https://example7.com https://example6.com")
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: delete after removing support bare domains for media proxy whitelist
|
# TODO: delete after removing support bare domains for media proxy whitelist
|
||||||
test "with media_proxy bare domains whitelist (deprecated)", %{conn: conn} do
|
test "with media_proxy bare domains whitelist (deprecated)", %{conn: conn} do
|
||||||
clear_config([:media_proxy, :whitelist], ["example4.com", "example5.com"])
|
clear_config([:media_proxy, :whitelist], ["example4.com", "example5.com"])
|
||||||
assert_media_img_src(conn, "example5.com example4.com")
|
assert_media_img_src(conn, proxy_whitelist: "example5.com example4.com")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "with media_proxy blocklist", %{conn: conn} do
|
test "with media_proxy blocklist", %{conn: conn} do
|
||||||
clear_config([:media_proxy, :whitelist], ["https://example6.com", "https://example7.com"])
|
clear_config([:media_proxy, :whitelist], ["https://example6.com", "https://example7.com"])
|
||||||
clear_config([:media_proxy, :blocklist], ["https://example8.com"])
|
clear_config([:media_proxy, :blocklist], ["https://example8.com"])
|
||||||
assert_media_img_src(conn, "https://example7.com https://example6.com")
|
assert_media_img_src(conn, proxy_whitelist: "https://example7.com https://example6.com")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp assert_media_img_src(conn, url) do
|
defp maybe_concat(nil, b), do: b
|
||||||
|
defp maybe_concat(a, nil), do: a
|
||||||
|
defp maybe_concat(a, b), do: a <> " " <> b
|
||||||
|
|
||||||
|
defp build_src_str(urls) do
|
||||||
|
urls[:proxy_whitelist]
|
||||||
|
|> maybe_concat(urls[:s3])
|
||||||
|
|> maybe_concat(urls[:upload])
|
||||||
|
|> maybe_concat(urls[:proxy])
|
||||||
|
|> maybe_concat(urls[:captcha])
|
||||||
|
end
|
||||||
|
|
||||||
|
defp assert_media_img_src(conn, urls) do
|
||||||
|
urlstr =
|
||||||
|
[upload: "http://localhost", proxy: "http://localhost"]
|
||||||
|
|> Keyword.merge(urls)
|
||||||
|
|> build_src_str()
|
||||||
|
|
||||||
conn = get(conn, "/api/v1/instance")
|
conn = get(conn, "/api/v1/instance")
|
||||||
[csp] = Conn.get_resp_header(conn, "content-security-policy")
|
[csp] = Conn.get_resp_header(conn, "content-security-policy")
|
||||||
assert csp =~ "media-src 'self' #{url};"
|
assert csp =~ "media-src 'self' #{urlstr};"
|
||||||
assert csp =~ "img-src 'self' data: blob: #{url};"
|
assert csp =~ "img-src 'self' data: blob: #{urlstr};"
|
||||||
end
|
end
|
||||||
|
|
||||||
defp assert_connect_src(conn, url) do
|
defp assert_connect_src(conn, url) do
|
||||||
|
|
|
@ -132,7 +132,7 @@ test "show follow page with error when user can not be fetched by `acct` link",
|
||||||
|> html_response(200)
|
|> html_response(200)
|
||||||
|
|
||||||
assert response =~ "Error fetching user"
|
assert response =~ "Error fetching user"
|
||||||
end) =~ "Object has been deleted"
|
end) =~ ":not_found"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
|
||||||
describe "PUT /api/pleroma/notification_settings" do
|
describe "PUT /api/pleroma/notification_settings" do
|
||||||
setup do: oauth_access(["write:accounts"])
|
setup do: oauth_access(["write:accounts"])
|
||||||
|
|
||||||
test "it updates notification settings", %{user: user, conn: conn} do
|
test "it updates notification settings via url paramters", %{user: user, conn: conn} do
|
||||||
conn
|
conn
|
||||||
|> put(
|
|> put(
|
||||||
"/api/pleroma/notification_settings?#{URI.encode_query(%{block_from_strangers: true})}"
|
"/api/pleroma/notification_settings?#{URI.encode_query(%{block_from_strangers: true})}"
|
||||||
|
@ -39,6 +39,57 @@ test "it updates notification settings", %{user: user, conn: conn} do
|
||||||
} == user.notification_settings
|
} == user.notification_settings
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it updates notification settings via JSON body params", %{user: user, conn: conn} do
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> put(
|
||||||
|
"/api/pleroma/notification_settings",
|
||||||
|
%{"block_from_strangers" => true}
|
||||||
|
)
|
||||||
|
|> json_response_and_validate_schema(:ok)
|
||||||
|
|
||||||
|
user = refresh_record(user)
|
||||||
|
|
||||||
|
assert %Pleroma.User.NotificationSetting{
|
||||||
|
block_from_strangers: true,
|
||||||
|
hide_notification_contents: false
|
||||||
|
} == user.notification_settings
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it updates notification settings via form data", %{user: user, conn: conn} do
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "multipart/form-data")
|
||||||
|
|> put(
|
||||||
|
"/api/pleroma/notification_settings",
|
||||||
|
%{:block_from_strangers => true}
|
||||||
|
)
|
||||||
|
|> json_response_and_validate_schema(:ok)
|
||||||
|
|
||||||
|
user = refresh_record(user)
|
||||||
|
|
||||||
|
assert %Pleroma.User.NotificationSetting{
|
||||||
|
block_from_strangers: true,
|
||||||
|
hide_notification_contents: false
|
||||||
|
} == user.notification_settings
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it updates notification settings via urlencoded body", %{user: user, conn: conn} do
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/x-www-form-urlencoded")
|
||||||
|
|> put(
|
||||||
|
"/api/pleroma/notification_settings",
|
||||||
|
"block_from_strangers=true"
|
||||||
|
)
|
||||||
|
|> json_response_and_validate_schema(:ok)
|
||||||
|
|
||||||
|
user = refresh_record(user)
|
||||||
|
|
||||||
|
assert %Pleroma.User.NotificationSetting{
|
||||||
|
block_from_strangers: true,
|
||||||
|
hide_notification_contents: false
|
||||||
|
} == user.notification_settings
|
||||||
|
end
|
||||||
|
|
||||||
test "it updates notification settings to enable hiding contents", %{user: user, conn: conn} do
|
test "it updates notification settings to enable hiding contents", %{user: user, conn: conn} do
|
||||||
conn
|
conn
|
||||||
|> put(
|
|> put(
|
||||||
|
@ -53,6 +104,27 @@ test "it updates notification settings to enable hiding contents", %{user: user,
|
||||||
hide_notification_contents: true
|
hide_notification_contents: true
|
||||||
} == user.notification_settings
|
} == user.notification_settings
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# we already test all body variants for block_from_strangers, so just one should suffice here
|
||||||
|
test "it updates notification settings to enable hiding contents via JSON body params", %{
|
||||||
|
user: user,
|
||||||
|
conn: conn
|
||||||
|
} do
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> put(
|
||||||
|
"/api/pleroma/notification_settings",
|
||||||
|
%{"hide_notification_contents" => true}
|
||||||
|
)
|
||||||
|
|> json_response_and_validate_schema(:ok)
|
||||||
|
|
||||||
|
user = refresh_record(user)
|
||||||
|
|
||||||
|
assert %Pleroma.User.NotificationSetting{
|
||||||
|
block_from_strangers: false,
|
||||||
|
hide_notification_contents: true
|
||||||
|
} == user.notification_settings
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "GET /api/pleroma/frontend_configurations" do
|
describe "GET /api/pleroma/frontend_configurations" do
|
||||||
|
|
|
@ -46,8 +46,7 @@ test "Webfinger JRD" do
|
||||||
assert response["subject"] == "acct:#{user.nickname}@localhost"
|
assert response["subject"] == "acct:#{user.nickname}@localhost"
|
||||||
|
|
||||||
assert response["aliases"] == [
|
assert response["aliases"] == [
|
||||||
"https://hyrule.world/users/zelda",
|
"https://hyrule.world/users/zelda"
|
||||||
"https://mushroom.kingdom/users/toad"
|
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -104,7 +103,6 @@ test "Webfinger XML" do
|
||||||
|> response(200)
|
|> response(200)
|
||||||
|
|
||||||
assert response =~ "<Alias>https://hyrule.world/users/zelda</Alias>"
|
assert response =~ "<Alias>https://hyrule.world/users/zelda</Alias>"
|
||||||
assert response =~ "<Alias>https://mushroom.kingdom/users/toad</Alias>"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it returns 404 when user isn't found (XML)" do
|
test "it returns 404 when user isn't found (XML)" do
|
||||||
|
|
69
test/pleroma/workers/remote_fetcher_worker_test.exs
Normal file
69
test/pleroma/workers/remote_fetcher_worker_test.exs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Workers.RemoteFetcherWorkerTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
use Oban.Testing, repo: Pleroma.Repo
|
||||||
|
|
||||||
|
alias Pleroma.Workers.RemoteFetcherWorker
|
||||||
|
|
||||||
|
@deleted_object_one "https://deleted-404.example.com/"
|
||||||
|
@deleted_object_two "https://deleted-410.example.com/"
|
||||||
|
@unauthorized_object "https://unauthorized.example.com/"
|
||||||
|
@depth_object "https://depth.example.com/"
|
||||||
|
|
||||||
|
describe "RemoteFetcherWorker" do
|
||||||
|
setup do
|
||||||
|
Tesla.Mock.mock(fn
|
||||||
|
%{method: :get, url: @deleted_object_one} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 404
|
||||||
|
}
|
||||||
|
|
||||||
|
%{method: :get, url: @deleted_object_two} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 410
|
||||||
|
}
|
||||||
|
|
||||||
|
%{method: :get, url: @unauthorized_object} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 403
|
||||||
|
}
|
||||||
|
|
||||||
|
%{method: :get, url: @depth_object} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not requeue a deleted object" do
|
||||||
|
assert {:discard, _} =
|
||||||
|
RemoteFetcherWorker.perform(%Oban.Job{
|
||||||
|
args: %{"op" => "fetch_remote", "id" => @deleted_object_one}
|
||||||
|
})
|
||||||
|
|
||||||
|
assert {:discard, _} =
|
||||||
|
RemoteFetcherWorker.perform(%Oban.Job{
|
||||||
|
args: %{"op" => "fetch_remote", "id" => @deleted_object_two}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not requeue an unauthorized object" do
|
||||||
|
assert {:discard, _} =
|
||||||
|
RemoteFetcherWorker.perform(%Oban.Job{
|
||||||
|
args: %{"op" => "fetch_remote", "id" => @unauthorized_object}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not requeue an object that exceeded depth" do
|
||||||
|
clear_config([:instance, :federation_incoming_replies_max_depth], 0)
|
||||||
|
|
||||||
|
assert {:discard, _} =
|
||||||
|
RemoteFetcherWorker.perform(%Oban.Job{
|
||||||
|
args: %{"op" => "fetch_remote", "id" => @depth_object, "depth" => 1}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -559,16 +559,15 @@ def undo_activity_factory(attrs \\ %{}) do
|
||||||
like_activity = attrs[:like_activity] || insert(:like_activity)
|
like_activity = attrs[:like_activity] || insert(:like_activity)
|
||||||
attrs = Map.drop(attrs, [:like_activity])
|
attrs = Map.drop(attrs, [:like_activity])
|
||||||
|
|
||||||
data =
|
data = %{
|
||||||
%{
|
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
|
||||||
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
|
"type" => "Undo",
|
||||||
"type" => "Undo",
|
"actor" => like_activity.data["actor"],
|
||||||
"actor" => like_activity.data["actor"],
|
"to" => like_activity.data["to"],
|
||||||
"to" => like_activity.data["to"],
|
"object" => like_activity.data["id"],
|
||||||
"object" => like_activity.data["id"],
|
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
|
||||||
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
|
"context" => like_activity.data["context"]
|
||||||
"context" => like_activity.data["context"]
|
}
|
||||||
}
|
|
||||||
|
|
||||||
%Pleroma.Activity{
|
%Pleroma.Activity{
|
||||||
data: data,
|
data: data,
|
||||||
|
|
|
@ -5,7 +5,16 @@
|
||||||
defmodule HttpRequestMock do
|
defmodule HttpRequestMock do
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
def activitypub_object_headers, do: [{"content-type", "application/activity+json"}]
|
def activitypub_object_headers,
|
||||||
|
do: [
|
||||||
|
{"content-type", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""}
|
||||||
|
]
|
||||||
|
|
||||||
|
# The Accept headers we genrate to be exact; AP spec only requires the first somewhere
|
||||||
|
@activitypub_accept_headers [
|
||||||
|
{"accept", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""},
|
||||||
|
{"accept", "application/activity+json"}
|
||||||
|
]
|
||||||
|
|
||||||
def request(
|
def request(
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
|
@ -97,7 +106,7 @@ def get("https://mastodon.sdf.org/users/rinpatch/collections/featured", _, _, _)
|
||||||
File.read!("test/fixtures/users_mock/masto_featured.json")
|
File.read!("test/fixtures/users_mock/masto_featured.json")
|
||||||
|> String.replace("{{domain}}", "mastodon.sdf.org")
|
|> String.replace("{{domain}}", "mastodon.sdf.org")
|
||||||
|> String.replace("{{nickname}}", "rinpatch"),
|
|> String.replace("{{nickname}}", "rinpatch"),
|
||||||
headers: [{"content-type", "application/activity+json"}]
|
headers: activitypub_object_headers()
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -208,7 +217,7 @@ def get(
|
||||||
"https://mst3k.interlinked.me/users/luciferMysticus",
|
"https://mst3k.interlinked.me/users/luciferMysticus",
|
||||||
_,
|
_,
|
||||||
_,
|
_,
|
||||||
[{"accept", "application/activity+json"}]
|
@activitypub_accept_headers
|
||||||
) do
|
) do
|
||||||
{:ok,
|
{:ok,
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
|
@ -231,7 +240,7 @@ def get(
|
||||||
"https://hubzilla.example.org/channel/kaniini",
|
"https://hubzilla.example.org/channel/kaniini",
|
||||||
_,
|
_,
|
||||||
_,
|
_,
|
||||||
[{"accept", "application/activity+json"}]
|
@activitypub_accept_headers
|
||||||
) do
|
) do
|
||||||
{:ok,
|
{:ok,
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
|
@ -241,7 +250,7 @@ def get(
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
def get("https://niu.moe/users/rye", _, _, [{"accept", "application/activity+json"}]) do
|
def get("https://niu.moe/users/rye", _, _, @activitypub_accept_headers) do
|
||||||
{:ok,
|
{:ok,
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
status: 200,
|
status: 200,
|
||||||
|
@ -250,7 +259,7 @@ def get("https://niu.moe/users/rye", _, _, [{"accept", "application/activity+jso
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
def get("https://n1u.moe/users/rye", _, _, [{"accept", "application/activity+json"}]) do
|
def get("https://n1u.moe/users/rye", _, _, @activitypub_accept_headers) do
|
||||||
{:ok,
|
{:ok,
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
status: 200,
|
status: 200,
|
||||||
|
@ -270,7 +279,7 @@ def get("http://mastodon.example.org/users/admin/statuses/100787282858396771", _
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
def get("https://puckipedia.com/", _, _, [{"accept", "application/activity+json"}]) do
|
def get("https://puckipedia.com/", _, _, @activitypub_accept_headers) do
|
||||||
{:ok,
|
{:ok,
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
status: 200,
|
status: 200,
|
||||||
|
@ -342,9 +351,12 @@ def get("https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
def get("https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39", _, _, [
|
def get(
|
||||||
{"accept", "application/activity+json"}
|
"https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39",
|
||||||
]) do
|
_,
|
||||||
|
_,
|
||||||
|
@activitypub_accept_headers
|
||||||
|
) do
|
||||||
{:ok,
|
{:ok,
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
status: 200,
|
status: 200,
|
||||||
|
@ -353,7 +365,7 @@ def get("https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39", _,
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
def get("https://mobilizon.org/@tcit", _, _, [{"accept", "application/activity+json"}]) do
|
def get("https://mobilizon.org/@tcit", _, _, @activitypub_accept_headers) do
|
||||||
{:ok,
|
{:ok,
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
status: 200,
|
status: 200,
|
||||||
|
@ -416,9 +428,7 @@ def get(
|
||||||
{:ok, %Tesla.Env{status: 404, body: ""}}
|
{:ok, %Tesla.Env{status: 404, body: ""}}
|
||||||
end
|
end
|
||||||
|
|
||||||
def get("http://mastodon.example.org/users/relay", _, _, [
|
def get("http://mastodon.example.org/users/relay", _, _, @activitypub_accept_headers) do
|
||||||
{"accept", "application/activity+json"}
|
|
||||||
]) do
|
|
||||||
{:ok,
|
{:ok,
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
status: 200,
|
status: 200,
|
||||||
|
@ -427,9 +437,7 @@ def get("http://mastodon.example.org/users/relay", _, _, [
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
def get("http://mastodon.example.org/users/gargron", _, _, [
|
def get("http://mastodon.example.org/users/gargron", _, _, @activitypub_accept_headers) do
|
||||||
{"accept", "application/activity+json"}
|
|
||||||
]) do
|
|
||||||
{:error, :nxdomain}
|
{:error, :nxdomain}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -620,7 +628,7 @@ def get("https://shitposter.club/notice/7369654", _, _, _) do
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
def get("https://mstdn.io/users/mayuutann", _, _, [{"accept", "application/activity+json"}]) do
|
def get("https://mstdn.io/users/mayuutann", _, _, @activitypub_accept_headers) do
|
||||||
{:ok,
|
{:ok,
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
status: 200,
|
status: 200,
|
||||||
|
@ -633,7 +641,7 @@ def get(
|
||||||
"https://mstdn.io/users/mayuutann/statuses/99568293732299394",
|
"https://mstdn.io/users/mayuutann/statuses/99568293732299394",
|
||||||
_,
|
_,
|
||||||
_,
|
_,
|
||||||
[{"accept", "application/activity+json"}]
|
@activitypub_accept_headers
|
||||||
) do
|
) do
|
||||||
{:ok,
|
{:ok,
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
|
@ -779,7 +787,7 @@ def get(
|
||||||
"http://gs.example.org:4040/index.php/user/1",
|
"http://gs.example.org:4040/index.php/user/1",
|
||||||
_,
|
_,
|
||||||
_,
|
_,
|
||||||
[{"accept", "application/activity+json"}]
|
@activitypub_accept_headers
|
||||||
) do
|
) do
|
||||||
{:ok, %Tesla.Env{status: 406, body: ""}}
|
{:ok, %Tesla.Env{status: 406, body: ""}}
|
||||||
end
|
end
|
||||||
|
@ -966,7 +974,7 @@ def get("https://apfed.club/channel/indio", _, _, _) do
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
def get("https://social.heldscal.la/user/23211", _, _, [{"accept", "application/activity+json"}]) do
|
def get("https://social.heldscal.la/user/23211", _, _, @activitypub_accept_headers) do
|
||||||
{:ok, Tesla.Mock.json(%{"id" => "https://social.heldscal.la/user/23211"}, status: 200)}
|
{:ok, Tesla.Mock.json(%{"id" => "https://social.heldscal.la/user/23211"}, status: 200)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1207,13 +1215,11 @@ def get("https://lm.kazv.moe/users/mewmew/collections/featured", _, _, _) do
|
||||||
File.read!("test/fixtures/users_mock/masto_featured.json")
|
File.read!("test/fixtures/users_mock/masto_featured.json")
|
||||||
|> String.replace("{{domain}}", "lm.kazv.moe")
|
|> String.replace("{{domain}}", "lm.kazv.moe")
|
||||||
|> String.replace("{{nickname}}", "mewmew"),
|
|> String.replace("{{nickname}}", "mewmew"),
|
||||||
headers: [{"content-type", "application/activity+json"}]
|
headers: activitypub_object_headers()
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
def get("https://info.pleroma.site/activity.json", _, _, [
|
def get("https://info.pleroma.site/activity.json", _, _, @activitypub_accept_headers) do
|
||||||
{"accept", "application/activity+json"}
|
|
||||||
]) do
|
|
||||||
{:ok,
|
{:ok,
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
status: 200,
|
status: 200,
|
||||||
|
@ -1226,9 +1232,7 @@ def get("https://info.pleroma.site/activity.json", _, _, _) do
|
||||||
{:ok, %Tesla.Env{status: 404, body: ""}}
|
{:ok, %Tesla.Env{status: 404, body: ""}}
|
||||||
end
|
end
|
||||||
|
|
||||||
def get("https://info.pleroma.site/activity2.json", _, _, [
|
def get("https://info.pleroma.site/activity2.json", _, _, @activitypub_accept_headers) do
|
||||||
{"accept", "application/activity+json"}
|
|
||||||
]) do
|
|
||||||
{:ok,
|
{:ok,
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
status: 200,
|
status: 200,
|
||||||
|
@ -1241,9 +1245,7 @@ def get("https://info.pleroma.site/activity2.json", _, _, _) do
|
||||||
{:ok, %Tesla.Env{status: 404, body: ""}}
|
{:ok, %Tesla.Env{status: 404, body: ""}}
|
||||||
end
|
end
|
||||||
|
|
||||||
def get("https://info.pleroma.site/activity3.json", _, _, [
|
def get("https://info.pleroma.site/activity3.json", _, _, @activitypub_accept_headers) do
|
||||||
{"accept", "application/activity+json"}
|
|
||||||
]) do
|
|
||||||
{:ok,
|
{:ok,
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
status: 200,
|
status: 200,
|
||||||
|
@ -1319,6 +1321,25 @@ def get("https://skippers-bin.com/notes/7x9tmrp97i", _, _, _) do
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# A misskey quote
|
||||||
|
def get("https://misskey.io/notes/8vs6wxufd0", _, _, _) do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/tesla_mock/misskey.io_8vs6wxufd0.json"),
|
||||||
|
headers: activitypub_object_headers()
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get("https://misskey.io/users/83ssedkv53", _, _, _) do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/tesla_mock/aimu@misskey.io.json"),
|
||||||
|
headers: activitypub_object_headers()
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
def get("https://example.org/emoji/firedfox.png", _, _, _) do
|
def get("https://example.org/emoji/firedfox.png", _, _, _) do
|
||||||
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.jpg")}}
|
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.jpg")}}
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue