Merge branch 'develop' into feld-2168-media-preview-proxy

This commit is contained in:
Mark Felder 2020-08-25 11:57:23 -05:00
commit 479578b148
54 changed files with 1095 additions and 254 deletions

View File

@ -194,7 +194,7 @@ amd64:
variables: &release-variables
MIX_ENV: prod
before_script: &before-release
- apt install cmake -y
- apt-get update && apt-get install -y cmake
- echo "import Mix.Config" > config/prod.secret.exs
- mix local.hex --force
- mix local.rebar --force

View File

@ -6,18 +6,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [unreleased]
### Changed
- **Breaking:** The default descriptions on uploads are now empty. The old behavior (filename as default) can be configured, see the cheat sheet.
- **Breaking:** Added the ObjectAgePolicy to the default set of MRFs. This will delist and strip the follower collection of any message received that is older than 7 days. This will stop users from seeing very old messages in the timelines. The messages can still be viewed on the user's page and in conversations. They also still trigger notifications.
- **Breaking:** Elixir >=1.9 is now required (was >= 1.8)
- **Breaking:** Configuration: `:auto_linker, :opts` moved to `:pleroma, Pleroma.Formatter`. Old config namespace is deprecated.
- **Breaking:** Configuration: `:instance, welcome_user_nickname` moved to `:welcome, :direct_message, :sender_nickname`, `:instance, :welcome_message` moved to `:welcome, :direct_message, :message`. Old config namespace is deprecated.
- **Breaking:** LDAP: Fallback to local database authentication has been removed for security reasons and lack of a mechanism to ensure the passwords are synchronized when LDAP passwords are updated.
- **Breaking** Changed defaults for `:restrict_unauthenticated` so that when `:instance, :public` is set to `false` then all `:restrict_unauthenticated` items be effectively set to `true`. If you'd like to allow unauthenticated access to specific API endpoints on a private instance, please explicitly set `:restrict_unauthenticated` to non-default value in `config/prod.secret.exs`.
- In Conversations, return only direct messages as `last_status`
- Using the `only_media` filter on timelines will now exclude reblog media
- MFR policy to set global expiration for all local Create activities
- OGP rich media parser merged with TwitterCard
- Configuration: `:instance, rewrite_policy` moved to `:mrf, policies`, `:instance, :mrf_transparency` moved to `:mrf, :transparency`, `:instance, :mrf_transparency_exclusions` moved to `:mrf, :transparency_exclusions`. Old config namespace is deprecated.
- Configuration: `:media_proxy, whitelist` format changed to host with scheme (e.g. `http://example.com` instead of `example.com`). Domain format is deprecated.
- **Breaking:** Configuration: `:instance, welcome_user_nickname` moved to `:welcome, :direct_message, :sender_nickname`, `:instance, :welcome_message` moved to `:welcome, :direct_message, :message`. Old config namespace is deprecated.
- **Breaking:** LDAP: Fallback to local database authentication has been removed for security reasons and lack of a mechanism to ensure the passwords are synchronized when LDAP passwords are updated.
- **Breaking** Changed defaults for `:restrict_unauthenticated` so that when `:instance, :public` is set to `false` then all `:restrict_unauthenticated` items be effectively set to `true`. If you'd like to allow unauthenticated access to specific API endpoints on a private instance, please explicitly set `:restrict_unauthenticated` to non-default value in `config/prod.secret.exs`.
<details>
<summary>API Changes</summary>
@ -25,33 +27,36 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- **Breaking:** Pleroma API: The routes to update avatar, banner and background have been removed.
- **Breaking:** Image description length is limited now.
- **Breaking:** Emoji API: changed methods and renamed routes.
- **Breaking:** Notification Settings API for suppressing notifications has been simplified down to `block_from_strangers`.
- **Breaking:** Notification Settings API option for hiding push notification contents has been renamed to `hide_notification_contents`.
- MastodonAPI: Allow removal of avatar, banner and background.
- Streaming: Repeats of a user's posts will no longer be pushed to the user's stream.
- Mastodon API: Added `pleroma.metadata.fields_limits` to /api/v1/instance
- Mastodon API: On deletion, returns the original post text.
- Mastodon API: Add `pleroma.unread_count` to the Marker entity.
- **Breaking:** Notification Settings API for suppressing notifications
has been simplified down to `block_from_strangers`.
- **Breaking:** Notification Settings API option for hiding push notification
contents has been renamed to `hide_notification_contents`
- Mastodon API: Added `pleroma.metadata.post_formats` to /api/v1/instance
- Mastodon API (legacy): Allow query parameters for `/api/v1/domain_blocks`, e.g. `/api/v1/domain_blocks?domain=badposters.zone`
- Pleroma API: `/api/pleroma/captcha` responses now include `seconds_valid` with an integer value.
</details>
<details>
<summary>Admin API Changes</summary>
- **Breaking** Changed relay `/api/pleroma/admin/relay` endpoints response format.
- Status visibility stats: now can return stats per instance.
- Mix task to refresh counter cache (`mix pleroma.refresh_counter_cache`)
</details>
### Removed
- **Breaking:** removed `with_move` parameter from notifications timeline.
### Added
- Frontends: Add mix task to install frontends.
- Frontends: Add configurable frontends for primary and admin fe.
- Configuration: Added a blacklist for email servers.
- Chats: Added `accepts_chat_messages` field to user, exposed in APIs and federation.
- Chats: Added support for federated chats. For details, see the docs.
@ -107,6 +112,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Emoji Packs could not be listed when instance was set to `public: false`
- Fix whole_word always returning false on filter get requests
- Migrations not working on OTP releases if the database was connected over ssl
- Fix relay following
## [Unreleased (patch)]

View File

@ -72,7 +72,8 @@ config :pleroma, Pleroma.Upload,
pool: :upload
]
],
filename_display_max_length: 30
filename_display_max_length: 30,
default_description: nil
config :pleroma, Pleroma.Uploaders.Local, uploads: "uploads"
@ -682,7 +683,50 @@ config :pleroma, :static_fe, enabled: false
# With no frontend configuration, the bundled files from the `static` directory will
# be used.
#
# config :pleroma, :frontends, primary: %{"name" => "pleroma", "ref" => "develop"}
# config :pleroma, :frontends,
# primary: %{"name" => "pleroma-fe", "ref" => "develop"},
# admin: %{"name" => "admin-fe", "ref" => "stable"},
# available: %{...}
config :pleroma, :frontends,
available: %{
"kenoma" => %{
"name" => "kenoma",
"git" => "https://git.pleroma.social/lambadalambda/kenoma",
"build_url" =>
"https://git.pleroma.social/lambadalambda/kenoma/-/jobs/artifacts/${ref}/download?job=build",
"ref" => "master"
},
"pleroma-fe" => %{
"name" => "pleroma-fe",
"git" => "https://git.pleroma.social/pleroma/pleroma-fe",
"build_url" =>
"https://git.pleroma.social/pleroma/pleroma-fe/-/jobs/artifacts/${ref}/download?job=build",
"ref" => "develop"
},
"fedi-fe" => %{
"name" => "fedi-fe",
"git" => "https://git.pleroma.social/pleroma/fedi-fe",
"build_url" =>
"https://git.pleroma.social/pleroma/fedi-fe/-/jobs/artifacts/${ref}/download?job=build",
"ref" => "master"
},
"admin-fe" => %{
"name" => "admin-fe",
"git" => "https://git.pleroma.social/pleroma/admin-fe",
"build_url" =>
"https://git.pleroma.social/pleroma/admin-fe/-/jobs/artifacts/${ref}/download?job=build",
"ref" => "develop"
},
"soapbox-fe" => %{
"name" => "soapbox-fe",
"git" => "https://gitlab.com/soapbox-pub/soapbox-fe",
"build_url" =>
"https://gitlab.com/soapbox-pub/soapbox-fe/-/jobs/artifacts/${ref}/download?job=build-production",
"ref" => "v1.0.0",
"build_dir" => "static"
}
}
config :pleroma, :web_cache_ttl,
activity_pub: nil,

View File

@ -12,6 +12,55 @@ websocket_config = [
compress: false
]
installed_frontend_options = [
%{
key: "name",
label: "Name",
type: :string,
description:
"Name of the installed frontend. Valid config must include both `Name` and `Reference` values."
},
%{
key: "ref",
label: "Reference",
type: :string,
description:
"Reference of the installed frontend to be used. Valid config must include both `Name` and `Reference` values."
}
]
frontend_options = [
%{
key: "name",
label: "Name",
type: :string,
description: "Name of the frontend."
},
%{
key: "ref",
label: "Reference",
type: :string,
description: "Reference of the frontend to be used."
},
%{
key: "git",
type: :string,
description: "URL of the git repository of the frontend"
},
%{
key: "build_url",
type: :string,
description:
"Either an url to a zip file containing the frontend or a template to build it by inserting the `ref`. The string `${ref}` will be replaced by the configured `ref`.",
example: "https://some.url/builds/${ref}.zip"
},
%{
key: "build_dir",
type: :string,
description: "The directory inside the zip file "
}
]
config :pleroma, :config_description, [
%{
group: :pleroma,
@ -3609,22 +3658,41 @@ config :pleroma, :config_description, [
key: :primary,
type: :map,
description: "Primary frontend, the one that is served for all pages by default",
children: [
%{
key: "name",
label: "Name",
type: :string,
description:
"Name of the installed primary frontend. Valid config must include both `Name` and `Reference` values."
children: installed_frontend_options
},
%{
key: "ref",
label: "Reference",
type: :string,
key: :admin,
type: :map,
description: "Admin frontend",
children: installed_frontend_options
},
%{
key: :available,
type: :map,
description:
"Reference of the installed primary frontend to be used. Valid config must include both `Name` and `Reference` values."
"A map containing available frontends and parameters for their installation.",
children: [
frontend_options
]
}
]
},
%{
group: :pleroma,
key: Pleroma.Web.Preload,
type: :group,
description: "Preload-related settings",
children: [
%{
key: :providers,
type: {:list, :module},
description: "List of preload providers to enable",
suggestions: [
Pleroma.Web.Preload.Providers.Instance,
Pleroma.Web.Preload.Providers.User,
Pleroma.Web.Preload.Providers.Timelines,
Pleroma.Web.Preload.Providers.StatusNet
]
}
]
}

View File

@ -21,7 +21,10 @@ config :logger, :console,
config :pleroma, :auth, oauth_consumer_strategies: []
config :pleroma, Pleroma.Upload, filters: [], link_name: false
config :pleroma, Pleroma.Upload,
filters: [],
link_name: false,
default_description: :filename
config :pleroma, Pleroma.Uploaders.Local, uploads: "test/uploads"

View File

@ -313,31 +313,53 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
- On failure: `Not found`
- On success: JSON array of user's latest statuses
## `GET /api/pleroma/admin/relay`
### List Relays
Params: none
Response:
* On success: JSON array of relays
```json
[
{"actor": "https://example.com/relay", "followed_back": true},
{"actor": "https://example2.com/relay", "followed_back": false}
]
```
## `POST /api/pleroma/admin/relay`
### Follow a Relay
- Params:
- `relay_url`
- Response:
- On success: URL of the followed relay
Params:
* `relay_url`
Response:
* On success: relay json object
```json
{"actor": "https://example.com/relay", "followed_back": true}
```
## `DELETE /api/pleroma/admin/relay`
### Unfollow a Relay
- Params:
- `relay_url`
- Response:
- On success: URL of the unfollowed relay
Params:
## `GET /api/pleroma/admin/relay`
* `relay_url`
### List Relays
Response:
- Params: none
- Response:
- On success: JSON array of relays
* On success: URL of the unfollowed relay
```json
{"https://example.com/relay"}
```
## `POST /api/pleroma/admin/users/invite_token`

View File

@ -0,0 +1,69 @@
# Managing frontends
`mix pleroma.frontend install <frontend> [--ref <ref>] [--file <file>] [--build-url <build-url>] [--path <path>] [--build-dir <build-dir>]`
Frontend can be installed either from local zip file, or automatically downloaded from the web.
You can give all the options directly on the command like, but missing information will be filled out by looking at the data configured under `frontends.available` in the config files.
Currently known `<frontend>` values are:
- [admin-fe](https://git.pleroma.social/pleroma/admin-fe)
- [kenoma](http://git.pleroma.social/lambadalambda/kenoma)
- [pleroma-fe](http://git.pleroma.social/pleroma/pleroma-fe)
- [fedi-fe](https://git.pleroma.social/pleroma/fedi-fe)
- [soapbox-fe](https://gitlab.com/soapbox-pub/soapbox-fe)
You can still install frontends that are not configured, see below.
## Example installations for a known frontend
For a frontend configured under the `available` key, it's enough to install it by name.
```sh tab="OTP"
./bin/pleroma_ctl frontend install pleroma
```
```sh tab="From Source"
mix pleroma.frontend install pleroma
```
This will download the latest build for the the pre-configured `ref` and install it. It can then be configured as the one of the served frontends in the config file (see `primary` or `admin`).
You can override any of the details. To install a pleroma build from a different url, you could do this:
```sh tab="OPT"
./bin/pleroma_ctl frontend install pleroma --ref 2hu_edition --build-url https://example.org/raymoo.zip
```
```sh tab="From Source"
mix pleroma.frontend install pleroma --ref 2hu_edition --build-url https://example.org/raymoo.zip
```
Similarly, you can also install from a local zip file.
```sh tab="OTP"
./bin/pleroma_ctl frontend install pleroma --ref mybuild --file ~/Downloads/doomfe.zip
```
```sh tab="From Source"
mix pleroma.frontend install pleroma --ref mybuild --file ~/Downloads/doomfe.zip
```
The resulting frontend will always be installed into a folder of this template: `${instance_static}/frontends/${name}/${ref}`
Careful: This folder will be completely replaced on installation
## Example installation for an unknown frontend
The installation process is the same, but you will have to give all the needed options on the commond line. For example:
```sh tab="OTP"
./bin/pleroma_ctl frontend install gensokyo --ref master --build-url https://gensokyo.2hu/builds/marisa.zip
```
```sh tab="From Source"
mix pleroma.frontend install gensokyo --ref master --build-url https://gensokyo.2hu/builds/marisa.zip
```
If you don't have a zip file but just want to install a frontend from a local path, you can simply copy the files over a folder of this template: `${instance_static}/frontends/${name}/${ref}`

View File

@ -6,11 +6,11 @@ Feel free to contact us to be added to this list!
### Roma for Desktop
- Homepage: <https://www.pleroma.com/#desktopApp>
- Source Code: <https://github.com/roma-apps/roma-desktop>
- Platforms: Windows, Mac, (Linux?)
- Platforms: Windows, Mac, Linux
- Features: Streaming Ready
### Social
- Source Code: <https://gitlab.gnome.org/BrainBlasted/Social>
- Source Code: <https://gitlab.gnome.org/World/Social>
- Contact: [@brainblasted@social.libre.fi](https://social.libre.fi/users/brainblasted)
- Platforms: Linux (GNOME)
- Note(2019-01-28): Not at a pre-alpha stage yet
@ -48,16 +48,9 @@ Feel free to contact us to be added to this list!
- Platforms: Android
- Features: No Streaming, Emoji Reactions, Text Formatting, FE Stickers
### Nekonium
- Homepage: [F-Droid Repository](https://repo.gdgd.jp.net/), [Google Play](https://play.google.com/store/apps/details?id=com.apps.nekonium), [Amazon](https://www.amazon.co.jp/dp/B076FXPRBC/)
- Source: <https://gogs.gdgd.jp.net/lin/nekonium>
- Contact: [@lin@pleroma.gdgd.jp.net](https://pleroma.gdgd.jp.net/users/lin)
- Platforms: Android
- Features: Streaming Ready
### Fedi
- Homepage: <https://www.fediapp.com/>
- Source Code: Proprietary, but free
- Source Code: Proprietary, but gratis
- Platforms: iOS, Android
- Features: Pleroma-specific features like Reactions
@ -70,9 +63,9 @@ Feel free to contact us to be added to this list!
### Twidere
- Homepage: <https://twidere.mariotaku.org/>
- Source Code: <https://github.com/TwidereProject/Twidere-Android/>, <https://github.com/TwidereProject/Twidere-iOS/>
- Source Code: <https://github.com/TwidereProject/Twidere-Android/>
- Contact: <me@mariotaku.org>
- Platform: Android, iOS
- Platform: Android
- Features: No Streaming
### Indigenous
@ -89,11 +82,6 @@ Feel free to contact us to be added to this list!
- Contact: [@gcupc@glitch.social](https://glitch.social/users/gcupc)
- Features: No Streaming
### Feather
- Source Code: <https://github.com/kaniini/feather>
- Contact: [@kaniini@pleroma.site](https://pleroma.site/kaniini)
- Features: No Streaming
### Halcyon
- Source Code: <https://notabug.org/halcyon-suite/halcyon>
- Contact: [@halcyon@social.csswg.org](https://social.csswg.org/users/halcyon)
@ -107,6 +95,15 @@ Feel free to contact us to be added to this list!
- Features: No Streaming
### Sengi
- Homepage: <https://nicolasconstant.github.io/sengi/>
- Source Code: <https://github.com/NicolasConstant/sengi>
- Contact: [@sengi_app@mastodon.social](https://mastodon.social/users/sengi_app)
- Note(2019-01-28): The development is currently in a early stage.
### DashFE
- Source Code: <https://notabug.org/daisuke/DashboardFE>
- Contact: [@dashfe@stereophonic.space](https://stereophonic.space/users/dashfe)
### BloatFE
- Source Code: <https://git.freesoftwareextremist.com/bloat/>
- Contact: [@r@freesoftwareextremist.com](https://freesoftwareextremist.com/users/r)
- Features: Does not requires JavaScript

View File

@ -552,6 +552,7 @@ the source code is here: [kocaptcha](https://github.com/koto-bank/kocaptcha). Th
* `proxy_remote`: If you're using a remote uploader, Pleroma will proxy media requests instead of redirecting to it.
* `proxy_opts`: Proxy options, see `Pleroma.ReverseProxy` documentation.
* `filename_display_max_length`: Set max length of a filename to display. 0 = no limit. Default: 30.
* `default_description`: Sets which default description an image has if none is set explicitly. Options: nil (default) - Don't set a default, :filename - use the filename of the file, a string (e.g. "attachment") - Use this string
!!! warning
`strip_exif` has been replaced by `Pleroma.Upload.Filter.Mogrify`.
@ -1069,11 +1070,11 @@ Control favicons for instances.
Frontends in Pleroma are swappable - you can specify which one to use here.
For now, you can set a frontend with the key `primary` and the options of `name` and `ref`. This will then make Pleroma serve the frontend from a folder constructed by concatenating the instance static path, `frontends` and the name and ref.
You can set a frontends for the key `primary` and `admin` and the options of `name` and `ref`. This will then make Pleroma serve the frontend from a folder constructed by concatenating the instance static path, `frontends` and the name and ref.
The key `primary` refers to the frontend that will be served by default for general requests. In the future, other frontends like the admin frontend will also be configurable here.
The key `primary` refers to the frontend that will be served by default for general requests. The key `admin` refers to the frontend that will be served at the `/pleroma/admin` path.
If you don't set anything here, the bundled frontend will be used.
If you don't set anything here, the bundled frontends will be used.
Example:
@ -1082,6 +1083,10 @@ config :pleroma, :frontends,
primary: %{
"name" => "pleroma",
"ref" => "stable"
},
admin: %{
"name" => "admin",
"ref" => "develop"
}
```

View File

@ -1,28 +1,27 @@
#!/bin/sh
# REQUIRE: DAEMON postgresql
# $FreeBSD$
# PROVIDE: pleroma
# REQUIRE: DAEMON postgresql
# KEYWORD: shutdown
# sudo -u pleroma MIX_ENV=prod elixir --erl \"-detached\" -S mix phx.server
. /etc/rc.subr
name="pleroma"
name=pleroma
rcvar=pleroma_enable
desc="Pleroma Social Media Platform"
rcvar=${name}_enable
command="/usr/local/bin/elixir"
command_args="--erl \"-detached\" -S /usr/local/bin/mix phx.server"
pidfile="/dev/null"
pleroma_user="pleroma"
pleroma_home="/home/pleroma"
pleroma_chdir="${pleroma_home}/pleroma"
pleroma_env="HOME=${pleroma_home} MIX_ENV=prod"
check_pidfile()
{
pid=$(pgrep beam.smp$)
echo -n "${pid}"
}
load_rc_config ${name}
: ${pleroma_user:=pleroma}
: ${pleroma_home:=$(getent passwd ${pleroma_user} | awk -F: '{print $6}')}
: ${pleroma_chdir:="${pleroma_home}/pleroma"}
: ${pleroma_env:="HOME=${pleroma_home} MIX_ENV=prod"}
command=/usr/local/bin/elixir
command_args="--erl \"-detached\" -S /usr/local/bin/mix phx.server"
procname="*beam.smp"
run_rc_command "$1"

View File

@ -14,7 +14,7 @@ defmodule Mix.Pleroma do
:swoosh,
:timex
]
@cachex_children ["object", "user"]
@cachex_children ["object", "user", "scrubber"]
@doc "Common functions to be reused in mix tasks"
def start_pleroma do
Pleroma.Config.Holder.save_default()

View File

@ -15,7 +15,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
{options, [], []} = parse_global_opts(args)
url_or_path = options[:manifest] || default_manifest()
manifest = fetch_and_decode(url_or_path)
manifest = fetch_and_decode!(url_or_path)
Enum.each(manifest, fn {name, info} ->
to_print = [
@ -42,7 +42,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
url_or_path = options[:manifest] || default_manifest()
manifest = fetch_and_decode(url_or_path)
manifest = fetch_and_decode!(url_or_path)
for pack_name <- pack_names do
if Map.has_key?(manifest, pack_name) do
@ -92,7 +92,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
])
)
files = fetch_and_decode(files_loc)
files = fetch_and_decode!(files_loc)
IO.puts(IO.ANSI.format(["Unpacking ", :bright, pack_name]))
@ -243,9 +243,11 @@ defmodule Mix.Tasks.Pleroma.Emoji do
IO.puts("Emoji packs have been reloaded.")
end
defp fetch_and_decode(from) do
defp fetch_and_decode!(from) do
with {:ok, json} <- fetch(from) do
Jason.decode!(json)
else
{:error, error} -> raise "#{from} cannot be fetched. Error: #{error} occur."
end
end

View File

@ -0,0 +1,140 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Frontend do
use Mix.Task
import Mix.Pleroma
@shortdoc "Manages bundled Pleroma frontends"
@moduledoc File.read!("docs/administration/CLI_tasks/frontend.md")
def run(["install", "none" | _args]) do
shell_info("Skipping frontend installation because none was requested")
"none"
end
def run(["install", frontend | args]) do
log_level = Logger.level()
Logger.configure(level: :warn)
start_pleroma()
{options, [], []} =
OptionParser.parse(
args,
strict: [
ref: :string,
static_dir: :string,
build_url: :string,
build_dir: :string,
file: :string
]
)
instance_static_dir =
with nil <- options[:static_dir] do
Pleroma.Config.get!([:instance, :static_dir])
end
cmd_frontend_info = %{
"name" => frontend,
"ref" => options[:ref],
"build_url" => options[:build_url],
"build_dir" => options[:build_dir]
}
config_frontend_info = Pleroma.Config.get([:frontends, :available, frontend], %{})
frontend_info =
Map.merge(config_frontend_info, cmd_frontend_info, fn _key, config, cmd ->
# This only overrides things that are actually set
cmd || config
end)
ref = frontend_info["ref"]
unless ref do
raise "No ref given or configured"
end
dest =
Path.join([
instance_static_dir,
"frontends",
frontend,
ref
])
fe_label = "#{frontend} (#{ref})"
tmp_dir = Path.join(dest, "tmp")
with {_, :ok} <-
{:download_or_unzip, download_or_unzip(frontend_info, tmp_dir, options[:file])},
shell_info("Installing #{fe_label} to #{dest}"),
:ok <- install_frontend(frontend_info, tmp_dir, dest) do
File.rm_rf!(tmp_dir)
shell_info("Frontend #{fe_label} installed to #{dest}")
Logger.configure(level: log_level)
else
{:download_or_unzip, _} ->
shell_info("Could not download or unzip the frontend")
_e ->
shell_info("Could not install the frontend")
end
end
defp download_or_unzip(frontend_info, temp_dir, file) do
if file do
with {:ok, zip} <- File.read(Path.expand(file)) do
unzip(zip, temp_dir)
end
else
download_build(frontend_info, temp_dir)
end
end
def unzip(zip, dest) do
with {:ok, unzipped} <- :zip.unzip(zip, [:memory]) do
File.rm_rf!(dest)
File.mkdir_p!(dest)
Enum.each(unzipped, fn {filename, data} ->
path = filename
new_file_path = Path.join(dest, path)
new_file_path
|> Path.dirname()
|> File.mkdir_p!()
File.write!(new_file_path, data)
end)
:ok
end
end
defp download_build(frontend_info, dest) do
shell_info("Downloading pre-built bundle for #{frontend_info["name"]}")
url = String.replace(frontend_info["build_url"], "${ref}", frontend_info["ref"])
with {:ok, %{status: 200, body: zip_body}} <-
Pleroma.HTTP.get(url, [], timeout: 120_000, recv_timeout: 120_000) do
unzip(zip_body, dest)
else
e -> {:error, e}
end
end
defp install_frontend(frontend_info, source, dest) do
from = frontend_info["build_dir"] || "dist"
File.mkdir_p!(dest)
File.cp_r!(Path.join([source, from]), dest)
:ok
end
end

View File

@ -35,10 +35,16 @@ defmodule Mix.Tasks.Pleroma.Relay do
def run(["list"]) do
start_pleroma()
with {:ok, list} <- Relay.list(true) do
list |> Enum.each(&shell_info(&1))
with {:ok, list} <- Relay.list() do
Enum.each(list, &print_relay_url/1)
else
{:error, e} -> shell_error("Error while fetching relay subscription list: #{inspect(e)}")
end
end
defp print_relay_url(%{followed_back: false} = relay) do
shell_info("#{relay.actor} - no Accept received (relay didn't follow back)")
end
defp print_relay_url(relay), do: shell_info(relay.actor)
end

View File

@ -107,6 +107,8 @@ defmodule Pleroma.Emails.UserEmail do
|> Enum.filter(&(&1.activity.data["type"] == "Create"))
|> Enum.map(fn notification ->
object = Pleroma.Object.normalize(notification.activity)
if not is_nil(object) do
object = update_in(object.data["content"], &format_links/1)
%{
@ -114,18 +116,25 @@ defmodule Pleroma.Emails.UserEmail do
object: object,
from: User.get_by_ap_id(notification.activity.actor)
}
end
end)
|> Enum.filter(& &1)
followers =
notifications
|> Enum.filter(&(&1.activity.data["type"] == "Follow"))
|> Enum.map(fn notification ->
from = User.get_by_ap_id(notification.activity.actor)
if not is_nil(from) do
%{
data: notification,
object: Pleroma.Object.normalize(notification.activity),
from: User.get_by_ap_id(notification.activity.actor)
}
end
end)
|> Enum.filter(& &1)
unless Enum.empty?(mentions) do
styling = Config.get([__MODULE__, :styling])

View File

@ -264,4 +264,12 @@ defmodule Pleroma.FollowingRelationship do
end
end)
end
@spec following_ap_ids(User.t()) :: [String.t()]
def following_ap_ids(%User{} = user) do
user
|> following_query()
|> select([r, u], u.ap_id)
|> Repo.all()
end
end

View File

@ -30,6 +30,7 @@ defmodule Pleroma.Plugs.FrontendStatic do
opts
|> Keyword.put(:from, "__unconfigured_frontend_static_plug")
|> Plug.Static.init()
|> Map.put(:frontend_type, opts[:frontend_type])
end
def call(conn, opts) do

View File

@ -56,6 +56,15 @@ defmodule Pleroma.Upload do
}
defstruct [:id, :name, :tempfile, :content_type, :path]
defp get_description(opts, upload) do
case {opts[:description], Pleroma.Config.get([Pleroma.Upload, :default_description])} do
{description, _} when is_binary(description) -> description
{_, :filename} -> upload.name
{_, str} when is_binary(str) -> str
_ -> ""
end
end
@spec store(source, options :: [option()]) :: {:ok, Map.t()} | {:error, any()}
def store(upload, opts \\ []) do
opts = get_opts(opts)
@ -63,7 +72,7 @@ defmodule Pleroma.Upload do
with {:ok, upload} <- prepare_upload(upload, opts),
upload = %__MODULE__{upload | path: upload.path || "#{upload.id}/#{upload.name}"},
{:ok, upload} <- Pleroma.Upload.Filter.filter(opts.filters, upload),
description = Map.get(opts, :description) || upload.name,
description = get_description(opts, upload),
{_, true} <-
{:description_limit,
String.length(description) <= Pleroma.Config.get([:instance, :description_limit])},

View File

@ -247,6 +247,13 @@ defmodule Pleroma.User do
end
end
defdelegate following_count(user), to: FollowingRelationship
defdelegate following(user), to: FollowingRelationship
defdelegate following?(follower, followed), to: FollowingRelationship
defdelegate following_ap_ids(user), to: FollowingRelationship
defdelegate get_follow_requests(user), to: FollowingRelationship
defdelegate search(query, opts \\ []), to: User.Search
@doc """
Dumps Flake Id to SQL-compatible format (16-byte UUID).
E.g. "9pQtDGXuq4p3VlcJEm" -> <<0, 0, 1, 110, 179, 218, 42, 92, 213, 41, 44, 227, 95, 213, 0, 0>>
@ -372,8 +379,6 @@ defmodule Pleroma.User do
from(u in query, where: u.deactivated != ^true)
end
defdelegate following_count(user), to: FollowingRelationship
defp truncate_fields_param(params) do
if Map.has_key?(params, :fields) do
Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
@ -868,8 +873,6 @@ defmodule Pleroma.User do
set_cache(follower)
end
defdelegate following(user), to: FollowingRelationship
def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
@ -923,8 +926,6 @@ defmodule Pleroma.User do
end
end
defdelegate following?(follower, followed), to: FollowingRelationship
@doc "Returns follow state as Pleroma.FollowingRelationship.State value"
def get_follow_state(%User{} = follower, %User{} = following) do
following_relationship = FollowingRelationship.get(follower, following)
@ -1189,8 +1190,6 @@ defmodule Pleroma.User do
|> Repo.all()
end
defdelegate get_follow_requests(user), to: FollowingRelationship
def increase_note_count(%User{} = user) do
User
|> where(id: ^user.id)
@ -2163,8 +2162,6 @@ defmodule Pleroma.User do
|> Repo.all()
end
defdelegate search(query, opts \\ []), to: User.Search
defp put_password_hash(
%Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
) do

View File

@ -85,7 +85,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp increase_replies_count_if_reply(_create_data), do: :noop
@object_types ["ChatMessage", "Question", "Answer"]
@object_types ~w[ChatMessage Question Answer Audio Event]
@spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()}
def persist(%{"type" => type} = object, meta) when type in @object_types do
with {:ok, object} <- Object.create(object) do
@ -1344,9 +1344,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
def maybe_handle_clashing_nickname(data) do
nickname = data[:nickname]
with %User{} = old_user <- User.get_by_nickname(nickname),
with nickname when is_binary(nickname) <- data[:nickname],
%User{} = old_user <- User.get_by_nickname(nickname),
{_, false} <- {:ap_id_comparison, data[:ap_id] == old_user.ap_id} do
Logger.info(
"Found an old user for #{nickname}, the old ap id is #{old_user.ap_id}, new one is #{
@ -1360,7 +1359,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
else
{:ap_id_comparison, true} ->
Logger.info(
"Found an old user for #{nickname}, but the ap id #{data[:ap_id]} is the same as the new user. Race condition? Not changing anything."
"Found an old user for #{data[:nickname]}, but the ap id #{data[:ap_id]} is the same as the new user. Race condition? Not changing anything."
)
_ ->

View File

@ -215,7 +215,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
to =
cond do
actor.ap_id == Relay.relay_ap_id() ->
actor.ap_id == Relay.ap_id() ->
[actor.follower_address]
public? ->

View File

@ -23,6 +23,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.EventValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator
@ -43,6 +44,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
end
end
def validate(%{"type" => "Event"} = object, meta) do
with {:ok, object} <-
object
|> EventValidator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert) do
object = stringify_keys(object)
{:ok, object, meta}
end
end
def validate(%{"type" => "Follow"} = object, meta) do
with {:ok, object} <-
object
@ -187,7 +198,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
%{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity,
meta
)
when objtype in ~w[Question Answer Audio] do
when objtype in ~w[Question Answer Audio Event] do
with {:ok, object_data} <- cast_and_apply(object),
meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
{:ok, create_activity} <-
@ -225,6 +236,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
AudioValidator.cast_and_apply(object)
end
def cast_and_apply(%{"type" => "Event"} = object) do
EventValidator.cast_and_apply(object)
end
def cast_and_apply(o), do: {:error, {:validator_not_set, o}}
# is_struct/1 isn't present in Elixir 1.8.x

View File

@ -41,34 +41,34 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
end
def fix_media_type(data) do
data =
data
|> Map.put_new("mediaType", data["mimeType"])
data = Map.put_new(data, "mediaType", data["mimeType"])
if MIME.valid?(data["mediaType"]) do
data
else
data
|> Map.put("mediaType", "application/octet-stream")
Map.put(data, "mediaType", "application/octet-stream")
end
end
def fix_url(data) do
case data["url"] do
url when is_binary(url) ->
data
|> Map.put(
"url",
defp handle_href(href, mediaType) do
[
%{
"href" => url,
"href" => href,
"type" => "Link",
"mediaType" => data["mediaType"]
"mediaType" => mediaType
}
]
)
end
_ ->
defp fix_url(data) do
cond do
is_binary(data["url"]) ->
Map.put(data, "url", handle_href(data["url"], data["mediaType"]))
is_binary(data["href"]) and data["url"] == nil ->
Map.put(data, "url", handle_href(data["href"], data["mediaType"]))
true ->
data
end
end

View File

@ -41,7 +41,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator do
field(:like_count, :integer, default: 0)
field(:announcement_count, :integer, default: 0)
field(:inReplyTo, :string)
field(:uri, ObjectValidators.Uri)
field(:url, ObjectValidators.Uri)
# short identifier for PleromaFE to group statuses by context
field(:context_id, :integer)
@ -66,10 +66,24 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator do
|> changeset(data)
end
defp fix_url(%{"url" => url} = data) when is_list(url) do
attachment =
Enum.find(url, fn x -> is_map(x) and String.starts_with?(x["mimeType"], "audio/") end)
link_element = Enum.find(url, fn x -> is_map(x) and x["mimeType"] == "text/html" end)
data
|> Map.put("attachment", [attachment])
|> Map.put("url", link_element["href"])
end
defp fix_url(data), do: data
defp fix(data) do
data
|> CommonFixes.fix_defaults()
|> CommonFixes.fix_attribution()
|> fix_url()
end
def changeset(struct, data) do
@ -83,7 +97,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator do
def validate_data(data_cng) do
data_cng
|> validate_inclusion(:type, ["Audio"])
|> validate_required([:id, :actor, :attributedTo, :type, :context])
|> validate_required([:id, :actor, :attributedTo, :type, :context, :attachment])
|> CommonValidations.validate_any_presence([:cc, :to])
|> CommonValidations.validate_fields_match([:actor, :attributedTo])
|> CommonValidations.validate_actor_presence()

View File

@ -61,9 +61,20 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
end
end
defp fix_addressing(data, meta) do
if object = meta[:object_data] do
data
|> Map.put_new("to", object["to"] || [])
|> Map.put_new("cc", object["cc"] || [])
else
data
end
end
defp fix(data, meta) do
data
|> fix_context(meta)
|> fix_addressing(meta)
end
def validate_data(cng, meta \\ []) do

View File

@ -0,0 +1,96 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
import Ecto.Changeset
@primary_key false
@derive Jason.Encoder
# Extends from NoteValidator
embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: [])
field(:bto, ObjectValidators.Recipients, default: [])
field(:bcc, ObjectValidators.Recipients, default: [])
# TODO: Write type
field(:tag, {:array, :map}, default: [])
field(:type, :string)
field(:name, :string)
field(:summary, :string)
field(:content, :string)
field(:context, :string)
# short identifier for PleromaFE to group statuses by context
field(:context_id, :integer)
# TODO: Remove actor on objects
field(:actor, ObjectValidators.ObjectID)
field(:attributedTo, ObjectValidators.ObjectID)
field(:published, ObjectValidators.DateTime)
# TODO: Write type
field(:emoji, :map, default: %{})
field(:sensitive, :boolean, default: false)
embeds_many(:attachment, AttachmentValidator)
field(:replies_count, :integer, default: 0)
field(:like_count, :integer, default: 0)
field(:announcement_count, :integer, default: 0)
field(:inReplyTo, ObjectValidators.ObjectID)
field(:url, ObjectValidators.Uri)
field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
end
def cast_and_apply(data) do
data
|> cast_data
|> apply_action(:insert)
end
def cast_and_validate(data) do
data
|> cast_data()
|> validate_data()
end
def cast_data(data) do
%__MODULE__{}
|> changeset(data)
end
defp fix(data) do
data
|> CommonFixes.fix_defaults()
|> CommonFixes.fix_attribution()
end
def changeset(struct, data) do
data = fix(data)
struct
|> cast(data, __schema__(:fields) -- [:attachment])
|> cast_embed(:attachment)
end
def validate_data(data_cng) do
data_cng
|> validate_inclusion(:type, ["Event"])
|> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])
|> CommonValidations.validate_any_presence([:cc, :to])
|> CommonValidations.validate_fields_match([:actor, :attributedTo])
|> CommonValidations.validate_actor_presence()
|> CommonValidations.validate_host_match()
end
end

View File

@ -20,11 +20,17 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do
# TODO: Write type
field(:tag, {:array, :map}, default: [])
field(:type, :string)
field(:name, :string)
field(:summary, :string)
field(:content, :string)
field(:context, :string)
# short identifier for PleromaFE to group statuses by context
field(:context_id, :integer)
field(:actor, ObjectValidators.ObjectID)
field(:attributedTo, ObjectValidators.ObjectID)
field(:summary, :string)
field(:published, ObjectValidators.DateTime)
# TODO: Write type
field(:emoji, :map, default: %{})
@ -35,13 +41,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do
field(:like_count, :integer, default: 0)
field(:announcement_count, :integer, default: 0)
field(:inReplyTo, ObjectValidators.ObjectID)
field(:uri, ObjectValidators.Uri)
field(:url, ObjectValidators.Uri)
field(:likes, {:array, :string}, default: [])
field(:announcements, {:array, :string}, default: [])
# see if needed
field(:context_id, :string)
end
def cast_and_validate(data) do

View File

@ -43,7 +43,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
field(:like_count, :integer, default: 0)
field(:announcement_count, :integer, default: 0)
field(:inReplyTo, ObjectValidators.ObjectID)
field(:uri, ObjectValidators.Uri)
field(:url, ObjectValidators.Uri)
# short identifier for PleromaFE to group statuses by context
field(:context_id, :integer)

View File

@ -10,19 +10,13 @@ defmodule Pleroma.Web.ActivityPub.Relay do
alias Pleroma.Web.CommonAPI
require Logger
@relay_nickname "relay"
@nickname "relay"
def get_actor do
actor =
relay_ap_id()
|> User.get_or_create_service_actor_by_ap_id(@relay_nickname)
@spec ap_id() :: String.t()
def ap_id, do: "#{Pleroma.Web.Endpoint.url()}/#{@nickname}"
actor
end
def relay_ap_id do
"#{Pleroma.Web.Endpoint.url()}/relay"
end
@spec get_actor() :: User.t() | nil
def get_actor, do: User.get_or_create_service_actor_by_ap_id(ap_id(), @nickname)
@spec follow(String.t()) :: {:ok, Activity.t()} | {:error, any()}
def follow(target_instance) do
@ -61,34 +55,38 @@ defmodule Pleroma.Web.ActivityPub.Relay do
def publish(_), do: {:error, "Not implemented"}
@spec list(boolean()) :: {:ok, [String.t()]} | {:error, any()}
def list(with_not_accepted \\ false) do
@spec list() :: {:ok, [%{actor: String.t(), followed_back: boolean()}]} | {:error, any()}
def list do
with %User{} = user <- get_actor() do
accepted =
user
|> User.following()
|> Enum.map(fn entry -> URI.parse(entry).host end)
|> Enum.uniq()
|> following()
|> Enum.map(fn actor -> %{actor: actor, followed_back: true} end)
list =
if with_not_accepted do
without_accept =
user
|> Pleroma.Activity.following_requests_for_actor()
|> Enum.map(fn a -> URI.parse(a.data["object"]).host <> " (no Accept received)" end)
|> Enum.map(fn activity -> %{actor: activity.data["object"], followed_back: false} end)
|> Enum.uniq()
accepted ++ without_accept
else
accepted
end
{:ok, list}
{:ok, accepted ++ without_accept}
else
error -> format_error(error)
end
end
@spec following() :: [String.t()]
def following do
get_actor()
|> following()
end
defp following(user) do
user
|> User.following_ap_ids()
|> Enum.uniq()
end
defp format_error({:error, error}), do: format_error(error)
defp format_error(error) do

View File

@ -341,7 +341,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
end
def handle_object_creation(%{"type" => objtype} = object, meta)
when objtype in ~w[Audio Question] do
when objtype in ~w[Audio Question Event] do
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
{:ok, object, meta}
end

View File

@ -276,13 +276,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
Map.put(object, "url", url["href"])
end
def fix_url(%{"type" => object_type, "url" => url} = object)
when object_type in ["Video", "Audio"] and is_list(url) do
def fix_url(%{"type" => "Video", "url" => url} = object) when is_list(url) do
attachment =
Enum.find(url, fn x ->
media_type = x["mediaType"] || x["mimeType"] || ""
is_map(x) and String.starts_with?(media_type, ["audio/", "video/"])
is_map(x) and String.starts_with?(media_type, "video/")
end)
link_element =
@ -461,7 +460,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
%{"type" => "Create", "object" => %{"type" => objtype} = object} = data,
options
)
when objtype in ~w{Article Event Note Video Page} do
when objtype in ~w{Article Note Video Page} do
actor = Containment.get_actor(data)
with nil <- Activity.get_create_by_object_ap_id(object["id"]),
@ -555,7 +554,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
%{"type" => "Create", "object" => %{"type" => objtype}} = data,
_options
)
when objtype in ~w{Question Answer ChatMessage Audio} do
when objtype in ~w{Question Answer ChatMessage Audio Event} do
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
{:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
{:ok, activity}

View File

@ -39,7 +39,7 @@ defmodule Pleroma.Web.AdminAPI.RelayController do
target: target
})
json(conn, target)
json(conn, %{actor: target, followed_back: target in Relay.following()})
else
_ ->
conn

View File

@ -79,7 +79,8 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
"confirmation_pending" => user.confirmation_pending,
"approval_pending" => user.approval_pending,
"url" => user.uri || user.ap_id,
"registration_reason" => user.registration_reason
"registration_reason" => user.registration_reason,
"actor_type" => user.actor_type
}
end

View File

@ -27,8 +27,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do
properties: %{
relays: %Schema{
type: :array,
items: %Schema{type: :string},
example: ["lain.com", "mstdn.io"]
items: relay()
}
}
})
@ -43,19 +42,9 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do
operationId: "AdminAPI.RelayController.follow",
security: [%{"oAuth" => ["write:follows"]}],
parameters: admin_api_params(),
requestBody:
request_body("Parameters", %Schema{
type: :object,
properties: %{
relay_url: %Schema{type: :string, format: :uri}
}
}),
requestBody: request_body("Parameters", relay_url()),
responses: %{
200 =>
Operation.response("Status", "application/json", %Schema{
type: :string,
example: "http://mastodon.example.org/users/admin"
})
200 => Operation.response("Status", "application/json", relay())
}
}
end
@ -67,13 +56,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do
operationId: "AdminAPI.RelayController.unfollow",
security: [%{"oAuth" => ["write:follows"]}],
parameters: admin_api_params(),
requestBody:
request_body("Parameters", %Schema{
type: :object,
properties: %{
relay_url: %Schema{type: :string, format: :uri}
}
}),
requestBody: request_body("Parameters", relay_url()),
responses: %{
200 =>
Operation.response("Status", "application/json", %Schema{
@ -83,4 +66,29 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do
}
}
end
defp relay do
%Schema{
type: :object,
properties: %{
actor: %Schema{
type: :string,
example: "https://example.com/relay"
},
followed_back: %Schema{
type: :boolean,
description: "Is relay followed back by this actor?"
}
}
}
end
defp relay_url do
%Schema{
type: :object,
properties: %{
relay_url: %Schema{type: :string, format: :uri}
}
}
end
end

View File

@ -39,6 +39,18 @@ defmodule Pleroma.Web.Endpoint do
}
)
plug(Plug.Static.IndexHtml, at: "/pleroma/admin/")
plug(Pleroma.Plugs.FrontendStatic,
at: "/pleroma/admin",
frontend_type: :admin,
gzip: true,
cache_control_for_etags: @static_cache_control,
headers: %{
"cache-control" => @static_cache_control
}
)
# Serve at "/" the static files from "priv/static" directory.
#
# You should set gzip to true if you are running phoenix.digest
@ -56,8 +68,6 @@ defmodule Pleroma.Web.Endpoint do
}
)
plug(Plug.Static.IndexHtml, at: "/pleroma/admin/")
plug(Plug.Static,
at: "/pleroma/admin/",
from: {:pleroma, "priv/static/adminfe/"}

View File

@ -474,23 +474,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
end
end
def render_content(%{data: %{"type" => object_type}} = object)
when object_type in ["Video", "Event", "Audio"] do
with name when not is_nil(name) and name != "" <- object.data["name"] do
"<p><a href=\"#{object.data["id"]}\">#{name}</a></p>#{object.data["content"]}"
else
_ -> object.data["content"] || ""
end
end
def render_content(%{data: %{"name" => name}} = object) when not is_nil(name) and name != "" do
url = object.data["url"] || object.data["id"]
def render_content(%{data: %{"type" => object_type}} = object)
when object_type in ["Article", "Page"] do
with summary when not is_nil(summary) and summary != "" <- object.data["name"],
url when is_bitstring(url) <- object.data["url"] do
"<p><a href=\"#{url}\">#{summary}</a></p>#{object.data["content"]}"
else
_ -> object.data["content"] || ""
end
"<p><a href=\"#{url}\">#{name}</a></p>#{object.data["content"]}"
end
def render_content(object), do: object.data["content"] || ""

View File

@ -14,10 +14,10 @@ defmodule Pleroma.Emails.MailerTest do
subject: "Pleroma test email",
to: [{"Test User", "user1@example.com"}]
}
setup do: clear_config([Pleroma.Emails.Mailer, :enabled])
setup do: clear_config([Pleroma.Emails.Mailer, :enabled], true)
test "not send email when mailer is disabled" do
Pleroma.Config.put([Pleroma.Emails.Mailer, :enabled], false)
clear_config([Pleroma.Emails.Mailer, :enabled], false)
Mailer.deliver(@email)
:timer.sleep(100)

View File

@ -0,0 +1 @@
this is a text file

BIN
test/fixtures/tesla_mock/frontend.zip vendored Normal file

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,58 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
"https://funkwhale.audio/ns",
{
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"Hashtag": "as:Hashtag"
}
],
"type": "Create",
"id": "https://channels.tests.funkwhale.audio/federation/music/uploads/42342395-0208-4fee-a38d-259a6dae0871/activity",
"actor": "https://channels.tests.funkwhale.audio/federation/actors/compositions",
"object": {
"id": "https://channels.tests.funkwhale.audio/federation/music/uploads/42342395-0208-4fee-a38d-259a6dae0871",
"type": "Audio",
"name": "Compositions - Test Audio for Pleroma",
"attributedTo": "https://channels.tests.funkwhale.audio/federation/actors/compositions",
"published": "2020-03-11T10:01:52.714918+00:00",
"to": "https://www.w3.org/ns/activitystreams#Public",
"url": [
{
"type": "Link",
"mimeType": "audio/ogg",
"href": "https://channels.tests.funkwhale.audio/api/v1/listen/3901e5d8-0445-49d5-9711-e096cf32e515/?upload=42342395-0208-4fee-a38d-259a6dae0871&download=false"
},
{
"type": "Link",
"mimeType": "text/html",
"href": "https://channels.tests.funkwhale.audio/library/tracks/74"
}
],
"content": "<p>This is a test Audio for Pleroma.</p>",
"mediaType": "text/html",
"tag": [
{
"type": "Hashtag",
"name": "#funkwhale"
},
{
"type": "Hashtag",
"name": "#test"
},
{
"type": "Hashtag",
"name": "#tests"
}
],
"summary": "#funkwhale #test #tests",
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers"
}
]
}
}

View File

@ -3,6 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.FrontendStaticPlugTest do
alias Pleroma.Plugs.FrontendStatic
use Pleroma.Web.ConnCase
@dir "test/tmp/instance_static"
@ -14,6 +15,18 @@ defmodule Pleroma.Web.FrontendStaticPlugTest do
setup do: clear_config([:instance, :static_dir], @dir)
test "init will give a static plug config + the frontend type" do
opts =
[
at: "/admin",
frontend_type: :admin
]
|> FrontendStatic.init()
assert opts[:at] == ["admin"]
assert opts[:frontend_type] == :admin
end
test "overrides existing static files", %{conn: conn} do
name = "pelmora"
ref = "uguu"
@ -27,4 +40,18 @@ defmodule Pleroma.Web.FrontendStaticPlugTest do
index = get(conn, "/")
assert html_response(index, 200) == "from frontend plug"
end
test "overrides existing static files for the `pleroma/admin` path", %{conn: conn} do
name = "pelmora"
ref = "uguu"
clear_config([:frontends, :admin], %{"name" => name, "ref" => ref})
path = "#{@dir}/frontends/#{name}/#{ref}"
File.mkdir_p!(path)
File.write!("#{path}/index.html", "from frontend plug")
index = get(conn, "/pleroma/admin/")
assert html_response(index, 200) == "from frontend plug"
end
end

View File

@ -17,6 +17,8 @@ defmodule Mix.Tasks.Pleroma.DigestTest do
:ok
end
setup do: clear_config([Pleroma.Emails.Mailer, :enabled], true)
describe "pleroma.digest test" do
test "Sends digest to the given user" do
user1 = insert(:user)

View File

@ -16,6 +16,8 @@ defmodule Mix.Tasks.Pleroma.EmailTest do
:ok
end
setup do: clear_config([Pleroma.Emails.Mailer, :enabled], true)
describe "pleroma.email test" do
test "Sends test email with no given address" do
mail_to = Config.get([:instance, :email])

View File

@ -0,0 +1,78 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.FrontendTest do
use Pleroma.DataCase
alias Mix.Tasks.Pleroma.Frontend
import ExUnit.CaptureIO, only: [capture_io: 1]
@dir "test/frontend_static_test"
setup do
File.mkdir_p!(@dir)
clear_config([:instance, :static_dir], @dir)
on_exit(fn ->
File.rm_rf(@dir)
end)
end
test "it downloads and unzips a known frontend" do
clear_config([:frontends, :available], %{
"pleroma" => %{
"ref" => "fantasy",
"name" => "pleroma",
"build_url" => "http://gensokyo.2hu/builds/${ref}"
}
})
Tesla.Mock.mock(fn %{url: "http://gensokyo.2hu/builds/fantasy"} ->
%Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/frontend_dist.zip")}
end)
capture_io(fn ->
Frontend.run(["install", "pleroma"])
end)
assert File.exists?(Path.join([@dir, "frontends", "pleroma", "fantasy", "test.txt"]))
end
test "it also works given a file" do
clear_config([:frontends, :available], %{
"pleroma" => %{
"ref" => "fantasy",
"name" => "pleroma",
"build_dir" => ""
}
})
capture_io(fn ->
Frontend.run(["install", "pleroma", "--file", "test/fixtures/tesla_mock/frontend.zip"])
end)
assert File.exists?(Path.join([@dir, "frontends", "pleroma", "fantasy", "test.txt"]))
end
test "it downloads and unzips unknown frontends" do
Tesla.Mock.mock(fn %{url: "http://gensokyo.2hu/madeup.zip"} ->
%Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/frontend.zip")}
end)
capture_io(fn ->
Frontend.run([
"install",
"unknown",
"--ref",
"baka",
"--build-url",
"http://gensokyo.2hu/madeup.zip",
"--build-dir",
""
])
end)
assert File.exists?(Path.join([@dir, "frontends", "unknown", "baka", "test.txt"]))
end
end

View File

@ -42,7 +42,11 @@ defmodule Mix.Tasks.Pleroma.RelayTest do
assert activity.data["object"] == target_user.ap_id
:ok = Mix.Tasks.Pleroma.Relay.run(["list"])
assert_receive {:mix_shell, :info, ["mastodon.example.org (no Accept received)"]}
assert_receive {:mix_shell, :info,
[
"http://mastodon.example.org/users/admin - no Accept received (relay didn't follow back)"
]}
end
end
@ -95,8 +99,8 @@ defmodule Mix.Tasks.Pleroma.RelayTest do
:ok = Mix.Tasks.Pleroma.Relay.run(["list"])
assert_receive {:mix_shell, :info, ["mstdn.io"]}
assert_receive {:mix_shell, :info, ["mastodon.example.org"]}
assert_receive {:mix_shell, :info, ["https://mstdn.io/users/mayuutann"]}
assert_receive {:mix_shell, :info, ["http://mastodon.example.org/users/admin"]}
end
end
end

View File

@ -533,7 +533,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
end)
:ok = Mix.Tasks.Pleroma.Relay.run(["list"])
assert_receive {:mix_shell, :info, ["relay.mastodon.host"]}
assert_receive {:mix_shell, :info, ["https://relay.mastodon.host/actor"]}
end
@tag capture_log: true

View File

@ -990,13 +990,39 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
end
describe "uploading files" do
test "copies the file to the configured folder" do
file = %Plug.Upload{
setup do
test_file = %Plug.Upload{
content_type: "image/jpg",
path: Path.absname("test/fixtures/image.jpg"),
filename: "an_image.jpg"
}
%{test_file: test_file}
end
test "sets a description if given", %{test_file: file} do
{:ok, %Object{} = object} = ActivityPub.upload(file, description: "a cool file")
assert object.data["name"] == "a cool file"
end
test "it sets the default description depending on the configuration", %{test_file: file} do
clear_config([Pleroma.Upload, :default_description])
Pleroma.Config.put([Pleroma.Upload, :default_description], nil)
{:ok, %Object{} = object} = ActivityPub.upload(file)
assert object.data["name"] == ""
Pleroma.Config.put([Pleroma.Upload, :default_description], :filename)
{:ok, %Object{} = object} = ActivityPub.upload(file)
assert object.data["name"] == "an_image.jpg"
Pleroma.Config.put([Pleroma.Upload, :default_description], "unnamed attachment")
{:ok, %Object{} = object} = ActivityPub.upload(file)
assert object.data["name"] == "unnamed attachment"
end
test "copies the file to the configured folder", %{test_file: file} do
clear_config([Pleroma.Upload, :default_description], :filename)
{:ok, %Object{} = object} = ActivityPub.upload(file)
assert object.data["name"] == "an_image.jpg"
end

View File

@ -42,4 +42,42 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.AudioHandlingTest do
assert object.data["album"] == "lain radio"
assert object.data["length"] == 180_000
end
test "Funkwhale Audio object" do
Tesla.Mock.mock(fn
%{url: "https://channels.tests.funkwhale.audio/federation/actors/compositions"} ->
%Tesla.Env{
status: 200,
body: File.read!("test/fixtures/tesla_mock/funkwhale_channel.json")
}
end)
data = File.read!("test/fixtures/tesla_mock/funkwhale_create_audio.json") |> Poison.decode!()
{:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
assert object = Object.normalize(activity, false)
assert object.data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
assert object.data["cc"] == []
assert object.data["url"] == "https://channels.tests.funkwhale.audio/library/tracks/74"
assert object.data["attachment"] == [
%{
"mediaType" => "audio/ogg",
"type" => "Link",
"name" => nil,
"url" => [
%{
"href" =>
"https://channels.tests.funkwhale.audio/api/v1/listen/3901e5d8-0445-49d5-9711-e096cf32e515/?upload=42342395-0208-4fee-a38d-259a6dae0871&download=false",
"mediaType" => "audio/ogg",
"type" => "Link"
}
]
}
]
end
end

View File

@ -0,0 +1,40 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Transmogrifier.EventHandlingTest do
use Oban.Testing, repo: Pleroma.Repo
use Pleroma.DataCase
alias Pleroma.Object.Fetcher
test "Mobilizon Event object" do
Tesla.Mock.mock(fn
%{url: "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"} ->
%Tesla.Env{
status: 200,
body: File.read!("test/fixtures/tesla_mock/mobilizon.org-event.json")
}
%{url: "https://mobilizon.org/@tcit"} ->
%Tesla.Env{
status: 200,
body: File.read!("test/fixtures/tesla_mock/mobilizon.org-user.json")
}
end)
assert {:ok, object} =
Fetcher.fetch_object_from_id(
"https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
)
assert object.data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
assert object.data["cc"] == []
assert object.data["url"] ==
"https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
assert object.data["published"] == "2019-12-17T11:33:56Z"
assert object.data["name"] == "Mobilizon Launching Party"
end
end

View File

@ -24,6 +24,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.QuestionHandlingTest do
object = Object.normalize(activity, false)
assert object.data["url"] == "https://mastodon.sdf.org/@rinpatch/102070944809637304"
assert object.data["closed"] == "2019-05-11T09:03:36Z"
assert object.data["context"] == activity.data["context"]

View File

@ -381,7 +381,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"confirmation_pending" => false,
"approval_pending" => false,
"url" => user.ap_id,
"registration_reason" => nil
"registration_reason" => nil,
"actor_type" => "Person"
}
assert expected == json_response(conn, 200)
@ -663,7 +664,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"confirmation_pending" => false,
"approval_pending" => false,
"url" => admin.ap_id,
"registration_reason" => nil
"registration_reason" => nil,
"actor_type" => "Person"
},
%{
"deactivated" => user.deactivated,
@ -677,7 +679,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"confirmation_pending" => false,
"approval_pending" => false,
"url" => user.ap_id,
"registration_reason" => nil
"registration_reason" => nil,
"actor_type" => "Person"
},
%{
"deactivated" => user2.deactivated,
@ -691,7 +694,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"confirmation_pending" => false,
"approval_pending" => true,
"url" => user2.ap_id,
"registration_reason" => "I'm a chill dude"
"registration_reason" => "I'm a chill dude",
"actor_type" => "Person"
}
]
|> Enum.sort_by(& &1["nickname"])
@ -766,7 +770,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"confirmation_pending" => false,
"approval_pending" => false,
"url" => user.ap_id,
"registration_reason" => nil
"registration_reason" => nil,
"actor_type" => "Person"
}
]
}
@ -794,7 +799,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"confirmation_pending" => false,
"approval_pending" => false,
"url" => user.ap_id,
"registration_reason" => nil
"registration_reason" => nil,
"actor_type" => "Person"
}
]
}
@ -822,7 +828,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"confirmation_pending" => false,
"approval_pending" => false,
"url" => user.ap_id,
"registration_reason" => nil
"registration_reason" => nil,
"actor_type" => "Person"
}
]
}
@ -850,7 +857,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"confirmation_pending" => false,
"approval_pending" => false,
"url" => user.ap_id,
"registration_reason" => nil
"registration_reason" => nil,
"actor_type" => "Person"
}
]
}
@ -878,7 +886,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"confirmation_pending" => false,
"approval_pending" => false,
"url" => user.ap_id,
"registration_reason" => nil
"registration_reason" => nil,
"actor_type" => "Person"
}
]
}
@ -906,7 +915,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"confirmation_pending" => false,
"approval_pending" => false,
"url" => user.ap_id,
"registration_reason" => nil
"registration_reason" => nil,
"actor_type" => "Person"
}
]
}
@ -929,7 +939,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"confirmation_pending" => false,
"approval_pending" => false,
"url" => user2.ap_id,
"registration_reason" => nil
"registration_reason" => nil,
"actor_type" => "Person"
}
]
}
@ -964,7 +975,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"confirmation_pending" => false,
"approval_pending" => false,
"url" => user.ap_id,
"registration_reason" => nil
"registration_reason" => nil,
"actor_type" => "Person"
}
]
}
@ -992,7 +1004,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"confirmation_pending" => false,
"approval_pending" => false,
"url" => user.ap_id,
"registration_reason" => nil
"registration_reason" => nil,
"actor_type" => "Person"
},
%{
"deactivated" => admin.deactivated,
@ -1006,7 +1019,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"confirmation_pending" => false,
"approval_pending" => false,
"url" => admin.ap_id,
"registration_reason" => nil
"registration_reason" => nil,
"actor_type" => "Person"
},
%{
"deactivated" => false,
@ -1020,7 +1034,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"confirmation_pending" => false,
"approval_pending" => false,
"url" => old_admin.ap_id,
"registration_reason" => nil
"registration_reason" => nil,
"actor_type" => "Person"
}
]
|> Enum.sort_by(& &1["nickname"])
@ -1058,7 +1073,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"confirmation_pending" => false,
"approval_pending" => true,
"url" => user.ap_id,
"registration_reason" => "Plz let me in!"
"registration_reason" => "Plz let me in!",
"actor_type" => "Person"
}
]
|> Enum.sort_by(& &1["nickname"])
@ -1091,7 +1107,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"confirmation_pending" => false,
"approval_pending" => false,
"url" => admin.ap_id,
"registration_reason" => nil
"registration_reason" => nil,
"actor_type" => "Person"
},
%{
"deactivated" => false,
@ -1105,7 +1122,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"confirmation_pending" => false,
"approval_pending" => false,
"url" => second_admin.ap_id,
"registration_reason" => nil
"registration_reason" => nil,
"actor_type" => "Person"
}
]
|> Enum.sort_by(& &1["nickname"])
@ -1140,7 +1158,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"confirmation_pending" => false,
"approval_pending" => false,
"url" => moderator.ap_id,
"registration_reason" => nil
"registration_reason" => nil,
"actor_type" => "Person"
}
]
}
@ -1168,7 +1187,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"confirmation_pending" => false,
"approval_pending" => false,
"url" => user1.ap_id,
"registration_reason" => nil
"registration_reason" => nil,
"actor_type" => "Person"
},
%{
"deactivated" => false,
@ -1182,7 +1202,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"confirmation_pending" => false,
"approval_pending" => false,
"url" => user2.ap_id,
"registration_reason" => nil
"registration_reason" => nil,
"actor_type" => "Person"
}
]
|> Enum.sort_by(& &1["nickname"])
@ -1245,7 +1266,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"confirmation_pending" => false,
"approval_pending" => false,
"url" => user.ap_id,
"registration_reason" => nil
"registration_reason" => nil,
"actor_type" => "Person"
}
]
}
@ -1272,7 +1294,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"confirmation_pending" => false,
"approval_pending" => false,
"url" => admin.ap_id,
"registration_reason" => nil
"registration_reason" => nil,
"actor_type" => "Person"
}
]
}
@ -1357,7 +1380,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"confirmation_pending" => false,
"approval_pending" => false,
"url" => user.ap_id,
"registration_reason" => nil
"registration_reason" => nil,
"actor_type" => "Person"
}
log_entry = Repo.one(ModerationLog)

View File

@ -39,8 +39,10 @@ defmodule Pleroma.Web.AdminAPI.RelayControllerTest do
relay_url: "http://mastodon.example.org/users/admin"
})
assert json_response_and_validate_schema(conn, 200) ==
"http://mastodon.example.org/users/admin"
assert json_response_and_validate_schema(conn, 200) == %{
"actor" => "http://mastodon.example.org/users/admin",
"followed_back" => false
}
log_entry = Repo.one(ModerationLog)
@ -59,8 +61,13 @@ defmodule Pleroma.Web.AdminAPI.RelayControllerTest do
conn = get(conn, "/api/pleroma/admin/relay")
assert json_response_and_validate_schema(conn, 200)["relays"] --
["mastodon.example.org", "mstdn.io"] == []
assert json_response_and_validate_schema(conn, 200)["relays"] == [
%{
"actor" => "http://mastodon.example.org/users/admin",
"followed_back" => true
},
%{"actor" => "https://mstdn.io/users/mayuutann", "followed_back" => true}
]
end
test "DELETE /relay", %{conn: conn, admin: admin} do

View File

@ -517,6 +517,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
represented = StatusView.render("show.json", %{for: user, activity: activity})
assert represented[:id] == to_string(activity.id)
assert represented[:url] ==
"https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
assert represented[:content] ==
"<p><a href=\"https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39\">Mobilizon Launching Party</a></p><p>Mobilizon is now federated! 🎉</p><p></p><p>You can view this event from other instances if they are subscribed to mobilizon.org, and soon directly from Mastodon and Pleroma. It is possible that you may see some comments from other instances, including Mastodon ones, just below.</p><p></p><p>With a Mobilizon account on an instance, you may <strong>participate</strong> at events from other instances and <strong>add comments</strong> on events.</p><p></p><p>Of course, it&#39;s still <u>a work in progress</u>: if reports made from an instance on events and comments can be federated, you can&#39;t block people right now, and moderators actions are rather limited, but this <strong>will definitely get fixed over time</strong> until first stable version next year.</p><p></p><p>Anyway, if you want to come up with some feedback, head over to our forum or - if you feel you have technical skills and are familiar with it - on our Gitlab repository.</p><p></p><p>Also, to people that want to set Mobilizon themselves even though we really don&#39;t advise to do that for now, we have a little documentation but it&#39;s quite the early days and you&#39;ll probably need some help. No worries, you can chat with us on our Forum or though our Matrix channel.</p><p></p><p>Check our website for more informations and follow us on Twitter or Mastodon.</p>"
end
describe "build_tags/1" do