forked from AkkomaGang/akkoma
Merge branch 'develop' of https://akkoma.dev/AkkomaGang/akkoma into akko.wtf
This commit is contained in:
commit
36f2422650
56 changed files with 9268 additions and 1049 deletions
25
CHANGELOG.md
25
CHANGELOG.md
|
@ -6,16 +6,41 @@ 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
|
||||
|
||||
|
|
2
Procfile
2
Procfile
|
@ -1,2 +0,0 @@
|
|||
web: mix phx.server
|
||||
release: mix ecto.migrate
|
|
@ -222,6 +222,26 @@ config :pleroma, :config_description, [
|
|||
}
|
||||
]
|
||||
},
|
||||
%{
|
||||
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,
|
||||
key: Pleroma.Emails.Mailer,
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"skip_files": [
|
||||
"test/support",
|
||||
"lib/mix/tasks/pleroma/benchmark.ex",
|
||||
"lib/credo/check/consistency/file_location.ex"
|
||||
]
|
||||
}
|
|
@ -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.)
|
||||
- `--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
|
||||
- `--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
|
||||
- `--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
|
||||
|
|
|
@ -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}`.
|
||||
|
||||
#### 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.
|
||||
|
||||
|
|
|
@ -145,47 +145,13 @@ If you want to open your newly installed instance to the world, you should run n
|
|||
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
|
||||
|
||||
```shell
|
||||
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).
|
||||
|
||||
```
|
||||
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;
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
* 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).
|
||||
* Enable and start nginx:
|
||||
|
||||
```shell
|
||||
|
@ -193,10 +159,37 @@ doas rc-update add nginx
|
|||
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
|
||||
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
|
||||
|
|
|
@ -136,16 +136,17 @@ If you want to open your newly installed instance to the world, you should run n
|
|||
sudo pacman -S nginx
|
||||
```
|
||||
|
||||
* Create directories for available and enabled sites:
|
||||
* Copy the example nginx configuration:
|
||||
|
||||
```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
|
||||
include sites-enabled/*;
|
||||
```shell
|
||||
sudo systemctl enable --now nginx.service
|
||||
```
|
||||
|
||||
* 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
|
||||
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`.
|
||||
|
||||
---
|
||||
|
||||
* Copy the example nginx configuration and activate it:
|
||||
To make sure renewals work, enable the appropriate systemd timer:
|
||||
|
||||
```shell
|
||||
sudo cp /opt/akkoma/installation/nginx/akkoma.nginx /etc/nginx/sites-available/akkoma.nginx
|
||||
sudo ln -s /etc/nginx/sites-available/akkoma.nginx /etc/nginx/sites-enabled/akkoma.nginx
|
||||
sudo systemctl enable --now certbot-renew.timer
|
||||
```
|
||||
|
||||
* Before starting nginx edit the configuration and change it to your needs (e.g. change servername, change cert paths)
|
||||
* Enable and start nginx:
|
||||
|
||||
```shell
|
||||
sudo systemctl enable --now nginx.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/
|
||||
```
|
||||
Certificate renewal should be handled automatically by Certbot from now on.
|
||||
|
||||
#### 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
|
||||
```
|
||||
|
||||
* 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:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
You can find example configurations for them in `/opt/akkoma/installation/`.
|
||||
|
|
|
@ -135,23 +135,6 @@ If you want to open your newly installed instance to the world, you should run n
|
|||
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:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
You can find example configurations for them in `/opt/akkoma/installation/`.
|
||||
|
|
|
@ -201,25 +201,6 @@ Assuming you want to open your newly installed federated social network to, well
|
|||
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:
|
||||
|
||||
```shell
|
||||
|
@ -237,9 +218,24 @@ Pay special attention to the line that begins with `ssl_ecdh_curve`. It is stong
|
|||
|
||||
```shell
|
||||
# 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.
|
||||
|
||||
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.
|
||||
|
||||
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`)
|
||||
|
||||
## `ffmpeg`
|
||||
|
@ -29,4 +29,5 @@ It is required for the following Akkoma features:
|
|||
`exiftool` is media files metadata reader/writer.
|
||||
|
||||
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.
|
||||
* 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.
|
||||
|
||||
|
@ -176,11 +176,6 @@ su akkoma -s $SHELL -lc "./bin/pleroma stop"
|
|||
|
||||
### 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
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
=== "Alpine"
|
||||
|
@ -252,32 +255,19 @@ If everything worked, you should see Akkoma-FE when visiting your domain. If tha
|
|||
## Post installation
|
||||
|
||||
### 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"
|
||||
```
|
||||
# Restart nginx
|
||||
rc-service nginx restart
|
||||
|
||||
# Start the cron daemon and make it start on boot
|
||||
rc-service crond start
|
||||
rc-update add crond
|
||||
|
||||
# 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
|
||||
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
|
||||
chmod +x /etc/periodic/daily/renew-akkoma-cert
|
||||
|
||||
|
@ -286,22 +276,7 @@ nginx -t
|
|||
```
|
||||
|
||||
=== "Debian/Ubuntu"
|
||||
```
|
||||
# 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
|
||||
```
|
||||
This should be automatically enabled with the `certbot-renew.timer` systemd unit.
|
||||
|
||||
## Create your first user and set as admin
|
||||
```sh
|
||||
|
|
|
@ -82,6 +82,7 @@ Other than things bundled in the OTP release Akkoma depends on:
|
|||
* PostgreSQL (also utilizes extensions in postgresql-contrib)
|
||||
* 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)
|
||||
* If you are using certbot, also install the `python3-certbot-nginx` package for the nginx plugin
|
||||
* libmagic/file
|
||||
|
||||
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
|
||||
|
||||
#### Get a Let's Encrypt certificate
|
||||
|
||||
```shell
|
||||
certbot certonly --standalone --preferred-challenges http -d yourinstance.tld
|
||||
```
|
||||
|
||||
#### Copy Akkoma nginx configuration to the nginx folder
|
||||
|
||||
```shell
|
||||
|
@ -195,8 +190,15 @@ sudo nginx -t
|
|||
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
|
||||
|
||||
|
@ -239,19 +241,11 @@ sudo nginx -t
|
|||
# Restart nginx
|
||||
sudo systemctl restart nginx
|
||||
|
||||
# Ensure the webroot menthod and post hook is working
|
||||
sudo 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
|
||||
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
|
||||
# Test that renewals work properly
|
||||
sudo certbot renew --cert-name yourinstance.tld --nginx --dry-run
|
||||
```
|
||||
|
||||
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
|
||||
```shell
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
elixir_version=1.14.3
|
||||
erlang_version=25.3
|
|
@ -1,12 +1,9 @@
|
|||
# default nginx site config for Akkoma
|
||||
#
|
||||
# Simple installation instructions:
|
||||
# 1. Install your TLS certificate, possibly using Let's Encrypt.
|
||||
# 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.
|
||||
# See the documentation at docs.akkoma.dev for your particular distro/OS for
|
||||
# installation instructions.
|
||||
|
||||
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;
|
||||
|
||||
# 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 {
|
||||
server_name example.tld;
|
||||
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
# Uncomment this if you need to use the 'webroot' method with certbot. Make sure
|
||||
# that the directory exists and that it is accessible by the webserver. If you followed
|
||||
# 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
|
||||
# to get the certificate, and then uncomment it.
|
||||
#
|
||||
# location ~ /\.well-known/acme-challenge {
|
||||
# root /var/lib/letsencrypt/;
|
||||
# }
|
||||
location / {
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
}
|
||||
# If you are setting up TLS certificates without certbot, uncomment the
|
||||
# following to enable HTTP -> HTTPS redirects. Certbot users don't need to do
|
||||
# this as it will automatically do this for you.
|
||||
# server {
|
||||
# server_name example.tld media.example.tld;
|
||||
#
|
||||
# listen 80;
|
||||
# listen [::]:80;
|
||||
#
|
||||
# location / {
|
||||
# return 301 https://$server_name$request_uri;
|
||||
# }
|
||||
# }
|
||||
|
||||
# Enable SSL session caching for improved performance
|
||||
ssl_session_cache shared:ssl_session_cache:10m;
|
||||
|
@ -41,22 +32,29 @@ ssl_session_cache shared:ssl_session_cache:10m;
|
|||
server {
|
||||
server_name example.tld;
|
||||
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
ssl_session_timeout 1d;
|
||||
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
|
||||
ssl_session_tickets off;
|
||||
# Once certbot is set up, this will automatically be updated to listen to
|
||||
# port 443 with TLS alongside a redirect from plaintext HTTP.
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
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;
|
||||
# If you are not using Certbot, comment out the above and uncomment/edit the following
|
||||
# listen 443 ssl http2;
|
||||
# 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_proxied any;
|
||||
|
@ -86,27 +84,22 @@ server {
|
|||
|
||||
# Upload and MediaProxy Subdomain
|
||||
# (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_name media.example.tld;
|
||||
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
# Same as above, will be updated to HTTPS once certbot is set up.
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
ssl_trusted_certificate /etc/letsencrypt/live/media.example.tld/chain.pem;
|
||||
ssl_certificate /etc/letsencrypt/live/media.example.tld/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/media.example.tld/privkey.pem;
|
||||
# .. copy all other the ssl_* and gzip_* stuff from main domain
|
||||
# If you are not using certbot, comment the above and copy all the ssl
|
||||
# stuff from above into here.
|
||||
|
||||
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
|
||||
client_max_body_size 16m;
|
||||
|
|
|
@ -35,7 +35,8 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
static_dir: :string,
|
||||
listen_ip: :string,
|
||||
listen_port: :string,
|
||||
strip_uploads: :string,
|
||||
strip_uploads_metadata: :string,
|
||||
read_uploads_description: :string,
|
||||
anonymize_uploads: :string
|
||||
],
|
||||
aliases: [
|
||||
|
@ -169,21 +170,38 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
)
|
||||
|> Path.expand()
|
||||
|
||||
{strip_uploads_message, strip_uploads_default} =
|
||||
{strip_uploads_metadata_message, strip_uploads_metadata_default} =
|
||||
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"}
|
||||
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"}
|
||||
end
|
||||
|
||||
strip_uploads =
|
||||
strip_uploads_metadata =
|
||||
get_option(
|
||||
options,
|
||||
:strip_uploads,
|
||||
strip_uploads_message,
|
||||
strip_uploads_default
|
||||
:strip_uploads_metadata,
|
||||
strip_uploads_metadata_message,
|
||||
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"
|
||||
|
||||
anonymize_uploads =
|
||||
|
@ -230,7 +248,8 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
listen_port: listen_port,
|
||||
upload_filters:
|
||||
upload_filters(%{
|
||||
strip: strip_uploads,
|
||||
strip_metadata: strip_uploads_metadata,
|
||||
read_description: read_uploads_description,
|
||||
anonymize: anonymize_uploads
|
||||
})
|
||||
)
|
||||
|
@ -305,11 +324,20 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
end
|
||||
|
||||
defp upload_filters(filters) when is_map(filters) do
|
||||
enabled_filters = []
|
||||
|
||||
enabled_filters =
|
||||
if filters.strip do
|
||||
[Pleroma.Upload.Filter.Exiftool]
|
||||
if filters.read_description do
|
||||
enabled_filters ++ [Pleroma.Upload.Filter.Exiftool.ReadDescription]
|
||||
else
|
||||
[]
|
||||
enabled_filters
|
||||
end
|
||||
|
||||
enabled_filters =
|
||||
if filters.strip_metadata do
|
||||
enabled_filters ++ [Pleroma.Upload.Filter.Exiftool.StripMetadata]
|
||||
else
|
||||
enabled_filters
|
||||
end
|
||||
|
||||
enabled_filters =
|
||||
|
|
|
@ -164,7 +164,8 @@ defmodule Pleroma.ApplicationRequirements do
|
|||
|
||||
defp check_system_commands!(:ok) do
|
||||
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.Mogrifun, "mogrify"),
|
||||
check_filter(Pleroma.Upload.Filter.AnalyzeMetadata, "mogrify"),
|
||||
|
|
|
@ -22,6 +22,43 @@ defmodule Pleroma.Config.DeprecationWarnings do
|
|||
"\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
|
||||
has_strings =
|
||||
Config.get([:mrf_simple])
|
||||
|
@ -184,7 +221,8 @@ defmodule Pleroma.Config.DeprecationWarnings do
|
|||
check_simple_policy_tuples(),
|
||||
check_http_adapter(),
|
||||
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
|
||||
:ok, :ok -> :ok
|
||||
|
|
|
@ -61,12 +61,23 @@ defmodule Pleroma.Upload do
|
|||
width: integer(),
|
||||
height: integer(),
|
||||
blurhash: String.t(),
|
||||
description: String.t(),
|
||||
path: String.t()
|
||||
}
|
||||
|
||||
@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()}
|
||||
@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."
|
||||
|
@ -76,7 +87,7 @@ defmodule Pleroma.Upload do
|
|||
with {:ok, upload} <- prepare_upload(upload, opts),
|
||||
upload = %__MODULE__{upload | path: upload.path || "#{upload.id}/#{upload.name}"},
|
||||
{:ok, upload} <- Pleroma.Upload.Filter.filter(opts.filters, upload),
|
||||
description = Map.get(opts, :description) || "",
|
||||
description = Map.get(upload, :description) || "",
|
||||
{_, true} <-
|
||||
{:description_limit,
|
||||
String.length(description) <= Pleroma.Config.get([:instance, :description_limit])},
|
||||
|
@ -152,7 +163,8 @@ defmodule Pleroma.Upload do
|
|||
id: UUID.generate(),
|
||||
name: file.filename,
|
||||
tempfile: file.path,
|
||||
content_type: file.content_type
|
||||
content_type: file.content_type,
|
||||
description: opts.description
|
||||
}}
|
||||
end
|
||||
end
|
||||
|
@ -172,7 +184,8 @@ defmodule Pleroma.Upload do
|
|||
id: UUID.generate(),
|
||||
name: hash <> "." <> ext,
|
||||
tempfile: tmp_path,
|
||||
content_type: content_type
|
||||
content_type: content_type,
|
||||
description: opts.description
|
||||
}}
|
||||
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/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Upload.Filter.Exiftool do
|
||||
defmodule Pleroma.Upload.Filter.Exiftool.StripMetadata do
|
||||
@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.
|
||||
"""
|
||||
@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()}
|
||||
|
||||
# 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/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
|
||||
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
|
||||
case System.cmd("exiftool", ["-overwrite_original", "-gps:all=", file], parallelism: true) do
|
||||
case System.cmd("exiftool", args, parallelism: true) do
|
||||
{_response, 0} -> {:ok, :filtered}
|
||||
{error, 1} -> {:error, error}
|
||||
end
|
|
@ -12,9 +12,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.InternalFetchActor
|
||||
alias Pleroma.Web.ActivityPub.ObjectView
|
||||
alias Pleroma.Web.ActivityPub.Pipeline
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Web.ActivityPub.UserView
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
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)
|
||||
plug(
|
||||
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(
|
||||
Pleroma.Web.Plugs.Cache,
|
||||
[query_params: false, tracking_fun: &__MODULE__.track_object_fetch/2]
|
||||
|
@ -160,7 +156,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
end
|
||||
end
|
||||
|
||||
# GET /relay/following
|
||||
@doc """
|
||||
GET /relay/following
|
||||
"""
|
||||
def relay_following(conn, _params) do
|
||||
with %{halted: false} = conn <- FederatingPlug.call(conn, []) do
|
||||
conn
|
||||
|
@ -197,7 +195,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
end
|
||||
end
|
||||
|
||||
# GET /relay/followers
|
||||
@doc """
|
||||
GET /relay/followers
|
||||
"""
|
||||
def relay_followers(conn, _params) do
|
||||
with %{halted: false} = conn <- FederatingPlug.call(conn, []) do
|
||||
conn
|
||||
|
@ -317,14 +317,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
|> represent_service_actor(conn)
|
||||
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(
|
||||
%{assigns: %{user: %User{nickname: nickname} = user}} = conn,
|
||||
%{"nickname" => nickname, "page" => page?} = params
|
||||
|
@ -375,105 +367,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
|> json(err)
|
||||
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
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|
@ -495,21 +388,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
conn
|
||||
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
|
||||
with %User{} = user <- User.get_cached_by_nickname(nickname) do
|
||||
conn
|
||||
|
|
|
@ -16,11 +16,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UserValidator do
|
|||
alias Pleroma.Object.Containment
|
||||
alias Pleroma.Signature
|
||||
|
||||
require Pleroma.Constants
|
||||
|
||||
@impl true
|
||||
def validate(object, 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),
|
||||
:ok <- validate_inbox(data),
|
||||
:ok <- contain_collection_origin(data) do
|
||||
|
|
|
@ -58,21 +58,48 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|
||||
def fix_summary(object), do: Map.put(object, "summary", "")
|
||||
|
||||
def fix_addressing_list(map, field) do
|
||||
addrs = map[field]
|
||||
|
||||
defp fix_addressing_list(addrs) do
|
||||
cond do
|
||||
is_list(addrs) ->
|
||||
Map.put(map, field, Enum.filter(addrs, &is_binary/1))
|
||||
|
||||
is_binary(addrs) ->
|
||||
Map.put(map, field, [addrs])
|
||||
|
||||
true ->
|
||||
Map.put(map, field, [])
|
||||
is_list(addrs) -> Enum.filter(addrs, &is_binary/1)
|
||||
is_binary(addrs) -> [addrs]
|
||||
true -> []
|
||||
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
|
||||
def fix_explicit_addressing(%{"directMessage" => true} = object, _follower_collection),
|
||||
do: object
|
||||
|
@ -96,6 +123,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|> Map.put("cc", final_cc)
|
||||
end
|
||||
|
||||
def fix_addressing_list_key(map, field) do
|
||||
Map.put(map, field, fix_addressing_list(map[field]))
|
||||
end
|
||||
|
||||
def fix_addressing(object) do
|
||||
{:ok, %User{follower_address: follower_collection}} =
|
||||
object
|
||||
|
@ -103,10 +134,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|> User.get_or_fetch_by_ap_id()
|
||||
|
||||
object
|
||||
|> fix_addressing_list("to")
|
||||
|> fix_addressing_list("cc")
|
||||
|> fix_addressing_list("bto")
|
||||
|> fix_addressing_list("bcc")
|
||||
|> fix_addressing_list_key("to")
|
||||
|> fix_addressing_list_key("cc")
|
||||
|> fix_addressing_list_key("bto")
|
||||
|> fix_addressing_list_key("bcc")
|
||||
|> fix_explicit_addressing(follower_collection)
|
||||
|> CommonFixes.fix_implicit_addressing(follower_collection)
|
||||
end
|
||||
|
@ -383,11 +414,28 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
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
|
||||
# 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(),
|
||||
content <- data["content"] || "",
|
||||
%User{} = actor <- User.get_cached_by_ap_id(actor),
|
||||
|
@ -408,20 +456,21 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
end
|
||||
|
||||
# disallow objects with bogus IDs
|
||||
def handle_incoming(%{"id" => nil}, _options), do: :error
|
||||
def handle_incoming(%{"id" => ""}, _options), do: :error
|
||||
defp handle_incoming_normalised(%{"id" => nil}, _options), do: :error
|
||||
defp handle_incoming_normalised(%{"id" => ""}, _options), do: :error
|
||||
# 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,
|
||||
do: :error
|
||||
defp handle_incoming_normalised(%{"id" => id}, _options)
|
||||
when is_binary(id) and byte_size(id) < 8,
|
||||
do: :error
|
||||
|
||||
@doc "Rewrite misskey likes into EmojiReacts"
|
||||
def handle_incoming(
|
||||
%{
|
||||
"type" => "Like",
|
||||
"content" => reaction
|
||||
} = data,
|
||||
options
|
||||
) do
|
||||
# Rewrite misskey likes into EmojiReacts
|
||||
defp handle_incoming_normalised(
|
||||
%{
|
||||
"type" => "Like",
|
||||
"content" => reaction
|
||||
} = data,
|
||||
options
|
||||
) do
|
||||
if Pleroma.Emoji.is_unicode_emoji?(reaction) || Pleroma.Emoji.matches_shortcode?(reaction) do
|
||||
data
|
||||
|> Map.put("type", "EmojiReact")
|
||||
|
@ -433,11 +482,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
end
|
||||
end
|
||||
|
||||
def handle_incoming(
|
||||
%{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,
|
||||
options
|
||||
)
|
||||
when objtype in ~w{Question Answer Audio Video Event Article Note Page} do
|
||||
defp handle_incoming_normalised(
|
||||
%{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,
|
||||
options
|
||||
)
|
||||
when objtype in ~w{Question Answer Audio Video Event Article Note Page} do
|
||||
fetch_options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
|
||||
|
||||
object =
|
||||
|
@ -469,8 +518,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
end
|
||||
end
|
||||
|
||||
def handle_incoming(%{"type" => type} = data, _options)
|
||||
when type in ~w{Like EmojiReact Announce Add Remove} do
|
||||
defp handle_incoming_normalised(%{"type" => type} = data, _options)
|
||||
when type in ~w{Like EmojiReact Announce Add Remove} do
|
||||
with :ok <- ObjectValidator.fetch_actor_and_object(data),
|
||||
{:ok, activity, _meta} <- Pipeline.common_pipeline(data, local: false) do
|
||||
{:ok, activity}
|
||||
|
@ -480,11 +529,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
end
|
||||
end
|
||||
|
||||
def handle_incoming(
|
||||
%{"type" => type} = data,
|
||||
_options
|
||||
)
|
||||
when type in ~w{Update Block Follow Accept Reject} do
|
||||
defp handle_incoming_normalised(
|
||||
%{"type" => type} = data,
|
||||
_options
|
||||
)
|
||||
when type in ~w{Update Block Follow Accept Reject} do
|
||||
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
|
||||
{:ok, activity, _} <-
|
||||
Pipeline.common_pipeline(data, local: false) do
|
||||
|
@ -492,10 +541,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
end
|
||||
end
|
||||
|
||||
def handle_incoming(
|
||||
%{"type" => "Delete"} = data,
|
||||
_options
|
||||
) do
|
||||
defp handle_incoming_normalised(
|
||||
%{"type" => "Delete"} = data,
|
||||
_options
|
||||
) do
|
||||
with {:ok, activity, _} <-
|
||||
Pipeline.common_pipeline(data, local: false) do
|
||||
{:ok, activity}
|
||||
|
@ -515,15 +564,15 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
end
|
||||
end
|
||||
|
||||
def handle_incoming(
|
||||
%{
|
||||
"type" => "Undo",
|
||||
"object" => %{"type" => "Follow", "object" => followed},
|
||||
"actor" => follower,
|
||||
"id" => id
|
||||
} = _data,
|
||||
_options
|
||||
) do
|
||||
defp handle_incoming_normalised(
|
||||
%{
|
||||
"type" => "Undo",
|
||||
"object" => %{"type" => "Follow", "object" => followed},
|
||||
"actor" => follower,
|
||||
"id" => id
|
||||
} = _data,
|
||||
_options
|
||||
) do
|
||||
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
|
||||
{:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower),
|
||||
{:ok, activity} <- ActivityPub.unfollow(follower, followed, id, false) do
|
||||
|
@ -534,28 +583,28 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
end
|
||||
end
|
||||
|
||||
def handle_incoming(
|
||||
%{
|
||||
"type" => "Undo",
|
||||
"object" => %{"type" => type}
|
||||
} = data,
|
||||
_options
|
||||
)
|
||||
when type in ["Like", "EmojiReact", "Announce", "Block"] do
|
||||
defp handle_incoming_normalised(
|
||||
%{
|
||||
"type" => "Undo",
|
||||
"object" => %{"type" => type}
|
||||
} = data,
|
||||
_options
|
||||
)
|
||||
when type in ["Like", "EmojiReact", "Announce", "Block"] do
|
||||
with {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
|
||||
{:ok, activity}
|
||||
end
|
||||
end
|
||||
|
||||
# For Undos that don't have the complete object attached, try to find it in our database.
|
||||
def handle_incoming(
|
||||
%{
|
||||
"type" => "Undo",
|
||||
"object" => object
|
||||
} = activity,
|
||||
options
|
||||
)
|
||||
when is_binary(object) do
|
||||
defp handle_incoming_normalised(
|
||||
%{
|
||||
"type" => "Undo",
|
||||
"object" => object
|
||||
} = activity,
|
||||
options
|
||||
)
|
||||
when is_binary(object) do
|
||||
with %Activity{data: data} <- Activity.get_by_ap_id(object) do
|
||||
activity
|
||||
|> Map.put("object", data)
|
||||
|
@ -565,15 +614,15 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
end
|
||||
end
|
||||
|
||||
def handle_incoming(
|
||||
%{
|
||||
"type" => "Move",
|
||||
"actor" => origin_actor,
|
||||
"object" => origin_actor,
|
||||
"target" => target_actor
|
||||
},
|
||||
_options
|
||||
) do
|
||||
defp handle_incoming_normalised(
|
||||
%{
|
||||
"type" => "Move",
|
||||
"actor" => origin_actor,
|
||||
"object" => origin_actor,
|
||||
"target" => target_actor
|
||||
},
|
||||
_options
|
||||
) do
|
||||
with %User{} = origin_user <- User.get_cached_by_ap_id(origin_actor),
|
||||
# Use a dramatically shortened maximum age before refresh here because it is reasonable
|
||||
# for a user to
|
||||
|
@ -588,7 +637,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
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
|
||||
def get_obj_helper(id, options \\ []) do
|
||||
|
|
|
@ -26,8 +26,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
"oauthAuthorizationEndpoint" => url(~p"/oauth/authorize"),
|
||||
"oauthRegistrationEndpoint" => url(~p"/api/v1/apps"),
|
||||
"oauthTokenEndpoint" => url(~p"/oauth/token"),
|
||||
"sharedInbox" => url(~p"/inbox"),
|
||||
"uploadMedia" => url(~p"/api/ap/upload_media")
|
||||
"sharedInbox" => url(~p"/inbox")
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -78,9 +78,7 @@ defmodule Pleroma.Web.Feed.FeedView do
|
|||
end
|
||||
|
||||
def activity_content(%{"content" => content}) do
|
||||
content
|
||||
|> String.replace(~r/[\n\r]/, "")
|
||||
|> escape()
|
||||
escape(content)
|
||||
end
|
||||
|
||||
def activity_content(_), do: ""
|
||||
|
|
|
@ -800,13 +800,9 @@ defmodule Pleroma.Web.Router do
|
|||
scope "/", Pleroma.Web.ActivityPub do
|
||||
pipe_through([:activitypub_client])
|
||||
|
||||
get("/api/ap/whoami", ActivityPubController, :whoami)
|
||||
get("/users/:nickname/inbox", ActivityPubController, :read_inbox)
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
|
|
|
@ -151,41 +151,40 @@ defmodule Pleroma.Web.Telemetry do
|
|||
# phoenix.router_dispatch.stop.duration
|
||||
# pleroma.repo.query.total_time
|
||||
# pleroma.repo.query.queue_time
|
||||
dist_metrics =
|
||||
[
|
||||
distribution("phoenix.endpoint.stop.duration.fdist",
|
||||
event_name: [:phoenix, :endpoint, :stop],
|
||||
measurement: :duration,
|
||||
unit: {:native, :millisecond},
|
||||
reporter_options: [
|
||||
buckets: simple_buckets
|
||||
]
|
||||
),
|
||||
distribution("pleroma.repo.query.decode_time.fdist",
|
||||
event_name: [:pleroma, :repo, :query],
|
||||
measurement: :decode_time,
|
||||
unit: {:native, :millisecond},
|
||||
reporter_options: [
|
||||
buckets: simple_buckets_quick
|
||||
]
|
||||
),
|
||||
distribution("pleroma.repo.query.query_time.fdist",
|
||||
event_name: [:pleroma, :repo, :query],
|
||||
measurement: :query_time,
|
||||
unit: {:native, :millisecond},
|
||||
reporter_options: [
|
||||
buckets: simple_buckets
|
||||
]
|
||||
),
|
||||
distribution("pleroma.repo.query.idle_time.fdist",
|
||||
event_name: [:pleroma, :repo, :query],
|
||||
measurement: :idle_time,
|
||||
unit: {:native, :millisecond},
|
||||
reporter_options: [
|
||||
buckets: simple_buckets
|
||||
]
|
||||
)
|
||||
]
|
||||
dist_metrics = [
|
||||
distribution("phoenix.endpoint.stop.duration.fdist",
|
||||
event_name: [:phoenix, :endpoint, :stop],
|
||||
measurement: :duration,
|
||||
unit: {:native, :millisecond},
|
||||
reporter_options: [
|
||||
buckets: simple_buckets
|
||||
]
|
||||
),
|
||||
distribution("pleroma.repo.query.decode_time.fdist",
|
||||
event_name: [:pleroma, :repo, :query],
|
||||
measurement: :decode_time,
|
||||
unit: {:native, :millisecond},
|
||||
reporter_options: [
|
||||
buckets: simple_buckets_quick
|
||||
]
|
||||
),
|
||||
distribution("pleroma.repo.query.query_time.fdist",
|
||||
event_name: [:pleroma, :repo, :query],
|
||||
measurement: :query_time,
|
||||
unit: {:native, :millisecond},
|
||||
reporter_options: [
|
||||
buckets: simple_buckets
|
||||
]
|
||||
),
|
||||
distribution("pleroma.repo.query.idle_time.fdist",
|
||||
event_name: [:pleroma, :repo, :query],
|
||||
measurement: :idle_time,
|
||||
unit: {:native, :millisecond},
|
||||
reporter_options: [
|
||||
buckets: simple_buckets
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
vm_metrics =
|
||||
sum_counter_pair("vm.memory.total",
|
||||
|
|
|
@ -14,7 +14,8 @@ defmodule Pleroma.Workers.ReceiverWorker do
|
|||
else
|
||||
{:error, :origin_containment_failed} -> {:discard, :origin_containment_failed}
|
||||
{:error, {:reject, reason}} -> {:discard, reason}
|
||||
e -> e
|
||||
{:error, _} = e -> e
|
||||
e -> {:error, e}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
2
mix.exs
2
mix.exs
|
@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do
|
|||
def project do
|
||||
[
|
||||
app: :pleroma,
|
||||
version: version("3.12.2"),
|
||||
version: version("3.13.1"),
|
||||
elixir: "~> 1.14",
|
||||
elixirc_paths: elixirc_paths(Mix.env()),
|
||||
compilers: Mix.compilers(),
|
||||
|
|
|
@ -3,14 +3,16 @@ msgstr ""
|
|||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-07-29 11:37+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
"PO-Revision-Date: 2024-04-13 22:55+0000\n"
|
||||
"Last-Translator: fadelkon <fadelkon@posteo.net>\n"
|
||||
"Language-Team: Catalan <http://translate.akkoma.dev/projects/akkoma/"
|
||||
"akkoma-backend-errors/ca/>\n"
|
||||
"Language: ca\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Translate Toolkit 3.7.1\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 4.18.2\n"
|
||||
|
||||
# # This file is a PO Template file.
|
||||
# #
|
||||
|
@ -610,46 +612,46 @@ msgstr "El teu compte espera aprovació."
|
|||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:262
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "File is too large"
|
||||
msgstr ""
|
||||
msgstr "L'arxiu és massa gran"
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:37
|
||||
#: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:48
|
||||
#: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:59
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Hashtag not found"
|
||||
msgstr "Llista no trobada"
|
||||
msgstr "No s'ha trobat l'etiqueta"
|
||||
|
||||
#: lib/pleroma/web/common_api/activity_draft.ex:144
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Invalid language"
|
||||
msgstr ""
|
||||
msgstr "Llengua incompatible"
|
||||
|
||||
#: lib/pleroma/web/o_auth/o_auth_controller.ex:218
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "This action is outside of authorized scopes"
|
||||
msgstr "Aquesta acció és fora dels àmbits autoritzats"
|
||||
|
||||
#: lib/pleroma/web/common_api/activity_draft.ex:129
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "You can only quote public or unlisted statuses"
|
||||
msgstr ""
|
||||
msgstr "Només pots citar publicacions desllistades o públiques"
|
||||
|
||||
#: lib/pleroma/web/common_api/activity_draft.ex:126
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "You can't quote a status that doesn't exist"
|
||||
msgstr ""
|
||||
msgstr "No pots citar una publicació que no existeix"
|
||||
|
||||
#: lib/pleroma/web/embed_controller.ex:35
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Federated posts cannot be embedded"
|
||||
msgstr ""
|
||||
msgstr "No es poden incrustar publicacions federades"
|
||||
|
||||
#: lib/pleroma/web/embed_controller.ex:38
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Not authorized to view this post"
|
||||
msgstr ""
|
||||
msgstr "No tens permís per veure aquesta publicació"
|
||||
|
||||
#: lib/pleroma/web/embed_controller.ex:32
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Post not found"
|
||||
msgstr "Llista no trobada"
|
||||
msgstr "No s'ha trobat la publicació"
|
||||
|
|
|
@ -3,8 +3,8 @@ msgstr ""
|
|||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-07-28 09:15+0000\n"
|
||||
"PO-Revision-Date: 2022-07-30 21:58+0000\n"
|
||||
"Last-Translator: sola <spla@mastodont.cat>\n"
|
||||
"PO-Revision-Date: 2024-04-13 22:55+0000\n"
|
||||
"Last-Translator: fadelkon <fadelkon@posteo.net>\n"
|
||||
"Language-Team: Catalan <http://translate.akkoma.dev/projects/akkoma/"
|
||||
"akkoma-backend-static-pages/ca/>\n"
|
||||
"Language: ca\n"
|
||||
|
@ -12,7 +12,7 @@ msgstr ""
|
|||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 4.13.1\n"
|
||||
"X-Generator: Weblate 4.18.2\n"
|
||||
|
||||
## This file is a PO Template file.
|
||||
##
|
||||
|
@ -531,25 +531,25 @@ msgid "Welcome to %{instance_name}!"
|
|||
msgstr "Benvingut a %{instance_name}!"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:368
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "account archive email body - admin requested"
|
||||
msgid "<p>Admin @%{admin_nickname} requested a full backup of your Akkoma account. It's ready for download:</p>\n<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
|
||||
msgstr ""
|
||||
"<p>L'Administrador @%{admin_nickname} ha sol·licitat una copia de seguretat "
|
||||
"completa del teu compte Akkoma. Està preparat per a descarrega:</p>\n"
|
||||
"<p>L'Administrador @%{admin_nickname} ha sol·licitat una còpia de seguretat "
|
||||
"completa del teu compte Akkoma. Està preparada per descarregar:</p>\n"
|
||||
"<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:356
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "account archive email body - self-requested"
|
||||
msgid "<p>You requested a full backup of your Akkoma account. It's ready for download:</p>\n<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
|
||||
msgstr ""
|
||||
"<p>Has sol·licitat una copia de seguretat completa del teu compte Akkoma. "
|
||||
"Està llest per a descarrega:</p>\n"
|
||||
"<p>Has sol·licitat una còpia de seguretat completa del teu compte Akkoma. Ja "
|
||||
"la pots descarregar:</p>\n"
|
||||
"<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:41
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth register page title"
|
||||
msgid "This is your first visit! Please enter your Akkoma handle."
|
||||
msgstr ""
|
||||
|
@ -560,22 +560,22 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow error message - unknown error"
|
||||
msgid "Something went wrong."
|
||||
msgstr ""
|
||||
msgstr "Hi ha hagut algun problema."
|
||||
|
||||
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:67
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow error message - user not found"
|
||||
msgid "Could not find user"
|
||||
msgstr ""
|
||||
msgstr "No s'ha trobat l'usuari/a/ï"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:8
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "status interact authorization button"
|
||||
msgid "Interact"
|
||||
msgstr ""
|
||||
msgstr "Interacciona"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:2
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "status interact error"
|
||||
msgid "Error: %{error}"
|
||||
msgstr "Error: %{error}"
|
||||
|
@ -584,33 +584,33 @@ msgstr "Error: %{error}"
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "status interact error message - status not found"
|
||||
msgid "Could not find status"
|
||||
msgstr ""
|
||||
msgstr "No s'ha trobat l'estat"
|
||||
|
||||
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:144
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "status interact error message - unknown error"
|
||||
msgid "Something went wrong."
|
||||
msgstr ""
|
||||
msgstr "Hi ha hagut algun problema."
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:4
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "status interact header"
|
||||
msgid "Interacting with %{nickname}'s %{status_link}"
|
||||
msgstr ""
|
||||
msgstr "S'està interactuant amb %{status_link} de %{nickname}"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:4
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "status interact header - status link text"
|
||||
msgid "status"
|
||||
msgstr ""
|
||||
msgstr "estat"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:119
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "user invitation email body"
|
||||
msgid "<h3>You are invited to %{instance_name}</h3>\n<p>%{inviter_name} invites you to join %{instance_name}, an instance of Akkoma federated social networking platform.</p>\n<p>Click the following link to register: <a href=\"%{registration_url}\">accept invitation</a>.</p>\n"
|
||||
msgstr ""
|
||||
"<h3>Has estat invitat a %{instance_name}</h3>\n"
|
||||
"<p>%{inviter_name} t'invita a unir-te a %{instance_name}, una instància de "
|
||||
"la plataforma de xarxa social federada Akkoma.</p>\n"
|
||||
"<h3>T'han convidat a %{instance_name}</h3>\n"
|
||||
"<p>%{inviter_name} t'anima a unir-te a %{instance_name}, una instància de la "
|
||||
"plataforma de xarxa social federada Akkoma.</p>\n"
|
||||
"<p>Clica el següent enllaç per a registrar-te: <a href=\"%{registration_url}"
|
||||
"\">accepta invitació</a>.</p>\n"
|
||||
"\">accepta la invitació</a>.</p>\n"
|
||||
|
|
6449
priv/gettext/pt/LC_MESSAGES/config_descriptions.po
Normal file
6449
priv/gettext/pt/LC_MESSAGES/config_descriptions.po
Normal file
File diff suppressed because it is too large
Load diff
657
priv/gettext/pt/LC_MESSAGES/errors.po
Normal file
657
priv/gettext/pt/LC_MESSAGES/errors.po
Normal file
|
@ -0,0 +1,657 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-03-18 12:55+0000\n"
|
||||
"PO-Revision-Date: 2024-03-19 01:10+0000\n"
|
||||
"Last-Translator: Jammer Lammer <akHarINlMYExpSmVPDRT@proton.me>\n"
|
||||
"Language-Team: Portuguese <http://translate.akkoma.dev/projects/akkoma/"
|
||||
"akkoma-backend-errors/pt/>\n"
|
||||
"Language: pt\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||
"X-Generator: Weblate 4.18.2\n"
|
||||
|
||||
## This file is a PO Template file.
|
||||
##
|
||||
## `msgid`s here are often extracted from source code.
|
||||
## Add new translations manually only if they're dynamic
|
||||
## translations that can't be statically extracted.
|
||||
##
|
||||
## Run `mix gettext.extract` to bring this file up to
|
||||
## date. Leave `msgstr`s empty as changing them here as no
|
||||
## effect: edit them in PO (`.po`) files instead.
|
||||
## From Ecto.Changeset.cast/4
|
||||
msgid "can't be blank"
|
||||
msgstr "não pode estar em branco"
|
||||
|
||||
## From Ecto.Changeset.unique_constraint/3
|
||||
msgid "has already been taken"
|
||||
msgstr "já está em uso"
|
||||
|
||||
## From Ecto.Changeset.put_change/3
|
||||
msgid "is invalid"
|
||||
msgstr "é inválido"
|
||||
|
||||
## From Ecto.Changeset.validate_format/3
|
||||
msgid "has invalid format"
|
||||
msgstr "tem um formato inválido"
|
||||
|
||||
## From Ecto.Changeset.validate_subset/3
|
||||
msgid "has an invalid entry"
|
||||
msgstr "tem uma entrada inválida"
|
||||
|
||||
## From Ecto.Changeset.validate_exclusion/3
|
||||
msgid "is reserved"
|
||||
msgstr "está reservado"
|
||||
|
||||
## From Ecto.Changeset.validate_confirmation/3
|
||||
msgid "does not match confirmation"
|
||||
msgstr "a confirmação não coincide"
|
||||
|
||||
## From Ecto.Changeset.no_assoc_constraint/3
|
||||
msgid "is still associated with this entry"
|
||||
msgstr "ainda está associado com essa entrada"
|
||||
|
||||
msgid "are still associated with this entry"
|
||||
msgstr "ainda estão associados com essa entrada"
|
||||
|
||||
## From Ecto.Changeset.validate_length/3
|
||||
msgid "should be %{count} character(s)"
|
||||
msgid_plural "should be %{count} character(s)"
|
||||
msgstr[0] "deve ser %{count} caractere"
|
||||
msgstr[1] "deve ser %{count} caracteres"
|
||||
|
||||
msgid "should have %{count} item(s)"
|
||||
msgid_plural "should have %{count} item(s)"
|
||||
msgstr[0] "deve ter %{count} item"
|
||||
msgstr[1] "deve ter %{count} itens"
|
||||
|
||||
msgid "should be at least %{count} character(s)"
|
||||
msgid_plural "should be at least %{count} character(s)"
|
||||
msgstr[0] "deve ter pelo menos %{count} caractere"
|
||||
msgstr[1] "deve ter pelo menos %{count} caracteres"
|
||||
|
||||
msgid "should have at least %{count} item(s)"
|
||||
msgid_plural "should have at least %{count} item(s)"
|
||||
msgstr[0] "deve ter pelo menos %{count} item"
|
||||
msgstr[1] "deve ter pelo menos %{count} itens"
|
||||
|
||||
msgid "should be at most %{count} character(s)"
|
||||
msgid_plural "should be at most %{count} character(s)"
|
||||
msgstr[0] "deve ter no máximo %{count} caractere"
|
||||
msgstr[1] "deve ter no máximo %{count} caracteres"
|
||||
|
||||
msgid "should have at most %{count} item(s)"
|
||||
msgid_plural "should have at most %{count} item(s)"
|
||||
msgstr[0] "deve ter no máximo %{count} item"
|
||||
msgstr[1] "deve ter no máximo %{count} itens"
|
||||
|
||||
## From Ecto.Changeset.validate_number/3
|
||||
msgid "must be less than %{number}"
|
||||
msgstr "deve ser menor que %{number}"
|
||||
|
||||
msgid "must be greater than %{number}"
|
||||
msgstr "deve ser maior que %{number}"
|
||||
|
||||
msgid "must be less than or equal to %{number}"
|
||||
msgstr "deve ser menor ou igual a %{number}"
|
||||
|
||||
msgid "must be greater than or equal to %{number}"
|
||||
msgstr "deve ser maior ou igual a %{number}"
|
||||
|
||||
msgid "must be equal to %{number}"
|
||||
msgstr "deve ser igual a %{number}"
|
||||
|
||||
#: lib/pleroma/web/common_api.ex:503
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Account not found"
|
||||
msgstr "Conta não encontrada"
|
||||
|
||||
#: lib/pleroma/web/common_api.ex:263
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Already voted"
|
||||
msgstr "Já votado"
|
||||
|
||||
#: lib/pleroma/web/o_auth/o_auth_controller.ex:427
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Bad request"
|
||||
msgstr "Má requisição"
|
||||
|
||||
#: lib/pleroma/web/controller_helper.ex:105
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Can't display this activity"
|
||||
msgstr "Não é possível mostrar essa atividade"
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:335
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Can't find user"
|
||||
msgstr "Não é possível encontrar o usuário"
|
||||
|
||||
#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:61
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Can't get favorites"
|
||||
msgstr "Não é possível obter os favoritos"
|
||||
|
||||
#: lib/pleroma/web/common_api/utils.ex:480
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Cannot post an empty status without attachments"
|
||||
msgstr "Não é possível publicar um status vazio sem anexos"
|
||||
|
||||
#: lib/pleroma/web/common_api/utils.ex:468
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Comment must be up to %{max_size} characters"
|
||||
msgstr "Comentários devem ter no máximo %{max_size} caracteres"
|
||||
|
||||
#: lib/pleroma/config_db.ex:199
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Config with params %{params} not found"
|
||||
msgstr "Configuração com parâmetros %{params} não encontrada"
|
||||
|
||||
#: lib/pleroma/web/common_api.ex:114
|
||||
#: lib/pleroma/web/common_api.ex:118
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Could not delete"
|
||||
msgstr "Não foi possível apagar"
|
||||
|
||||
#: lib/pleroma/web/common_api.ex:164
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Could not favorite"
|
||||
msgstr "Não foi possível favoritar"
|
||||
|
||||
#: lib/pleroma/web/common_api.ex:201
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Could not unfavorite"
|
||||
msgstr "Não foi possível eliminar favorito"
|
||||
|
||||
#: lib/pleroma/web/common_api.ex:149
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Could not unrepeat"
|
||||
msgstr "Não foi possível republicar"
|
||||
|
||||
#: lib/pleroma/web/common_api.ex:510
|
||||
#: lib/pleroma/web/common_api.ex:519
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Could not update state"
|
||||
msgstr "Não foi possível atualizar estado"
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:278
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Error."
|
||||
msgstr "Erro."
|
||||
|
||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:104
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Invalid CAPTCHA"
|
||||
msgstr "CAPTCHA inválido"
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:143
|
||||
#: lib/pleroma/web/o_auth/o_auth_controller.ex:660
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Invalid credentials"
|
||||
msgstr "Credenciais inválidas"
|
||||
|
||||
#: lib/pleroma/web/plugs/ensure_authenticated_plug.ex:42
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Invalid credentials."
|
||||
msgstr "Credenciais inválidas."
|
||||
|
||||
#: lib/pleroma/web/common_api.ex:284
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Invalid indices"
|
||||
msgstr "Índices inválidos"
|
||||
|
||||
#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:29
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Invalid parameters"
|
||||
msgstr "Parâmetros inválidos"
|
||||
|
||||
#: lib/pleroma/web/common_api/utils.ex:376
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Invalid password."
|
||||
msgstr "Senha inválida."
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:265
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Invalid request"
|
||||
msgstr "Requisição inválida"
|
||||
|
||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:107
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Kocaptcha service unavailable"
|
||||
msgstr "Serviço de Kocaptcha indisponível"
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:139
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Missing parameters"
|
||||
msgstr "Parâmetros faltando"
|
||||
|
||||
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:151
|
||||
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:177
|
||||
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:219
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "No such permission_group"
|
||||
msgstr "Não a tal permission_group"
|
||||
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:480
|
||||
#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:11
|
||||
#: lib/pleroma/web/feed/tag_controller.ex:16
|
||||
#: lib/pleroma/web/feed/user_controller.ex:70
|
||||
#: lib/pleroma/web/o_status/o_status_controller.ex:135
|
||||
#: lib/pleroma/web/plugs/uploaded_media.ex:83
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Not found"
|
||||
msgstr "Não encontrado"
|
||||
|
||||
#: lib/pleroma/web/common_api.ex:255
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Poll's author can't vote"
|
||||
msgstr "Autor da enquete não pode votar"
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:478
|
||||
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20
|
||||
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:39
|
||||
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:51
|
||||
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:52
|
||||
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Record not found"
|
||||
msgstr "Registro não encontrado"
|
||||
|
||||
#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:35
|
||||
#: lib/pleroma/web/feed/user_controller.ex:79
|
||||
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:42
|
||||
#: lib/pleroma/web/o_status/o_status_controller.ex:141
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Something went wrong"
|
||||
msgstr "Algo deu errado"
|
||||
|
||||
#: lib/pleroma/web/common_api/activity_draft.ex:156
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "The message visibility must be direct"
|
||||
msgstr "A visibilidade da mensagem deve ser direta"
|
||||
|
||||
#: lib/pleroma/web/common_api/utils.ex:490
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "The status is over the character limit"
|
||||
msgstr "O status está acima do limite de caracteres"
|
||||
|
||||
#: lib/pleroma/web/plugs/ensure_public_or_authenticated_plug.ex:36
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "This resource requires authentication."
|
||||
msgstr "Esse recurso requer autenticação."
|
||||
|
||||
#: lib/pleroma/web/plugs/rate_limiter.ex:214
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Throttled"
|
||||
msgstr "Limitado"
|
||||
|
||||
#: lib/pleroma/web/common_api.ex:285
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Too many choices"
|
||||
msgstr "Muitas opções"
|
||||
|
||||
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:248
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "You can't revoke your own admin status."
|
||||
msgstr "Você não pode revogar seu próprio status de administrador."
|
||||
|
||||
#: lib/pleroma/web/o_auth/o_auth_controller.ex:267
|
||||
#: lib/pleroma/web/o_auth/o_auth_controller.ex:358
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Your account is currently disabled"
|
||||
msgstr "Sua conta está atualmente desativada"
|
||||
|
||||
#: lib/pleroma/web/o_auth/o_auth_controller.ex:229
|
||||
#: lib/pleroma/web/o_auth/o_auth_controller.ex:381
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Your login is missing a confirmed e-mail address"
|
||||
msgstr "Sua conta não possui uma endereço de email confirmado"
|
||||
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:368
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "can't read inbox of %{nickname} as %{as_nickname}"
|
||||
msgstr "não é possível ler o inbox de %{nickname} como %{as_nickname}"
|
||||
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:467
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "can't update outbox of %{nickname} as %{as_nickname}"
|
||||
msgstr "não é possível atualizar o inbox de %{nickname} como %{nickname}"
|
||||
|
||||
#: lib/pleroma/web/common_api.ex:455
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "conversation is already muted"
|
||||
msgstr "a conversa já está silenciada"
|
||||
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:486
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "error"
|
||||
msgstr "erro"
|
||||
|
||||
#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:34
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "mascots can only be images"
|
||||
msgstr "mascotes só podem ser imagens"
|
||||
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:62
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "not found"
|
||||
msgstr "não encontrado"
|
||||
|
||||
#: lib/pleroma/web/o_auth/o_auth_controller.ex:462
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Bad OAuth request."
|
||||
msgstr "Requisição de OAuth inválida."
|
||||
|
||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:113
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "CAPTCHA already used"
|
||||
msgstr "CAPTCHA já está em uso"
|
||||
|
||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:110
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "CAPTCHA expired"
|
||||
msgstr "CAPTCHA expirado"
|
||||
|
||||
#: lib/pleroma/web/plugs/uploaded_media.ex:56
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Failed"
|
||||
msgstr "Falhou"
|
||||
|
||||
#: lib/pleroma/web/o_auth/o_auth_controller.ex:478
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Failed to authenticate: %{message}."
|
||||
msgstr "Falha ao autenticar: %{message}."
|
||||
|
||||
#: lib/pleroma/web/o_auth/o_auth_controller.ex:509
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Failed to set up user account."
|
||||
msgstr "Falha ao definir conta de usuário."
|
||||
|
||||
#: lib/pleroma/web/plugs/o_auth_scopes_plug.ex:37
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Insufficient permissions: %{permissions}."
|
||||
msgstr "Permissões insuficientes: %{permissions}."
|
||||
|
||||
#: lib/pleroma/web/plugs/uploaded_media.ex:98
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Internal Error"
|
||||
msgstr "Erro Interno"
|
||||
|
||||
#: lib/pleroma/web/o_auth/fallback_controller.ex:22
|
||||
#: lib/pleroma/web/o_auth/fallback_controller.ex:29
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Invalid Username/Password"
|
||||
msgstr "Usuário/Senha Inválidos"
|
||||
|
||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:116
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Invalid answer data"
|
||||
msgstr "dado de resposta inválido"
|
||||
|
||||
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:40
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Nodeinfo schema version not handled"
|
||||
msgstr "Esquema de versão do Nodeinfo não tratado"
|
||||
|
||||
#: lib/pleroma/web/o_auth/fallback_controller.ex:14
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Unknown error, please check the details and try again."
|
||||
msgstr "Erro desconhecido. Por favor, cheque os detalhes e tente novamente."
|
||||
|
||||
#: lib/pleroma/web/o_auth/o_auth_controller.ex:158
|
||||
#: lib/pleroma/web/o_auth/o_auth_controller.ex:204
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Unlisted redirect_uri."
|
||||
msgstr "redirect_uri Não listada."
|
||||
|
||||
#: lib/pleroma/web/o_auth/o_auth_controller.ex:458
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Unsupported OAuth provider: %{provider}."
|
||||
msgstr "Provedor de OAuth não suportado: %{provider}."
|
||||
|
||||
#: lib/pleroma/uploaders/uploader.ex:74
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Uploader callback timeout"
|
||||
msgstr "Tempo esgotado para callback do uploader"
|
||||
|
||||
#: lib/pleroma/web/uploader_controller.ex:23
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "bad request"
|
||||
msgstr "má requisição"
|
||||
|
||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:101
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "CAPTCHA Error"
|
||||
msgstr "Erro no CAPTCHA"
|
||||
|
||||
#: lib/pleroma/web/common_api.ex:213
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Could not add reaction emoji"
|
||||
msgstr "Não foi possível adicionar emoji de reação"
|
||||
|
||||
#: lib/pleroma/web/common_api.ex:224
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Could not remove reaction emoji"
|
||||
msgstr "Não foi possível remover emoji de reação"
|
||||
|
||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:127
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Invalid CAPTCHA (Missing parameter: %{name})"
|
||||
msgstr "CAPTCHA inválido (Parâmetro faltando: %{name})"
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:96
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "List not found"
|
||||
msgstr "Lista não encontrada"
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:150
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Missing parameter: %{name}"
|
||||
msgstr "Parâmetro faltando: %{name}"
|
||||
|
||||
#: lib/pleroma/web/o_auth/o_auth_controller.ex:256
|
||||
#: lib/pleroma/web/o_auth/o_auth_controller.ex:371
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Password reset is required"
|
||||
msgstr "Redefinição de senha é necessária"
|
||||
|
||||
#: lib/pleroma/tests/auth_test_controller.ex:9
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6
|
||||
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6
|
||||
#: lib/pleroma/web/admin_api/controllers/announcement_controller.ex:6
|
||||
#: lib/pleroma/web/admin_api/controllers/config_controller.ex:6
|
||||
#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:6
|
||||
#: lib/pleroma/web/admin_api/controllers/frontend_controller.ex:6
|
||||
#: lib/pleroma/web/admin_api/controllers/instance_controller.ex:6
|
||||
#: lib/pleroma/web/admin_api/controllers/instance_document_controller.ex:6
|
||||
#: lib/pleroma/web/admin_api/controllers/invite_controller.ex:6
|
||||
#: lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex:6
|
||||
#: lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex:6
|
||||
#: lib/pleroma/web/admin_api/controllers/relay_controller.ex:6
|
||||
#: lib/pleroma/web/admin_api/controllers/report_controller.ex:6
|
||||
#: lib/pleroma/web/admin_api/controllers/status_controller.ex:6
|
||||
#: lib/pleroma/web/admin_api/controllers/user_controller.ex:6
|
||||
#: lib/pleroma/web/akkoma_api/controllers/frontend_settings_controller.ex:2
|
||||
#: lib/pleroma/web/akkoma_api/controllers/frontend_switcher.ex:2
|
||||
#: lib/pleroma/web/akkoma_api/controllers/metrics_controller.ex:2
|
||||
#: lib/pleroma/web/akkoma_api/controllers/translation_controller.ex:2
|
||||
#: lib/pleroma/web/controller_helper.ex:6
|
||||
#: lib/pleroma/web/embed_controller.ex:6
|
||||
#: lib/pleroma/web/fallback/redirect_controller.ex:6
|
||||
#: lib/pleroma/web/feed/tag_controller.ex:6
|
||||
#: lib/pleroma/web/feed/user_controller.ex:6
|
||||
#: lib/pleroma/web/mailer/subscription_controller.ex:6
|
||||
#: lib/pleroma/web/manifest_controller.ex:6
|
||||
#: lib/pleroma/web/masto_fe_controller.ex:6
|
||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:6
|
||||
#: lib/pleroma/web/mastodon_api/controllers/announcement_controller.ex:6
|
||||
#: lib/pleroma/web/mastodon_api/controllers/app_controller.ex:11
|
||||
#: lib/pleroma/web/mastodon_api/controllers/auth_controller.ex:6
|
||||
#: lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex:6
|
||||
#: lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex:6
|
||||
#: lib/pleroma/web/mastodon_api/controllers/directory_controller.ex:6
|
||||
#: lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex:6
|
||||
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:6
|
||||
#: lib/pleroma/web/mastodon_api/controllers/filter_controller.ex:6
|
||||
#: lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex:6
|
||||
#: lib/pleroma/web/mastodon_api/controllers/instance_controller.ex:6
|
||||
#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:6
|
||||
#: lib/pleroma/web/mastodon_api/controllers/marker_controller.ex:6
|
||||
#: lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex:14
|
||||
#: lib/pleroma/web/mastodon_api/controllers/media_controller.ex:6
|
||||
#: lib/pleroma/web/mastodon_api/controllers/notification_controller.ex:6
|
||||
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:6
|
||||
#: lib/pleroma/web/mastodon_api/controllers/report_controller.ex:6
|
||||
#: lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex:6
|
||||
#: lib/pleroma/web/mastodon_api/controllers/search_controller.ex:6
|
||||
#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:6
|
||||
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:7
|
||||
#: lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex:6
|
||||
#: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:3
|
||||
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:6
|
||||
#: lib/pleroma/web/media_proxy/media_proxy_controller.ex:6
|
||||
#: lib/pleroma/web/mongoose_im/mongoose_im_controller.ex:6
|
||||
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:6
|
||||
#: lib/pleroma/web/o_auth/fallback_controller.ex:6
|
||||
#: lib/pleroma/web/o_auth/mfa_controller.ex:10
|
||||
#: lib/pleroma/web/o_auth/o_auth_controller.ex:6
|
||||
#: lib/pleroma/web/o_status/o_status_controller.ex:6
|
||||
#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:6
|
||||
#: lib/pleroma/web/pleroma_api/controllers/app_controller.ex:6
|
||||
#: lib/pleroma/web/pleroma_api/controllers/backup_controller.ex:6
|
||||
#: lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex:6
|
||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex:6
|
||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:6
|
||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex:6
|
||||
#: lib/pleroma/web/pleroma_api/controllers/instances_controller.ex:6
|
||||
#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:6
|
||||
#: lib/pleroma/web/pleroma_api/controllers/notification_controller.ex:6
|
||||
#: lib/pleroma/web/pleroma_api/controllers/report_controller.ex:6
|
||||
#: lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex:7
|
||||
#: lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex:6
|
||||
#: lib/pleroma/web/static_fe/static_fe_controller.ex:6
|
||||
#: lib/pleroma/web/twitter_api/controller.ex:6
|
||||
#: lib/pleroma/web/twitter_api/controllers/password_controller.ex:10
|
||||
#: lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex:6
|
||||
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:6
|
||||
#: lib/pleroma/web/uploader_controller.ex:6
|
||||
#: lib/pleroma/web/web_finger/web_finger_controller.ex:6
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
|
||||
msgstr ""
|
||||
"Violação de segurança: Verificação do escopo do OAuth tanto não lidou quanto "
|
||||
"não explicitamente pulou."
|
||||
|
||||
#: lib/pleroma/web/plugs/ensure_authenticated_plug.ex:32
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Two-factor authentication enabled, you must use a access token."
|
||||
msgstr ""
|
||||
"Autenticação de dois-fatores ativada, você deve usar um token de acesso."
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Web push subscription is disabled on this Pleroma instance"
|
||||
msgstr "Inscrição de web push está desativada nessa instância do Akkoma"
|
||||
|
||||
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:214
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "You can't revoke your own admin/moderator status."
|
||||
msgstr "Você não pode revogar o seu próprio status de admin/moderador."
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:202
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "authorization required for timeline view"
|
||||
msgstr "autorização necessária para visualização da linha do tempo"
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Access denied"
|
||||
msgstr "Acesso negado"
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:332
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "This API requires an authenticated user"
|
||||
msgstr "Essa API necessita de um usuário autentica"
|
||||
|
||||
#: lib/pleroma/web/plugs/ensure_staff_privileged_plug.ex:26
|
||||
#: lib/pleroma/web/plugs/user_is_admin_plug.ex:21
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "User is not an admin."
|
||||
msgstr "Usuário não é um administrador."
|
||||
|
||||
#: lib/pleroma/user/backup.ex:73
|
||||
#, elixir-format
|
||||
msgid "Last export was less than a day ago"
|
||||
msgid_plural "Last export was less than %{days} days ago"
|
||||
msgstr[0] "Última exportação foi a menos de um dia atrás"
|
||||
msgstr[1] "Última exportação foi a menos de %{days} atrás"
|
||||
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:399
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Character limit (%{limit} characters) exceeded, contains %{length} characters"
|
||||
msgstr ""
|
||||
"Limite de caracteres (%{limit} caracteres) excedido, pois contém %{length} "
|
||||
"caracteres"
|
||||
|
||||
#: lib/pleroma/web/plugs/ensure_staff_privileged_plug.ex:33
|
||||
#: lib/pleroma/web/plugs/user_is_staff_plug.ex:20
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "User is not a staff member."
|
||||
msgstr "Usuário não é um membro da staff."
|
||||
|
||||
#: lib/pleroma/web/o_auth/o_auth_controller.ex:391
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Your account is awaiting approval."
|
||||
msgstr "Sua conta aguarda aprovação."
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:256
|
||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:259
|
||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:262
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "File is too large"
|
||||
msgstr "Arquivo muito grande"
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:37
|
||||
#: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:48
|
||||
#: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:59
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Hashtag not found"
|
||||
msgstr "Hashtag não encontrada"
|
||||
|
||||
#: lib/pleroma/web/common_api/activity_draft.ex:144
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Invalid language"
|
||||
msgstr "Idioma inválido"
|
||||
|
||||
#: lib/pleroma/web/o_auth/o_auth_controller.ex:218
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "This action is outside of authorized scopes"
|
||||
msgstr "Essa ação está fora do escopo autorizado"
|
||||
|
||||
#: lib/pleroma/web/common_api/activity_draft.ex:129
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "You can only quote public or unlisted statuses"
|
||||
msgstr "Você pode apenas citar status públicos ou não-listados"
|
||||
|
||||
#: lib/pleroma/web/common_api/activity_draft.ex:126
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "You can't quote a status that doesn't exist"
|
||||
msgstr "Você não pode citar um status que não existe"
|
||||
|
||||
#: lib/pleroma/web/embed_controller.ex:35
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Federated posts cannot be embedded"
|
||||
msgstr "Publicações federadas não podem ser embutidas"
|
||||
|
||||
#: lib/pleroma/web/embed_controller.ex:38
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Not authorized to view this post"
|
||||
msgstr "Não autorizado a ver essa publicação"
|
||||
|
||||
#: lib/pleroma/web/embed_controller.ex:32
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Post not found"
|
||||
msgstr "Publicação não encontrada"
|
163
priv/gettext/pt/LC_MESSAGES/posix_errors.po
Normal file
163
priv/gettext/pt/LC_MESSAGES/posix_errors.po
Normal file
|
@ -0,0 +1,163 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-03-17 22:50+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: pt\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Translate Toolkit 3.9.2\n"
|
||||
|
||||
## This file is a PO Template file.
|
||||
##
|
||||
## `msgid`s here are often extracted from source code.
|
||||
## Add new translations manually only if they're dynamic
|
||||
## translations that can't be statically extracted.
|
||||
##
|
||||
## Run `mix gettext.extract` to bring this file up to
|
||||
## date. Leave `msgstr`s empty as changing them here as no
|
||||
## effect: edit them in PO (`.po`) files instead.
|
||||
msgid "eperm"
|
||||
msgstr ""
|
||||
|
||||
msgid "eacces"
|
||||
msgstr ""
|
||||
|
||||
msgid "eagain"
|
||||
msgstr ""
|
||||
|
||||
msgid "ebadf"
|
||||
msgstr ""
|
||||
|
||||
msgid "ebadmsg"
|
||||
msgstr ""
|
||||
|
||||
msgid "ebusy"
|
||||
msgstr ""
|
||||
|
||||
msgid "edeadlk"
|
||||
msgstr ""
|
||||
|
||||
msgid "edeadlock"
|
||||
msgstr ""
|
||||
|
||||
msgid "edquot"
|
||||
msgstr ""
|
||||
|
||||
msgid "eexist"
|
||||
msgstr ""
|
||||
|
||||
msgid "efault"
|
||||
msgstr ""
|
||||
|
||||
msgid "efbig"
|
||||
msgstr ""
|
||||
|
||||
msgid "eftype"
|
||||
msgstr ""
|
||||
|
||||
msgid "eintr"
|
||||
msgstr ""
|
||||
|
||||
msgid "einval"
|
||||
msgstr ""
|
||||
|
||||
msgid "eio"
|
||||
msgstr ""
|
||||
|
||||
msgid "eisdir"
|
||||
msgstr ""
|
||||
|
||||
msgid "eloop"
|
||||
msgstr ""
|
||||
|
||||
msgid "emfile"
|
||||
msgstr ""
|
||||
|
||||
msgid "emlink"
|
||||
msgstr ""
|
||||
|
||||
msgid "emultihop"
|
||||
msgstr ""
|
||||
|
||||
msgid "enametoolong"
|
||||
msgstr ""
|
||||
|
||||
msgid "enfile"
|
||||
msgstr ""
|
||||
|
||||
msgid "enobufs"
|
||||
msgstr ""
|
||||
|
||||
msgid "enodev"
|
||||
msgstr ""
|
||||
|
||||
msgid "enolck"
|
||||
msgstr ""
|
||||
|
||||
msgid "enolink"
|
||||
msgstr ""
|
||||
|
||||
msgid "enoent"
|
||||
msgstr ""
|
||||
|
||||
msgid "enomem"
|
||||
msgstr ""
|
||||
|
||||
msgid "enospc"
|
||||
msgstr ""
|
||||
|
||||
msgid "enosr"
|
||||
msgstr ""
|
||||
|
||||
msgid "enostr"
|
||||
msgstr ""
|
||||
|
||||
msgid "enosys"
|
||||
msgstr ""
|
||||
|
||||
msgid "enotblk"
|
||||
msgstr ""
|
||||
|
||||
msgid "enotdir"
|
||||
msgstr ""
|
||||
|
||||
msgid "enotsup"
|
||||
msgstr ""
|
||||
|
||||
msgid "enxio"
|
||||
msgstr ""
|
||||
|
||||
msgid "eopnotsupp"
|
||||
msgstr ""
|
||||
|
||||
msgid "eoverflow"
|
||||
msgstr ""
|
||||
|
||||
msgid "epipe"
|
||||
msgstr ""
|
||||
|
||||
msgid "erange"
|
||||
msgstr ""
|
||||
|
||||
msgid "erofs"
|
||||
msgstr ""
|
||||
|
||||
msgid "espipe"
|
||||
msgstr ""
|
||||
|
||||
msgid "esrch"
|
||||
msgstr ""
|
||||
|
||||
msgid "estale"
|
||||
msgstr ""
|
||||
|
||||
msgid "etxtbsy"
|
||||
msgstr ""
|
||||
|
||||
msgid "exdev"
|
||||
msgstr ""
|
616
priv/gettext/pt/LC_MESSAGES/static_pages.po
Normal file
616
priv/gettext/pt/LC_MESSAGES/static_pages.po
Normal file
|
@ -0,0 +1,616 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-03-17 22:51+0000\n"
|
||||
"PO-Revision-Date: 2024-03-19 01:10+0000\n"
|
||||
"Last-Translator: Jammer Lammer <akHarINlMYExpSmVPDRT@proton.me>\n"
|
||||
"Language-Team: Portuguese <http://translate.akkoma.dev/projects/akkoma/"
|
||||
"akkoma-backend-static-pages/pt/>\n"
|
||||
"Language: pt\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||
"X-Generator: Weblate 4.18.2\n"
|
||||
|
||||
## This file is a PO Template file.
|
||||
##
|
||||
## "msgid"s here are often extracted from source code.
|
||||
## Add new translations manually only if they're dynamic
|
||||
## translations that can't be statically extracted.
|
||||
##
|
||||
## Run "mix gettext.extract" to bring this file up to
|
||||
## date. Leave "msgstr"s empty as changing them here as no
|
||||
## effect: edit them in PO (.po) files instead.
|
||||
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:9
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow authorization button"
|
||||
msgid "Authorize"
|
||||
msgstr "Autorizar"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:2
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow error"
|
||||
msgid "Error fetching user"
|
||||
msgstr "Erro ao buscar usuário"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:4
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow header"
|
||||
msgid "Remote follow"
|
||||
msgstr "Seguimento remoto"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:8
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "placeholder text for auth code entry"
|
||||
msgid "Authentication code"
|
||||
msgstr "Código de autenticação"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:10
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "placeholder text for password entry"
|
||||
msgid "Password"
|
||||
msgstr "Senha"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:8
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "placeholder text for username entry"
|
||||
msgid "Username"
|
||||
msgstr "Nome de usuário"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:13
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow authorization button for login"
|
||||
msgid "Authorize"
|
||||
msgstr "Autorizar"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:12
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow authorization button for mfa"
|
||||
msgid "Authorize"
|
||||
msgstr "Autorizar"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex:2
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow error"
|
||||
msgid "Error following account"
|
||||
msgstr "Erro ao seguir conta"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:4
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow header, need login"
|
||||
msgid "Log in to follow"
|
||||
msgstr "Inicia a sessão para seguir"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:4
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow mfa header"
|
||||
msgid "Two-factor authentication"
|
||||
msgstr "Autenticação de dois-fatores"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex:4
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow success"
|
||||
msgid "Account followed!"
|
||||
msgstr "Conta seguida!"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:7
|
||||
#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:7
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "placeholder text for account id"
|
||||
msgid "Your account ID, e.g. lain@quitter.se"
|
||||
msgstr "Sua ID de conta, ex. lain@quitter.se"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:8
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow authorization button for following with a remote account"
|
||||
msgid "Follow"
|
||||
msgstr "Seguir"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:2
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow error"
|
||||
msgid "Error: %{error}"
|
||||
msgstr "Erro: %{error}"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:4
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow header"
|
||||
msgid "Remotely follow %{nickname}"
|
||||
msgstr "Seguir remotamente %{nickname}"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:12
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "password reset button"
|
||||
msgid "Reset"
|
||||
msgstr "Resetar"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex:4
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "password reset failed homepage link"
|
||||
msgid "Homepage"
|
||||
msgstr "Página inicial"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex:1
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "password reset failed message"
|
||||
msgid "Password reset failed"
|
||||
msgstr "Falha ao redefinir senha"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:8
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "password reset form confirm password prompt"
|
||||
msgid "Confirmation"
|
||||
msgstr "Confirmação"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:4
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "password reset form password prompt"
|
||||
msgid "Password"
|
||||
msgstr "Senha"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex:1
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "password reset invalid token message"
|
||||
msgid "Invalid Token"
|
||||
msgstr "Token Inválido"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex:2
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "password reset successful homepage link"
|
||||
msgid "Homepage"
|
||||
msgstr "Página inicial"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex:1
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "password reset successful message"
|
||||
msgid "Password changed!"
|
||||
msgstr "Senha alterada!"
|
||||
|
||||
#: lib/pleroma/web/templates/feed/feed/tag.atom.eex:15
|
||||
#: lib/pleroma/web/templates/feed/feed/tag.rss.eex:7
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "tag feed description"
|
||||
msgid "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse."
|
||||
msgstr ""
|
||||
"Estas são notas públicas marcadas com #%{tag}. Você pode interagir com elas "
|
||||
"se você tem uma conta em qualquer lugar no fediverse."
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex:3
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth authorization exists page title"
|
||||
msgid "Authorization exists"
|
||||
msgstr "Existe autorização"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:37
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth authorize approve button"
|
||||
msgid "Approve"
|
||||
msgstr "Aprovar"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:35
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth authorize cancel button"
|
||||
msgid "Cancel"
|
||||
msgstr "Cancelar"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:26
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth authorize message"
|
||||
msgid "Application <strong>%{client_name}</strong> is requesting access to your account."
|
||||
msgstr ""
|
||||
"A aplicação <strong>%{client_name}</strong> está requisitando acesso à sua "
|
||||
"conta."
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex:3
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth authorized page title"
|
||||
msgid "Successfully authorized"
|
||||
msgstr "Autorização feita com sucesso"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex:1
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth external provider page title"
|
||||
msgid "Sign in with external provider"
|
||||
msgstr "Iniciar sessão com provedor externo"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex:13
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth external provider sign in button"
|
||||
msgid "Sign in with %{strategy}"
|
||||
msgstr "Iniciar sessão com %{strategy}"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:59
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth login button"
|
||||
msgid "Log In"
|
||||
msgstr "Iniciar sessão"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:56
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth login password prompt"
|
||||
msgid "Password"
|
||||
msgstr "Senha"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:52
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth login username prompt"
|
||||
msgid "Username"
|
||||
msgstr "Usuário"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:44
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth register nickname prompt"
|
||||
msgid "Pleroma Handle"
|
||||
msgstr "Usuário Pleroma"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:42
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth register nickname unchangeable warning"
|
||||
msgid "Choose carefully! You won't be able to change this later. You will be able to change your display name, though."
|
||||
msgstr ""
|
||||
"Escolha com cautela! Você não será capaz de mudar isso depois. No entanto, "
|
||||
"você será capaz de mudar o seu nome à mostra."
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:18
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth register page email prompt"
|
||||
msgid "Email"
|
||||
msgstr "Email"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:10
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth register page fill form prompt"
|
||||
msgid "If you'd like to register a new account, please provide the details below."
|
||||
msgstr ""
|
||||
"Se você deseja registrar uma nova conta, por favor, provenha os detalhes "
|
||||
"abaixo."
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:35
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth register page login button"
|
||||
msgid "Proceed as existing user"
|
||||
msgstr "Proceder como um usuário já existente"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:31
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth register page login password prompt"
|
||||
msgid "Password"
|
||||
msgstr "Senha"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:24
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth register page login prompt"
|
||||
msgid "Alternatively, sign in to connect to existing account."
|
||||
msgstr "Alternativamente, inicia uma sessão em uma conta existente."
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:27
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth register page login username prompt"
|
||||
msgid "Name or email"
|
||||
msgstr "Nome ou email"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:14
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth register page nickname prompt"
|
||||
msgid "Nickname"
|
||||
msgstr "Apelido"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:22
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth register page register button"
|
||||
msgid "Proceed as new user"
|
||||
msgstr "Proceder como um novo usuário"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:8
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth register page title"
|
||||
msgid "Registration Details"
|
||||
msgstr "Detalhes do registro"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex:2
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth scopes message"
|
||||
msgid "The following permissions will be granted"
|
||||
msgstr "As seguintes permissões serão garantidas"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex:6
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex:6
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth token code message"
|
||||
msgid "Token code is <br>%{token}"
|
||||
msgstr "O código do token é <br>%{token}"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:14
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "mfa auth code prompt"
|
||||
msgid "Authentication code"
|
||||
msgstr "Código de autenticação"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:9
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "mfa auth page title"
|
||||
msgid "Two-factor authentication"
|
||||
msgstr "Autenticação de dois-fatores"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:25
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "mfa auth page use recovery code link"
|
||||
msgid "Enter a two-factor recovery code"
|
||||
msgstr "Insira um código de recuperação de dois-fatores"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:22
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "mfa auth verify code button"
|
||||
msgid "Verify"
|
||||
msgstr "Verificar"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:9
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "mfa recover page title"
|
||||
msgid "Two-factor recovery"
|
||||
msgstr "Recuperação de dois-fatores"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:14
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "mfa recover recovery code prompt"
|
||||
msgid "Recovery code"
|
||||
msgstr "Código de recuperação"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:25
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "mfa recover use 2fa code link"
|
||||
msgid "Enter a two-factor code"
|
||||
msgstr "Insira um código de dois-fatores"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:22
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "mfa recover verify recovery code button"
|
||||
msgid "Verify"
|
||||
msgstr "Verificar"
|
||||
|
||||
#: lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex:42
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "static fe profile page remote follow button"
|
||||
msgid "Remote follow"
|
||||
msgstr "Seguir remotamente"
|
||||
|
||||
#: lib/pleroma/web/templates/email/digest.html.eex:163
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "digest email header line"
|
||||
msgid "Hey %{nickname}, here is what you've missed!"
|
||||
msgstr "Ei, %{nickname}, veja o que você perdeu!"
|
||||
|
||||
#: lib/pleroma/web/templates/email/digest.html.eex:544
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "digest email receiver address"
|
||||
msgid "The email address you are subscribed as is <a href='mailto:%{@user.email}' style='color: %{color};text-decoration: none;'>%{email}</a>. "
|
||||
msgstr ""
|
||||
"O email ao qual você está inscrito é <a href='mailto:%{@user.email}' "
|
||||
"style='color:%{color};text-decoration:none;'>%{email}</a>. "
|
||||
|
||||
#: lib/pleroma/web/templates/email/digest.html.eex:538
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "digest email sending reason"
|
||||
msgid "You have received this email because you have signed up to receive digest emails from <b>%{instance}</b> Pleroma instance."
|
||||
msgstr ""
|
||||
"Você recebeu esse email porque você se inscreveu para receber resumos por "
|
||||
"email da instância Akkoma <b>%{instance}</b>."
|
||||
|
||||
#: lib/pleroma/web/templates/email/digest.html.eex:547
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "digest email unsubscribe action"
|
||||
msgid "To unsubscribe, please go %{here}."
|
||||
msgstr "Para se desinscrever, por favor vá %{here}."
|
||||
|
||||
#: lib/pleroma/web/templates/email/digest.html.eex:547
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "digest email unsubscribe action link text"
|
||||
msgid "here"
|
||||
msgstr "aqui"
|
||||
|
||||
#: lib/pleroma/web/templates/mailer/subscription/unsubscribe_failure.html.eex:1
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "mailer unsubscribe failed message"
|
||||
msgid "UNSUBSCRIBE FAILURE"
|
||||
msgstr "FALHA AO SE DESINSCREVER"
|
||||
|
||||
#: lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex:1
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "mailer unsubscribe successful message"
|
||||
msgid "UNSUBSCRIBE SUCCESSFUL"
|
||||
msgstr "INSCRIÇÃO CANCELADA COM SUCESSO"
|
||||
|
||||
#: lib/pleroma/web/templates/email/digest.html.eex:385
|
||||
#, elixir-format
|
||||
msgctxt "new followers count header"
|
||||
msgid "%{count} New Follower"
|
||||
msgid_plural "%{count} New Followers"
|
||||
msgstr[0] "%{count} Novo Seguidor"
|
||||
msgstr[1] "%{count} Novos Seguidores"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:384
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "account archive email subject"
|
||||
msgid "Your account archive is ready"
|
||||
msgstr "O arquivamento da sua conta está pronto"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:188
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "approval pending email body"
|
||||
msgid "<h3>Awaiting Approval</h3>\n<p>Your account at %{instance_name} is being reviewed by staff. You will receive another email once your account is approved.</p>\n"
|
||||
msgstr ""
|
||||
"<h3> Aguardando Aprovação </h3>\n"
|
||||
"<p>Sua conta na instância %{instance_name} está sendo revisada pela staff. "
|
||||
"Você receberá um novo email assim que a conta for aprovada. </p>\n"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:202
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "approval pending email subject"
|
||||
msgid "Your account is awaiting approval"
|
||||
msgstr "Sua conta está aguardando aprovação"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:158
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "confirmation email body"
|
||||
msgid "<h3>Thank you for registering on %{instance_name}</h3>\n<p>Email confirmation is required to activate the account.</p>\n<p>Please click the following link to <a href=\"%{confirmation_url}\">activate your account</a>.</p>\n"
|
||||
msgstr ""
|
||||
"<h3> Obrigado por se registrar na instância %{instance_name}</h3>\n"
|
||||
"<p> Uma confirmação de email é necessário para ativar a sua conta </p>\n"
|
||||
"<p>Por favor, clique no seguinte link para <a href=\"%{confirmation_url}\""
|
||||
">ativar a sua conta</a>.</p>\n"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:174
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "confirmation email subject"
|
||||
msgid "%{instance_name} account confirmation"
|
||||
msgstr "Confirmação de conta de %{instance_name}"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:310
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "digest email subject"
|
||||
msgid "Your digest from %{instance_name}"
|
||||
msgstr "O seu resumo de %{instance_name}"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:81
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "password reset email body"
|
||||
msgid "<h3>Reset your password at %{instance_name}</h3>\n<p>Someone has requested password change for your account at %{instance_name}.</p>\n<p>If it was you, visit the following link to proceed: <a href=\"%{password_reset_url}\">reset password</a>.</p>\n<p>If it was someone else, nothing to worry about: your data is secure and your password has not been changed.</p>\n"
|
||||
msgstr ""
|
||||
"<h3>Redefina a sua senha em %{instance_name}</h3>\n"
|
||||
"<p>Alguém requisitou uma redefinição de senha para a sua conta em "
|
||||
"%{instance_name}.</p>\n"
|
||||
"<p>Caso tenha sido você, visite o seguinte link para proceder: <a href=\""
|
||||
"%{passoword_reset_url}\">redefinir senha</a>.</p>\n"
|
||||
"<p>Se por acaso tenha sido outra pessoa, não há com o que se preocupar: seus "
|
||||
"dados estão seguros e a sua senha não foi alterada.</p>\n"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:98
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "password reset email subject"
|
||||
msgid "Password reset"
|
||||
msgstr "Senha redefinida"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:215
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "successful registration email body"
|
||||
msgid "<h3>Hello @%{nickname},</h3>\n<p>Your account at %{instance_name} has been registered successfully.</p>\n<p>No further action is required to activate your account.</p>\n"
|
||||
msgstr ""
|
||||
"<h3>Olá, @%{nickname},</h3>\n"
|
||||
"<p>Sua conta em %{instance_name} foi criada com sucesso.</p>\n"
|
||||
"<p>Nenhuma ação extra é necessária para ativar a sua conta.</p>\n"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:231
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "successful registration email subject"
|
||||
msgid "Account registered on %{instance_name}"
|
||||
msgstr "Conta registrada em %{instance_name}"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:136
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "user invitation email subject"
|
||||
msgid "Invitation to %{instance_name}"
|
||||
msgstr "Convite para %{instance_name}"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:53
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "welcome email html body"
|
||||
msgid "Welcome to %{instance_name}!"
|
||||
msgstr "Bem-vindo(a) à instância %{instance_name}!"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:41
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "welcome email subject"
|
||||
msgid "Welcome to %{instance_name}!"
|
||||
msgstr "Bem-vindo(a) à instância %{instance_name}!"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:65
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "welcome email text body"
|
||||
msgid "Welcome to %{instance_name}!"
|
||||
msgstr "Bem-vindo(a) à instância %{instance_name}!"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:368
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "account archive email body - admin requested"
|
||||
msgid "<p>Admin @%{admin_nickname} requested a full backup of your Akkoma account. It's ready for download:</p>\n<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
|
||||
msgstr ""
|
||||
"<p>O administrador @%{admin_nickname} requisitou um backup completo da sua "
|
||||
"conta Akkoma. Ele está pronto para download:</p>\n"
|
||||
"<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:356
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "account archive email body - self-requested"
|
||||
msgid "<p>You requested a full backup of your Akkoma account. It's ready for download:</p>\n<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
|
||||
msgstr ""
|
||||
"<p> Foi requisitou uma backup completo da sua conta Akkoma. Está pronto para "
|
||||
"download:</p>\n"
|
||||
"<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:41
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth register page title"
|
||||
msgid "This is your first visit! Please enter your Akkoma handle."
|
||||
msgstr ""
|
||||
"Esta é a sua primeira visita! Por favor, entre com o seu sobrenome Akkoma."
|
||||
|
||||
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:123
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow error message - unknown error"
|
||||
msgid "Something went wrong."
|
||||
msgstr "Algo deu errado."
|
||||
|
||||
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:67
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow error message - user not found"
|
||||
msgid "Could not find user"
|
||||
msgstr "Não foi possível encontrar o usuário"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:8
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "status interact authorization button"
|
||||
msgid "Interact"
|
||||
msgstr "Interagir"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:2
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "status interact error"
|
||||
msgid "Error: %{error}"
|
||||
msgstr "Erro: %{error}"
|
||||
|
||||
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:95
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "status interact error message - status not found"
|
||||
msgid "Could not find status"
|
||||
msgstr "Não foi possível achar o status"
|
||||
|
||||
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:144
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "status interact error message - unknown error"
|
||||
msgid "Something went wrong."
|
||||
msgstr "Algo deu errado."
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:4
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "status interact header"
|
||||
msgid "Interacting with %{nickname}'s %{status_link}"
|
||||
msgstr "Interagindo com o %{status_link} de %{nickname}"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:4
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "status interact header - status link text"
|
||||
msgid "status"
|
||||
msgstr "status"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:119
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "user invitation email body"
|
||||
msgid "<h3>You are invited to %{instance_name}</h3>\n<p>%{inviter_name} invites you to join %{instance_name}, an instance of Akkoma federated social networking platform.</p>\n<p>Click the following link to register: <a href=\"%{registration_url}\">accept invitation</a>.</p>\n"
|
||||
msgstr ""
|
||||
"<h3> Você foi convidado(a) para a instância %{instance_name}</h3>\n"
|
||||
"<p>%{inviter_name} lhe convidou a se juntar à instância %{instance_name}, "
|
||||
"uma instância Akkoma da plataforma de redes sociais federadas.</p>\n"
|
||||
"<p>Clique no seguinte link para se registrar: <a href=\"%{registration_url}\""
|
||||
">aceitar convite</a>.</p>\n"
|
|
@ -8,555 +8,589 @@
|
|||
### to merge POT files into PO files.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"PO-Revision-Date: 2024-04-22 23:54+0000\n"
|
||||
"Last-Translator: Toot <toothpicker@users.noreply.translate.akkoma.dev>\n"
|
||||
"Language-Team: Chinese (Traditional) <http://translate.akkoma.dev/projects/"
|
||||
"akkoma/akkoma-backend-static-pages/zh_Hant/>\n"
|
||||
"Language: zh_Hant\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Weblate 4.18.2\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:9
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow authorization button"
|
||||
msgid "Authorize"
|
||||
msgstr ""
|
||||
msgstr "批准"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:2
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow error"
|
||||
msgid "Error fetching user"
|
||||
msgstr ""
|
||||
msgstr "無法獲取用戶信息"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:4
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow header"
|
||||
msgid "Remote follow"
|
||||
msgstr ""
|
||||
msgstr "跨站關注"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:8
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "placeholder text for auth code entry"
|
||||
msgid "Authentication code"
|
||||
msgstr ""
|
||||
msgstr "驗證碼"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:10
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "placeholder text for password entry"
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
msgstr "密碼"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:8
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "placeholder text for username entry"
|
||||
msgid "Username"
|
||||
msgstr ""
|
||||
msgstr "用戶名"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:13
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow authorization button for login"
|
||||
msgid "Authorize"
|
||||
msgstr ""
|
||||
msgstr "授權"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:12
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow authorization button for mfa"
|
||||
msgid "Authorize"
|
||||
msgstr ""
|
||||
msgstr "授權"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex:2
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow error"
|
||||
msgid "Error following account"
|
||||
msgstr ""
|
||||
msgstr "無法關注帳戶"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:4
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow header, need login"
|
||||
msgid "Log in to follow"
|
||||
msgstr ""
|
||||
msgstr "登錄以關注"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:4
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow mfa header"
|
||||
msgid "Two-factor authentication"
|
||||
msgstr ""
|
||||
msgstr "雙因素身份驗證"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex:4
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow success"
|
||||
msgid "Account followed!"
|
||||
msgstr ""
|
||||
msgstr "已關注該賬號!"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:7
|
||||
#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:7
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "placeholder text for account id"
|
||||
msgid "Your account ID, e.g. lain@quitter.se"
|
||||
msgstr ""
|
||||
msgstr "你的帳號ID,比如:lain@quitter.se"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:8
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow authorization button for following with a remote account"
|
||||
msgid "Follow"
|
||||
msgstr ""
|
||||
msgstr "關注"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:2
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow error"
|
||||
msgid "Error: %{error}"
|
||||
msgstr ""
|
||||
msgstr "錯誤:%{error}"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:4
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow header"
|
||||
msgid "Remotely follow %{nickname}"
|
||||
msgstr ""
|
||||
msgstr "遠程關注 %{nickname}"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:12
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "password reset button"
|
||||
msgid "Reset"
|
||||
msgstr ""
|
||||
msgstr "重設"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex:4
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "password reset failed homepage link"
|
||||
msgid "Homepage"
|
||||
msgstr ""
|
||||
msgstr "主頁"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex:1
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "password reset failed message"
|
||||
msgid "Password reset failed"
|
||||
msgstr ""
|
||||
msgstr "密碼重置失敗"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:8
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "password reset form confirm password prompt"
|
||||
msgid "Confirmation"
|
||||
msgstr ""
|
||||
msgstr "確認"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:4
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "password reset form password prompt"
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
msgstr "密碼"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex:1
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "password reset invalid token message"
|
||||
msgid "Invalid Token"
|
||||
msgstr ""
|
||||
msgstr "無效 Token"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex:2
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "password reset successful homepage link"
|
||||
msgid "Homepage"
|
||||
msgstr ""
|
||||
msgstr "主頁"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex:1
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "password reset successful message"
|
||||
msgid "Password changed!"
|
||||
msgstr ""
|
||||
msgstr "密碼已修改!"
|
||||
|
||||
#: lib/pleroma/web/templates/feed/feed/tag.atom.eex:15
|
||||
#: lib/pleroma/web/templates/feed/feed/tag.rss.eex:7
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "tag feed description"
|
||||
msgid "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse."
|
||||
msgstr ""
|
||||
msgstr "這些是帶有 #%{tag} "
|
||||
"標籤的公開貼文。如果你在聯邦宇宙的任何地方有帳號,你可以與它們進行互動。"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex:3
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth authorization exists page title"
|
||||
msgid "Authorization exists"
|
||||
msgstr ""
|
||||
msgstr "已存在授權"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:37
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth authorize approve button"
|
||||
msgid "Approve"
|
||||
msgstr ""
|
||||
msgstr "允許"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:35
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth authorize cancel button"
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
msgstr "取消"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:26
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth authorize message"
|
||||
msgid "Application <strong>%{client_name}</strong> is requesting access to your account."
|
||||
msgstr ""
|
||||
msgstr "應用程序 <strong>%{client_name}</strong> 正在請求訪問您的帳戶。"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex:3
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth authorized page title"
|
||||
msgid "Successfully authorized"
|
||||
msgstr ""
|
||||
msgstr "授權成功"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex:1
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth external provider page title"
|
||||
msgid "Sign in with external provider"
|
||||
msgstr ""
|
||||
msgstr "使用外部服務進行登錄"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex:13
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth external provider sign in button"
|
||||
msgid "Sign in with %{strategy}"
|
||||
msgstr ""
|
||||
msgstr "用 %{strategy} 登錄"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:59
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth login button"
|
||||
msgid "Log In"
|
||||
msgstr ""
|
||||
msgstr "登錄"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:56
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth login password prompt"
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
msgstr "密碼"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:52
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth login username prompt"
|
||||
msgid "Username"
|
||||
msgstr ""
|
||||
msgstr "用戶名"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:44
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth register nickname prompt"
|
||||
msgid "Pleroma Handle"
|
||||
msgstr ""
|
||||
msgstr "Pleroma 帳號"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:42
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth register nickname unchangeable warning"
|
||||
msgid "Choose carefully! You won't be able to change this later. You will be able to change your display name, though."
|
||||
msgstr ""
|
||||
msgstr "選擇時要慎重!您以後將無法更改此項。不過您可以更改您的顯示名稱。"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:18
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth register page email prompt"
|
||||
msgid "Email"
|
||||
msgstr ""
|
||||
msgstr "郵箱"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:10
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth register page fill form prompt"
|
||||
msgid "If you'd like to register a new account, please provide the details below."
|
||||
msgstr ""
|
||||
msgstr "如果您想註冊一個新帳戶,請提供以下細節。"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:35
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth register page login button"
|
||||
msgid "Proceed as existing user"
|
||||
msgstr ""
|
||||
msgstr "以現有用戶身份進行"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:31
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth register page login password prompt"
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
msgstr "密碼"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:24
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth register page login prompt"
|
||||
msgid "Alternatively, sign in to connect to existing account."
|
||||
msgstr ""
|
||||
msgstr "或者登錄後連接到現有賬戶。"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:27
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth register page login username prompt"
|
||||
msgid "Name or email"
|
||||
msgstr ""
|
||||
msgstr "名字或者郵箱"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:14
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth register page nickname prompt"
|
||||
msgid "Nickname"
|
||||
msgstr ""
|
||||
msgstr "暱稱"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:22
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth register page register button"
|
||||
msgid "Proceed as new user"
|
||||
msgstr ""
|
||||
msgstr "以新用戶身份進行"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:8
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth register page title"
|
||||
msgid "Registration Details"
|
||||
msgstr ""
|
||||
msgstr "註冊細節"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex:2
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth scopes message"
|
||||
msgid "The following permissions will be granted"
|
||||
msgstr ""
|
||||
msgstr "將授予以下權限"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex:6
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex:6
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth token code message"
|
||||
msgid "Token code is <br>%{token}"
|
||||
msgstr ""
|
||||
msgstr "Token 碼是 <br>%{token}"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:14
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "mfa auth code prompt"
|
||||
msgid "Authentication code"
|
||||
msgstr ""
|
||||
msgstr "授權碼"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:9
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "mfa auth page title"
|
||||
msgid "Two-factor authentication"
|
||||
msgstr ""
|
||||
msgstr "雙因素身份驗證"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:25
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "mfa auth page use recovery code link"
|
||||
msgid "Enter a two-factor recovery code"
|
||||
msgstr ""
|
||||
msgstr "輸入一個雙因素恢復的恢復代碼"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:22
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "mfa auth verify code button"
|
||||
msgid "Verify"
|
||||
msgstr ""
|
||||
msgstr "認證"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:9
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "mfa recover page title"
|
||||
msgid "Two-factor recovery"
|
||||
msgstr ""
|
||||
msgstr "雙因素恢復"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:14
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "mfa recover recovery code prompt"
|
||||
msgid "Recovery code"
|
||||
msgstr ""
|
||||
msgstr "恢復碼"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:25
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "mfa recover use 2fa code link"
|
||||
msgid "Enter a two-factor code"
|
||||
msgstr ""
|
||||
msgstr "輸入一個雙重因素驗證碼"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:22
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "mfa recover verify recovery code button"
|
||||
msgid "Verify"
|
||||
msgstr ""
|
||||
msgstr "驗證"
|
||||
|
||||
#: lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex:42
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "static fe profile page remote follow button"
|
||||
msgid "Remote follow"
|
||||
msgstr ""
|
||||
msgstr "跨站關注"
|
||||
|
||||
#: lib/pleroma/web/templates/email/digest.html.eex:163
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "digest email header line"
|
||||
msgid "Hey %{nickname}, here is what you've missed!"
|
||||
msgstr ""
|
||||
msgstr "嗨 %{nickname},這是你錯過了的一些東西!"
|
||||
|
||||
#: lib/pleroma/web/templates/email/digest.html.eex:544
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "digest email receiver address"
|
||||
msgid "The email address you are subscribed as is <a href='mailto:%{@user.email}' style='color: %{color};text-decoration: none;'>%{email}</a>. "
|
||||
msgstr ""
|
||||
"您訂閱的電子郵件地址是 <a href='mailto:%{@user.email}' style='color: %{color"
|
||||
"};text-decoration: none;'>%{email}</a>。 "
|
||||
|
||||
#: lib/pleroma/web/templates/email/digest.html.eex:538
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "digest email sending reason"
|
||||
msgid "You have received this email because you have signed up to receive digest emails from <b>%{instance}</b> Pleroma instance."
|
||||
msgstr ""
|
||||
msgstr "您之所以會收到來自 <b>%{instance}</b> Akkoma "
|
||||
"實例的郵件摘要,是因爲您已經註冊了該服務實例。"
|
||||
|
||||
#: lib/pleroma/web/templates/email/digest.html.eex:547
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "digest email unsubscribe action"
|
||||
msgid "To unsubscribe, please go %{here}."
|
||||
msgstr ""
|
||||
msgstr "取消訂閱,請點擊 %{here}."
|
||||
|
||||
#: lib/pleroma/web/templates/email/digest.html.eex:547
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "digest email unsubscribe action link text"
|
||||
msgid "here"
|
||||
msgstr ""
|
||||
msgstr "這里"
|
||||
|
||||
#: lib/pleroma/web/templates/mailer/subscription/unsubscribe_failure.html.eex:1
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "mailer unsubscribe failed message"
|
||||
msgid "UNSUBSCRIBE FAILURE"
|
||||
msgstr ""
|
||||
msgstr "取消訂閱失敗"
|
||||
|
||||
#: lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex:1
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "mailer unsubscribe successful message"
|
||||
msgid "UNSUBSCRIBE SUCCESSFUL"
|
||||
msgstr ""
|
||||
msgstr "成功取消訂閱"
|
||||
|
||||
#: lib/pleroma/web/templates/email/digest.html.eex:385
|
||||
#, elixir-format
|
||||
msgctxt "new followers count header"
|
||||
msgid "%{count} New Follower"
|
||||
msgid_plural "%{count} New Followers"
|
||||
msgstr[0] ""
|
||||
msgstr[0] "%{count} 個新關注者"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:384
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "account archive email subject"
|
||||
msgid "Your account archive is ready"
|
||||
msgstr ""
|
||||
msgstr "您的帳戶檔案已經準備好了"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:188
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "approval pending email body"
|
||||
msgid "<h3>Awaiting Approval</h3>\n<p>Your account at %{instance_name} is being reviewed by staff. You will receive another email once your account is approved.</p>\n"
|
||||
msgstr ""
|
||||
"<h3>正在等待批准</h3>\n"
|
||||
"<p> 您在 %{instance_name} 的帳戶正在被工作人員審查。一旦您的帳戶被批准通過,"
|
||||
"您將收到另一封電子郵件。</p>\n"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:202
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "approval pending email subject"
|
||||
msgid "Your account is awaiting approval"
|
||||
msgstr ""
|
||||
msgstr "您的帳戶正在等待審批"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:158
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "confirmation email body"
|
||||
msgid "<h3>Thank you for registering on %{instance_name}</h3>\n<p>Email confirmation is required to activate the account.</p>\n<p>Please click the following link to <a href=\"%{confirmation_url}\">activate your account</a>.</p>\n"
|
||||
msgstr ""
|
||||
"<h3>感謝註冊 %{instance_name}</h3>\n"
|
||||
"<p>需要電子郵件確認才能激活該帳戶</p>\n"
|
||||
"<p>請點擊以下鏈結以 <a href=\"%{confirmation_url}\">確認您的帳戶</a></p>\n"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:174
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "confirmation email subject"
|
||||
msgid "%{instance_name} account confirmation"
|
||||
msgstr ""
|
||||
msgstr "%{instance_name} 帳戶確認"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:310
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "digest email subject"
|
||||
msgid "Your digest from %{instance_name}"
|
||||
msgstr ""
|
||||
msgstr "您來自 %{instance_name} 的摘要郵件"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:81
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "password reset email body"
|
||||
msgid "<h3>Reset your password at %{instance_name}</h3>\n<p>Someone has requested password change for your account at %{instance_name}.</p>\n<p>If it was you, visit the following link to proceed: <a href=\"%{password_reset_url}\">reset password</a>.</p>\n<p>If it was someone else, nothing to worry about: your data is secure and your password has not been changed.</p>\n"
|
||||
msgstr ""
|
||||
"<h3>在 %{instance_name} 重置您的密碼</h3>\n"
|
||||
"<p>有人請求更改您在 %{instance_name} 的帳戶密碼。</p>\n"
|
||||
"<p>如果這是您的操作,請點擊以下鏈結繼續重置密碼:<a href=\""
|
||||
"%{password_reset_url}\">重置密碼</a>。</p>\n"
|
||||
"<p>如果這不是您的操作,請不用擔心:您的數據是安全的,您的密碼未被更改。</p>\n"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:98
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "password reset email subject"
|
||||
msgid "Password reset"
|
||||
msgstr ""
|
||||
msgstr "重置密碼"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:215
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "successful registration email body"
|
||||
msgid "<h3>Hello @%{nickname},</h3>\n<p>Your account at %{instance_name} has been registered successfully.</p>\n<p>No further action is required to activate your account.</p>\n"
|
||||
msgstr ""
|
||||
"<h3>你好,@%{nickname},</h3>\n"
|
||||
"<p>你在 %{instance_name} 的帳戶已經成功註冊。</p>\n"
|
||||
"<p>無需進行其他操作即可激活你的帳戶。</p>\n"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:231
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "successful registration email subject"
|
||||
msgid "Account registered on %{instance_name}"
|
||||
msgstr ""
|
||||
msgstr "帳號註冊在 %{instance_name}"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:136
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "user invitation email subject"
|
||||
msgid "Invitation to %{instance_name}"
|
||||
msgstr ""
|
||||
msgstr "邀請加入 %{instance_name}"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:53
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "welcome email html body"
|
||||
msgid "Welcome to %{instance_name}!"
|
||||
msgstr ""
|
||||
msgstr "歡迎來到 %{instance_name}!"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:41
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "welcome email subject"
|
||||
msgid "Welcome to %{instance_name}!"
|
||||
msgstr ""
|
||||
msgstr "歡迎來到 %{instance_name}!"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:65
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "welcome email text body"
|
||||
msgid "Welcome to %{instance_name}!"
|
||||
msgstr ""
|
||||
msgstr "歡迎來到 %{instance_name}!"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:368
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "account archive email body - admin requested"
|
||||
msgid "<p>Admin @%{admin_nickname} requested a full backup of your Akkoma account. It's ready for download:</p>\n<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
|
||||
msgstr ""
|
||||
"<p>管理員 @%{admin_nickname} 請求對你的 Akkoma "
|
||||
"帳戶進行完整備份,備份已準備好可供下載:</p>\n"
|
||||
"<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:356
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "account archive email body - self-requested"
|
||||
msgid "<p>You requested a full backup of your Akkoma account. It's ready for download:</p>\n<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
|
||||
msgstr ""
|
||||
"<p>您請求了 Akkoma 帳戶的完整備份。備份已準備就緒,可以下載:</p>\n"
|
||||
"<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
|
||||
|
||||
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:41
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "oauth register page title"
|
||||
msgid "This is your first visit! Please enter your Akkoma handle."
|
||||
msgstr ""
|
||||
msgstr "這是您的第一次訪問!請填寫您的 Akkoma 帳號。"
|
||||
|
||||
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:123
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow error message - unknown error"
|
||||
msgid "Something went wrong."
|
||||
msgstr ""
|
||||
msgstr "發生了一些錯誤。"
|
||||
|
||||
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:67
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "remote follow error message - user not found"
|
||||
msgid "Could not find user"
|
||||
msgstr ""
|
||||
msgstr "無法找到相應用戶"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:8
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "status interact authorization button"
|
||||
msgid "Interact"
|
||||
msgstr ""
|
||||
msgstr "互動"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:2
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "status interact error"
|
||||
msgid "Error: %{error}"
|
||||
msgstr ""
|
||||
msgstr "錯誤:%{error}"
|
||||
|
||||
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:95
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "status interact error message - status not found"
|
||||
msgid "Could not find status"
|
||||
msgstr ""
|
||||
msgstr "無法找到貼文"
|
||||
|
||||
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:144
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "status interact error message - unknown error"
|
||||
msgid "Something went wrong."
|
||||
msgstr ""
|
||||
msgstr "發生了一些錯誤。"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:4
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "status interact header"
|
||||
msgid "Interacting with %{nickname}'s %{status_link}"
|
||||
msgstr ""
|
||||
msgstr "與 %{nickname} 的 %{status_link} 進行交互"
|
||||
|
||||
#: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:4
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "status interact header - status link text"
|
||||
msgid "status"
|
||||
msgstr ""
|
||||
msgstr "貼文"
|
||||
|
||||
#: lib/pleroma/emails/user_email.ex:119
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "user invitation email body"
|
||||
msgid "<h3>You are invited to %{instance_name}</h3>\n<p>%{inviter_name} invites you to join %{instance_name}, an instance of Akkoma federated social networking platform.</p>\n<p>Click the following link to register: <a href=\"%{registration_url}\">accept invitation</a>.</p>\n"
|
||||
msgstr ""
|
||||
"<h3>您被邀請加入 %{instance_name}</h3>\n"
|
||||
"<p>%{inviter_name} 邀請您加入 %{instance_name},這是一個使用 Akkoma "
|
||||
"聯邦社交網絡平臺的實例。</p>\n"
|
||||
"<p>點擊以下鏈結註冊: <a href=\"%{registration_url}\">接受邀請</a>.</p>\n"
|
||||
|
|
|
@ -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
|
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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -69,7 +69,9 @@ defmodule Mix.Tasks.Pleroma.InstanceTest do
|
|||
"test/uploads",
|
||||
"--static-dir",
|
||||
"./test/../test/instance/static/",
|
||||
"--strip-uploads",
|
||||
"--strip-uploads-metadata",
|
||||
"y",
|
||||
"--read-uploads-description",
|
||||
"y",
|
||||
"--anonymize-uploads",
|
||||
"n"
|
||||
|
@ -91,7 +93,10 @@ defmodule Mix.Tasks.Pleroma.InstanceTest do
|
|||
assert generated_config =~ "password: \"dbpass\""
|
||||
assert generated_config =~ "configurable_from_database: true"
|
||||
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 File.read!(tmp_path() <> "setup.psql") == generated_setup_psql()
|
||||
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.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
|
||||
test "gives warning when there are still strings" do
|
||||
clear_config([:mrf_simple],
|
||||
|
|
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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
|||
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
|
||||
test "it returns relay followers", %{conn: conn} do
|
||||
relay_actor = Relay.get_actor()
|
||||
|
@ -1977,95 +1739,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
|||
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
|
||||
clear_config([:instance, :max_pinned_statuses], 2)
|
||||
user = insert(:user)
|
||||
|
|
|
@ -12,14 +12,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidatorTest do
|
|||
|
||||
describe "attachments" do
|
||||
test "works with apng" do
|
||||
attachment =
|
||||
%{
|
||||
"mediaType" => "image/apng",
|
||||
"name" => "",
|
||||
"type" => "Document",
|
||||
"url" =>
|
||||
"https://media.misskeyusercontent.com/io/2859c26e-cd43-4550-848b-b6243bc3fe28.apng"
|
||||
}
|
||||
attachment = %{
|
||||
"mediaType" => "image/apng",
|
||||
"name" => "",
|
||||
"type" => "Document",
|
||||
"url" =>
|
||||
"https://media.misskeyusercontent.com/io/2859c26e-cd43-4550-848b-b6243bc3fe28.apng"
|
||||
}
|
||||
|
||||
assert {:ok, 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
|
|
@ -137,6 +137,37 @@ defmodule Pleroma.Web.FederatorTest do
|
|||
assert {:error, :already_present} = ObanHelpers.perform(job)
|
||||
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
|
||||
params = %{
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
|
|
|
@ -111,7 +111,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
|||
# 2 hours
|
||||
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
|
||||
|
@ -123,12 +123,16 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
|||
|
||||
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(
|
||||
worker: Pleroma.Workers.PurgeExpiredActivity,
|
||||
args: %{activity_id: fourth_id},
|
||||
scheduled_at: expires_at
|
||||
scheduled_at: expires_at2
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -148,16 +152,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
|||
activity = Activity.get_by_id_with_object(id)
|
||||
{:ok, expires_at, _} = DateTime.from_iso8601(activity.data["expires_at"])
|
||||
|
||||
assert Timex.diff(
|
||||
expires_at,
|
||||
DateTime.utc_now(),
|
||||
:hours
|
||||
) == 23
|
||||
expiry_delay = Timex.diff(expires_at, DateTime.utc_now(), :hours)
|
||||
assert(expiry_delay in [23, 24])
|
||||
|
||||
assert_enqueued(
|
||||
worker: Pleroma.Workers.PurgeExpiredActivity,
|
||||
args: %{activity_id: id},
|
||||
scheduled_at: DateTime.add(DateTime.utc_now(), 1 * 60 * 60 * 24)
|
||||
scheduled_at: expires_at
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -1405,7 +1406,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
|||
%{conn: conn} = oauth_access(["write:accounts", "write:statuses"])
|
||||
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} =
|
||||
conn
|
||||
|
@ -1416,10 +1417,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
|||
})
|
||||
|> 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(
|
||||
worker: Pleroma.Workers.PurgeExpiredActivity,
|
||||
args: %{activity_id: id},
|
||||
scheduled_at: expires_at
|
||||
scheduled_at: expires_at2
|
||||
)
|
||||
|
||||
assert %{"id" => ^id, "pinned" => true} =
|
||||
|
@ -1431,7 +1437,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
|||
refute_enqueued(
|
||||
worker: Pleroma.Workers.PurgeExpiredActivity,
|
||||
args: %{activity_id: id},
|
||||
scheduled_at: expires_at
|
||||
scheduled_at: expires_at2
|
||||
)
|
||||
|
||||
assert %{"id" => ^id, "pinned" => false} =
|
||||
|
@ -1443,7 +1449,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
|||
assert_enqueued(
|
||||
worker: Pleroma.Workers.PurgeExpiredActivity,
|
||||
args: %{activity_id: id},
|
||||
scheduled_at: expires_at
|
||||
scheduled_at: expires_at2
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -1944,7 +1950,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
|||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
{: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"])
|
||||
|
||||
|
|
|
@ -559,16 +559,15 @@ defmodule Pleroma.Factory do
|
|||
like_activity = attrs[:like_activity] || insert(:like_activity)
|
||||
attrs = Map.drop(attrs, [:like_activity])
|
||||
|
||||
data =
|
||||
%{
|
||||
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
|
||||
"type" => "Undo",
|
||||
"actor" => like_activity.data["actor"],
|
||||
"to" => like_activity.data["to"],
|
||||
"object" => like_activity.data["id"],
|
||||
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
|
||||
"context" => like_activity.data["context"]
|
||||
}
|
||||
data = %{
|
||||
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
|
||||
"type" => "Undo",
|
||||
"actor" => like_activity.data["actor"],
|
||||
"to" => like_activity.data["to"],
|
||||
"object" => like_activity.data["id"],
|
||||
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
|
||||
"context" => like_activity.data["context"]
|
||||
}
|
||||
|
||||
%Pleroma.Activity{
|
||||
data: data,
|
||||
|
|
Loading…
Reference in a new issue