diff --git a/.gitattributes b/.gitattributes
index c46415a5c..68895bf88 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,2 +1,8 @@
*.ex diff=elixir
*.exs diff=elixir
+# At the time of writing all js/css files included
+# in the repo are minified bundles, and we don't want
+# to search/diff those as text files.
+*.js binary
+*.js.map binary
+*.css binary
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index fd0c5c8d4..9a754ed78 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -229,7 +229,7 @@ arm:
only: *release-only
tags:
- arm32
- image: elixir:1.10.3
+ image: arm32v7/elixir:1.10.3
cache: *release-cache
variables: *release-variables
before_script: *before-release
@@ -241,7 +241,7 @@ arm-musl:
only: *release-only
tags:
- arm32
- image: elixir:1.10.3-alpine
+ image: arm32v7/elixir:1.10.3-alpine
cache: *release-cache
variables: *release-variables
before_script: *before-release-musl
@@ -253,7 +253,7 @@ arm64:
only: *release-only
tags:
- arm
- image: elixir:1.10.3
+ image: arm64v8/elixir:1.10.3
cache: *release-cache
variables: *release-variables
before_script: *before-release
@@ -265,8 +265,7 @@ arm64-musl:
only: *release-only
tags:
- arm
- # TODO: Replace with upstream image when 1.9.0 comes out
- image: elixir:1.10.3-alpine
+ image: arm64v8/elixir:1.10.3-alpine
cache: *release-cache
variables: *release-variables
before_script: *before-release-musl
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f61ccd2c3..a402b80ff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,54 +1,91 @@
# Changelog
+
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Unreleased
+### Changed
+
+- Polls now always return a `voters_count`, even if they are single-choice.
+- Admin Emails: The ap id is used as the user link in emails now.
+- **Breaking:** Changed `mix pleroma.user toggle_confirmed` to `mix pleroma.user confirm`
+
### Added
-- Mix tasks for controlling user account confirmation status in bulk (`mix pleroma.user confirm_all` and `mix pleroma.user unconfirm_all`)
-- Mix task for sending confirmation emails to all unconfirmed users (`mix pleroma.email send_confirmation_mails`)
-- Mix task option for force-unfollowing relays
-- Media preview proxy (requires `ffmpeg` and `ImageMagick` to be installed and media proxy to be enabled; see `:media_preview_proxy` config for more details).
+
- Reports now generate notifications for admins and mods.
-- Pleroma API: Importing the mutes users from CSV files.
- Experimental websocket-based federation between Pleroma instances.
-- Support pagination of blocks and mutes
-- App metrics: ability to restrict access to specified IP whitelist.
-- Account backup
+- Support for local-only statuses
+- 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.
-- Ability to view remote timelines, with ex. `/api/v1/timelines/public?instance=lain.com` and streams `public:remote` and `public:remote:media`
+- 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.
+
+
+ API Changes
+- Admin API: (`GET /api/pleroma/admin/users`) filter users by `unconfirmed` status and `actor_type`.
+- Pleroma API: Add `idempotency_key` to the chat message entity that can be used for optimistic message sending.
+- Pleroma API: (`GET /api/v1/pleroma/federation_status`) Add a way to get a list of unreachable instances.
+- Mastodon API: User and conversation mutes can now auto-expire if `expires_in` parameter was given while adding the mute.
+- Admin API: An endpoint to manage frontends
+
+
+
+### Fixed
+
+- Users with `is_discoverable` field set to false (default value) will appear in in-service search results but be hidden from external services (search bots etc.).
+
+
+ API Changes
+ - Mastodon API: Current user is now included in conversation if it's the only participant.
+ - Mastodon API: Fixed last_status.account being not filled with account data.
+
+
+## Unreleased (Patch)
+
+### Changed
+
+### Fixed
+
+- Config generation: rename `Pleroma.Upload.Filter.ExifTool` to `Pleroma.Upload.Filter.Exiftool`.
+- Search: RUM index search speed has been fixed.
+- S3 Uploads with Elixir 1.11.
+- Emoji Reaction activity filtering from blocked and muted accounts.
+- Mix task pleroma.user delete_activities for source installations.
+- Fix ability to update Pleroma Chat push notifications with PUT /api/v1/push/subscription and alert type pleroma:chat_mention
+- Forwarded reports duplication from Pleroma instances.
+
+
+ API
+- Statuses were not displayed for Mastodon forwarded reports.
+
+
+
+## [2.2.0] - 2020-11-12
+
+### Security
+
+- Fixed the possibility of using file uploads to spoof posts.
### Changed
- **Breaking** Requires `libmagic` (or `file`) to guess file types.
+- **Breaking:** App metrics endpoint (`/api/pleroma/app_metrics`) is disabled by default, check `docs/API/prometheus.md` on enabling and configuring.
- **Breaking:** Pleroma Admin API: emoji packs and files routes changed.
- **Breaking:** Sensitive/NSFW statuses no longer disable link previews.
-- **Breaking:** App metrics endpoint (`/api/pleroma/app_metrics`) is disabled by default, check `docs/API/prometheus.md` on enabling and configuring.
- Search: Users are now findable by their urls.
- Renamed `:await_up_timeout` in `:connections_pool` namespace to `:connect_timeout`, old name is deprecated.
- Renamed `:timeout` in `pools` namespace to `:recv_timeout`, old name is deprecated.
- The `discoverable` field in the `User` struct will now add a NOINDEX metatag to profile pages when false.
-- Users with the `discoverable` field set to false will not show up in searches.
+- Users with the `is_discoverable` field set to false will not show up in searches ([bug](https://git.pleroma.social/pleroma/pleroma/-/issues/2301)).
- Minimum lifetime for ephmeral activities changed to 10 minutes and made configurable (`:min_lifetime` option).
- Introduced optional dependencies on `ffmpeg`, `ImageMagick`, `exiftool` software packages. Please refer to `docs/installation/optional/media_graphics_packages.md`.
-- Changed `mix pleroma.user toggle_confirmed` to `mix pleroma.user confirm`
-- Polls now always return a `voters_count`, even if they are single-choice
-- Admin Emails: The ap id is used as the user link in emails now.
-
-
+-
API Changes
-
-- Pleroma API: Importing the mutes users from CSV files.
-- Admin API: Importing emoji from a zip file
-- Pleroma API: Pagination for remote/local packs and emoji.
-- Admin API: (`GET /api/pleroma/admin/users`) added filters user by `unconfirmed` status
-- Admin API: (`GET /api/pleroma/admin/users`) added filters user by `actor_type`
-- Pleroma API: Add `idempotency_key` to the chat message entity that can be used for optimistic message sending.
-- Pleroma API: (`GET /api/v1/pleroma/federation_status`) Add a way to get a list of unreachable instances.
-- Mastodon API: User and conversation mutes can now auto-expire if `expires_in` parameter was given while adding the mute.
-
+- API: Empty parameter values for integer parameters are now ignored in non-strict validaton mode.
### Removed
@@ -59,23 +96,38 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Removed `:managed_config` option. In practice, it was accidentally removed with 2.0.0 release when frontends were
switched to a new configuration mechanism, however it was not officially removed until now.
+### Added
+
+- Media preview proxy (requires `ffmpeg` and `ImageMagick` to be installed and media proxy to be enabled; see `:media_preview_proxy` config for more details).
+- Mix tasks for controlling user account confirmation status in bulk (`mix pleroma.user confirm_all` and `mix pleroma.user unconfirm_all`)
+- Mix task for sending confirmation emails to all unconfirmed users (`mix pleroma.email send_confirmation_mails`)
+- Mix task option for force-unfollowing relays
+- App metrics: ability to restrict access to specified IP whitelist.
+
+
+ API Changes
+
+- Admin API: Importing emoji from a zip file
+- Pleroma API: Importing the mutes users from CSV files.
+- Pleroma API: Pagination for remote/local packs and emoji.
+
+
+
### Fixed
- Add documented-but-missing chat pagination.
- Allow sending out emails again.
-- Allow sending chat messages to yourself.
-- Fix remote users with a whitespace name.
+- Allow sending chat messages to yourself
- OStatus / static FE endpoints: fixed inaccessibility for anonymous users on non-federating instances, switched to handling per `:restrict_unauthenticated` setting.
-- Mastodon API: Current user is now included in conversation if it's the only participant
-- Mastodon API: Fixed last_status.account being not filled with account data
+- Fix remote users with a whitespace name.
-## Unreleased (Patch)
+### Upgrade notes
-### Changed
-- API: Empty parameter values for integer parameters are now ignored in non-strict validaton mode.
-
-### Fixes
-- Config generation: rename `Pleroma.Upload.Filter.ExifTool` to `Pleroma.Upload.Filter.Exiftool`
+1. Install libmagic and development headers (`libmagic-dev` on Ubuntu/Debian, `file-dev` on Alpine Linux)
+2. Run database migrations (inside Pleroma directory):
+ - OTP: `./bin/pleroma_ctl migrate`
+ - From Source: `mix ecto.migrate`
+3. Restart Pleroma
## [2.1.2] - 2020-09-17
diff --git a/Dockerfile b/Dockerfile
index 4e7c01c5d..a1dc9d050 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -31,7 +31,7 @@ LABEL maintainer="ops@pleroma.social" \
ARG HOME=/opt/pleroma
ARG DATA=/var/lib/pleroma
-RUN echo "https://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories &&\
+RUN echo "http://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories &&\
apk update &&\
apk add exiftool imagemagick ncurses postgresql-client &&\
adduser --system --shell /bin/false --home ${HOME} pleroma &&\
diff --git a/SECURITY.md b/SECURITY.md
index 8617c1434..c009d21d9 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -6,7 +6,7 @@ Currently, Pleroma offers bugfixes and security patches only for the latest mino
| Version | Support
|---------| --------
-| 2.1 | Bugfixes and security patches
+| 2.2 | Bugfixes and security patches
## Reporting a vulnerability
diff --git a/config/config.exs b/config/config.exs
index 0b8a75aad..be5257663 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -129,7 +129,6 @@
dispatch: [
{:_,
[
- {"/api/fedsocket/v1", Pleroma.Web.FedSockets.IncomingHandler, []},
{"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
{"/websocket", Phoenix.Endpoint.CowboyWebSocket,
{Phoenix.Transports.WebSocket,
@@ -264,7 +263,8 @@
length: 16
]
],
- show_reactions: true
+ show_reactions: true,
+ password_reset_token_validity: 60 * 60 * 24
config :pleroma, :welcome,
direct_message: [
diff --git a/config/description.exs b/config/description.exs
index 0552b37e0..a663d8127 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -272,19 +272,6 @@
}
]
},
- %{
- group: :pleroma,
- key: :fed_sockets,
- type: :group,
- description: "Websocket based federation",
- children: [
- %{
- key: :enabled,
- type: :boolean,
- description: "Enable FedSockets"
- }
- ]
- },
%{
group: :pleroma,
key: Pleroma.Emails.Mailer,
diff --git a/docs/API/admin_api.md b/docs/API/admin_api.md
index f7b5bcae7..266f8cef8 100644
--- a/docs/API/admin_api.md
+++ b/docs/API/admin_api.md
@@ -554,7 +554,7 @@ Response:
* `show_role`
* `skip_thread_containment`
* `fields`
- * `discoverable`
+ * `is_discoverable`
* `actor_type`
* Responses:
@@ -1499,3 +1499,66 @@ Returns the content of the document
"url": "https://example.com/instance/panel.html"
}
```
+
+## `GET /api/pleroma/admin/frontends
+
+### List available frontends
+
+- Response:
+
+```json
+[
+ {
+ "build_url": "https://git.pleroma.social/pleroma/fedi-fe/-/jobs/artifacts/${ref}/download?job=build",
+ "git": "https://git.pleroma.social/pleroma/fedi-fe",
+ "installed": true,
+ "name": "fedi-fe",
+ "ref": "master"
+ },
+ {
+ "build_url": "https://git.pleroma.social/lambadalambda/kenoma/-/jobs/artifacts/${ref}/download?job=build",
+ "git": "https://git.pleroma.social/lambadalambda/kenoma",
+ "installed": false,
+ "name": "kenoma",
+ "ref": "master"
+ }
+]
+```
+
+## `POST /api/pleroma/admin/frontends/install`
+
+### Install a frontend
+
+- Params:
+ - `name`: frontend name, required
+ - `ref`: frontend ref
+ - `file`: path to a frontend zip file
+ - `build_url`: build URL
+ - `build_dir`: build directory
+
+- Response:
+
+```json
+[
+ {
+ "build_url": "https://git.pleroma.social/pleroma/fedi-fe/-/jobs/artifacts/${ref}/download?job=build",
+ "git": "https://git.pleroma.social/pleroma/fedi-fe",
+ "installed": true,
+ "name": "fedi-fe",
+ "ref": "master"
+ },
+ {
+ "build_url": "https://git.pleroma.social/lambadalambda/kenoma/-/jobs/artifacts/${ref}/download?job=build",
+ "git": "https://git.pleroma.social/lambadalambda/kenoma",
+ "installed": false,
+ "name": "kenoma",
+ "ref": "master"
+ }
+]
+```
+
+```json
+{
+ "error": "Could not install frontend"
+}
+```
diff --git a/docs/API/differences_in_mastoapi_responses.md b/docs/API/differences_in_mastoapi_responses.md
index ba48a2ca1..6b0ad85d1 100644
--- a/docs/API/differences_in_mastoapi_responses.md
+++ b/docs/API/differences_in_mastoapi_responses.md
@@ -18,7 +18,7 @@ Adding the parameter `instance=lain.com` to the public timeline will show only s
## Statuses
-- `visibility`: has an additional possible value `list`
+- `visibility`: has additional possible values `list` and `local` (for local-only statuses)
Has these additional fields under the `pleroma` object:
@@ -84,7 +84,7 @@ Has these additional fields under the `pleroma` object:
- `show_role`: boolean, nullable, true when the user wants his role (e.g admin, moderator) to be shown
- `no_rich_text` - boolean, nullable, true when html tags are stripped from all statuses requested from the API
-- `discoverable`: boolean, true when the user allows discovery of the account in search results and other services.
+- `discoverable`: boolean, true when the user allows external services (search bots) etc. to index / list the account (regardless of this setting, user will still appear in regular search results)
- `actor_type`: string, the type of this account.
## Conversations
@@ -173,7 +173,7 @@ Additional parameters can be added to the JSON body/Form data:
- `preview`: boolean, if set to `true` the post won't be actually posted, but the status entitiy would still be rendered back. This could be useful for previewing rich text/custom emoji, for example.
- `content_type`: string, contain the MIME type of the status, it is transformed into HTML by the backend. You can get the list of the supported MIME types with the nodeinfo endpoint.
- `to`: A list of nicknames (like `lain@soykaf.club` or `lain` on the local server) that will be used to determine who is going to be addressed by this post. Using this will disable the implicit addressing by mentioned names in the `status` body, only the people in the `to` list will be addressed. The normal rules for for post visibility are not affected by this and will still apply.
-- `visibility`: string, besides standard MastoAPI values (`direct`, `private`, `unlisted` or `public`) it can be used to address a List by setting it to `list:LIST_ID`.
+- `visibility`: string, besides standard MastoAPI values (`direct`, `private`, `unlisted`, `local` or `public`) it can be used to address a List by setting it to `list:LIST_ID`.
- `expires_in`: The number of seconds the posted activity should expire in. When a posted activity expires it will be deleted from the server, and a delete request for it will be federated. This needs to be longer than an hour.
- `in_reply_to_conversation_id`: Will reply to a given conversation, addressing only the people who are part of the recipient set of that conversation. Sets the visibility to `direct`.
@@ -207,7 +207,7 @@ Additional parameters can be added to the JSON body/Form data:
- `skip_thread_containment` - if true, skip filtering out broken threads
- `allow_following_move` - if true, allows automatically follow moved following accounts
- `pleroma_background_image` - sets the background image of the user. Can be set to "" (an empty string) to reset.
-- `discoverable` - if true, discovery of this account in search results and other services is allowed.
+- `discoverable` - if true, external services (search bots) etc. are allowed to index / list the account (regardless of this setting, user will still appear in regular search results).
- `actor_type` - the type of this account.
- `accepts_chat_messages` - if false, this account will reject all chat messages.
@@ -233,7 +233,7 @@ Post here request with `grant_type=refresh_token` to obtain new access token. Re
`POST /api/v1/accounts`
-Has theses additional parameters (which are the same as in Pleroma-API):
+Has these additional parameters (which are the same as in Pleroma-API):
- `fullname`: optional
- `bio`: optional
@@ -261,6 +261,16 @@ Has theses additional parameters (which are the same as in Pleroma-API):
- `pleroma.metadata.post_formats`: A list of the allowed post format types
- `vapid_public_key`: The public key needed for push messages
+## Push Subscription
+
+`POST /api/v1/push/subscription`
+`PUT /api/v1/push/subscription`
+
+Permits these additional alert types:
+
+- pleroma:chat_mention
+- pleroma:emoji_reaction
+
## Markers
Has these additional fields under the `pleroma` object:
diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md
index ebf95ebc9..85551362c 100644
--- a/docs/configuration/cheatsheet.md
+++ b/docs/configuration/cheatsheet.md
@@ -63,6 +63,7 @@ To add configuration to your config file, you can copy it from the base config.
* `external_user_synchronization`: Enabling following/followers counters synchronization for external users.
* `cleanup_attachments`: Remove attachments along with statuses. Does not affect duplicate files and attachments without status. Enabling this will increase load to database when deleting statuses on larger instances.
* `show_reactions`: Let favourites and emoji reactions be viewed through the API (default: `true`).
+* `password_reset_token_validity`: The time after which reset tokens aren't accepted anymore, in seconds (default: one day).
## Welcome
* `direct_message`: - welcome message sent as a direct message.
@@ -220,18 +221,6 @@ config :pleroma, :mrf_user_allowlist, %{
* `total_user_limit`: the number of scheduled activities a user is allowed to create in total (Default: `300`)
* `enabled`: whether scheduled activities are sent to the job queue to be executed
-## FedSockets
-FedSockets is an experimental feature allowing for Pleroma backends to federate using a persistant websocket connection as opposed to making each federation a seperate http connection. This feature is currently off by default. It is configurable throught he following options.
-
-### :fedsockets
-* `enabled`: Enables FedSockets for this instance. `false` by default.
-* `connection_duration`: Time an idle websocket is kept open.
-* `rejection_duration`: Failures to connect via FedSockets will not be retried for this period of time.
-* `fed_socket_fetches` and `fed_socket_rejections`: Settings passed to `cachex` for the fetch registry, and rejection stacks. See `Pleroma.Web.FedSockets` for more details.
-
-
-## Frontends
-
### :frontend_configurations
This can be used to configure a keyword list that keeps the configuration data for any kind of frontend. By default, settings for `pleroma_fe` and `masto_fe` are configured. You can find the documentation for `pleroma_fe` configuration into [Pleroma-FE configuration and customization for instance administrators](/frontend/CONFIGURATION/#options).
diff --git a/docs/installation/debian_based_en.md b/docs/installation/debian_based_en.md
index 75ceb6595..2b1c7406f 100644
--- a/docs/installation/debian_based_en.md
+++ b/docs/installation/debian_based_en.md
@@ -35,7 +35,7 @@ sudo apt full-upgrade
* Install some of the above mentioned programs:
```shell
-sudo apt install git build-essential postgresql postgresql-contrib cmake libmagic-devel
+sudo apt install git build-essential postgresql postgresql-contrib cmake libmagic-dev
```
### Install Elixir and Erlang
diff --git a/installation/pleroma.nginx b/installation/pleroma.nginx
index d613befd2..9890cb2b1 100644
--- a/installation/pleroma.nginx
+++ b/installation/pleroma.nginx
@@ -93,9 +93,4 @@ server {
chunked_transfer_encoding on;
proxy_pass http://phoenix;
}
-
- location /api/fedsocket/v1 {
- proxy_request_buffering off;
- proxy_pass http://phoenix/api/fedsocket/v1;
- }
}
diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex
index 49ba2aae4..6df1cf538 100644
--- a/lib/mix/pleroma.ex
+++ b/lib/mix/pleroma.ex
@@ -14,11 +14,12 @@ defmodule Mix.Pleroma do
:swoosh,
:timex
]
- @cachex_children ["object", "user", "scrubber"]
+ @cachex_children ["object", "user", "scrubber", "web_resp"]
@doc "Common functions to be reused in mix tasks"
def start_pleroma do
Pleroma.Config.Holder.save_default()
Pleroma.Config.Oban.warn()
+ Pleroma.Application.limiters_setup()
Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
if Pleroma.Config.get(:env) != :test do
diff --git a/lib/mix/tasks/pleroma/frontend.ex b/lib/mix/tasks/pleroma/frontend.ex
index cbce81ab9..f15dbc38b 100644
--- a/lib/mix/tasks/pleroma/frontend.ex
+++ b/lib/mix/tasks/pleroma/frontend.ex
@@ -17,8 +17,6 @@ def run(["install", "none" | _args]) do
end
def run(["install", frontend | args]) do
- log_level = Logger.level()
- Logger.configure(level: :warn)
start_pleroma()
{options, [], []} =
@@ -33,109 +31,6 @@ def run(["install", frontend | args]) do
]
)
- 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([instance_static_dir, "frontends", "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, [], pool: :media, 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.rm_rf!(dest)
- File.mkdir_p!(dest)
- File.cp_r!(Path.join([source, from]), dest)
- :ok
+ Pleroma.Frontend.install(frontend, options)
end
end
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index 553834da0..8559ae6a9 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -356,4 +356,15 @@ def pinned_by_actor?(%Activity{} = activity) do
actor = user_actor(activity)
activity.id in actor.pinned_activities
end
+
+ @spec get_by_object_ap_id_with_object(String.t()) :: t() | nil
+ def get_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do
+ ap_id
+ |> Queries.by_object_id()
+ |> with_preloaded_object()
+ |> first()
+ |> Repo.one()
+ end
+
+ def get_by_object_ap_id_with_object(_), do: nil
end
diff --git a/lib/pleroma/activity/search.ex b/lib/pleroma/activity/search.ex
index ceb365bb3..382c81118 100644
--- a/lib/pleroma/activity/search.ex
+++ b/lib/pleroma/activity/search.ex
@@ -27,7 +27,10 @@ def search(user, search_query, options \\ []) do
|> maybe_restrict_local(user)
|> maybe_restrict_author(author)
|> maybe_restrict_blocked(user)
- |> Pagination.fetch_paginated(%{"offset" => offset, "limit" => limit}, :offset)
+ |> Pagination.fetch_paginated(
+ %{"offset" => offset, "limit" => limit, "skip_order" => index_type == :rum},
+ :offset
+ )
|> maybe_fetch(user, search_query)
end
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index 7c4cd9626..ced14f87f 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -57,6 +57,7 @@ def start(_type, _args) do
setup_instrumenters()
load_custom_modules()
Pleroma.Docs.JSON.compile()
+ limiters_setup()
adapter = Application.get_env(:tesla, :adapter)
@@ -207,8 +208,7 @@ defp dont_run_in_test(_) do
name: Pleroma.Web.Streamer.registry(),
keys: :duplicate,
partitions: System.schedulers_online()
- ]},
- Pleroma.Web.FedSockets.Supervisor
+ ]}
]
end
@@ -273,4 +273,10 @@ defp http_children(Tesla.Adapter.Gun, _) do
end
defp http_children(_, _), do: []
+
+ @spec limiters_setup() :: :ok
+ def limiters_setup do
+ [Pleroma.Web.RichMedia.Helpers, Pleroma.Web.MediaProxy]
+ |> Enum.each(&ConcurrentLimiter.new(&1, 1, 0))
+ end
end
diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex
index 13eeaa96b..cf8182d55 100644
--- a/lib/pleroma/constants.ex
+++ b/lib/pleroma/constants.ex
@@ -26,4 +26,6 @@ defmodule Pleroma.Constants do
do:
~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc embed.js embed.css)
)
+
+ def as_local_public, do: Pleroma.Web.base_url() <> "/#Public"
end
diff --git a/lib/pleroma/emails/admin_email.ex b/lib/pleroma/emails/admin_email.ex
index 02274554f..d5757c12a 100644
--- a/lib/pleroma/emails/admin_email.ex
+++ b/lib/pleroma/emails/admin_email.ex
@@ -48,6 +48,9 @@ def report(to, reporter, account, statuses, comment) do
status_url = Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, id)
"#{status_url} "
+ %{"id" => id} when is_binary(id) ->
+ "#{id} "
+
id when is_binary(id) ->
"#{id} "
end)
diff --git a/lib/pleroma/frontend.ex b/lib/pleroma/frontend.ex
new file mode 100644
index 000000000..bf935a728
--- /dev/null
+++ b/lib/pleroma/frontend.ex
@@ -0,0 +1,110 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Frontend do
+ alias Pleroma.Config
+
+ require Logger
+
+ def install(name, opts \\ []) do
+ frontend_info = %{
+ "ref" => opts[:ref],
+ "build_url" => opts[:build_url],
+ "build_dir" => opts[:build_dir]
+ }
+
+ frontend_info =
+ [:frontends, :available, name]
+ |> Config.get(%{})
+ |> Map.merge(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([dir(), name, ref])
+
+ label = "#{name} (#{ref})"
+ tmp_dir = Path.join(dir(), "tmp")
+
+ with {_, :ok} <-
+ {:download_or_unzip, download_or_unzip(frontend_info, tmp_dir, opts[:file])},
+ Logger.info("Installing #{label} to #{dest}"),
+ :ok <- install_frontend(frontend_info, tmp_dir, dest) do
+ File.rm_rf!(tmp_dir)
+ Logger.info("Frontend #{label} installed to #{dest}")
+ else
+ {:download_or_unzip, _} ->
+ Logger.info("Could not download or unzip the frontend")
+ {:error, "Could not download or unzip the frontend"}
+
+ _e ->
+ Logger.info("Could not install the frontend")
+ {:error, "Could not install the frontend"}
+ end
+ end
+
+ def dir(opts \\ []) do
+ if is_nil(opts[:static_dir]) do
+ Pleroma.Config.get!([:instance, :static_dir])
+ else
+ opts[:static_dir]
+ end
+ |> Path.join("frontends")
+ end
+
+ defp download_or_unzip(frontend_info, temp_dir, nil),
+ do: download_build(frontend_info, temp_dir)
+
+ defp download_or_unzip(_frontend_info, temp_dir, file) do
+ with {:ok, zip} <- File.read(Path.expand(file)) do
+ unzip(zip, 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)
+ end
+ end
+
+ defp download_build(frontend_info, dest) do
+ Logger.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, [], pool: :media, recv_timeout: 120_000) do
+ unzip(zip_body, dest)
+ else
+ {:error, e} -> {:error, e}
+ e -> {:error, e}
+ end
+ end
+
+ defp install_frontend(frontend_info, source, dest) do
+ from = frontend_info["build_dir"] || "dist"
+ File.rm_rf!(dest)
+ File.mkdir_p!(dest)
+ File.cp_r!(Path.join([source, from]), dest)
+ :ok
+ end
+end
diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex
index ae4301738..20d8f687d 100644
--- a/lib/pleroma/object/fetcher.ex
+++ b/lib/pleroma/object/fetcher.ex
@@ -12,7 +12,6 @@ defmodule Pleroma.Object.Fetcher do
alias Pleroma.Web.ActivityPub.ObjectValidator
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.Federator
- alias Pleroma.Web.FedSockets
require Logger
require Pleroma.Constants
@@ -183,16 +182,16 @@ defp maybe_date_fetch(headers, date) do
end
end
- def fetch_and_contain_remote_object_from_id(prm, opts \\ [])
+ def fetch_and_contain_remote_object_from_id(id)
- def fetch_and_contain_remote_object_from_id(%{"id" => id}, opts),
- do: fetch_and_contain_remote_object_from_id(id, opts)
+ def fetch_and_contain_remote_object_from_id(%{"id" => id}),
+ do: fetch_and_contain_remote_object_from_id(id)
- def fetch_and_contain_remote_object_from_id(id, opts) when is_binary(id) do
+ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
Logger.debug("Fetching object #{id} via AP")
with {:scheme, true} <- {:scheme, String.starts_with?(id, "http")},
- {:ok, body} <- get_object(id, opts),
+ {:ok, body} <- get_object(id),
{:ok, data} <- safe_json_decode(body),
:ok <- Containment.contain_origin_from_id(id, data) do
{:ok, data}
@@ -208,22 +207,10 @@ def fetch_and_contain_remote_object_from_id(id, opts) when is_binary(id) do
end
end
- def fetch_and_contain_remote_object_from_id(_id, _opts),
+ def fetch_and_contain_remote_object_from_id(_id),
do: {:error, "id must be a string"}
- defp get_object(id, opts) do
- with false <- Keyword.get(opts, :force_http, false),
- {:ok, fedsocket} <- FedSockets.get_or_create_fed_socket(id) do
- Logger.debug("fetching via fedsocket - #{inspect(id)}")
- FedSockets.fetch(fedsocket, id)
- else
- _other ->
- Logger.debug("fetching via http - #{inspect(id)}")
- get_object_http(id)
- end
- end
-
- defp get_object_http(id) do
+ defp get_object(id) do
date = Pleroma.Signature.signed_date()
headers =
diff --git a/lib/pleroma/password_reset_token.ex b/lib/pleroma/password_reset_token.ex
index 787bd4781..fea5b1c22 100644
--- a/lib/pleroma/password_reset_token.ex
+++ b/lib/pleroma/password_reset_token.ex
@@ -40,6 +40,7 @@ def used_changeset(struct) do
@spec reset_password(binary(), map()) :: {:ok, User.t()} | {:error, binary()}
def reset_password(token, data) do
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
+ false <- expired?(token),
%User{} = user <- User.get_cached_by_id(token.user_id),
{:ok, _user} <- User.reset_password(user, data),
{:ok, token} <- Repo.update(used_changeset(token)) do
@@ -48,4 +49,14 @@ def reset_password(token, data) do
_e -> {:error, token}
end
end
+
+ def expired?(%__MODULE__{inserted_at: inserted_at}) do
+ validity = Pleroma.Config.get([:instance, :password_reset_token_validity], 0)
+
+ now = NaiveDateTime.utc_now()
+
+ difference = NaiveDateTime.diff(now, inserted_at)
+
+ difference > validity
+ end
end
diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex
index e388993b7..3aa6909d2 100644
--- a/lib/pleroma/signature.ex
+++ b/lib/pleroma/signature.ex
@@ -39,7 +39,7 @@ def key_id_to_actor_id(key_id) do
def fetch_public_key(conn) do
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
{:ok, actor_id} <- key_id_to_actor_id(kid),
- {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id, force_http: true) do
+ {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
{:ok, public_key}
else
e ->
@@ -50,8 +50,8 @@ def fetch_public_key(conn) do
def refetch_public_key(conn) do
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
{:ok, actor_id} <- key_id_to_actor_id(kid),
- {:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id, force_http: true),
- {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id, force_http: true) do
+ {:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id),
+ {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
{:ok, public_key}
else
e ->
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index f93fd7a0c..62171078c 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -245,6 +245,18 @@ def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \
end
end
+ def cached_blocked_users_ap_ids(user) do
+ Cachex.fetch!(:user_cache, "blocked_users_ap_ids:#{user.ap_id}", fn _ ->
+ blocked_users_ap_ids(user)
+ end)
+ end
+
+ def cached_muted_users_ap_ids(user) do
+ Cachex.fetch!(:user_cache, "muted_users_ap_ids:#{user.ap_id}", fn _ ->
+ muted_users_ap_ids(user)
+ end)
+ end
+
defdelegate following_count(user), to: FollowingRelationship
defdelegate following(user), to: FollowingRelationship
defdelegate following?(follower, followed), to: FollowingRelationship
@@ -1068,6 +1080,8 @@ def invalidate_cache(user) do
Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
Cachex.del(:user_cache, "nickname:#{user.nickname}")
Cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
+ Cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
+ Cachex.del(:user_cache, "muted_users_ap_ids:#{user.ap_id}")
end
@spec get_cached_by_ap_id(String.t()) :: User.t() | nil
@@ -1374,6 +1388,8 @@ def mute(%User{} = muter, %User{} = mutee, params \\ %{}) do
)
end
+ Cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
+
{:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
end
end
@@ -1382,6 +1398,7 @@ def unmute(%User{} = muter, %User{} = mutee) do
with {:ok, user_mute} <- UserRelationship.delete_mute(muter, mutee),
{:ok, user_notification_mute} <-
UserRelationship.delete_notification_mute(muter, mutee) do
+ Cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
{:ok, [user_mute, user_notification_mute]}
end
end
@@ -1827,12 +1844,12 @@ def html_filter_policy(%User{no_rich_text: true}) do
def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])
- def fetch_by_ap_id(ap_id, opts \\ []), do: ActivityPub.make_user_from_ap_id(ap_id, opts)
+ def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
- def get_or_fetch_by_ap_id(ap_id, opts \\ []) do
+ def get_or_fetch_by_ap_id(ap_id) do
cached_user = get_cached_by_ap_id(ap_id)
- maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id, opts)
+ maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id)
case {cached_user, maybe_fetched_user} do
{_, {:ok, %User{} = user}} ->
@@ -1905,8 +1922,8 @@ def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
def public_key(_), do: {:error, "key not found"}
- def get_public_key_for_ap_id(ap_id, opts \\ []) do
- with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id, opts),
+ def get_public_key_for_ap_id(ap_id) do
+ with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
{:ok, public_key} <- public_key(user) do
{:ok, public_key}
else
@@ -2388,13 +2405,19 @@ def unblock_domain(user, domain_blocked) do
@spec add_to_block(User.t(), User.t()) ::
{:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
defp add_to_block(%User{} = user, %User{} = blocked) do
- UserRelationship.create_block(user, blocked)
+ with {:ok, relationship} <- UserRelationship.create_block(user, blocked) do
+ Cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
+ {:ok, relationship}
+ end
end
@spec add_to_block(User.t(), User.t()) ::
{:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
defp remove_from_block(%User{} = user, %User{} = blocked) do
- UserRelationship.delete_block(user, blocked)
+ with {:ok, relationship} <- UserRelationship.delete_block(user, blocked) do
+ Cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
+ {:ok, relationship}
+ end
end
def set_invisible(user, invisible) do
diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex
index 2dab67211..f1761ef03 100644
--- a/lib/pleroma/user/search.ex
+++ b/lib/pleroma/user/search.ex
@@ -85,7 +85,6 @@ defp search_query(query_string, for_user, following, top_user_ids) do
|> base_query(following)
|> filter_blocked_user(for_user)
|> filter_invisible_users()
- |> filter_discoverable_users()
|> filter_internal_users()
|> filter_blocked_domains(for_user)
|> fts_search(query_string)
@@ -163,10 +162,6 @@ defp filter_invisible_users(query) do
from(q in query, where: q.invisible == false)
end
- defp filter_discoverable_users(query) do
- from(q in query, where: q.is_discoverable == true)
- end
-
defp filter_internal_users(query) do
from(q in query, where: q.actor_type != "Application")
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index d8f685d38..1c91bc074 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -123,7 +123,9 @@ def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when
# Splice in the child object if we have one.
activity = Maps.put_if_present(activity, :object, object)
- BackgroundWorker.enqueue("fetch_data_for_activity", %{"activity_id" => activity.id})
+ ConcurrentLimiter.limit(Pleroma.Web.RichMedia.Helpers, fn ->
+ Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end)
+ end)
{:ok, activity}
else
@@ -332,15 +334,21 @@ defp do_unfollow(follower, followed, activity_id, local) do
end
@spec flag(map()) :: {:ok, Activity.t()} | {:error, any()}
- def flag(
- %{
- actor: actor,
- context: _context,
- account: account,
- statuses: statuses,
- content: content
- } = params
- ) do
+ def flag(params) do
+ with {:ok, result} <- Repo.transaction(fn -> do_flag(params) end) do
+ result
+ end
+ end
+
+ defp do_flag(
+ %{
+ actor: actor,
+ context: _context,
+ account: account,
+ statuses: statuses,
+ content: content
+ } = params
+ ) do
# only accept false as false value
local = !(params[:local] == false)
forward = !(params[:forward] == false)
@@ -358,7 +366,8 @@ def flag(
{:ok, activity} <- insert(flag_data, local),
{:ok, stripped_activity} <- strip_report_status_data(activity),
_ <- notify_and_stream(activity),
- :ok <- maybe_federate(stripped_activity) do
+ :ok <-
+ maybe_federate(stripped_activity) do
User.all_superusers()
|> Enum.filter(fn user -> not is_nil(user.email) end)
|> Enum.each(fn superuser ->
@@ -368,6 +377,8 @@ def flag(
end)
{:ok, activity}
+ else
+ {:error, error} -> Repo.rollback(error)
end
end
@@ -791,10 +802,10 @@ defp restrict_replies(query, %{
where:
fragment(
"""
- ?->>'type' != 'Create' -- This isn't a Create
+ ?->>'type' != 'Create' -- This isn't a Create
OR ?->>'inReplyTo' is null -- this isn't a reply
- OR ? && array_remove(?, ?) -- The recipient is us or one of our friends,
- -- unless they are the author (because authors
+ OR ? && array_remove(?, ?) -- The recipient is us or one of our friends,
+ -- unless they are the author (because authors
-- are also part of the recipients). This leads
-- to a bug that self-replies by friends won't
-- show up.
@@ -1289,12 +1300,10 @@ defp object_to_user_data(data) do
def fetch_follow_information_for_user(user) do
with {:ok, following_data} <-
- Fetcher.fetch_and_contain_remote_object_from_id(user.following_address,
- force_http: true
- ),
+ Fetcher.fetch_and_contain_remote_object_from_id(user.following_address),
{:ok, hide_follows} <- collection_private(following_data),
{:ok, followers_data} <-
- Fetcher.fetch_and_contain_remote_object_from_id(user.follower_address, force_http: true),
+ Fetcher.fetch_and_contain_remote_object_from_id(user.follower_address),
{:ok, hide_followers} <- collection_private(followers_data) do
{:ok,
%{
@@ -1368,8 +1377,8 @@ def user_data_from_user_object(data) do
end
end
- def fetch_and_prepare_user_from_ap_id(ap_id, opts \\ []) do
- with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id, opts),
+ def fetch_and_prepare_user_from_ap_id(ap_id) do
+ with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id),
{:ok, data} <- user_data_from_user_object(data) do
{:ok, maybe_update_follow_information(data)}
else
@@ -1412,13 +1421,13 @@ def maybe_handle_clashing_nickname(data) do
end
end
- def make_user_from_ap_id(ap_id, opts \\ []) do
+ def make_user_from_ap_id(ap_id) do
user = User.get_cached_by_ap_id(ap_id)
if user && !User.ap_enabled?(user) do
Transmogrifier.upgrade_user_from_ap_id(ap_id)
else
- with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id, opts) do
+ with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do
if user do
user
|> User.remote_user_changeset(data)
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index 31df80adb..7e5647f8f 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -82,7 +82,8 @@ def user(conn, %{"nickname" => nickname}) do
def object(conn, _) do
with ap_id <- Endpoint.url() <> conn.request_path,
%Object{} = object <- Object.get_cached_by_ap_id(ap_id),
- {_, true} <- {:public?, Visibility.is_public?(object)} do
+ {_, true} <- {:public?, Visibility.is_public?(object)},
+ {_, false} <- {:local?, Visibility.is_local_public?(object)} do
conn
|> assign(:tracking_fun_data, object.id)
|> set_cache_ttl_for(object)
@@ -92,6 +93,9 @@ def object(conn, _) do
else
{:public?, false} ->
{:error, :not_found}
+
+ {:local?, true} ->
+ {:error, :not_found}
end
end
@@ -108,7 +112,8 @@ def track_object_fetch(conn, object_id) do
def activity(conn, _params) do
with ap_id <- Endpoint.url() <> conn.request_path,
%Activity{} = activity <- Activity.normalize(ap_id),
- {_, true} <- {:public?, Visibility.is_public?(activity)} do
+ {_, true} <- {:public?, Visibility.is_public?(activity)},
+ {_, false} <- {:local?, Visibility.is_local_public?(activity)} do
conn
|> maybe_set_tracking_data(activity)
|> set_cache_ttl_for(activity)
@@ -117,6 +122,7 @@ def activity(conn, _params) do
|> render("object.json", object: activity)
else
{:public?, false} -> {:error, :not_found}
+ {:local?, true} -> {:error, :not_found}
nil -> {:error, :not_found}
end
end
diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex
index 298aff6b7..e99f6fd83 100644
--- a/lib/pleroma/web/activity_pub/builder.ex
+++ b/lib/pleroma/web/activity_pub/builder.ex
@@ -222,6 +222,9 @@ def announce(actor, object, options \\ []) do
actor.ap_id == Relay.ap_id() ->
[actor.follower_address]
+ public? and Visibility.is_local_public?(object) ->
+ [actor.follower_address, object.data["actor"], Pleroma.Constants.as_local_public()]
+
public? ->
[actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()]
diff --git a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
index 0fb05d3c4..816cc89bf 100644
--- a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
@@ -8,7 +8,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
alias Pleroma.HTTP
alias Pleroma.Web.MediaProxy
- alias Pleroma.Workers.BackgroundWorker
require Logger
@@ -17,7 +16,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
recv_timeout: 10_000
]
- def perform(:prefetch, url) do
+ defp prefetch(url) do
# Fetching only proxiable resources
if MediaProxy.enabled?() and MediaProxy.url_proxiable?(url) do
# If preview proxy is enabled, it'll also hit media proxy (so we're caching both requests)
@@ -25,17 +24,25 @@ def perform(:prefetch, url) do
Logger.debug("Prefetching #{inspect(url)} as #{inspect(prefetch_url)}")
- HTTP.get(prefetch_url, [], @adapter_options)
+ if Pleroma.Config.get(:env) == :test do
+ fetch(prefetch_url)
+ else
+ ConcurrentLimiter.limit(MediaProxy, fn ->
+ Task.start(fn -> fetch(prefetch_url) end)
+ end)
+ end
end
end
- def perform(:preload, %{"object" => %{"attachment" => attachments}} = _message) do
+ defp fetch(url), do: HTTP.get(url, [], @adapter_options)
+
+ defp preload(%{"object" => %{"attachment" => attachments}} = _message) do
Enum.each(attachments, fn
%{"url" => url} when is_list(url) ->
url
|> Enum.each(fn
%{"href" => href} ->
- BackgroundWorker.enqueue("media_proxy_prefetch", %{"url" => href})
+ prefetch(href)
x ->
Logger.debug("Unhandled attachment URL object #{inspect(x)}")
@@ -51,7 +58,7 @@ def filter(
%{"type" => "Create", "object" => %{"attachment" => attachments} = _object} = message
)
when is_list(attachments) and length(attachments) > 0 do
- BackgroundWorker.enqueue("media_proxy_preload", %{"message" => message})
+ preload(message)
{:ok, message}
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex b/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex
index 6f757f49c..338957db8 100644
--- a/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex
@@ -67,7 +67,12 @@ def validate_announcable(cng) do
%Object{} = object <- Object.get_cached_by_ap_id(object),
false <- Visibility.is_public?(object) do
same_actor = object.data["actor"] == actor.ap_id
- is_public = Pleroma.Constants.as_public() in (get_field(cng, :to) ++ get_field(cng, :cc))
+ recipients = get_field(cng, :to) ++ get_field(cng, :cc)
+ local_public = Pleroma.Constants.as_local_public()
+
+ is_public =
+ Enum.member?(recipients, Pleroma.Constants.as_public()) or
+ Enum.member?(recipients, local_public)
cond do
same_actor && is_public ->
diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex
index 2db86f116..98c32a42b 100644
--- a/lib/pleroma/web/activity_pub/pipeline.ex
+++ b/lib/pleroma/web/activity_pub/pipeline.ex
@@ -11,6 +11,7 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
alias Pleroma.Web.ActivityPub.MRF
alias Pleroma.Web.ActivityPub.ObjectValidator
alias Pleroma.Web.ActivityPub.SideEffects
+ alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.Federator
@spec common_pipeline(map(), keyword()) ::
@@ -55,7 +56,7 @@ defp maybe_federate(%Activity{} = activity, meta) do
with {:ok, local} <- Keyword.fetch(meta, :local) do
do_not_federate = meta[:do_not_federate] || !Config.get([:instance, :federating])
- if !do_not_federate && local do
+ if !do_not_federate and local and not Visibility.is_local_public?(activity) do
activity =
if object = Keyword.get(meta, :object_data) do
%{activity | data: Map.put(activity.data, "object", object)}
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
index a2930c1cd..5ab3562bf 100644
--- a/lib/pleroma/web/activity_pub/publisher.ex
+++ b/lib/pleroma/web/activity_pub/publisher.ex
@@ -13,7 +13,6 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Transmogrifier
- alias Pleroma.Web.FedSockets
require Pleroma.Constants
@@ -50,28 +49,6 @@ def is_representable?(%Activity{} = activity) do
"""
def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do
Logger.debug("Federating #{id} to #{inbox}")
-
- case FedSockets.get_or_create_fed_socket(inbox) do
- {:ok, fedsocket} ->
- Logger.debug("publishing via fedsockets - #{inspect(inbox)}")
- FedSockets.publish(fedsocket, json)
-
- _ ->
- Logger.debug("publishing via http - #{inspect(inbox)}")
- http_publish(inbox, actor, json, params)
- end
- end
-
- def publish_one(%{actor_id: actor_id} = params) do
- actor = User.get_cached_by_id(actor_id)
-
- params
- |> Map.delete(:actor_id)
- |> Map.put(:actor, actor)
- |> publish_one()
- end
-
- defp http_publish(inbox, actor, json, params) do
uri = %{path: path} = URI.parse(inbox)
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
@@ -110,6 +87,15 @@ defp http_publish(inbox, actor, json, params) do
end
end
+ def publish_one(%{actor_id: actor_id} = params) do
+ actor = User.get_cached_by_id(actor_id)
+
+ params
+ |> Map.delete(:actor_id)
+ |> Map.put(:actor, actor)
+ |> publish_one()
+ end
+
defp signature_host(%URI{port: port, scheme: scheme, host: host}) do
if port == URI.default_port(scheme) do
host
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
index bbff35c36..4d8fb721e 100644
--- a/lib/pleroma/web/activity_pub/side_effects.ex
+++ b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -24,7 +24,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Push
alias Pleroma.Web.Streamer
- alias Pleroma.Workers.BackgroundWorker
require Logger
@@ -191,7 +190,9 @@ def handle(%{data: %{"type" => "Create"}} = activity, meta) do
Object.increase_replies_count(in_reply_to)
end
- BackgroundWorker.enqueue("fetch_data_for_activity", %{"activity_id" => activity.id})
+ ConcurrentLimiter.limit(Pleroma.Web.RichMedia.Helpers, fn ->
+ Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end)
+ end)
meta =
meta
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 0bcd1db22..565d32433 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -1008,7 +1008,7 @@ def perform(:user_upgrade, user) do
def upgrade_user_from_ap_id(ap_id) do
with %User{local: false} = user <- User.get_cached_by_ap_id(ap_id),
- {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id, force_http: true),
+ {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id),
{:ok, user} <- update_user(user, data) do
TransmogrifierWorker.enqueue("user_upgrade", %{"user_id" => user.id})
{:ok, user}
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index 713b0ca1f..ea1c3a04a 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -175,7 +175,8 @@ def maybe_federate(%Activity{local: true, data: %{"type" => type}} = activity) d
outgoing_blocks = Config.get([:activitypub, :outgoing_blocks])
with true <- Config.get!([:instance, :federating]),
- true <- type != "Block" || outgoing_blocks do
+ true <- type != "Block" || outgoing_blocks,
+ false <- Visibility.is_local_public?(activity) do
Pleroma.Web.Federator.publish(activity)
end
@@ -701,14 +702,30 @@ def make_flag_data(%{actor: actor, context: context, content: content} = params,
def make_flag_data(_, _), do: %{}
- defp build_flag_object(%{account: account, statuses: statuses} = _) do
- [account.ap_id] ++ build_flag_object(%{statuses: statuses})
+ defp build_flag_object(%{account: account, statuses: statuses}) do
+ [account.ap_id | build_flag_object(%{statuses: statuses})]
end
defp build_flag_object(%{statuses: statuses}) do
Enum.map(statuses || [], &build_flag_object/1)
end
+ defp build_flag_object(%Activity{data: %{"id" => id}, object: %{data: data}}) do
+ activity_actor = User.get_by_ap_id(data["actor"])
+
+ %{
+ "type" => "Note",
+ "id" => id,
+ "content" => data["content"],
+ "published" => data["published"],
+ "actor" =>
+ AccountView.render(
+ "show.json",
+ %{user: activity_actor, skip_visibility_check: true}
+ )
+ }
+ end
+
defp build_flag_object(act) when is_map(act) or is_binary(act) do
id =
case act do
@@ -719,22 +736,14 @@ defp build_flag_object(act) when is_map(act) or is_binary(act) do
case Activity.get_by_ap_id_with_object(id) do
%Activity{} = activity ->
- activity_actor = User.get_by_ap_id(activity.object.data["actor"])
+ build_flag_object(activity)
- %{
- "type" => "Note",
- "id" => activity.data["id"],
- "content" => activity.object.data["content"],
- "published" => activity.object.data["published"],
- "actor" =>
- AccountView.render(
- "show.json",
- %{user: activity_actor, skip_visibility_check: true}
- )
- }
-
- _ ->
- %{"id" => id, "deleted" => true}
+ nil ->
+ if activity = Activity.get_by_object_ap_id_with_object(id) do
+ build_flag_object(activity)
+ else
+ %{"id" => id, "deleted" => true}
+ end
end
end
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index 4dc45cde3..93c9f436c 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -110,6 +110,7 @@ def render("user.json", %{user: user}) do
"endpoints" => endpoints,
"attachment" => fields,
"tag" => emoji_tags,
+ # Note: key name is indeed "discoverable" (not an error)
"discoverable" => user.is_discoverable,
"capabilities" => capabilities
}
diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex
index 76bd54a42..2cb5a2bd0 100644
--- a/lib/pleroma/web/activity_pub/visibility.ex
+++ b/lib/pleroma/web/activity_pub/visibility.ex
@@ -17,7 +17,19 @@ def is_public?(%Object{data: data}), do: is_public?(data)
def is_public?(%Activity{data: %{"type" => "Move"}}), do: true
def is_public?(%Activity{data: data}), do: is_public?(data)
def is_public?(%{"directMessage" => true}), do: false
- def is_public?(data), do: Utils.label_in_message?(Pleroma.Constants.as_public(), data)
+
+ def is_public?(data) do
+ Utils.label_in_message?(Pleroma.Constants.as_public(), data) or
+ Utils.label_in_message?(Pleroma.Constants.as_local_public(), data)
+ end
+
+ def is_local_public?(%Object{data: data}), do: is_local_public?(data)
+ def is_local_public?(%Activity{data: data}), do: is_local_public?(data)
+
+ def is_local_public?(data) do
+ Utils.label_in_message?(Pleroma.Constants.as_local_public(), data) and
+ not Utils.label_in_message?(Pleroma.Constants.as_public(), data)
+ end
def is_private?(activity) do
with false <- is_public?(activity),
@@ -114,6 +126,9 @@ def get_visibility(object) do
Pleroma.Constants.as_public() in cc ->
"unlisted"
+ Pleroma.Constants.as_local_public() in to ->
+ "local"
+
# this should use the sql for the object's activity
Enum.any?(to, &String.contains?(&1, "/followers")) ->
"private"
diff --git a/lib/pleroma/web/admin_api/controllers/frontend_controller.ex b/lib/pleroma/web/admin_api/controllers/frontend_controller.ex
new file mode 100644
index 000000000..fac3522b8
--- /dev/null
+++ b/lib/pleroma/web/admin_api/controllers/frontend_controller.ex
@@ -0,0 +1,40 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.FrontendController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Config
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+ plug(OAuthScopesPlug, %{scopes: ["write"], admin: true} when action == :install)
+ plug(OAuthScopesPlug, %{scopes: ["read"], admin: true} when action == :index)
+ action_fallback(Pleroma.Web.AdminAPI.FallbackController)
+
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.FrontendOperation
+
+ def index(conn, _params) do
+ installed = installed()
+
+ frontends =
+ [:frontends, :available]
+ |> Config.get([])
+ |> Enum.map(fn {name, desc} ->
+ Map.put(desc, "installed", name in installed)
+ end)
+
+ render(conn, "index.json", frontends: frontends)
+ end
+
+ def install(%{body_params: params} = conn, _params) do
+ with :ok <- Pleroma.Frontend.install(params.name, Map.delete(params, :name)) do
+ index(conn, %{})
+ end
+ end
+
+ defp installed do
+ File.ls!(Pleroma.Frontend.dir())
+ end
+end
diff --git a/lib/pleroma/web/admin_api/views/frontend_view.ex b/lib/pleroma/web/admin_api/views/frontend_view.ex
new file mode 100644
index 000000000..374841d0b
--- /dev/null
+++ b/lib/pleroma/web/admin_api/views/frontend_view.ex
@@ -0,0 +1,21 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.FrontendView do
+ use Pleroma.Web, :view
+
+ def render("index.json", %{frontends: frontends}) do
+ render_many(frontends, __MODULE__, "show.json")
+ end
+
+ def render("show.json", %{frontend: frontend}) do
+ %{
+ name: frontend["name"],
+ git: frontend["git"],
+ build_url: frontend["build_url"],
+ ref: frontend["ref"],
+ installed: frontend["installed"]
+ }
+ end
+end
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 451aa2477..280100c3d 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -139,6 +139,12 @@ def statuses_operation do
:query,
%Schema{type: :array, items: VisibilityScope},
"Exclude visibilities"
+ ),
+ Operation.parameter(
+ :with_muted,
+ :query,
+ BooleanLike,
+ "Include reactions from muted acccounts."
)
] ++ pagination_params(),
responses: %{
@@ -618,7 +624,7 @@ defp update_credentials_request do
allOf: [BooleanLike],
nullable: true,
description:
- "Discovery of this account in search results and other services is allowed."
+ "Discovery (listing, indexing) of this account by external services (search bots etc.) is allowed."
},
actor_type: ActorType
},
diff --git a/lib/pleroma/web/api_spec/operations/admin/frontend_operation.ex b/lib/pleroma/web/api_spec/operations/admin/frontend_operation.ex
new file mode 100644
index 000000000..96d4cdee7
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/admin/frontend_operation.ex
@@ -0,0 +1,85 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Admin.FrontendOperation do
+ alias OpenApiSpex.Operation
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.ApiError
+
+ import Pleroma.Web.ApiSpec.Helpers
+
+ def open_api_operation(action) do
+ operation = String.to_existing_atom("#{action}_operation")
+ apply(__MODULE__, operation, [])
+ end
+
+ def index_operation do
+ %Operation{
+ tags: ["Admin", "Reports"],
+ summary: "Get a list of available frontends",
+ operationId: "AdminAPI.FrontendController.index",
+ security: [%{"oAuth" => ["read"]}],
+ responses: %{
+ 200 => Operation.response("Response", "application/json", list_of_frontends()),
+ 403 => Operation.response("Forbidden", "application/json", ApiError)
+ }
+ }
+ end
+
+ def install_operation do
+ %Operation{
+ tags: ["Admin", "Reports"],
+ summary: "Install a frontend",
+ operationId: "AdminAPI.FrontendController.install",
+ security: [%{"oAuth" => ["read"]}],
+ requestBody: request_body("Parameters", install_request(), required: true),
+ responses: %{
+ 200 => Operation.response("Response", "application/json", list_of_frontends()),
+ 403 => Operation.response("Forbidden", "application/json", ApiError),
+ 400 => Operation.response("Error", "application/json", ApiError)
+ }
+ }
+ end
+
+ defp list_of_frontends do
+ %Schema{
+ type: :array,
+ items: %Schema{
+ type: :object,
+ properties: %{
+ name: %Schema{type: :string},
+ git: %Schema{type: :string, format: :uri, nullable: true},
+ build_url: %Schema{type: :string, format: :uri, nullable: true},
+ ref: %Schema{type: :string},
+ installed: %Schema{type: :boolean}
+ }
+ }
+ }
+ end
+
+ defp install_request do
+ %Schema{
+ title: "FrontendInstallRequest",
+ type: :object,
+ required: [:name],
+ properties: %{
+ name: %Schema{
+ type: :string
+ },
+ ref: %Schema{
+ type: :string
+ },
+ file: %Schema{
+ type: :string
+ },
+ build_url: %Schema{
+ type: :string
+ },
+ build_dir: %Schema{
+ type: :string
+ }
+ }
+ }
+ end
+end
diff --git a/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex b/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex
index 745d41f88..9d0e39fc7 100644
--- a/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex
@@ -24,6 +24,12 @@ def index_operation do
Operation.parameter(:id, :path, FlakeID, "Status ID", required: true),
Operation.parameter(:emoji, :path, :string, "Filter by a single unicode emoji",
required: nil
+ ),
+ Operation.parameter(
+ :with_muted,
+ :query,
+ :boolean,
+ "Include reactions from muted acccounts."
)
],
security: [%{"oAuth" => ["read:statuses"]}],
diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex
index b3b6ceb68..4ab918d83 100644
--- a/lib/pleroma/web/api_spec/operations/status_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/status_operation.ex
@@ -31,6 +31,12 @@ def index_operation do
:query,
%Schema{type: :array, items: FlakeID},
"Array of status IDs"
+ ),
+ Operation.parameter(
+ :with_muted,
+ :query,
+ BooleanLike,
+ "Include reactions from muted acccounts."
)
],
operationId: "StatusController.index",
@@ -67,7 +73,15 @@ def show_operation do
description: "View information about a status",
operationId: "StatusController.show",
security: [%{"oAuth" => ["read:statuses"]}],
- parameters: [id_param()],
+ parameters: [
+ id_param(),
+ Operation.parameter(
+ :with_muted,
+ :query,
+ BooleanLike,
+ "Include reactions from muted acccounts."
+ )
+ ],
responses: %{
200 => status_response(),
404 => Operation.response("Not Found", "application/json", ApiError)
diff --git a/lib/pleroma/web/api_spec/operations/subscription_operation.ex b/lib/pleroma/web/api_spec/operations/subscription_operation.ex
index 775dd795d..67c7ea8f3 100644
--- a/lib/pleroma/web/api_spec/operations/subscription_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/subscription_operation.ex
@@ -146,6 +146,11 @@ defp create_request do
allOf: [BooleanLike],
nullable: true,
description: "Receive chat notifications?"
+ },
+ "pleroma:emoji_reaction": %Schema{
+ allOf: [BooleanLike],
+ nullable: true,
+ description: "Receive emoji reaction notifications?"
}
}
}
@@ -210,6 +215,16 @@ defp update_request do
allOf: [BooleanLike],
nullable: true,
description: "Receive poll notifications?"
+ },
+ "pleroma:chat_mention": %Schema{
+ allOf: [BooleanLike],
+ nullable: true,
+ description: "Receive chat notifications?"
+ },
+ "pleroma:emoji_reaction": %Schema{
+ allOf: [BooleanLike],
+ nullable: true,
+ description: "Receive emoji reaction notifications?"
}
}
}
diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex
index ca79f0747..684f6fc92 100644
--- a/lib/pleroma/web/api_spec/schemas/account.ex
+++ b/lib/pleroma/web/api_spec/schemas/account.ex
@@ -127,7 +127,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
discoverable: %Schema{
type: :boolean,
description:
- "whether the user allows discovery of the account in search results and other services."
+ "whether the user allows indexing / listing of the account by external services (search engines etc.)."
},
no_rich_text: %Schema{
type: :boolean,
diff --git a/lib/pleroma/web/api_spec/schemas/visibility_scope.ex b/lib/pleroma/web/api_spec/schemas/visibility_scope.ex
index 831734e27..633269a92 100644
--- a/lib/pleroma/web/api_spec/schemas/visibility_scope.ex
+++ b/lib/pleroma/web/api_spec/schemas/visibility_scope.ex
@@ -9,6 +9,6 @@ defmodule Pleroma.Web.ApiSpec.Schemas.VisibilityScope do
title: "VisibilityScope",
description: "Status visibility",
type: :string,
- enum: ["public", "unlisted", "private", "direct", "list"]
+ enum: ["public", "unlisted", "local", "private", "direct", "list"]
})
end
diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex
index 0ab1b115d..e59254791 100644
--- a/lib/pleroma/web/common_api.ex
+++ b/lib/pleroma/web/common_api.ex
@@ -15,6 +15,7 @@ defmodule Pleroma.Web.CommonAPI do
alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
+ alias Pleroma.Web.CommonAPI.ActivityDraft
import Pleroma.Web.Gettext
import Pleroma.Web.CommonAPI.Utils
@@ -358,7 +359,7 @@ def public_announce?(object, _) do
def get_visibility(_, _, %Participation{}), do: {"direct", "direct"}
def get_visibility(%{visibility: visibility}, in_reply_to, _)
- when visibility in ~w{public unlisted private direct},
+ when visibility in ~w{public local unlisted private direct},
do: {visibility, get_replied_to_visibility(in_reply_to)}
def get_visibility(%{visibility: "list:" <> list_id}, in_reply_to, _) do
@@ -399,31 +400,13 @@ def check_expiry_date(expiry_str) do
end
def listen(user, data) do
- visibility = Map.get(data, :visibility, "public")
-
- with {to, cc} <- get_to_and_cc(user, [], nil, visibility, nil),
- listen_data <-
- data
- |> Map.take([:album, :artist, :title, :length])
- |> Map.new(fn {key, value} -> {to_string(key), value} end)
- |> Map.put("type", "Audio")
- |> Map.put("to", to)
- |> Map.put("cc", cc)
- |> Map.put("actor", user.ap_id),
- {:ok, activity} <-
- ActivityPub.listen(%{
- actor: user,
- to: to,
- object: listen_data,
- context: Utils.generate_context_id(),
- additional: %{"cc" => cc}
- }) do
- {:ok, activity}
+ with {:ok, draft} <- ActivityDraft.listen(user, data) do
+ ActivityPub.listen(draft.changes)
end
end
def post(user, %{status: _} = data) do
- with {:ok, draft} <- Pleroma.Web.CommonAPI.ActivityDraft.create(user, data) do
+ with {:ok, draft} <- ActivityDraft.create(user, data) do
ActivityPub.create(draft.changes, draft.preview?)
end
end
diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex
index 548f76609..aa2616d9e 100644
--- a/lib/pleroma/web/common_api/activity_draft.ex
+++ b/lib/pleroma/web/common_api/activity_draft.ex
@@ -22,7 +22,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
in_reply_to_conversation: nil,
visibility: nil,
expires_at: nil,
- poll: nil,
+ extra: nil,
emoji: %{},
content_html: nil,
mentions: [],
@@ -35,9 +35,14 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
preview?: false,
changes: %{}
- def create(user, params) do
+ def new(user, params) do
%__MODULE__{user: user}
|> put_params(params)
+ end
+
+ def create(user, params) do
+ user
+ |> new(params)
|> status()
|> summary()
|> with_valid(&attachments/1)
@@ -57,6 +62,30 @@ def create(user, params) do
|> validate()
end
+ def listen(user, params) do
+ user
+ |> new(params)
+ |> visibility()
+ |> to_and_cc()
+ |> context()
+ |> listen_object()
+ |> with_valid(&changes/1)
+ |> validate()
+ end
+
+ defp listen_object(draft) do
+ object =
+ draft.params
+ |> Map.take([:album, :artist, :title, :length])
+ |> Map.new(fn {key, value} -> {to_string(key), value} end)
+ |> Map.put("type", "Audio")
+ |> Map.put("to", draft.to)
+ |> Map.put("cc", draft.cc)
+ |> Map.put("actor", draft.user.ap_id)
+
+ %__MODULE__{draft | object: object}
+ end
+
defp put_params(draft, params) do
params = Map.put_new(params, :in_reply_to_status_id, params[:in_reply_to_id])
%__MODULE__{draft | params: params}
@@ -121,7 +150,7 @@ defp expires_at(draft) do
defp poll(draft) do
case Utils.make_poll_data(draft.params) do
{:ok, {poll, poll_emoji}} ->
- %__MODULE__{draft | poll: poll, emoji: Map.merge(draft.emoji, poll_emoji)}
+ %__MODULE__{draft | extra: poll, emoji: Map.merge(draft.emoji, poll_emoji)}
{:error, message} ->
add_error(draft, message)
@@ -129,32 +158,18 @@ defp poll(draft) do
end
defp content(draft) do
- {content_html, mentions, tags} =
- Utils.make_content_html(
- draft.status,
- draft.attachments,
- draft.params,
- draft.visibility
- )
+ {content_html, mentioned_users, tags} = Utils.make_content_html(draft)
+
+ mentions =
+ mentioned_users
+ |> Enum.map(fn {_, mentioned_user} -> mentioned_user.ap_id end)
+ |> Utils.get_addressed_users(draft.params[:to])
%__MODULE__{draft | content_html: content_html, mentions: mentions, tags: tags}
end
defp to_and_cc(draft) do
- addressed_users =
- draft.mentions
- |> Enum.map(fn {_, mentioned_user} -> mentioned_user.ap_id end)
- |> Utils.get_addressed_users(draft.params[:to])
-
- {to, cc} =
- Utils.get_to_and_cc(
- draft.user,
- addressed_users,
- draft.in_reply_to,
- draft.visibility,
- draft.in_reply_to_conversation
- )
-
+ {to, cc} = Utils.get_to_and_cc(draft)
%__MODULE__{draft | to: to, cc: cc}
end
@@ -172,19 +187,7 @@ defp object(draft) do
emoji = Map.merge(Pleroma.Emoji.Formatter.get_emoji_map(draft.full_payload), draft.emoji)
object =
- Utils.make_note_data(
- draft.user.ap_id,
- draft.to,
- draft.context,
- draft.content_html,
- draft.attachments,
- draft.in_reply_to,
- draft.tags,
- draft.summary,
- draft.cc,
- draft.sensitive,
- draft.poll
- )
+ Utils.make_note_data(draft)
|> Map.put("emoji", emoji)
|> Map.put("source", draft.status)
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 3b71adf0e..1c74ea787 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -16,6 +16,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
+ alias Pleroma.Web.CommonAPI.ActivityDraft
alias Pleroma.Web.MediaProxy
alias Pleroma.Web.Plugs.AuthenticationPlug
@@ -50,67 +51,62 @@ def attachments_from_ids_descs(ids, descs_str) do
{_, descs} = Jason.decode(descs_str)
Enum.map(ids, fn media_id ->
- case Repo.get(Object, media_id) do
- %Object{data: data} ->
- Map.put(data, "name", descs[media_id])
-
- _ ->
- nil
+ with %Object{data: data} <- Repo.get(Object, media_id) do
+ Map.put(data, "name", descs[media_id])
end
end)
|> Enum.reject(&is_nil/1)
end
- @spec get_to_and_cc(
- User.t(),
- list(String.t()),
- Activity.t() | nil,
- String.t(),
- Participation.t() | nil
- ) :: {list(String.t()), list(String.t())}
+ @spec get_to_and_cc(ActivityDraft.t()) :: {list(String.t()), list(String.t())}
- def get_to_and_cc(_, _, _, _, %Participation{} = participation) do
+ def get_to_and_cc(%{in_reply_to_conversation: %Participation{} = participation}) do
participation = Repo.preload(participation, :recipients)
{Enum.map(participation.recipients, & &1.ap_id), []}
end
- def get_to_and_cc(user, mentioned_users, inReplyTo, "public", _) do
- to = [Pleroma.Constants.as_public() | mentioned_users]
- cc = [user.follower_address]
+ def get_to_and_cc(%{visibility: visibility} = draft) when visibility in ["public", "local"] do
+ to =
+ case visibility do
+ "public" -> [Pleroma.Constants.as_public() | draft.mentions]
+ "local" -> [Pleroma.Constants.as_local_public() | draft.mentions]
+ end
- if inReplyTo do
- {Enum.uniq([inReplyTo.data["actor"] | to]), cc}
+ cc = [draft.user.follower_address]
+
+ if draft.in_reply_to do
+ {Enum.uniq([draft.in_reply_to.data["actor"] | to]), cc}
else
{to, cc}
end
end
- def get_to_and_cc(user, mentioned_users, inReplyTo, "unlisted", _) do
- to = [user.follower_address | mentioned_users]
+ def get_to_and_cc(%{visibility: "unlisted"} = draft) do
+ to = [draft.user.follower_address | draft.mentions]
cc = [Pleroma.Constants.as_public()]
- if inReplyTo do
- {Enum.uniq([inReplyTo.data["actor"] | to]), cc}
+ if draft.in_reply_to do
+ {Enum.uniq([draft.in_reply_to.data["actor"] | to]), cc}
else
{to, cc}
end
end
- def get_to_and_cc(user, mentioned_users, inReplyTo, "private", _) do
- {to, cc} = get_to_and_cc(user, mentioned_users, inReplyTo, "direct", nil)
- {[user.follower_address | to], cc}
+ def get_to_and_cc(%{visibility: "private"} = draft) do
+ {to, cc} = get_to_and_cc(struct(draft, visibility: "direct"))
+ {[draft.user.follower_address | to], cc}
end
- def get_to_and_cc(_user, mentioned_users, inReplyTo, "direct", _) do
+ def get_to_and_cc(%{visibility: "direct"} = draft) do
# If the OP is a DM already, add the implicit actor.
- if inReplyTo && Visibility.is_direct?(inReplyTo) do
- {Enum.uniq([inReplyTo.data["actor"] | mentioned_users]), []}
+ if draft.in_reply_to && Visibility.is_direct?(draft.in_reply_to) do
+ {Enum.uniq([draft.in_reply_to.data["actor"] | draft.mentions]), []}
else
- {mentioned_users, []}
+ {draft.mentions, []}
end
end
- def get_to_and_cc(_user, mentions, _inReplyTo, {:list, _}, _), do: {mentions, []}
+ def get_to_and_cc(%{visibility: {:list, _}, mentions: mentions}), do: {mentions, []}
def get_addressed_users(_, to) when is_list(to) do
User.get_ap_ids_by_nicknames(to)
@@ -203,30 +199,25 @@ defp validate_poll_expiration(expires_in, %{min_expiration: min, max_expiration:
end
end
- def make_content_html(
- status,
- attachments,
- data,
- visibility
- ) do
+ def make_content_html(%ActivityDraft{} = draft) do
attachment_links =
- data
+ draft.params
|> Map.get("attachment_links", Config.get([:instance, :attachment_links]))
|> truthy_param?()
- content_type = get_content_type(data[:content_type])
+ content_type = get_content_type(draft.params[:content_type])
options =
- if visibility == "direct" && Config.get([:instance, :safe_dm_mentions]) do
+ if draft.visibility == "direct" && Config.get([:instance, :safe_dm_mentions]) do
[safe_mention: true]
else
[]
end
- status
+ draft.status
|> format_input(content_type, options)
- |> maybe_add_attachments(attachments, attachment_links)
- |> maybe_add_nsfw_tag(data)
+ |> maybe_add_attachments(draft.attachments, attachment_links)
+ |> maybe_add_nsfw_tag(draft.params)
end
defp get_content_type(content_type) do
@@ -308,33 +299,21 @@ def format_input(text, "text/markdown", options) do
|> Formatter.html_escape("text/html")
end
- def make_note_data(
- actor,
- to,
- context,
- content_html,
- attachments,
- in_reply_to,
- tags,
- summary \\ nil,
- cc \\ [],
- sensitive \\ false,
- extra_params \\ %{}
- ) do
+ def make_note_data(%ActivityDraft{} = draft) do
%{
"type" => "Note",
- "to" => to,
- "cc" => cc,
- "content" => content_html,
- "summary" => summary,
- "sensitive" => truthy_param?(sensitive),
- "context" => context,
- "attachment" => attachments,
- "actor" => actor,
- "tag" => Keyword.values(tags) |> Enum.uniq()
+ "to" => draft.to,
+ "cc" => draft.cc,
+ "content" => draft.content_html,
+ "summary" => draft.summary,
+ "sensitive" => draft.sensitive,
+ "context" => draft.context,
+ "attachment" => draft.attachments,
+ "actor" => draft.user.ap_id,
+ "tag" => Keyword.values(draft.tags) |> Enum.uniq()
}
- |> add_in_reply_to(in_reply_to)
- |> Map.merge(extra_params)
+ |> add_in_reply_to(draft.in_reply_to)
+ |> Map.merge(draft.extra)
end
defp add_in_reply_to(object, nil), do: object
diff --git a/lib/pleroma/web/fed_sockets.ex b/lib/pleroma/web/fed_sockets.ex
deleted file mode 100644
index 1fd5899c8..000000000
--- a/lib/pleroma/web/fed_sockets.ex
+++ /dev/null
@@ -1,185 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.FedSockets do
- @moduledoc """
- This documents the FedSockets framework. A framework for federating
- ActivityPub objects between servers via persistant WebSocket connections.
-
- FedSockets allow servers to authenticate on first contact and maintain that
- connection, eliminating the need to authenticate every time data needs to be shared.
-
- ## Protocol
- FedSockets currently support 2 types of data transfer:
- * `publish` method which doesn't require a response
- * `fetch` method requires a response be sent
-
- ### Publish
- The publish operation sends a json encoded map of the shape:
- %{action: :publish, data: json}
- and accepts (but does not require) a reply of form:
- %{"action" => "publish_reply"}
-
- The outgoing params represent
- * data: ActivityPub object encoded into json
-
-
- ### Fetch
- The fetch operation sends a json encoded map of the shape:
- %{action: :fetch, data: id, uuid: fetch_uuid}
- and requires a reply of form:
- %{"action" => "fetch_reply", "uuid" => uuid, "data" => data}
-
- The outgoing params represent
- * id: an ActivityPub object URI
- * uuid: a unique uuid generated by the sender
-
- The reply params represent
- * data: an ActivityPub object encoded into json
- * uuid: the uuid sent along with the fetch request
-
- ## Examples
- Clients of FedSocket transfers shouldn't need to use any of the functions outside of this module.
-
- A typical publish operation can be performed through the following code, and a fetch operation in a similar manner.
-
- case FedSockets.get_or_create_fed_socket(inbox) do
- {:ok, fedsocket} ->
- FedSockets.publish(fedsocket, json)
-
- _ ->
- alternative_publish(inbox, actor, json, params)
- end
-
- ## Configuration
- FedSockets have the following config settings
-
- config :pleroma, :fed_sockets,
- enabled: true,
- ping_interval: :timer.seconds(15),
- connection_duration: :timer.hours(1),
- rejection_duration: :timer.hours(1),
- fed_socket_fetches: [
- default: 12_000,
- interval: 3_000,
- lazy: false
- ]
- * enabled - turn FedSockets on or off with this flag. Can be toggled at runtime.
- * connection_duration - How long a FedSocket can sit idle before it's culled.
- * rejection_duration - After failing to make a FedSocket connection a host will be excluded
- from further connections for this amount of time
- * fed_socket_fetches - Use these parameters to pass options to the Cachex queue backing the FetchRegistry
- * fed_socket_rejections - Use these parameters to pass options to the Cachex queue backing the FedRegistry
-
- Cachex options are
- * default: the minimum amount of time a fetch can wait before it times out.
- * interval: the interval between checks for timed out entries. This plus the default represent the maximum time allowed
- * lazy: leave at false for consistant and fast lookups, set to true for stricter timeout enforcement
-
- """
- require Logger
-
- alias Pleroma.Web.FedSockets.FedRegistry
- alias Pleroma.Web.FedSockets.FedSocket
- alias Pleroma.Web.FedSockets.SocketInfo
-
- @doc """
- returns a FedSocket for the given origin. Will reuse an existing one or create a new one.
-
- address is expected to be a fully formed URL such as:
- "http://www.example.com" or "http://www.example.com:8080"
-
- It can and usually does include additional path parameters,
- but these are ignored as the FedSockets are organized by host and port info alone.
- """
- def get_or_create_fed_socket(address) do
- with {:cache, {:error, :missing}} <- {:cache, get_fed_socket(address)},
- {:connect, {:ok, _pid}} <- {:connect, FedSocket.connect_to_host(address)},
- {:cache, {:ok, fed_socket}} <- {:cache, get_fed_socket(address)} do
- Logger.debug("fedsocket created for - #{inspect(address)}")
- {:ok, fed_socket}
- else
- {:cache, {:ok, socket}} ->
- Logger.debug("fedsocket found in cache - #{inspect(address)}")
- {:ok, socket}
-
- {:cache, {:error, :rejected} = e} ->
- e
-
- {:connect, {:error, _host}} ->
- Logger.debug("set host rejected for - #{inspect(address)}")
- FedRegistry.set_host_rejected(address)
- {:error, :rejected}
-
- {_, {:error, :disabled}} ->
- {:error, :disabled}
-
- {_, {:error, reason}} ->
- Logger.warn("get_or_create_fed_socket error - #{inspect(reason)}")
- {:error, reason}
- end
- end
-
- @doc """
- returns a FedSocket for the given origin. Will not create a new FedSocket if one does not exist.
-
- address is expected to be a fully formed URL such as:
- "http://www.example.com" or "http://www.example.com:8080"
- """
- def get_fed_socket(address) do
- origin = SocketInfo.origin(address)
-
- with {:config, true} <- {:config, Pleroma.Config.get([:fed_sockets, :enabled], false)},
- {:ok, socket} <- FedRegistry.get_fed_socket(origin) do
- {:ok, socket}
- else
- {:config, _} ->
- {:error, :disabled}
-
- {:error, :rejected} ->
- Logger.debug("FedSocket previously rejected - #{inspect(origin)}")
- {:error, :rejected}
-
- {:error, reason} ->
- {:error, reason}
- end
- end
-
- @doc """
- Sends the supplied data via the publish protocol.
- It will not block waiting for a reply.
- Returns :ok but this is not an indication of a successful transfer.
-
- the data is expected to be JSON encoded binary data.
- """
- def publish(%SocketInfo{} = fed_socket, json) do
- FedSocket.publish(fed_socket, json)
- end
-
- @doc """
- Sends the supplied data via the fetch protocol.
- It will block waiting for a reply or timeout.
-
- Returns {:ok, object} where object is the requested object (or nil)
- {:error, :timeout} in the event the message was not responded to
-
- the id is expected to be the URI of an ActivityPub object.
- """
- def fetch(%SocketInfo{} = fed_socket, id) do
- FedSocket.fetch(fed_socket, id)
- end
-
- @doc """
- Disconnect all and restart FedSockets.
- This is mainly used in development and testing but could be useful in production.
- """
- def reset do
- FedRegistry
- |> Process.whereis()
- |> Process.exit(:testing)
- end
-
- def uri_for_origin(origin),
- do: "ws://#{origin}/api/fedsocket/v1"
-end
diff --git a/lib/pleroma/web/fed_sockets/fed_registry.ex b/lib/pleroma/web/fed_sockets/fed_registry.ex
deleted file mode 100644
index e00ea69c0..000000000
--- a/lib/pleroma/web/fed_sockets/fed_registry.ex
+++ /dev/null
@@ -1,185 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.FedSockets.FedRegistry do
- @moduledoc """
- The FedRegistry stores the active FedSockets for quick retrieval.
-
- The storage and retrieval portion of the FedRegistry is done in process through
- elixir's `Registry` module for speed and its ability to monitor for terminated processes.
-
- Dropped connections will be caught by `Registry` and deleted. Since the next
- message will initiate a new connection there is no reason to try and reconnect at that point.
-
- Normally outside modules should have no need to call or use the FedRegistry themselves.
- """
-
- alias Pleroma.Web.FedSockets.FedSocket
- alias Pleroma.Web.FedSockets.SocketInfo
-
- require Logger
-
- @default_rejection_duration 15 * 60 * 1000
- @rejections :fed_socket_rejections
-
- @doc """
- Retrieves a FedSocket from the Registry given it's origin.
-
- The origin is expected to be a string identifying the endpoint "example.com" or "example2.com:8080"
-
- Will return:
- * {:ok, fed_socket} for working FedSockets
- * {:error, :rejected} for origins that have been tried and refused within the rejection duration interval
- * {:error, some_reason} usually :missing for unknown origins
- """
- def get_fed_socket(origin) do
- case get_registry_data(origin) do
- {:error, reason} ->
- {:error, reason}
-
- {:ok, %{state: :connected} = socket_info} ->
- {:ok, socket_info}
- end
- end
-
- @doc """
- Adds a connected FedSocket to the Registry.
-
- Always returns {:ok, fed_socket}
- """
- def add_fed_socket(origin, pid \\ nil) do
- origin
- |> SocketInfo.build(pid)
- |> SocketInfo.connect()
- |> add_socket_info
- end
-
- defp add_socket_info(%{origin: origin, state: :connected} = socket_info) do
- case Registry.register(FedSockets.Registry, origin, socket_info) do
- {:ok, _owner} ->
- clear_prior_rejection(origin)
- Logger.debug("fedsocket added: #{inspect(origin)}")
-
- {:ok, socket_info}
-
- {:error, {:already_registered, _pid}} ->
- FedSocket.close(socket_info)
- existing_socket_info = Registry.lookup(FedSockets.Registry, origin)
-
- {:ok, existing_socket_info}
-
- _ ->
- {:error, :error_adding_socket}
- end
- end
-
- @doc """
- Mark this origin as having rejected a connection attempt.
- This will keep it from getting additional connection attempts
- for a period of time specified in the config.
-
- Always returns {:ok, new_reg_data}
- """
- def set_host_rejected(uri) do
- new_reg_data =
- uri
- |> SocketInfo.origin()
- |> get_or_create_registry_data()
- |> set_to_rejected()
- |> save_registry_data()
-
- {:ok, new_reg_data}
- end
-
- @doc """
- Retrieves the FedRegistryData from the Registry given it's origin.
-
- The origin is expected to be a string identifying the endpoint "example.com" or "example2.com:8080"
-
- Will return:
- * {:ok, fed_registry_data} for known origins
- * {:error, :missing} for uniknown origins
- * {:error, :cache_error} indicating some low level runtime issues
- """
- def get_registry_data(origin) do
- case Registry.lookup(FedSockets.Registry, origin) do
- [] ->
- if is_rejected?(origin) do
- Logger.debug("previously rejected fedsocket requested")
- {:error, :rejected}
- else
- {:error, :missing}
- end
-
- [{_pid, %{state: :connected} = socket_info}] ->
- {:ok, socket_info}
-
- _ ->
- {:error, :cache_error}
- end
- end
-
- @doc """
- Retrieves a map of all sockets from the Registry. The keys are the origins and the values are the corresponding SocketInfo
- """
- def list_all do
- (list_all_connected() ++ list_all_rejected())
- |> Enum.into(%{})
- end
-
- defp list_all_connected do
- FedSockets.Registry
- |> Registry.select([{{:"$1", :_, :"$3"}, [], [{{:"$1", :"$3"}}]}])
- end
-
- defp list_all_rejected do
- {:ok, keys} = Cachex.keys(@rejections)
-
- {:ok, registry_data} =
- Cachex.execute(@rejections, fn worker ->
- Enum.map(keys, fn k -> {k, Cachex.get!(worker, k)} end)
- end)
-
- registry_data
- end
-
- defp clear_prior_rejection(origin),
- do: Cachex.del(@rejections, origin)
-
- defp is_rejected?(origin) do
- case Cachex.get(@rejections, origin) do
- {:ok, nil} ->
- false
-
- {:ok, _} ->
- true
- end
- end
-
- defp get_or_create_registry_data(origin) do
- case get_registry_data(origin) do
- {:error, :missing} ->
- %SocketInfo{origin: origin}
-
- {:ok, socket_info} ->
- socket_info
- end
- end
-
- defp save_registry_data(%SocketInfo{origin: origin, state: :connected} = socket_info) do
- {:ok, true} = Registry.update_value(FedSockets.Registry, origin, fn _ -> socket_info end)
- socket_info
- end
-
- defp save_registry_data(%SocketInfo{origin: origin, state: :rejected} = socket_info) do
- rejection_expiration =
- Pleroma.Config.get([:fed_sockets, :rejection_duration], @default_rejection_duration)
-
- {:ok, true} = Cachex.put(@rejections, origin, socket_info, ttl: rejection_expiration)
- socket_info
- end
-
- defp set_to_rejected(%SocketInfo{} = socket_info),
- do: %SocketInfo{socket_info | state: :rejected}
-end
diff --git a/lib/pleroma/web/fed_sockets/fed_socket.ex b/lib/pleroma/web/fed_sockets/fed_socket.ex
deleted file mode 100644
index 98d64e65a..000000000
--- a/lib/pleroma/web/fed_sockets/fed_socket.ex
+++ /dev/null
@@ -1,137 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.FedSockets.FedSocket do
- @moduledoc """
- The FedSocket module abstracts the actions to be taken taken on connections regardless of
- whether the connection started as inbound or outbound.
-
-
- Normally outside modules will have no need to call the FedSocket module directly.
- """
-
- alias Pleroma.Object
- alias Pleroma.Object.Containment
- alias Pleroma.User
- alias Pleroma.Web.ActivityPub.ObjectView
- alias Pleroma.Web.ActivityPub.UserView
- alias Pleroma.Web.ActivityPub.Visibility
- alias Pleroma.Web.FedSockets.FetchRegistry
- alias Pleroma.Web.FedSockets.IngesterWorker
- alias Pleroma.Web.FedSockets.OutgoingHandler
- alias Pleroma.Web.FedSockets.SocketInfo
-
- require Logger
-
- @shake "61dd18f7-f1e6-49a4-939a-a749fcdc1103"
-
- def connect_to_host(uri) do
- case OutgoingHandler.start_link(uri) do
- {:ok, pid} ->
- {:ok, pid}
-
- error ->
- {:error, error}
- end
- end
-
- def close(%SocketInfo{pid: socket_pid}),
- do: Process.send(socket_pid, :close, [])
-
- def publish(%SocketInfo{pid: socket_pid}, json) do
- %{action: :publish, data: json}
- |> Jason.encode!()
- |> send_packet(socket_pid)
- end
-
- def fetch(%SocketInfo{pid: socket_pid}, id) do
- fetch_uuid = FetchRegistry.register_fetch(id)
-
- %{action: :fetch, data: id, uuid: fetch_uuid}
- |> Jason.encode!()
- |> send_packet(socket_pid)
-
- wait_for_fetch_to_return(fetch_uuid, 0)
- end
-
- def receive_package(%SocketInfo{} = fed_socket, json) do
- json
- |> Jason.decode!()
- |> process_package(fed_socket)
- end
-
- defp wait_for_fetch_to_return(uuid, cntr) do
- case FetchRegistry.check_fetch(uuid) do
- {:error, :waiting} ->
- Process.sleep(:math.pow(cntr, 3) |> Kernel.trunc())
- wait_for_fetch_to_return(uuid, cntr + 1)
-
- {:error, :missing} ->
- Logger.error("FedSocket fetch timed out - #{inspect(uuid)}")
- {:error, :timeout}
-
- {:ok, _fr} ->
- FetchRegistry.pop_fetch(uuid)
- end
- end
-
- defp process_package(%{"action" => "publish", "data" => data}, %{origin: origin} = _fed_socket) do
- if Containment.contain_origin(origin, data) do
- IngesterWorker.enqueue("ingest", %{"object" => data})
- end
-
- {:reply, %{"action" => "publish_reply", "status" => "processed"}}
- end
-
- defp process_package(%{"action" => "fetch_reply", "uuid" => uuid, "data" => data}, _fed_socket) do
- FetchRegistry.register_fetch_received(uuid, data)
- {:noreply, nil}
- end
-
- defp process_package(%{"action" => "fetch", "uuid" => uuid, "data" => ap_id}, _fed_socket) do
- {:ok, data} = render_fetched_data(ap_id, uuid)
- {:reply, data}
- end
-
- defp process_package(%{"action" => "publish_reply"}, _fed_socket) do
- {:noreply, nil}
- end
-
- defp process_package(other, _fed_socket) do
- Logger.warn("unknown json packages received #{inspect(other)}")
- {:noreply, nil}
- end
-
- defp render_fetched_data(ap_id, uuid) do
- {:ok,
- %{
- "action" => "fetch_reply",
- "status" => "processed",
- "uuid" => uuid,
- "data" => represent_item(ap_id)
- }}
- end
-
- defp represent_item(ap_id) do
- case User.get_by_ap_id(ap_id) do
- nil ->
- object = Object.get_cached_by_ap_id(ap_id)
-
- if Visibility.is_public?(object) do
- Phoenix.View.render_to_string(ObjectView, "object.json", object: object)
- else
- nil
- end
-
- user ->
- Phoenix.View.render_to_string(UserView, "user.json", user: user)
- end
- end
-
- defp send_packet(data, socket_pid) do
- Process.send(socket_pid, {:send, data}, [])
- end
-
- def shake, do: @shake
-end
diff --git a/lib/pleroma/web/fed_sockets/fetch_registry.ex b/lib/pleroma/web/fed_sockets/fetch_registry.ex
deleted file mode 100644
index 7897f0fc6..000000000
--- a/lib/pleroma/web/fed_sockets/fetch_registry.ex
+++ /dev/null
@@ -1,151 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.FedSockets.FetchRegistry do
- @moduledoc """
- The FetchRegistry acts as a broker for fetch requests and return values.
- This allows calling processes to block while waiting for a reply.
- It doesn't impose it's own process instead using `Cachex` to handle fetches in process, allowing
- multi threaded processes to avoid bottlenecking.
-
- Normally outside modules will have no need to call or use the FetchRegistry themselves.
-
- The `Cachex` parameters can be controlled from the config. Since exact timeout intervals
- aren't necessary the following settings are used by default:
-
- config :pleroma, :fed_sockets,
- fed_socket_fetches: [
- default: 12_000,
- interval: 3_000,
- lazy: false
- ]
-
- """
-
- defmodule FetchRegistryData do
- defstruct uuid: nil,
- sent_json: nil,
- received_json: nil,
- sent_at: nil,
- received_at: nil
- end
-
- alias Ecto.UUID
-
- require Logger
-
- @fetches :fed_socket_fetches
-
- @doc """
- Registers a json request wth the FetchRegistry and returns the identifying UUID.
- """
- def register_fetch(json) do
- %FetchRegistryData{uuid: uuid} =
- json
- |> new_registry_data
- |> save_registry_data
-
- uuid
- end
-
- @doc """
- Reports on the status of a Fetch given the identifying UUID.
-
- Will return
- * {:ok, fetched_object} if a fetch has completed
- * {:error, :waiting} if a fetch is still pending
- * {:error, other_error} usually :missing to indicate a fetch that has timed out
- """
- def check_fetch(uuid) do
- case get_registry_data(uuid) do
- {:ok, %FetchRegistryData{received_at: nil}} ->
- {:error, :waiting}
-
- {:ok, %FetchRegistryData{} = reg_data} ->
- {:ok, reg_data}
-
- e ->
- e
- end
- end
-
- @doc """
- Retrieves the response to a fetch given the identifying UUID.
- The completed fetch will be deleted from the FetchRegistry
-
- Will return
- * {:ok, fetched_object} if a fetch has completed
- * {:error, :waiting} if a fetch is still pending
- * {:error, other_error} usually :missing to indicate a fetch that has timed out
- """
- def pop_fetch(uuid) do
- case check_fetch(uuid) do
- {:ok, %FetchRegistryData{received_json: received_json}} ->
- delete_registry_data(uuid)
- {:ok, received_json}
-
- e ->
- e
- end
- end
-
- @doc """
- This is called to register a fetch has returned.
- It expects the result data along with the UUID that was sent in the request
-
- Will return the fetched object or :error
- """
- def register_fetch_received(uuid, data) do
- case get_registry_data(uuid) do
- {:ok, %FetchRegistryData{received_at: nil} = reg_data} ->
- reg_data
- |> set_fetch_received(data)
- |> save_registry_data()
-
- {:ok, %FetchRegistryData{} = reg_data} ->
- Logger.warn("tried to add fetched data twice - #{uuid}")
- reg_data
-
- {:error, _} ->
- Logger.warn("Error adding fetch to registry - #{uuid}")
- :error
- end
- end
-
- defp new_registry_data(json) do
- %FetchRegistryData{
- uuid: UUID.generate(),
- sent_json: json,
- sent_at: :erlang.monotonic_time(:millisecond)
- }
- end
-
- defp get_registry_data(origin) do
- case Cachex.get(@fetches, origin) do
- {:ok, nil} ->
- {:error, :missing}
-
- {:ok, reg_data} ->
- {:ok, reg_data}
-
- _ ->
- {:error, :cache_error}
- end
- end
-
- defp set_fetch_received(%FetchRegistryData{} = reg_data, data),
- do: %FetchRegistryData{
- reg_data
- | received_at: :erlang.monotonic_time(:millisecond),
- received_json: data
- }
-
- defp save_registry_data(%FetchRegistryData{uuid: uuid} = reg_data) do
- {:ok, true} = Cachex.put(@fetches, uuid, reg_data)
- reg_data
- end
-
- defp delete_registry_data(origin),
- do: {:ok, true} = Cachex.del(@fetches, origin)
-end
diff --git a/lib/pleroma/web/fed_sockets/incoming_handler.ex b/lib/pleroma/web/fed_sockets/incoming_handler.ex
deleted file mode 100644
index 49d0d9d84..000000000
--- a/lib/pleroma/web/fed_sockets/incoming_handler.ex
+++ /dev/null
@@ -1,88 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.FedSockets.IncomingHandler do
- require Logger
-
- alias Pleroma.Web.FedSockets.FedRegistry
- alias Pleroma.Web.FedSockets.FedSocket
- alias Pleroma.Web.FedSockets.SocketInfo
-
- import HTTPSignatures, only: [validate_conn: 1, split_signature: 1]
-
- @behaviour :cowboy_websocket
-
- def init(req, state) do
- shake = FedSocket.shake()
-
- with true <- Pleroma.Config.get([:fed_sockets, :enabled]),
- sec_protocol <- :cowboy_req.header("sec-websocket-protocol", req, nil),
- headers = %{"(request-target)" => ^shake} <- :cowboy_req.headers(req),
- true <- validate_conn(%{req_headers: headers}),
- %{"keyId" => origin} <- split_signature(headers["signature"]) do
- req =
- if is_nil(sec_protocol) do
- req
- else
- :cowboy_req.set_resp_header("sec-websocket-protocol", sec_protocol, req)
- end
-
- {:cowboy_websocket, req, %{origin: origin}, %{}}
- else
- _ ->
- {:ok, req, state}
- end
- end
-
- def websocket_init(%{origin: origin}) do
- case FedRegistry.add_fed_socket(origin) do
- {:ok, socket_info} ->
- {:ok, socket_info}
-
- e ->
- Logger.error("FedSocket websocket_init failed - #{inspect(e)}")
- {:error, inspect(e)}
- end
- end
-
- # Use the ping to check if the connection should be expired
- def websocket_handle(:ping, socket_info) do
- if SocketInfo.expired?(socket_info) do
- {:stop, socket_info}
- else
- {:ok, socket_info, :hibernate}
- end
- end
-
- def websocket_handle({:text, data}, socket_info) do
- socket_info = SocketInfo.touch(socket_info)
-
- case FedSocket.receive_package(socket_info, data) do
- {:noreply, _} ->
- {:ok, socket_info}
-
- {:reply, reply} ->
- {:reply, {:text, Jason.encode!(reply)}, socket_info}
-
- {:error, reason} ->
- Logger.error("incoming error - receive_package: #{inspect(reason)}")
- {:ok, socket_info}
- end
- end
-
- def websocket_info({:send, message}, socket_info) do
- socket_info = SocketInfo.touch(socket_info)
-
- {:reply, {:text, message}, socket_info}
- end
-
- def websocket_info(:close, state) do
- {:stop, state}
- end
-
- def websocket_info(message, state) do
- Logger.debug("#{__MODULE__} unknown message #{inspect(message)}")
- {:ok, state}
- end
-end
diff --git a/lib/pleroma/web/fed_sockets/ingester_worker.ex b/lib/pleroma/web/fed_sockets/ingester_worker.ex
deleted file mode 100644
index 325f2a4ab..000000000
--- a/lib/pleroma/web/fed_sockets/ingester_worker.ex
+++ /dev/null
@@ -1,33 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.FedSockets.IngesterWorker do
- use Pleroma.Workers.WorkerHelper, queue: "ingestion_queue"
- require Logger
-
- alias Pleroma.Web.Federator
-
- @impl Oban.Worker
- def perform(%Job{args: %{"op" => "ingest", "object" => ingestee}}) do
- try do
- ingestee
- |> Jason.decode!()
- |> do_ingestion()
- rescue
- e ->
- Logger.error("IngesterWorker error - #{inspect(e)}")
- e
- end
- end
-
- defp do_ingestion(params) do
- case Federator.incoming_ap_doc(params) do
- {:error, reason} ->
- {:error, reason}
-
- {:ok, object} ->
- {:ok, object}
- end
- end
-end
diff --git a/lib/pleroma/web/fed_sockets/outgoing_handler.ex b/lib/pleroma/web/fed_sockets/outgoing_handler.ex
deleted file mode 100644
index e235a7c43..000000000
--- a/lib/pleroma/web/fed_sockets/outgoing_handler.ex
+++ /dev/null
@@ -1,151 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.FedSockets.OutgoingHandler do
- use GenServer
-
- require Logger
-
- alias Pleroma.Application
- alias Pleroma.Web.ActivityPub.InternalFetchActor
- alias Pleroma.Web.FedSockets
- alias Pleroma.Web.FedSockets.FedRegistry
- alias Pleroma.Web.FedSockets.FedSocket
- alias Pleroma.Web.FedSockets.SocketInfo
-
- def start_link(uri) do
- GenServer.start_link(__MODULE__, %{uri: uri})
- end
-
- def init(%{uri: uri}) do
- case initiate_connection(uri) do
- {:ok, ws_origin, conn_pid} ->
- FedRegistry.add_fed_socket(ws_origin, conn_pid)
-
- {:error, reason} ->
- Logger.debug("Outgoing connection failed - #{inspect(reason)}")
- :ignore
- end
- end
-
- def handle_info({:gun_ws, conn_pid, _ref, {:text, data}}, socket_info) do
- socket_info = SocketInfo.touch(socket_info)
-
- case FedSocket.receive_package(socket_info, data) do
- {:noreply, _} ->
- {:noreply, socket_info}
-
- {:reply, reply} ->
- :gun.ws_send(conn_pid, {:text, Jason.encode!(reply)})
- {:noreply, socket_info}
-
- {:error, reason} ->
- Logger.error("incoming error - receive_package: #{inspect(reason)}")
- {:noreply, socket_info}
- end
- end
-
- def handle_info(:close, state) do
- Logger.debug("Sending close frame !!!!!!!")
- {:close, state}
- end
-
- def handle_info({:gun_down, _pid, _prot, :closed, _}, state) do
- {:stop, :normal, state}
- end
-
- def handle_info({:send, data}, %{conn_pid: conn_pid} = socket_info) do
- socket_info = SocketInfo.touch(socket_info)
- :gun.ws_send(conn_pid, {:text, data})
- {:noreply, socket_info}
- end
-
- def handle_info({:gun_ws, _, _, :pong}, state) do
- {:noreply, state, :hibernate}
- end
-
- def handle_info(msg, state) do
- Logger.debug("#{__MODULE__} unhandled event #{inspect(msg)}")
- {:noreply, state}
- end
-
- def terminate(reason, state) do
- Logger.debug(
- "#{__MODULE__} terminating outgoing connection for #{inspect(state)} for #{inspect(reason)}"
- )
-
- {:ok, state}
- end
-
- def initiate_connection(uri) do
- ws_uri =
- uri
- |> SocketInfo.origin()
- |> FedSockets.uri_for_origin()
-
- %{host: host, port: port, path: path} = URI.parse(ws_uri)
-
- with {:ok, conn_pid} <- :gun.open(to_charlist(host), port, %{protocols: [:http]}),
- {:ok, _} <- :gun.await_up(conn_pid),
- reference <-
- :gun.get(conn_pid, to_charlist(path), [
- {'user-agent', to_charlist(Application.user_agent())}
- ]),
- {:response, :fin, 204, _} <- :gun.await(conn_pid, reference),
- headers <- build_headers(uri),
- ref <- :gun.ws_upgrade(conn_pid, to_charlist(path), headers, %{silence_pings: false}) do
- receive do
- {:gun_upgrade, ^conn_pid, ^ref, [<<"websocket">>], _} ->
- {:ok, ws_uri, conn_pid}
- after
- 15_000 ->
- Logger.debug("Fedsocket timeout connecting to #{inspect(uri)}")
- {:error, :timeout}
- end
- else
- {:response, :nofin, 404, _} ->
- {:error, :fedsockets_not_supported}
-
- e ->
- Logger.debug("Fedsocket error connecting to #{inspect(uri)}")
- {:error, e}
- end
- end
-
- defp build_headers(uri) do
- host_for_sig = uri |> URI.parse() |> host_signature()
-
- shake = FedSocket.shake()
- digest = "SHA-256=" <> (:crypto.hash(:sha256, shake) |> Base.encode64())
- date = Pleroma.Signature.signed_date()
- shake_size = byte_size(shake)
-
- signature_opts = %{
- "(request-target)": shake,
- "content-length": to_charlist("#{shake_size}"),
- date: date,
- digest: digest,
- host: host_for_sig
- }
-
- signature = Pleroma.Signature.sign(InternalFetchActor.get_actor(), signature_opts)
-
- [
- {'signature', to_charlist(signature)},
- {'date', date},
- {'digest', to_charlist(digest)},
- {'content-length', to_charlist("#{shake_size}")},
- {to_charlist("(request-target)"), to_charlist(shake)},
- {'user-agent', to_charlist(Application.user_agent())}
- ]
- end
-
- defp host_signature(%{host: host, scheme: scheme, port: port}) do
- if port == URI.default_port(scheme) do
- host
- else
- "#{host}:#{port}"
- end
- end
-end
diff --git a/lib/pleroma/web/fed_sockets/socket_info.ex b/lib/pleroma/web/fed_sockets/socket_info.ex
deleted file mode 100644
index d6fdffe1a..000000000
--- a/lib/pleroma/web/fed_sockets/socket_info.ex
+++ /dev/null
@@ -1,52 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.FedSockets.SocketInfo do
- defstruct origin: nil,
- pid: nil,
- conn_pid: nil,
- state: :default,
- connected_until: nil
-
- alias Pleroma.Web.FedSockets.SocketInfo
- @default_connection_duration 15 * 60 * 1000
-
- def build(uri, conn_pid \\ nil) do
- uri
- |> build_origin()
- |> build_pids(conn_pid)
- |> touch()
- end
-
- def touch(%SocketInfo{} = socket_info),
- do: %{socket_info | connected_until: new_ttl()}
-
- def connect(%SocketInfo{} = socket_info),
- do: %{socket_info | state: :connected}
-
- def expired?(%{connected_until: connected_until}),
- do: connected_until < :erlang.monotonic_time(:millisecond)
-
- def origin(uri),
- do: build_origin(uri).origin
-
- defp build_pids(socket_info, conn_pid),
- do: struct(socket_info, pid: self(), conn_pid: conn_pid)
-
- defp build_origin(uri) when is_binary(uri),
- do: uri |> URI.parse() |> build_origin
-
- defp build_origin(%{host: host, port: nil, scheme: scheme}),
- do: build_origin(%{host: host, port: URI.default_port(scheme)})
-
- defp build_origin(%{host: host, port: port}),
- do: %SocketInfo{origin: "#{host}:#{port}"}
-
- defp new_ttl do
- connection_duration =
- Pleroma.Config.get([:fed_sockets, :connection_duration], @default_connection_duration)
-
- :erlang.monotonic_time(:millisecond) + connection_duration
- end
-end
diff --git a/lib/pleroma/web/fed_sockets/supervisor.ex b/lib/pleroma/web/fed_sockets/supervisor.ex
deleted file mode 100644
index a5f4bebfb..000000000
--- a/lib/pleroma/web/fed_sockets/supervisor.ex
+++ /dev/null
@@ -1,59 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.FedSockets.Supervisor do
- use Supervisor
- import Cachex.Spec
-
- def start_link(opts) do
- Supervisor.start_link(__MODULE__, opts, name: __MODULE__)
- end
-
- def init(args) do
- children = [
- build_cache(:fed_socket_fetches, args),
- build_cache(:fed_socket_rejections, args),
- {Registry, keys: :unique, name: FedSockets.Registry, meta: [rejected: %{}]}
- ]
-
- opts = [strategy: :one_for_all, name: Pleroma.Web.Streamer.Supervisor]
- Supervisor.init(children, opts)
- end
-
- defp build_cache(name, args) do
- opts = get_opts(name, args)
-
- %{
- id: String.to_atom("#{name}_cache"),
- start: {Cachex, :start_link, [name, opts]},
- type: :worker
- }
- end
-
- defp get_opts(cache_name, args)
- when cache_name in [:fed_socket_fetches, :fed_socket_rejections] do
- default = get_opts_or_config(args, cache_name, :default, 15_000)
- interval = get_opts_or_config(args, cache_name, :interval, 3_000)
- lazy = get_opts_or_config(args, cache_name, :lazy, false)
-
- [expiration: expiration(default: default, interval: interval, lazy: lazy)]
- end
-
- defp get_opts(name, args) do
- Keyword.get(args, name, [])
- end
-
- defp get_opts_or_config(args, name, key, default) do
- args
- |> Keyword.get(name, [])
- |> Keyword.get(key)
- |> case do
- nil ->
- Pleroma.Config.get([:fed_sockets, name, key], default)
-
- value ->
- value
- end
- end
-end
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index 784fdc975..7011b7eb1 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -208,7 +208,9 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
if bot, do: {:ok, "Service"}, else: {:ok, "Person"}
end)
|> Maps.put_if_present(:actor_type, params[:actor_type])
+ # Note: param name is indeed :locked (not an error)
|> Maps.put_if_present(:is_locked, params[:locked])
+ # Note: param name is indeed :discoverable (not an error)
|> Maps.put_if_present(:is_discoverable, params[:discoverable])
# What happens here:
@@ -292,7 +294,8 @@ def statuses(%{assigns: %{user: reading_user}} = conn, params) do
|> render("index.json",
activities: activities,
for: reading_user,
- as: :activity
+ as: :activity,
+ with_muted: Map.get(params, :with_muted, false)
)
else
error -> user_visibility_error(conn, error)
diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
index 4d9be5240..9e3a584f0 100644
--- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
@@ -109,7 +109,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
`ids` query param is required
"""
- def index(%{assigns: %{user: user}} = conn, %{ids: ids} = _params) do
+ def index(%{assigns: %{user: user}} = conn, %{ids: ids} = params) do
limit = 100
activities =
@@ -121,7 +121,8 @@ def index(%{assigns: %{user: user}} = conn, %{ids: ids} = _params) do
render(conn, "index.json",
activities: activities,
for: user,
- as: :activity
+ as: :activity,
+ with_muted: Map.get(params, :with_muted, false)
)
end
@@ -189,13 +190,14 @@ def create(%{assigns: %{user: _user}, body_params: %{media_ids: _} = params} = c
end
@doc "GET /api/v1/statuses/:id"
- def show(%{assigns: %{user: user}} = conn, %{id: id}) do
+ def show(%{assigns: %{user: user}} = conn, %{id: id} = params) do
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
true <- Visibility.visible_for_user?(activity, user) do
try_render(conn, "show.json",
activity: activity,
for: user,
- with_direct_conversation_id: true
+ with_direct_conversation_id: true,
+ with_muted: Map.get(params, :with_muted, false)
)
else
_ -> {:error, :not_found}
diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
index ac96520a3..852bd0695 100644
--- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
@@ -62,7 +62,8 @@ def home(%{assigns: %{user: user}} = conn, params) do
|> render("index.json",
activities: activities,
for: user,
- as: :activity
+ as: :activity,
+ with_muted: Map.get(params, :with_muted, false)
)
end
@@ -119,7 +120,8 @@ def public(%{assigns: %{user: user}} = conn, params) do
|> render("index.json",
activities: activities,
for: user,
- as: :activity
+ as: :activity,
+ with_muted: Map.get(params, :with_muted, false)
)
end
end
@@ -173,7 +175,8 @@ def hashtag(%{assigns: %{user: user}} = conn, params) do
|> render("index.json",
activities: activities,
for: user,
- as: :activity
+ as: :activity,
+ with_muted: Map.get(params, :with_muted, false)
)
end
end
@@ -202,7 +205,8 @@ def list(%{assigns: %{user: user}} = conn, %{list_id: id} = params) do
render(conn, "index.json",
activities: activities,
for: user,
- as: :activity
+ as: :activity,
+ with_muted: Map.get(params, :with_muted, false)
)
else
_e -> render_error(conn, :forbidden, "Error.")
diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex
index ea2d3aa9c..c5aca5506 100644
--- a/lib/pleroma/web/mastodon_api/views/instance_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex
@@ -23,7 +23,7 @@ def render("show.json", _) do
streaming_api: Pleroma.Web.Endpoint.websocket_url()
},
stats: Pleroma.Stats.get_stats(),
- thumbnail: Keyword.get(instance, :instance_thumbnail),
+ thumbnail: Pleroma.Web.base_url() <> Keyword.get(instance, :instance_thumbnail),
languages: ["en"],
registrations: Keyword.get(instance, :registrations_open),
approval_required: Keyword.get(instance, :account_approval_required),
@@ -34,7 +34,7 @@ def render("show.json", _) do
avatar_upload_limit: Keyword.get(instance, :avatar_upload_limit),
background_upload_limit: Keyword.get(instance, :background_upload_limit),
banner_upload_limit: Keyword.get(instance, :banner_upload_limit),
- background_image: Keyword.get(instance, :background_image),
+ background_image: Pleroma.Web.base_url() <> Keyword.get(instance, :background_image),
chat_limit: Keyword.get(instance, :chat_limit),
description_limit: Keyword.get(instance, :description_limit),
pleroma: %{
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 7cbbd3750..2301e21cf 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -19,6 +19,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
alias Pleroma.Web.MastodonAPI.PollView
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.MediaProxy
+ alias Pleroma.Web.PleromaAPI.EmojiReactionController
import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1, visible_for_user?: 2]
@@ -294,21 +295,16 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
end
emoji_reactions =
- with %{data: %{"reactions" => emoji_reactions}} <- object do
- Enum.map(emoji_reactions, fn
- [emoji, users] when is_list(users) ->
- build_emoji_map(emoji, users, opts[:for])
-
- {emoji, users} when is_list(users) ->
- build_emoji_map(emoji, users, opts[:for])
-
- _ ->
- nil
- end)
- |> Enum.reject(&is_nil/1)
- else
- _ -> []
- end
+ object.data
+ |> Map.get("reactions", [])
+ |> EmojiReactionController.filter_allowed_users(
+ opts[:for],
+ Map.get(opts, :with_muted, false)
+ )
+ |> Stream.map(fn {emoji, users} ->
+ build_emoji_map(emoji, users, opts[:for])
+ end)
+ |> Enum.to_list()
# Status muted state (would do 1 request per status unless user mutes are preloaded)
muted =
diff --git a/lib/pleroma/web/metadata/providers/restrict_indexing.ex b/lib/pleroma/web/metadata/providers/restrict_indexing.ex
index 900c2434d..a08a04b4a 100644
--- a/lib/pleroma/web/metadata/providers/restrict_indexing.ex
+++ b/lib/pleroma/web/metadata/providers/restrict_indexing.ex
@@ -6,7 +6,7 @@ defmodule Pleroma.Web.Metadata.Providers.RestrictIndexing do
@behaviour Pleroma.Web.Metadata.Providers.Provider
@moduledoc """
- Restricts indexing of remote users.
+ Restricts indexing of remote and/or non-discoverable users.
"""
@impl true
diff --git a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex
index 77564b342..bfc0a1f19 100644
--- a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex
@@ -140,8 +140,8 @@ def messages(%{assigns: %{user: user}} = conn, %{id: id} = params) do
def index(%{assigns: %{user: %{id: user_id} = user}} = conn, params) do
exclude_users =
- User.blocked_users_ap_ids(user) ++
- if params[:with_muted], do: [], else: User.muted_users_ap_ids(user)
+ User.cached_blocked_users_ap_ids(user) ++
+ if params[:with_muted], do: [], else: User.cached_muted_users_ap_ids(user)
chats =
user_id
diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex
index ae199a50f..dd9c746dc 100644
--- a/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do
alias Pleroma.Activity
alias Pleroma.Object
+ alias Pleroma.User
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.Plugs.OAuthScopesPlug
@@ -29,13 +30,42 @@ def index(%{assigns: %{user: user}} = conn, %{id: activity_id} = params) do
%Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
%Object{data: %{"reactions" => reactions}} when is_list(reactions) <-
Object.normalize(activity) do
- reactions = filter(reactions, params)
+ reactions =
+ reactions
+ |> filter(params)
+ |> filter_allowed_users(user, Map.get(params, :with_muted, false))
+
render(conn, "index.json", emoji_reactions: reactions, user: user)
else
_e -> json(conn, [])
end
end
+ def filter_allowed_users(reactions, user, with_muted) do
+ exclude_ap_ids =
+ if is_nil(user) do
+ []
+ else
+ User.cached_blocked_users_ap_ids(user) ++
+ if not with_muted, do: User.cached_muted_users_ap_ids(user), else: []
+ end
+
+ filter_emoji = fn emoji, users ->
+ case Enum.reject(users, &(&1 in exclude_ap_ids)) do
+ [] -> nil
+ users -> {emoji, users}
+ end
+ end
+
+ reactions
+ |> Stream.map(fn
+ [emoji, users] when is_list(users) -> filter_emoji.(emoji, users)
+ {emoji, users} when is_list(users) -> filter_emoji.(emoji, users)
+ _ -> nil
+ end)
+ |> Stream.reject(&is_nil/1)
+ end
+
defp filter(reactions, %{emoji: emoji}) when is_binary(emoji) do
Enum.filter(reactions, fn [e, _] -> e == emoji end)
end
diff --git a/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex b/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex
index e0f98b50a..110e8a041 100644
--- a/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex
+++ b/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex
@@ -11,7 +11,7 @@ def render("index.json", %{emoji_reactions: emoji_reactions} = opts) do
render_many(emoji_reactions, __MODULE__, "show.json", opts)
end
- def render("show.json", %{emoji_reaction: [emoji, user_ap_ids], user: user}) do
+ def render("show.json", %{emoji_reaction: {emoji, user_ap_ids}, user: user}) do
users = fetch_users(user_ap_ids)
%{
diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex
index da535aa68..82152dffa 100644
--- a/lib/pleroma/web/push/impl.ex
+++ b/lib/pleroma/web/push/impl.ex
@@ -16,7 +16,7 @@ defmodule Pleroma.Web.Push.Impl do
require Logger
import Ecto.Query
- @types ["Create", "Follow", "Announce", "Like", "Move"]
+ @types ["Create", "Follow", "Announce", "Like", "Move", "EmojiReact"]
@doc "Performs sending notifications for user subscriptions"
@spec perform(Notification.t()) :: list(any) | :error | {:error, :unknown_type}
@@ -149,6 +149,15 @@ def format_body(
"@#{actor.nickname} repeated: #{Utils.scrub_html_and_truncate(content, 80)}"
end
+ def format_body(
+ %{activity: %{data: %{"type" => "EmojiReact", "content" => content}}},
+ actor,
+ _object,
+ _mastodon_type
+ ) do
+ "@#{actor.nickname} reacted with #{content}"
+ end
+
def format_body(
%{activity: %{data: %{"type" => type}}} = notification,
actor,
@@ -179,6 +188,7 @@ def format_title(%{type: type}, mastodon_type) do
"reblog" -> "New Repeat"
"favourite" -> "New Favorite"
"pleroma:chat_mention" -> "New Chat Message"
+ "pleroma:emoji_reaction" -> "New Reaction"
type -> "New #{String.capitalize(type || "event")}"
end
end
diff --git a/lib/pleroma/web/push/subscription.ex b/lib/pleroma/web/push/subscription.ex
index 5b5aa0d59..749a573ba 100644
--- a/lib/pleroma/web/push/subscription.ex
+++ b/lib/pleroma/web/push/subscription.ex
@@ -25,7 +25,8 @@ defmodule Pleroma.Web.Push.Subscription do
timestamps()
end
- @supported_alert_types ~w[follow favourite mention reblog pleroma:chat_mention]a
+ # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
+ @supported_alert_types ~w[follow favourite mention reblog pleroma:chat_mention pleroma:emoji_reaction]a
defp alerts(%{data: %{alerts: alerts}}) do
alerts = Map.take(alerts, @supported_alert_types)
diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex
index d67b594b5..442bf9995 100644
--- a/lib/pleroma/web/rich_media/helpers.ex
+++ b/lib/pleroma/web/rich_media/helpers.ex
@@ -78,11 +78,6 @@ def fetch_data_for_activity(%Activity{data: %{"type" => "Create"}} = activity) d
def fetch_data_for_activity(_), do: %{}
- def perform(:fetch, %Activity{} = activity) do
- fetch_data_for_activity(activity)
- :ok
- end
-
def rich_media_get(url) do
headers = [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}]
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 0f0538182..75a885377 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -244,6 +244,9 @@ defmodule Pleroma.Web.Router do
get("/chats/:id/messages", ChatController, :messages)
delete("/chats/:id/messages/:message_id", ChatController, :delete_message)
+ get("/frontends", FrontendController, :index)
+ post("/frontends/install", FrontendController, :install)
+
post("/backups", AdminAPIController, :create_backup)
end
diff --git a/lib/pleroma/web/twitter_api/controllers/password_controller.ex b/lib/pleroma/web/twitter_api/controllers/password_controller.ex
index 800ab8954..b1a9d810e 100644
--- a/lib/pleroma/web/twitter_api/controllers/password_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/password_controller.ex
@@ -17,6 +17,7 @@ defmodule Pleroma.Web.TwitterAPI.PasswordController do
def reset(conn, %{"token" => token}) do
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
+ false <- PasswordResetToken.expired?(token),
%User{} = user <- User.get_cached_by_id(token.user_id) do
render(conn, "reset.html", %{
token: token,
diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex
index 55b5a13d9..0647c65ae 100644
--- a/lib/pleroma/workers/background_worker.ex
+++ b/lib/pleroma/workers/background_worker.ex
@@ -3,9 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.BackgroundWorker do
- alias Pleroma.Activity
alias Pleroma.User
- alias Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy
use Pleroma.Workers.WorkerHelper, queue: "background"
@@ -32,19 +30,6 @@ def perform(%Job{args: %{"op" => op, "user_id" => user_id, "identifiers" => iden
{:ok, User.Import.perform(String.to_atom(op), user, identifiers)}
end
- def perform(%Job{args: %{"op" => "media_proxy_preload", "message" => message}}) do
- MediaProxyWarmingPolicy.perform(:preload, message)
- end
-
- def perform(%Job{args: %{"op" => "media_proxy_prefetch", "url" => url}}) do
- MediaProxyWarmingPolicy.perform(:prefetch, url)
- end
-
- def perform(%Job{args: %{"op" => "fetch_data_for_activity", "activity_id" => activity_id}}) do
- activity = Activity.get_by_id(activity_id)
- Pleroma.Web.RichMedia.Helpers.perform(:fetch, activity)
- end
-
def perform(%Job{
args: %{"op" => "move_following", "origin_id" => origin_id, "target_id" => target_id}
}) do
diff --git a/mix.exs b/mix.exs
index 0691902a6..be7fe29d8 100644
--- a/mix.exs
+++ b/mix.exs
@@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do
def project do
[
app: :pleroma,
- version: version("2.1.50"),
+ version: version("2.2.50"),
elixir: "~> 1.9",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
@@ -133,17 +133,14 @@ defp deps do
{:calendar, "~> 1.0"},
{:cachex, "~> 3.2"},
{:poison, "~> 3.0", override: true},
- {:tesla,
- git: "https://github.com/teamon/tesla.git",
- ref: "9f7261ca49f9f901ceb73b60219ad6f8a9f6aa30",
- override: true},
+ {:tesla, "~> 1.4.0", override: true},
{:castore, "~> 0.1"},
{:cowlib, "~> 2.9", override: true},
{:gun,
github: "ninenines/gun", ref: "921c47146b2d9567eac7e9a4d2ccc60fffd4f327", override: true},
{:jason, "~> 1.2"},
{:mogrify, "~> 0.7.4"},
- {:ex_aws, "~> 2.1"},
+ {:ex_aws, "~> 2.1.6"},
{:ex_aws_s3, "~> 2.0"},
{:sweet_xml, "~> 0.6.6"},
{:earmark, "1.4.3"},
diff --git a/mix.lock b/mix.lock
index e5d9bc693..5989c675b 100644
--- a/mix.lock
+++ b/mix.lock
@@ -37,7 +37,7 @@
"esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm", "d73e341e3009d390aa36387dc8862860bf9f874c94d9fd92ade2926376f49981"},
"eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm", "b14f1dc204321429479c569cfbe8fb287541184ed040956c8862cb7a677b8406"},
"ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"},
- "ex_aws": {:hex, :ex_aws, "2.1.3", "26b6f036f0127548706aade4a509978fc7c26bd5334b004fba9bfe2687a525df", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "0bdbe2aed9f326922fc5a6a80417e32f0c895f4b3b2b0b9676ebf23dd16c5da4"},
+ "ex_aws": {:hex, :ex_aws, "2.1.6", "41ab8b4caa48035c96d07faa035d2d9de6df480e7e084c054e662ac888dcd4d4", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "a541bd042c1ee26412bb1e749ddf2a1c327e4fb7e382b1cd227e1b00eed3d469"},
"ex_aws_s3": {:hex, :ex_aws_s3, "2.0.2", "c0258bbdfea55de4f98f0b2f0ca61fe402cc696f573815134beb1866e778f47b", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "0569f5b211b1a3b12b705fe2a9d0e237eb1360b9d76298028df2346cad13097a"},
"ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"},
"ex_doc": {:hex, :ex_doc, "0.22.2", "03a2a58bdd2ba0d83d004507c4ee113b9c521956938298eba16e55cc4aba4a6c", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "cf60e1b3e2efe317095b6bb79651f83a2c1b3edcb4d319c421d7fcda8b3aff26"},
@@ -115,7 +115,7 @@
"swoosh": {:hex, :swoosh, "1.0.6", "6765e334c67dacabe721f0d701c7e5a6f06e4595c90df6f91e73ebd54d555833", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "7c50ef78e4acfd1cbd4907dc1fa87b5540675a6be9dc979d04890f49d7ec1830"},
"syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
"telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
- "tesla": {:git, "https://github.com/teamon/tesla.git", "9f7261ca49f9f901ceb73b60219ad6f8a9f6aa30", [ref: "9f7261ca49f9f901ceb73b60219ad6f8a9f6aa30"]},
+ "tesla": {:hex, :tesla, "1.4.0", "1081bef0124b8bdec1c3d330bbe91956648fb008cf0d3950a369cda466a31a87", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "bf1374a5569f5fca8e641363b63f7347d680d91388880979a33bc12a6eb3e0aa"},
"timex": {:hex, :timex, "3.6.2", "845cdeb6119e2fef10751c0b247b6c59d86d78554c83f78db612e3290f819bc2", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "26030b46199d02a590be61c2394b37ea25a3664c02fafbeca0b24c972025d47a"},
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
"tzdata": {:hex, :tzdata, "1.0.4", "a3baa4709ea8dba552dca165af6ae97c624a2d6ac14bd265165eaa8e8af94af6", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "b02637db3df1fd66dd2d3c4f194a81633d0e4b44308d36c1b2fdfd1e4e6f169b"},
diff --git a/priv/gettext/he/LC_MESSAGES/errors.po b/priv/gettext/he/LC_MESSAGES/errors.po
new file mode 100644
index 000000000..6d97b620f
--- /dev/null
+++ b/priv/gettext/he/LC_MESSAGES/errors.po
@@ -0,0 +1,596 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-11-10 13:39+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: he\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 2.5.1\n"
+
+## This file is a PO Template file.
+##
+## `msgid`s here are often extracted from source code.
+## Add new translations manually only if they're dynamic
+## translations that can't be statically extracted.
+##
+## Run `mix gettext.extract` to bring this file up to
+## date. Leave `msgstr`s empty as changing them here as no
+## effect: edit them in PO (`.po`) files instead.
+## From Ecto.Changeset.cast/4
+msgid "can't be blank"
+msgstr ""
+
+## From Ecto.Changeset.unique_constraint/3
+msgid "has already been taken"
+msgstr ""
+
+## From Ecto.Changeset.put_change/3
+msgid "is invalid"
+msgstr ""
+
+## From Ecto.Changeset.validate_format/3
+msgid "has invalid format"
+msgstr ""
+
+## From Ecto.Changeset.validate_subset/3
+msgid "has an invalid entry"
+msgstr ""
+
+## From Ecto.Changeset.validate_exclusion/3
+msgid "is reserved"
+msgstr ""
+
+## From Ecto.Changeset.validate_confirmation/3
+msgid "does not match confirmation"
+msgstr ""
+
+## From Ecto.Changeset.no_assoc_constraint/3
+msgid "is still associated with this entry"
+msgstr ""
+
+msgid "are still associated with this entry"
+msgstr ""
+
+## From Ecto.Changeset.validate_length/3
+msgid "should be %{count} character(s)"
+msgid_plural "should be %{count} character(s)"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "should have %{count} item(s)"
+msgid_plural "should have %{count} item(s)"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "should be at least %{count} character(s)"
+msgid_plural "should be at least %{count} character(s)"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "should have at least %{count} item(s)"
+msgid_plural "should have at least %{count} item(s)"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "should be at most %{count} character(s)"
+msgid_plural "should be at most %{count} character(s)"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "should have at most %{count} item(s)"
+msgid_plural "should have at most %{count} item(s)"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+## From Ecto.Changeset.validate_number/3
+msgid "must be less than %{number}"
+msgstr ""
+
+msgid "must be greater than %{number}"
+msgstr ""
+
+msgid "must be less than or equal to %{number}"
+msgstr ""
+
+msgid "must be greater than or equal to %{number}"
+msgstr ""
+
+msgid "must be equal to %{number}"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:505
+#, elixir-format
+msgid "Account not found"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:339
+#, elixir-format
+msgid "Already voted"
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:359
+#, elixir-format
+msgid "Bad request"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:426
+#, elixir-format
+msgid "Can't delete object"
+msgstr ""
+
+#: lib/pleroma/web/controller_helper.ex:105
+#: lib/pleroma/web/controller_helper.ex:111
+#, elixir-format
+msgid "Can't display this activity"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:285
+#, elixir-format
+msgid "Can't find user"
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:61
+#, elixir-format
+msgid "Can't get favorites"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:438
+#, elixir-format
+msgid "Can't like object"
+msgstr ""
+
+#: lib/pleroma/web/common_api/utils.ex:563
+#, elixir-format
+msgid "Cannot post an empty status without attachments"
+msgstr ""
+
+#: lib/pleroma/web/common_api/utils.ex:511
+#, elixir-format
+msgid "Comment must be up to %{max_size} characters"
+msgstr ""
+
+#: lib/pleroma/config/config_db.ex:191
+#, elixir-format
+msgid "Config with params %{params} not found"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:181
+#: lib/pleroma/web/common_api/common_api.ex:185
+#, elixir-format
+msgid "Could not delete"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:231
+#, elixir-format
+msgid "Could not favorite"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:453
+#, elixir-format
+msgid "Could not pin"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:278
+#, elixir-format
+msgid "Could not unfavorite"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:463
+#, elixir-format
+msgid "Could not unpin"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:216
+#, elixir-format
+msgid "Could not unrepeat"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:512
+#: lib/pleroma/web/common_api/common_api.ex:521
+#, elixir-format
+msgid "Could not update state"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:207
+#, elixir-format
+msgid "Error."
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:106
+#, elixir-format
+msgid "Invalid CAPTCHA"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:116
+#: lib/pleroma/web/oauth/oauth_controller.ex:568
+#, elixir-format
+msgid "Invalid credentials"
+msgstr ""
+
+#: lib/pleroma/plugs/ensure_authenticated_plug.ex:38
+#, elixir-format
+msgid "Invalid credentials."
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:355
+#, elixir-format
+msgid "Invalid indices"
+msgstr ""
+
+#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:29
+#, elixir-format
+msgid "Invalid parameters"
+msgstr ""
+
+#: lib/pleroma/web/common_api/utils.ex:414
+#, elixir-format
+msgid "Invalid password."
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:220
+#, elixir-format
+msgid "Invalid request"
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:109
+#, elixir-format
+msgid "Kocaptcha service unavailable"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:112
+#, elixir-format
+msgid "Missing parameters"
+msgstr ""
+
+#: lib/pleroma/web/common_api/utils.ex:547
+#, elixir-format
+msgid "No such conversation"
+msgstr ""
+
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:388
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:414 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:456
+#, elixir-format
+msgid "No such permission_group"
+msgstr ""
+
+#: lib/pleroma/plugs/uploaded_media.ex:84
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:486 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:11
+#: lib/pleroma/web/feed/user_controller.ex:71 lib/pleroma/web/ostatus/ostatus_controller.ex:143
+#, elixir-format
+msgid "Not found"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:331
+#, elixir-format
+msgid "Poll's author can't vote"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20
+#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49
+#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:50 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:306
+#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71
+#, elixir-format
+msgid "Record not found"
+msgstr ""
+
+#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:35
+#: lib/pleroma/web/feed/user_controller.ex:77 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:36
+#: lib/pleroma/web/ostatus/ostatus_controller.ex:149
+#, elixir-format
+msgid "Something went wrong"
+msgstr ""
+
+#: lib/pleroma/web/common_api/activity_draft.ex:107
+#, elixir-format
+msgid "The message visibility must be direct"
+msgstr ""
+
+#: lib/pleroma/web/common_api/utils.ex:573
+#, elixir-format
+msgid "The status is over the character limit"
+msgstr ""
+
+#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31
+#, elixir-format
+msgid "This resource requires authentication."
+msgstr ""
+
+#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206
+#, elixir-format
+msgid "Throttled"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:356
+#, elixir-format
+msgid "Too many choices"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:443
+#, elixir-format
+msgid "Unhandled activity type"
+msgstr ""
+
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:485
+#, elixir-format
+msgid "You can't revoke your own admin status."
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:221
+#: lib/pleroma/web/oauth/oauth_controller.ex:308
+#, elixir-format
+msgid "Your account is currently disabled"
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:183
+#: lib/pleroma/web/oauth/oauth_controller.ex:331
+#, elixir-format
+msgid "Your login is missing a confirmed e-mail address"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:390
+#, elixir-format
+msgid "can't read inbox of %{nickname} as %{as_nickname}"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:473
+#, elixir-format
+msgid "can't update outbox of %{nickname} as %{as_nickname}"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:471
+#, elixir-format
+msgid "conversation is already muted"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:314
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:492
+#, elixir-format
+msgid "error"
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:32
+#, elixir-format
+msgid "mascots can only be images"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:62
+#, elixir-format
+msgid "not found"
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:394
+#, elixir-format
+msgid "Bad OAuth request."
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:115
+#, elixir-format
+msgid "CAPTCHA already used"
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:112
+#, elixir-format
+msgid "CAPTCHA expired"
+msgstr ""
+
+#: lib/pleroma/plugs/uploaded_media.ex:57
+#, elixir-format
+msgid "Failed"
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:410
+#, elixir-format
+msgid "Failed to authenticate: %{message}."
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:441
+#, elixir-format
+msgid "Failed to set up user account."
+msgstr ""
+
+#: lib/pleroma/plugs/oauth_scopes_plug.ex:38
+#, elixir-format
+msgid "Insufficient permissions: %{permissions}."
+msgstr ""
+
+#: lib/pleroma/plugs/uploaded_media.ex:104
+#, elixir-format
+msgid "Internal Error"
+msgstr ""
+
+#: lib/pleroma/web/oauth/fallback_controller.ex:22
+#: lib/pleroma/web/oauth/fallback_controller.ex:29
+#, elixir-format
+msgid "Invalid Username/Password"
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:118
+#, elixir-format
+msgid "Invalid answer data"
+msgstr ""
+
+#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:33
+#, elixir-format
+msgid "Nodeinfo schema version not handled"
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:172
+#, elixir-format
+msgid "This action is outside the authorized scopes"
+msgstr ""
+
+#: lib/pleroma/web/oauth/fallback_controller.ex:14
+#, elixir-format
+msgid "Unknown error, please check the details and try again."
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:119
+#: lib/pleroma/web/oauth/oauth_controller.ex:158
+#, elixir-format
+msgid "Unlisted redirect_uri."
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:390
+#, elixir-format
+msgid "Unsupported OAuth provider: %{provider}."
+msgstr ""
+
+#: lib/pleroma/uploaders/uploader.ex:72
+#, elixir-format
+msgid "Uploader callback timeout"
+msgstr ""
+
+#: lib/pleroma/web/uploader_controller.ex:23
+#, elixir-format
+msgid "bad request"
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:103
+#, elixir-format
+msgid "CAPTCHA Error"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:290
+#, elixir-format
+msgid "Could not add reaction emoji"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:301
+#, elixir-format
+msgid "Could not remove reaction emoji"
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:129
+#, elixir-format
+msgid "Invalid CAPTCHA (Missing parameter: %{name})"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92
+#, elixir-format
+msgid "List not found"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:123
+#, elixir-format
+msgid "Missing parameter: %{name}"
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:210
+#: lib/pleroma/web/oauth/oauth_controller.ex:321
+#, elixir-format
+msgid "Password reset is required"
+msgstr ""
+
+#: lib/pleroma/tests/auth_test_controller.ex:9
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/config_controller.ex:6 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/invite_controller.ex:6 lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex:6 lib/pleroma/web/admin_api/controllers/relay_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/report_controller.ex:6 lib/pleroma/web/admin_api/controllers/status_controller.ex:6
+#: lib/pleroma/web/controller_helper.ex:6 lib/pleroma/web/embed_controller.ex:6
+#: lib/pleroma/web/fallback_redirect_controller.ex:6 lib/pleroma/web/feed/tag_controller.ex:6
+#: lib/pleroma/web/feed/user_controller.ex:6 lib/pleroma/web/mailer/subscription_controller.ex:2
+#: lib/pleroma/web/masto_fe_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/account_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/app_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/auth_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/filter_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/instance_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/list_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/marker_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex:14
+#: lib/pleroma/web/mastodon_api/controllers/media_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/notification_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/report_controller.ex:8
+#: lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/search_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:7
+#: lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:6
+#: lib/pleroma/web/media_proxy/media_proxy_controller.ex:6 lib/pleroma/web/mongooseim/mongoose_im_controller.ex:6
+#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:6 lib/pleroma/web/oauth/fallback_controller.ex:6
+#: lib/pleroma/web/oauth/mfa_controller.ex:10 lib/pleroma/web/oauth/oauth_controller.ex:6
+#: lib/pleroma/web/ostatus/ostatus_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/account_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/chat_controller.ex:5 lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:2 lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/notification_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex:7 lib/pleroma/web/static_fe/static_fe_controller.ex:6
+#: lib/pleroma/web/twitter_api/controllers/password_controller.ex:10 lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex:6
+#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:6 lib/pleroma/web/twitter_api/twitter_api_controller.ex:6
+#: lib/pleroma/web/uploader_controller.ex:6 lib/pleroma/web/web_finger/web_finger_controller.ex:6
+#, elixir-format
+msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
+msgstr ""
+
+#: lib/pleroma/plugs/ensure_authenticated_plug.ex:28
+#, elixir-format
+msgid "Two-factor authentication enabled, you must use a access token."
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:210
+#, elixir-format
+msgid "Unexpected error occurred while adding file to pack."
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:138
+#, elixir-format
+msgid "Unexpected error occurred while creating pack."
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:278
+#, elixir-format
+msgid "Unexpected error occurred while removing file from pack."
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:250
+#, elixir-format
+msgid "Unexpected error occurred while updating file in pack."
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:179
+#, elixir-format
+msgid "Unexpected error occurred while updating pack metadata."
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
+#, elixir-format
+msgid "Web push subscription is disabled on this Pleroma instance"
+msgstr ""
+
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:451
+#, elixir-format
+msgid "You can't revoke your own admin/moderator status."
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:126
+#, elixir-format
+msgid "authorization required for timeline view"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24
+#, elixir-format
+msgid "Access denied"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:282
+#, elixir-format
+msgid "This API requires an authenticated user"
+msgstr ""
+
+#: lib/pleroma/plugs/user_is_admin_plug.ex:21
+#, elixir-format
+msgid "User is not an admin."
+msgstr ""
diff --git a/priv/repo/migrations/20200915095704_remove_background_jobs.exs b/priv/repo/migrations/20200915095704_remove_background_jobs.exs
new file mode 100644
index 000000000..9785bfb8a
--- /dev/null
+++ b/priv/repo/migrations/20200915095704_remove_background_jobs.exs
@@ -0,0 +1,22 @@
+defmodule Pleroma.Repo.Migrations.RemoveBackgroundJobs do
+ use Ecto.Migration
+
+ import Ecto.Query, only: [from: 2]
+
+ def up do
+ from(j in "oban_jobs",
+ where:
+ j.queue == ^"background" and
+ fragment("?->>'op'", j.args) in ^[
+ "fetch_data_for_activity",
+ "media_proxy_prefetch",
+ "media_proxy_preload"
+ ] and
+ j.worker == ^"Pleroma.Workers.BackgroundWorker",
+ select: [:id]
+ )
+ |> Pleroma.Repo.delete_all()
+ end
+
+ def down, do: :ok
+end
diff --git a/priv/static/adminfe/chunk-03c5.f59788cf.css b/priv/static/adminfe/chunk-03c5.e6a0e2d0.css
similarity index 100%
rename from priv/static/adminfe/chunk-03c5.f59788cf.css
rename to priv/static/adminfe/chunk-03c5.e6a0e2d0.css
diff --git a/priv/static/adminfe/chunk-9d55.e2cb1409.css b/priv/static/adminfe/chunk-0492.15b0611f.css
similarity index 100%
rename from priv/static/adminfe/chunk-9d55.e2cb1409.css
rename to priv/static/adminfe/chunk-0492.15b0611f.css
diff --git a/priv/static/adminfe/chunk-342d.e342722b.css b/priv/static/adminfe/chunk-04b0.7e25cd78.css
similarity index 96%
rename from priv/static/adminfe/chunk-342d.e342722b.css
rename to priv/static/adminfe/chunk-04b0.7e25cd78.css
index b0fd8dcb3..8dfdc0dcf 100644
Binary files a/priv/static/adminfe/chunk-342d.e342722b.css and b/priv/static/adminfe/chunk-04b0.7e25cd78.css differ
diff --git a/priv/static/adminfe/chunk-546f.692d1ab2.css b/priv/static/adminfe/chunk-0537.cd83e5d6.css
similarity index 100%
rename from priv/static/adminfe/chunk-546f.692d1ab2.css
rename to priv/static/adminfe/chunk-0537.cd83e5d6.css
diff --git a/priv/static/adminfe/chunk-170f.fea927c5.css b/priv/static/adminfe/chunk-170f.fea927c5.css
new file mode 100644
index 000000000..a4ab52d51
Binary files /dev/null and b/priv/static/adminfe/chunk-170f.fea927c5.css differ
diff --git a/priv/static/adminfe/chunk-176e.a3c8376d.css b/priv/static/adminfe/chunk-176e.d9a630b2.css
similarity index 100%
rename from priv/static/adminfe/chunk-176e.a3c8376d.css
rename to priv/static/adminfe/chunk-176e.d9a630b2.css
diff --git a/priv/static/adminfe/chunk-521f.b745ee5d.css b/priv/static/adminfe/chunk-1944.731ba892.css
similarity index 62%
rename from priv/static/adminfe/chunk-521f.b745ee5d.css
rename to priv/static/adminfe/chunk-1944.731ba892.css
index 7e8ffb651..6392d8e75 100644
Binary files a/priv/static/adminfe/chunk-521f.b745ee5d.css and b/priv/static/adminfe/chunk-1944.731ba892.css differ
diff --git a/priv/static/adminfe/chunk-654d.94689c39.css b/priv/static/adminfe/chunk-654d.94689c39.css
deleted file mode 100644
index 483d88545..000000000
Binary files a/priv/static/adminfe/chunk-654d.94689c39.css and /dev/null differ
diff --git a/priv/static/adminfe/chunk-68ea9.dac85813.css b/priv/static/adminfe/chunk-68ea9.8331e95e.css
similarity index 100%
rename from priv/static/adminfe/chunk-68ea9.dac85813.css
rename to priv/static/adminfe/chunk-68ea9.8331e95e.css
diff --git a/priv/static/adminfe/chunk-6e81.1c0f2da2.css b/priv/static/adminfe/chunk-6e81.559b76f9.css
similarity index 100%
rename from priv/static/adminfe/chunk-6e81.1c0f2da2.css
rename to priv/static/adminfe/chunk-6e81.559b76f9.css
diff --git a/priv/static/adminfe/chunk-7968.283bc086.css b/priv/static/adminfe/chunk-7968.283bc086.css
new file mode 100644
index 000000000..5d9863d3a
Binary files /dev/null and b/priv/static/adminfe/chunk-7968.283bc086.css differ
diff --git a/priv/static/adminfe/chunk-7c6b.365cbeda.css b/priv/static/adminfe/chunk-7c6b.b633878a.css
similarity index 100%
rename from priv/static/adminfe/chunk-7c6b.365cbeda.css
rename to priv/static/adminfe/chunk-7c6b.b633878a.css
diff --git a/priv/static/adminfe/chunk-850d.cc4f0ac6.css b/priv/static/adminfe/chunk-850d.cc4f0ac6.css
deleted file mode 100644
index 1cb2ead63..000000000
Binary files a/priv/static/adminfe/chunk-850d.cc4f0ac6.css and /dev/null differ
diff --git a/priv/static/adminfe/chunk-8fbb.dd321643.css b/priv/static/adminfe/chunk-8fbb.dd321643.css
new file mode 100644
index 000000000..f50d974bd
Binary files /dev/null and b/priv/static/adminfe/chunk-8fbb.dd321643.css differ
diff --git a/priv/static/adminfe/chunk-3365.201aa8e6.css b/priv/static/adminfe/chunk-ad1e.1a3c5339.css
similarity index 100%
rename from priv/static/adminfe/chunk-3365.201aa8e6.css
rename to priv/static/adminfe/chunk-ad1e.1a3c5339.css
diff --git a/priv/static/adminfe/chunk-commons.c0eb3eb7.css b/priv/static/adminfe/chunk-commons.f7c3d390.css
similarity index 100%
rename from priv/static/adminfe/chunk-commons.c0eb3eb7.css
rename to priv/static/adminfe/chunk-commons.f7c3d390.css
diff --git a/priv/static/adminfe/chunk-d34d.b0dd6fb4.css b/priv/static/adminfe/chunk-e660.9e75af5b.css
similarity index 100%
rename from priv/static/adminfe/chunk-d34d.b0dd6fb4.css
rename to priv/static/adminfe/chunk-e660.9e75af5b.css
diff --git a/priv/static/adminfe/chunk-f364.6b5f3f0d.css b/priv/static/adminfe/chunk-f364.6b5f3f0d.css
new file mode 100644
index 000000000..ec665da84
Binary files /dev/null and b/priv/static/adminfe/chunk-f364.6b5f3f0d.css differ
diff --git a/priv/static/adminfe/chunk-f625.25a6a4ae.css b/priv/static/adminfe/chunk-f625.bcd0ea3b.css
similarity index 100%
rename from priv/static/adminfe/chunk-f625.25a6a4ae.css
rename to priv/static/adminfe/chunk-f625.bcd0ea3b.css
diff --git a/priv/static/adminfe/index.html b/priv/static/adminfe/index.html
index d6b9b22b8..e6af40e97 100644
--- a/priv/static/adminfe/index.html
+++ b/priv/static/adminfe/index.html
@@ -1 +1 @@
-Admin FE
\ No newline at end of file
+Admin FE
\ No newline at end of file
diff --git a/priv/static/adminfe/static/js/app.69891fda.js b/priv/static/adminfe/static/js/app.69891fda.js
deleted file mode 100644
index 3d04d9273..000000000
Binary files a/priv/static/adminfe/static/js/app.69891fda.js and /dev/null differ
diff --git a/priv/static/adminfe/static/js/app.69891fda.js.map b/priv/static/adminfe/static/js/app.69891fda.js.map
deleted file mode 100644
index 0131793e9..000000000
Binary files a/priv/static/adminfe/static/js/app.69891fda.js.map and /dev/null differ
diff --git a/priv/static/adminfe/static/js/app.c67f9a2f.js b/priv/static/adminfe/static/js/app.c67f9a2f.js
new file mode 100644
index 000000000..65f9d4a29
Binary files /dev/null and b/priv/static/adminfe/static/js/app.c67f9a2f.js differ
diff --git a/priv/static/adminfe/static/js/app.c67f9a2f.js.map b/priv/static/adminfe/static/js/app.c67f9a2f.js.map
new file mode 100644
index 000000000..41b4375aa
Binary files /dev/null and b/priv/static/adminfe/static/js/app.c67f9a2f.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-03c5.1c694c49.js b/priv/static/adminfe/static/js/chunk-03c5.6de0c4c7.js
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-03c5.1c694c49.js
rename to priv/static/adminfe/static/js/chunk-03c5.6de0c4c7.js
index b4601abae..a89c65572 100644
Binary files a/priv/static/adminfe/static/js/chunk-03c5.1c694c49.js and b/priv/static/adminfe/static/js/chunk-03c5.6de0c4c7.js differ
diff --git a/priv/static/adminfe/static/js/chunk-03c5.1c694c49.js.map b/priv/static/adminfe/static/js/chunk-03c5.6de0c4c7.js.map
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-03c5.1c694c49.js.map
rename to priv/static/adminfe/static/js/chunk-03c5.6de0c4c7.js.map
index 193c65bb1..963ff6dee 100644
Binary files a/priv/static/adminfe/static/js/chunk-03c5.1c694c49.js.map and b/priv/static/adminfe/static/js/chunk-03c5.6de0c4c7.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-0492.47abe1dc.js b/priv/static/adminfe/static/js/chunk-0492.47abe1dc.js
new file mode 100644
index 000000000..243ecde70
Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-0492.47abe1dc.js differ
diff --git a/priv/static/adminfe/static/js/chunk-0492.47abe1dc.js.map b/priv/static/adminfe/static/js/chunk-0492.47abe1dc.js.map
new file mode 100644
index 000000000..f5e0d9ebc
Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-0492.47abe1dc.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-04b0.90c6d24c.js b/priv/static/adminfe/static/js/chunk-04b0.90c6d24c.js
new file mode 100644
index 000000000..9d0352814
Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-04b0.90c6d24c.js differ
diff --git a/priv/static/adminfe/static/js/chunk-04b0.90c6d24c.js.map b/priv/static/adminfe/static/js/chunk-04b0.90c6d24c.js.map
new file mode 100644
index 000000000..a9bee3721
Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-04b0.90c6d24c.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-546f.81668ba7.js b/priv/static/adminfe/static/js/chunk-0537.74db16b0.js
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-546f.81668ba7.js
rename to priv/static/adminfe/static/js/chunk-0537.74db16b0.js
index 252991021..35231e562 100644
Binary files a/priv/static/adminfe/static/js/chunk-546f.81668ba7.js and b/priv/static/adminfe/static/js/chunk-0537.74db16b0.js differ
diff --git a/priv/static/adminfe/static/js/chunk-546f.81668ba7.js.map b/priv/static/adminfe/static/js/chunk-0537.74db16b0.js.map
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-546f.81668ba7.js.map
rename to priv/static/adminfe/static/js/chunk-0537.74db16b0.js.map
index 6a9d2a8dc..fa87bd76d 100644
Binary files a/priv/static/adminfe/static/js/chunk-546f.81668ba7.js.map and b/priv/static/adminfe/static/js/chunk-0537.74db16b0.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-170f.e1d6aac3.js b/priv/static/adminfe/static/js/chunk-170f.e1d6aac3.js
new file mode 100644
index 000000000..d40dc29bd
Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-170f.e1d6aac3.js differ
diff --git a/priv/static/adminfe/static/js/chunk-170f.e1d6aac3.js.map b/priv/static/adminfe/static/js/chunk-170f.e1d6aac3.js.map
new file mode 100644
index 000000000..91d3bc70d
Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-170f.e1d6aac3.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-176e.be050aba.js b/priv/static/adminfe/static/js/chunk-176e.f64cb745.js
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-176e.be050aba.js
rename to priv/static/adminfe/static/js/chunk-176e.f64cb745.js
index cab83489b..dd60693da 100644
Binary files a/priv/static/adminfe/static/js/chunk-176e.be050aba.js and b/priv/static/adminfe/static/js/chunk-176e.f64cb745.js differ
diff --git a/priv/static/adminfe/static/js/chunk-176e.be050aba.js.map b/priv/static/adminfe/static/js/chunk-176e.f64cb745.js.map
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-176e.be050aba.js.map
rename to priv/static/adminfe/static/js/chunk-176e.f64cb745.js.map
index bff959cd6..9ee6aa6e4 100644
Binary files a/priv/static/adminfe/static/js/chunk-176e.be050aba.js.map and b/priv/static/adminfe/static/js/chunk-176e.f64cb745.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-1944.7bed0c4b.js b/priv/static/adminfe/static/js/chunk-1944.7bed0c4b.js
new file mode 100644
index 000000000..87590c6ce
Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-1944.7bed0c4b.js differ
diff --git a/priv/static/adminfe/static/js/chunk-1944.7bed0c4b.js.map b/priv/static/adminfe/static/js/chunk-1944.7bed0c4b.js.map
new file mode 100644
index 000000000..23229293e
Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-1944.7bed0c4b.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-342d.479e01dd.js b/priv/static/adminfe/static/js/chunk-342d.479e01dd.js
deleted file mode 100644
index 5ee311c4a..000000000
Binary files a/priv/static/adminfe/static/js/chunk-342d.479e01dd.js and /dev/null differ
diff --git a/priv/static/adminfe/static/js/chunk-342d.479e01dd.js.map b/priv/static/adminfe/static/js/chunk-342d.479e01dd.js.map
deleted file mode 100644
index b73bbb0aa..000000000
Binary files a/priv/static/adminfe/static/js/chunk-342d.479e01dd.js.map and /dev/null differ
diff --git a/priv/static/adminfe/static/js/chunk-521f.748b331d.js b/priv/static/adminfe/static/js/chunk-521f.748b331d.js
deleted file mode 100644
index 570dab224..000000000
Binary files a/priv/static/adminfe/static/js/chunk-521f.748b331d.js and /dev/null differ
diff --git a/priv/static/adminfe/static/js/chunk-521f.748b331d.js.map b/priv/static/adminfe/static/js/chunk-521f.748b331d.js.map
deleted file mode 100644
index 3380bbbd5..000000000
Binary files a/priv/static/adminfe/static/js/chunk-521f.748b331d.js.map and /dev/null differ
diff --git a/priv/static/adminfe/static/js/chunk-654d.653b067f.js b/priv/static/adminfe/static/js/chunk-654d.653b067f.js
deleted file mode 100644
index 209873ec1..000000000
Binary files a/priv/static/adminfe/static/js/chunk-654d.653b067f.js and /dev/null differ
diff --git a/priv/static/adminfe/static/js/chunk-654d.653b067f.js.map b/priv/static/adminfe/static/js/chunk-654d.653b067f.js.map
deleted file mode 100644
index 72aca0e98..000000000
Binary files a/priv/static/adminfe/static/js/chunk-654d.653b067f.js.map and /dev/null differ
diff --git a/priv/static/adminfe/static/js/chunk-68ea9.9821cd6a.js b/priv/static/adminfe/static/js/chunk-68ea9.2b2877d5.js
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-68ea9.9821cd6a.js
rename to priv/static/adminfe/static/js/chunk-68ea9.2b2877d5.js
index 02091ed84..60056454d 100644
Binary files a/priv/static/adminfe/static/js/chunk-68ea9.9821cd6a.js and b/priv/static/adminfe/static/js/chunk-68ea9.2b2877d5.js differ
diff --git a/priv/static/adminfe/static/js/chunk-68ea9.9821cd6a.js.map b/priv/static/adminfe/static/js/chunk-68ea9.2b2877d5.js.map
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-68ea9.9821cd6a.js.map
rename to priv/static/adminfe/static/js/chunk-68ea9.2b2877d5.js.map
index 019cede66..9e26519c3 100644
Binary files a/priv/static/adminfe/static/js/chunk-68ea9.9821cd6a.js.map and b/priv/static/adminfe/static/js/chunk-68ea9.2b2877d5.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-6e81.ebe9039f.js b/priv/static/adminfe/static/js/chunk-6e81.afade883.js
similarity index 97%
rename from priv/static/adminfe/static/js/chunk-6e81.ebe9039f.js
rename to priv/static/adminfe/static/js/chunk-6e81.afade883.js
index cd79db1d3..3b5dd6c5c 100644
Binary files a/priv/static/adminfe/static/js/chunk-6e81.ebe9039f.js and b/priv/static/adminfe/static/js/chunk-6e81.afade883.js differ
diff --git a/priv/static/adminfe/static/js/chunk-6e81.ebe9039f.js.map b/priv/static/adminfe/static/js/chunk-6e81.afade883.js.map
similarity index 98%
rename from priv/static/adminfe/static/js/chunk-6e81.ebe9039f.js.map
rename to priv/static/adminfe/static/js/chunk-6e81.afade883.js.map
index 10b437760..a0f7fca19 100644
Binary files a/priv/static/adminfe/static/js/chunk-6e81.ebe9039f.js.map and b/priv/static/adminfe/static/js/chunk-6e81.afade883.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-7968.f51e3292.js b/priv/static/adminfe/static/js/chunk-7968.f51e3292.js
new file mode 100644
index 000000000..dc981706f
Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-7968.f51e3292.js differ
diff --git a/priv/static/adminfe/static/js/chunk-7968.f51e3292.js.map b/priv/static/adminfe/static/js/chunk-7968.f51e3292.js.map
new file mode 100644
index 000000000..c2f0726b7
Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-7968.f51e3292.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-7c6b.56a14571.js b/priv/static/adminfe/static/js/chunk-7c6b.34152862.js
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-7c6b.56a14571.js
rename to priv/static/adminfe/static/js/chunk-7c6b.34152862.js
index df9b7d6ac..27d57d3ff 100644
Binary files a/priv/static/adminfe/static/js/chunk-7c6b.56a14571.js and b/priv/static/adminfe/static/js/chunk-7c6b.34152862.js differ
diff --git a/priv/static/adminfe/static/js/chunk-7c6b.56a14571.js.map b/priv/static/adminfe/static/js/chunk-7c6b.34152862.js.map
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-7c6b.56a14571.js.map
rename to priv/static/adminfe/static/js/chunk-7c6b.34152862.js.map
index 6584ba082..78026f5f4 100644
Binary files a/priv/static/adminfe/static/js/chunk-7c6b.56a14571.js.map and b/priv/static/adminfe/static/js/chunk-7c6b.34152862.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-850d.3e6102c2.js b/priv/static/adminfe/static/js/chunk-850d.3e6102c2.js
deleted file mode 100644
index a2c2df2e7..000000000
Binary files a/priv/static/adminfe/static/js/chunk-850d.3e6102c2.js and /dev/null differ
diff --git a/priv/static/adminfe/static/js/chunk-850d.3e6102c2.js.map b/priv/static/adminfe/static/js/chunk-850d.3e6102c2.js.map
deleted file mode 100644
index 7f7718547..000000000
Binary files a/priv/static/adminfe/static/js/chunk-850d.3e6102c2.js.map and /dev/null differ
diff --git a/priv/static/adminfe/static/js/chunk-8fbb.c847ce9d.js b/priv/static/adminfe/static/js/chunk-8fbb.c847ce9d.js
new file mode 100644
index 000000000..74ffe9194
Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-8fbb.c847ce9d.js differ
diff --git a/priv/static/adminfe/static/js/chunk-8fbb.c847ce9d.js.map b/priv/static/adminfe/static/js/chunk-8fbb.c847ce9d.js.map
new file mode 100644
index 000000000..b3c3b5fe8
Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-8fbb.c847ce9d.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-9d55.7af22f45.js b/priv/static/adminfe/static/js/chunk-9d55.7af22f45.js
deleted file mode 100644
index 89d35af79..000000000
Binary files a/priv/static/adminfe/static/js/chunk-9d55.7af22f45.js and /dev/null differ
diff --git a/priv/static/adminfe/static/js/chunk-9d55.7af22f45.js.map b/priv/static/adminfe/static/js/chunk-9d55.7af22f45.js.map
deleted file mode 100644
index fa8694b8e..000000000
Binary files a/priv/static/adminfe/static/js/chunk-9d55.7af22f45.js.map and /dev/null differ
diff --git a/priv/static/adminfe/static/js/chunk-3365.b73c30a8.js b/priv/static/adminfe/static/js/chunk-ad1e.eba9db26.js
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-3365.b73c30a8.js
rename to priv/static/adminfe/static/js/chunk-ad1e.eba9db26.js
index 421bf2a99..82ddd4df2 100644
Binary files a/priv/static/adminfe/static/js/chunk-3365.b73c30a8.js and b/priv/static/adminfe/static/js/chunk-ad1e.eba9db26.js differ
diff --git a/priv/static/adminfe/static/js/chunk-3365.b73c30a8.js.map b/priv/static/adminfe/static/js/chunk-ad1e.eba9db26.js.map
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-3365.b73c30a8.js.map
rename to priv/static/adminfe/static/js/chunk-ad1e.eba9db26.js.map
index d2ad4d9aa..d74c2498f 100644
Binary files a/priv/static/adminfe/static/js/chunk-3365.b73c30a8.js.map and b/priv/static/adminfe/static/js/chunk-ad1e.eba9db26.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-commons.a6002038.js b/priv/static/adminfe/static/js/chunk-commons.4ae74caa.js
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-commons.a6002038.js
rename to priv/static/adminfe/static/js/chunk-commons.4ae74caa.js
index 2b16da9c7..1ee2ea9e4 100644
Binary files a/priv/static/adminfe/static/js/chunk-commons.a6002038.js and b/priv/static/adminfe/static/js/chunk-commons.4ae74caa.js differ
diff --git a/priv/static/adminfe/static/js/chunk-commons.a6002038.js.map b/priv/static/adminfe/static/js/chunk-commons.4ae74caa.js.map
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-commons.a6002038.js.map
rename to priv/static/adminfe/static/js/chunk-commons.4ae74caa.js.map
index 3c7d78861..41a884d15 100644
Binary files a/priv/static/adminfe/static/js/chunk-commons.a6002038.js.map and b/priv/static/adminfe/static/js/chunk-commons.4ae74caa.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-d34d.0f06fe76.js b/priv/static/adminfe/static/js/chunk-e660.feca27c4.js
similarity index 97%
rename from priv/static/adminfe/static/js/chunk-d34d.0f06fe76.js
rename to priv/static/adminfe/static/js/chunk-e660.feca27c4.js
index edd221e6e..5659d263e 100644
Binary files a/priv/static/adminfe/static/js/chunk-d34d.0f06fe76.js and b/priv/static/adminfe/static/js/chunk-e660.feca27c4.js differ
diff --git a/priv/static/adminfe/static/js/chunk-d34d.0f06fe76.js.map b/priv/static/adminfe/static/js/chunk-e660.feca27c4.js.map
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-d34d.0f06fe76.js.map
rename to priv/static/adminfe/static/js/chunk-e660.feca27c4.js.map
index 6bcd4ed8b..cfc2e08af 100644
Binary files a/priv/static/adminfe/static/js/chunk-d34d.0f06fe76.js.map and b/priv/static/adminfe/static/js/chunk-e660.feca27c4.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-f364.1122502b.js b/priv/static/adminfe/static/js/chunk-f364.1122502b.js
new file mode 100644
index 000000000..facad2ed5
Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-f364.1122502b.js differ
diff --git a/priv/static/adminfe/static/js/chunk-f364.1122502b.js.map b/priv/static/adminfe/static/js/chunk-f364.1122502b.js.map
new file mode 100644
index 000000000..f89dabe30
Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-f364.1122502b.js.map differ
diff --git a/priv/static/adminfe/static/js/chunk-f625.29237434.js b/priv/static/adminfe/static/js/chunk-f625.904137fd.js
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-f625.29237434.js
rename to priv/static/adminfe/static/js/chunk-f625.904137fd.js
index 522755a98..053590b28 100644
Binary files a/priv/static/adminfe/static/js/chunk-f625.29237434.js and b/priv/static/adminfe/static/js/chunk-f625.904137fd.js differ
diff --git a/priv/static/adminfe/static/js/chunk-f625.29237434.js.map b/priv/static/adminfe/static/js/chunk-f625.904137fd.js.map
similarity index 99%
rename from priv/static/adminfe/static/js/chunk-f625.29237434.js.map
rename to priv/static/adminfe/static/js/chunk-f625.904137fd.js.map
index 4f8774c3a..59c1c274e 100644
Binary files a/priv/static/adminfe/static/js/chunk-f625.29237434.js.map and b/priv/static/adminfe/static/js/chunk-f625.904137fd.js.map differ
diff --git a/priv/static/adminfe/static/js/runtime.8f631d12.js b/priv/static/adminfe/static/js/runtime.8f631d12.js
deleted file mode 100644
index 6fa7d9ee1..000000000
Binary files a/priv/static/adminfe/static/js/runtime.8f631d12.js and /dev/null differ
diff --git a/priv/static/adminfe/static/js/runtime.8f631d12.js.map b/priv/static/adminfe/static/js/runtime.8f631d12.js.map
deleted file mode 100644
index d5c20400e..000000000
Binary files a/priv/static/adminfe/static/js/runtime.8f631d12.js.map and /dev/null differ
diff --git a/priv/static/adminfe/static/js/runtime.ba96836e.js b/priv/static/adminfe/static/js/runtime.ba96836e.js
new file mode 100644
index 000000000..245c7fe20
Binary files /dev/null and b/priv/static/adminfe/static/js/runtime.ba96836e.js differ
diff --git a/priv/static/adminfe/static/js/runtime.ba96836e.js.map b/priv/static/adminfe/static/js/runtime.ba96836e.js.map
new file mode 100644
index 000000000..f3c5a82af
Binary files /dev/null and b/priv/static/adminfe/static/js/runtime.ba96836e.js.map differ
diff --git a/priv/static/index.html b/priv/static/index.html
index e848c5f8c..9b774959a 100644
--- a/priv/static/index.html
+++ b/priv/static/index.html
@@ -1 +1 @@
-To use Pleroma, please enable JavaScript.
+Pleroma To use Pleroma, please enable JavaScript.
\ No newline at end of file
diff --git a/priv/static/static/config.json b/priv/static/static/config.json
index 0030f78f1..f59e645ac 100644
--- a/priv/static/static/config.json
+++ b/priv/static/static/config.json
@@ -10,9 +10,10 @@
"hideSitename": false,
"hideUserStats": false,
"loginMethod": "password",
- "logo": "/static/logo.png",
+ "logo": "/static/logo.svg",
"logoMargin": ".1em",
"logoMask": true,
+ "logoLeft": false,
"minimalScopesMode": false,
"nsfwCensorImage": "",
"postContentType": "text/plain",
diff --git a/priv/static/static/css/app.77b1644622e3bae24b6b.css.map b/priv/static/static/css/app.77b1644622e3bae24b6b.css.map
deleted file mode 100644
index 4b042ef35..000000000
--- a/priv/static/static/css/app.77b1644622e3bae24b6b.css.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"sources":["webpack:///./src/components/tab_switcher/tab_switcher.scss","webpack:///./src/hocs/with_load_more/with_load_more.scss"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,C;ACtOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,C","file":"static/css/app.77b1644622e3bae24b6b.css","sourcesContent":[".tab-switcher {\n display: -ms-flexbox;\n display: flex;\n}\n.tab-switcher .tab-icon {\n font-size: 2em;\n display: block;\n}\n.tab-switcher.top-tabs {\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher.top-tabs > .tabs {\n width: 100%;\n overflow-y: hidden;\n overflow-x: auto;\n padding-top: 5px;\n -ms-flex-direction: row;\n flex-direction: row;\n}\n.tab-switcher.top-tabs > .tabs::after, .tab-switcher.top-tabs > .tabs::before {\n content: \"\";\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n border-bottom: 1px solid;\n border-bottom-color: #222;\n border-bottom-color: var(--border, #222);\n}\n.tab-switcher.top-tabs > .tabs .tab-wrapper {\n height: 28px;\n}\n.tab-switcher.top-tabs > .tabs .tab-wrapper:not(.active)::after {\n left: 0;\n right: 0;\n bottom: 0;\n border-bottom: 1px solid;\n border-bottom-color: #222;\n border-bottom-color: var(--border, #222);\n}\n.tab-switcher.top-tabs > .tabs .tab {\n width: 100%;\n min-width: 1px;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n padding-bottom: 99px;\n margin-bottom: -93px;\n}\n.tab-switcher.top-tabs .contents.scrollable-tabs {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n}\n.tab-switcher.side-tabs {\n -ms-flex-direction: row;\n flex-direction: row;\n}\n@media all and (max-width: 800px) {\n .tab-switcher.side-tabs {\n overflow-x: auto;\n }\n}\n.tab-switcher.side-tabs > .contents {\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n}\n.tab-switcher.side-tabs > .tabs {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n overflow-y: auto;\n overflow-x: hidden;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher.side-tabs > .tabs::after, .tab-switcher.side-tabs > .tabs::before {\n -ms-flex-negative: 0;\n flex-shrink: 0;\n -ms-flex-preferred-size: 0.5em;\n flex-basis: 0.5em;\n content: \"\";\n border-right: 1px solid;\n border-right-color: #222;\n border-right-color: var(--border, #222);\n}\n.tab-switcher.side-tabs > .tabs::after {\n -ms-flex-positive: 1;\n flex-grow: 1;\n}\n.tab-switcher.side-tabs > .tabs::before {\n -ms-flex-positive: 0;\n flex-grow: 0;\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper {\n min-width: 10em;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n@media all and (max-width: 800px) {\n .tab-switcher.side-tabs > .tabs .tab-wrapper {\n min-width: 1em;\n }\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper:not(.active)::after {\n top: 0;\n right: 0;\n bottom: 0;\n border-right: 1px solid;\n border-right-color: #222;\n border-right-color: var(--border, #222);\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper::before {\n -ms-flex: 0 0 6px;\n flex: 0 0 6px;\n content: \"\";\n border-right: 1px solid;\n border-right-color: #222;\n border-right-color: var(--border, #222);\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper:last-child .tab {\n margin-bottom: 0;\n}\n.tab-switcher.side-tabs > .tabs .tab {\n -ms-flex: 1;\n flex: 1;\n box-sizing: content-box;\n min-width: 10em;\n min-width: 1px;\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n padding-left: 1em;\n padding-right: calc(1em + 200px);\n margin-right: -200px;\n margin-left: 1em;\n}\n@media all and (max-width: 800px) {\n .tab-switcher.side-tabs > .tabs .tab {\n padding-left: 0.25em;\n padding-right: calc(.25em + 200px);\n margin-right: calc(.25em - 200px);\n margin-left: 0.25em;\n }\n .tab-switcher.side-tabs > .tabs .tab .text {\n display: none;\n }\n}\n.tab-switcher .contents {\n -ms-flex: 1 0 auto;\n flex: 1 0 auto;\n min-height: 0px;\n}\n.tab-switcher .contents .hidden {\n display: none;\n}\n.tab-switcher .contents .full-height:not(.hidden) {\n height: 100%;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher .contents .full-height:not(.hidden) > *:not(.mobile-label) {\n -ms-flex: 1;\n flex: 1;\n}\n.tab-switcher .contents.scrollable-tabs {\n overflow-y: auto;\n}\n.tab-switcher .tab {\n position: relative;\n white-space: nowrap;\n padding: 6px 1em;\n background-color: #182230;\n background-color: var(--tab, #182230);\n}\n.tab-switcher .tab, .tab-switcher .tab:active .tab-icon {\n color: #b9b9ba;\n color: var(--tabText, #b9b9ba);\n}\n.tab-switcher .tab:not(.active) {\n z-index: 4;\n}\n.tab-switcher .tab:not(.active):hover {\n z-index: 6;\n}\n.tab-switcher .tab.active {\n background: transparent;\n z-index: 5;\n color: #b9b9ba;\n color: var(--tabActiveText, #b9b9ba);\n}\n.tab-switcher .tab img {\n max-height: 26px;\n vertical-align: top;\n margin-top: -5px;\n}\n.tab-switcher .tabs {\n display: -ms-flexbox;\n display: flex;\n position: relative;\n box-sizing: border-box;\n}\n.tab-switcher .tabs::after, .tab-switcher .tabs::before {\n display: block;\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n}\n.tab-switcher .tab-wrapper {\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n}\n.tab-switcher .tab-wrapper:not(.active)::after {\n content: \"\";\n position: absolute;\n z-index: 7;\n}\n.tab-switcher .mobile-label {\n padding-left: 0.3em;\n padding-bottom: 0.25em;\n margin-top: 0.5em;\n margin-left: 0.2em;\n margin-bottom: 0.25em;\n border-bottom: 1px solid var(--border, #222);\n}\n@media all and (min-width: 800px) {\n .tab-switcher .mobile-label {\n display: none;\n }\n}",".with-load-more-footer {\n padding: 10px;\n text-align: center;\n border-top: 1px solid;\n border-top-color: #222;\n border-top-color: var(--border, #222);\n}\n.with-load-more-footer .error {\n font-size: 14px;\n}\n.with-load-more-footer a {\n cursor: pointer;\n}"],"sourceRoot":""}
\ No newline at end of file
diff --git a/priv/static/static/css/app.77b1644622e3bae24b6b.css b/priv/static/static/css/app.9a4c5ede37b2f0230836.css
similarity index 98%
rename from priv/static/static/css/app.77b1644622e3bae24b6b.css
rename to priv/static/static/css/app.9a4c5ede37b2f0230836.css
index 8038882c0..22b9fdbe7 100644
Binary files a/priv/static/static/css/app.77b1644622e3bae24b6b.css and b/priv/static/static/css/app.9a4c5ede37b2f0230836.css differ
diff --git a/priv/static/static/css/app.9a4c5ede37b2f0230836.css.map b/priv/static/static/css/app.9a4c5ede37b2f0230836.css.map
new file mode 100644
index 000000000..f54bd9ee6
--- /dev/null
+++ b/priv/static/static/css/app.9a4c5ede37b2f0230836.css.map
@@ -0,0 +1 @@
+{"version":3,"sources":["webpack:///./src/components/tab_switcher/tab_switcher.scss","webpack:///./src/hocs/with_load_more/with_load_more.scss"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,C;ACtOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,C","file":"static/css/app.9a4c5ede37b2f0230836.css","sourcesContent":[".tab-switcher {\n display: -ms-flexbox;\n display: flex;\n}\n.tab-switcher .tab-icon {\n margin: 0.2em auto;\n display: block;\n}\n.tab-switcher.top-tabs {\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher.top-tabs > .tabs {\n width: 100%;\n overflow-y: hidden;\n overflow-x: auto;\n padding-top: 5px;\n -ms-flex-direction: row;\n flex-direction: row;\n}\n.tab-switcher.top-tabs > .tabs::after, .tab-switcher.top-tabs > .tabs::before {\n content: \"\";\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n border-bottom: 1px solid;\n border-bottom-color: #222;\n border-bottom-color: var(--border, #222);\n}\n.tab-switcher.top-tabs > .tabs .tab-wrapper {\n height: 28px;\n}\n.tab-switcher.top-tabs > .tabs .tab-wrapper:not(.active)::after {\n left: 0;\n right: 0;\n bottom: 0;\n border-bottom: 1px solid;\n border-bottom-color: #222;\n border-bottom-color: var(--border, #222);\n}\n.tab-switcher.top-tabs > .tabs .tab {\n width: 100%;\n min-width: 1px;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n padding-bottom: 99px;\n margin-bottom: -93px;\n}\n.tab-switcher.top-tabs .contents.scrollable-tabs {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n}\n.tab-switcher.side-tabs {\n -ms-flex-direction: row;\n flex-direction: row;\n}\n@media all and (max-width: 800px) {\n .tab-switcher.side-tabs {\n overflow-x: auto;\n }\n}\n.tab-switcher.side-tabs > .contents {\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n}\n.tab-switcher.side-tabs > .tabs {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n overflow-y: auto;\n overflow-x: hidden;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher.side-tabs > .tabs::after, .tab-switcher.side-tabs > .tabs::before {\n -ms-flex-negative: 0;\n flex-shrink: 0;\n -ms-flex-preferred-size: 0.5em;\n flex-basis: 0.5em;\n content: \"\";\n border-right: 1px solid;\n border-right-color: #222;\n border-right-color: var(--border, #222);\n}\n.tab-switcher.side-tabs > .tabs::after {\n -ms-flex-positive: 1;\n flex-grow: 1;\n}\n.tab-switcher.side-tabs > .tabs::before {\n -ms-flex-positive: 0;\n flex-grow: 0;\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper {\n min-width: 10em;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n@media all and (max-width: 800px) {\n .tab-switcher.side-tabs > .tabs .tab-wrapper {\n min-width: 4em;\n }\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper:not(.active)::after {\n top: 0;\n right: 0;\n bottom: 0;\n border-right: 1px solid;\n border-right-color: #222;\n border-right-color: var(--border, #222);\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper::before {\n -ms-flex: 0 0 6px;\n flex: 0 0 6px;\n content: \"\";\n border-right: 1px solid;\n border-right-color: #222;\n border-right-color: var(--border, #222);\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper:last-child .tab {\n margin-bottom: 0;\n}\n.tab-switcher.side-tabs > .tabs .tab {\n -ms-flex: 1;\n flex: 1;\n box-sizing: content-box;\n min-width: 10em;\n min-width: 1px;\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n padding-left: 1em;\n padding-right: calc(1em + 200px);\n margin-right: -200px;\n margin-left: 1em;\n}\n@media all and (max-width: 800px) {\n .tab-switcher.side-tabs > .tabs .tab {\n padding-left: 0.25em;\n padding-right: calc(.25em + 200px);\n margin-right: calc(.25em - 200px);\n margin-left: 0.25em;\n }\n .tab-switcher.side-tabs > .tabs .tab .text {\n display: none;\n }\n}\n.tab-switcher .contents {\n -ms-flex: 1 0 auto;\n flex: 1 0 auto;\n min-height: 0px;\n}\n.tab-switcher .contents .hidden {\n display: none;\n}\n.tab-switcher .contents .full-height:not(.hidden) {\n height: 100%;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher .contents .full-height:not(.hidden) > *:not(.mobile-label) {\n -ms-flex: 1;\n flex: 1;\n}\n.tab-switcher .contents.scrollable-tabs {\n overflow-y: auto;\n}\n.tab-switcher .tab {\n position: relative;\n white-space: nowrap;\n padding: 6px 1em;\n background-color: #182230;\n background-color: var(--tab, #182230);\n}\n.tab-switcher .tab, .tab-switcher .tab:active .tab-icon {\n color: #b9b9ba;\n color: var(--tabText, #b9b9ba);\n}\n.tab-switcher .tab:not(.active) {\n z-index: 4;\n}\n.tab-switcher .tab:not(.active):hover {\n z-index: 6;\n}\n.tab-switcher .tab.active {\n background: transparent;\n z-index: 5;\n color: #b9b9ba;\n color: var(--tabActiveText, #b9b9ba);\n}\n.tab-switcher .tab img {\n max-height: 26px;\n vertical-align: top;\n margin-top: -5px;\n}\n.tab-switcher .tabs {\n display: -ms-flexbox;\n display: flex;\n position: relative;\n box-sizing: border-box;\n}\n.tab-switcher .tabs::after, .tab-switcher .tabs::before {\n display: block;\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n}\n.tab-switcher .tab-wrapper {\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n}\n.tab-switcher .tab-wrapper:not(.active)::after {\n content: \"\";\n position: absolute;\n z-index: 7;\n}\n.tab-switcher .mobile-label {\n padding-left: 0.3em;\n padding-bottom: 0.25em;\n margin-top: 0.5em;\n margin-left: 0.2em;\n margin-bottom: 0.25em;\n border-bottom: 1px solid var(--border, #222);\n}\n@media all and (min-width: 800px) {\n .tab-switcher .mobile-label {\n display: none;\n }\n}",".with-load-more-footer {\n padding: 10px;\n text-align: center;\n border-top: 1px solid;\n border-top-color: #222;\n border-top-color: var(--border, #222);\n}\n.with-load-more-footer .error {\n font-size: 14px;\n}\n.with-load-more-footer a {\n cursor: pointer;\n}"],"sourceRoot":""}
\ No newline at end of file
diff --git a/priv/static/static/font/fontello.1600365488745.eot b/priv/static/static/font/fontello.1600365488745.eot
deleted file mode 100644
index 255f50372..000000000
Binary files a/priv/static/static/font/fontello.1600365488745.eot and /dev/null differ
diff --git a/priv/static/static/font/fontello.1600365488745.svg b/priv/static/static/font/fontello.1600365488745.svg
deleted file mode 100644
index 9eddf62ea..000000000
--- a/priv/static/static/font/fontello.1600365488745.svg
+++ /dev/null
@@ -1,140 +0,0 @@
-
-
-
-Copyright (C) 2020 by original authors @ fontello.com
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/priv/static/static/font/fontello.1600365488745.ttf b/priv/static/static/font/fontello.1600365488745.ttf
deleted file mode 100644
index 6bda99d50..000000000
Binary files a/priv/static/static/font/fontello.1600365488745.ttf and /dev/null differ
diff --git a/priv/static/static/font/fontello.1600365488745.woff b/priv/static/static/font/fontello.1600365488745.woff
deleted file mode 100644
index 11c866ae0..000000000
Binary files a/priv/static/static/font/fontello.1600365488745.woff and /dev/null differ
diff --git a/priv/static/static/font/fontello.1600365488745.woff2 b/priv/static/static/font/fontello.1600365488745.woff2
deleted file mode 100644
index 06151d28c..000000000
Binary files a/priv/static/static/font/fontello.1600365488745.woff2 and /dev/null differ
diff --git a/priv/static/static/fontello.1600365488745.css b/priv/static/static/fontello.1600365488745.css
deleted file mode 100644
index 781ff7620..000000000
Binary files a/priv/static/static/fontello.1600365488745.css and /dev/null differ
diff --git a/priv/static/static/fontello.json b/priv/static/static/fontello.json
deleted file mode 100644
index b0136fd90..000000000
--- a/priv/static/static/fontello.json
+++ /dev/null
@@ -1,416 +0,0 @@
-{
- "name": "",
- "css_prefix_text": "icon-",
- "css_use_suffix": false,
- "hinting": true,
- "units_per_em": 1000,
- "ascent": 857,
- "glyphs": [
- {
- "uid": "9bd60140934a1eb9236fd7a8ab1ff6ba",
- "css": "spin4",
- "code": 59444,
- "src": "fontelico"
- },
- {
- "uid": "5211af474d3a9848f67f945e2ccaf143",
- "css": "cancel",
- "code": 59392,
- "src": "fontawesome"
- },
- {
- "uid": "eeec3208c90b7b48e804919d0d2d4a41",
- "css": "upload",
- "code": 59393,
- "src": "fontawesome"
- },
- {
- "uid": "2a6740fc2f9d0edea54205963f662594",
- "css": "spin3",
- "code": 59442,
- "src": "fontelico"
- },
- {
- "uid": "c6be5a58ee4e63a5ec399c2b0d15cf2c",
- "css": "reply",
- "code": 61714,
- "src": "fontawesome"
- },
- {
- "uid": "474656633f79ea2f1dad59ff63f6bf07",
- "css": "star",
- "code": 59394,
- "src": "fontawesome"
- },
- {
- "uid": "d17030afaecc1e1c22349b99f3c4992a",
- "css": "star-empty",
- "code": 59395,
- "src": "fontawesome"
- },
- {
- "uid": "09feb4465d9bd1364f4e301c9ddbaa92",
- "css": "retweet",
- "code": 59396,
- "src": "fontawesome"
- },
- {
- "uid": "7fd683b2c518ceb9e5fa6757f2276faa",
- "css": "eye-off",
- "code": 59397,
- "src": "fontawesome"
- },
- {
- "uid": "73ffeb70554099177620847206c12457",
- "css": "binoculars",
- "code": 61925,
- "src": "fontawesome"
- },
- {
- "uid": "e99461abfef3923546da8d745372c995",
- "css": "cog",
- "code": 59399,
- "src": "fontawesome"
- },
- {
- "uid": "1bafeeb1808a5fe24484c7890096901a",
- "css": "user-plus",
- "code": 62004,
- "src": "fontawesome"
- },
- {
- "uid": "559647a6f430b3aeadbecd67194451dd",
- "css": "menu",
- "code": 61641,
- "src": "fontawesome"
- },
- {
- "uid": "0d20938846444af8deb1920dc85a29fb",
- "css": "logout",
- "code": 59400,
- "src": "fontawesome"
- },
- {
- "uid": "ccddff8e8670dcd130e3cb55fdfc2fd0",
- "css": "down-open",
- "code": 59401,
- "src": "fontawesome"
- },
- {
- "uid": "44b9e75612c5fad5505edd70d071651f",
- "css": "attach",
- "code": 59402,
- "src": "entypo"
- },
- {
- "uid": "e15f0d620a7897e2035c18c80142f6d9",
- "css": "link-ext",
- "code": 61582,
- "src": "fontawesome"
- },
- {
- "uid": "e35de5ea31cd56970498e33efbcb8e36",
- "css": "link-ext-alt",
- "code": 61583,
- "src": "fontawesome"
- },
- {
- "uid": "381da2c2f7fd51f8de877c044d7f439d",
- "css": "picture",
- "code": 59403,
- "src": "fontawesome"
- },
- {
- "uid": "872d9516df93eb6b776cc4d94bd97dac",
- "css": "video",
- "code": 59404,
- "src": "fontawesome"
- },
- {
- "uid": "399ef63b1e23ab1b761dfbb5591fa4da",
- "css": "right-open",
- "code": 59405,
- "src": "fontawesome"
- },
- {
- "uid": "d870630ff8f81e6de3958ecaeac532f2",
- "css": "left-open",
- "code": 59406,
- "src": "fontawesome"
- },
- {
- "uid": "fe6697b391355dec12f3d86d6d490397",
- "css": "up-open",
- "code": 59407,
- "src": "fontawesome"
- },
- {
- "uid": "9c1376672bb4f1ed616fdd78a23667e9",
- "css": "comment-empty",
- "code": 61669,
- "src": "fontawesome"
- },
- {
- "uid": "ccc2329632396dc096bb638d4b46fb98",
- "css": "mail-alt",
- "code": 61664,
- "src": "fontawesome"
- },
- {
- "uid": "c1f1975c885aa9f3dad7810c53b82074",
- "css": "lock",
- "code": 59409,
- "src": "fontawesome"
- },
- {
- "uid": "05376be04a27d5a46e855a233d6e8508",
- "css": "lock-open-alt",
- "code": 61758,
- "src": "fontawesome"
- },
- {
- "uid": "197375a3cea8cb90b02d06e4ddf1433d",
- "css": "globe",
- "code": 59410,
- "src": "fontawesome"
- },
- {
- "uid": "b3a9e2dab4d19ea3b2f628242c926bfe",
- "css": "brush",
- "code": 59411,
- "src": "iconic"
- },
- {
- "uid": "9dd9e835aebe1060ba7190ad2b2ed951",
- "css": "search",
- "code": 59398,
- "src": "fontawesome"
- },
- {
- "uid": "ca90da02d2c6a3183f2458e4dc416285",
- "css": "adjust",
- "code": 59414,
- "src": "fontawesome"
- },
- {
- "uid": "5e2ab018e3044337bcef5f7e94098ea1",
- "css": "thumbs-up-alt",
- "code": 61796,
- "src": "fontawesome"
- },
- {
- "uid": "c76b7947c957c9b78b11741173c8349b",
- "css": "attention",
- "code": 59412,
- "src": "fontawesome"
- },
- {
- "uid": "1a5cfa186647e8c929c2b17b9fc4dac1",
- "css": "plus-squared",
- "code": 61694,
- "src": "fontawesome"
- },
- {
- "uid": "44e04715aecbca7f266a17d5a7863c68",
- "css": "plus",
- "code": 59413,
- "src": "fontawesome"
- },
- {
- "uid": "41087bc74d4b20b55059c60a33bf4008",
- "css": "edit",
- "code": 59415,
- "src": "fontawesome"
- },
- {
- "uid": "5717236f6134afe2d2a278a5c9b3927a",
- "css": "play-circled",
- "code": 61764,
- "src": "fontawesome"
- },
- {
- "uid": "d35a1d35efeb784d1dc9ac18b9b6c2b6",
- "css": "pencil",
- "code": 59416,
- "src": "fontawesome"
- },
- {
- "uid": "266d5d9adf15a61800477a5acf9a4462",
- "css": "chart-bar",
- "code": 59419,
- "src": "fontawesome"
- },
- {
- "uid": "d862a10e1448589215be19702f98f2c1",
- "css": "smile",
- "code": 61720,
- "src": "fontawesome"
- },
- {
- "uid": "671f29fa10dda08074a4c6a341bb4f39",
- "css": "bell-alt",
- "code": 61683,
- "src": "fontawesome"
- },
- {
- "uid": "5bb103cd29de77e0e06a52638527b575",
- "css": "wrench",
- "code": 59418,
- "src": "fontawesome"
- },
- {
- "uid": "5b0772e9484a1a11646793a82edd622a",
- "css": "pin",
- "code": 59417,
- "src": "fontawesome"
- },
- {
- "uid": "22411a88489225a018f68db737de3c77",
- "css": "ellipsis",
- "code": 61761,
- "src": "custom_icons",
- "selected": true,
- "svg": {
- "path": "M214 411V518Q214 540 199 556T161 571H54Q31 571 16 556T0 518V411Q0 388 16 373T54 357H161Q183 357 199 373T214 411ZM500 411V518Q500 540 484 556T446 571H339Q317 571 301 556T286 518V411Q286 388 301 373T339 357H446Q469 357 484 373T500 411ZM786 411V518Q786 540 770 556T732 571H625Q603 571 587 556T571 518V411Q571 388 587 373T625 357H732Q755 357 770 373T786 411Z",
- "width": 785.7
- },
- "search": [
- "ellipsis"
- ]
- },
- {
- "uid": "0bef873af785ead27781fdf98b3ae740",
- "css": "bell-ringing-o",
- "code": 59408,
- "src": "custom_icons",
- "selected": true,
- "svg": {
- "path": "M497.8 0C468.3 0 444.4 23.9 444.4 53.3 444.4 61.1 446.1 68.3 448.9 75 301.7 96.7 213.3 213.3 213.3 320 213.3 588.3 117.8 712.8 35.6 782.2 35.6 821.1 67.8 853.3 106.7 853.3H355.6C355.6 931.7 419.4 995.6 497.8 995.6S640 931.7 640 853.3H888.9C927.8 853.3 960 821.1 960 782.2 877.8 712.8 782.2 588.3 782.2 320 782.2 213.3 693.9 96.7 546.7 75 549.4 68.3 551.1 61.1 551.1 53.3 551.1 23.9 527.2 0 497.8 0ZM189.4 44.8C108.4 118.6 70.5 215.1 71.1 320.2L142.2 319.8C141.7 231.2 170.4 158.3 237.3 97.4L189.4 44.8ZM806.2 44.8L758.3 97.4C825.2 158.3 853.9 231.2 853.3 319.8L924.4 320.2C925.1 215.1 887.2 118.6 806.2 44.8ZM408.9 844.4C413.9 844.4 417.8 848.3 417.8 853.3 417.8 897.2 453.9 933.3 497.8 933.3 502.8 933.3 506.7 937.2 506.7 942.2S502.8 951.1 497.8 951.1C443.9 951.1 400 907.2 400 853.3 400 848.3 403.9 844.4 408.9 844.4Z",
- "width": 1000
- },
- "search": [
- "bell-ringing-o"
- ]
- },
- {
- "uid": "0b2b66e526028a6972d51a6f10281b4b",
- "css": "zoom-in",
- "code": 59420,
- "src": "fontawesome"
- },
- {
- "uid": "0bda4bc779d4c32623dec2e43bd67ee8",
- "css": "gauge",
- "code": 61668,
- "src": "fontawesome"
- },
- {
- "uid": "31972e4e9d080eaa796290349ae6c1fd",
- "css": "users",
- "code": 59421,
- "src": "fontawesome"
- },
- {
- "uid": "e82cedfa1d5f15b00c5a81c9bd731ea2",
- "css": "info-circled",
- "code": 59423,
- "src": "fontawesome"
- },
- {
- "uid": "w3nzesrlbezu6f30q7ytyq919p6gdlb6",
- "css": "home-2",
- "code": 59425,
- "src": "typicons"
- },
- {
- "uid": "dcedf50ab1ede3283d7a6c70e2fe32f3",
- "css": "chat",
- "code": 59422,
- "src": "fontawesome"
- },
- {
- "uid": "3a00327e61b997b58518bd43ed83c3df",
- "css": "login",
- "code": 59424,
- "src": "fontawesome"
- },
- {
- "uid": "f3ebd6751c15a280af5cc5f4a764187d",
- "css": "arrow-curved",
- "code": 59426,
- "src": "iconic"
- },
- {
- "uid": "0ddd3e8201ccc7d41f7b7c9d27eca6c1",
- "css": "link",
- "code": 59427,
- "src": "fontawesome"
- },
- {
- "uid": "4aad6bb50b02c18508aae9cbe14e784e",
- "css": "share",
- "code": 61920,
- "src": "fontawesome"
- },
- {
- "uid": "8b80d36d4ef43889db10bc1f0dc9a862",
- "css": "user",
- "code": 59428,
- "src": "fontawesome"
- },
- {
- "uid": "12f4ece88e46abd864e40b35e05b11cd",
- "css": "ok",
- "code": 59431,
- "src": "fontawesome"
- },
- {
- "uid": "4109c474ff99cad28fd5a2c38af2ec6f",
- "css": "filter",
- "code": 61616,
- "src": "fontawesome"
- },
- {
- "uid": "9a76bc135eac17d2c8b8ad4a5774fc87",
- "css": "download",
- "code": 59429,
- "src": "fontawesome"
- },
- {
- "uid": "f04a5d24e9e659145b966739c4fde82a",
- "css": "bookmark",
- "code": 59430,
- "src": "fontawesome"
- },
- {
- "uid": "2f5ef6f6b7aaebc56458ab4e865beff5",
- "css": "bookmark-empty",
- "code": 61591,
- "src": "fontawesome"
- },
- {
- "uid": "9ea0a737ccc45d6c510dcbae56058849",
- "css": "music",
- "code": 59432,
- "src": "fontawesome"
- },
- {
- "uid": "1b5a5d7b7e3c71437f5a26befdd045ed",
- "css": "doc",
- "code": 59433,
- "src": "fontawesome"
- },
- {
- "uid": "98d9c83c1ee7c2c25af784b518c522c5",
- "css": "block",
- "code": 59434,
- "src": "fontawesome"
- },
- {
- "uid": "3e674995cacc2b09692c096ea7eb6165",
- "css": "megaphone",
- "code": 59435,
- "src": "fontawesome"
- }
- ]
-}
\ No newline at end of file
diff --git a/priv/static/static/js/10.46fbbdfaf0d4800f349b.js b/priv/static/static/js/10.46f441b948010eda4403.js
similarity index 71%
rename from priv/static/static/js/10.46fbbdfaf0d4800f349b.js
rename to priv/static/static/js/10.46f441b948010eda4403.js
index 0fd8463df..308d124c0 100644
Binary files a/priv/static/static/js/10.46fbbdfaf0d4800f349b.js and b/priv/static/static/js/10.46f441b948010eda4403.js differ
diff --git a/priv/static/static/js/10.46fbbdfaf0d4800f349b.js.map b/priv/static/static/js/10.46f441b948010eda4403.js.map
similarity index 56%
rename from priv/static/static/js/10.46fbbdfaf0d4800f349b.js.map
rename to priv/static/static/js/10.46f441b948010eda4403.js.map
index bee2feb10..e0623e6bf 100644
Binary files a/priv/static/static/js/10.46fbbdfaf0d4800f349b.js.map and b/priv/static/static/js/10.46f441b948010eda4403.js.map differ
diff --git a/priv/static/static/js/11.708cc2513c53879a92cc.js b/priv/static/static/js/11.8ff1ed54814f2d34cb3e.js
similarity index 99%
rename from priv/static/static/js/11.708cc2513c53879a92cc.js
rename to priv/static/static/js/11.8ff1ed54814f2d34cb3e.js
index 4fe316ecf..cb57f2a65 100644
Binary files a/priv/static/static/js/11.708cc2513c53879a92cc.js and b/priv/static/static/js/11.8ff1ed54814f2d34cb3e.js differ
diff --git a/priv/static/static/js/11.708cc2513c53879a92cc.js.map b/priv/static/static/js/11.8ff1ed54814f2d34cb3e.js.map
similarity index 56%
rename from priv/static/static/js/11.708cc2513c53879a92cc.js.map
rename to priv/static/static/js/11.8ff1ed54814f2d34cb3e.js.map
index 64c9320c4..4ce6d7227 100644
Binary files a/priv/static/static/js/11.708cc2513c53879a92cc.js.map and b/priv/static/static/js/11.8ff1ed54814f2d34cb3e.js.map differ
diff --git a/priv/static/static/js/12.13204bdd0ad5703a3ea3.js b/priv/static/static/js/12.13204bdd0ad5703a3ea3.js
new file mode 100644
index 000000000..a89bfeb67
Binary files /dev/null and b/priv/static/static/js/12.13204bdd0ad5703a3ea3.js differ
diff --git a/priv/static/static/js/12.b3bf0bc313861d6ec36b.js.map b/priv/static/static/js/12.13204bdd0ad5703a3ea3.js.map
similarity index 56%
rename from priv/static/static/js/12.b3bf0bc313861d6ec36b.js.map
rename to priv/static/static/js/12.13204bdd0ad5703a3ea3.js.map
index 28545ac96..366ec2927 100644
Binary files a/priv/static/static/js/12.b3bf0bc313861d6ec36b.js.map and b/priv/static/static/js/12.13204bdd0ad5703a3ea3.js.map differ
diff --git a/priv/static/static/js/12.b3bf0bc313861d6ec36b.js b/priv/static/static/js/12.b3bf0bc313861d6ec36b.js
deleted file mode 100644
index 4890ca10a..000000000
Binary files a/priv/static/static/js/12.b3bf0bc313861d6ec36b.js and /dev/null differ
diff --git a/priv/static/static/js/13.adb8a942514d735722c4.js b/priv/static/static/js/13.e27c3eeddcc4b11c1f54.js
similarity index 76%
rename from priv/static/static/js/13.adb8a942514d735722c4.js
rename to priv/static/static/js/13.e27c3eeddcc4b11c1f54.js
index 41abcb5a6..8cd482b41 100644
Binary files a/priv/static/static/js/13.adb8a942514d735722c4.js and b/priv/static/static/js/13.e27c3eeddcc4b11c1f54.js differ
diff --git a/priv/static/static/js/13.adb8a942514d735722c4.js.map b/priv/static/static/js/13.e27c3eeddcc4b11c1f54.js.map
similarity index 56%
rename from priv/static/static/js/13.adb8a942514d735722c4.js.map
rename to priv/static/static/js/13.e27c3eeddcc4b11c1f54.js.map
index 2b8ff6d6c..0c61c3fca 100644
Binary files a/priv/static/static/js/13.adb8a942514d735722c4.js.map and b/priv/static/static/js/13.e27c3eeddcc4b11c1f54.js.map differ
diff --git a/priv/static/static/js/14.273855b3e4e27ce80219.js b/priv/static/static/js/14.273855b3e4e27ce80219.js
new file mode 100644
index 000000000..78c0bfebc
Binary files /dev/null and b/priv/static/static/js/14.273855b3e4e27ce80219.js differ
diff --git a/priv/static/static/js/14.273855b3e4e27ce80219.js.map b/priv/static/static/js/14.273855b3e4e27ce80219.js.map
new file mode 100644
index 000000000..9ee527eaa
Binary files /dev/null and b/priv/static/static/js/14.273855b3e4e27ce80219.js.map differ
diff --git a/priv/static/static/js/14.d015d9b2ea16407e389c.js b/priv/static/static/js/14.d015d9b2ea16407e389c.js
deleted file mode 100644
index 200a79625..000000000
Binary files a/priv/static/static/js/14.d015d9b2ea16407e389c.js and /dev/null differ
diff --git a/priv/static/static/js/14.d015d9b2ea16407e389c.js.map b/priv/static/static/js/14.d015d9b2ea16407e389c.js.map
deleted file mode 100644
index 49dab13f7..000000000
Binary files a/priv/static/static/js/14.d015d9b2ea16407e389c.js.map and /dev/null differ
diff --git a/priv/static/static/js/15.19866e6a366ccf982284.js.map b/priv/static/static/js/15.19866e6a366ccf982284.js.map
deleted file mode 100644
index 561ab7dcf..000000000
Binary files a/priv/static/static/js/15.19866e6a366ccf982284.js.map and /dev/null differ
diff --git a/priv/static/static/js/15.19866e6a366ccf982284.js b/priv/static/static/js/15.afbe29b6665fcd015b2d.js
similarity index 98%
rename from priv/static/static/js/15.19866e6a366ccf982284.js
rename to priv/static/static/js/15.afbe29b6665fcd015b2d.js
index 0cc2e266a..b83752240 100644
Binary files a/priv/static/static/js/15.19866e6a366ccf982284.js and b/priv/static/static/js/15.afbe29b6665fcd015b2d.js differ
diff --git a/priv/static/static/js/15.afbe29b6665fcd015b2d.js.map b/priv/static/static/js/15.afbe29b6665fcd015b2d.js.map
new file mode 100644
index 000000000..c7a0be582
Binary files /dev/null and b/priv/static/static/js/15.afbe29b6665fcd015b2d.js.map differ
diff --git a/priv/static/static/js/16.38a984effd54736f6a2c.js.map b/priv/static/static/js/16.38a984effd54736f6a2c.js.map
deleted file mode 100644
index 68ee95f97..000000000
Binary files a/priv/static/static/js/16.38a984effd54736f6a2c.js.map and /dev/null differ
diff --git a/priv/static/static/js/16.38a984effd54736f6a2c.js b/priv/static/static/js/16.5e3f20da470591d0cabf.js
similarity index 99%
rename from priv/static/static/js/16.38a984effd54736f6a2c.js
rename to priv/static/static/js/16.5e3f20da470591d0cabf.js
index b3cebb0bd..e90ed4ca1 100644
Binary files a/priv/static/static/js/16.38a984effd54736f6a2c.js and b/priv/static/static/js/16.5e3f20da470591d0cabf.js differ
diff --git a/priv/static/static/js/16.5e3f20da470591d0cabf.js.map b/priv/static/static/js/16.5e3f20da470591d0cabf.js.map
new file mode 100644
index 000000000..0c4d0e385
Binary files /dev/null and b/priv/static/static/js/16.5e3f20da470591d0cabf.js.map differ
diff --git a/priv/static/static/js/17.9c25507194320db2e85b.js b/priv/static/static/js/17.44e90ef82ee2ef12dc3f.js
similarity index 94%
rename from priv/static/static/js/17.9c25507194320db2e85b.js
rename to priv/static/static/js/17.44e90ef82ee2ef12dc3f.js
index 451bf8bd3..9b5adfd12 100644
Binary files a/priv/static/static/js/17.9c25507194320db2e85b.js and b/priv/static/static/js/17.44e90ef82ee2ef12dc3f.js differ
diff --git a/priv/static/static/js/17.44e90ef82ee2ef12dc3f.js.map b/priv/static/static/js/17.44e90ef82ee2ef12dc3f.js.map
new file mode 100644
index 000000000..1d191b94a
Binary files /dev/null and b/priv/static/static/js/17.44e90ef82ee2ef12dc3f.js.map differ
diff --git a/priv/static/static/js/17.9c25507194320db2e85b.js.map b/priv/static/static/js/17.9c25507194320db2e85b.js.map
deleted file mode 100644
index f843d4400..000000000
Binary files a/priv/static/static/js/17.9c25507194320db2e85b.js.map and /dev/null differ
diff --git a/priv/static/static/js/18.94946caca48930c224c7.js.map b/priv/static/static/js/18.94946caca48930c224c7.js.map
deleted file mode 100644
index ad04b99ab..000000000
Binary files a/priv/static/static/js/18.94946caca48930c224c7.js.map and /dev/null differ
diff --git a/priv/static/static/js/18.94946caca48930c224c7.js b/priv/static/static/js/18.9a5b877f94b2b53065e1.js
similarity index 57%
rename from priv/static/static/js/18.94946caca48930c224c7.js
rename to priv/static/static/js/18.9a5b877f94b2b53065e1.js
index 5a1f40c6d..c4aea5b25 100644
Binary files a/priv/static/static/js/18.94946caca48930c224c7.js and b/priv/static/static/js/18.9a5b877f94b2b53065e1.js differ
diff --git a/priv/static/static/js/18.9a5b877f94b2b53065e1.js.map b/priv/static/static/js/18.9a5b877f94b2b53065e1.js.map
new file mode 100644
index 000000000..61d9a7d41
Binary files /dev/null and b/priv/static/static/js/18.9a5b877f94b2b53065e1.js.map differ
diff --git a/priv/static/static/js/19.233c81ac2c28d55e9f13.js b/priv/static/static/js/19.1fd4da643df0abf89122.js
similarity index 99%
rename from priv/static/static/js/19.233c81ac2c28d55e9f13.js
rename to priv/static/static/js/19.1fd4da643df0abf89122.js
index ace0a1d41..c1ca1643b 100644
Binary files a/priv/static/static/js/19.233c81ac2c28d55e9f13.js and b/priv/static/static/js/19.1fd4da643df0abf89122.js differ
diff --git a/priv/static/static/js/19.1fd4da643df0abf89122.js.map b/priv/static/static/js/19.1fd4da643df0abf89122.js.map
new file mode 100644
index 000000000..010c8674d
Binary files /dev/null and b/priv/static/static/js/19.1fd4da643df0abf89122.js.map differ
diff --git a/priv/static/static/js/19.233c81ac2c28d55e9f13.js.map b/priv/static/static/js/19.233c81ac2c28d55e9f13.js.map
deleted file mode 100644
index cd3f7354d..000000000
Binary files a/priv/static/static/js/19.233c81ac2c28d55e9f13.js.map and /dev/null differ
diff --git a/priv/static/static/js/2.422e6c756ac673a6fd44.js b/priv/static/static/js/2.422e6c756ac673a6fd44.js
new file mode 100644
index 000000000..9fb47e2bf
Binary files /dev/null and b/priv/static/static/js/2.422e6c756ac673a6fd44.js differ
diff --git a/priv/static/static/js/2.422e6c756ac673a6fd44.js.map b/priv/static/static/js/2.422e6c756ac673a6fd44.js.map
new file mode 100644
index 000000000..92fdb4d2c
Binary files /dev/null and b/priv/static/static/js/2.422e6c756ac673a6fd44.js.map differ
diff --git a/priv/static/static/js/2.e852a6b4b3bba752b838.js b/priv/static/static/js/2.e852a6b4b3bba752b838.js
deleted file mode 100644
index 42e446575..000000000
Binary files a/priv/static/static/js/2.e852a6b4b3bba752b838.js and /dev/null differ
diff --git a/priv/static/static/js/2.e852a6b4b3bba752b838.js.map b/priv/static/static/js/2.e852a6b4b3bba752b838.js.map
deleted file mode 100644
index d698f09e1..000000000
Binary files a/priv/static/static/js/2.e852a6b4b3bba752b838.js.map and /dev/null differ
diff --git a/priv/static/static/js/20.818c38d27369c3a4d677.js.map b/priv/static/static/js/20.818c38d27369c3a4d677.js.map
deleted file mode 100644
index 696eab20f..000000000
Binary files a/priv/static/static/js/20.818c38d27369c3a4d677.js.map and /dev/null differ
diff --git a/priv/static/static/js/20.818c38d27369c3a4d677.js b/priv/static/static/js/20.a64fd29da59076399a27.js
similarity index 99%
rename from priv/static/static/js/20.818c38d27369c3a4d677.js
rename to priv/static/static/js/20.a64fd29da59076399a27.js
index 133eac52d..eae5b3947 100644
Binary files a/priv/static/static/js/20.818c38d27369c3a4d677.js and b/priv/static/static/js/20.a64fd29da59076399a27.js differ
diff --git a/priv/static/static/js/20.a64fd29da59076399a27.js.map b/priv/static/static/js/20.a64fd29da59076399a27.js.map
new file mode 100644
index 000000000..b2917fa10
Binary files /dev/null and b/priv/static/static/js/20.a64fd29da59076399a27.js.map differ
diff --git a/priv/static/static/js/21.ce4cda179d888ca6bc2a.js b/priv/static/static/js/21.243d9e6ebf469a2dc740.js
similarity index 99%
rename from priv/static/static/js/21.ce4cda179d888ca6bc2a.js
rename to priv/static/static/js/21.243d9e6ebf469a2dc740.js
index 49700403c..61633519b 100644
Binary files a/priv/static/static/js/21.ce4cda179d888ca6bc2a.js and b/priv/static/static/js/21.243d9e6ebf469a2dc740.js differ
diff --git a/priv/static/static/js/21.243d9e6ebf469a2dc740.js.map b/priv/static/static/js/21.243d9e6ebf469a2dc740.js.map
new file mode 100644
index 000000000..3f98250fa
Binary files /dev/null and b/priv/static/static/js/21.243d9e6ebf469a2dc740.js.map differ
diff --git a/priv/static/static/js/21.ce4cda179d888ca6bc2a.js.map b/priv/static/static/js/21.ce4cda179d888ca6bc2a.js.map
deleted file mode 100644
index 124d58abc..000000000
Binary files a/priv/static/static/js/21.ce4cda179d888ca6bc2a.js.map and /dev/null differ
diff --git a/priv/static/static/js/22.2ea93c6cc569ef0256ab.js.map b/priv/static/static/js/22.2ea93c6cc569ef0256ab.js.map
deleted file mode 100644
index 773159f01..000000000
Binary files a/priv/static/static/js/22.2ea93c6cc569ef0256ab.js.map and /dev/null differ
diff --git a/priv/static/static/js/22.2ea93c6cc569ef0256ab.js b/priv/static/static/js/22.e20ef7e5fefc0964cdd1.js
similarity index 99%
rename from priv/static/static/js/22.2ea93c6cc569ef0256ab.js
rename to priv/static/static/js/22.e20ef7e5fefc0964cdd1.js
index 1d2077720..e8f309f8a 100644
Binary files a/priv/static/static/js/22.2ea93c6cc569ef0256ab.js and b/priv/static/static/js/22.e20ef7e5fefc0964cdd1.js differ
diff --git a/priv/static/static/js/22.e20ef7e5fefc0964cdd1.js.map b/priv/static/static/js/22.e20ef7e5fefc0964cdd1.js.map
new file mode 100644
index 000000000..7780cffe6
Binary files /dev/null and b/priv/static/static/js/22.e20ef7e5fefc0964cdd1.js.map differ
diff --git a/priv/static/static/js/23.a57a7845cc20fafd06d1.js b/priv/static/static/js/23.614a35f9ded445292f4a.js
similarity index 99%
rename from priv/static/static/js/23.a57a7845cc20fafd06d1.js
rename to priv/static/static/js/23.614a35f9ded445292f4a.js
index b15a888df..a35450986 100644
Binary files a/priv/static/static/js/23.a57a7845cc20fafd06d1.js and b/priv/static/static/js/23.614a35f9ded445292f4a.js differ
diff --git a/priv/static/static/js/23.614a35f9ded445292f4a.js.map b/priv/static/static/js/23.614a35f9ded445292f4a.js.map
new file mode 100644
index 000000000..4158041f4
Binary files /dev/null and b/priv/static/static/js/23.614a35f9ded445292f4a.js.map differ
diff --git a/priv/static/static/js/23.a57a7845cc20fafd06d1.js.map b/priv/static/static/js/23.a57a7845cc20fafd06d1.js.map
deleted file mode 100644
index 0e5b421e6..000000000
Binary files a/priv/static/static/js/23.a57a7845cc20fafd06d1.js.map and /dev/null differ
diff --git a/priv/static/static/js/24.35eb55a657b5485f8491.js.map b/priv/static/static/js/24.35eb55a657b5485f8491.js.map
deleted file mode 100644
index 93ffbb2e9..000000000
Binary files a/priv/static/static/js/24.35eb55a657b5485f8491.js.map and /dev/null differ
diff --git a/priv/static/static/js/24.35eb55a657b5485f8491.js b/priv/static/static/js/24.6ae9ca51e51e023afbe4.js
similarity index 99%
rename from priv/static/static/js/24.35eb55a657b5485f8491.js
rename to priv/static/static/js/24.6ae9ca51e51e023afbe4.js
index d09d5c371..d075f3b1f 100644
Binary files a/priv/static/static/js/24.35eb55a657b5485f8491.js and b/priv/static/static/js/24.6ae9ca51e51e023afbe4.js differ
diff --git a/priv/static/static/js/24.6ae9ca51e51e023afbe4.js.map b/priv/static/static/js/24.6ae9ca51e51e023afbe4.js.map
new file mode 100644
index 000000000..7e68d5eaa
Binary files /dev/null and b/priv/static/static/js/24.6ae9ca51e51e023afbe4.js.map differ
diff --git a/priv/static/static/js/25.5a9efe20e3ae1352e6d2.js b/priv/static/static/js/25.5a9efe20e3ae1352e6d2.js
deleted file mode 100644
index e96c5e6ec..000000000
Binary files a/priv/static/static/js/25.5a9efe20e3ae1352e6d2.js and /dev/null differ
diff --git a/priv/static/static/js/25.5a9efe20e3ae1352e6d2.js.map b/priv/static/static/js/25.5a9efe20e3ae1352e6d2.js.map
deleted file mode 100644
index a506e6fa8..000000000
Binary files a/priv/static/static/js/25.5a9efe20e3ae1352e6d2.js.map and /dev/null differ
diff --git a/priv/static/static/js/25.eadae0d48ee5be52a16c.js b/priv/static/static/js/25.eadae0d48ee5be52a16c.js
new file mode 100644
index 000000000..a0e44e1aa
Binary files /dev/null and b/priv/static/static/js/25.eadae0d48ee5be52a16c.js differ
diff --git a/priv/static/static/js/25.eadae0d48ee5be52a16c.js.map b/priv/static/static/js/25.eadae0d48ee5be52a16c.js.map
new file mode 100644
index 000000000..aaa5e3a57
Binary files /dev/null and b/priv/static/static/js/25.eadae0d48ee5be52a16c.js.map differ
diff --git a/priv/static/static/js/26.cf13231d524e5ca3b3e6.js b/priv/static/static/js/26.8fd0027b982c4bcdc88f.js
similarity index 99%
rename from priv/static/static/js/26.cf13231d524e5ca3b3e6.js
rename to priv/static/static/js/26.8fd0027b982c4bcdc88f.js
index adc57d6c7..3b149915b 100644
Binary files a/priv/static/static/js/26.cf13231d524e5ca3b3e6.js and b/priv/static/static/js/26.8fd0027b982c4bcdc88f.js differ
diff --git a/priv/static/static/js/26.8fd0027b982c4bcdc88f.js.map b/priv/static/static/js/26.8fd0027b982c4bcdc88f.js.map
new file mode 100644
index 000000000..d40f1979a
Binary files /dev/null and b/priv/static/static/js/26.8fd0027b982c4bcdc88f.js.map differ
diff --git a/priv/static/static/js/26.cf13231d524e5ca3b3e6.js.map b/priv/static/static/js/26.cf13231d524e5ca3b3e6.js.map
deleted file mode 100644
index 8654bda10..000000000
Binary files a/priv/static/static/js/26.cf13231d524e5ca3b3e6.js.map and /dev/null differ
diff --git a/priv/static/static/js/27.fca8d4f6e444bd14f376.js b/priv/static/static/js/27.6d90a54efba08d261d69.js
similarity index 94%
rename from priv/static/static/js/27.fca8d4f6e444bd14f376.js
rename to priv/static/static/js/27.6d90a54efba08d261d69.js
index 9f8b5c85d..e8420a54f 100644
Binary files a/priv/static/static/js/27.fca8d4f6e444bd14f376.js and b/priv/static/static/js/27.6d90a54efba08d261d69.js differ
diff --git a/priv/static/static/js/27.6d90a54efba08d261d69.js.map b/priv/static/static/js/27.6d90a54efba08d261d69.js.map
new file mode 100644
index 000000000..6685474ce
Binary files /dev/null and b/priv/static/static/js/27.6d90a54efba08d261d69.js.map differ
diff --git a/priv/static/static/js/27.fca8d4f6e444bd14f376.js.map b/priv/static/static/js/27.fca8d4f6e444bd14f376.js.map
deleted file mode 100644
index f6ea8afc9..000000000
Binary files a/priv/static/static/js/27.fca8d4f6e444bd14f376.js.map and /dev/null differ
diff --git a/priv/static/static/js/28.e0f9f164e0bfd890dc61.js.map b/priv/static/static/js/28.e0f9f164e0bfd890dc61.js.map
deleted file mode 100644
index 536ae2d7a..000000000
Binary files a/priv/static/static/js/28.e0f9f164e0bfd890dc61.js.map and /dev/null differ
diff --git a/priv/static/static/js/28.e0f9f164e0bfd890dc61.js b/priv/static/static/js/28.f1353aa382a104262d1a.js
similarity index 98%
rename from priv/static/static/js/28.e0f9f164e0bfd890dc61.js
rename to priv/static/static/js/28.f1353aa382a104262d1a.js
index 75ba6d69d..a253284f0 100644
Binary files a/priv/static/static/js/28.e0f9f164e0bfd890dc61.js and b/priv/static/static/js/28.f1353aa382a104262d1a.js differ
diff --git a/priv/static/static/js/28.f1353aa382a104262d1a.js.map b/priv/static/static/js/28.f1353aa382a104262d1a.js.map
new file mode 100644
index 000000000..3421c9511
Binary files /dev/null and b/priv/static/static/js/28.f1353aa382a104262d1a.js.map differ
diff --git a/priv/static/static/js/29.0b69359f0fe5c0785746.js.map b/priv/static/static/js/29.0b69359f0fe5c0785746.js.map
deleted file mode 100644
index 65cd6bc82..000000000
Binary files a/priv/static/static/js/29.0b69359f0fe5c0785746.js.map and /dev/null differ
diff --git a/priv/static/static/js/29.0b69359f0fe5c0785746.js b/priv/static/static/js/29.39c1e87a689c840395b2.js
similarity index 99%
rename from priv/static/static/js/29.0b69359f0fe5c0785746.js
rename to priv/static/static/js/29.39c1e87a689c840395b2.js
index 24d73bcd5..ddb512279 100644
Binary files a/priv/static/static/js/29.0b69359f0fe5c0785746.js and b/priv/static/static/js/29.39c1e87a689c840395b2.js differ
diff --git a/priv/static/static/js/29.39c1e87a689c840395b2.js.map b/priv/static/static/js/29.39c1e87a689c840395b2.js.map
new file mode 100644
index 000000000..5901ce9b7
Binary files /dev/null and b/priv/static/static/js/29.39c1e87a689c840395b2.js.map differ
diff --git a/priv/static/static/js/3.7d21accf4e5bd07e3ebf.js b/priv/static/static/js/3.a0df8a5bcd120d1f8581.js
similarity index 99%
rename from priv/static/static/js/3.7d21accf4e5bd07e3ebf.js
rename to priv/static/static/js/3.a0df8a5bcd120d1f8581.js
index d98aadec2..423121114 100644
Binary files a/priv/static/static/js/3.7d21accf4e5bd07e3ebf.js and b/priv/static/static/js/3.a0df8a5bcd120d1f8581.js differ
diff --git a/priv/static/static/js/3.7d21accf4e5bd07e3ebf.js.map b/priv/static/static/js/3.a0df8a5bcd120d1f8581.js.map
similarity index 99%
rename from priv/static/static/js/3.7d21accf4e5bd07e3ebf.js.map
rename to priv/static/static/js/3.a0df8a5bcd120d1f8581.js.map
index 37826baac..653727d10 100644
Binary files a/priv/static/static/js/3.7d21accf4e5bd07e3ebf.js.map and b/priv/static/static/js/3.a0df8a5bcd120d1f8581.js.map differ
diff --git a/priv/static/static/js/30.64736585965c63c2b5d4.js b/priv/static/static/js/30.64736585965c63c2b5d4.js
new file mode 100644
index 000000000..4fdbe8c3e
Binary files /dev/null and b/priv/static/static/js/30.64736585965c63c2b5d4.js differ
diff --git a/priv/static/static/js/30.64736585965c63c2b5d4.js.map b/priv/static/static/js/30.64736585965c63c2b5d4.js.map
new file mode 100644
index 000000000..376920946
Binary files /dev/null and b/priv/static/static/js/30.64736585965c63c2b5d4.js.map differ
diff --git a/priv/static/static/js/30.fce58be0b52ca3e32fa4.js b/priv/static/static/js/30.fce58be0b52ca3e32fa4.js
deleted file mode 100644
index 03a5d65f6..000000000
Binary files a/priv/static/static/js/30.fce58be0b52ca3e32fa4.js and /dev/null differ
diff --git a/priv/static/static/js/30.fce58be0b52ca3e32fa4.js.map b/priv/static/static/js/30.fce58be0b52ca3e32fa4.js.map
deleted file mode 100644
index f7dc83701..000000000
Binary files a/priv/static/static/js/30.fce58be0b52ca3e32fa4.js.map and /dev/null differ
diff --git a/priv/static/static/js/4.5719922a4e807145346d.js b/priv/static/static/js/4.4cde7fdd1fe6bf2a9499.js
similarity index 77%
rename from priv/static/static/js/4.5719922a4e807145346d.js
rename to priv/static/static/js/4.4cde7fdd1fe6bf2a9499.js
index 91ea2ac5e..4da4c56fa 100644
Binary files a/priv/static/static/js/4.5719922a4e807145346d.js and b/priv/static/static/js/4.4cde7fdd1fe6bf2a9499.js differ
diff --git a/priv/static/static/js/4.5719922a4e807145346d.js.map b/priv/static/static/js/4.4cde7fdd1fe6bf2a9499.js.map
similarity index 99%
rename from priv/static/static/js/4.5719922a4e807145346d.js.map
rename to priv/static/static/js/4.4cde7fdd1fe6bf2a9499.js.map
index d5e592cfd..bc040ab9b 100644
Binary files a/priv/static/static/js/4.5719922a4e807145346d.js.map and b/priv/static/static/js/4.4cde7fdd1fe6bf2a9499.js.map differ
diff --git a/priv/static/static/js/5.cf05c5ddbdbac890ae35.js b/priv/static/static/js/5.2e165bc072548e533dd4.js
similarity index 98%
rename from priv/static/static/js/5.cf05c5ddbdbac890ae35.js
rename to priv/static/static/js/5.2e165bc072548e533dd4.js
index f54d67fb3..cfd84226c 100644
Binary files a/priv/static/static/js/5.cf05c5ddbdbac890ae35.js and b/priv/static/static/js/5.2e165bc072548e533dd4.js differ
diff --git a/priv/static/static/js/5.cf05c5ddbdbac890ae35.js.map b/priv/static/static/js/5.2e165bc072548e533dd4.js.map
similarity index 57%
rename from priv/static/static/js/5.cf05c5ddbdbac890ae35.js.map
rename to priv/static/static/js/5.2e165bc072548e533dd4.js.map
index 77f2d0898..49959c78e 100644
Binary files a/priv/static/static/js/5.cf05c5ddbdbac890ae35.js.map and b/priv/static/static/js/5.2e165bc072548e533dd4.js.map differ
diff --git a/priv/static/static/js/6.260ccd84f8cd2af27970.js b/priv/static/static/js/6.260ccd84f8cd2af27970.js
new file mode 100644
index 000000000..fb4a690f4
Binary files /dev/null and b/priv/static/static/js/6.260ccd84f8cd2af27970.js differ
diff --git a/priv/static/static/js/6.ecfd3302a692de148391.js.map b/priv/static/static/js/6.260ccd84f8cd2af27970.js.map
similarity index 57%
rename from priv/static/static/js/6.ecfd3302a692de148391.js.map
rename to priv/static/static/js/6.260ccd84f8cd2af27970.js.map
index a17c7d297..850fe731a 100644
Binary files a/priv/static/static/js/6.ecfd3302a692de148391.js.map and b/priv/static/static/js/6.260ccd84f8cd2af27970.js.map differ
diff --git a/priv/static/static/js/6.ecfd3302a692de148391.js b/priv/static/static/js/6.ecfd3302a692de148391.js
deleted file mode 100644
index 354243ec2..000000000
Binary files a/priv/static/static/js/6.ecfd3302a692de148391.js and /dev/null differ
diff --git a/priv/static/static/js/7.dd44c3d58fb9dced093d.js b/priv/static/static/js/7.1c41eff6cfc75a00bde4.js
similarity index 99%
rename from priv/static/static/js/7.dd44c3d58fb9dced093d.js
rename to priv/static/static/js/7.1c41eff6cfc75a00bde4.js
index cb95efc73..317770a53 100644
Binary files a/priv/static/static/js/7.dd44c3d58fb9dced093d.js and b/priv/static/static/js/7.1c41eff6cfc75a00bde4.js differ
diff --git a/priv/static/static/js/7.dd44c3d58fb9dced093d.js.map b/priv/static/static/js/7.1c41eff6cfc75a00bde4.js.map
similarity index 57%
rename from priv/static/static/js/7.dd44c3d58fb9dced093d.js.map
rename to priv/static/static/js/7.1c41eff6cfc75a00bde4.js.map
index ae7e35d5d..36f327b3f 100644
Binary files a/priv/static/static/js/7.dd44c3d58fb9dced093d.js.map and b/priv/static/static/js/7.1c41eff6cfc75a00bde4.js.map differ
diff --git a/priv/static/static/js/8.636322a87bb10a1754f8.js b/priv/static/static/js/8.9b35c2fee24ab7481e00.js
similarity index 99%
rename from priv/static/static/js/8.636322a87bb10a1754f8.js
rename to priv/static/static/js/8.9b35c2fee24ab7481e00.js
index 6e635fb6a..cb7844ffc 100644
Binary files a/priv/static/static/js/8.636322a87bb10a1754f8.js and b/priv/static/static/js/8.9b35c2fee24ab7481e00.js differ
diff --git a/priv/static/static/js/8.636322a87bb10a1754f8.js.map b/priv/static/static/js/8.9b35c2fee24ab7481e00.js.map
similarity index 57%
rename from priv/static/static/js/8.636322a87bb10a1754f8.js.map
rename to priv/static/static/js/8.9b35c2fee24ab7481e00.js.map
index f074928a5..65f4d5ae9 100644
Binary files a/priv/static/static/js/8.636322a87bb10a1754f8.js.map and b/priv/static/static/js/8.9b35c2fee24ab7481e00.js.map differ
diff --git a/priv/static/static/js/9.3a29094f1886648a0af3.js b/priv/static/static/js/9.3a29094f1886648a0af3.js
new file mode 100644
index 000000000..eb3516dcd
Binary files /dev/null and b/priv/static/static/js/9.3a29094f1886648a0af3.js differ
diff --git a/priv/static/static/js/9.3a29094f1886648a0af3.js.map b/priv/static/static/js/9.3a29094f1886648a0af3.js.map
new file mode 100644
index 000000000..1b6224a6a
Binary files /dev/null and b/priv/static/static/js/9.3a29094f1886648a0af3.js.map differ
diff --git a/priv/static/static/js/9.6010dbcce7b4d7c05a18.js b/priv/static/static/js/9.6010dbcce7b4d7c05a18.js
deleted file mode 100644
index fcad39a7e..000000000
Binary files a/priv/static/static/js/9.6010dbcce7b4d7c05a18.js and /dev/null differ
diff --git a/priv/static/static/js/9.6010dbcce7b4d7c05a18.js.map b/priv/static/static/js/9.6010dbcce7b4d7c05a18.js.map
deleted file mode 100644
index e5e1cd823..000000000
Binary files a/priv/static/static/js/9.6010dbcce7b4d7c05a18.js.map and /dev/null differ
diff --git a/priv/static/static/js/app.45547c05212c403dd77c.js b/priv/static/static/js/app.45547c05212c403dd77c.js
new file mode 100644
index 000000000..219a59493
Binary files /dev/null and b/priv/static/static/js/app.45547c05212c403dd77c.js differ
diff --git a/priv/static/static/js/app.45547c05212c403dd77c.js.map b/priv/static/static/js/app.45547c05212c403dd77c.js.map
new file mode 100644
index 000000000..e1dd6c992
Binary files /dev/null and b/priv/static/static/js/app.45547c05212c403dd77c.js.map differ
diff --git a/priv/static/static/js/app.826c44232e0a76bbd9ba.js b/priv/static/static/js/app.826c44232e0a76bbd9ba.js
deleted file mode 100644
index 16762165e..000000000
Binary files a/priv/static/static/js/app.826c44232e0a76bbd9ba.js and /dev/null differ
diff --git a/priv/static/static/js/app.826c44232e0a76bbd9ba.js.map b/priv/static/static/js/app.826c44232e0a76bbd9ba.js.map
deleted file mode 100644
index b188c3379..000000000
Binary files a/priv/static/static/js/app.826c44232e0a76bbd9ba.js.map and /dev/null differ
diff --git a/priv/static/static/js/vendors~app.90c4af83c1ae68f4cd95.js b/priv/static/static/js/vendors~app.90c4af83c1ae68f4cd95.js
deleted file mode 100644
index 879a3b312..000000000
Binary files a/priv/static/static/js/vendors~app.90c4af83c1ae68f4cd95.js and /dev/null differ
diff --git a/priv/static/static/js/vendors~app.90c4af83c1ae68f4cd95.js.map b/priv/static/static/js/vendors~app.90c4af83c1ae68f4cd95.js.map
deleted file mode 100644
index 395f83b6b..000000000
Binary files a/priv/static/static/js/vendors~app.90c4af83c1ae68f4cd95.js.map and /dev/null differ
diff --git a/priv/static/static/js/vendors~app.952124344a84613dbac0.js b/priv/static/static/js/vendors~app.952124344a84613dbac0.js
new file mode 100644
index 000000000..f7943c122
Binary files /dev/null and b/priv/static/static/js/vendors~app.952124344a84613dbac0.js differ
diff --git a/priv/static/static/js/vendors~app.952124344a84613dbac0.js.map b/priv/static/static/js/vendors~app.952124344a84613dbac0.js.map
new file mode 100644
index 000000000..05fc07c18
Binary files /dev/null and b/priv/static/static/js/vendors~app.952124344a84613dbac0.js.map differ
diff --git a/priv/static/static/logo.png b/priv/static/static/logo.png
deleted file mode 100644
index 7744b1acc..000000000
Binary files a/priv/static/static/logo.png and /dev/null differ
diff --git a/priv/static/static/logo.svg b/priv/static/static/logo.svg
new file mode 100644
index 000000000..68e647e6c
--- /dev/null
+++ b/priv/static/static/logo.svg
@@ -0,0 +1,71 @@
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/priv/static/static/terms-of-service.html b/priv/static/static/terms-of-service.html
index 3b6bbb36b..2b7bf7697 100644
--- a/priv/static/static/terms-of-service.html
+++ b/priv/static/static/terms-of-service.html
@@ -6,4 +6,4 @@
Pleroma install containing the real ToS for your instance.
See the Pleroma documentation for more information.
-
+
diff --git a/priv/static/sw-pleroma.js b/priv/static/sw-pleroma.js
index fa4969025..385ee2f0c 100644
Binary files a/priv/static/sw-pleroma.js and b/priv/static/sw-pleroma.js differ
diff --git a/priv/static/sw-pleroma.js.map b/priv/static/sw-pleroma.js.map
index cd5ea0ae6..0b6a76c2f 100644
Binary files a/priv/static/sw-pleroma.js.map and b/priv/static/sw-pleroma.js.map differ
diff --git a/test/fixtures/mastodon/application_actor.json b/test/fixtures/mastodon/application_actor.json
new file mode 100644
index 000000000..2089ea049
--- /dev/null
+++ b/test/fixtures/mastodon/application_actor.json
@@ -0,0 +1,67 @@
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ {
+ "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
+ "toot": "http://joinmastodon.org/ns#",
+ "featured": {
+ "@id": "toot:featured",
+ "@type": "@id"
+ },
+ "alsoKnownAs": {
+ "@id": "as:alsoKnownAs",
+ "@type": "@id"
+ },
+ "movedTo": {
+ "@id": "as:movedTo",
+ "@type": "@id"
+ },
+ "schema": "http://schema.org#",
+ "PropertyValue": "schema:PropertyValue",
+ "value": "schema:value",
+ "IdentityProof": "toot:IdentityProof",
+ "discoverable": "toot:discoverable",
+ "Device": "toot:Device",
+ "Ed25519Signature": "toot:Ed25519Signature",
+ "Ed25519Key": "toot:Ed25519Key",
+ "Curve25519Key": "toot:Curve25519Key",
+ "EncryptedMessage": "toot:EncryptedMessage",
+ "publicKeyBase64": "toot:publicKeyBase64",
+ "deviceId": "toot:deviceId",
+ "claim": {
+ "@type": "@id",
+ "@id": "toot:claim"
+ },
+ "fingerprintKey": {
+ "@type": "@id",
+ "@id": "toot:fingerprintKey"
+ },
+ "identityKey": {
+ "@type": "@id",
+ "@id": "toot:identityKey"
+ },
+ "devices": {
+ "@type": "@id",
+ "@id": "toot:devices"
+ },
+ "messageFranking": "toot:messageFranking",
+ "messageType": "toot:messageType",
+ "cipherText": "toot:cipherText"
+ }
+ ],
+ "id": "https://{{DOMAIN}}/actor",
+ "type": "Application",
+ "inbox": "https://{{DOMAIN}}/actor/inbox",
+ "preferredUsername": "{{DOMAIN}}",
+ "url": "https://{{DOMAIN}}/about/more?instance_actor=true",
+ "manuallyApprovesFollowers": true,
+ "publicKey": {
+ "id": "https://{{DOMAIN}}/actor#main-key",
+ "owner": "https://{{DOMAIN}}/actor",
+ "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAA0CA08AMIIBCgKCAQEAyi2T2FFZJgRPY+96YQrn\n6J6eF2P60J+nz+/pRc/acv/Nx+NLxxPyXby0F2s60MV7uALRQbBBnf7oNKCd/T4S\nvbr7UXMCWTdaJBpYubMKWT9uBlaUUkUfqL+WTV+IQnlcKtssQ4+AwrAKAZXza8ws\nZypevOsLHzayyEzztmm1KQC9GCUOITCLf7Q6qEhy8z/HuqLBEC0Own0pD7QsbfcS\no1peuZY7g1E/jJ9HR9GqJccMaR0H28KmJ7tT1Yzlyf5uZMRIdPxsoMR9sGLjR2B8\noegSwaf9SogR3ScP395Tt/9Ud1VVzuhpoS8Uy7jKSs+3CuLJsEGoMrib8VyOwadS\n9wIDAQAB\n-----END PUBLIC KEY-----\n"
+ },
+ "endpoints": {
+ "sharedInbox": "https://{{DOMAIN}}/inbox"
+ }
+}
diff --git a/test/pleroma/activity_test.exs b/test/pleroma/activity_test.exs
index ee6a99cc3..3e9fe209e 100644
--- a/test/pleroma/activity_test.exs
+++ b/test/pleroma/activity_test.exs
@@ -231,4 +231,20 @@ test "all_by_actor_and_id/2" do
assert [%Activity{id: ^id1}, %Activity{id: ^id2}] = activities
end
+
+ test "get_by_object_ap_id_with_object/1" do
+ user = insert(:user)
+ another = insert(:user)
+
+ {:ok, %{id: id, object: %{data: %{"id" => obj_id}}}} =
+ Pleroma.Web.CommonAPI.post(user, %{status: "cofe"})
+
+ Pleroma.Web.CommonAPI.favorite(another, id)
+
+ assert obj_id
+ |> Pleroma.Activity.Queries.by_object_id()
+ |> Repo.aggregate(:count, :id) == 2
+
+ assert %{id: ^id} = Activity.get_by_object_ap_id_with_object(obj_id)
+ end
end
diff --git a/test/pleroma/config/deprecation_warnings_test.exs b/test/pleroma/config/deprecation_warnings_test.exs
index 0cfed4555..f52629f8a 100644
--- a/test/pleroma/config/deprecation_warnings_test.exs
+++ b/test/pleroma/config/deprecation_warnings_test.exs
@@ -12,7 +12,7 @@ defmodule Pleroma.Config.DeprecationWarningsTest do
alias Pleroma.Config.DeprecationWarnings
test "check_old_mrf_config/0" do
- clear_config([:instance, :rewrite_policy], Pleroma.Web.ActivityPub.MRF.NoOpPolicy)
+ clear_config([:instance, :rewrite_policy], [])
clear_config([:instance, :mrf_transparency], true)
clear_config([:instance, :mrf_transparency_exclusions], [])
diff --git a/test/pleroma/frontend_test.exs b/test/pleroma/frontend_test.exs
new file mode 100644
index 000000000..223625857
--- /dev/null
+++ b/test/pleroma/frontend_test.exs
@@ -0,0 +1,72 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.FrontendTest do
+ use Pleroma.DataCase
+ alias Pleroma.Frontend
+
+ @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)
+
+ Frontend.install("pleroma")
+
+ 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" => ""
+ }
+ })
+
+ folder = Path.join([@dir, "frontends", "pleroma", "fantasy"])
+ previously_existing = Path.join([folder, "temp"])
+ File.mkdir_p!(folder)
+ File.write!(previously_existing, "yey")
+ assert File.exists?(previously_existing)
+
+ Frontend.install("pleroma", file: "test/fixtures/tesla_mock/frontend.zip")
+
+ assert File.exists?(Path.join([folder, "test.txt"]))
+ refute File.exists?(previously_existing)
+ 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)
+
+ Frontend.install("unknown",
+ ref: "baka",
+ build_url: "http://gensokyo.2hu/madeup.zip",
+ build_dir: ""
+ )
+
+ assert File.exists?(Path.join([@dir, "frontends", "unknown", "baka", "test.txt"]))
+ end
+end
diff --git a/test/pleroma/user_search_test.exs b/test/pleroma/user_search_test.exs
index 31d787ffa..de1df2e9c 100644
--- a/test/pleroma/user_search_test.exs
+++ b/test/pleroma/user_search_test.exs
@@ -65,12 +65,13 @@ test "excludes invisible users from results" do
assert found_user.id == user.id
end
- test "excludes users when discoverable is false" do
+ # Note: as in Mastodon, `is_discoverable` doesn't anyhow relate to user searchability
+ test "includes non-discoverable users in results" do
insert(:user, %{nickname: "john 3000", is_discoverable: false})
insert(:user, %{nickname: "john 3001"})
users = User.search("john")
- assert Enum.count(users) == 1
+ assert Enum.count(users) == 2
end
test "excludes service actors from results" do
diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
index b696a24f4..b577e25dd 100644
--- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
@@ -213,6 +213,23 @@ test "it returns a json representation of the activity with accept application/j
end
describe "/objects/:uuid" do
+ test "it doesn't return a local-only object", %{conn: conn} do
+ user = insert(:user)
+ {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
+
+ assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
+
+ object = Object.normalize(post, false)
+ uuid = String.split(object.data["id"], "/") |> List.last()
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> get("/objects/#{uuid}")
+
+ assert json_response(conn, 404)
+ end
+
test "it returns a json representation of the object with accept application/json", %{
conn: conn
} do
@@ -326,6 +343,22 @@ test "cached purged after object deletion", %{conn: conn} do
end
describe "/activities/:uuid" do
+ test "it doesn't return a local-only activity", %{conn: conn} do
+ user = insert(:user)
+ {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
+
+ assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
+
+ uuid = String.split(post.data["id"], "/") |> List.last()
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> get("/activities/#{uuid}")
+
+ assert json_response(conn, 404)
+ end
+
test "it returns a json representation of the activity", %{conn: conn} do
activity = insert(:note_activity)
uuid = String.split(activity.data["id"], "/") |> List.last()
@@ -766,6 +799,142 @@ test "it requires authentication", %{conn: conn} do
assert json_response(ret_conn, 200)
end
+
+ @tag capture_log: true
+ test "forwarded report", %{conn: conn} do
+ admin = insert(:user, is_admin: true)
+ actor = insert(:user, local: false)
+ remote_domain = URI.parse(actor.ap_id).host
+ reported_user = insert(:user)
+
+ note = insert(:note_activity, user: reported_user)
+
+ data = %{
+ "@context" => [
+ "https://www.w3.org/ns/activitystreams",
+ "https://#{remote_domain}/schemas/litepub-0.1.jsonld",
+ %{
+ "@language" => "und"
+ }
+ ],
+ "actor" => actor.ap_id,
+ "cc" => [
+ reported_user.ap_id
+ ],
+ "content" => "test",
+ "context" => "context",
+ "id" => "http://#{remote_domain}/activities/02be56cf-35e3-46b4-b2c6-47ae08dfee9e",
+ "nickname" => reported_user.nickname,
+ "object" => [
+ reported_user.ap_id,
+ %{
+ "actor" => %{
+ "actor_type" => "Person",
+ "approval_pending" => false,
+ "avatar" => "",
+ "confirmation_pending" => false,
+ "deactivated" => false,
+ "display_name" => "test user",
+ "id" => reported_user.id,
+ "local" => false,
+ "nickname" => reported_user.nickname,
+ "registration_reason" => nil,
+ "roles" => %{
+ "admin" => false,
+ "moderator" => false
+ },
+ "tags" => [],
+ "url" => reported_user.ap_id
+ },
+ "content" => "",
+ "id" => note.data["id"],
+ "published" => note.data["published"],
+ "type" => "Note"
+ }
+ ],
+ "published" => note.data["published"],
+ "state" => "open",
+ "to" => [],
+ "type" => "Flag"
+ }
+
+ conn
+ |> assign(:valid_signature, true)
+ |> put_req_header("content-type", "application/activity+json")
+ |> post("/users/#{reported_user.nickname}/inbox", data)
+ |> json_response(200)
+
+ ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
+
+ assert Pleroma.Repo.aggregate(Activity, :count, :id) == 2
+
+ ObanHelpers.perform_all()
+
+ Swoosh.TestAssertions.assert_email_sent(
+ to: {admin.name, admin.email},
+ html_body: ~r/Reported Account:/i
+ )
+ end
+
+ @tag capture_log: true
+ test "forwarded report from mastodon", %{conn: conn} do
+ admin = insert(:user, is_admin: true)
+ actor = insert(:user, local: false)
+ remote_domain = URI.parse(actor.ap_id).host
+ remote_actor = "https://#{remote_domain}/actor"
+ [reported_user, another] = insert_list(2, :user)
+
+ note = insert(:note_activity, user: reported_user)
+
+ Pleroma.Web.CommonAPI.favorite(another, note.id)
+
+ mock_json_body =
+ "test/fixtures/mastodon/application_actor.json"
+ |> File.read!()
+ |> String.replace("{{DOMAIN}}", remote_domain)
+
+ Tesla.Mock.mock(fn %{url: ^remote_actor} ->
+ %Tesla.Env{
+ status: 200,
+ body: mock_json_body,
+ headers: [{"content-type", "application/activity+json"}]
+ }
+ end)
+
+ data = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "actor" => remote_actor,
+ "content" => "test report",
+ "id" => "https://#{remote_domain}/e3b12fd1-948c-446e-b93b-a5e67edbe1d8",
+ "nickname" => reported_user.nickname,
+ "object" => [
+ reported_user.ap_id,
+ note.data["object"]
+ ],
+ "type" => "Flag"
+ }
+
+ conn
+ |> assign(:valid_signature, true)
+ |> put_req_header("content-type", "application/activity+json")
+ |> post("/users/#{reported_user.nickname}/inbox", data)
+ |> json_response(200)
+
+ ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
+
+ flag_activity = "Flag" |> Pleroma.Activity.Queries.by_type() |> Pleroma.Repo.one()
+ reported_user_ap_id = reported_user.ap_id
+
+ [^reported_user_ap_id, flag_data] = flag_activity.data["object"]
+
+ Enum.each(~w(actor content id published type), &Map.has_key?(flag_data, &1))
+ ObanHelpers.perform_all()
+
+ Swoosh.TestAssertions.assert_email_sent(
+ to: {admin.name, admin.email},
+ html_body: ~r/#{note.data["object"]}/i
+ )
+ end
end
describe "GET /users/:nickname/outbox" do
diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs
index 3eeb0f735..6cc25dd9e 100644
--- a/test/pleroma/web/activity_pub/activity_pub_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_test.exs
@@ -1298,6 +1298,31 @@ test "it can create a Flag activity",
assert_called(Utils.maybe_federate(%{activity | data: new_data}))
end
+
+ test_with_mock "reverts on error",
+ %{
+ reporter: reporter,
+ context: context,
+ target_account: target_account,
+ reported_activity: reported_activity,
+ content: content
+ },
+ Utils,
+ [:passthrough],
+ maybe_federate: fn _ -> {:error, :reverted} end do
+ assert {:error, :reverted} =
+ ActivityPub.flag(%{
+ actor: reporter,
+ context: context,
+ account: target_account,
+ statuses: [reported_activity],
+ content: content
+ })
+
+ assert Repo.aggregate(Activity, :count, :id) == 1
+ assert Repo.aggregate(Object, :count, :id) == 2
+ assert Repo.aggregate(Notification, :count, :id) == 0
+ end
end
test "fetch_activities/2 returns activities addressed to a list " do
diff --git a/test/pleroma/web/activity_pub/mrf/media_proxy_warming_policy_test.exs b/test/pleroma/web/activity_pub/mrf/media_proxy_warming_policy_test.exs
index 1710c4d2a..84362ce78 100644
--- a/test/pleroma/web/activity_pub/mrf/media_proxy_warming_policy_test.exs
+++ b/test/pleroma/web/activity_pub/mrf/media_proxy_warming_policy_test.exs
@@ -3,10 +3,10 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicyTest do
- use Pleroma.DataCase
+ use ExUnit.Case
+ use Pleroma.Tests.Helpers
alias Pleroma.HTTP
- alias Pleroma.Tests.ObanHelpers
alias Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy
import Mock
@@ -25,13 +25,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicyTest do
setup do: clear_config([:media_proxy, :enabled], true)
test "it prefetches media proxy URIs" do
+ Tesla.Mock.mock(fn %{method: :get, url: "http://example.com/image.jpg"} ->
+ {:ok, %Tesla.Env{status: 200, body: ""}}
+ end)
+
with_mock HTTP, get: fn _, _, _ -> {:ok, []} end do
MediaProxyWarmingPolicy.filter(@message)
- ObanHelpers.perform_all()
- # Performing jobs which has been just enqueued
- ObanHelpers.perform_all()
-
assert called(HTTP.get(:_, :_, :_))
end
end
diff --git a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs
new file mode 100644
index 000000000..b4a006aec
--- /dev/null
+++ b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs
@@ -0,0 +1,749 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
+ use Oban.Testing, repo: Pleroma.Repo
+ use Pleroma.DataCase
+
+ alias Pleroma.Activity
+ alias Pleroma.Object
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+ alias Pleroma.Web.CommonAPI
+
+ import Mock
+ import Pleroma.Factory
+ import ExUnit.CaptureLog
+
+ setup_all do
+ Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
+
+ setup do: clear_config([:instance, :max_remote_account_fields])
+
+ describe "handle_incoming" do
+ test "it works for incoming notices with tag not being an array (kroeg)" do
+ data = File.read!("test/fixtures/kroeg-array-less-emoji.json") |> Jason.decode!()
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+ object = Object.normalize(data["object"])
+
+ assert object.data["emoji"] == %{
+ "icon_e_smile" => "https://puckipedia.com/forum/images/smilies/icon_e_smile.png"
+ }
+
+ data = File.read!("test/fixtures/kroeg-array-less-hashtag.json") |> Jason.decode!()
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+ object = Object.normalize(data["object"])
+
+ assert "test" in object.data["tag"]
+ end
+
+ test "it cleans up incoming notices which are not really DMs" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ to = [user.ap_id, other_user.ap_id]
+
+ data =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Jason.decode!()
+ |> Map.put("to", to)
+ |> Map.put("cc", [])
+
+ object =
+ data["object"]
+ |> Map.put("to", to)
+ |> Map.put("cc", [])
+
+ data = Map.put(data, "object", object)
+
+ {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
+
+ assert data["to"] == []
+ assert data["cc"] == to
+
+ object_data = Object.normalize(activity).data
+
+ assert object_data["to"] == []
+ assert object_data["cc"] == to
+ end
+
+ test "it ignores an incoming notice if we already have it" do
+ activity = insert(:note_activity)
+
+ data =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Jason.decode!()
+ |> Map.put("object", Object.normalize(activity).data)
+
+ {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
+
+ assert activity == returned_activity
+ end
+
+ @tag capture_log: true
+ test "it fetches reply-to activities if we don't have them" do
+ data =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Jason.decode!()
+
+ object =
+ data["object"]
+ |> Map.put("inReplyTo", "https://mstdn.io/users/mayuutann/statuses/99568293732299394")
+
+ data = Map.put(data, "object", object)
+ {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
+ returned_object = Object.normalize(returned_activity, false)
+
+ assert %Activity{} =
+ Activity.get_create_by_object_ap_id(
+ "https://mstdn.io/users/mayuutann/statuses/99568293732299394"
+ )
+
+ assert returned_object.data["inReplyTo"] ==
+ "https://mstdn.io/users/mayuutann/statuses/99568293732299394"
+ end
+
+ test "it does not fetch reply-to activities beyond max replies depth limit" do
+ data =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Jason.decode!()
+
+ object =
+ data["object"]
+ |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
+
+ data = Map.put(data, "object", object)
+
+ with_mock Pleroma.Web.Federator,
+ allowed_thread_distance?: fn _ -> false end do
+ {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
+
+ returned_object = Object.normalize(returned_activity, false)
+
+ refute Activity.get_create_by_object_ap_id(
+ "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
+ )
+
+ assert returned_object.data["inReplyTo"] == "https://shitposter.club/notice/2827873"
+ end
+ end
+
+ test "it does not crash if the object in inReplyTo can't be fetched" do
+ data =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Jason.decode!()
+
+ object =
+ data["object"]
+ |> Map.put("inReplyTo", "https://404.site/whatever")
+
+ data =
+ data
+ |> Map.put("object", object)
+
+ assert capture_log(fn ->
+ {:ok, _returned_activity} = Transmogrifier.handle_incoming(data)
+ end) =~ "[warn] Couldn't fetch \"https://404.site/whatever\", error: nil"
+ end
+
+ test "it does not work for deactivated users" do
+ data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
+
+ insert(:user, ap_id: data["actor"], deactivated: true)
+
+ assert {:error, _} = Transmogrifier.handle_incoming(data)
+ end
+
+ test "it works for incoming notices" do
+ data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert data["id"] ==
+ "http://mastodon.example.org/users/admin/statuses/99512778738411822/activity"
+
+ assert data["context"] ==
+ "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
+
+ assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
+
+ assert data["cc"] == [
+ "http://mastodon.example.org/users/admin/followers",
+ "http://localtesting.pleroma.lol/users/lain"
+ ]
+
+ assert data["actor"] == "http://mastodon.example.org/users/admin"
+
+ object_data = Object.normalize(data["object"]).data
+
+ assert object_data["id"] ==
+ "http://mastodon.example.org/users/admin/statuses/99512778738411822"
+
+ assert object_data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
+
+ assert object_data["cc"] == [
+ "http://mastodon.example.org/users/admin/followers",
+ "http://localtesting.pleroma.lol/users/lain"
+ ]
+
+ assert object_data["actor"] == "http://mastodon.example.org/users/admin"
+ assert object_data["attributedTo"] == "http://mastodon.example.org/users/admin"
+
+ assert object_data["context"] ==
+ "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
+
+ assert object_data["sensitive"] == true
+
+ user = User.get_cached_by_ap_id(object_data["actor"])
+
+ assert user.note_count == 1
+ end
+
+ test "it works for incoming notices without the sensitive property but an nsfw hashtag" do
+ data = File.read!("test/fixtures/mastodon-post-activity-nsfw.json") |> Jason.decode!()
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ object_data = Object.normalize(data["object"], false).data
+
+ assert object_data["sensitive"] == true
+ end
+
+ test "it works for incoming notices with hashtags" do
+ data = File.read!("test/fixtures/mastodon-post-activity-hashtag.json") |> Jason.decode!()
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+ object = Object.normalize(data["object"])
+
+ assert Enum.at(object.data["tag"], 2) == "moo"
+ end
+
+ test "it works for incoming notices with contentMap" do
+ data = File.read!("test/fixtures/mastodon-post-activity-contentmap.json") |> Jason.decode!()
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+ object = Object.normalize(data["object"])
+
+ assert object.data["content"] ==
+ "@lain
"
+ end
+
+ test "it works for incoming notices with to/cc not being an array (kroeg)" do
+ data = File.read!("test/fixtures/kroeg-post-activity.json") |> Jason.decode!()
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+ object = Object.normalize(data["object"])
+
+ assert object.data["content"] ==
+ "henlo from my Psion netBook
message sent from my Psion netBook
"
+ end
+
+ test "it ensures that as:Public activities make it to their followers collection" do
+ user = insert(:user)
+
+ data =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Jason.decode!()
+ |> Map.put("actor", user.ap_id)
+ |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
+ |> Map.put("cc", [])
+
+ object =
+ data["object"]
+ |> Map.put("attributedTo", user.ap_id)
+ |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
+ |> Map.put("cc", [])
+ |> Map.put("id", user.ap_id <> "/activities/12345678")
+
+ data = Map.put(data, "object", object)
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert data["cc"] == [User.ap_followers(user)]
+ end
+
+ test "it ensures that address fields become lists" do
+ user = insert(:user)
+
+ data =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Jason.decode!()
+ |> Map.put("actor", user.ap_id)
+ |> Map.put("to", nil)
+ |> Map.put("cc", nil)
+
+ object =
+ data["object"]
+ |> Map.put("attributedTo", user.ap_id)
+ |> Map.put("to", nil)
+ |> Map.put("cc", nil)
+ |> Map.put("id", user.ap_id <> "/activities/12345678")
+
+ data = Map.put(data, "object", object)
+
+ {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+ assert !is_nil(data["to"])
+ assert !is_nil(data["cc"])
+ end
+
+ test "it strips internal likes" do
+ data =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Jason.decode!()
+
+ likes = %{
+ "first" =>
+ "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes?page=1",
+ "id" => "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes",
+ "totalItems" => 3,
+ "type" => "OrderedCollection"
+ }
+
+ object = Map.put(data["object"], "likes", likes)
+ data = Map.put(data, "object", object)
+
+ {:ok, %Activity{object: object}} = Transmogrifier.handle_incoming(data)
+
+ refute Map.has_key?(object.data, "likes")
+ end
+
+ test "it strips internal reactions" do
+ user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{status: "#cofe"})
+ {:ok, _} = CommonAPI.react_with_emoji(activity.id, user, "📢")
+
+ %{object: object} = Activity.get_by_id_with_object(activity.id)
+ assert Map.has_key?(object.data, "reactions")
+ assert Map.has_key?(object.data, "reaction_count")
+
+ object_data = Transmogrifier.strip_internal_fields(object.data)
+ refute Map.has_key?(object_data, "reactions")
+ refute Map.has_key?(object_data, "reaction_count")
+ end
+
+ test "it correctly processes messages with non-array to field" do
+ user = insert(:user)
+
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "to" => "https://www.w3.org/ns/activitystreams#Public",
+ "type" => "Create",
+ "object" => %{
+ "content" => "blah blah blah",
+ "type" => "Note",
+ "attributedTo" => user.ap_id,
+ "inReplyTo" => nil
+ },
+ "actor" => user.ap_id
+ }
+
+ assert {:ok, activity} = Transmogrifier.handle_incoming(message)
+
+ assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
+ end
+
+ test "it correctly processes messages with non-array cc field" do
+ user = insert(:user)
+
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "to" => user.follower_address,
+ "cc" => "https://www.w3.org/ns/activitystreams#Public",
+ "type" => "Create",
+ "object" => %{
+ "content" => "blah blah blah",
+ "type" => "Note",
+ "attributedTo" => user.ap_id,
+ "inReplyTo" => nil
+ },
+ "actor" => user.ap_id
+ }
+
+ assert {:ok, activity} = Transmogrifier.handle_incoming(message)
+
+ assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
+ assert [user.follower_address] == activity.data["to"]
+ end
+
+ test "it correctly processes messages with weirdness in address fields" do
+ user = insert(:user)
+
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "to" => [nil, user.follower_address],
+ "cc" => ["https://www.w3.org/ns/activitystreams#Public", ["¿"]],
+ "type" => "Create",
+ "object" => %{
+ "content" => "…",
+ "type" => "Note",
+ "attributedTo" => user.ap_id,
+ "inReplyTo" => nil
+ },
+ "actor" => user.ap_id
+ }
+
+ assert {:ok, activity} = Transmogrifier.handle_incoming(message)
+
+ assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
+ assert [user.follower_address] == activity.data["to"]
+ end
+ end
+
+ describe "`handle_incoming/2`, Mastodon format `replies` handling" do
+ setup do: clear_config([:activitypub, :note_replies_output_limit], 5)
+ setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
+
+ setup do
+ data =
+ "test/fixtures/mastodon-post-activity.json"
+ |> File.read!()
+ |> Jason.decode!()
+
+ items = get_in(data, ["object", "replies", "first", "items"])
+ assert length(items) > 0
+
+ %{data: data, items: items}
+ end
+
+ test "schedules background fetching of `replies` items if max thread depth limit allows", %{
+ data: data,
+ items: items
+ } do
+ Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 10)
+
+ {:ok, _activity} = Transmogrifier.handle_incoming(data)
+
+ for id <- items do
+ job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
+ assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
+ end
+ end
+
+ test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
+ %{data: data} do
+ Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
+
+ {:ok, _activity} = Transmogrifier.handle_incoming(data)
+
+ assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
+ end
+ end
+
+ describe "`handle_incoming/2`, Pleroma format `replies` handling" do
+ setup do: clear_config([:activitypub, :note_replies_output_limit], 5)
+ setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
+
+ setup do
+ user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{status: "post1"})
+
+ {:ok, reply1} =
+ CommonAPI.post(user, %{status: "reply1", in_reply_to_status_id: activity.id})
+
+ {:ok, reply2} =
+ CommonAPI.post(user, %{status: "reply2", in_reply_to_status_id: activity.id})
+
+ replies_uris = Enum.map([reply1, reply2], fn a -> a.object.data["id"] end)
+
+ {:ok, federation_output} = Transmogrifier.prepare_outgoing(activity.data)
+
+ Repo.delete(activity.object)
+ Repo.delete(activity)
+
+ %{federation_output: federation_output, replies_uris: replies_uris}
+ end
+
+ test "schedules background fetching of `replies` items if max thread depth limit allows", %{
+ federation_output: federation_output,
+ replies_uris: replies_uris
+ } do
+ Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 1)
+
+ {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
+
+ for id <- replies_uris do
+ job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
+ assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
+ end
+ end
+
+ test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
+ %{federation_output: federation_output} do
+ Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
+
+ {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
+
+ assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
+ end
+ end
+
+ describe "reserialization" do
+ test "successfully reserializes a message with inReplyTo == nil" do
+ user = insert(:user)
+
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => [],
+ "type" => "Create",
+ "object" => %{
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => [],
+ "type" => "Note",
+ "content" => "Hi",
+ "inReplyTo" => nil,
+ "attributedTo" => user.ap_id
+ },
+ "actor" => user.ap_id
+ }
+
+ {:ok, activity} = Transmogrifier.handle_incoming(message)
+
+ {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
+ end
+
+ test "successfully reserializes a message with AS2 objects in IR" do
+ user = insert(:user)
+
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => [],
+ "type" => "Create",
+ "object" => %{
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => [],
+ "type" => "Note",
+ "content" => "Hi",
+ "inReplyTo" => nil,
+ "attributedTo" => user.ap_id,
+ "tag" => [
+ %{"name" => "#2hu", "href" => "http://example.com/2hu", "type" => "Hashtag"},
+ %{"name" => "Bob", "href" => "http://example.com/bob", "type" => "Mention"}
+ ]
+ },
+ "actor" => user.ap_id
+ }
+
+ {:ok, activity} = Transmogrifier.handle_incoming(message)
+
+ {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
+ end
+ end
+
+ describe "fix_in_reply_to/2" do
+ setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
+
+ setup do
+ data = Jason.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
+ [data: data]
+ end
+
+ test "returns not modified object when hasn't containts inReplyTo field", %{data: data} do
+ assert Transmogrifier.fix_in_reply_to(data) == data
+ end
+
+ test "returns object with inReplyTo when denied incoming reply", %{data: data} do
+ Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
+
+ object_with_reply =
+ Map.put(data["object"], "inReplyTo", "https://shitposter.club/notice/2827873")
+
+ modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
+ assert modified_object["inReplyTo"] == "https://shitposter.club/notice/2827873"
+
+ object_with_reply =
+ Map.put(data["object"], "inReplyTo", %{"id" => "https://shitposter.club/notice/2827873"})
+
+ modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
+ assert modified_object["inReplyTo"] == %{"id" => "https://shitposter.club/notice/2827873"}
+
+ object_with_reply =
+ Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"])
+
+ modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
+ assert modified_object["inReplyTo"] == ["https://shitposter.club/notice/2827873"]
+
+ object_with_reply = Map.put(data["object"], "inReplyTo", [])
+ modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
+ assert modified_object["inReplyTo"] == []
+ end
+
+ @tag capture_log: true
+ test "returns modified object when allowed incoming reply", %{data: data} do
+ object_with_reply =
+ Map.put(
+ data["object"],
+ "inReplyTo",
+ "https://mstdn.io/users/mayuutann/statuses/99568293732299394"
+ )
+
+ Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 5)
+ modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
+
+ assert modified_object["inReplyTo"] ==
+ "https://mstdn.io/users/mayuutann/statuses/99568293732299394"
+
+ assert modified_object["context"] ==
+ "tag:shitposter.club,2018-02-22:objectType=thread:nonce=e5a7c72d60a9c0e4"
+ end
+ end
+
+ describe "fix_attachments/1" do
+ test "returns not modified object" do
+ data = Jason.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
+ assert Transmogrifier.fix_attachments(data) == data
+ end
+
+ test "returns modified object when attachment is map" do
+ assert Transmogrifier.fix_attachments(%{
+ "attachment" => %{
+ "mediaType" => "video/mp4",
+ "url" => "https://peertube.moe/stat-480.mp4"
+ }
+ }) == %{
+ "attachment" => [
+ %{
+ "mediaType" => "video/mp4",
+ "type" => "Document",
+ "url" => [
+ %{
+ "href" => "https://peertube.moe/stat-480.mp4",
+ "mediaType" => "video/mp4",
+ "type" => "Link"
+ }
+ ]
+ }
+ ]
+ }
+ end
+
+ test "returns modified object when attachment is list" do
+ assert Transmogrifier.fix_attachments(%{
+ "attachment" => [
+ %{"mediaType" => "video/mp4", "url" => "https://pe.er/stat-480.mp4"},
+ %{"mimeType" => "video/mp4", "href" => "https://pe.er/stat-480.mp4"}
+ ]
+ }) == %{
+ "attachment" => [
+ %{
+ "mediaType" => "video/mp4",
+ "type" => "Document",
+ "url" => [
+ %{
+ "href" => "https://pe.er/stat-480.mp4",
+ "mediaType" => "video/mp4",
+ "type" => "Link"
+ }
+ ]
+ },
+ %{
+ "mediaType" => "video/mp4",
+ "type" => "Document",
+ "url" => [
+ %{
+ "href" => "https://pe.er/stat-480.mp4",
+ "mediaType" => "video/mp4",
+ "type" => "Link"
+ }
+ ]
+ }
+ ]
+ }
+ end
+ end
+
+ describe "fix_emoji/1" do
+ test "returns not modified object when object not contains tags" do
+ data = Jason.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
+ assert Transmogrifier.fix_emoji(data) == data
+ end
+
+ test "returns object with emoji when object contains list tags" do
+ assert Transmogrifier.fix_emoji(%{
+ "tag" => [
+ %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}},
+ %{"type" => "Hashtag"}
+ ]
+ }) == %{
+ "emoji" => %{"bib" => "/test"},
+ "tag" => [
+ %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"},
+ %{"type" => "Hashtag"}
+ ]
+ }
+ end
+
+ test "returns object with emoji when object contains map tag" do
+ assert Transmogrifier.fix_emoji(%{
+ "tag" => %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}}
+ }) == %{
+ "emoji" => %{"bib" => "/test"},
+ "tag" => %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"}
+ }
+ end
+ end
+
+ describe "set_replies/1" do
+ setup do: clear_config([:activitypub, :note_replies_output_limit], 2)
+
+ test "returns unmodified object if activity doesn't have self-replies" do
+ data = Jason.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
+ assert Transmogrifier.set_replies(data) == data
+ end
+
+ test "sets `replies` collection with a limited number of self-replies" do
+ [user, another_user] = insert_list(2, :user)
+
+ {:ok, %{id: id1} = activity} = CommonAPI.post(user, %{status: "1"})
+
+ {:ok, %{id: id2} = self_reply1} =
+ CommonAPI.post(user, %{status: "self-reply 1", in_reply_to_status_id: id1})
+
+ {:ok, self_reply2} =
+ CommonAPI.post(user, %{status: "self-reply 2", in_reply_to_status_id: id1})
+
+ # Assuming to _not_ be present in `replies` due to :note_replies_output_limit is set to 2
+ {:ok, _} = CommonAPI.post(user, %{status: "self-reply 3", in_reply_to_status_id: id1})
+
+ {:ok, _} =
+ CommonAPI.post(user, %{
+ status: "self-reply to self-reply",
+ in_reply_to_status_id: id2
+ })
+
+ {:ok, _} =
+ CommonAPI.post(another_user, %{
+ status: "another user's reply",
+ in_reply_to_status_id: id1
+ })
+
+ object = Object.normalize(activity)
+ replies_uris = Enum.map([self_reply1, self_reply2], fn a -> a.object.data["id"] end)
+
+ assert %{"type" => "Collection", "items" => ^replies_uris} =
+ Transmogrifier.set_replies(object.data)["replies"]
+ end
+ end
+
+ test "take_emoji_tags/1" do
+ user = insert(:user, %{emoji: %{"firefox" => "https://example.org/firefox.png"}})
+
+ assert Transmogrifier.take_emoji_tags(user) == [
+ %{
+ "icon" => %{"type" => "Image", "url" => "https://example.org/firefox.png"},
+ "id" => "https://example.org/firefox.png",
+ "name" => ":firefox:",
+ "type" => "Emoji",
+ "updated" => "1970-01-01T00:00:00Z"
+ }
+ ]
+ end
+end
diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs
index e39af1dfc..333bb4f9b 100644
--- a/test/pleroma/web/activity_pub/transmogrifier_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs
@@ -26,310 +26,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
setup do: clear_config([:instance, :max_remote_account_fields])
describe "handle_incoming" do
- test "it works for incoming notices with tag not being an array (kroeg)" do
- data = File.read!("test/fixtures/kroeg-array-less-emoji.json") |> Poison.decode!()
-
- {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
- object = Object.normalize(data["object"])
-
- assert object.data["emoji"] == %{
- "icon_e_smile" => "https://puckipedia.com/forum/images/smilies/icon_e_smile.png"
- }
-
- data = File.read!("test/fixtures/kroeg-array-less-hashtag.json") |> Poison.decode!()
-
- {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
- object = Object.normalize(data["object"])
-
- assert "test" in object.data["tag"]
- end
-
- test "it cleans up incoming notices which are not really DMs" do
- user = insert(:user)
- other_user = insert(:user)
-
- to = [user.ap_id, other_user.ap_id]
-
- data =
- File.read!("test/fixtures/mastodon-post-activity.json")
- |> Poison.decode!()
- |> Map.put("to", to)
- |> Map.put("cc", [])
-
- object =
- data["object"]
- |> Map.put("to", to)
- |> Map.put("cc", [])
-
- data = Map.put(data, "object", object)
-
- {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
-
- assert data["to"] == []
- assert data["cc"] == to
-
- object_data = Object.normalize(activity).data
-
- assert object_data["to"] == []
- assert object_data["cc"] == to
- end
-
- test "it ignores an incoming notice if we already have it" do
- activity = insert(:note_activity)
-
- data =
- File.read!("test/fixtures/mastodon-post-activity.json")
- |> Poison.decode!()
- |> Map.put("object", Object.normalize(activity).data)
-
- {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
-
- assert activity == returned_activity
- end
-
- @tag capture_log: true
- test "it fetches reply-to activities if we don't have them" do
- data =
- File.read!("test/fixtures/mastodon-post-activity.json")
- |> Poison.decode!()
-
- object =
- data["object"]
- |> Map.put("inReplyTo", "https://mstdn.io/users/mayuutann/statuses/99568293732299394")
-
- data = Map.put(data, "object", object)
- {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
- returned_object = Object.normalize(returned_activity, false)
-
- assert %Activity{} =
- Activity.get_create_by_object_ap_id(
- "https://mstdn.io/users/mayuutann/statuses/99568293732299394"
- )
-
- assert returned_object.data["inReplyTo"] ==
- "https://mstdn.io/users/mayuutann/statuses/99568293732299394"
- end
-
- test "it does not fetch reply-to activities beyond max replies depth limit" do
- data =
- File.read!("test/fixtures/mastodon-post-activity.json")
- |> Poison.decode!()
-
- object =
- data["object"]
- |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
-
- data = Map.put(data, "object", object)
-
- with_mock Pleroma.Web.Federator,
- allowed_thread_distance?: fn _ -> false end do
- {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
-
- returned_object = Object.normalize(returned_activity, false)
-
- refute Activity.get_create_by_object_ap_id(
- "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
- )
-
- assert returned_object.data["inReplyTo"] == "https://shitposter.club/notice/2827873"
- end
- end
-
- test "it does not crash if the object in inReplyTo can't be fetched" do
- data =
- File.read!("test/fixtures/mastodon-post-activity.json")
- |> Poison.decode!()
-
- object =
- data["object"]
- |> Map.put("inReplyTo", "https://404.site/whatever")
-
- data =
- data
- |> Map.put("object", object)
-
- assert capture_log(fn ->
- {:ok, _returned_activity} = Transmogrifier.handle_incoming(data)
- end) =~ "[warn] Couldn't fetch \"https://404.site/whatever\", error: nil"
- end
-
- test "it does not work for deactivated users" do
- data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
-
- insert(:user, ap_id: data["actor"], deactivated: true)
-
- assert {:error, _} = Transmogrifier.handle_incoming(data)
- end
-
- test "it works for incoming notices" do
- data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
-
- {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
-
- assert data["id"] ==
- "http://mastodon.example.org/users/admin/statuses/99512778738411822/activity"
-
- assert data["context"] ==
- "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
-
- assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
-
- assert data["cc"] == [
- "http://mastodon.example.org/users/admin/followers",
- "http://localtesting.pleroma.lol/users/lain"
- ]
-
- assert data["actor"] == "http://mastodon.example.org/users/admin"
-
- object_data = Object.normalize(data["object"]).data
-
- assert object_data["id"] ==
- "http://mastodon.example.org/users/admin/statuses/99512778738411822"
-
- assert object_data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
-
- assert object_data["cc"] == [
- "http://mastodon.example.org/users/admin/followers",
- "http://localtesting.pleroma.lol/users/lain"
- ]
-
- assert object_data["actor"] == "http://mastodon.example.org/users/admin"
- assert object_data["attributedTo"] == "http://mastodon.example.org/users/admin"
-
- assert object_data["context"] ==
- "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
-
- assert object_data["sensitive"] == true
-
- user = User.get_cached_by_ap_id(object_data["actor"])
-
- assert user.note_count == 1
- end
-
- test "it works for incoming notices without the sensitive property but an nsfw hashtag" do
- data = File.read!("test/fixtures/mastodon-post-activity-nsfw.json") |> Poison.decode!()
-
- {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
-
- object_data = Object.normalize(data["object"], false).data
-
- assert object_data["sensitive"] == true
- end
-
- test "it works for incoming notices with hashtags" do
- data = File.read!("test/fixtures/mastodon-post-activity-hashtag.json") |> Poison.decode!()
-
- {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
- object = Object.normalize(data["object"])
-
- assert Enum.at(object.data["tag"], 2) == "moo"
- end
-
- test "it works for incoming notices with contentMap" do
- data =
- File.read!("test/fixtures/mastodon-post-activity-contentmap.json") |> Poison.decode!()
-
- {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
- object = Object.normalize(data["object"])
-
- assert object.data["content"] ==
- "@lain
"
- end
-
- test "it works for incoming notices with to/cc not being an array (kroeg)" do
- data = File.read!("test/fixtures/kroeg-post-activity.json") |> Poison.decode!()
-
- {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
- object = Object.normalize(data["object"])
-
- assert object.data["content"] ==
- "henlo from my Psion netBook
message sent from my Psion netBook
"
- end
-
- test "it ensures that as:Public activities make it to their followers collection" do
- user = insert(:user)
-
- data =
- File.read!("test/fixtures/mastodon-post-activity.json")
- |> Poison.decode!()
- |> Map.put("actor", user.ap_id)
- |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
- |> Map.put("cc", [])
-
- object =
- data["object"]
- |> Map.put("attributedTo", user.ap_id)
- |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
- |> Map.put("cc", [])
- |> Map.put("id", user.ap_id <> "/activities/12345678")
-
- data = Map.put(data, "object", object)
-
- {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
-
- assert data["cc"] == [User.ap_followers(user)]
- end
-
- test "it ensures that address fields become lists" do
- user = insert(:user)
-
- data =
- File.read!("test/fixtures/mastodon-post-activity.json")
- |> Poison.decode!()
- |> Map.put("actor", user.ap_id)
- |> Map.put("to", nil)
- |> Map.put("cc", nil)
-
- object =
- data["object"]
- |> Map.put("attributedTo", user.ap_id)
- |> Map.put("to", nil)
- |> Map.put("cc", nil)
- |> Map.put("id", user.ap_id <> "/activities/12345678")
-
- data = Map.put(data, "object", object)
-
- {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
-
- assert !is_nil(data["to"])
- assert !is_nil(data["cc"])
- end
-
- test "it strips internal likes" do
- data =
- File.read!("test/fixtures/mastodon-post-activity.json")
- |> Poison.decode!()
-
- likes = %{
- "first" =>
- "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes?page=1",
- "id" => "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes",
- "totalItems" => 3,
- "type" => "OrderedCollection"
- }
-
- object = Map.put(data["object"], "likes", likes)
- data = Map.put(data, "object", object)
-
- {:ok, %Activity{object: object}} = Transmogrifier.handle_incoming(data)
-
- refute Map.has_key?(object.data, "likes")
- end
-
- test "it strips internal reactions" do
- user = insert(:user)
- {:ok, activity} = CommonAPI.post(user, %{status: "#cofe"})
- {:ok, _} = CommonAPI.react_with_emoji(activity.id, user, "📢")
-
- %{object: object} = Activity.get_by_id_with_object(activity.id)
- assert Map.has_key?(object.data, "reactions")
- assert Map.has_key?(object.data, "reaction_count")
-
- object_data = Transmogrifier.strip_internal_fields(object.data)
- refute Map.has_key?(object_data, "reactions")
- refute Map.has_key?(object_data, "reaction_count")
- end
-
test "it works for incoming unfollows with an existing follow" do
user = insert(:user)
@@ -387,73 +83,6 @@ test "it accepts Flag activities" do
assert activity.data["cc"] == [user.ap_id]
end
- test "it correctly processes messages with non-array to field" do
- user = insert(:user)
-
- message = %{
- "@context" => "https://www.w3.org/ns/activitystreams",
- "to" => "https://www.w3.org/ns/activitystreams#Public",
- "type" => "Create",
- "object" => %{
- "content" => "blah blah blah",
- "type" => "Note",
- "attributedTo" => user.ap_id,
- "inReplyTo" => nil
- },
- "actor" => user.ap_id
- }
-
- assert {:ok, activity} = Transmogrifier.handle_incoming(message)
-
- assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
- end
-
- test "it correctly processes messages with non-array cc field" do
- user = insert(:user)
-
- message = %{
- "@context" => "https://www.w3.org/ns/activitystreams",
- "to" => user.follower_address,
- "cc" => "https://www.w3.org/ns/activitystreams#Public",
- "type" => "Create",
- "object" => %{
- "content" => "blah blah blah",
- "type" => "Note",
- "attributedTo" => user.ap_id,
- "inReplyTo" => nil
- },
- "actor" => user.ap_id
- }
-
- assert {:ok, activity} = Transmogrifier.handle_incoming(message)
-
- assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
- assert [user.follower_address] == activity.data["to"]
- end
-
- test "it correctly processes messages with weirdness in address fields" do
- user = insert(:user)
-
- message = %{
- "@context" => "https://www.w3.org/ns/activitystreams",
- "to" => [nil, user.follower_address],
- "cc" => ["https://www.w3.org/ns/activitystreams#Public", ["¿"]],
- "type" => "Create",
- "object" => %{
- "content" => "…",
- "type" => "Note",
- "attributedTo" => user.ap_id,
- "inReplyTo" => nil
- },
- "actor" => user.ap_id
- }
-
- assert {:ok, activity} = Transmogrifier.handle_incoming(message)
-
- assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
- assert [user.follower_address] == activity.data["to"]
- end
-
test "it accepts Move activities" do
old_user = insert(:user)
new_user = insert(:user)
@@ -479,95 +108,6 @@ test "it accepts Move activities" do
end
end
- describe "`handle_incoming/2`, Mastodon format `replies` handling" do
- setup do: clear_config([:activitypub, :note_replies_output_limit], 5)
- setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
-
- setup do
- data =
- "test/fixtures/mastodon-post-activity.json"
- |> File.read!()
- |> Poison.decode!()
-
- items = get_in(data, ["object", "replies", "first", "items"])
- assert length(items) > 0
-
- %{data: data, items: items}
- end
-
- test "schedules background fetching of `replies` items if max thread depth limit allows", %{
- data: data,
- items: items
- } do
- Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 10)
-
- {:ok, _activity} = Transmogrifier.handle_incoming(data)
-
- for id <- items do
- job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
- assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
- end
- end
-
- test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
- %{data: data} do
- Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
-
- {:ok, _activity} = Transmogrifier.handle_incoming(data)
-
- assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
- end
- end
-
- describe "`handle_incoming/2`, Pleroma format `replies` handling" do
- setup do: clear_config([:activitypub, :note_replies_output_limit], 5)
- setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
-
- setup do
- user = insert(:user)
-
- {:ok, activity} = CommonAPI.post(user, %{status: "post1"})
-
- {:ok, reply1} =
- CommonAPI.post(user, %{status: "reply1", in_reply_to_status_id: activity.id})
-
- {:ok, reply2} =
- CommonAPI.post(user, %{status: "reply2", in_reply_to_status_id: activity.id})
-
- replies_uris = Enum.map([reply1, reply2], fn a -> a.object.data["id"] end)
-
- {:ok, federation_output} = Transmogrifier.prepare_outgoing(activity.data)
-
- Repo.delete(activity.object)
- Repo.delete(activity)
-
- %{federation_output: federation_output, replies_uris: replies_uris}
- end
-
- test "schedules background fetching of `replies` items if max thread depth limit allows", %{
- federation_output: federation_output,
- replies_uris: replies_uris
- } do
- Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 1)
-
- {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
-
- for id <- replies_uris do
- job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
- assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
- end
- end
-
- test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
- %{federation_output: federation_output} do
- Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
-
- {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
-
- assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
- end
- end
-
describe "prepare outgoing" do
test "it inlines private announced objects" do
user = insert(:user)
@@ -864,60 +404,6 @@ test "it rejects activities which reference objects that have an incorrect attri
end
end
- describe "reserialization" do
- test "successfully reserializes a message with inReplyTo == nil" do
- user = insert(:user)
-
- message = %{
- "@context" => "https://www.w3.org/ns/activitystreams",
- "to" => ["https://www.w3.org/ns/activitystreams#Public"],
- "cc" => [],
- "type" => "Create",
- "object" => %{
- "to" => ["https://www.w3.org/ns/activitystreams#Public"],
- "cc" => [],
- "type" => "Note",
- "content" => "Hi",
- "inReplyTo" => nil,
- "attributedTo" => user.ap_id
- },
- "actor" => user.ap_id
- }
-
- {:ok, activity} = Transmogrifier.handle_incoming(message)
-
- {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
- end
-
- test "successfully reserializes a message with AS2 objects in IR" do
- user = insert(:user)
-
- message = %{
- "@context" => "https://www.w3.org/ns/activitystreams",
- "to" => ["https://www.w3.org/ns/activitystreams#Public"],
- "cc" => [],
- "type" => "Create",
- "object" => %{
- "to" => ["https://www.w3.org/ns/activitystreams#Public"],
- "cc" => [],
- "type" => "Note",
- "content" => "Hi",
- "inReplyTo" => nil,
- "attributedTo" => user.ap_id,
- "tag" => [
- %{"name" => "#2hu", "href" => "http://example.com/2hu", "type" => "Hashtag"},
- %{"name" => "Bob", "href" => "http://example.com/bob", "type" => "Mention"}
- ]
- },
- "actor" => user.ap_id
- }
-
- {:ok, activity} = Transmogrifier.handle_incoming(message)
-
- {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
- end
- end
-
describe "fix_explicit_addressing" do
setup do
user = insert(:user)
@@ -983,64 +469,6 @@ test "returns fixed object" do
end
end
- describe "fix_in_reply_to/2" do
- setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
-
- setup do
- data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
- [data: data]
- end
-
- test "returns not modified object when hasn't containts inReplyTo field", %{data: data} do
- assert Transmogrifier.fix_in_reply_to(data) == data
- end
-
- test "returns object with inReplyTo when denied incoming reply", %{data: data} do
- Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
-
- object_with_reply =
- Map.put(data["object"], "inReplyTo", "https://shitposter.club/notice/2827873")
-
- modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
- assert modified_object["inReplyTo"] == "https://shitposter.club/notice/2827873"
-
- object_with_reply =
- Map.put(data["object"], "inReplyTo", %{"id" => "https://shitposter.club/notice/2827873"})
-
- modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
- assert modified_object["inReplyTo"] == %{"id" => "https://shitposter.club/notice/2827873"}
-
- object_with_reply =
- Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"])
-
- modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
- assert modified_object["inReplyTo"] == ["https://shitposter.club/notice/2827873"]
-
- object_with_reply = Map.put(data["object"], "inReplyTo", [])
- modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
- assert modified_object["inReplyTo"] == []
- end
-
- @tag capture_log: true
- test "returns modified object when allowed incoming reply", %{data: data} do
- object_with_reply =
- Map.put(
- data["object"],
- "inReplyTo",
- "https://mstdn.io/users/mayuutann/statuses/99568293732299394"
- )
-
- Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 5)
- modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
-
- assert modified_object["inReplyTo"] ==
- "https://mstdn.io/users/mayuutann/statuses/99568293732299394"
-
- assert modified_object["context"] ==
- "tag:shitposter.club,2018-02-22:objectType=thread:nonce=e5a7c72d60a9c0e4"
- end
- end
-
describe "fix_url/1" do
test "fixes data for object when url is map" do
object = %{
@@ -1076,155 +504,4 @@ test "returns {:ok, %Object{}} for success case" do
)
end
end
-
- describe "fix_attachments/1" do
- test "returns not modified object" do
- data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
- assert Transmogrifier.fix_attachments(data) == data
- end
-
- test "returns modified object when attachment is map" do
- assert Transmogrifier.fix_attachments(%{
- "attachment" => %{
- "mediaType" => "video/mp4",
- "url" => "https://peertube.moe/stat-480.mp4"
- }
- }) == %{
- "attachment" => [
- %{
- "mediaType" => "video/mp4",
- "type" => "Document",
- "url" => [
- %{
- "href" => "https://peertube.moe/stat-480.mp4",
- "mediaType" => "video/mp4",
- "type" => "Link"
- }
- ]
- }
- ]
- }
- end
-
- test "returns modified object when attachment is list" do
- assert Transmogrifier.fix_attachments(%{
- "attachment" => [
- %{"mediaType" => "video/mp4", "url" => "https://pe.er/stat-480.mp4"},
- %{"mimeType" => "video/mp4", "href" => "https://pe.er/stat-480.mp4"}
- ]
- }) == %{
- "attachment" => [
- %{
- "mediaType" => "video/mp4",
- "type" => "Document",
- "url" => [
- %{
- "href" => "https://pe.er/stat-480.mp4",
- "mediaType" => "video/mp4",
- "type" => "Link"
- }
- ]
- },
- %{
- "mediaType" => "video/mp4",
- "type" => "Document",
- "url" => [
- %{
- "href" => "https://pe.er/stat-480.mp4",
- "mediaType" => "video/mp4",
- "type" => "Link"
- }
- ]
- }
- ]
- }
- end
- end
-
- describe "fix_emoji/1" do
- test "returns not modified object when object not contains tags" do
- data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
- assert Transmogrifier.fix_emoji(data) == data
- end
-
- test "returns object with emoji when object contains list tags" do
- assert Transmogrifier.fix_emoji(%{
- "tag" => [
- %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}},
- %{"type" => "Hashtag"}
- ]
- }) == %{
- "emoji" => %{"bib" => "/test"},
- "tag" => [
- %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"},
- %{"type" => "Hashtag"}
- ]
- }
- end
-
- test "returns object with emoji when object contains map tag" do
- assert Transmogrifier.fix_emoji(%{
- "tag" => %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}}
- }) == %{
- "emoji" => %{"bib" => "/test"},
- "tag" => %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"}
- }
- end
- end
-
- describe "set_replies/1" do
- setup do: clear_config([:activitypub, :note_replies_output_limit], 2)
-
- test "returns unmodified object if activity doesn't have self-replies" do
- data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
- assert Transmogrifier.set_replies(data) == data
- end
-
- test "sets `replies` collection with a limited number of self-replies" do
- [user, another_user] = insert_list(2, :user)
-
- {:ok, %{id: id1} = activity} = CommonAPI.post(user, %{status: "1"})
-
- {:ok, %{id: id2} = self_reply1} =
- CommonAPI.post(user, %{status: "self-reply 1", in_reply_to_status_id: id1})
-
- {:ok, self_reply2} =
- CommonAPI.post(user, %{status: "self-reply 2", in_reply_to_status_id: id1})
-
- # Assuming to _not_ be present in `replies` due to :note_replies_output_limit is set to 2
- {:ok, _} = CommonAPI.post(user, %{status: "self-reply 3", in_reply_to_status_id: id1})
-
- {:ok, _} =
- CommonAPI.post(user, %{
- status: "self-reply to self-reply",
- in_reply_to_status_id: id2
- })
-
- {:ok, _} =
- CommonAPI.post(another_user, %{
- status: "another user's reply",
- in_reply_to_status_id: id1
- })
-
- object = Object.normalize(activity)
- replies_uris = Enum.map([self_reply1, self_reply2], fn a -> a.object.data["id"] end)
-
- assert %{"type" => "Collection", "items" => ^replies_uris} =
- Transmogrifier.set_replies(object.data)["replies"]
- end
- end
-
- test "take_emoji_tags/1" do
- user = insert(:user, %{emoji: %{"firefox" => "https://example.org/firefox.png"}})
-
- assert Transmogrifier.take_emoji_tags(user) == [
- %{
- "icon" => %{"type" => "Image", "url" => "https://example.org/firefox.png"},
- "id" => "https://example.org/firefox.png",
- "name" => ":firefox:",
- "type" => "Emoji",
- "updated" => "1970-01-01T00:00:00Z"
- }
- ]
- end
end
diff --git a/test/pleroma/web/admin_api/controllers/frontend_controller_test.exs b/test/pleroma/web/admin_api/controllers/frontend_controller_test.exs
new file mode 100644
index 000000000..94873f6db
--- /dev/null
+++ b/test/pleroma/web/admin_api/controllers/frontend_controller_test.exs
@@ -0,0 +1,141 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.FrontendControllerTest do
+ use Pleroma.Web.ConnCase
+
+ import Pleroma.Factory
+
+ alias Pleroma.Config
+
+ @dir "test/frontend_static_test"
+
+ setup do
+ clear_config([:instance, :static_dir], @dir)
+ File.mkdir_p!(Pleroma.Frontend.dir())
+
+ on_exit(fn ->
+ File.rm_rf(@dir)
+ end)
+
+ admin = insert(:user, is_admin: true)
+ token = insert(:oauth_admin_token, user: admin)
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> assign(:token, token)
+
+ {:ok, %{admin: admin, token: token, conn: conn}}
+ end
+
+ describe "GET /api/pleroma/admin/frontends" do
+ test "it lists available frontends", %{conn: conn} do
+ response =
+ conn
+ |> get("/api/pleroma/admin/frontends")
+ |> json_response_and_validate_schema(:ok)
+
+ assert Enum.map(response, & &1["name"]) ==
+ Enum.map(Config.get([:frontends, :available]), fn {_, map} -> map["name"] end)
+
+ refute Enum.any?(response, fn frontend -> frontend["installed"] == true end)
+ end
+ end
+
+ describe "POST /api/pleroma/admin/frontends/install" do
+ test "from available frontends", %{conn: conn} 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)
+
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/pleroma/admin/frontends/install", %{name: "pleroma"})
+ |> json_response_and_validate_schema(:ok)
+
+ assert File.exists?(Path.join([@dir, "frontends", "pleroma", "fantasy", "test.txt"]))
+
+ response =
+ conn
+ |> get("/api/pleroma/admin/frontends")
+ |> json_response_and_validate_schema(:ok)
+
+ assert response == [
+ %{
+ "build_url" => "http://gensokyo.2hu/builds/${ref}",
+ "git" => nil,
+ "installed" => true,
+ "name" => "pleroma",
+ "ref" => "fantasy"
+ }
+ ]
+ end
+
+ test "from a file", %{conn: conn} do
+ clear_config([:frontends, :available], %{
+ "pleroma" => %{
+ "ref" => "fantasy",
+ "name" => "pleroma",
+ "build_dir" => ""
+ }
+ })
+
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/pleroma/admin/frontends/install", %{
+ name: "pleroma",
+ file: "test/fixtures/tesla_mock/frontend.zip"
+ })
+ |> json_response_and_validate_schema(:ok)
+
+ assert File.exists?(Path.join([@dir, "frontends", "pleroma", "fantasy", "test.txt"]))
+ end
+
+ test "from an URL", %{conn: conn} 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)
+
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/pleroma/admin/frontends/install", %{
+ name: "unknown",
+ ref: "baka",
+ build_url: "http://gensokyo.2hu/madeup.zip",
+ build_dir: ""
+ })
+ |> json_response_and_validate_schema(:ok)
+
+ assert File.exists?(Path.join([@dir, "frontends", "unknown", "baka", "test.txt"]))
+ end
+
+ test "failing returns an error", %{conn: conn} do
+ Tesla.Mock.mock(fn %{url: "http://gensokyo.2hu/madeup.zip"} ->
+ %Tesla.Env{status: 404, body: ""}
+ end)
+
+ result =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/pleroma/admin/frontends/install", %{
+ name: "unknown",
+ ref: "baka",
+ build_url: "http://gensokyo.2hu/madeup.zip",
+ build_dir: ""
+ })
+ |> json_response_and_validate_schema(400)
+
+ assert result == %{"error" => "Could not download or unzip the frontend"}
+ end
+ end
+end
diff --git a/test/pleroma/web/admin_api/search_test.exs b/test/pleroma/web/admin_api/search_test.exs
index 92a116c65..9bc58640c 100644
--- a/test/pleroma/web/admin_api/search_test.exs
+++ b/test/pleroma/web/admin_api/search_test.exs
@@ -203,6 +203,7 @@ test "it returns unconfirmed user" do
assert count == 1
end
+ # Note: as in Mastodon, `is_discoverable` doesn't anyhow relate to user searchability
test "it returns non-discoverable users" do
insert(:user)
insert(:user, is_discoverable: false)
diff --git a/test/pleroma/web/common_api/utils_test.exs b/test/pleroma/web/common_api/utils_test.exs
index e67c10b93..4d6c9ea26 100644
--- a/test/pleroma/web/common_api/utils_test.exs
+++ b/test/pleroma/web/common_api/utils_test.exs
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do
alias Pleroma.Builders.UserBuilder
alias Pleroma.Object
alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.CommonAPI.ActivityDraft
alias Pleroma.Web.CommonAPI.Utils
use Pleroma.DataCase
@@ -235,9 +236,9 @@ test "when date is a random string" do
test "for public posts, not a reply" do
user = insert(:user)
mentioned_user = insert(:user)
- mentions = [mentioned_user.ap_id]
+ draft = %ActivityDraft{user: user, mentions: [mentioned_user.ap_id], visibility: "public"}
- {to, cc} = Utils.get_to_and_cc(user, mentions, nil, "public", nil)
+ {to, cc} = Utils.get_to_and_cc(draft)
assert length(to) == 2
assert length(cc) == 1
@@ -252,9 +253,15 @@ test "for public posts, a reply" do
mentioned_user = insert(:user)
third_user = insert(:user)
{:ok, activity} = CommonAPI.post(third_user, %{status: "uguu"})
- mentions = [mentioned_user.ap_id]
- {to, cc} = Utils.get_to_and_cc(user, mentions, activity, "public", nil)
+ draft = %ActivityDraft{
+ user: user,
+ mentions: [mentioned_user.ap_id],
+ visibility: "public",
+ in_reply_to: activity
+ }
+
+ {to, cc} = Utils.get_to_and_cc(draft)
assert length(to) == 3
assert length(cc) == 1
@@ -268,9 +275,9 @@ test "for public posts, a reply" do
test "for unlisted posts, not a reply" do
user = insert(:user)
mentioned_user = insert(:user)
- mentions = [mentioned_user.ap_id]
+ draft = %ActivityDraft{user: user, mentions: [mentioned_user.ap_id], visibility: "unlisted"}
- {to, cc} = Utils.get_to_and_cc(user, mentions, nil, "unlisted", nil)
+ {to, cc} = Utils.get_to_and_cc(draft)
assert length(to) == 2
assert length(cc) == 1
@@ -285,9 +292,15 @@ test "for unlisted posts, a reply" do
mentioned_user = insert(:user)
third_user = insert(:user)
{:ok, activity} = CommonAPI.post(third_user, %{status: "uguu"})
- mentions = [mentioned_user.ap_id]
- {to, cc} = Utils.get_to_and_cc(user, mentions, activity, "unlisted", nil)
+ draft = %ActivityDraft{
+ user: user,
+ mentions: [mentioned_user.ap_id],
+ visibility: "unlisted",
+ in_reply_to: activity
+ }
+
+ {to, cc} = Utils.get_to_and_cc(draft)
assert length(to) == 3
assert length(cc) == 1
@@ -301,9 +314,9 @@ test "for unlisted posts, a reply" do
test "for private posts, not a reply" do
user = insert(:user)
mentioned_user = insert(:user)
- mentions = [mentioned_user.ap_id]
+ draft = %ActivityDraft{user: user, mentions: [mentioned_user.ap_id], visibility: "private"}
- {to, cc} = Utils.get_to_and_cc(user, mentions, nil, "private", nil)
+ {to, cc} = Utils.get_to_and_cc(draft)
assert length(to) == 2
assert Enum.empty?(cc)
@@ -316,9 +329,15 @@ test "for private posts, a reply" do
mentioned_user = insert(:user)
third_user = insert(:user)
{:ok, activity} = CommonAPI.post(third_user, %{status: "uguu"})
- mentions = [mentioned_user.ap_id]
- {to, cc} = Utils.get_to_and_cc(user, mentions, activity, "private", nil)
+ draft = %ActivityDraft{
+ user: user,
+ mentions: [mentioned_user.ap_id],
+ visibility: "private",
+ in_reply_to: activity
+ }
+
+ {to, cc} = Utils.get_to_and_cc(draft)
assert length(to) == 2
assert Enum.empty?(cc)
@@ -330,9 +349,9 @@ test "for private posts, a reply" do
test "for direct posts, not a reply" do
user = insert(:user)
mentioned_user = insert(:user)
- mentions = [mentioned_user.ap_id]
+ draft = %ActivityDraft{user: user, mentions: [mentioned_user.ap_id], visibility: "direct"}
- {to, cc} = Utils.get_to_and_cc(user, mentions, nil, "direct", nil)
+ {to, cc} = Utils.get_to_and_cc(draft)
assert length(to) == 1
assert Enum.empty?(cc)
@@ -345,9 +364,15 @@ test "for direct posts, a reply" do
mentioned_user = insert(:user)
third_user = insert(:user)
{:ok, activity} = CommonAPI.post(third_user, %{status: "uguu"})
- mentions = [mentioned_user.ap_id]
- {to, cc} = Utils.get_to_and_cc(user, mentions, activity, "direct", nil)
+ draft = %ActivityDraft{
+ user: user,
+ mentions: [mentioned_user.ap_id],
+ visibility: "direct",
+ in_reply_to: activity
+ }
+
+ {to, cc} = Utils.get_to_and_cc(draft)
assert length(to) == 1
assert Enum.empty?(cc)
@@ -356,7 +381,14 @@ test "for direct posts, a reply" do
{:ok, direct_activity} = CommonAPI.post(third_user, %{status: "uguu", visibility: "direct"})
- {to, cc} = Utils.get_to_and_cc(user, mentions, direct_activity, "direct", nil)
+ draft = %ActivityDraft{
+ user: user,
+ mentions: [mentioned_user.ap_id],
+ visibility: "direct",
+ in_reply_to: direct_activity
+ }
+
+ {to, cc} = Utils.get_to_and_cc(draft)
assert length(to) == 2
assert Enum.empty?(cc)
@@ -532,26 +564,26 @@ test "returns original params when list not found" do
end
end
- describe "make_note_data/11" do
+ describe "make_note_data/1" do
test "returns note data" do
user = insert(:user)
note = insert(:note)
user2 = insert(:user)
user3 = insert(:user)
- assert Utils.make_note_data(
- user.ap_id,
- [user2.ap_id],
- "2hu",
- "This is :moominmamma: note ",
- [],
- note.id,
- [name: "jimm"],
- "test summary",
- [user3.ap_id],
- false,
- %{"custom_tag" => "test"}
- ) == %{
+ draft = %ActivityDraft{
+ user: user,
+ to: [user2.ap_id],
+ context: "2hu",
+ content_html: "This is :moominmamma: note ",
+ in_reply_to: note.id,
+ tags: [name: "jimm"],
+ summary: "test summary",
+ cc: [user3.ap_id],
+ extra: %{"custom_tag" => "test"}
+ }
+
+ assert Utils.make_note_data(draft) == %{
"actor" => user.ap_id,
"attachment" => [],
"cc" => [user3.ap_id],
diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs
index 8e87e69fe..585b2c174 100644
--- a/test/pleroma/web/common_api_test.exs
+++ b/test/pleroma/web/common_api_test.exs
@@ -1277,4 +1277,128 @@ test "fallback" do
} = CommonAPI.get_user("")
end
end
+
+ describe "with `local` visibility" do
+ setup do: clear_config([:instance, :federating], true)
+
+ test "post" do
+ user = insert(:user)
+
+ with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
+ {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
+
+ assert Visibility.is_local_public?(activity)
+ assert_not_called(Pleroma.Web.Federator.publish(activity))
+ end
+ end
+
+ test "delete" do
+ user = insert(:user)
+
+ {:ok, %Activity{id: activity_id}} =
+ CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
+
+ with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
+ assert {:ok, %Activity{data: %{"deleted_activity_id" => ^activity_id}} = activity} =
+ CommonAPI.delete(activity_id, user)
+
+ assert Visibility.is_local_public?(activity)
+ assert_not_called(Pleroma.Web.Federator.publish(activity))
+ end
+ end
+
+ test "repeat" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, %Activity{id: activity_id}} =
+ CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
+
+ with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
+ assert {:ok, %Activity{data: %{"type" => "Announce"}} = activity} =
+ CommonAPI.repeat(activity_id, user)
+
+ assert Visibility.is_local_public?(activity)
+ refute called(Pleroma.Web.Federator.publish(activity))
+ end
+ end
+
+ test "unrepeat" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, %Activity{id: activity_id}} =
+ CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
+
+ assert {:ok, _} = CommonAPI.repeat(activity_id, user)
+
+ with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
+ assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
+ CommonAPI.unrepeat(activity_id, user)
+
+ assert Visibility.is_local_public?(activity)
+ refute called(Pleroma.Web.Federator.publish(activity))
+ end
+ end
+
+ test "favorite" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
+
+ with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
+ assert {:ok, %Activity{data: %{"type" => "Like"}} = activity} =
+ CommonAPI.favorite(user, activity.id)
+
+ assert Visibility.is_local_public?(activity)
+ refute called(Pleroma.Web.Federator.publish(activity))
+ end
+ end
+
+ test "unfavorite" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
+
+ {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
+
+ with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
+ assert {:ok, activity} = CommonAPI.unfavorite(activity.id, user)
+ assert Visibility.is_local_public?(activity)
+ refute called(Pleroma.Web.Federator.publish(activity))
+ end
+ end
+
+ test "react_with_emoji" do
+ user = insert(:user)
+ other_user = insert(:user)
+ {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
+
+ with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
+ assert {:ok, %Activity{data: %{"type" => "EmojiReact"}} = activity} =
+ CommonAPI.react_with_emoji(activity.id, user, "👍")
+
+ assert Visibility.is_local_public?(activity)
+ refute called(Pleroma.Web.Federator.publish(activity))
+ end
+ end
+
+ test "unreact_with_emoji" do
+ user = insert(:user)
+ other_user = insert(:user)
+ {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
+
+ {:ok, _reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
+
+ with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
+ assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
+ CommonAPI.unreact_with_emoji(activity.id, user, "👍")
+
+ assert Visibility.is_local_public?(activity)
+ refute called(Pleroma.Web.Federator.publish(activity))
+ end
+ end
+ end
end
diff --git a/test/pleroma/web/fed_sockets/fed_registry_test.exs b/test/pleroma/web/fed_sockets/fed_registry_test.exs
deleted file mode 100644
index 73aaced46..000000000
--- a/test/pleroma/web/fed_sockets/fed_registry_test.exs
+++ /dev/null
@@ -1,124 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.FedSockets.FedRegistryTest do
- use ExUnit.Case
-
- alias Pleroma.Web.FedSockets
- alias Pleroma.Web.FedSockets.FedRegistry
- alias Pleroma.Web.FedSockets.SocketInfo
-
- @good_domain "http://good.domain"
- @good_domain_origin "good.domain:80"
-
- setup do
- start_supervised({Pleroma.Web.FedSockets.Supervisor, []})
- build_test_socket(@good_domain)
- Process.sleep(10)
-
- :ok
- end
-
- describe "add_fed_socket/1 without conflicting sockets" do
- test "can be added" do
- Process.sleep(10)
- assert {:ok, %SocketInfo{origin: origin}} = FedRegistry.get_fed_socket(@good_domain_origin)
- assert origin == "good.domain:80"
- end
-
- test "multiple origins can be added" do
- build_test_socket("http://anothergood.domain")
- Process.sleep(10)
-
- assert {:ok, %SocketInfo{origin: origin_1}} =
- FedRegistry.get_fed_socket(@good_domain_origin)
-
- assert {:ok, %SocketInfo{origin: origin_2}} =
- FedRegistry.get_fed_socket("anothergood.domain:80")
-
- assert origin_1 == "good.domain:80"
- assert origin_2 == "anothergood.domain:80"
- assert FedRegistry.list_all() |> Enum.count() == 2
- end
- end
-
- describe "add_fed_socket/1 when duplicate sockets conflict" do
- setup do
- build_test_socket(@good_domain)
- build_test_socket(@good_domain)
- Process.sleep(10)
- :ok
- end
-
- test "will be ignored" do
- assert {:ok, %SocketInfo{origin: origin, pid: _pid_one}} =
- FedRegistry.get_fed_socket(@good_domain_origin)
-
- assert origin == "good.domain:80"
-
- assert FedRegistry.list_all() |> Enum.count() == 1
- end
-
- test "the newer process will be closed" do
- pid_two = build_test_socket(@good_domain)
-
- assert {:ok, %SocketInfo{origin: origin, pid: _pid_one}} =
- FedRegistry.get_fed_socket(@good_domain_origin)
-
- assert origin == "good.domain:80"
- Process.sleep(10)
-
- refute Process.alive?(pid_two)
-
- assert FedRegistry.list_all() |> Enum.count() == 1
- end
- end
-
- describe "get_fed_socket/1" do
- test "returns missing for unknown hosts" do
- assert {:error, :missing} = FedRegistry.get_fed_socket("not_a_dmoain")
- end
-
- test "returns rejected for hosts previously rejected" do
- "rejected.domain:80"
- |> FedSockets.uri_for_origin()
- |> FedRegistry.set_host_rejected()
-
- assert {:error, :rejected} = FedRegistry.get_fed_socket("rejected.domain:80")
- end
-
- test "can retrieve a previously added SocketInfo" do
- build_test_socket(@good_domain)
- Process.sleep(10)
- assert {:ok, %SocketInfo{origin: origin}} = FedRegistry.get_fed_socket(@good_domain_origin)
- assert origin == "good.domain:80"
- end
-
- test "removes references to SocketInfos when the process crashes" do
- assert {:ok, %SocketInfo{origin: origin, pid: pid}} =
- FedRegistry.get_fed_socket(@good_domain_origin)
-
- assert origin == "good.domain:80"
-
- Process.exit(pid, :testing)
- Process.sleep(100)
- assert {:error, :missing} = FedRegistry.get_fed_socket(@good_domain_origin)
- end
- end
-
- def build_test_socket(uri) do
- Kernel.spawn(fn -> fed_socket_almost(uri) end)
- end
-
- def fed_socket_almost(origin) do
- FedRegistry.add_fed_socket(origin)
-
- receive do
- :close ->
- :ok
- after
- 5_000 -> :timeout
- end
- end
-end
diff --git a/test/pleroma/web/fed_sockets/fetch_registry_test.exs b/test/pleroma/web/fed_sockets/fetch_registry_test.exs
deleted file mode 100644
index 7bd2d995a..000000000
--- a/test/pleroma/web/fed_sockets/fetch_registry_test.exs
+++ /dev/null
@@ -1,67 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.FedSockets.FetchRegistryTest do
- use ExUnit.Case
-
- alias Pleroma.Web.FedSockets.FetchRegistry
- alias Pleroma.Web.FedSockets.FetchRegistry.FetchRegistryData
-
- @json_message "hello"
- @json_reply "hello back"
-
- setup do
- start_supervised(
- {Pleroma.Web.FedSockets.Supervisor,
- [
- ping_interval: 8,
- connection_duration: 15,
- rejection_duration: 5,
- fed_socket_fetches: [default: 10, interval: 10]
- ]}
- )
-
- :ok
- end
-
- test "fetches can be stored" do
- uuid = FetchRegistry.register_fetch(@json_message)
-
- assert {:error, :waiting} = FetchRegistry.check_fetch(uuid)
- end
-
- test "fetches can return" do
- uuid = FetchRegistry.register_fetch(@json_message)
- task = Task.async(fn -> FetchRegistry.register_fetch_received(uuid, @json_reply) end)
-
- assert {:error, :waiting} = FetchRegistry.check_fetch(uuid)
- Task.await(task)
-
- assert {:ok, %FetchRegistryData{received_json: received_json}} =
- FetchRegistry.check_fetch(uuid)
-
- assert received_json == @json_reply
- end
-
- test "fetches are deleted once popped from stack" do
- uuid = FetchRegistry.register_fetch(@json_message)
- task = Task.async(fn -> FetchRegistry.register_fetch_received(uuid, @json_reply) end)
- Task.await(task)
-
- assert {:ok, %FetchRegistryData{received_json: received_json}} =
- FetchRegistry.check_fetch(uuid)
-
- assert received_json == @json_reply
- assert {:ok, @json_reply} = FetchRegistry.pop_fetch(uuid)
-
- assert {:error, :missing} = FetchRegistry.check_fetch(uuid)
- end
-
- test "fetches can time out" do
- uuid = FetchRegistry.register_fetch(@json_message)
- assert {:error, :waiting} = FetchRegistry.check_fetch(uuid)
- Process.sleep(500)
- assert {:error, :missing} = FetchRegistry.check_fetch(uuid)
- end
-end
diff --git a/test/pleroma/web/fed_sockets/socket_info_test.exs b/test/pleroma/web/fed_sockets/socket_info_test.exs
deleted file mode 100644
index db3d6edcd..000000000
--- a/test/pleroma/web/fed_sockets/socket_info_test.exs
+++ /dev/null
@@ -1,118 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.FedSockets.SocketInfoTest do
- use ExUnit.Case
-
- alias Pleroma.Web.FedSockets
- alias Pleroma.Web.FedSockets.SocketInfo
-
- describe "uri_for_origin" do
- test "provides the fed_socket URL given the origin information" do
- endpoint = "example.com:4000"
- assert FedSockets.uri_for_origin(endpoint) =~ "ws://"
- assert FedSockets.uri_for_origin(endpoint) =~ endpoint
- end
- end
-
- describe "origin" do
- test "will provide the origin field given a url" do
- endpoint = "example.com:4000"
- assert SocketInfo.origin("ws://#{endpoint}") == endpoint
- assert SocketInfo.origin("http://#{endpoint}") == endpoint
- assert SocketInfo.origin("https://#{endpoint}") == endpoint
- end
-
- test "will proide the origin field given a uri" do
- endpoint = "example.com:4000"
- uri = URI.parse("http://#{endpoint}")
-
- assert SocketInfo.origin(uri) == endpoint
- end
- end
-
- describe "touch" do
- test "will update the TTL" do
- endpoint = "example.com:4000"
- socket = SocketInfo.build("ws://#{endpoint}")
- Process.sleep(2)
- touched_socket = SocketInfo.touch(socket)
-
- assert socket.connected_until < touched_socket.connected_until
- end
- end
-
- describe "expired?" do
- setup do
- start_supervised(
- {Pleroma.Web.FedSockets.Supervisor,
- [
- ping_interval: 8,
- connection_duration: 5,
- rejection_duration: 5,
- fed_socket_rejections: [lazy: true]
- ]}
- )
-
- :ok
- end
-
- test "tests if the TTL is exceeded" do
- endpoint = "example.com:4000"
- socket = SocketInfo.build("ws://#{endpoint}")
- refute SocketInfo.expired?(socket)
- Process.sleep(10)
-
- assert SocketInfo.expired?(socket)
- end
- end
-
- describe "creating outgoing connection records" do
- test "can be passed a string" do
- assert %{conn_pid: :pid, origin: _origin} = SocketInfo.build("example.com:4000", :pid)
- end
-
- test "can be passed a URI" do
- uri = URI.parse("http://example.com:4000")
- assert %{conn_pid: :pid, origin: origin} = SocketInfo.build(uri, :pid)
- assert origin =~ "example.com:4000"
- end
-
- test "will include the port number" do
- assert %{conn_pid: :pid, origin: origin} = SocketInfo.build("http://example.com:4000", :pid)
-
- assert origin =~ ":4000"
- end
-
- test "will provide the port if missing" do
- assert %{conn_pid: :pid, origin: "example.com:80"} =
- SocketInfo.build("http://example.com", :pid)
-
- assert %{conn_pid: :pid, origin: "example.com:443"} =
- SocketInfo.build("https://example.com", :pid)
- end
- end
-
- describe "creating incoming connection records" do
- test "can be passed a string" do
- assert %{pid: _, origin: _origin} = SocketInfo.build("example.com:4000")
- end
-
- test "can be passed a URI" do
- uri = URI.parse("example.com:4000")
- assert %{pid: _, origin: _origin} = SocketInfo.build(uri)
- end
-
- test "will include the port number" do
- assert %{pid: _, origin: origin} = SocketInfo.build("http://example.com:4000")
-
- assert origin =~ ":4000"
- end
-
- test "will provide the port if missing" do
- assert %{pid: _, origin: "example.com:80"} = SocketInfo.build("http://example.com")
- assert %{pid: _, origin: "example.com:443"} = SocketInfo.build("https://example.com")
- end
- end
-end
diff --git a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs
index 58ce76ab8..e8a00dd6b 100644
--- a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs
@@ -436,6 +436,39 @@ test "the user views their own timelines and excludes direct messages", %{
conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?exclude_visibilities[]=direct")
assert [%{"id" => ^public_activity_id}] = json_response_and_validate_schema(conn, 200)
end
+
+ test "muted reactions", %{user: user, conn: conn} do
+ user2 = insert(:user)
+ User.mute(user, user2)
+ {:ok, activity} = CommonAPI.post(user, %{status: "."})
+ {:ok, _} = CommonAPI.react_with_emoji(activity.id, user2, "🎅")
+
+ result =
+ conn
+ |> get("/api/v1/accounts/#{user.id}/statuses")
+ |> json_response_and_validate_schema(200)
+
+ assert [
+ %{
+ "pleroma" => %{
+ "emoji_reactions" => []
+ }
+ }
+ ] = result
+
+ result =
+ conn
+ |> get("/api/v1/accounts/#{user.id}/statuses?with_muted=true")
+ |> json_response_and_validate_schema(200)
+
+ assert [
+ %{
+ "pleroma" => %{
+ "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
+ }
+ }
+ ] = result
+ end
end
defp local_and_remote_activities(%{local: local, remote: remote}) do
diff --git a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs
index 6a9ccd979..605df6ed6 100644
--- a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs
@@ -13,6 +13,9 @@ test "get instance information", %{conn: conn} do
assert result = json_response_and_validate_schema(conn, 200)
email = Pleroma.Config.get([:instance, :email])
+ thumbnail = Pleroma.Web.base_url() <> Pleroma.Config.get([:instance, :instance_thumbnail])
+ background = Pleroma.Web.base_url() <> Pleroma.Config.get([:instance, :background_image])
+
# Note: not checking for "max_toot_chars" since it's optional
assert %{
"uri" => _,
@@ -24,7 +27,7 @@ test "get instance information", %{conn: conn} do
"streaming_api" => _
},
"stats" => _,
- "thumbnail" => _,
+ "thumbnail" => from_config_thumbnail,
"languages" => _,
"registrations" => _,
"approval_required" => _,
@@ -33,7 +36,7 @@ test "get instance information", %{conn: conn} do
"avatar_upload_limit" => _,
"background_upload_limit" => _,
"banner_upload_limit" => _,
- "background_image" => _,
+ "background_image" => from_config_background,
"chat_limit" => _,
"description_limit" => _
} = result
@@ -45,6 +48,8 @@ test "get instance information", %{conn: conn} do
assert result["pleroma"]["vapid_public_key"]
assert email == from_config_email
+ assert thumbnail == from_config_thumbnail
+ assert background == from_config_background
end
test "get instance stats", %{conn: conn} do
diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
index 436608e51..30d542dfa 100644
--- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
@@ -328,7 +328,7 @@ test "fake statuses' preview card is not cached", %{conn: conn} do
end
test "posting a status with OGP link preview", %{conn: conn} do
- Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
clear_config([:rich_media, :enabled], true)
conn =
@@ -1197,7 +1197,7 @@ test "on pin removes deletion job, on unpin reschedule deletion" do
end
test "returns rich-media card", %{conn: conn, user: user} do
- Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
{:ok, activity} = CommonAPI.post(user, %{status: "https://example.com/ogp"})
@@ -1242,7 +1242,7 @@ test "returns rich-media card", %{conn: conn, user: user} do
end
test "replaces missing description with an empty string", %{conn: conn, user: user} do
- Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
{:ok, activity} = CommonAPI.post(user, %{status: "https://example.com/ogp-missing-data"})
@@ -1740,4 +1740,94 @@ test "expires_at is nil for another user" do
|> get("/api/v1/statuses/#{activity.id}")
|> json_response_and_validate_schema(:ok)
end
+
+ test "posting a local only status" do
+ %{user: _user, conn: conn} = oauth_access(["write:statuses"])
+
+ conn_one =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/statuses", %{
+ "status" => "cofe",
+ "visibility" => "local"
+ })
+
+ local = Pleroma.Constants.as_local_public()
+
+ assert %{"content" => "cofe", "id" => id, "visibility" => "local"} =
+ json_response(conn_one, 200)
+
+ assert %Activity{id: ^id, data: %{"to" => [^local]}} = Activity.get_by_id(id)
+ end
+
+ describe "muted reactions" do
+ test "index" do
+ %{conn: conn, user: user} = oauth_access(["read:statuses"])
+
+ other_user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{status: "test"})
+
+ {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
+ User.mute(user, other_user)
+
+ result =
+ conn
+ |> get("/api/v1/statuses/?ids[]=#{activity.id}")
+ |> json_response_and_validate_schema(200)
+
+ assert [
+ %{
+ "pleroma" => %{
+ "emoji_reactions" => []
+ }
+ }
+ ] = result
+
+ result =
+ conn
+ |> get("/api/v1/statuses/?ids[]=#{activity.id}&with_muted=true")
+ |> json_response_and_validate_schema(200)
+
+ assert [
+ %{
+ "pleroma" => %{
+ "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
+ }
+ }
+ ] = result
+ end
+
+ test "show" do
+ # %{conn: conn, user: user, token: token} = oauth_access(["read:statuses"])
+ %{conn: conn, user: user, token: _token} = oauth_access(["read:statuses"])
+
+ other_user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{status: "test"})
+
+ {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
+ User.mute(user, other_user)
+
+ result =
+ conn
+ |> get("/api/v1/statuses/#{activity.id}")
+ |> json_response_and_validate_schema(200)
+
+ assert %{
+ "pleroma" => %{
+ "emoji_reactions" => []
+ }
+ } = result
+
+ result =
+ conn
+ |> get("/api/v1/statuses/#{activity.id}?with_muted=true")
+ |> json_response_and_validate_schema(200)
+
+ assert %{
+ "pleroma" => %{
+ "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
+ }
+ } = result
+ end
+ end
end
diff --git a/test/pleroma/web/mastodon_api/controllers/subscription_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/subscription_controller_test.exs
index d36bb1ae8..5ef39bdb2 100644
--- a/test/pleroma/web/mastodon_api/controllers/subscription_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/subscription_controller_test.exs
@@ -45,21 +45,77 @@ defmacro assert_error_when_disable_push(do: yield) do
end
end
- describe "creates push subscription" do
- test "returns error when push disabled ", %{conn: conn} do
+ describe "when disabled" do
+ test "POST returns error", %{conn: conn} do
assert_error_when_disable_push do
conn
- |> post("/api/v1/push/subscription", %{subscription: @sub})
+ |> post("/api/v1/push/subscription", %{
+ "data" => %{"alerts" => %{"mention" => true}},
+ "subscription" => @sub
+ })
|> json_response_and_validate_schema(403)
end
end
+ test "GET returns error", %{conn: conn} do
+ assert_error_when_disable_push do
+ conn
+ |> get("/api/v1/push/subscription", %{})
+ |> json_response_and_validate_schema(403)
+ end
+ end
+
+ test "PUT returns error", %{conn: conn} do
+ assert_error_when_disable_push do
+ conn
+ |> put("/api/v1/push/subscription", %{data: %{"alerts" => %{"mention" => false}}})
+ |> json_response_and_validate_schema(403)
+ end
+ end
+
+ test "DELETE returns error", %{conn: conn} do
+ assert_error_when_disable_push do
+ conn
+ |> delete("/api/v1/push/subscription", %{})
+ |> json_response_and_validate_schema(403)
+ end
+ end
+ end
+
+ describe "creates push subscription" do
+ test "ignores unsupported types", %{conn: conn} do
+ result =
+ conn
+ |> post("/api/v1/push/subscription", %{
+ "data" => %{
+ "alerts" => %{
+ "fake_unsupported_type" => true
+ }
+ },
+ "subscription" => @sub
+ })
+ |> json_response_and_validate_schema(200)
+
+ refute %{
+ "alerts" => %{
+ "fake_unsupported_type" => true
+ }
+ } == result
+ end
+
test "successful creation", %{conn: conn} do
result =
conn
|> post("/api/v1/push/subscription", %{
"data" => %{
- "alerts" => %{"mention" => true, "test" => true, "pleroma:chat_mention" => true}
+ "alerts" => %{
+ "mention" => true,
+ "favourite" => true,
+ "follow" => true,
+ "reblog" => true,
+ "pleroma:chat_mention" => true,
+ "pleroma:emoji_reaction" => true
+ }
},
"subscription" => @sub
})
@@ -68,7 +124,14 @@ test "successful creation", %{conn: conn} do
[subscription] = Pleroma.Repo.all(Subscription)
assert %{
- "alerts" => %{"mention" => true, "pleroma:chat_mention" => true},
+ "alerts" => %{
+ "mention" => true,
+ "favourite" => true,
+ "follow" => true,
+ "reblog" => true,
+ "pleroma:chat_mention" => true,
+ "pleroma:emoji_reaction" => true
+ },
"endpoint" => subscription.endpoint,
"id" => to_string(subscription.id),
"server_key" => @server_key
@@ -77,14 +140,6 @@ test "successful creation", %{conn: conn} do
end
describe "gets a user subscription" do
- test "returns error when push disabled ", %{conn: conn} do
- assert_error_when_disable_push do
- conn
- |> get("/api/v1/push/subscription", %{})
- |> json_response_and_validate_schema(403)
- end
- end
-
test "returns error when user hasn't subscription", %{conn: conn} do
res =
conn
@@ -124,30 +179,47 @@ test "returns a user subsciption", %{conn: conn, user: user, token: token} do
insert(:push_subscription,
user: user,
token: token,
- data: %{"alerts" => %{"mention" => true}}
+ data: %{
+ "alerts" => %{
+ "mention" => true,
+ "favourite" => true,
+ "follow" => true,
+ "reblog" => true,
+ "pleroma:chat_mention" => true,
+ "pleroma:emoji_reaction" => true
+ }
+ }
)
%{conn: conn, user: user, token: token, subscription: subscription}
end
- test "returns error when push disabled ", %{conn: conn} do
- assert_error_when_disable_push do
- conn
- |> put("/api/v1/push/subscription", %{data: %{"alerts" => %{"mention" => false}}})
- |> json_response_and_validate_schema(403)
- end
- end
-
test "returns updated subsciption", %{conn: conn, subscription: subscription} do
res =
conn
|> put("/api/v1/push/subscription", %{
- data: %{"alerts" => %{"mention" => false, "follow" => true}}
+ data: %{
+ "alerts" => %{
+ "mention" => false,
+ "favourite" => false,
+ "follow" => false,
+ "reblog" => false,
+ "pleroma:chat_mention" => false,
+ "pleroma:emoji_reaction" => false
+ }
+ }
})
|> json_response_and_validate_schema(200)
expect = %{
- "alerts" => %{"follow" => true, "mention" => false},
+ "alerts" => %{
+ "mention" => false,
+ "favourite" => false,
+ "follow" => false,
+ "reblog" => false,
+ "pleroma:chat_mention" => false,
+ "pleroma:emoji_reaction" => false
+ },
"endpoint" => "https://example.com/example/1234",
"id" => to_string(subscription.id),
"server_key" => @server_key
@@ -158,14 +230,6 @@ test "returns updated subsciption", %{conn: conn, subscription: subscription} do
end
describe "deletes the user subscription" do
- test "returns error when push disabled ", %{conn: conn} do
- assert_error_when_disable_push do
- conn
- |> delete("/api/v1/push/subscription", %{})
- |> json_response_and_validate_schema(403)
- end
- end
-
test "returns error when user hasn't subscription", %{conn: conn} do
res =
conn
diff --git a/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs
index 4c08ad60a..8356b64d3 100644
--- a/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs
@@ -54,6 +54,42 @@ test "the home timeline when the direct messages are excluded", %{user: user, co
assert private_activity.id in status_ids
refute direct_activity.id in status_ids
end
+
+ test "muted emotions", %{user: user, conn: conn} do
+ other_user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{status: "."})
+
+ {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
+ User.mute(user, other_user)
+
+ result =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/timelines/home")
+ |> json_response_and_validate_schema(200)
+
+ assert [
+ %{
+ "pleroma" => %{
+ "emoji_reactions" => []
+ }
+ }
+ ] = result
+
+ result =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/timelines/home?with_muted=true")
+ |> json_response_and_validate_schema(200)
+
+ assert [
+ %{
+ "pleroma" => %{
+ "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
+ }
+ }
+ ] = result
+ end
end
describe "public" do
@@ -159,6 +195,48 @@ test "can be filtered by instance", %{conn: conn} do
assert length(json_response_and_validate_schema(conn, :ok)) == 1
end
+
+ test "muted emotions", %{conn: conn} do
+ user = insert(:user)
+ token = insert(:oauth_token, user: user, scopes: ["read:statuses"])
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> assign(:token, token)
+
+ other_user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{status: "."})
+
+ {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
+ User.mute(user, other_user)
+
+ result =
+ conn
+ |> get("/api/v1/timelines/public")
+ |> json_response_and_validate_schema(200)
+
+ assert [
+ %{
+ "pleroma" => %{
+ "emoji_reactions" => []
+ }
+ }
+ ] = result
+
+ result =
+ conn
+ |> get("/api/v1/timelines/public?with_muted=true")
+ |> json_response_and_validate_schema(200)
+
+ assert [
+ %{
+ "pleroma" => %{
+ "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
+ }
+ }
+ ] = result
+ end
end
defp local_and_remote_activities do
@@ -428,6 +506,44 @@ test "list timeline does not leak non-public statuses for unfollowed users", %{
assert id == to_string(activity_one.id)
end
+
+ test "muted emotions", %{user: user, conn: conn} do
+ user2 = insert(:user)
+ user3 = insert(:user)
+ {:ok, activity} = CommonAPI.post(user2, %{status: "."})
+
+ {:ok, _} = CommonAPI.react_with_emoji(activity.id, user3, "🎅")
+ User.mute(user, user3)
+
+ {:ok, list} = Pleroma.List.create("name", user)
+ {:ok, list} = Pleroma.List.follow(list, user2)
+
+ result =
+ conn
+ |> get("/api/v1/timelines/list/#{list.id}")
+ |> json_response_and_validate_schema(200)
+
+ assert [
+ %{
+ "pleroma" => %{
+ "emoji_reactions" => []
+ }
+ }
+ ] = result
+
+ result =
+ conn
+ |> get("/api/v1/timelines/list/#{list.id}?with_muted=true")
+ |> json_response_and_validate_schema(200)
+
+ assert [
+ %{
+ "pleroma" => %{
+ "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
+ }
+ }
+ ] = result
+ end
end
describe "hashtag" do
@@ -476,6 +592,48 @@ test "multi-hashtag timeline", %{conn: conn} do
assert [status_none] == json_response_and_validate_schema(all_test, :ok)
end
+
+ test "muted emotions", %{conn: conn} do
+ user = insert(:user)
+ token = insert(:oauth_token, user: user, scopes: ["read:statuses"])
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> assign(:token, token)
+
+ other_user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{status: "test #2hu"})
+
+ {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
+ User.mute(user, other_user)
+
+ result =
+ conn
+ |> get("/api/v1/timelines/tag/2hu")
+ |> json_response_and_validate_schema(200)
+
+ assert [
+ %{
+ "pleroma" => %{
+ "emoji_reactions" => []
+ }
+ }
+ ] = result
+
+ result =
+ conn
+ |> get("/api/v1/timelines/tag/2hu?with_muted=true")
+ |> json_response_and_validate_schema(200)
+
+ assert [
+ %{
+ "pleroma" => %{
+ "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
+ }
+ }
+ ] = result
+ end
end
describe "hashtag timeline handling of :restrict_unauthenticated setting" do
diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs
index 665199f97..f2a7469ed 100644
--- a/test/pleroma/web/mastodon_api/views/status_view_test.exs
+++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs
@@ -73,6 +73,50 @@ test "works correctly with badly formatted emojis" do
]
end
+ test "doesn't show reactions from muted and blocked users" do
+ user = insert(:user)
+ other_user = insert(:user)
+ third_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{status: "dae cofe??"})
+
+ {:ok, _} = User.mute(user, other_user)
+ {:ok, _} = User.block(other_user, third_user)
+
+ {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
+
+ activity = Repo.get(Activity, activity.id)
+ status = StatusView.render("show.json", activity: activity)
+
+ assert status[:pleroma][:emoji_reactions] == [
+ %{name: "☕", count: 1, me: false}
+ ]
+
+ status = StatusView.render("show.json", activity: activity, for: user)
+
+ assert status[:pleroma][:emoji_reactions] == []
+
+ {:ok, _} = CommonAPI.react_with_emoji(activity.id, third_user, "☕")
+
+ status = StatusView.render("show.json", activity: activity)
+
+ assert status[:pleroma][:emoji_reactions] == [
+ %{name: "☕", count: 2, me: false}
+ ]
+
+ status = StatusView.render("show.json", activity: activity, for: user)
+
+ assert status[:pleroma][:emoji_reactions] == [
+ %{name: "☕", count: 1, me: false}
+ ]
+
+ status = StatusView.render("show.json", activity: activity, for: other_user)
+
+ assert status[:pleroma][:emoji_reactions] == [
+ %{name: "☕", count: 1, me: true}
+ ]
+ end
+
test "loads and returns the direct conversation id when given the `with_direct_conversation_id` option" do
user = insert(:user)
diff --git a/test/pleroma/web/metadata/providers/restrict_indexing_test.exs b/test/pleroma/web/metadata/providers/restrict_indexing_test.exs
index 282d132c8..52399fdc8 100644
--- a/test/pleroma/web/metadata/providers/restrict_indexing_test.exs
+++ b/test/pleroma/web/metadata/providers/restrict_indexing_test.exs
@@ -18,7 +18,7 @@ test "for local user" do
}) == []
end
- test "for local user when discoverable is false" do
+ test "for local user when `is_discoverable` is false" do
assert Pleroma.Web.Metadata.Providers.RestrictIndexing.build_tags(%{
user: %Pleroma.User{local: true, is_discoverable: false}
}) == [{:meta, [name: "robots", content: "noindex, noarchive"], []}]
diff --git a/test/pleroma/web/metadata_test.exs b/test/pleroma/web/metadata_test.exs
deleted file mode 100644
index 8fb946540..000000000
--- a/test/pleroma/web/metadata_test.exs
+++ /dev/null
@@ -1,49 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.MetadataTest do
- use Pleroma.DataCase, async: true
-
- import Pleroma.Factory
-
- describe "restrict indexing remote users" do
- test "for remote user" do
- user = insert(:user, local: false)
-
- assert Pleroma.Web.Metadata.build_tags(%{user: user}) =~
- " "
- end
-
- test "for local user" do
- user = insert(:user, is_discoverable: false)
-
- assert Pleroma.Web.Metadata.build_tags(%{user: user}) =~
- " "
- end
-
- test "for local user set to discoverable" do
- user = insert(:user, is_discoverable: true)
-
- refute Pleroma.Web.Metadata.build_tags(%{user: user}) =~
- " "
- end
- end
-
- describe "no metadata for private instances" do
- test "for local user set to discoverable" do
- clear_config([:instance, :public], false)
- user = insert(:user, bio: "This is my secret fedi account bio", is_discoverable: true)
-
- assert "" = Pleroma.Web.Metadata.build_tags(%{user: user})
- end
-
- test "search exclusion metadata is included" do
- clear_config([:instance, :public], false)
- user = insert(:user, bio: "This is my secret fedi account bio", is_discoverable: false)
-
- assert ~s( ) ==
- Pleroma.Web.Metadata.build_tags(%{user: user})
- end
- end
-end
diff --git a/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs
index 3deab30d1..bda9c20c6 100644
--- a/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs
+++ b/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs
@@ -106,6 +106,48 @@ test "GET /api/v1/pleroma/statuses/:id/reactions", %{conn: conn} do
result
end
+ test "GET /api/v1/pleroma/statuses/:id/reactions?with_muted=true", %{conn: conn} do
+ user = insert(:user)
+ user2 = insert(:user)
+ user3 = insert(:user)
+
+ token = insert(:oauth_token, user: user, scopes: ["read:statuses"])
+
+ {:ok, activity} = CommonAPI.post(user, %{status: "#cofe"})
+
+ {:ok, _} = CommonAPI.react_with_emoji(activity.id, user2, "🎅")
+ {:ok, _} = CommonAPI.react_with_emoji(activity.id, user3, "🎅")
+
+ result =
+ conn
+ |> assign(:user, user)
+ |> assign(:token, token)
+ |> get("/api/v1/pleroma/statuses/#{activity.id}/reactions")
+ |> json_response_and_validate_schema(200)
+
+ assert [%{"name" => "🎅", "count" => 2}] = result
+
+ User.mute(user, user3)
+
+ result =
+ conn
+ |> assign(:user, user)
+ |> assign(:token, token)
+ |> get("/api/v1/pleroma/statuses/#{activity.id}/reactions")
+ |> json_response_and_validate_schema(200)
+
+ assert [%{"name" => "🎅", "count" => 1}] = result
+
+ result =
+ conn
+ |> assign(:user, user)
+ |> assign(:token, token)
+ |> get("/api/v1/pleroma/statuses/#{activity.id}/reactions?with_muted=true")
+ |> json_response_and_validate_schema(200)
+
+ assert [%{"name" => "🎅", "count" => 2}] = result
+ end
+
test "GET /api/v1/pleroma/statuses/:id/reactions with :show_reactions disabled", %{conn: conn} do
clear_config([:instance, :show_reactions], false)
diff --git a/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs b/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs
index ae8257870..93eef00a2 100644
--- a/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs
+++ b/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs
@@ -48,7 +48,7 @@ test "it displays a chat message" do
clear_config([:rich_media, :enabled], true)
- Tesla.Mock.mock(fn
+ Tesla.Mock.mock_global(fn
%{url: "https://example.com/ogp"} ->
%Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/ogp.html")}
end)
diff --git a/test/pleroma/web/push/impl_test.exs b/test/pleroma/web/push/impl_test.exs
index 7d8cc999a..2a4a8fd06 100644
--- a/test/pleroma/web/push/impl_test.exs
+++ b/test/pleroma/web/push/impl_test.exs
@@ -184,6 +184,24 @@ test "renders title and body for like activity" do
"New Favorite"
end
+ test "renders title and body for pleroma:emoji_reaction activity" do
+ user = insert(:user, nickname: "Bob")
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ status: "This post is a really good post!"
+ })
+
+ {:ok, activity} = CommonAPI.react_with_emoji(activity.id, user, "👍")
+ object = Object.normalize(activity)
+
+ assert Impl.format_body(%{activity: activity, type: "pleroma:emoji_reaction"}, user, object) ==
+ "@Bob reacted with 👍"
+
+ assert Impl.format_title(%{activity: activity, type: "pleroma:emoji_reaction"}) ==
+ "New Reaction"
+ end
+
test "renders title for create activity with direct visibility" do
user = insert(:user, nickname: "Bob")
diff --git a/test/pleroma/web/twitter_api/password_controller_test.exs b/test/pleroma/web/twitter_api/password_controller_test.exs
index a5e9e2178..6d08075cc 100644
--- a/test/pleroma/web/twitter_api/password_controller_test.exs
+++ b/test/pleroma/web/twitter_api/password_controller_test.exs
@@ -31,9 +31,48 @@ test "it shows password reset form", %{conn: conn} do
assert response =~ "Password Reset for #{user.nickname} "
end
+
+ test "it returns an error when the token has expired", %{conn: conn} do
+ clear_config([:instance, :password_reset_token_validity], 0)
+
+ user = insert(:user)
+ {:ok, token} = PasswordResetToken.create_token(user)
+
+ :timer.sleep(2000)
+
+ response =
+ conn
+ |> get("/api/pleroma/password_reset/#{token.token}")
+ |> html_response(:ok)
+
+ assert response =~ "Invalid Token "
+ end
end
describe "POST /api/pleroma/password_reset" do
+ test "it fails for an expired token", %{conn: conn} do
+ clear_config([:instance, :password_reset_token_validity], 0)
+
+ user = insert(:user)
+ {:ok, token} = PasswordResetToken.create_token(user)
+ :timer.sleep(2000)
+ {:ok, _access_token} = Token.create(insert(:oauth_app), user, %{})
+
+ params = %{
+ "password" => "test",
+ password_confirmation: "test",
+ token: token.token
+ }
+
+ response =
+ conn
+ |> assign(:user, user)
+ |> post("/api/pleroma/password_reset", %{data: params})
+ |> html_response(:ok)
+
+ refute response =~ "Password changed! "
+ end
+
test "it returns HTTP 200", %{conn: conn} do
user = insert(:user)
{:ok, token} = PasswordResetToken.create_token(user)
diff --git a/test/support/factory.ex b/test/support/factory.ex
index 80b882ee4..8eb07dc3c 100644
--- a/test/support/factory.ex
+++ b/test/support/factory.ex
@@ -24,7 +24,7 @@ def conversation_factory do
}
end
- def user_factory do
+ def user_factory(attrs \\ %{}) do
user = %User{
name: sequence(:name, &"Test テスト User #{&1}"),
email: sequence(:email, &"user#{&1}@example.com"),
@@ -39,13 +39,29 @@ def user_factory do
ap_enabled: true
}
- %{
- user
- | ap_id: User.ap_id(user),
- follower_address: User.ap_followers(user),
- following_address: User.ap_following(user),
- raw_bio: user.bio
- }
+ urls =
+ if attrs[:local] == false do
+ base_domain = Enum.random(["domain1.com", "domain2.com", "domain3.com"])
+
+ ap_id = "https://#{base_domain}/users/#{user.nickname}"
+
+ %{
+ ap_id: ap_id,
+ follower_address: ap_id <> "/followers",
+ following_address: ap_id <> "/following"
+ }
+ else
+ %{
+ ap_id: User.ap_id(user),
+ follower_address: User.ap_followers(user),
+ following_address: User.ap_following(user)
+ }
+ end
+
+ user
+ |> Map.put(:raw_bio, user.bio)
+ |> Map.merge(urls)
+ |> merge_attributes(attrs)
end
def user_relationship_factory(attrs \\ %{}) do