mirror of
https://git.pleroma.social/pleroma/pleroma.git
synced 2026-02-15 17:16:57 +00:00
Merge remote-tracking branch 'origin/develop' into notice-routes
This commit is contained in:
commit
6614b52a17
54 changed files with 1314 additions and 755 deletions
|
|
@ -8,7 +8,9 @@ variables: &global_variables
|
|||
MIX_ENV: test
|
||||
|
||||
cache: &global_cache_policy
|
||||
key: ${CI_COMMIT_REF_SLUG}
|
||||
key:
|
||||
files:
|
||||
- mix.lock
|
||||
paths:
|
||||
- deps
|
||||
- _build
|
||||
|
|
@ -59,7 +61,7 @@ benchmark:
|
|||
|
||||
unit-testing:
|
||||
stage: test
|
||||
retry: 2
|
||||
# retry: 2
|
||||
cache: &testing_cache_policy
|
||||
<<: *global_cache_policy
|
||||
policy: pull
|
||||
|
|
@ -91,24 +93,24 @@ unit-testing:
|
|||
# - epmd -daemon
|
||||
# - mix test --trace --only federated
|
||||
|
||||
unit-testing-rum:
|
||||
stage: test
|
||||
retry: 2
|
||||
cache: *testing_cache_policy
|
||||
services:
|
||||
- name: minibikini/postgres-with-rum:12
|
||||
alias: postgres
|
||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||
variables:
|
||||
<<: *global_variables
|
||||
RUM_ENABLED: "true"
|
||||
script:
|
||||
- apt-get update && apt-get install -y libimage-exiftool-perl ffmpeg
|
||||
- mix deps.get
|
||||
- mix ecto.create
|
||||
- mix ecto.migrate
|
||||
- "mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/"
|
||||
- mix test --preload-modules
|
||||
# unit-testing-rum:
|
||||
# stage: test
|
||||
# retry: 2
|
||||
# cache: *testing_cache_policy
|
||||
# services:
|
||||
# - name: minibikini/postgres-with-rum:12
|
||||
# alias: postgres
|
||||
# command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||
# variables:
|
||||
# <<: *global_variables
|
||||
# RUM_ENABLED: "true"
|
||||
# script:
|
||||
# - apt-get update && apt-get install -y libimage-exiftool-perl ffmpeg
|
||||
# - mix deps.get
|
||||
# - mix ecto.create
|
||||
# - mix ecto.migrate
|
||||
# - "mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/"
|
||||
# - mix test --preload-modules
|
||||
|
||||
lint:
|
||||
stage: test
|
||||
|
|
@ -175,8 +177,8 @@ spec-deploy:
|
|||
- apk add curl
|
||||
script:
|
||||
- curl -X POST -F"token=$API_DOCS_PIPELINE_TRIGGER" -F'ref=master' -F"variables[BRANCH]=$CI_COMMIT_REF_NAME" -F"variables[JOB_REF]=$CI_JOB_ID" https://git.pleroma.social/api/v4/projects/1130/trigger/pipeline
|
||||
|
||||
|
||||
|
||||
|
||||
stop_review_app:
|
||||
image: alpine:3.9
|
||||
stage: deploy
|
||||
|
|
@ -235,7 +237,7 @@ amd64-musl:
|
|||
stage: release
|
||||
artifacts: *release-artifacts
|
||||
only: *release-only
|
||||
image: elixir:1.10.3-alpine
|
||||
image: elixir:1.10.3-alpine
|
||||
cache: *release-cache
|
||||
variables: *release-variables
|
||||
before_script: &before-release-musl
|
||||
|
|
@ -393,4 +395,4 @@ docker-adhoc:
|
|||
tags:
|
||||
- dind
|
||||
only:
|
||||
- /^build-docker/.*$/@pleroma/pleroma
|
||||
- /^build-docker/.*$/@pleroma/pleroma
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Support pagination of blocks and mutes.
|
||||
- Account backup.
|
||||
- Configuration: Add `:instance, autofollowing_nicknames` setting to provide a way to make accounts automatically follow new users that register on the local Pleroma instance.
|
||||
- `[:activitypub, :blockers_visible]` config to control visibility of blockers.
|
||||
- Ability to view remote timelines, with ex. `/api/v1/timelines/public?instance=lain.com` and streams `public:remote` and `public:remote:media`.
|
||||
- The site title is now injected as a `title` tag like preloads or metadata.
|
||||
- Password reset tokens now are not accepted after a certain age.
|
||||
|
|
|
|||
24
CHANGELOG_soapbox.md
Normal file
24
CHANGELOG_soapbox.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
This file is only for changes to Soapbox.
|
||||
For changes to Pleroma, see `CHANGELOG.md`
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## [0.1.0] - unreleased
|
||||
|
||||
Based on Pleroma 2.3.0-stable.
|
||||
|
||||
### Added
|
||||
- Rich media embeds for sites like YouTube, etc.
|
||||
- Twitter-like block behavior, configured under "ActivityPub > Blockers visible" in AdminFE.
|
||||
- The Soapbox version in `/api/v1/instance`
|
||||
|
||||
### Changed
|
||||
- Twitter-like block behavior is now the default.
|
||||
|
||||
### Fixed
|
||||
- Domain blocks: reposts from a blocked domain are now correctly blocked.
|
||||
- Fixed some (not all) Markdown issues, such as broken trailing slash in links.
|
||||
- Don't crash so hard when email settings are invalid.
|
||||
65
README.md
65
README.md
|
|
@ -1,54 +1,35 @@
|
|||
<img src="https://git.pleroma.social/pleroma/pleroma/uploads/8cec84f5a084d887339f57deeb8a293e/pleroma-banner-vector-nopad-notext.svg" width="300px" />
|
||||
# Soapbox
|
||||
|
||||
## About
|
||||

|
||||
|
||||
Pleroma is a microblogging server software that can federate (= exchange messages with) other servers that support ActivityPub. What that means is that you can host a server for yourself or your friends and stay in control of your online identity, but still exchange messages with people on larger servers. Pleroma will federate with all servers that implement ActivityPub, like Friendica, GNU Social, Hubzilla, Mastodon, Misskey, Peertube, and Pixelfed.
|
||||
> :warning: Not yet ready for production use.
|
||||
|
||||
Pleroma is written in Elixir and uses PostgresSQL for data storage. It's efficient enough to be ran on low-power devices like Raspberry Pi (though we wouldn't recommend storing the database on the internal SD card ;) but can scale well when ran on more powerful hardware (albeit only single-node for now).
|
||||
**Soapbox** is a federated social media server with a focus on user experience.
|
||||
It is based on [Pleroma](https://pleroma.social/).
|
||||
|
||||
For clients it supports the [Mastodon client API](https://docs.joinmastodon.org/api/guidelines/) with Pleroma extensions (see the API section on <https://docs-develop.pleroma.social>).
|
||||
## Your social media server
|
||||
|
||||
- [Client Applications for Pleroma](https://docs-develop.pleroma.social/backend/clients/)
|
||||
Soapbox empowers people to take control of their social media experience.
|
||||
Hosting your own server means that *you* get to decide the rules.
|
||||
|
||||
Soapbox connects to over 4,000 other servers on the Fediverse.
|
||||
It is designed to spread your message far and wide, while being resilient to deplatforming.
|
||||
|
||||
## Installation
|
||||
|
||||
### OTP releases (Recommended)
|
||||
If you are running Linux (glibc or musl) on x86/arm, the recommended way to install Pleroma is by using OTP releases. OTP releases are as close as you can get to binary releases with Erlang/Elixir. The release is self-contained, and provides everything needed to boot it. The installation instructions are available [here](https://docs-develop.pleroma.social/backend/installation/otp_en/).
|
||||
See [the installation guide](https://gitlab.com/soapbox-pub/soapbox/-/blob/develop/docs/installation/ubuntu_en.md).
|
||||
|
||||
### From Source
|
||||
If your platform is not supported, or you just want to be able to edit the source code easily, you may install Pleroma from source.
|
||||
## License
|
||||
|
||||
- [Alpine Linux](https://docs-develop.pleroma.social/backend/installation/alpine_linux_en/)
|
||||
- [Arch Linux](https://docs-develop.pleroma.social/backend/installation/arch_linux_en/)
|
||||
- [CentOS 7](https://docs-develop.pleroma.social/backend/installation/centos7_en/)
|
||||
- [Debian-based](https://docs-develop.pleroma.social/backend/installation/debian_based_en/)
|
||||
- [Debian-based (jp)](https://docs-develop.pleroma.social/backend/installation/debian_based_jp/)
|
||||
- [FreeBSD](https://docs-develop.pleroma.social/backend/installation/freebsd_en/)
|
||||
- [Gentoo Linux](https://docs-develop.pleroma.social/backend/installation/gentoo_en/)
|
||||
- [NetBSD](https://docs-develop.pleroma.social/backend/installation/netbsd_en/)
|
||||
- [OpenBSD](https://docs-develop.pleroma.social/backend/installation/openbsd_en/)
|
||||
- [OpenBSD (fi)](https://docs-develop.pleroma.social/backend/installation/openbsd_fi/)
|
||||
Soapbox is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
### OS/Distro packages
|
||||
Currently Pleroma is not packaged by any OS/Distros, but if you want to package it for one, we can guide you through the process on our [community channels](#community-channels). If you want to change default options in your Pleroma package, please **discuss it with us first**.
|
||||
Soapbox is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
### Docker
|
||||
While we don’t provide docker files, other people have written very good ones. Take a look at <https://github.com/angristan/docker-pleroma> or <https://glitch.sh/sn0w/pleroma-docker>.
|
||||
|
||||
### Compilation Troubleshooting
|
||||
If you ever encounter compilation issues during the updating of Pleroma, you can try these commands and see if they fix things:
|
||||
|
||||
- `mix deps.clean --all`
|
||||
- `mix local.rebar`
|
||||
- `mix local.hex`
|
||||
- `rm -r _build`
|
||||
|
||||
If you are not developing Pleroma, it is better to use the OTP release, which comes with everything precompiled.
|
||||
|
||||
## Documentation
|
||||
- Latest Released revision: <https://docs.pleroma.social>
|
||||
- Latest Git revision: <https://docs-develop.pleroma.social>
|
||||
|
||||
## Community Channels
|
||||
* IRC: **#pleroma** and **#pleroma-dev** on freenode, webchat is available at <https://irc.pleroma.social>
|
||||
* Matrix: <https://matrix.to/#/#freenode_#pleroma:matrix.org> and <https://matrix.to/#/#freenode_#pleroma-dev:matrix.org>
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Soapbox. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
|
|
|||
|
|
@ -353,6 +353,7 @@ config :pleroma, :manifest,
|
|||
config :pleroma, :activitypub,
|
||||
unfollow_blocked: true,
|
||||
outgoing_blocks: true,
|
||||
blockers_visible: true,
|
||||
follow_handshake_timeout: 500,
|
||||
note_replies_output_limit: 5,
|
||||
sign_object_fetches: true,
|
||||
|
|
@ -409,9 +410,10 @@ config :pleroma, :rich_media,
|
|||
ignore_hosts: [],
|
||||
ignore_tld: ["local", "localdomain", "lan"],
|
||||
parsers: [
|
||||
Pleroma.Web.RichMedia.Parsers.TwitterCard,
|
||||
Pleroma.Web.RichMedia.Parsers.OEmbed
|
||||
Pleroma.Web.RichMedia.Parsers.OEmbed,
|
||||
Pleroma.Web.RichMedia.Parsers.TwitterCard
|
||||
],
|
||||
oembed_providers_enabled: true,
|
||||
failure_backoff: 60_000,
|
||||
ttl_setters: [Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl]
|
||||
|
||||
|
|
@ -840,6 +842,8 @@ config :pleroma, ConcurrentLimiter, [
|
|||
{Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy, [max_running: 5, max_waiting: 5]}
|
||||
]
|
||||
|
||||
import_config "soapbox.exs"
|
||||
|
||||
# Import environment specific config. This must remain at the bottom
|
||||
# of this file so it overrides the configuration defined above.
|
||||
import_config "#{Mix.env()}.exs"
|
||||
|
|
|
|||
|
|
@ -1665,6 +1665,11 @@ config :pleroma, :config_description, [
|
|||
type: :boolean,
|
||||
description: "Whether to federate blocks to other instances"
|
||||
},
|
||||
%{
|
||||
key: :blockers_visible,
|
||||
type: :boolean,
|
||||
description: "Whether a user can see someone who has blocked them"
|
||||
},
|
||||
%{
|
||||
key: :sign_object_fetches,
|
||||
type: :boolean,
|
||||
|
|
@ -1976,6 +1981,12 @@ config :pleroma, :config_description, [
|
|||
type: :boolean,
|
||||
description: "Enables RichMedia parsing of URLs"
|
||||
},
|
||||
%{
|
||||
key: :oembed_providers_enabled,
|
||||
type: :boolean,
|
||||
description:
|
||||
"Embed rich media from a list of known providers. This takes precedence over other parsers."
|
||||
},
|
||||
%{
|
||||
key: :ignore_hosts,
|
||||
type: {:list, :string},
|
||||
|
|
|
|||
7
config/soapbox.exs
Normal file
7
config/soapbox.exs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Soapbox default config overrides
|
||||
# This file gets loaded after config.exs
|
||||
# and before prod.secret.exs
|
||||
use Mix.Config
|
||||
|
||||
# Twitter-like block behavior
|
||||
config :pleroma, :activitypub, blockers_visible: false
|
||||
|
|
@ -206,6 +206,7 @@ config :pleroma, :mrf_user_allowlist, %{
|
|||
### :activitypub
|
||||
* `unfollow_blocked`: Whether blocks result in people getting unfollowed
|
||||
* `outgoing_blocks`: Whether to federate blocks to other instances
|
||||
* `blockers_visible`: Whether a user can see the posts of users who blocked them
|
||||
* `deny_follow_blocked`: Whether to disallow following an account that has blocked the user in question
|
||||
* `sign_object_fetches`: Sign object fetches with HTTP signatures
|
||||
* `authorized_fetch_mode`: Require HTTP signatures for AP fetches
|
||||
|
|
@ -362,6 +363,7 @@ config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Http,
|
|||
* `ignore_hosts`: list of hosts which will be ignored by the metadata parser. For example `["accounts.google.com", "xss.website"]`, defaults to `[]`.
|
||||
* `ignore_tld`: list TLDs (top-level domains) which will ignore for parse metadata. default is ["local", "localdomain", "lan"].
|
||||
* `parsers`: list of Rich Media parsers.
|
||||
* `oembed_providers_enabled`: Embed rich media from a list of known providers. This takes precedence over other parsers.
|
||||
* `failure_backoff`: Amount of milliseconds after request failure, during which the request will not be retried.
|
||||
|
||||
## HTTP server
|
||||
|
|
|
|||
261
docs/installation/ubuntu_en.md
Normal file
261
docs/installation/ubuntu_en.md
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
# Installing Soapbox on Ubuntu
|
||||
|
||||
We recommend installing Soapbox on a **dedicated VPS (virtual private server) running Ubuntu 20.04 LTS**.
|
||||
You should get your VPS up and running before starting this guide.
|
||||
|
||||
Some popular VPS hosting providers include:
|
||||
|
||||
- [DigitalOcean](https://m.do.co/c/84e2ff1e790f) <sup>[referral link]</sup> — easy to use
|
||||
- [Hetzner Cloud](https://www.hetzner.com/cloud) — cheap
|
||||
- [BuyVM](https://buyvm.net/) — supports free speech
|
||||
|
||||
Expect to spend between **$10–15 USD/mo**, depending on the size of your community and how you choose to configure it.
|
||||
|
||||
You should already have a **domain name** from a registrar like [Namecheap](https://www.namecheap.com/) or [Epik](https://www.epik.com/).
|
||||
Create an `A` record with your registrar pointing to the IP address of your VPS.
|
||||
|
||||
## 1. Shelling in
|
||||
|
||||
Once your VPS is running, you'll need to open a **terminal program** on your computer.
|
||||
This will allow you to remotely connect to the server so you can run commands and install Soapbox.
|
||||
|
||||

|
||||
|
||||
Linux and Mac users should have a terminal program pre-installed (it's just called **"Terminal"**), but Windows users may need to install [Cygwin](https://www.cygwin.com/) first.
|
||||
|
||||
Once the terminal is open, connect to your server with the username and IP address provided by your VPS host.
|
||||
It will likely prompt for a password.
|
||||
|
||||
```sh
|
||||
ssh root@123.456.789
|
||||
```
|
||||
|
||||
If you see a screen that looks like this, you've succeeded:
|
||||
|
||||
```
|
||||
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-65-generic x86_64)
|
||||
|
||||
* Documentation: https://help.ubuntu.com
|
||||
* Management: https://landscape.canonical.com
|
||||
* Support: https://ubuntu.com/advantage
|
||||
|
||||
System information as of Wed Apr 28 18:59:27 UTC 2021
|
||||
|
||||
System load: 1.86 Processes: 201
|
||||
Usage of /: 66.1% of 146.15GB Users logged in: 0
|
||||
Memory usage: 29% IPv4 address for ens18: 10.0.0.100
|
||||
Swap usage: 4% IPv4 address for ens19: 192.168.1.100
|
||||
|
||||
* Pure upstream Kubernetes 1.21, smallest, simplest cluster ops!
|
||||
|
||||
https://microk8s.io/
|
||||
|
||||
79 updates can be installed immediately.
|
||||
0 of these updates are security updates.
|
||||
To see these additional updates run: apt list --upgradable
|
||||
|
||||
|
||||
Last login: Tue Apr 27 17:28:56 2021 from 98.198.61.119
|
||||
root@gleasonator:~#
|
||||
```
|
||||
|
||||
## 2. System setup
|
||||
|
||||
Before installing Soapbox, we have to prepare the system.
|
||||
|
||||
### 2.a. Install updates
|
||||
|
||||
Usually a fresh VPS already has outdated software, so run the following commands to update it:
|
||||
|
||||
```shell
|
||||
sudo apt update
|
||||
sudo apt upgrade
|
||||
```
|
||||
|
||||
When prompted (`[Y/n]`) type `Y` and hit Enter.
|
||||
|
||||
### 2.b. Install system dependencies
|
||||
|
||||
Soapbox relies on some additional system software in order to function.
|
||||
Install them with the following command:
|
||||
|
||||
```shell
|
||||
sudo apt install git build-essential postgresql postgresql-contrib cmake libmagic-dev imagemagick ffmpeg libimage-exiftool-perl nginx certbot
|
||||
```
|
||||
|
||||
### 2.c. Install Elixir
|
||||
|
||||
Soapbox uses the Elixir programming language (based on Erlang).
|
||||
Unfortunately the latest version is not included in Ubuntu by default, so we have to add a third-party repository before we can install it.
|
||||
|
||||
To install the Elixir repository, use these commands:
|
||||
|
||||
```shell
|
||||
wget -P /tmp/ https://packages.erlang-solutions.com/erlang-solutions_2.0_all.deb
|
||||
sudo dpkg -i /tmp/erlang-solutions_2.0_all.deb
|
||||
```
|
||||
|
||||
Now we can install Elixir (and Erlang):
|
||||
|
||||
```shell
|
||||
sudo apt update
|
||||
sudo apt install elixir erlang-dev erlang-nox
|
||||
```
|
||||
|
||||
### 2.d. Create the Pleroma user
|
||||
|
||||
For security reasons, it's best to run Soapbox as a separate user with limited access.
|
||||
|
||||
We'll create this user and call it `pleroma`:
|
||||
|
||||
```shell
|
||||
sudo useradd -r -s /bin/false -m -d /var/lib/pleroma -U pleroma
|
||||
```
|
||||
|
||||
## 3. Install Soapbox
|
||||
|
||||
Finally! It's time to install Soapbox itself.
|
||||
Let's get things up and running.
|
||||
|
||||
### 3.a. Downloading the source code
|
||||
|
||||
We'll need to create a folder to hold the Soapbox source code, then download it with git:
|
||||
|
||||
```shell
|
||||
sudo mkdir -p /opt/pleroma
|
||||
sudo chown -R pleroma:pleroma /opt/pleroma
|
||||
sudo -Hu pleroma git clone -b stable https://gitlab.com/soapbox-pub/soapbox /opt/pleroma
|
||||
```
|
||||
|
||||
### 3.b. Install Elixir dependencies
|
||||
|
||||
First let's enter the Soapbox source code directory:
|
||||
|
||||
```shell
|
||||
cd /opt/pleroma
|
||||
```
|
||||
|
||||
Soapbox depends on third-party Elixir modules which need to be downloaded:
|
||||
|
||||
```shell
|
||||
sudo -Hu pleroma mix deps.get
|
||||
```
|
||||
|
||||
If it asks you to install `Hex`, answer `yes`:
|
||||
|
||||
### 3.c. Generate the configuration
|
||||
|
||||
It's time to preconfigure our instance.
|
||||
The following command will set up some basics such as your domain name.
|
||||
|
||||
```sh
|
||||
sudo -Hu pleroma mix pleroma.instance gen
|
||||
```
|
||||
|
||||
* Answer with `yes` if it asks you to install `rebar3`.
|
||||
|
||||
* This may take some time, because parts of pleroma get compiled first.
|
||||
|
||||
* After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
|
||||
|
||||
Check if the configuration looks right.
|
||||
If so, rename it to `prod.secret.exs`:
|
||||
|
||||
```shell
|
||||
sudo -Hu pleroma mv config/{generated_config.exs,prod.secret.exs}
|
||||
```
|
||||
|
||||
### 3.d. Provision the database
|
||||
|
||||
The previous section also created a file called `config/setup_db.psql`, which you can use to create the database:
|
||||
|
||||
```shell
|
||||
sudo -Hu postgres psql -f config/setup_db.psql
|
||||
```
|
||||
|
||||
Now run the database migration:
|
||||
|
||||
```shell
|
||||
sudo -Hu pleroma MIX_ENV=prod mix ecto.migrate
|
||||
```
|
||||
|
||||
### 3.e. Start Soapbox
|
||||
|
||||
Copy the systemd service and enable it to start Soapbox:
|
||||
|
||||
```shell
|
||||
sudo cp /opt/pleroma/installation/pleroma.service /etc/systemd/system/pleroma.service
|
||||
sudo systemctl enable --now pleroma.service
|
||||
```
|
||||
|
||||
If you've made it this far, congrats!
|
||||
You're very close to being done.
|
||||
Your Soapbox server is running, and you just need to make it accessible to the outside world.
|
||||
|
||||
## 4. Getting online
|
||||
|
||||
The last step is to make your server accessible to the outside world.
|
||||
We'll achieve that by installing Nginx and enabling HTTPS support.
|
||||
|
||||
### 4.a. HTTPS
|
||||
|
||||
We'll use certbot to get an SSL certificate.
|
||||
|
||||
First, shut off Nginx:
|
||||
|
||||
```sh
|
||||
systemctl stop nginx
|
||||
```
|
||||
|
||||
Now you can get the certificate:
|
||||
|
||||
```shell
|
||||
sudo mkdir -p /var/lib/letsencrypt/
|
||||
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --standalone
|
||||
```
|
||||
|
||||
Replace `<your@emailaddress>` and `<yourdomain>` with real values.
|
||||
|
||||
### 4.b. Nginx
|
||||
|
||||
Copy the example nginx configuration and activate it:
|
||||
|
||||
```shell
|
||||
sudo cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/sites-available/pleroma.nginx
|
||||
sudo ln -s /etc/nginx/sites-available/pleroma.nginx /etc/nginx/sites-enabled/pleroma.nginx
|
||||
```
|
||||
|
||||
Before starting Nginx again, 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
|
||||
```
|
||||
|
||||
🎉 Congrats, you're done!
|
||||
Check your site in a browser and it should be online.
|
||||
|
||||
## 5. Post-installation
|
||||
|
||||
Below are some additional steps you can take after you've finished installation.
|
||||
|
||||
### Create your first user
|
||||
|
||||
If your instance is up and running, you can create your first user with administrative rights with the following task:
|
||||
|
||||
```shell
|
||||
sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress> --admin
|
||||
```
|
||||
|
||||
### Renewing SSL
|
||||
|
||||
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/
|
||||
```
|
||||
|
||||
## Questions
|
||||
|
||||
If you have questions or run into trouble, please [create an issue](https://gitlab.com/soapbox-pub/soapbox/-/issues) on the Soapbox GitLab.
|
||||
|
|
@ -34,15 +34,16 @@ defmodule Pleroma.ApplicationRequirements do
|
|||
defp check_welcome_message_config!(:ok) do
|
||||
if Pleroma.Config.get([:welcome, :email, :enabled], false) and
|
||||
not Pleroma.Emails.Mailer.enabled?() do
|
||||
Logger.error("""
|
||||
To send welcome email do you need to enable mail.
|
||||
\nconfig :pleroma, Pleroma.Emails.Mailer, enabled: true
|
||||
""")
|
||||
Logger.warn("""
|
||||
To send welcome emails, you need to enable the mailer.
|
||||
Welcome emails will NOT be sent with the current config.
|
||||
|
||||
{:error, "The mail disabled."}
|
||||
else
|
||||
:ok
|
||||
Enable the mailer:
|
||||
config :pleroma, Pleroma.Emails.Mailer, enabled: true
|
||||
""")
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp check_welcome_message_config!(result), do: result
|
||||
|
|
@ -51,18 +52,21 @@ defmodule Pleroma.ApplicationRequirements do
|
|||
#
|
||||
def check_confirmation_accounts!(:ok) do
|
||||
if Pleroma.Config.get([:instance, :account_activation_required]) &&
|
||||
not Pleroma.Config.get([Pleroma.Emails.Mailer, :enabled]) do
|
||||
Logger.error(
|
||||
"Account activation enabled, but no Mailer settings enabled.\n" <>
|
||||
"Please set config :pleroma, :instance, account_activation_required: false\n" <>
|
||||
"Otherwise setup and enable Mailer."
|
||||
)
|
||||
not Pleroma.Emails.Mailer.enabled?() do
|
||||
Logger.warn("""
|
||||
Account activation is required, but the mailer is disabled.
|
||||
Users will NOT be able to confirm their accounts with this config.
|
||||
Either disable account activation or enable the mailer.
|
||||
|
||||
{:error,
|
||||
"Account activation enabled, but Mailer is disabled. Cannot send confirmation emails."}
|
||||
else
|
||||
:ok
|
||||
Disable account activation:
|
||||
config :pleroma, :instance, account_activation_required: false
|
||||
|
||||
Enable the mailer:
|
||||
config :pleroma, Pleroma.Emails.Mailer, enabled: true
|
||||
""")
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def check_confirmation_accounts!(result), do: result
|
||||
|
|
|
|||
|
|
@ -1,256 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
#
|
||||
# This file is derived from Earmark, under the following copyright:
|
||||
# Copyright © 2014 Dave Thomas, The Pragmatic Programmers
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Upstream: https://github.com/pragdave/earmark/blob/master/lib/earmark/html_renderer.ex
|
||||
defmodule Pleroma.EarmarkRenderer do
|
||||
@moduledoc false
|
||||
|
||||
alias Earmark.Block
|
||||
alias Earmark.Context
|
||||
alias Earmark.HtmlRenderer
|
||||
alias Earmark.Options
|
||||
|
||||
import Earmark.Inline, only: [convert: 3]
|
||||
import Earmark.Helpers.HtmlHelpers
|
||||
import Earmark.Message, only: [add_messages_from: 2, get_messages: 1, set_messages: 2]
|
||||
import Earmark.Context, only: [append: 2, set_value: 2]
|
||||
import Earmark.Options, only: [get_mapper: 1]
|
||||
|
||||
@doc false
|
||||
def render(blocks, %Context{options: %Options{}} = context) do
|
||||
messages = get_messages(context)
|
||||
|
||||
{contexts, html} =
|
||||
get_mapper(context.options).(
|
||||
blocks,
|
||||
&render_block(&1, put_in(context.options.messages, []))
|
||||
)
|
||||
|> Enum.unzip()
|
||||
|
||||
all_messages =
|
||||
contexts
|
||||
|> Enum.reduce(messages, fn ctx, messages1 -> messages1 ++ get_messages(ctx) end)
|
||||
|
||||
{put_in(context.options.messages, all_messages), html |> IO.iodata_to_binary()}
|
||||
end
|
||||
|
||||
#############
|
||||
# Paragraph #
|
||||
#############
|
||||
defp render_block(%Block.Para{lnb: lnb, lines: lines, attrs: attrs}, context) do
|
||||
lines = convert(lines, lnb, context)
|
||||
add_attrs(lines, "<p>#{lines.value}</p>", attrs, [], lnb)
|
||||
end
|
||||
|
||||
########
|
||||
# Html #
|
||||
########
|
||||
defp render_block(%Block.Html{html: html}, context) do
|
||||
{context, html}
|
||||
end
|
||||
|
||||
defp render_block(%Block.HtmlComment{lines: lines}, context) do
|
||||
{context, lines}
|
||||
end
|
||||
|
||||
defp render_block(%Block.HtmlOneline{html: html}, context) do
|
||||
{context, html}
|
||||
end
|
||||
|
||||
#########
|
||||
# Ruler #
|
||||
#########
|
||||
defp render_block(%Block.Ruler{lnb: lnb, attrs: attrs}, context) do
|
||||
add_attrs(context, "<hr />", attrs, [], lnb)
|
||||
end
|
||||
|
||||
###########
|
||||
# Heading #
|
||||
###########
|
||||
defp render_block(
|
||||
%Block.Heading{lnb: lnb, level: level, content: content, attrs: attrs},
|
||||
context
|
||||
) do
|
||||
converted = convert(content, lnb, context)
|
||||
html = "<h#{level}>#{converted.value}</h#{level}>"
|
||||
add_attrs(converted, html, attrs, [], lnb)
|
||||
end
|
||||
|
||||
##############
|
||||
# Blockquote #
|
||||
##############
|
||||
|
||||
defp render_block(%Block.BlockQuote{lnb: lnb, blocks: blocks, attrs: attrs}, context) do
|
||||
{context1, body} = render(blocks, context)
|
||||
html = "<blockquote>#{body}</blockquote>"
|
||||
add_attrs(context1, html, attrs, [], lnb)
|
||||
end
|
||||
|
||||
#########
|
||||
# Table #
|
||||
#########
|
||||
|
||||
defp render_block(
|
||||
%Block.Table{lnb: lnb, header: header, rows: rows, alignments: aligns, attrs: attrs},
|
||||
context
|
||||
) do
|
||||
{context1, html} = add_attrs(context, "<table>", attrs, [], lnb)
|
||||
context2 = set_value(context1, html)
|
||||
|
||||
context3 =
|
||||
if header do
|
||||
append(add_trs(append(context2, "<thead>"), [header], "th", aligns, lnb), "</thead>")
|
||||
else
|
||||
# Maybe an error, needed append(context, html)
|
||||
context2
|
||||
end
|
||||
|
||||
context4 = append(add_trs(append(context3, "<tbody>"), rows, "td", aligns, lnb), "</tbody>")
|
||||
|
||||
{context4, [context4.value, "</table>"]}
|
||||
end
|
||||
|
||||
########
|
||||
# Code #
|
||||
########
|
||||
|
||||
defp render_block(
|
||||
%Block.Code{lnb: lnb, language: language, attrs: attrs} = block,
|
||||
%Context{options: options} = context
|
||||
) do
|
||||
class =
|
||||
if language, do: ~s{ class="#{code_classes(language, options.code_class_prefix)}"}, else: ""
|
||||
|
||||
tag = ~s[<pre><code#{class}>]
|
||||
lines = options.render_code.(block)
|
||||
html = ~s[#{tag}#{lines}</code></pre>]
|
||||
add_attrs(context, html, attrs, [], lnb)
|
||||
end
|
||||
|
||||
#########
|
||||
# Lists #
|
||||
#########
|
||||
|
||||
defp render_block(
|
||||
%Block.List{lnb: lnb, type: type, blocks: items, attrs: attrs, start: start},
|
||||
context
|
||||
) do
|
||||
{context1, content} = render(items, context)
|
||||
html = "<#{type}#{start}>#{content}</#{type}>"
|
||||
add_attrs(context1, html, attrs, [], lnb)
|
||||
end
|
||||
|
||||
# format a single paragraph list item, and remove the para tags
|
||||
defp render_block(
|
||||
%Block.ListItem{lnb: lnb, blocks: blocks, spaced: false, attrs: attrs},
|
||||
context
|
||||
)
|
||||
when length(blocks) == 1 do
|
||||
{context1, content} = render(blocks, context)
|
||||
content = Regex.replace(~r{</?p>}, content, "")
|
||||
html = "<li>#{content}</li>"
|
||||
add_attrs(context1, html, attrs, [], lnb)
|
||||
end
|
||||
|
||||
# format a spaced list item
|
||||
defp render_block(%Block.ListItem{lnb: lnb, blocks: blocks, attrs: attrs}, context) do
|
||||
{context1, content} = render(blocks, context)
|
||||
html = "<li>#{content}</li>"
|
||||
add_attrs(context1, html, attrs, [], lnb)
|
||||
end
|
||||
|
||||
##################
|
||||
# Footnote Block #
|
||||
##################
|
||||
|
||||
defp render_block(%Block.FnList{blocks: footnotes}, context) do
|
||||
items =
|
||||
Enum.map(footnotes, fn note ->
|
||||
blocks = append_footnote_link(note)
|
||||
%Block.ListItem{attrs: "#fn:#{note.number}", type: :ol, blocks: blocks}
|
||||
end)
|
||||
|
||||
{context1, html} = render_block(%Block.List{type: :ol, blocks: items}, context)
|
||||
{context1, Enum.join([~s[<div class="footnotes">], "<hr />", html, "</div>"])}
|
||||
end
|
||||
|
||||
#######################################
|
||||
# Isolated IALs are rendered as paras #
|
||||
#######################################
|
||||
|
||||
defp render_block(%Block.Ial{verbatim: verbatim}, context) do
|
||||
{context, "<p>{:#{verbatim}}</p>"}
|
||||
end
|
||||
|
||||
####################
|
||||
# IDDef is ignored #
|
||||
####################
|
||||
|
||||
defp render_block(%Block.IdDef{}, context), do: {context, ""}
|
||||
|
||||
#####################################
|
||||
# And here are the inline renderers #
|
||||
#####################################
|
||||
|
||||
defdelegate br, to: HtmlRenderer
|
||||
defdelegate codespan(text), to: HtmlRenderer
|
||||
defdelegate em(text), to: HtmlRenderer
|
||||
defdelegate strong(text), to: HtmlRenderer
|
||||
defdelegate strikethrough(text), to: HtmlRenderer
|
||||
|
||||
defdelegate link(url, text), to: HtmlRenderer
|
||||
defdelegate link(url, text, title), to: HtmlRenderer
|
||||
|
||||
defdelegate image(path, alt, title), to: HtmlRenderer
|
||||
|
||||
defdelegate footnote_link(ref, backref, number), to: HtmlRenderer
|
||||
|
||||
# Table rows
|
||||
defp add_trs(context, rows, tag, aligns, lnb) do
|
||||
numbered_rows =
|
||||
rows
|
||||
|> Enum.zip(Stream.iterate(lnb, &(&1 + 1)))
|
||||
|
||||
numbered_rows
|
||||
|> Enum.reduce(context, fn {row, lnb}, ctx ->
|
||||
append(add_tds(append(ctx, "<tr>"), row, tag, aligns, lnb), "</tr>")
|
||||
end)
|
||||
end
|
||||
|
||||
defp add_tds(context, row, tag, aligns, lnb) do
|
||||
Enum.reduce(1..length(row), context, add_td_fn(row, tag, aligns, lnb))
|
||||
end
|
||||
|
||||
defp add_td_fn(row, tag, aligns, lnb) do
|
||||
fn n, ctx ->
|
||||
style =
|
||||
case Enum.at(aligns, n - 1, :default) do
|
||||
:default -> ""
|
||||
align -> " style=\"text-align: #{align}\""
|
||||
end
|
||||
|
||||
col = Enum.at(row, n - 1)
|
||||
converted = convert(col, lnb, set_messages(ctx, []))
|
||||
append(add_messages_from(ctx, converted), "<#{tag}#{style}>#{converted.value}</#{tag}>")
|
||||
end
|
||||
end
|
||||
|
||||
###############################
|
||||
# Append Footnote Return Link #
|
||||
###############################
|
||||
|
||||
defdelegate append_footnote_link(note), to: HtmlRenderer
|
||||
defdelegate append_footnote_link(note, fnlink), to: HtmlRenderer
|
||||
|
||||
defdelegate render_code(lines), to: HtmlRenderer
|
||||
|
||||
defp code_classes(language, prefix) do
|
||||
["" | String.split(prefix || "")]
|
||||
|> Enum.map(fn pfx -> "#{pfx}#{language}" end)
|
||||
|> Enum.join(" ")
|
||||
end
|
||||
end
|
||||
|
|
@ -121,6 +121,10 @@ defmodule Pleroma.Formatter do
|
|||
end
|
||||
end
|
||||
|
||||
def markdown_to_html(text) do
|
||||
Earmark.as_html!(text, %Earmark.Options{compact_output: true})
|
||||
end
|
||||
|
||||
def html_escape({text, mentions, hashtags}, type) do
|
||||
{html_escape(text, type), mentions, hashtags}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -127,6 +127,7 @@ defmodule Pleroma.Notification do
|
|||
|> where([user_actor: user_actor], user_actor.is_active)
|
||||
|> exclude_notification_muted(user, exclude_notification_muted_opts)
|
||||
|> exclude_blocked(user, exclude_blocked_opts)
|
||||
|> exclude_blockers(user)
|
||||
|> exclude_filtered(user)
|
||||
|> exclude_visibility(opts)
|
||||
end
|
||||
|
|
@ -140,6 +141,17 @@ defmodule Pleroma.Notification do
|
|||
|> FollowingRelationship.keep_following_or_not_domain_blocked(user)
|
||||
end
|
||||
|
||||
defp exclude_blockers(query, user) do
|
||||
if Pleroma.Config.get([:activitypub, :blockers_visible]) == true do
|
||||
query
|
||||
else
|
||||
blocker_ap_ids = User.incoming_relationships_ungrouped_ap_ids(user, [:block])
|
||||
|
||||
query
|
||||
|> where([n, a], a.actor not in ^blocker_ap_ids)
|
||||
end
|
||||
end
|
||||
|
||||
defp exclude_notification_muted(query, _, %{@include_muted_option => true}) do
|
||||
query
|
||||
end
|
||||
|
|
|
|||
|
|
@ -430,6 +430,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|> maybe_preload_bookmarks(opts)
|
||||
|> maybe_set_thread_muted_field(opts)
|
||||
|> restrict_blocked(opts)
|
||||
|> restrict_blockers_visibility(opts)
|
||||
|> restrict_recipients(recipients, opts[:user])
|
||||
|> restrict_filtered(opts)
|
||||
|> where(
|
||||
|
|
@ -908,7 +909,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|
||||
from(
|
||||
[activity, object: o] in query,
|
||||
# You don't block the author
|
||||
where: fragment("not (? = ANY(?))", activity.actor, ^blocked_ap_ids),
|
||||
|
||||
# You don't block any recipients, and didn't author the post
|
||||
where:
|
||||
fragment(
|
||||
"((not (? && ?)) or ? = ?)",
|
||||
|
|
@ -917,12 +921,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
activity.actor,
|
||||
^user.ap_id
|
||||
),
|
||||
|
||||
# You don't block the domain of any recipients, and didn't author the post
|
||||
where:
|
||||
fragment(
|
||||
"recipients_contain_blocked_domains(?, ?) = false",
|
||||
"(recipients_contain_blocked_domains(?, ?) = false) or ? = ?",
|
||||
activity.recipients,
|
||||
^domain_blocks
|
||||
^domain_blocks,
|
||||
activity.actor,
|
||||
^user.ap_id
|
||||
),
|
||||
|
||||
# It's not a boost of a user you block
|
||||
where:
|
||||
fragment(
|
||||
"not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
|
||||
|
|
@ -930,6 +940,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
activity.data,
|
||||
^blocked_ap_ids
|
||||
),
|
||||
|
||||
# You don't block the author's domain, and also don't follow the author
|
||||
where:
|
||||
fragment(
|
||||
"(not (split_part(?, '/', 3) = ANY(?))) or ? = ANY(?)",
|
||||
|
|
@ -938,6 +950,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
activity.actor,
|
||||
^following_ap_ids
|
||||
),
|
||||
|
||||
# Same as above, but checks the Object
|
||||
where:
|
||||
fragment(
|
||||
"(not (split_part(?->>'actor', '/', 3) = ANY(?))) or (?->>'actor') = ANY(?)",
|
||||
|
|
@ -951,6 +965,31 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|
||||
defp restrict_blocked(query, _), do: query
|
||||
|
||||
defp restrict_blockers_visibility(query, %{blocking_user: %User{} = user}) do
|
||||
if Config.get([:activitypub, :blockers_visible]) == true do
|
||||
query
|
||||
else
|
||||
blocker_ap_ids = User.incoming_relationships_ungrouped_ap_ids(user, [:block])
|
||||
|
||||
from(
|
||||
activity in query,
|
||||
# The author doesn't block you
|
||||
where: fragment("not (? = ANY(?))", activity.actor, ^blocker_ap_ids),
|
||||
|
||||
# It's not a boost of a user that blocks you
|
||||
where:
|
||||
fragment(
|
||||
"not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
|
||||
activity.data,
|
||||
activity.data,
|
||||
^blocker_ap_ids
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defp restrict_blockers_visibility(query, _), do: query
|
||||
|
||||
defp restrict_unlisted(query, %{restrict_unlisted: true}) do
|
||||
from(
|
||||
activity in query,
|
||||
|
|
@ -1147,6 +1186,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|> restrict_state(opts)
|
||||
|> restrict_favorited_by(opts)
|
||||
|> restrict_blocked(restrict_blocked_opts)
|
||||
|> restrict_blockers_visibility(opts)
|
||||
|> restrict_muted(restrict_muted_opts)
|
||||
|> restrict_filtered(opts)
|
||||
|> restrict_media(opts)
|
||||
|
|
|
|||
|
|
@ -181,6 +181,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
|||
|
||||
defp check_banner_removal(_actor_info, object), do: {:ok, object}
|
||||
|
||||
defp check_object(%{"object" => object} = activity) do
|
||||
with {:ok, _object} <- filter(object) do
|
||||
{:ok, activity}
|
||||
end
|
||||
end
|
||||
|
||||
defp check_object(object), do: {:ok, object}
|
||||
|
||||
@impl true
|
||||
def filter(%{"type" => "Delete", "actor" => actor} = object) do
|
||||
%{host: actor_host} = URI.parse(actor)
|
||||
|
|
@ -206,7 +214,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
|||
{:ok, object} <- check_media_nsfw(actor_info, object),
|
||||
{:ok, object} <- check_ftl_removal(actor_info, object),
|
||||
{:ok, object} <- check_followers_only(actor_info, object),
|
||||
{:ok, object} <- check_report_removal(actor_info, object) do
|
||||
{:ok, object} <- check_report_removal(actor_info, object),
|
||||
{:ok, object} <- check_object(object) do
|
||||
{:ok, object}
|
||||
else
|
||||
{:reject, nil} -> {:reject, "[SimplePolicy]"}
|
||||
|
|
@ -231,6 +240,19 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
|||
end
|
||||
end
|
||||
|
||||
def filter(object) when is_binary(object) do
|
||||
uri = URI.parse(object)
|
||||
|
||||
with {:ok, object} <- check_accept(uri, object),
|
||||
{:ok, object} <- check_reject(uri, object) do
|
||||
{:ok, object}
|
||||
else
|
||||
{:reject, nil} -> {:reject, "[SimplePolicy]"}
|
||||
{:reject, _} = e -> e
|
||||
_ -> {:reject, "[SimplePolicy]"}
|
||||
end
|
||||
end
|
||||
|
||||
def filter(object), do: {:ok, object}
|
||||
|
||||
@impl true
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.EarmarkRenderer
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
||||
|
|
@ -110,7 +109,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
|
|||
when is_binary(content) do
|
||||
content =
|
||||
content
|
||||
|> Earmark.as_html!(%Earmark.Options{renderer: EarmarkRenderer})
|
||||
|> Pleroma.Formatter.markdown_to_html()
|
||||
|> Pleroma.HTML.filter_tags()
|
||||
|
||||
Map.put(data, "content", content)
|
||||
|
|
|
|||
|
|
@ -294,7 +294,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
def format_input(text, "text/markdown", options) do
|
||||
text
|
||||
|> Formatter.mentions_escape(options)
|
||||
|> Earmark.as_html!(%Earmark.Options{renderer: Pleroma.EarmarkRenderer})
|
||||
|> Formatter.markdown_to_html()
|
||||
|> Formatter.linkify(options)
|
||||
|> Formatter.html_escape("text/html")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -312,7 +312,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
with %Activity{} = activity <- Activity.get_by_id(status_id),
|
||||
true <- Visibility.visible_for_user?(activity, user) do
|
||||
data = Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
|
||||
render(conn, "card.json", data)
|
||||
render(conn, "card.json", %{embed: data})
|
||||
else
|
||||
_ -> render_error(conn, :not_found, "Record not found")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -47,6 +47,9 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
|
|||
},
|
||||
stats: %{mau: Pleroma.User.active_user_count()},
|
||||
vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
|
||||
},
|
||||
soapbox: %{
|
||||
version: Soapbox.version()
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
alias Pleroma.Web.MediaProxy
|
||||
alias Pleroma.Web.PleromaAPI.EmojiReactionController
|
||||
alias Pleroma.Web.RichMedia.Parser.Card
|
||||
alias Pleroma.Web.RichMedia.Parser.Embed
|
||||
|
||||
import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1, visible_for_user?: 2]
|
||||
|
||||
|
|
@ -269,7 +271,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
|
||||
summary = object.data["summary"] || ""
|
||||
|
||||
card = render("card.json", Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity))
|
||||
card =
|
||||
render("card.json", %{
|
||||
embed: Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
|
||||
})
|
||||
|
||||
url =
|
||||
if user.local do
|
||||
|
|
@ -367,38 +372,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
nil
|
||||
end
|
||||
|
||||
def render("card.json", %{rich_media: rich_media, page_url: page_url}) do
|
||||
page_url_data = URI.parse(page_url)
|
||||
|
||||
page_url_data =
|
||||
if is_binary(rich_media["url"]) do
|
||||
URI.merge(page_url_data, URI.parse(rich_media["url"]))
|
||||
else
|
||||
page_url_data
|
||||
end
|
||||
|
||||
page_url = page_url_data |> to_string
|
||||
|
||||
image_url =
|
||||
if is_binary(rich_media["image"]) do
|
||||
URI.merge(page_url_data, URI.parse(rich_media["image"]))
|
||||
|> to_string
|
||||
end
|
||||
|
||||
%{
|
||||
type: "link",
|
||||
provider_name: page_url_data.host,
|
||||
provider_url: page_url_data.scheme <> "://" <> page_url_data.host,
|
||||
url: page_url,
|
||||
image: image_url |> MediaProxy.url(),
|
||||
title: rich_media["title"] || "",
|
||||
description: rich_media["description"] || "",
|
||||
pleroma: %{
|
||||
opengraph: rich_media
|
||||
}
|
||||
}
|
||||
def render("card.json", %{embed: %Embed{} = embed}) do
|
||||
with {:ok, %Card{} = card} <- Card.parse(embed) do
|
||||
Card.to_map(card)
|
||||
else
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def render("card.json", %{embed: %Card{} = card}), do: Card.to_map(card)
|
||||
def render("card.json", _), do: nil
|
||||
|
||||
def render("attachment.json", %{attachment: attachment}) do
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceView do
|
|||
card:
|
||||
StatusView.render(
|
||||
"card.json",
|
||||
Pleroma.Web.RichMedia.Helpers.fetch_data_for_object(object)
|
||||
%{embed: Pleroma.Web.RichMedia.Helpers.fetch_data_for_object(object)}
|
||||
)
|
||||
}
|
||||
|> put_idempotency_key()
|
||||
|
|
|
|||
|
|
@ -79,7 +79,8 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlug do
|
|||
"frame-ancestors 'none'",
|
||||
"style-src 'self' 'unsafe-inline'",
|
||||
"font-src 'self'",
|
||||
"manifest-src 'self'"
|
||||
"manifest-src 'self'",
|
||||
"frame-src 'self' https:"
|
||||
]
|
||||
|
||||
@csp_start [Enum.join(static_csp_rules, ";") <> ";"]
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Web.RichMedia.Helpers do
|
|||
alias Pleroma.HTML
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Web.RichMedia.Parser
|
||||
alias Pleroma.Web.RichMedia.Parser.Embed
|
||||
|
||||
@options [
|
||||
pool: :media,
|
||||
|
|
@ -15,6 +16,8 @@ defmodule Pleroma.Web.RichMedia.Helpers do
|
|||
recv_timeout: 2_000
|
||||
]
|
||||
|
||||
@headers [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}]
|
||||
|
||||
@spec validate_page_url(URI.t() | binary()) :: :ok | :error
|
||||
defp validate_page_url(page_url) when is_binary(page_url) do
|
||||
validate_tld = Config.get([Pleroma.Formatter, :validate_tld])
|
||||
|
|
@ -60,10 +63,10 @@ defmodule Pleroma.Web.RichMedia.Helpers do
|
|||
{:ok, page_url} <-
|
||||
HTML.extract_first_external_url_from_object(object),
|
||||
:ok <- validate_page_url(page_url),
|
||||
{:ok, rich_media} <- Parser.parse(page_url) do
|
||||
%{page_url: page_url, rich_media: rich_media}
|
||||
{:ok, %Embed{} = embed} <- Parser.parse(page_url) do
|
||||
embed
|
||||
else
|
||||
_ -> %{}
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -72,14 +75,18 @@ defmodule Pleroma.Web.RichMedia.Helpers do
|
|||
%Object{} = object <- Object.normalize(activity, fetch: false) do
|
||||
fetch_data_for_object(object)
|
||||
else
|
||||
_ -> %{}
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_data_for_activity(_), do: %{}
|
||||
|
||||
def oembed_get(url) do
|
||||
Pleroma.HTTP.get(url, @headers, @options)
|
||||
end
|
||||
|
||||
def rich_media_get(url) do
|
||||
headers = [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}]
|
||||
headers = @headers
|
||||
|
||||
head_check =
|
||||
case Pleroma.HTTP.head(url, headers, @options) do
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
defmodule Pleroma.Web.RichMedia.Parser do
|
||||
require Logger
|
||||
alias Pleroma.Web.RichMedia.Parser.Card
|
||||
alias Pleroma.Web.RichMedia.Parser.Embed
|
||||
|
||||
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
|
||||
|
||||
|
|
@ -131,39 +133,42 @@ defmodule Pleroma.Web.RichMedia.Parser do
|
|||
end
|
||||
|
||||
def parse_url(url) do
|
||||
with {:ok, %Tesla.Env{body: html}} <- Pleroma.Web.RichMedia.Helpers.rich_media_get(url),
|
||||
{:ok, html} <- Floki.parse_document(html) do
|
||||
html
|
||||
|> maybe_parse()
|
||||
|> Map.put("url", url)
|
||||
|> clean_parsed_data()
|
||||
|> check_parsed_data()
|
||||
case maybe_fetch_oembed(url) do
|
||||
{:ok, %Embed{} = embed} -> {:ok, embed}
|
||||
_ -> fetch_document(url)
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_parse(html) do
|
||||
Enum.reduce_while(parsers(), %{}, fn parser, acc ->
|
||||
case parser.parse(html, acc) do
|
||||
data when data != %{} -> {:halt, data}
|
||||
_ -> {:cont, acc}
|
||||
end
|
||||
defp maybe_fetch_oembed(url) do
|
||||
with true <- Pleroma.Config.get([:rich_media, :oembed_providers_enabled]),
|
||||
{:ok, oembed_url} <- OEmbedProviders.oembed_url(url),
|
||||
{:ok, %Tesla.Env{body: json}} <-
|
||||
Pleroma.Web.RichMedia.Helpers.oembed_get(oembed_url),
|
||||
{:ok, data} <- Jason.decode(json),
|
||||
embed <- %Embed{url: url, oembed: data},
|
||||
{:ok, %Card{}} <- Card.validate(embed) do
|
||||
{:ok, embed}
|
||||
else
|
||||
{:error, error} -> {:error, error}
|
||||
error -> {:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
defp fetch_document(url) do
|
||||
with {:ok, %Tesla.Env{body: html}} <- Pleroma.Web.RichMedia.Helpers.rich_media_get(url),
|
||||
{:ok, html} <- Floki.parse_document(html),
|
||||
%Embed{} = embed <- parse_embed(html, url),
|
||||
{:ok, %Card{}} <- Card.validate(embed) do
|
||||
{:ok, embed}
|
||||
else
|
||||
{:error, error} -> {:error, error}
|
||||
error -> {:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_embed(html, url) do
|
||||
Enum.reduce(parsers(), %Embed{url: url}, fn parser, acc ->
|
||||
parser.parse(html, acc)
|
||||
end)
|
||||
end
|
||||
|
||||
defp check_parsed_data(%{"title" => title} = data)
|
||||
when is_binary(title) and title != "" do
|
||||
{:ok, data}
|
||||
end
|
||||
|
||||
defp check_parsed_data(data) do
|
||||
{:error, {:invalid_metadata, data}}
|
||||
end
|
||||
|
||||
defp clean_parsed_data(data) do
|
||||
data
|
||||
|> Enum.reject(fn {key, val} ->
|
||||
not match?({:ok, _}, Jason.encode(%{key => val}))
|
||||
end)
|
||||
|> Map.new()
|
||||
end
|
||||
end
|
||||
|
|
|
|||
136
lib/pleroma/web/rich_media/parser/card.ex
Normal file
136
lib/pleroma/web/rich_media/parser/card.ex
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.RichMedia.Parser.Card do
|
||||
alias Pleroma.Web.RichMedia.Parser.Card
|
||||
alias Pleroma.Web.RichMedia.Parser.Embed
|
||||
|
||||
@types ["link", "photo", "video", "rich"]
|
||||
|
||||
# https://docs.joinmastodon.org/entities/card/
|
||||
defstruct url: nil,
|
||||
title: nil,
|
||||
description: "",
|
||||
type: "link",
|
||||
author_name: "",
|
||||
author_url: "",
|
||||
provider_name: "",
|
||||
provider_url: "",
|
||||
html: "",
|
||||
width: 0,
|
||||
height: 0,
|
||||
image: nil,
|
||||
embed_url: "",
|
||||
blurhash: nil
|
||||
|
||||
def parse(%Embed{url: url, oembed: %{"type" => type, "title" => title} = oembed} = embed)
|
||||
when type in @types and is_binary(url) do
|
||||
uri = URI.parse(url)
|
||||
|
||||
%Card{
|
||||
url: url,
|
||||
title: title,
|
||||
description: get_description(embed),
|
||||
type: oembed["type"],
|
||||
author_name: oembed["author_name"],
|
||||
author_url: oembed["author_url"],
|
||||
provider_name: oembed["provider_name"] || uri.host,
|
||||
provider_url: oembed["provider_url"] || "#{uri.scheme}://#{uri.host}",
|
||||
html: sanitize_html(oembed["html"]),
|
||||
width: oembed["width"],
|
||||
height: oembed["height"],
|
||||
image: get_image(oembed) |> proxy(),
|
||||
embed_url: oembed["url"] |> proxy()
|
||||
}
|
||||
|> validate()
|
||||
end
|
||||
|
||||
def parse(%Embed{url: url} = embed) when is_binary(url) do
|
||||
uri = URI.parse(url)
|
||||
|
||||
%Card{
|
||||
url: url,
|
||||
title: get_title(embed),
|
||||
description: get_description(embed),
|
||||
type: "link",
|
||||
provider_name: uri.host,
|
||||
provider_url: "#{uri.scheme}://#{uri.host}",
|
||||
image: get_image(embed) |> proxy()
|
||||
}
|
||||
|> validate()
|
||||
end
|
||||
|
||||
def parse(card), do: {:error, {:invalid_metadata, card}}
|
||||
|
||||
defp get_title(embed) do
|
||||
case embed do
|
||||
%{meta: %{"twitter:title" => title}} when is_binary(title) and title != "" -> title
|
||||
%{meta: %{"og:title" => title}} when is_binary(title) and title != "" -> title
|
||||
%{title: title} when is_binary(title) and title != "" -> title
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp get_description(%{meta: meta}) do
|
||||
case meta do
|
||||
%{"twitter:description" => desc} when is_binary(desc) and desc != "" -> desc
|
||||
%{"og:description" => desc} when is_binary(desc) and desc != "" -> desc
|
||||
%{"description" => desc} when is_binary(desc) and desc != "" -> desc
|
||||
_ -> ""
|
||||
end
|
||||
end
|
||||
|
||||
defp get_image(%{meta: meta}) do
|
||||
case meta do
|
||||
%{"twitter:image" => image} when is_binary(image) and image != "" -> image
|
||||
%{"og:image" => image} when is_binary(image) and image != "" -> image
|
||||
_ -> ""
|
||||
end
|
||||
end
|
||||
|
||||
defp get_image(%{"thumbnail_url" => image}) when is_binary(image) and image != "", do: image
|
||||
defp get_image(%{"type" => "photo", "url" => image}), do: image
|
||||
defp get_image(_), do: ""
|
||||
|
||||
defp sanitize_html(html) do
|
||||
with {:ok, html} <- FastSanitize.Sanitizer.scrub(html, Pleroma.HTML.Scrubber.OEmbed),
|
||||
{:ok, [{"iframe", _, _}]} <- Floki.parse_fragment(html) do
|
||||
html
|
||||
else
|
||||
_ -> ""
|
||||
end
|
||||
end
|
||||
|
||||
def to_map(%Card{} = card) do
|
||||
card
|
||||
|> Map.from_struct()
|
||||
|> stringify_keys()
|
||||
end
|
||||
|
||||
def to_map(%{} = card), do: stringify_keys(card)
|
||||
|
||||
defp stringify_keys(%{} = map), do: Map.new(map, fn {k, v} -> {Atom.to_string(k), v} end)
|
||||
|
||||
defp proxy(url) when is_binary(url), do: Pleroma.Web.MediaProxy.url(url)
|
||||
defp proxy(_), do: nil
|
||||
|
||||
def validate(%Card{type: type, html: html} = card)
|
||||
when type in ["video", "rich"] and (is_binary(html) == false or html == "") do
|
||||
{:error, {:invalid_metadata, card}}
|
||||
end
|
||||
|
||||
def validate(%Card{type: type, title: title} = card)
|
||||
when type in @types and is_binary(title) and title != "" do
|
||||
{:ok, card}
|
||||
end
|
||||
|
||||
def validate(%Embed{} = embed) do
|
||||
case Card.parse(embed) do
|
||||
{:ok, %Card{} = card} -> validate(card)
|
||||
card -> {:error, {:invalid_metadata, card}}
|
||||
end
|
||||
end
|
||||
|
||||
def validate(card), do: {:error, {:invalid_metadata, card}}
|
||||
end
|
||||
10
lib/pleroma/web/rich_media/parser/embed.ex
Normal file
10
lib/pleroma/web/rich_media/parser/embed.ex
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.RichMedia.Parser.Embed do
|
||||
@moduledoc """
|
||||
Represents embedded content, including scraped markup and OEmbed.
|
||||
"""
|
||||
defstruct url: nil, title: nil, meta: nil, oembed: nil
|
||||
end
|
||||
39
lib/pleroma/web/rich_media/parser/meta_tags.ex
Normal file
39
lib/pleroma/web/rich_media/parser/meta_tags.ex
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.RichMedia.Parser.MetaTags do
|
||||
@doc """
|
||||
Parses a `Floki.html_tree/0` and returns a map of raw `<meta>` tag values.
|
||||
"""
|
||||
@spec parse(html_tree :: Floki.html_tree()) :: map()
|
||||
def parse(html_tree) do
|
||||
html_tree
|
||||
|> Floki.find("meta")
|
||||
|> Enum.reduce(%{}, fn html_node, acc ->
|
||||
case parse_node(html_node) do
|
||||
{:ok, {name, content}} -> Map.put(acc, name, content)
|
||||
_ -> acc
|
||||
end
|
||||
end)
|
||||
|> clean_data()
|
||||
end
|
||||
|
||||
defp parse_node({_tag, attrs, _children}) when is_list(attrs) do
|
||||
case Map.new(attrs) do
|
||||
%{"name" => name, "content" => content} -> {:ok, {name, content}}
|
||||
%{"property" => name, "content" => content} -> {:ok, {name, content}}
|
||||
_ -> {:error, :invalid_meta_tag}
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_node(_), do: {:error, :invalid_meta_tag}
|
||||
|
||||
defp clean_data(data) do
|
||||
data
|
||||
|> Enum.reject(fn {key, val} ->
|
||||
not match?({:ok, _}, Jason.encode(%{key => val}))
|
||||
end)
|
||||
|> Map.new()
|
||||
end
|
||||
end
|
||||
|
|
@ -1,46 +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.Web.RichMedia.Parsers.MetaTagsParser do
|
||||
def parse(data, html, prefix, key_name, value_name \\ "content") do
|
||||
html
|
||||
|> get_elements(key_name, prefix)
|
||||
|> Enum.reduce(data, fn el, acc ->
|
||||
attributes = normalize_attributes(el, prefix, key_name, value_name)
|
||||
|
||||
Map.merge(acc, attributes)
|
||||
end)
|
||||
|> maybe_put_title(html)
|
||||
end
|
||||
|
||||
defp get_elements(html, key_name, prefix) do
|
||||
html |> Floki.find("meta[#{key_name}^='#{prefix}:']")
|
||||
end
|
||||
|
||||
defp normalize_attributes(html_node, prefix, key_name, value_name) do
|
||||
{_tag, attributes, _children} = html_node
|
||||
|
||||
data =
|
||||
Map.new(attributes, fn {name, value} ->
|
||||
{name, String.trim_leading(value, "#{prefix}:")}
|
||||
end)
|
||||
|
||||
%{data[key_name] => data[value_name]}
|
||||
end
|
||||
|
||||
defp maybe_put_title(%{"title" => _} = meta, _), do: meta
|
||||
|
||||
defp maybe_put_title(meta, html) when meta != %{} do
|
||||
case get_page_title(html) do
|
||||
"" -> meta
|
||||
title -> Map.put_new(meta, "title", title)
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_put_title(meta, _), do: meta
|
||||
|
||||
defp get_page_title(html) do
|
||||
Floki.find(html, "html head title") |> List.first() |> Floki.text()
|
||||
end
|
||||
end
|
||||
|
|
@ -3,13 +3,13 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.RichMedia.Parsers.OEmbed do
|
||||
def parse(html, _data) do
|
||||
def parse(html, data) do
|
||||
with elements = [_ | _] <- get_discovery_data(html),
|
||||
oembed_url when is_binary(oembed_url) <- get_oembed_url(elements),
|
||||
{:ok, oembed_data} <- get_oembed_data(oembed_url) do
|
||||
oembed_data
|
||||
Map.put(data, :oembed, oembed_data)
|
||||
else
|
||||
_e -> %{}
|
||||
_e -> data
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -22,7 +22,7 @@ defmodule Pleroma.Web.RichMedia.Parsers.OEmbed do
|
|||
end
|
||||
|
||||
defp get_oembed_data(url) do
|
||||
with {:ok, %Tesla.Env{body: json}} <- Pleroma.Web.RichMedia.Helpers.rich_media_get(url) do
|
||||
with {:ok, %Tesla.Env{body: json}} <- Pleroma.Web.RichMedia.Helpers.oembed_get(url) do
|
||||
Jason.decode(json)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,7 +4,5 @@
|
|||
|
||||
defmodule Pleroma.Web.RichMedia.Parsers.OGP do
|
||||
@deprecated "OGP parser is deprecated. Use TwitterCard instead."
|
||||
def parse(_html, _data) do
|
||||
%{}
|
||||
end
|
||||
def parse(_html, data), do: data
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,13 +3,22 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.RichMedia.Parsers.TwitterCard do
|
||||
alias Pleroma.Web.RichMedia.Parsers.MetaTagsParser
|
||||
alias Pleroma.Web.RichMedia.Parser.MetaTags
|
||||
|
||||
@spec parse(list(), map()) :: map()
|
||||
@spec parse(Floki.html_tree(), map()) :: map()
|
||||
def parse(html, data) do
|
||||
data
|
||||
|> MetaTagsParser.parse(html, "og", "property")
|
||||
|> MetaTagsParser.parse(html, "twitter", "name")
|
||||
|> MetaTagsParser.parse(html, "twitter", "property")
|
||||
|> Map.put(:title, get_page_title(html))
|
||||
|> Map.put(:meta, MetaTags.parse(html))
|
||||
end
|
||||
|
||||
def get_page_title(html) do
|
||||
with [node | _] <- Floki.find(html, "html head title"),
|
||||
title when is_binary(title) and title != "" <- Floki.text(node),
|
||||
true <- String.valid?(title) do
|
||||
title
|
||||
else
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
5
lib/soapbox.ex
Normal file
5
lib/soapbox.ex
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
defmodule Soapbox do
|
||||
@version "0.0.99"
|
||||
|
||||
def version, do: @version
|
||||
end
|
||||
3
mix.exs
3
mix.exs
|
|
@ -143,7 +143,7 @@ defmodule Pleroma.Mixfile do
|
|||
{:ex_aws, "~> 2.1.6"},
|
||||
{:ex_aws_s3, "~> 2.0"},
|
||||
{:sweet_xml, "~> 0.6.6"},
|
||||
{:earmark, "1.4.3"},
|
||||
{:earmark, "1.4.15"},
|
||||
{:bbcode_pleroma, "~> 0.2.0"},
|
||||
{:crypt,
|
||||
git: "https://git.pleroma.social/pleroma/elixir-libraries/crypt.git",
|
||||
|
|
@ -198,6 +198,7 @@ defmodule Pleroma.Mixfile do
|
|||
{:open_api_spex,
|
||||
git: "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git",
|
||||
ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"},
|
||||
{:oembed_providers, "~> 0.1.0"},
|
||||
|
||||
## dev & test
|
||||
{:ex_doc, "~> 0.22", only: :dev, runtime: false},
|
||||
|
|
|
|||
6
mix.lock
6
mix.lock
|
|
@ -27,8 +27,8 @@
|
|||
"db_connection": {:hex, :db_connection, "2.3.1", "4c9f3ed1ef37471cbdd2762d6655be11e38193904d9c5c1c9389f1b891a3088e", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "abaab61780dde30301d840417890bd9f74131041afd02174cf4e10635b3a63f5"},
|
||||
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
|
||||
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
|
||||
"earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.10", "6603d7a603b9c18d3d20db69921527f82ef09990885ed7525003c7fe7dc86c56", [:mix], [], "hexpm", "8e2d5370b732385db2c9b22215c3f59c84ac7dda7ed7e544d7c459496ae519c0"},
|
||||
"earmark": {:hex, :earmark, "1.4.15", "2c7f924bf495ec1f65bd144b355d0949a05a254d0ec561740308a54946a67888", [:mix], [{:earmark_parser, ">= 1.4.13", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "3b1209b85bc9f3586f370f7c363f6533788fb4e51db23aa79565875e7f9999ee"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.13", "0c98163e7d04a15feb62000e1a891489feb29f3d10cb57d4f845c405852bbef8", [:mix], [], "hexpm", "d602c26af3a0af43d2f2645613f65841657ad6efc9f0e361c3b6c06b578214ba"},
|
||||
"ecto": {:hex, :ecto, "3.4.6", "08f7afad3257d6eb8613309af31037e16c36808dfda5a3cd0cb4e9738db030e4", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6f13a9e2a62e75c2dcfc7207bfc65645ab387af8360db4c89fee8b5a4bf3f70b"},
|
||||
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
|
||||
"ecto_sql": {:hex, :ecto_sql, "3.4.5", "30161f81b167d561a9a2df4329c10ae05ff36eca7ccc84628f2c8b9fa1e43323", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31990c6a3579b36a3c0841d34a94c275e727de8b84f58509da5f1b2032c98ac2"},
|
||||
|
|
@ -52,6 +52,7 @@
|
|||
"gen_stage": {:hex, :gen_stage, "0.14.3", "d0c66f1c87faa301c1a85a809a3ee9097a4264b2edf7644bf5c123237ef732bf", [:mix], [], "hexpm"},
|
||||
"gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"},
|
||||
"gettext": {:hex, :gettext, "0.18.0", "406d6b9e0e3278162c2ae1de0a60270452c553536772167e2d701f028116f870", [:mix], [], "hexpm", "c3f850be6367ebe1a08616c2158affe4a23231c70391050bf359d5f92f66a571"},
|
||||
"glob": {:hex, :glob, "1.0.0", "b4d54d66e7797ce037cdd18f2587fc9932187355340e222cafe125cd333d7a0a", [:rebar3], [], "hexpm", "ca25de25ac5a762ba6c979718ae6afef8402cfc9155b87479d215fbe676801e1"},
|
||||
"gun": {:git, "https://github.com/ninenines/gun.git", "921c47146b2d9567eac7e9a4d2ccc60fffd4f327", [ref: "921c47146b2d9567eac7e9a4d2ccc60fffd4f327"]},
|
||||
"hackney": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/hackney.git", "7d7119f0651515d6d7669c78393fd90950a3ec6e", [ref: "7d7119f0651515d6d7669c78393fd90950a3ec6e"]},
|
||||
"html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"},
|
||||
|
|
@ -82,6 +83,7 @@
|
|||
"nimble_pool": {:hex, :nimble_pool, "0.1.0", "ffa9d5be27eee2b00b0c634eb649aa27f97b39186fec3c493716c2a33e784ec6", [:mix], [], "hexpm", "343a1eaa620ddcf3430a83f39f2af499fe2370390d4f785cd475b4df5acaf3f9"},
|
||||
"nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
|
||||
"oban": {:hex, :oban, "2.3.4", "ec7509b9af2524d55f529cb7aee93d36131ae0bf0f37706f65d2fe707f4d9fd8", [:mix], [{:ecto_sql, ">= 3.4.3", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c70ca0434758fd1805422ea4446af5e910ddc697c0c861549c8f0eb0cfbd2fdf"},
|
||||
"oembed_providers": {:hex, :oembed_providers, "0.1.0", "9b336ee5f3ca20ee4ed005383c74b154d30d0abeb98e95828855c0e2841ae46b", [:mix], [{:glob, "~> 1.0", [hex: :glob, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "ac1dda0f743aa6fdead3eef59decfefc9de91d550bf0805b8fce16ed10d421ba"},
|
||||
"open_api_spex": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", "f296ac0924ba3cf79c7a588c4c252889df4c2edd", [ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"]},
|
||||
"p1_utils": {:hex, :p1_utils, "1.0.18", "3fe224de5b2e190d730a3c5da9d6e8540c96484cf4b4692921d1e28f0c32b01c", [:rebar3], [], "hexpm", "1fc8773a71a15553b179c986b22fbeead19b28fe486c332d4929700ffeb71f88"},
|
||||
"parse_trans": {:git, "https://github.com/uwiger/parse_trans.git", "76abb347c3c1d00fb0ccf9e4b43e22b3d2288484", [tag: "3.3.0"]},
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ defmodule Pleroma.HTML.Scrubber.Default do
|
|||
Meta.allow_tag_with_these_attributes(:code, [])
|
||||
Meta.allow_tag_with_these_attributes(:del, [])
|
||||
Meta.allow_tag_with_these_attributes(:em, [])
|
||||
Meta.allow_tag_with_these_attributes(:hr, [])
|
||||
Meta.allow_tag_with_these_attributes(:i, [])
|
||||
Meta.allow_tag_with_these_attributes(:li, [])
|
||||
Meta.allow_tag_with_these_attributes(:ol, [])
|
||||
|
|
@ -58,6 +59,8 @@ defmodule Pleroma.HTML.Scrubber.Default do
|
|||
Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card"])
|
||||
Meta.allow_tag_with_these_attributes(:span, [])
|
||||
|
||||
Meta.allow_tag_with_this_attribute_values(:code, "class", ["inline"])
|
||||
|
||||
@allow_inline_images Pleroma.Config.get([:markup, :allow_inline_images])
|
||||
|
||||
if @allow_inline_images do
|
||||
|
|
|
|||
18
priv/scrubbers/o_embed.ex
Normal file
18
priv/scrubbers/o_embed.ex
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
defmodule Pleroma.HTML.Scrubber.OEmbed do
|
||||
@moduledoc """
|
||||
Scrubs OEmbed HTML
|
||||
"""
|
||||
require FastSanitize.Sanitizer.Meta
|
||||
alias FastSanitize.Sanitizer.Meta
|
||||
|
||||
Meta.strip_comments()
|
||||
|
||||
Meta.allow_tag_with_these_attributes(:iframe, [
|
||||
"width",
|
||||
"height",
|
||||
"src",
|
||||
"allowfullscreen"
|
||||
])
|
||||
|
||||
Meta.strip_everything_not_covered()
|
||||
end
|
||||
|
|
@ -35,13 +35,13 @@ defmodule Pleroma.ApplicationRequirementsTest do
|
|||
setup do: clear_config([:welcome])
|
||||
setup do: clear_config([Pleroma.Emails.Mailer])
|
||||
|
||||
test "raises if welcome email enabled but mail disabled" do
|
||||
test "warns if welcome email enabled but mail disabled" do
|
||||
clear_config([:welcome, :email, :enabled], true)
|
||||
clear_config([Pleroma.Emails.Mailer, :enabled], false)
|
||||
|
||||
assert_raise Pleroma.ApplicationRequirements.VerifyError, "The mail disabled.", fn ->
|
||||
capture_log(&Pleroma.ApplicationRequirements.verify!/0)
|
||||
end
|
||||
assert capture_log(fn ->
|
||||
assert Pleroma.ApplicationRequirements.verify!() == :ok
|
||||
end) =~ "Welcome emails will NOT be sent"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -57,15 +57,13 @@ defmodule Pleroma.ApplicationRequirementsTest do
|
|||
|
||||
setup do: clear_config([:instance, :account_activation_required])
|
||||
|
||||
test "raises if account confirmation is required but mailer isn't enable" do
|
||||
test "warns if account confirmation is required but mailer isn't enabled" do
|
||||
clear_config([:instance, :account_activation_required], true)
|
||||
clear_config([Pleroma.Emails.Mailer, :enabled], false)
|
||||
|
||||
assert_raise Pleroma.ApplicationRequirements.VerifyError,
|
||||
"Account activation enabled, but Mailer is disabled. Cannot send confirmation emails.",
|
||||
fn ->
|
||||
capture_log(&Pleroma.ApplicationRequirements.verify!/0)
|
||||
end
|
||||
assert capture_log(fn ->
|
||||
assert Pleroma.ApplicationRequirements.verify!() == :ok
|
||||
end) =~ "Users will NOT be able to confirm their accounts"
|
||||
end
|
||||
|
||||
test "doesn't do anything if account confirmation is disabled" do
|
||||
|
|
|
|||
|
|
@ -1,79 +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.EarmarkRendererTest do
|
||||
use Pleroma.DataCase, async: true
|
||||
|
||||
test "Paragraph" do
|
||||
code = ~s[Hello\n\nWorld!]
|
||||
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
|
||||
assert result == "<p>Hello</p><p>World!</p>"
|
||||
end
|
||||
|
||||
test "raw HTML" do
|
||||
code = ~s[<a href="http://example.org/">OwO</a><!-- what's this?-->]
|
||||
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
|
||||
assert result == "<p>#{code}</p>"
|
||||
end
|
||||
|
||||
test "rulers" do
|
||||
code = ~s[before\n\n-----\n\nafter]
|
||||
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
|
||||
assert result == "<p>before</p><hr /><p>after</p>"
|
||||
end
|
||||
|
||||
test "headings" do
|
||||
code = ~s[# h1\n## h2\n### h3\n]
|
||||
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
|
||||
assert result == ~s[<h1>h1</h1><h2>h2</h2><h3>h3</h3>]
|
||||
end
|
||||
|
||||
test "blockquote" do
|
||||
code = ~s[> whoms't are you quoting?]
|
||||
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
|
||||
assert result == "<blockquote><p>whoms’t are you quoting?</p></blockquote>"
|
||||
end
|
||||
|
||||
test "code" do
|
||||
code = ~s[`mix`]
|
||||
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
|
||||
assert result == ~s[<p><code class="inline">mix</code></p>]
|
||||
|
||||
code = ~s[``mix``]
|
||||
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
|
||||
assert result == ~s[<p><code class="inline">mix</code></p>]
|
||||
|
||||
code = ~s[```\nputs "Hello World"\n```]
|
||||
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
|
||||
assert result == ~s[<pre><code class="">puts "Hello World"</code></pre>]
|
||||
end
|
||||
|
||||
test "lists" do
|
||||
code = ~s[- one\n- two\n- three\n- four]
|
||||
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
|
||||
assert result == "<ul><li>one</li><li>two</li><li>three</li><li>four</li></ul>"
|
||||
|
||||
code = ~s[1. one\n2. two\n3. three\n4. four\n]
|
||||
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
|
||||
assert result == "<ol><li>one</li><li>two</li><li>three</li><li>four</li></ol>"
|
||||
end
|
||||
|
||||
test "delegated renderers" do
|
||||
code = ~s[a<br/>b]
|
||||
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
|
||||
assert result == "<p>#{code}</p>"
|
||||
|
||||
code = ~s[*aaaa~*]
|
||||
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
|
||||
assert result == ~s[<p><em>aaaa~</em></p>]
|
||||
|
||||
code = ~s[**aaaa~**]
|
||||
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
|
||||
assert result == ~s[<p><strong>aaaa~</strong></p>]
|
||||
|
||||
# strikethrought
|
||||
code = ~s[<del>aaaa~</del>]
|
||||
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
|
||||
assert result == ~s[<p><del>aaaa~</del></p>]
|
||||
end
|
||||
end
|
||||
|
|
@ -46,6 +46,8 @@ defmodule Pleroma.Gun.ConnectionPoolTest do
|
|||
end
|
||||
end
|
||||
|
||||
@tag :skip
|
||||
# https://git.pleroma.social/pleroma/pleroma/-/issues/2628
|
||||
test "connection limit is respected with concurrent requests" do
|
||||
clear_config([:connections_pool, :max_connections]) do
|
||||
clear_config([:connections_pool, :max_connections], 1)
|
||||
|
|
|
|||
|
|
@ -572,6 +572,24 @@ defmodule Pleroma.UserTest do
|
|||
)
|
||||
end
|
||||
|
||||
test "it fails gracefully with invalid email config" do
|
||||
cng = User.register_changeset(%User{}, @full_user_data)
|
||||
|
||||
# Disable the mailer but enable all the things that want to send emails
|
||||
clear_config([Pleroma.Emails.Mailer, :enabled], false)
|
||||
clear_config([:instance, :account_activation_required], true)
|
||||
clear_config([:instance, :account_approval_required], true)
|
||||
clear_config([:welcome, :email, :enabled], true)
|
||||
clear_config([:welcome, :email, :sender], "lain@lain.com")
|
||||
|
||||
# The user is still created
|
||||
assert {:ok, %User{nickname: "nick"}} = User.register(cng)
|
||||
|
||||
# No emails are sent
|
||||
ObanHelpers.perform_all()
|
||||
refute_email_sent()
|
||||
end
|
||||
|
||||
test "it requires an email, name, nickname and password, bio is optional when account_activation_required is enabled" do
|
||||
clear_config([:instance, :account_activation_required], true)
|
||||
|
||||
|
|
|
|||
|
|
@ -640,6 +640,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
|
|||
assert Enum.member?(activities, activity_one)
|
||||
end
|
||||
|
||||
test "always see your own posts even when they address people you block" do
|
||||
user = insert(:user)
|
||||
blockee = insert(:user)
|
||||
|
||||
{:ok, _} = User.block(user, blockee)
|
||||
{:ok, activity} = CommonAPI.post(user, %{status: "hey! @#{blockee.nickname}"})
|
||||
|
||||
activities = ActivityPub.fetch_activities([], %{blocking_user: user})
|
||||
|
||||
assert Enum.member?(activities, activity)
|
||||
end
|
||||
|
||||
test "doesn't return transitive interactions concerning blocked users" do
|
||||
blocker = insert(:user)
|
||||
blockee = insert(:user)
|
||||
|
|
@ -739,6 +751,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
|
|||
refute repeat_activity in activities
|
||||
end
|
||||
|
||||
test "see your own posts even when they adress actors from blocked domains" do
|
||||
user = insert(:user)
|
||||
|
||||
domain = "dogwhistle.zone"
|
||||
domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
|
||||
|
||||
{:ok, user} = User.block_domain(user, domain)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, %{status: "hey! @#{domain_user.nickname}"})
|
||||
|
||||
activities = ActivityPub.fetch_activities([], %{blocking_user: user})
|
||||
|
||||
assert Enum.member?(activities, activity)
|
||||
end
|
||||
|
||||
test "does return activities from followed users on blocked domains" do
|
||||
domain = "meanies.social"
|
||||
domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
|
||||
|
|
|
|||
|
|
@ -260,6 +260,30 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
|
|||
|
||||
assert {:reject, _} = SimplePolicy.filter(remote_user)
|
||||
end
|
||||
|
||||
test "reject Announce when object would be rejected" do
|
||||
clear_config([:mrf_simple, :reject], ["blocked.tld"])
|
||||
|
||||
announce = %{
|
||||
"type" => "Announce",
|
||||
"actor" => "https://okay.tld/users/alice",
|
||||
"object" => %{"type" => "Note", "actor" => "https://blocked.tld/users/bob"}
|
||||
}
|
||||
|
||||
assert {:reject, _} = SimplePolicy.filter(announce)
|
||||
end
|
||||
|
||||
test "reject by URI object" do
|
||||
clear_config([:mrf_simple, :reject], ["blocked.tld"])
|
||||
|
||||
announce = %{
|
||||
"type" => "Announce",
|
||||
"actor" => "https://okay.tld/users/alice",
|
||||
"object" => "https://blocked.tld/activities/1"
|
||||
}
|
||||
|
||||
assert {:reject, _} = SimplePolicy.filter(announce)
|
||||
end
|
||||
end
|
||||
|
||||
describe "when :followers_only" do
|
||||
|
|
|
|||
|
|
@ -168,6 +168,123 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "format_input/3 with markdown" do
|
||||
test "Paragraph" do
|
||||
code = ~s[Hello\n\nWorld!]
|
||||
{result, [], []} = Utils.format_input(code, "text/markdown")
|
||||
assert result == "<p>Hello</p><p>World!</p>"
|
||||
end
|
||||
|
||||
test "links" do
|
||||
code = "https://en.wikipedia.org/wiki/Animal_Crossing_(video_game)"
|
||||
{result, [], []} = Utils.format_input(code, "text/markdown")
|
||||
assert result == ~s[<p><a href="#{code}">#{code}</a></p>]
|
||||
|
||||
code = "https://github.com/pragdave/earmark/"
|
||||
{result, [], []} = Utils.format_input(code, "text/markdown")
|
||||
assert result == ~s[<p><a href="#{code}">#{code}</a></p>]
|
||||
end
|
||||
|
||||
test "link with local mention" do
|
||||
insert(:user, %{nickname: "lain"})
|
||||
|
||||
code = "https://example.com/@lain"
|
||||
{result, [], []} = Utils.format_input(code, "text/markdown")
|
||||
assert result == ~s[<p><a href="#{code}">#{code}</a></p>]
|
||||
end
|
||||
|
||||
test "local mentions" do
|
||||
mario = insert(:user, %{nickname: "mario"})
|
||||
luigi = insert(:user, %{nickname: "luigi"})
|
||||
|
||||
code = "@mario @luigi yo what's up?"
|
||||
{result, _, []} = Utils.format_input(code, "text/markdown")
|
||||
|
||||
assert result ==
|
||||
~s[<p><span class="h-card"><a class="u-url mention" data-user="#{mario.id}" href="#{
|
||||
mario.ap_id
|
||||
}" rel="ugc">@<span>mario</span></a></span> <span class="h-card"><a class="u-url mention" data-user="#{
|
||||
luigi.id
|
||||
}" href="#{luigi.ap_id}" rel="ugc">@<span>luigi</span></a></span> yo what’s up?</p>]
|
||||
end
|
||||
|
||||
test "remote mentions" do
|
||||
mario = insert(:user, %{nickname: "mario@mushroom.world", local: false})
|
||||
luigi = insert(:user, %{nickname: "luigi@mushroom.world", local: false})
|
||||
|
||||
code = "@mario@mushroom.world @luigi@mushroom.world yo what's up?"
|
||||
{result, _, []} = Utils.format_input(code, "text/markdown")
|
||||
|
||||
assert result ==
|
||||
~s[<p><span class="h-card"><a class="u-url mention" data-user="#{mario.id}" href="#{
|
||||
mario.ap_id
|
||||
}" rel="ugc">@<span>mario</span></a></span> <span class="h-card"><a class="u-url mention" data-user="#{
|
||||
luigi.id
|
||||
}" href="#{luigi.ap_id}" rel="ugc">@<span>luigi</span></a></span> yo what’s up?</p>]
|
||||
end
|
||||
|
||||
test "raw HTML" do
|
||||
code = ~s[<a href="http://example.org/">OwO</a><!-- what's this?-->]
|
||||
{result, [], []} = Utils.format_input(code, "text/markdown")
|
||||
assert result == ~s[<a href="http://example.org/">OwO</a>]
|
||||
end
|
||||
|
||||
test "rulers" do
|
||||
code = ~s[before\n\n-----\n\nafter]
|
||||
{result, [], []} = Utils.format_input(code, "text/markdown")
|
||||
assert result == "<p>before</p><hr/><p>after</p>"
|
||||
end
|
||||
|
||||
test "blockquote" do
|
||||
code = ~s[> whoms't are you quoting?]
|
||||
{result, [], []} = Utils.format_input(code, "text/markdown")
|
||||
assert result == "<blockquote><p>whoms’t are you quoting?</p></blockquote>"
|
||||
end
|
||||
|
||||
test "code" do
|
||||
code = ~s[`mix`]
|
||||
{result, [], []} = Utils.format_input(code, "text/markdown")
|
||||
assert result == ~s[<p><code class="inline">mix</code></p>]
|
||||
|
||||
code = ~s[``mix``]
|
||||
{result, [], []} = Utils.format_input(code, "text/markdown")
|
||||
assert result == ~s[<p><code class="inline">mix</code></p>]
|
||||
|
||||
code = ~s[```\nputs "Hello World"\n```]
|
||||
{result, [], []} = Utils.format_input(code, "text/markdown")
|
||||
assert result == ~s[<pre><code>puts "Hello World"</code></pre>]
|
||||
|
||||
code = ~s[ <div>\n </div>]
|
||||
{result, [], []} = Utils.format_input(code, "text/markdown")
|
||||
assert result == ~s[<pre><code><div>\n</div></code></pre>]
|
||||
end
|
||||
|
||||
test "lists" do
|
||||
code = ~s[- one\n- two\n- three\n- four]
|
||||
{result, [], []} = Utils.format_input(code, "text/markdown")
|
||||
assert result == "<ul><li>one</li><li>two</li><li>three</li><li>four</li></ul>"
|
||||
|
||||
code = ~s[1. one\n2. two\n3. three\n4. four\n]
|
||||
{result, [], []} = Utils.format_input(code, "text/markdown")
|
||||
assert result == "<ol><li>one</li><li>two</li><li>three</li><li>four</li></ol>"
|
||||
end
|
||||
|
||||
test "delegated renderers" do
|
||||
code = ~s[*aaaa~*]
|
||||
{result, [], []} = Utils.format_input(code, "text/markdown")
|
||||
assert result == ~s[<p><em>aaaa~</em></p>]
|
||||
|
||||
code = ~s[**aaaa~**]
|
||||
{result, [], []} = Utils.format_input(code, "text/markdown")
|
||||
assert result == ~s[<p><strong>aaaa~</strong></p>]
|
||||
|
||||
# strikethrough
|
||||
code = ~s[~~aaaa~~~]
|
||||
{result, [], []} = Utils.format_input(code, "text/markdown")
|
||||
assert result == ~s[<p><del>aaaa</del>~</p>]
|
||||
end
|
||||
end
|
||||
|
||||
describe "context_to_conversation_id" do
|
||||
test "creates a mapping object" do
|
||||
conversation_id = Utils.context_to_conversation_id("random context")
|
||||
|
|
|
|||
|
|
@ -571,7 +571,7 @@ defmodule Pleroma.Web.CommonAPITest do
|
|||
|
||||
object = Object.normalize(activity, fetch: false)
|
||||
|
||||
assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"
|
||||
assert object.data["content"] == "<p><b>2hu</b></p>"
|
||||
assert object.data["source"] == post
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do
|
|||
assert result["pleroma"]["metadata"]["fields_limits"]
|
||||
assert result["pleroma"]["vapid_public_key"]
|
||||
assert result["pleroma"]["stats"]["mau"] == 0
|
||||
assert result["soapbox"]["version"] =~ "."
|
||||
|
||||
assert email == from_config_email
|
||||
assert thumbnail == from_config_thumbnail
|
||||
|
|
|
|||
|
|
@ -103,6 +103,25 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
|
|||
assert [_] = result
|
||||
end
|
||||
|
||||
test "excludes mentions from blockers when blockers_visible is false" do
|
||||
clear_config([:activitypub, :blockers_visible], false)
|
||||
|
||||
%{user: user, conn: conn} = oauth_access(["read:notifications"])
|
||||
blocker = insert(:user)
|
||||
|
||||
{:ok, _} = CommonAPI.block(blocker, user)
|
||||
{:ok, activity} = CommonAPI.post(blocker, %{status: "hi @#{user.nickname}"})
|
||||
|
||||
{:ok, [_notification]} = Notification.create_notifications(activity)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/notifications")
|
||||
|
||||
assert [] == json_response_and_validate_schema(conn, 200)
|
||||
end
|
||||
|
||||
test "getting a single notification" do
|
||||
%{user: user, conn: conn} = oauth_access(["read:notifications"])
|
||||
other_user = insert(:user)
|
||||
|
|
|
|||
|
|
@ -1331,16 +1331,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
|||
"url" => "https://example.com/ogp",
|
||||
"description" =>
|
||||
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
|
||||
"pleroma" => %{
|
||||
"opengraph" => %{
|
||||
"image" => "http://ia.media-imdb.com/images/rock.jpg",
|
||||
"title" => "The Rock",
|
||||
"type" => "video.movie",
|
||||
"url" => "https://example.com/ogp",
|
||||
"description" =>
|
||||
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
|
||||
}
|
||||
}
|
||||
"author_name" => "",
|
||||
"author_url" => "",
|
||||
"blurhash" => nil,
|
||||
"embed_url" => "",
|
||||
"height" => 0,
|
||||
"html" => "",
|
||||
"width" => 0
|
||||
}
|
||||
|
||||
response =
|
||||
|
|
@ -1380,13 +1377,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
|||
"provider_name" => "example.com",
|
||||
"provider_url" => "https://example.com",
|
||||
"url" => "https://example.com/ogp-missing-data",
|
||||
"pleroma" => %{
|
||||
"opengraph" => %{
|
||||
"title" => "Pleroma",
|
||||
"type" => "website",
|
||||
"url" => "https://example.com/ogp-missing-data"
|
||||
}
|
||||
}
|
||||
"author_name" => "",
|
||||
"author_url" => "",
|
||||
"blurhash" => nil,
|
||||
"embed_url" => "",
|
||||
"height" => 0,
|
||||
"html" => "",
|
||||
"width" => 0
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -273,6 +273,24 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
|
|||
[%{"id" => ^reply_from_me}, %{"id" => ^activity_id}] = response
|
||||
end
|
||||
|
||||
test "doesn't return posts from users who blocked you when :blockers_visible is disabled" do
|
||||
clear_config([:activitypub, :blockers_visible], false)
|
||||
|
||||
%{conn: conn, user: blockee} = oauth_access(["read:statuses"])
|
||||
blocker = insert(:user)
|
||||
{:ok, _} = User.block(blocker, blockee)
|
||||
|
||||
conn = assign(conn, :user, blockee)
|
||||
|
||||
{:ok, _} = CommonAPI.post(blocker, %{status: "hey!"})
|
||||
|
||||
response =
|
||||
get(conn, "/api/v1/timelines/public")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert length(response) == 0
|
||||
end
|
||||
|
||||
test "doesn't return replies if follow is posting with users from blocked domain" do
|
||||
%{conn: conn, user: blocker} = oauth_access(["read:statuses"])
|
||||
friend = insert(:user)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
|
|||
alias Pleroma.Web.CommonAPI.Utils
|
||||
alias Pleroma.Web.MastodonAPI.AccountView
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
alias Pleroma.Web.RichMedia.Parser.Embed
|
||||
|
||||
import Pleroma.Factory
|
||||
import Tesla.Mock
|
||||
|
|
@ -591,56 +592,45 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
|
|||
|
||||
describe "rich media cards" do
|
||||
test "a rich media card without a site name renders correctly" do
|
||||
page_url = "http://example.com"
|
||||
|
||||
card = %{
|
||||
url: page_url,
|
||||
image: page_url <> "/example.jpg",
|
||||
title: "Example website"
|
||||
embed = %Embed{
|
||||
url: "http://example.com",
|
||||
title: "Example website",
|
||||
meta: %{"twitter:image" => "http://example.com/example.jpg"}
|
||||
}
|
||||
|
||||
%{provider_name: "example.com"} =
|
||||
StatusView.render("card.json", %{page_url: page_url, rich_media: card})
|
||||
%{"provider_name" => "example.com"} = StatusView.render("card.json", %{embed: embed})
|
||||
end
|
||||
|
||||
test "a rich media card without a site name or image renders correctly" do
|
||||
page_url = "http://example.com"
|
||||
|
||||
card = %{
|
||||
url: page_url,
|
||||
embed = %Embed{
|
||||
url: "http://example.com",
|
||||
title: "Example website"
|
||||
}
|
||||
|
||||
%{provider_name: "example.com"} =
|
||||
StatusView.render("card.json", %{page_url: page_url, rich_media: card})
|
||||
%{"provider_name" => "example.com"} = StatusView.render("card.json", %{embed: embed})
|
||||
end
|
||||
|
||||
test "a rich media card without an image renders correctly" do
|
||||
page_url = "http://example.com"
|
||||
|
||||
card = %{
|
||||
url: page_url,
|
||||
site_name: "Example site name",
|
||||
title: "Example website"
|
||||
embed = %Embed{
|
||||
url: "http://example.com",
|
||||
title: "Example website",
|
||||
meta: %{"twitter:title" => "Example site name"}
|
||||
}
|
||||
|
||||
%{provider_name: "example.com"} =
|
||||
StatusView.render("card.json", %{page_url: page_url, rich_media: card})
|
||||
%{"provider_name" => "example.com"} = StatusView.render("card.json", %{embed: embed})
|
||||
end
|
||||
|
||||
test "a rich media card with all relevant data renders correctly" do
|
||||
page_url = "http://example.com"
|
||||
|
||||
card = %{
|
||||
url: page_url,
|
||||
site_name: "Example site name",
|
||||
embed = %Embed{
|
||||
url: "http://example.com",
|
||||
title: "Example website",
|
||||
image: page_url <> "/example.jpg",
|
||||
description: "Example description"
|
||||
meta: %{
|
||||
"twitter:title" => "Example site name",
|
||||
"twitter:image" => "http://example.com/example.jpg"
|
||||
}
|
||||
}
|
||||
|
||||
%{provider_name: "example.com"} =
|
||||
StatusView.render("card.json", %{page_url: page_url, rich_media: card})
|
||||
%{"provider_name" => "example.com"} = StatusView.render("card.json", %{embed: embed})
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ defmodule Pleroma.Web.RichMedia.HelpersTest do
|
|||
|
||||
clear_config([:rich_media, :enabled], true)
|
||||
|
||||
assert %{} == Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
|
||||
assert Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) == nil
|
||||
end
|
||||
|
||||
test "refuses to crawl malformed URLs" do
|
||||
|
|
@ -44,7 +44,7 @@ defmodule Pleroma.Web.RichMedia.HelpersTest do
|
|||
|
||||
clear_config([:rich_media, :enabled], true)
|
||||
|
||||
assert %{} == Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
|
||||
assert Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) == nil
|
||||
end
|
||||
|
||||
test "crawls valid, complete URLs" do
|
||||
|
|
@ -58,7 +58,7 @@ defmodule Pleroma.Web.RichMedia.HelpersTest do
|
|||
|
||||
clear_config([:rich_media, :enabled], true)
|
||||
|
||||
assert %{page_url: "https://example.com/ogp", rich_media: _} =
|
||||
assert %{url: "https://example.com/ogp", meta: %{} = _} =
|
||||
Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
|
||||
end
|
||||
|
||||
|
|
@ -75,10 +75,10 @@ defmodule Pleroma.Web.RichMedia.HelpersTest do
|
|||
|
||||
clear_config([:rich_media, :enabled], true)
|
||||
|
||||
assert %{} = Helpers.fetch_data_for_activity(activity)
|
||||
assert %{} = Helpers.fetch_data_for_activity(activity2)
|
||||
assert %{} = Helpers.fetch_data_for_activity(activity3)
|
||||
assert %{} = Helpers.fetch_data_for_activity(activity4)
|
||||
assert %{} = Helpers.fetch_data_for_activity(activity5)
|
||||
assert Helpers.fetch_data_for_activity(activity) == nil
|
||||
assert Helpers.fetch_data_for_activity(activity2) == nil
|
||||
assert Helpers.fetch_data_for_activity(activity3) == nil
|
||||
assert Helpers.fetch_data_for_activity(activity4) == nil
|
||||
assert Helpers.fetch_data_for_activity(activity5) == nil
|
||||
end
|
||||
end
|
||||
|
|
|
|||
48
test/pleroma/web/rich_media/parser/card_test.exs
Normal file
48
test/pleroma/web/rich_media/parser/card_test.exs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.RichMedia.Parser.CardTest do
|
||||
use ExUnit.Case, async: true
|
||||
alias Pleroma.Web.RichMedia.Parser.Card
|
||||
alias Pleroma.Web.RichMedia.Parser.Embed
|
||||
alias Pleroma.Web.RichMedia.Parsers.TwitterCard
|
||||
|
||||
describe "parse/1" do
|
||||
test "converts an %Embed{} into a %Card{}" do
|
||||
url =
|
||||
"https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html"
|
||||
|
||||
embed =
|
||||
File.read!("test/fixtures/nypd-facial-recognition-children-teenagers.html")
|
||||
|> Floki.parse_document!()
|
||||
|> TwitterCard.parse(%Embed{url: url})
|
||||
|
||||
expected = %Card{
|
||||
description:
|
||||
"With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.",
|
||||
image:
|
||||
"https://static01.nyt.com/images/2019/08/01/nyregion/01nypd-juveniles-promo/01nypd-juveniles-promo-videoSixteenByNineJumbo1600.jpg",
|
||||
title: "She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database.",
|
||||
type: "link",
|
||||
provider_name: "www.nytimes.com",
|
||||
provider_url: "https://www.nytimes.com",
|
||||
url: url
|
||||
}
|
||||
|
||||
assert Card.parse(embed) == {:ok, expected}
|
||||
end
|
||||
end
|
||||
|
||||
describe "validate/1" do
|
||||
test "returns {:ok, card} with a valid %Card{}" do
|
||||
card = %Card{
|
||||
title: "Moms can't believe this one trick",
|
||||
url: "http://spam.com",
|
||||
type: "link"
|
||||
}
|
||||
|
||||
assert {:ok, ^card} = Card.validate(card)
|
||||
end
|
||||
end
|
||||
end
|
||||
81
test/pleroma/web/rich_media/parser/meta_tags_test.exs
Normal file
81
test/pleroma/web/rich_media/parser/meta_tags_test.exs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.RichMedia.Parser.MetaTagsTest do
|
||||
use ExUnit.Case, async: true
|
||||
alias Pleroma.Web.RichMedia.Parser.MetaTags
|
||||
|
||||
test "returns a map of <meta> values" do
|
||||
html =
|
||||
File.read!("test/fixtures/nypd-facial-recognition-children-teenagers.html")
|
||||
|> Floki.parse_document!()
|
||||
|
||||
expected = %{
|
||||
"CG" => "nyregion",
|
||||
"CN" => "experience-tech-and-society",
|
||||
"CT" => "spotlight",
|
||||
"PST" => "News",
|
||||
"PT" => "article",
|
||||
"SCG" => "",
|
||||
"al:android:app_name" => "NYTimes",
|
||||
"al:android:package" => "com.nytimes.android",
|
||||
"al:android:url" => "nytimes://reader/id/100000006583622",
|
||||
"al:ipad:app_name" => "NYTimes",
|
||||
"al:ipad:app_store_id" => "357066198",
|
||||
"al:ipad:url" =>
|
||||
"nytimes://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html",
|
||||
"al:iphone:app_name" => "NYTimes",
|
||||
"al:iphone:app_store_id" => "284862083",
|
||||
"al:iphone:url" =>
|
||||
"nytimes://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html",
|
||||
"article:modified" => "2019-08-02T09:30:23.000Z",
|
||||
"article:published" => "2019-08-01T17:15:31.000Z",
|
||||
"article:section" => "New York",
|
||||
"article:tag" => "New York City",
|
||||
"articleid" => "100000006583622",
|
||||
"byl" => "By Joseph Goldstein and Ali Watkins",
|
||||
"description" =>
|
||||
"With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.",
|
||||
"fb:app_id" => "9869919170",
|
||||
"image" =>
|
||||
"https://static01.nyt.com/images/2019/08/01/nyregion/01nypd-juveniles-promo/01nypd-juveniles-promo-facebookJumbo.jpg",
|
||||
"msapplication-starturl" => "https://www.nytimes.com",
|
||||
"news_keywords" =>
|
||||
"NYPD,Juvenile delinquency,Facial Recognition,Privacy,Government Surveillance,Police,Civil Rights,NYC",
|
||||
"nyt_uri" => "nyt://article/9da58246-2495-505f-9abd-b5fda8e67b56",
|
||||
"og:description" =>
|
||||
"With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.",
|
||||
"og:image" =>
|
||||
"https://static01.nyt.com/images/2019/08/01/nyregion/01nypd-juveniles-promo/01nypd-juveniles-promo-facebookJumbo.jpg",
|
||||
"og:title" =>
|
||||
"She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database.",
|
||||
"og:type" => "article",
|
||||
"og:url" =>
|
||||
"https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html",
|
||||
"pdate" => "20190801",
|
||||
"pubp_event_id" => "pubp://event/47a657bafa8a476bb36832f90ee5ac6e",
|
||||
"robots" => "noarchive",
|
||||
"thumbnail" =>
|
||||
"https://static01.nyt.com/images/2019/08/01/nyregion/01nypd-juveniles-promo/01nypd-juveniles-promo-thumbStandard.jpg",
|
||||
"twitter:app:id:googleplay" => "com.nytimes.android",
|
||||
"twitter:app:name:googleplay" => "NYTimes",
|
||||
"twitter:app:url:googleplay" => "nytimes://reader/id/100000006583622",
|
||||
"twitter:card" => "summary_large_image",
|
||||
"twitter:description" =>
|
||||
"With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.",
|
||||
"twitter:image" =>
|
||||
"https://static01.nyt.com/images/2019/08/01/nyregion/01nypd-juveniles-promo/01nypd-juveniles-promo-videoSixteenByNineJumbo1600.jpg",
|
||||
"twitter:image:alt" => "",
|
||||
"twitter:title" =>
|
||||
"She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database.",
|
||||
"twitter:url" =>
|
||||
"https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html",
|
||||
"url" =>
|
||||
"https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html",
|
||||
"viewport" => "width=device-width, initial-scale=1, maximum-scale=1"
|
||||
}
|
||||
|
||||
assert MetaTags.parse(html) == expected
|
||||
end
|
||||
end
|
||||
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.RichMedia.ParserTest do
|
|||
use ExUnit.Case, async: true
|
||||
|
||||
alias Pleroma.Web.RichMedia.Parser
|
||||
alias Pleroma.Web.RichMedia.Parser.Embed
|
||||
|
||||
setup do
|
||||
Tesla.Mock.mock(fn
|
||||
|
|
@ -86,76 +87,83 @@ defmodule Pleroma.Web.RichMedia.ParserTest do
|
|||
assert {:error, _} = Parser.parse("http://example.com/empty")
|
||||
end
|
||||
|
||||
test "doesn't just add a title" do
|
||||
assert {:error, {:invalid_metadata, _}} = Parser.parse("http://example.com/non-ogp")
|
||||
end
|
||||
|
||||
test "parses ogp" do
|
||||
assert Parser.parse("http://example.com/ogp") ==
|
||||
{:ok,
|
||||
%{
|
||||
"image" => "http://ia.media-imdb.com/images/rock.jpg",
|
||||
"title" => "The Rock",
|
||||
"description" =>
|
||||
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
|
||||
"type" => "video.movie",
|
||||
"url" => "http://example.com/ogp"
|
||||
}}
|
||||
url = "http://example.com/ogp"
|
||||
|
||||
expected = %Embed{
|
||||
meta: %{
|
||||
"og:image" => "http://ia.media-imdb.com/images/rock.jpg",
|
||||
"og:title" => "The Rock",
|
||||
"og:description" =>
|
||||
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
|
||||
"og:type" => "video.movie",
|
||||
"og:url" => "http://www.imdb.com/title/tt0117500/"
|
||||
},
|
||||
oembed: nil,
|
||||
title: "The Rock (1996)",
|
||||
url: "http://example.com/ogp"
|
||||
}
|
||||
|
||||
assert Parser.parse(url) == {:ok, expected}
|
||||
end
|
||||
|
||||
test "falls back to <title> when ogp:title is missing" do
|
||||
assert Parser.parse("http://example.com/ogp-missing-title") ==
|
||||
{:ok,
|
||||
%{
|
||||
"image" => "http://ia.media-imdb.com/images/rock.jpg",
|
||||
"title" => "The Rock (1996)",
|
||||
"description" =>
|
||||
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
|
||||
"type" => "video.movie",
|
||||
"url" => "http://example.com/ogp-missing-title"
|
||||
}}
|
||||
test "gets <title> tag" do
|
||||
url = "http://example.com/ogp-missing-title"
|
||||
expected = "The Rock (1996)"
|
||||
assert {:ok, %Embed{title: ^expected}} = Parser.parse(url)
|
||||
end
|
||||
|
||||
test "parses twitter card" do
|
||||
assert Parser.parse("http://example.com/twitter-card") ==
|
||||
{:ok,
|
||||
%{
|
||||
"card" => "summary",
|
||||
"site" => "@flickr",
|
||||
"image" => "https://farm6.staticflickr.com/5510/14338202952_93595258ff_z.jpg",
|
||||
"title" => "Small Island Developing States Photo Submission",
|
||||
"description" => "View the album on Flickr.",
|
||||
"url" => "http://example.com/twitter-card"
|
||||
}}
|
||||
url = "http://example.com/twitter-card"
|
||||
|
||||
expected = %Embed{
|
||||
meta: %{
|
||||
"twitter:card" => "summary",
|
||||
"twitter:description" => "View the album on Flickr.",
|
||||
"twitter:image" => "https://farm6.staticflickr.com/5510/14338202952_93595258ff_z.jpg",
|
||||
"twitter:site" => "@flickr",
|
||||
"twitter:title" => "Small Island Developing States Photo Submission"
|
||||
},
|
||||
oembed: nil,
|
||||
title: nil,
|
||||
url: "http://example.com/twitter-card"
|
||||
}
|
||||
|
||||
assert Parser.parse(url) == {:ok, expected}
|
||||
end
|
||||
|
||||
test "parses OEmbed" do
|
||||
assert Parser.parse("http://example.com/oembed") ==
|
||||
{:ok,
|
||||
%{
|
||||
"author_name" => "bees",
|
||||
"author_url" => "https://www.flickr.com/photos/bees/",
|
||||
"cache_age" => 3600,
|
||||
"flickr_type" => "photo",
|
||||
"height" => "768",
|
||||
"html" =>
|
||||
"<a data-flickr-embed=\"true\" href=\"https://www.flickr.com/photos/bees/2362225867/\" title=\"Bacon Lollys by bees, on Flickr\"><img src=\"https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_b.jpg\" width=\"1024\" height=\"768\" alt=\"Bacon Lollys\"></a><script async src=\"https://embedr.flickr.com/assets/client-code.js\" charset=\"utf-8\"></script>",
|
||||
"license" => "All Rights Reserved",
|
||||
"license_id" => 0,
|
||||
"provider_name" => "Flickr",
|
||||
"provider_url" => "https://www.flickr.com/",
|
||||
"thumbnail_height" => 150,
|
||||
"thumbnail_url" =>
|
||||
"https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_q.jpg",
|
||||
"thumbnail_width" => 150,
|
||||
"title" => "Bacon Lollys",
|
||||
"type" => "photo",
|
||||
"url" => "http://example.com/oembed",
|
||||
"version" => "1.0",
|
||||
"web_page" => "https://www.flickr.com/photos/bees/2362225867/",
|
||||
"web_page_short_url" => "https://flic.kr/p/4AK2sc",
|
||||
"width" => "1024"
|
||||
}}
|
||||
url = "http://example.com/oembed"
|
||||
|
||||
expected = %Embed{
|
||||
meta: %{},
|
||||
oembed: %{
|
||||
"author_name" => "bees",
|
||||
"author_url" => "https://www.flickr.com/photos/bees/",
|
||||
"cache_age" => 3600,
|
||||
"flickr_type" => "photo",
|
||||
"height" => "768",
|
||||
"html" =>
|
||||
"<a data-flickr-embed=\"true\" href=\"https://www.flickr.com/photos/bees/2362225867/\" title=\"Bacon Lollys by bees, on Flickr\"><img src=\"https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_b.jpg\" width=\"1024\" height=\"768\" alt=\"Bacon Lollys\"></a><script async src=\"https://embedr.flickr.com/assets/client-code.js\" charset=\"utf-8\"></script>",
|
||||
"license" => "All Rights Reserved",
|
||||
"license_id" => 0,
|
||||
"provider_name" => "Flickr",
|
||||
"provider_url" => "https://www.flickr.com/",
|
||||
"thumbnail_height" => 150,
|
||||
"thumbnail_url" => "https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_q.jpg",
|
||||
"thumbnail_width" => 150,
|
||||
"title" => "Bacon Lollys",
|
||||
"type" => "photo",
|
||||
"url" => "https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_b.jpg",
|
||||
"version" => "1.0",
|
||||
"web_page" => "https://www.flickr.com/photos/bees/2362225867/",
|
||||
"web_page_short_url" => "https://flic.kr/p/4AK2sc",
|
||||
"width" => "1024"
|
||||
},
|
||||
url: "http://example.com/oembed"
|
||||
}
|
||||
|
||||
assert Parser.parse(url) == {:ok, expected}
|
||||
end
|
||||
|
||||
test "rejects invalid OGP data" do
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@ defmodule Pleroma.Web.RichMedia.Parsers.TwitterCardTest do
|
|||
use ExUnit.Case, async: true
|
||||
alias Pleroma.Web.RichMedia.Parsers.TwitterCard
|
||||
|
||||
test "returns error when html not contains twitter card" do
|
||||
assert TwitterCard.parse([{"html", [], [{"head", [], []}, {"body", [], []}]}], %{}) == %{}
|
||||
test "fails gracefully with barebones HTML" do
|
||||
html = [{"html", [], [{"head", [], []}, {"body", [], []}]}]
|
||||
expected = %{meta: %{}, title: nil}
|
||||
assert TwitterCard.parse(html, %{}) == expected
|
||||
end
|
||||
|
||||
test "parses twitter card with only name attributes" do
|
||||
|
|
@ -15,22 +17,24 @@ defmodule Pleroma.Web.RichMedia.Parsers.TwitterCardTest do
|
|||
File.read!("test/fixtures/nypd-facial-recognition-children-teenagers3.html")
|
||||
|> Floki.parse_document!()
|
||||
|
||||
assert TwitterCard.parse(html, %{}) ==
|
||||
%{
|
||||
"app:id:googleplay" => "com.nytimes.android",
|
||||
"app:name:googleplay" => "NYTimes",
|
||||
"app:url:googleplay" => "nytimes://reader/id/100000006583622",
|
||||
"site" => nil,
|
||||
"description" =>
|
||||
assert %{
|
||||
title:
|
||||
"She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database. - The New York Times",
|
||||
meta: %{
|
||||
"twitter:app:id:googleplay" => "com.nytimes.android",
|
||||
"twitter:app:name:googleplay" => "NYTimes",
|
||||
"twitter:app:url:googleplay" => "nytimes://reader/id/100000006583622",
|
||||
"og:description" =>
|
||||
"With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.",
|
||||
"image" =>
|
||||
"og:image" =>
|
||||
"https://static01.nyt.com/images/2019/08/01/nyregion/01nypd-juveniles-promo/01nypd-juveniles-promo-facebookJumbo.jpg",
|
||||
"type" => "article",
|
||||
"url" =>
|
||||
"https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html",
|
||||
"title" =>
|
||||
"She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database."
|
||||
"og:title" =>
|
||||
"She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database.",
|
||||
"og:type" => "article",
|
||||
"og:url" =>
|
||||
"https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html"
|
||||
}
|
||||
} = TwitterCard.parse(html, %{})
|
||||
end
|
||||
|
||||
test "parses twitter card with only property attributes" do
|
||||
|
|
@ -38,20 +42,31 @@ defmodule Pleroma.Web.RichMedia.Parsers.TwitterCardTest do
|
|||
File.read!("test/fixtures/nypd-facial-recognition-children-teenagers2.html")
|
||||
|> Floki.parse_document!()
|
||||
|
||||
assert TwitterCard.parse(html, %{}) ==
|
||||
%{
|
||||
"card" => "summary_large_image",
|
||||
"description" =>
|
||||
assert %{
|
||||
title:
|
||||
"She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database. - The New York Times",
|
||||
meta: %{
|
||||
"twitter:card" => "summary_large_image",
|
||||
"twitter:description" =>
|
||||
"With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.",
|
||||
"image" =>
|
||||
"twitter:image" =>
|
||||
"https://static01.nyt.com/images/2019/08/01/nyregion/01nypd-juveniles-promo/01nypd-juveniles-promo-videoSixteenByNineJumbo1600.jpg",
|
||||
"image:alt" => "",
|
||||
"title" =>
|
||||
"twitter:image:alt" => "",
|
||||
"twitter:title" =>
|
||||
"She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database.",
|
||||
"url" =>
|
||||
"twitter:url" =>
|
||||
"https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html",
|
||||
"type" => "article"
|
||||
"og:description" =>
|
||||
"With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.",
|
||||
"og:image" =>
|
||||
"https://static01.nyt.com/images/2019/08/01/nyregion/01nypd-juveniles-promo/01nypd-juveniles-promo-facebookJumbo.jpg",
|
||||
"og:title" =>
|
||||
"She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database.",
|
||||
"og:url" =>
|
||||
"https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html",
|
||||
"og:type" => "article"
|
||||
}
|
||||
} = TwitterCard.parse(html, %{})
|
||||
end
|
||||
|
||||
test "parses twitter card with name & property attributes" do
|
||||
|
|
@ -59,69 +74,53 @@ defmodule Pleroma.Web.RichMedia.Parsers.TwitterCardTest do
|
|||
File.read!("test/fixtures/nypd-facial-recognition-children-teenagers.html")
|
||||
|> Floki.parse_document!()
|
||||
|
||||
assert TwitterCard.parse(html, %{}) ==
|
||||
%{
|
||||
"app:id:googleplay" => "com.nytimes.android",
|
||||
"app:name:googleplay" => "NYTimes",
|
||||
"app:url:googleplay" => "nytimes://reader/id/100000006583622",
|
||||
"card" => "summary_large_image",
|
||||
"description" =>
|
||||
assert %{
|
||||
title:
|
||||
"She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database. - The New York Times",
|
||||
meta: %{
|
||||
"twitter:app:id:googleplay" => "com.nytimes.android",
|
||||
"twitter:app:name:googleplay" => "NYTimes",
|
||||
"twitter:app:url:googleplay" => "nytimes://reader/id/100000006583622",
|
||||
"twitter:card" => "summary_large_image",
|
||||
"twitter:description" =>
|
||||
"With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.",
|
||||
"image" =>
|
||||
"twitter:image" =>
|
||||
"https://static01.nyt.com/images/2019/08/01/nyregion/01nypd-juveniles-promo/01nypd-juveniles-promo-videoSixteenByNineJumbo1600.jpg",
|
||||
"image:alt" => "",
|
||||
"site" => nil,
|
||||
"title" =>
|
||||
"twitter:image:alt" => "",
|
||||
"twitter:title" =>
|
||||
"She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database.",
|
||||
"url" =>
|
||||
"twitter:url" =>
|
||||
"https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html",
|
||||
"type" => "article"
|
||||
"og:description" =>
|
||||
"With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.",
|
||||
"og:image" =>
|
||||
"https://static01.nyt.com/images/2019/08/01/nyregion/01nypd-juveniles-promo/01nypd-juveniles-promo-facebookJumbo.jpg",
|
||||
"og:title" =>
|
||||
"She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database.",
|
||||
"og:url" =>
|
||||
"https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html",
|
||||
"og:type" => "article"
|
||||
}
|
||||
} = TwitterCard.parse(html, %{})
|
||||
end
|
||||
|
||||
test "respect only first title tag on the page" do
|
||||
image_path =
|
||||
"https://assets.atlasobscura.com/media/W1siZiIsInVwbG9hZHMvYXNzZXRzLzkwYzgyMzI4LThlMDUtNGRiNS05MDg3LTUzMGUxZTM5N2RmMmVkOTM5ZDM4MGM4OTIx" <>
|
||||
"YTQ5MF9EQVIgZXhodW1hdGlvbiBvZiBNYXJnYXJldCBDb3JiaW4gZ3JhdmUgMTkyNi5qcGciXSxbInAiLCJjb252ZXJ0IiwiIl0sWyJwIiwiY29udmVydCIsIi1xdWFsaXR5IDgxIC1hdXRvLW9" <>
|
||||
"yaWVudCJdLFsicCIsInRodW1iIiwiNjAweD4iXV0/DAR%20exhumation%20of%20Margaret%20Corbin%20grave%201926.jpg"
|
||||
|
||||
html =
|
||||
File.read!("test/fixtures/margaret-corbin-grave-west-point.html") |> Floki.parse_document!()
|
||||
|
||||
assert TwitterCard.parse(html, %{}) ==
|
||||
%{
|
||||
"site" => "@atlasobscura",
|
||||
"title" => "The Missing Grave of Margaret Corbin, Revolutionary War Veteran",
|
||||
"card" => "summary_large_image",
|
||||
"image" => image_path,
|
||||
"description" =>
|
||||
"She's the only woman veteran honored with a monument at West Point. But where was she buried?",
|
||||
"site_name" => "Atlas Obscura",
|
||||
"type" => "article",
|
||||
"url" => "http://www.atlasobscura.com/articles/margaret-corbin-grave-west-point"
|
||||
}
|
||||
expected = "The Missing Grave of Margaret Corbin, Revolutionary War Veteran - Atlas Obscura"
|
||||
|
||||
assert %{title: ^expected} = TwitterCard.parse(html, %{})
|
||||
end
|
||||
|
||||
test "takes first founded title in html head if there is html markup error" do
|
||||
test "takes first title found in html head if there is an html markup error" do
|
||||
html =
|
||||
File.read!("test/fixtures/nypd-facial-recognition-children-teenagers4.html")
|
||||
|> Floki.parse_document!()
|
||||
|
||||
assert TwitterCard.parse(html, %{}) ==
|
||||
%{
|
||||
"site" => nil,
|
||||
"title" =>
|
||||
"She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database.",
|
||||
"app:id:googleplay" => "com.nytimes.android",
|
||||
"app:name:googleplay" => "NYTimes",
|
||||
"app:url:googleplay" => "nytimes://reader/id/100000006583622",
|
||||
"description" =>
|
||||
"With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.",
|
||||
"image" =>
|
||||
"https://static01.nyt.com/images/2019/08/01/nyregion/01nypd-juveniles-promo/01nypd-juveniles-promo-facebookJumbo.jpg",
|
||||
"type" => "article",
|
||||
"url" =>
|
||||
"https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html"
|
||||
}
|
||||
expected =
|
||||
"She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database. - The New York Times"
|
||||
|
||||
assert %{title: ^expected} = TwitterCard.parse(html, %{})
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue