Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma into develop

This commit is contained in:
sadposter 2021-08-30 22:04:42 +01:00
commit b202cf15d6
314 changed files with 1943 additions and 494 deletions

View file

@ -8,7 +8,39 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Changed ### Changed
### Added
### Fixed
- Subscription(Bell) Notifications: Don't create from Pipeline Ingested replies
### Removed
## 2.4.1 - 2021-08-29
### Changed
- Make `mix pleroma.database set_text_search_config` run concurrently and indefinitely
### Added
- AdminAPI: Missing configuration description for StealEmojiPolicy
### Fixed
- MastodonAPI: Stream out Create activities
- MRF ObjectAgePolicy: Fix pattern matching on "published"
- TwitterAPI: Make `change_password` and `change_email` require params on body instead of query
- Subscription(Bell) Notifications: Don't create from Pipeline Ingested replies
- AdminAPI: Fix rendering reports containing a `nil` object
- Mastodon API: Activity Search fallbacks on status fetching after a DB Timeout/Error
- Mastodon API: Fix crash in Streamer related to reblogging
- AdminAPI: List available frontends when `static/frontends` folder is missing
- Make activity search properly use language-aware GIN indexes
- AdminAPI: Fix suggestions for MRF Policies
## 2.4.0 - 2021-08-08
### Changed
- **Breaking:** Configuration: `:chat, enabled` moved to `:shout, enabled` and `:instance, chat_limit` moved to `:shout, limit` - **Breaking:** Configuration: `:chat, enabled` moved to `:shout, enabled` and `:instance, chat_limit` moved to `:shout, limit`
- **Breaking** Entries for simple_policy, transparency_exclusions and quarantined_instances now list both the instance and a reason.
- Support for Erlang/OTP 24 - Support for Erlang/OTP 24
- The `application` metadata returned with statuses is no longer hardcoded. Apps that want to display these details will now have valid data for new posts after this change. - The `application` metadata returned with statuses is no longer hardcoded. Apps that want to display these details will now have valid data for new posts after this change.
- HTTPSecurityPlug now sends a response header to opt out of Google's FLoC (Federated Learning of Cohorts) targeted advertising. - HTTPSecurityPlug now sends a response header to opt out of Google's FLoC (Federated Learning of Cohorts) targeted advertising.
@ -24,6 +56,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- AdminAPI: return `created_at` date with users. - AdminAPI: return `created_at` date with users.
- `AnalyzeMetadata` upload filter for extracting image/video attachment dimensions and generating blurhashes for images. Blurhashes for videos are not generated at this time. - `AnalyzeMetadata` upload filter for extracting image/video attachment dimensions and generating blurhashes for images. Blurhashes for videos are not generated at this time.
- Attachment dimensions and blurhashes are federated when available. - Attachment dimensions and blurhashes are federated when available.
- Mastodon API: support `poll` notification.
- Pinned posts federation - Pinned posts federation
### Fixed ### Fixed
@ -34,14 +67,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Mix task `pleroma.database prune_objects` - Mix task `pleroma.database prune_objects`
- Fixed rendering of JSON errors on ActivityPub endpoints. - Fixed rendering of JSON errors on ActivityPub endpoints.
- Linkify: Parsing crash with URLs ending in unbalanced closed paren, no path separator, and no query parameters - Linkify: Parsing crash with URLs ending in unbalanced closed paren, no path separator, and no query parameters
### Removed
- **Breaking**: Remove deprecated `/api/qvitter/statuses/notifications/read` (replaced by `/api/v1/pleroma/notifications/read`)
## Unreleased (Patch)
### Fixed
- Try to save exported ConfigDB settings (migrate_from_db) in the system temp directory if default location is not writable. - Try to save exported ConfigDB settings (migrate_from_db) in the system temp directory if default location is not writable.
- Uploading custom instance thumbnail via AdminAPI/AdminFE generated invalid URL to the image - Uploading custom instance thumbnail via AdminAPI/AdminFE generated invalid URL to the image
- Applying ConcurrentLimiter settings via AdminAPI - Applying ConcurrentLimiter settings via AdminAPI
@ -50,7 +75,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- MRF (`SimplePolicy`): Embedded objects are now checked. If any embedded object would be rejected, its parent is rejected. This fixes Announces leaking posts from blocked domains. - MRF (`SimplePolicy`): Embedded objects are now checked. If any embedded object would be rejected, its parent is rejected. This fixes Announces leaking posts from blocked domains.
- Fixed some Markdown issues, including trailing slash in links. - Fixed some Markdown issues, including trailing slash in links.
## [2.3.0] - 2020-03-01 ### Removed
- **Breaking**: Remove deprecated `/api/qvitter/statuses/notifications/read` (replaced by `/api/v1/pleroma/notifications/read`)
## [2.3.0] - 2021-03-01
### Security ### Security
@ -151,7 +179,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Mastodon API: Support for expires_in/expires_at in the Filters. - Mastodon API: Support for expires_in/expires_at in the Filters.
</details> </details>
## [2.2.2] - 2020-01-18 ## [2.2.2] - 2021-01-18
### Fixed ### Fixed

View file

@ -30,7 +30,7 @@ If your platform is not supported, or you just want to be able to edit the sourc
- [OpenBSD (fi)](https://docs-develop.pleroma.social/backend/installation/openbsd_fi/) - [OpenBSD (fi)](https://docs-develop.pleroma.social/backend/installation/openbsd_fi/)
### OS/Distro packages ### OS/Distro packages
Currently Pleroma is not packaged by any OS/Distros, but if you want to package it for one, we can guide you through the process on our [community channels](#community-channels). If you want to change default options in your Pleroma package, please **discuss it with us first**. Currently Pleroma is packaged for [YunoHost](https://yunohost.org). If you want to package Pleroma for any OS/Distros, we can guide you through the process on our [community channels](#community-channels). If you want to change default options in your Pleroma package, please **discuss it with us first**.
### Docker ### Docker
While we dont provide docker files, other people have written very good ones. Take a look at <https://github.com/angristan/docker-pleroma> or <https://glitch.sh/sn0w/pleroma-docker>. While we dont provide docker files, other people have written very good ones. Take a look at <https://github.com/angristan/docker-pleroma> or <https://glitch.sh/sn0w/pleroma-docker>.

View file

@ -563,6 +563,7 @@
mailer: 10, mailer: 10,
transmogrifier: 20, transmogrifier: 20,
scheduled_activities: 10, scheduled_activities: 10,
poll_notifications: 10,
background: 5, background: 5,
remote_fetcher: 2, remote_fetcher: 2,
attachments_cleanup: 1, attachments_cleanup: 1,

View file

@ -687,12 +687,14 @@
}, },
%{ %{
key: :quarantined_instances, key: :quarantined_instances,
type: {:list, :string}, type: {:list, :tuple},
key_placeholder: "instance",
value_placeholder: "reason",
description: description:
"List of ActivityPub instances where private (DMs, followers-only) activities will not be sent", "List of ActivityPub instances where private (DMs, followers-only) activities will not be sent and the reason for doing so",
suggestions: [ suggestions: [
"quarantined.com", {"quarantined.com", "Reason"},
"*.quarantined.com" {"*.quarantined.com", "Reason"}
] ]
}, },
%{ %{

View file

@ -62,6 +62,7 @@
import_config "dev.secret.exs" import_config "dev.secret.exs"
else else
IO.puts( IO.puts(
:stderr,
"!!! RUNNING IN LOCALHOST DEV MODE! !!!\nFEDERATION WON'T WORK UNTIL YOU CONFIGURE A dev.secret.exs" "!!! RUNNING IN LOCALHOST DEV MODE! !!!\nFEDERATION WON'T WORK UNTIL YOU CONFIGURE A dev.secret.exs"
) )
end end

View file

@ -39,7 +39,7 @@ To add configuration to your config file, you can copy it from the base config.
* `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it. * `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it.
* `allow_relay`: Permits remote instances to subscribe to all public posts of your instance. This may increase the visibility of your instance. * `allow_relay`: Permits remote instances to subscribe to all public posts of your instance. This may increase the visibility of your instance.
* `public`: Makes the client API in authenticated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network. Note that there is a dependent setting restricting or allowing unauthenticated access to specific resources, see `restrict_unauthenticated` for more details. * `public`: Makes the client API in authenticated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network. Note that there is a dependent setting restricting or allowing unauthenticated access to specific resources, see `restrict_unauthenticated` for more details.
* `quarantined_instances`: List of ActivityPub instances where private (DMs, followers-only) activities will not be send. * `quarantined_instances`: ActivityPub instances where private (DMs, followers-only) activities will not be send.
* `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML). * `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML).
* `extended_nickname_format`: Set to `true` to use extended local nicknames format (allows underscores/dashes). This will break federation with * `extended_nickname_format`: Set to `true` to use extended local nicknames format (allows underscores/dashes). This will break federation with
older software for theses nicknames. older software for theses nicknames.
@ -135,15 +135,16 @@ To add configuration to your config file, you can copy it from the base config.
Configuring MRF policies is not enough for them to take effect. You have to enable them by specifying their module in `policies` under [:mrf](#mrf) section. Configuring MRF policies is not enough for them to take effect. You have to enable them by specifying their module in `policies` under [:mrf](#mrf) section.
#### :mrf_simple #### :mrf_simple
* `media_removal`: List of instances to remove media from. * `media_removal`: List of instances to strip media attachments from and the reason for doing so.
* `media_nsfw`: List of instances to put media as NSFW(sensitive) from. * `media_nsfw`: List of instances to tag all media as NSFW (sensitive) from and the reason for doing so.
* `federated_timeline_removal`: List of instances to remove from Federated (aka The Whole Known Network) Timeline. * `federated_timeline_removal`: List of instances to remove from the Federated Timeline (aka The Whole Known Network) and the reason for doing so.
* `reject`: List of instances to reject any activities from. * `reject`: List of instances to reject activities (except deletes) from and the reason for doing so.
* `accept`: List of instances to accept any activities from. * `accept`: List of instances to only accept activities (except deletes) from and the reason for doing so.
* `followers_only`: List of instances to decrease post visibility to only the followers, including for DM mentions. * `followers_only`: Force posts from the given instances to be visible by followers only and the reason for doing so.
* `report_removal`: List of instances to reject reports from. * `report_removal`: List of instances to reject reports from and the reason for doing so.
* `avatar_removal`: List of instances to strip avatars from. * `avatar_removal`: List of instances to strip avatars from and the reason for doing so.
* `banner_removal`: List of instances to strip banners from. * `banner_removal`: List of instances to strip banners from and the reason for doing so.
* `reject_deletes`: List of instances to reject deletions from and the reason for doing so.
#### :mrf_subchain #### :mrf_subchain
This policy processes messages through an alternate pipeline when a given message matches certain criteria. This policy processes messages through an alternate pipeline when a given message matches certain criteria.

View file

@ -5,7 +5,7 @@ Pleroma's full text search feature is powered by PostgreSQL's native [text searc
## Setup and test the new search config ## Setup and test the new search config
In most cases, you would need an extension installed to support parsing CJK text. Here are a few extension you may choose from, or you are more than welcome to share additional ones you found working for you with the rest of Pleroma community. In most cases, you would need an extension installed to support parsing CJK text. Here are a few extensions you may choose from, or you are more than welcome to share additional ones you found working for you with the rest of Pleroma community.
* [a generic n-gram parser](https://github.com/huangjimmy/pg_cjk_parser) supports Simplifed/Traditional Chinese, Japanese, and Korean * [a generic n-gram parser](https://github.com/huangjimmy/pg_cjk_parser) supports Simplifed/Traditional Chinese, Japanese, and Korean
* [a Korean parser](https://github.com/i0seph/textsearch_ko) based on mecab * [a Korean parser](https://github.com/i0seph/textsearch_ko) based on mecab
@ -34,7 +34,7 @@ Check output of the query, and see if it matches your expectation.
mix pleroma.database set_text_search_config YOUR.CONFIG mix pleroma.database set_text_search_config YOUR.CONFIG
``` ```
Note: index update may take a while. Note: index update may take a while, and it can be done while the instance is up and running, so you may restart db connection as soon as you see `Recreate index` in task output.
## Restart database connection ## Restart database connection
Since some changes above will only apply with a new database connection, you will have to restart either Pleroma or PostgreSQL process, or use `pg_terminate_backend` SQL command without restarting either. Since some changes above will only apply with a new database connection, you will have to restart either Pleroma or PostgreSQL process, or use `pg_terminate_backend` SQL command without restarting either.

View file

@ -55,18 +55,18 @@ Servers should be configured as lists.
### Example ### Example
This example will enable `SimplePolicy`, block media from `illegalporn.biz`, mark media as NSFW from `porn.biz` and `porn.business`, reject messages from `spam.com`, remove messages from `spam.university` from the federated timeline and block reports (flags) from `whiny.whiner`: This example will enable `SimplePolicy`, block media from `illegalporn.biz`, mark media as NSFW from `porn.biz` and `porn.business`, reject messages from `spam.com`, remove messages from `spam.university` from the federated timeline and block reports (flags) from `whiny.whiner`. We also give a reason why the moderation was done:
```elixir ```elixir
config :pleroma, :mrf, config :pleroma, :mrf,
policies: [Pleroma.Web.ActivityPub.MRF.SimplePolicy] policies: [Pleroma.Web.ActivityPub.MRF.SimplePolicy]
config :pleroma, :mrf_simple, config :pleroma, :mrf_simple,
media_removal: ["illegalporn.biz"], media_removal: [{"illegalporn.biz", "Media can contain illegal contant"}],
media_nsfw: ["porn.biz", "porn.business"], media_nsfw: [{"porn.biz", "unmarked nsfw media"}, {"porn.business", "A lot of unmarked nsfw media"}],
reject: ["spam.com"], reject: [{"spam.com", "They keep spamming our users"}],
federated_timeline_removal: ["spam.university"], federated_timeline_removal: [{"spam.university", "Annoying low-quality posts who otherwise fill up TWKN"}],
report_removal: ["whiny.whiner"] report_removal: [{"whiny.whiner", "Keep spamming us with irrelevant reports"}]
``` ```
### Use with Care ### Use with Care

View file

@ -1,4 +1,7 @@
# Installing on Alpine Linux # Installing on Alpine Linux
{! backend/installation/otp_vs_from_source_source.include !}
## Installation ## Installation
This guide is a step-by-step installation guide for Alpine Linux. The instructions were verified against Alpine v3.10 standard image. You might miss additional dependencies if you use `netboot` instead. This guide is a step-by-step installation guide for Alpine Linux. The instructions were verified against Alpine v3.10 standard image. You might miss additional dependencies if you use `netboot` instead.

View file

@ -1,4 +1,7 @@
# Installing on Arch Linux # Installing on Arch Linux
{! backend/installation/otp_vs_from_source_source.include !}
## Installation ## Installation
This guide will assume that you have administrative rights, either as root or a user with [sudo permissions](https://wiki.archlinux.org/index.php/Sudo). If you want to run this guide with root, ignore the `sudo` at the beginning of the lines, unless it calls a user like `sudo -Hu pleroma`; in this case, use `su <username> -s $SHELL -c 'command'` instead. This guide will assume that you have administrative rights, either as root or a user with [sudo permissions](https://wiki.archlinux.org/index.php/Sudo). If you want to run this guide with root, ignore the `sudo` at the beginning of the lines, unless it calls a user like `sudo -Hu pleroma`; in this case, use `su <username> -s $SHELL -c 'command'` instead.

View file

@ -1,4 +1,7 @@
# Installing on Debian Based Distributions # Installing on Debian Based Distributions
{! backend/installation/otp_vs_from_source_source.include !}
## Installation ## Installation
This guide will assume you are on Debian 11 (“bullseye”) or later. This guide should also work with Ubuntu 18.04 (“Bionic Beaver”) and later. It also assumes that you have administrative rights, either as root or a user with [sudo permissions](https://www.digitalocean.com/community/tutorials/how-to-add-delete-and-grant-sudo-privileges-to-users-on-a-debian-vps). If you want to run this guide with root, ignore the `sudo` at the beginning of the lines, unless it calls a user like `sudo -Hu pleroma`; in this case, use `su <username> -s $SHELL -c 'command'` instead. This guide will assume you are on Debian 11 (“bullseye”) or later. This guide should also work with Ubuntu 18.04 (“Bionic Beaver”) and later. It also assumes that you have administrative rights, either as root or a user with [sudo permissions](https://www.digitalocean.com/community/tutorials/how-to-add-delete-and-grant-sudo-privileges-to-users-on-a-debian-vps). If you want to run this guide with root, ignore the `sudo` at the beginning of the lines, unless it calls a user like `sudo -Hu pleroma`; in this case, use `su <username> -s $SHELL -c 'command'` instead.

View file

@ -1,4 +1,7 @@
# Installing on Gentoo GNU/Linux # Installing on Gentoo GNU/Linux
{! backend/installation/otp_vs_from_source_source.include !}
## Installation ## Installation
This guide will assume that you have administrative rights, either as root or a user with [sudo permissions](https://wiki.gentoo.org/wiki/Sudo). Lines that begin with `#` indicate that they should be run as the superuser. Lines using `$` should be run as the indicated user, e.g. `pleroma$` should be run as the `pleroma` user. This guide will assume that you have administrative rights, either as root or a user with [sudo permissions](https://wiki.gentoo.org/wiki/Sudo). Lines that begin with `#` indicate that they should be run as the superuser. Lines using `$` should be run as the indicated user, e.g. `pleroma$` should be run as the `pleroma` user.

View file

@ -1,7 +1,8 @@
# Switching a from-source install to OTP releases # Switching a from-source install to OTP releases
## What are OTP releases? {! backend/installation/otp_vs_from_source.include !}
OTP releases are as close as you can get to binary releases with Erlang/Elixir. The release is self-contained, and provides everything needed to boot it, it is easily administered via the provided shell script to open up a remote console, start/stop/restart the release, start in the background, send remote commands, and more.
In this guide we cover how you can migrate from a from source installation to one using OTP releases.
## Pre-requisites ## Pre-requisites
You will be running commands as root. If you aren't root already, please elevate your priviledges by executing `sudo su`/`su`. You will be running commands as root. If you aren't root already, please elevate your priviledges by executing `sudo su`/`su`.

View file

@ -1,5 +1,9 @@
# Installing on Linux using OTP releases # Installing on Linux using OTP releases
{! backend/installation/otp_vs_from_source.include !}
This guide covers a installation using an OTP release. To install Pleroma from source, please check out the corresponding guide for your distro.
## Pre-requisites ## Pre-requisites
* A machine running Linux with GNU (e.g. Debian, Ubuntu) or musl (e.g. Alpine) libc and `x86_64`, `aarch64` or `armv7l` CPU, you have root access to. If you are not sure if it's compatible see [Detecting flavour section](#detecting-flavour) below * A machine running Linux with GNU (e.g. Debian, Ubuntu) or musl (e.g. Alpine) libc and `x86_64`, `aarch64` or `armv7l` CPU, you have root access to. If you are not sure if it's compatible see [Detecting flavour section](#detecting-flavour) below
* A (sub)domain pointed to the machine * A (sub)domain pointed to the machine

View file

@ -0,0 +1,3 @@
## OTP releases vs from-source installations
There are two ways to install Pleroma. You can use OTP releases or do a from-source installation. OTP releases are as close as you can get to binary releases with Erlang/Elixir. The release is self-contained, and provides everything needed to boot it, it is easily administered via the provided shell script to open up a remote console, start/stop/restart the release, start in the background, send remote commands, and more. With from source installations you install Pleroma from source, meaning you have to install certain dependencies like Erlang+Elixir and compile Pleroma yourself.

View file

@ -0,0 +1,3 @@
{! backend/installation/otp_vs_from_source.include !}
This guide covers a from-source installation. To install using OTP releases, please check out [the OTP guide](./otp_en.md).

View file

@ -0,0 +1,9 @@
# Installing on Yunohost
[YunoHost](https://yunohost.org) is a server operating system aimed at self-hosting. The YunoHost community maintains a package of Pleroma which allows you to install Pleroma on YunoHost. You can install it via the normal way through the admin web interface, or through the CLI. More information can be found at [the repo of the package](https://github.com/YunoHost-Apps/pleroma_ynh).
## Questions
Questions and problems related to the YunoHost parts can be done through the [regular YunoHost channels](https://yunohost.org/en/help).
For questions about Pleroma, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.

View file

@ -209,7 +209,9 @@ def run(["set_text_search_config", tsconfig]) do
new.fts_content := to_tsvector(new.data->>'content'); new.fts_content := to_tsvector(new.data->>'content');
RETURN new; RETURN new;
END END
$$ LANGUAGE plpgsql" $$ LANGUAGE plpgsql",
[],
timeout: :infinity
) )
shell_info("Refresh RUM index") shell_info("Refresh RUM index")
@ -219,7 +221,9 @@ def run(["set_text_search_config", tsconfig]) do
Ecto.Adapters.SQL.query!( Ecto.Adapters.SQL.query!(
Pleroma.Repo, Pleroma.Repo,
"CREATE INDEX objects_fts ON objects USING gin(to_tsvector('#{tsconfig}', data->>'content')); " "CREATE INDEX CONCURRENTLY objects_fts ON objects USING gin(to_tsvector('#{tsconfig}', data->>'content')); ",
[],
timeout: :infinity
) )
end end

View file

@ -26,6 +26,7 @@ def search(user, search_query, options \\ []) do
:plain :plain
end end
try do
Activity Activity
|> Activity.with_preloaded_object() |> Activity.with_preloaded_object()
|> Activity.restrict_deactivated_users() |> Activity.restrict_deactivated_users()
@ -39,6 +40,9 @@ def search(user, search_query, options \\ []) do
:offset :offset
) )
|> maybe_fetch(user, search_query) |> maybe_fetch(user, search_query)
rescue
_ -> maybe_fetch([], user, search_query)
end
end end
def maybe_restrict_author(query, %User{} = author) do def maybe_restrict_author(query, %User{} = author) do
@ -61,10 +65,17 @@ defp restrict_public(q) do
end end
defp query_with(q, :gin, search_query, :plain) do defp query_with(q, :gin, search_query, :plain) do
%{rows: [[tsc]]} =
Ecto.Adapters.SQL.query!(
Pleroma.Repo,
"select current_setting('default_text_search_config')::regconfig::oid;"
)
from([a, o] in q, from([a, o] in q,
where: where:
fragment( fragment(
"to_tsvector(?->>'content') @@ plainto_tsquery(?)", "to_tsvector(?::oid::regconfig, ?->>'content') @@ plainto_tsquery(?)",
^tsc,
o.data, o.data,
^search_query ^search_query
) )
@ -72,10 +83,17 @@ defp query_with(q, :gin, search_query, :plain) do
end end
defp query_with(q, :gin, search_query, :websearch) do defp query_with(q, :gin, search_query, :websearch) do
%{rows: [[tsc]]} =
Ecto.Adapters.SQL.query!(
Pleroma.Repo,
"select current_setting('default_text_search_config')::regconfig::oid;"
)
from([a, o] in q, from([a, o] in q,
where: where:
fragment( fragment(
"to_tsvector(?->>'content') @@ websearch_to_tsquery(?)", "to_tsvector(?::oid::regconfig, ?->>'content') @@ websearch_to_tsquery(?)",
^tsc,
o.data, o.data,
^search_query ^search_query
) )

View file

@ -20,6 +20,140 @@ defmodule Pleroma.Config.DeprecationWarnings do
"\n* `config :pleroma, :instance, mrf_transparency_exclusions` is now `config :pleroma, :mrf, transparency_exclusions`"} "\n* `config :pleroma, :instance, mrf_transparency_exclusions` is now `config :pleroma, :mrf, transparency_exclusions`"}
] ]
def check_simple_policy_tuples do
has_strings =
Config.get([:mrf_simple])
|> Enum.any?(fn {_, v} -> Enum.any?(v, &is_binary/1) end)
if has_strings do
Logger.warn("""
!!!DEPRECATION WARNING!!!
Your config is using strings in the SimplePolicy configuration instead of tuples. They should work for now, but you are advised to change to the new configuration to prevent possible issues later:
```
config :pleroma, :mrf_simple,
media_removal: ["instance.tld"],
media_nsfw: ["instance.tld"],
federated_timeline_removal: ["instance.tld"],
report_removal: ["instance.tld"],
reject: ["instance.tld"],
followers_only: ["instance.tld"],
accept: ["instance.tld"],
avatar_removal: ["instance.tld"],
banner_removal: ["instance.tld"],
reject_deletes: ["instance.tld"]
```
Is now
```
config :pleroma, :mrf_simple,
media_removal: [{"instance.tld", "Reason for media removal"}],
media_nsfw: [{"instance.tld", "Reason for media nsfw"}],
federated_timeline_removal: [{"instance.tld", "Reason for federated timeline removal"}],
report_removal: [{"instance.tld", "Reason for report removal"}],
reject: [{"instance.tld", "Reason for reject"}],
followers_only: [{"instance.tld", "Reason for followers only"}],
accept: [{"instance.tld", "Reason for accept"}],
avatar_removal: [{"instance.tld", "Reason for avatar removal"}],
banner_removal: [{"instance.tld", "Reason for banner removal"}],
reject_deletes: [{"instance.tld", "Reason for reject deletes"}]
```
""")
new_config =
Config.get([:mrf_simple])
|> Enum.map(fn {k, v} ->
{k,
Enum.map(v, fn
{instance, reason} -> {instance, reason}
instance -> {instance, ""}
end)}
end)
Config.put([:mrf_simple], new_config)
:error
else
:ok
end
end
def check_quarantined_instances_tuples do
has_strings = Config.get([:instance, :quarantined_instances]) |> Enum.any?(&is_binary/1)
if has_strings do
Logger.warn("""
!!!DEPRECATION WARNING!!!
Your config is using strings in the quarantined_instances configuration instead of tuples. They should work for now, but you are advised to change to the new configuration to prevent possible issues later:
```
config :pleroma, :instance,
quarantined_instances: ["instance.tld"]
```
Is now
```
config :pleroma, :instance,
quarantined_instances: [{"instance.tld", "Reason for quarantine"}]
```
""")
new_config =
Config.get([:instance, :quarantined_instances])
|> Enum.map(fn
{instance, reason} -> {instance, reason}
instance -> {instance, ""}
end)
Config.put([:instance, :quarantined_instances], new_config)
:error
else
:ok
end
end
def check_transparency_exclusions_tuples do
has_strings = Config.get([:mrf, :transparency_exclusions]) |> Enum.any?(&is_binary/1)
if has_strings do
Logger.warn("""
!!!DEPRECATION WARNING!!!
Your config is using strings in the transparency_exclusions configuration instead of tuples. They should work for now, but you are advised to change to the new configuration to prevent possible issues later:
```
config :pleroma, :mrf,
transparency_exclusions: ["instance.tld"]
```
Is now
```
config :pleroma, :mrf,
transparency_exclusions: [{"instance.tld", "Reason to exlude transparency"}]
```
""")
new_config =
Config.get([:mrf, :transparency_exclusions])
|> Enum.map(fn
{instance, reason} -> {instance, reason}
instance -> {instance, ""}
end)
Config.put([:mrf, :transparency_exclusions], new_config)
:error
else
:ok
end
end
def check_hellthread_threshold do def check_hellthread_threshold do
if Config.get([:mrf_hellthread, :threshold]) do if Config.get([:mrf_hellthread, :threshold]) do
Logger.warn(""" Logger.warn("""
@ -34,20 +168,24 @@ def check_hellthread_threshold do
end end
def warn do def warn do
with :ok <- check_hellthread_threshold(), [
:ok <- check_old_mrf_config(), check_hellthread_threshold(),
:ok <- check_media_proxy_whitelist_config(), check_old_mrf_config(),
:ok <- check_welcome_message_config(), check_media_proxy_whitelist_config(),
:ok <- check_gun_pool_options(), check_welcome_message_config(),
:ok <- check_activity_expiration_config(), check_gun_pool_options(),
:ok <- check_remote_ip_plug_name(), check_activity_expiration_config(),
:ok <- check_uploders_s3_public_endpoint(), check_remote_ip_plug_name(),
:ok <- check_old_chat_shoutbox() do check_uploders_s3_public_endpoint(),
:ok check_old_chat_shoutbox(),
else check_quarantined_instances_tuples(),
_ -> check_transparency_exclusions_tuples(),
:error check_simple_policy_tuples()
end ]
|> Enum.reduce(:ok, fn
:ok, :ok -> :ok
_, _ -> :error
end)
end end
def check_welcome_message_config do def check_welcome_message_config do

View file

@ -72,6 +72,7 @@ def unread_notifications_count(%User{id: user_id}) do
pleroma:emoji_reaction pleroma:emoji_reaction
pleroma:report pleroma:report
reblog reblog
poll
} }
def changeset(%Notification{} = notification, attrs) do def changeset(%Notification{} = notification, attrs) do
@ -379,7 +380,7 @@ defp do_create_notifications(%Activity{} = activity, options) do
notifications = notifications =
Enum.map(potential_receivers, fn user -> Enum.map(potential_receivers, fn user ->
do_send = do_send && user in enabled_receivers do_send = do_send && user in enabled_receivers
create_notification(activity, user, do_send) create_notification(activity, user, do_send: do_send)
end) end)
|> Enum.reject(&is_nil/1) |> Enum.reject(&is_nil/1)
@ -435,15 +436,18 @@ defp type_from_activity_object(%{data: %{"type" => "Create"}} = activity) do
end end
# TODO move to sql, too. # TODO move to sql, too.
def create_notification(%Activity{} = activity, %User{} = user, do_send \\ true) do def create_notification(%Activity{} = activity, %User{} = user, opts \\ []) do
unless skip?(activity, user) do do_send = Keyword.get(opts, :do_send, true)
type = Keyword.get(opts, :type, type_from_activity(activity))
unless skip?(activity, user, opts) do
{:ok, %{notification: notification}} = {:ok, %{notification: notification}} =
Multi.new() Multi.new()
|> Multi.insert(:notification, %Notification{ |> Multi.insert(:notification, %Notification{
user_id: user.id, user_id: user.id,
activity: activity, activity: activity,
seen: mark_as_read?(activity, user), seen: mark_as_read?(activity, user),
type: type_from_activity(activity) type: type
}) })
|> Marker.multi_set_last_read_id(user, "notifications") |> Marker.multi_set_last_read_id(user, "notifications")
|> Repo.transaction() |> Repo.transaction()
@ -457,6 +461,28 @@ def create_notification(%Activity{} = activity, %User{} = user, do_send \\ true)
end end
end end
def create_poll_notifications(%Activity{} = activity) do
with %Object{data: %{"type" => "Question", "actor" => actor} = data} <-
Object.normalize(activity) do
voters =
case data do
%{"voters" => voters} when is_list(voters) -> voters
_ -> []
end
notifications =
Enum.reduce([actor | voters], [], fn ap_id, acc ->
with %User{local: true} = user <- User.get_by_ap_id(ap_id) do
[create_notification(activity, user, type: "poll") | acc]
else
_ -> acc
end
end)
{:ok, notifications}
end
end
@doc """ @doc """
Returns a tuple with 2 elements: Returns a tuple with 2 elements:
{notification-enabled receivers, currently disabled receivers (blocking / [thread] muting)} {notification-enabled receivers, currently disabled receivers (blocking / [thread] muting)}
@ -572,8 +598,10 @@ def exclude_thread_muter_ap_ids(ap_ids, %Activity{} = activity) do
Enum.uniq(ap_ids) -- thread_muter_ap_ids Enum.uniq(ap_ids) -- thread_muter_ap_ids
end end
@spec skip?(Activity.t(), User.t()) :: boolean() def skip?(activity, user, opts \\ [])
def skip?(%Activity{} = activity, %User{} = user) do
@spec skip?(Activity.t(), User.t(), Keyword.t()) :: boolean()
def skip?(%Activity{} = activity, %User{} = user, opts) do
[ [
:self, :self,
:invisible, :invisible,
@ -581,17 +609,21 @@ def skip?(%Activity{} = activity, %User{} = user) do
:recently_followed, :recently_followed,
:filtered :filtered
] ]
|> Enum.find(&skip?(&1, activity, user)) |> Enum.find(&skip?(&1, activity, user, opts))
end end
def skip?(_, _), do: false def skip?(_activity, _user, _opts), do: false
@spec skip?(atom(), Activity.t(), User.t()) :: boolean() @spec skip?(atom(), Activity.t(), User.t(), Keyword.t()) :: boolean()
def skip?(:self, %Activity{} = activity, %User{} = user) do def skip?(:self, %Activity{} = activity, %User{} = user, opts) do
activity.data["actor"] == user.ap_id cond do
opts[:type] == "poll" -> false
activity.data["actor"] == user.ap_id -> true
true -> false
end
end end
def skip?(:invisible, %Activity{} = activity, _) do def skip?(:invisible, %Activity{} = activity, _user, _opts) do
actor = activity.data["actor"] actor = activity.data["actor"]
user = User.get_cached_by_ap_id(actor) user = User.get_cached_by_ap_id(actor)
User.invisible?(user) User.invisible?(user)
@ -600,15 +632,27 @@ def skip?(:invisible, %Activity{} = activity, _) do
def skip?( def skip?(
:block_from_strangers, :block_from_strangers,
%Activity{} = activity, %Activity{} = activity,
%User{notification_settings: %{block_from_strangers: true}} = user %User{notification_settings: %{block_from_strangers: true}} = user,
opts
) do ) do
actor = activity.data["actor"] actor = activity.data["actor"]
follower = User.get_cached_by_ap_id(actor) follower = User.get_cached_by_ap_id(actor)
!User.following?(follower, user)
cond do
opts[:type] == "poll" -> false
user.ap_id == actor -> false
!User.following?(follower, user) -> true
true -> false
end
end end
# To do: consider defining recency in hours and checking FollowingRelationship with a single SQL # To do: consider defining recency in hours and checking FollowingRelationship with a single SQL
def skip?(:recently_followed, %Activity{data: %{"type" => "Follow"}} = activity, %User{} = user) do def skip?(
:recently_followed,
%Activity{data: %{"type" => "Follow"}} = activity,
%User{} = user,
_opts
) do
actor = activity.data["actor"] actor = activity.data["actor"]
Notification.for_user(user) Notification.for_user(user)
@ -618,9 +662,10 @@ def skip?(:recently_followed, %Activity{data: %{"type" => "Follow"}} = activity,
end) end)
end end
def skip?(:filtered, %{data: %{"type" => type}}, _) when type in ["Follow", "Move"], do: false def skip?(:filtered, %{data: %{"type" => type}}, _user, _opts) when type in ["Follow", "Move"],
do: false
def skip?(:filtered, activity, user) do def skip?(:filtered, activity, user, _opts) do
object = Object.normalize(activity, fetch: false) object = Object.normalize(activity, fetch: false)
cond do cond do
@ -638,7 +683,7 @@ def skip?(:filtered, activity, user) do
end end
end end
def skip?(_, _, _), do: false def skip?(_type, _activity, _user, _opts), do: false
def mark_as_read?(activity, target_user) do def mark_as_read?(activity, target_user) do
user = Activity.user_actor(activity) user = Activity.user_actor(activity)

View file

@ -25,6 +25,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.Web.Streamer alias Pleroma.Web.Streamer
alias Pleroma.Web.WebFinger alias Pleroma.Web.WebFinger
alias Pleroma.Workers.BackgroundWorker alias Pleroma.Workers.BackgroundWorker
alias Pleroma.Workers.PollWorker
import Ecto.Query import Ecto.Query
import Pleroma.Web.ActivityPub.Utils import Pleroma.Web.ActivityPub.Utils
@ -288,6 +289,7 @@ defp do_create(%{to: to, actor: actor, context: context, object: object} = param
{:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity}, {:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity},
{:ok, _actor} <- increase_note_count_if_public(actor, activity), {:ok, _actor} <- increase_note_count_if_public(actor, activity),
_ <- notify_and_stream(activity), _ <- notify_and_stream(activity),
:ok <- maybe_schedule_poll_notifications(activity),
:ok <- maybe_federate(activity) do :ok <- maybe_federate(activity) do
{:ok, activity} {:ok, activity}
else else
@ -302,6 +304,11 @@ defp do_create(%{to: to, actor: actor, context: context, object: object} = param
end end
end end
defp maybe_schedule_poll_notifications(activity) do
PollWorker.schedule_poll_end(activity)
:ok
end
@spec listen(map()) :: {:ok, Activity.t()} | {:error, any()} @spec listen(map()) :: {:ok, Activity.t()} | {:error, any()}
def listen(%{to: to, actor: actor, context: context, object: object} = params) do def listen(%{to: to, actor: actor, context: context, object: object} = params) do
additional = params[:additional] || %{} additional = params[:additional] || %{}

View file

@ -15,6 +15,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.CommonAPI.ActivityDraft
require Pleroma.Constants require Pleroma.Constants
@ -125,6 +126,37 @@ def create(actor, object, recipients) do
|> Pleroma.Maps.put_if_present("context", context), []} |> Pleroma.Maps.put_if_present("context", context), []}
end end
@spec note(ActivityDraft.t()) :: {:ok, map(), keyword()}
def note(%ActivityDraft{} = draft) do
data =
%{
"type" => "Note",
"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(draft.in_reply_to)
|> Map.merge(draft.extra)
{:ok, data, []}
end
defp add_in_reply_to(object, nil), do: object
defp add_in_reply_to(object, in_reply_to) do
with %Object{} = in_reply_to_object <- Object.normalize(in_reply_to, fetch: false) do
Map.put(object, "inReplyTo", in_reply_to_object.data["id"])
else
_ -> object
end
end
def chat_message(actor, recipient, content, opts \\ []) do def chat_message(actor, recipient, content, opts \\ []) do
basic = %{ basic = %{
"id" => Utils.generate_object_id(), "id" => Utils.generate_object_id(),

View file

@ -21,7 +21,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do
type: [:module, {:list, :module}], type: [:module, {:list, :module}],
description: description:
"A list of MRF policies enabled. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name.", "A list of MRF policies enabled. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name.",
suggestions: {:list_behaviour_implementations, Pleroma.Web.ActivityPub.MRF} suggestions: {:list_behaviour_implementations, Pleroma.Web.ActivityPub.MRF.Policy}
}, },
%{ %{
key: :transparency, key: :transparency,
@ -33,9 +33,11 @@ defmodule Pleroma.Web.ActivityPub.MRF do
%{ %{
key: :transparency_exclusions, key: :transparency_exclusions,
label: "MRF transparency exclusions", label: "MRF transparency exclusions",
type: {:list, :string}, type: {:list, :tuple},
key_placeholder: "instance",
value_placeholder: "reason",
description: description:
"Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.", "Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value. You can also provide a reason for excluding these instance names. The instances and reasons won't be publicly disclosed.",
suggestions: [ suggestions: [
"exclusion.com" "exclusion.com"
] ]
@ -100,6 +102,11 @@ def subdomain_match?(domains, host) do
Enum.any?(domains, fn domain -> Regex.match?(domain, host) end) Enum.any?(domains, fn domain -> Regex.match?(domain, host) end)
end end
@spec instance_list_from_tuples([{String.t(), String.t()}]) :: [String.t()]
def instance_list_from_tuples(list) do
Enum.map(list, fn {instance, _} -> instance end)
end
def describe(policies) do def describe(policies) do
{:ok, policy_configs} = {:ok, policy_configs} =
policies policies

View file

@ -159,6 +159,8 @@ def config_description do
%{ %{
key: :replace, key: :replace,
type: {:list, :tuple}, type: {:list, :tuple},
key_placeholder: "instance",
value_placeholder: "reason",
description: """ description: """
**Pattern**: a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`. **Pattern**: a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.

View file

@ -49,6 +49,8 @@ defp check_delist(message, actions) do
message message
|> Map.put("to", to) |> Map.put("to", to)
|> Map.put("cc", cc) |> Map.put("cc", cc)
|> Kernel.put_in(["object", "to"], to)
|> Kernel.put_in(["object", "cc"], cc)
{:ok, message} {:ok, message}
else else
@ -70,6 +72,8 @@ defp check_strip_followers(message, actions) do
message message
|> Map.put("to", to) |> Map.put("to", to)
|> Map.put("cc", cc) |> Map.put("cc", cc)
|> Kernel.put_in(["object", "to"], to)
|> Kernel.put_in(["object", "cc"], cc)
{:ok, message} {:ok, message}
else else
@ -82,7 +86,7 @@ defp check_strip_followers(message, actions) do
end end
@impl true @impl true
def filter(%{"type" => "Create", "published" => _} = message) do def filter(%{"type" => "Create", "object" => %{"published" => _}} = message) do
with actions <- Config.get([:mrf_object_age, :actions]), with actions <- Config.get([:mrf_object_age, :actions]),
{:reject, _} <- check_date(message), {:reject, _} <- check_date(message),
{:ok, message} <- check_reject(message, actions), {:ok, message} <- check_reject(message, actions),

View file

@ -47,7 +47,7 @@ def filter(object), do: {:ok, object}
@impl true @impl true
def describe, def describe,
do: {:ok, %{mrf_rejectnonpublic: Config.get(:mrf_rejectnonpublic) |> Enum.into(%{})}} do: {:ok, %{mrf_rejectnonpublic: Config.get(:mrf_rejectnonpublic) |> Map.new()}}
@impl true @impl true
def config_description do def config_description do

View file

@ -15,7 +15,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_accept(%{host: actor_host} = _actor_info, object) do defp check_accept(%{host: actor_host} = _actor_info, object) do
accepts = accepts =
Config.get([:mrf_simple, :accept]) instance_list(:accept)
|> MRF.subdomains_regex() |> MRF.subdomains_regex()
cond do cond do
@ -28,7 +28,7 @@ defp check_accept(%{host: actor_host} = _actor_info, object) do
defp check_reject(%{host: actor_host} = _actor_info, object) do defp check_reject(%{host: actor_host} = _actor_info, object) do
rejects = rejects =
Config.get([:mrf_simple, :reject]) instance_list(:reject)
|> MRF.subdomains_regex() |> MRF.subdomains_regex()
if MRF.subdomain_match?(rejects, actor_host) do if MRF.subdomain_match?(rejects, actor_host) do
@ -44,7 +44,7 @@ defp check_media_removal(
) )
when length(child_attachment) > 0 do when length(child_attachment) > 0 do
media_removal = media_removal =
Config.get([:mrf_simple, :media_removal]) instance_list(:media_removal)
|> MRF.subdomains_regex() |> MRF.subdomains_regex()
object = object =
@ -68,7 +68,7 @@ defp check_media_nsfw(
} = object } = object
) do ) do
media_nsfw = media_nsfw =
Config.get([:mrf_simple, :media_nsfw]) instance_list(:media_nsfw)
|> MRF.subdomains_regex() |> MRF.subdomains_regex()
object = object =
@ -85,7 +85,7 @@ defp check_media_nsfw(_actor_info, object), do: {:ok, object}
defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
timeline_removal = timeline_removal =
Config.get([:mrf_simple, :federated_timeline_removal]) instance_list(:federated_timeline_removal)
|> MRF.subdomains_regex() |> MRF.subdomains_regex()
object = object =
@ -112,7 +112,7 @@ defp intersection(list1, list2) do
defp check_followers_only(%{host: actor_host} = _actor_info, object) do defp check_followers_only(%{host: actor_host} = _actor_info, object) do
followers_only = followers_only =
Config.get([:mrf_simple, :followers_only]) instance_list(:followers_only)
|> MRF.subdomains_regex() |> MRF.subdomains_regex()
object = object =
@ -137,7 +137,7 @@ defp check_followers_only(%{host: actor_host} = _actor_info, object) do
defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do
report_removal = report_removal =
Config.get([:mrf_simple, :report_removal]) instance_list(:report_removal)
|> MRF.subdomains_regex() |> MRF.subdomains_regex()
if MRF.subdomain_match?(report_removal, actor_host) do if MRF.subdomain_match?(report_removal, actor_host) do
@ -151,7 +151,7 @@ defp check_report_removal(_actor_info, object), do: {:ok, object}
defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do
avatar_removal = avatar_removal =
Config.get([:mrf_simple, :avatar_removal]) instance_list(:avatar_removal)
|> MRF.subdomains_regex() |> MRF.subdomains_regex()
if MRF.subdomain_match?(avatar_removal, actor_host) do if MRF.subdomain_match?(avatar_removal, actor_host) do
@ -165,7 +165,7 @@ defp check_avatar_removal(_actor_info, object), do: {:ok, object}
defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do
banner_removal = banner_removal =
Config.get([:mrf_simple, :banner_removal]) instance_list(:banner_removal)
|> MRF.subdomains_regex() |> MRF.subdomains_regex()
if MRF.subdomain_match?(banner_removal, actor_host) do if MRF.subdomain_match?(banner_removal, actor_host) do
@ -185,12 +185,17 @@ defp check_object(%{"object" => object} = activity) do
defp check_object(object), do: {:ok, object} defp check_object(object), do: {:ok, object}
defp instance_list(config_key) do
Config.get([:mrf_simple, config_key])
|> MRF.instance_list_from_tuples()
end
@impl true @impl true
def filter(%{"type" => "Delete", "actor" => actor} = object) do def filter(%{"type" => "Delete", "actor" => actor} = object) do
%{host: actor_host} = URI.parse(actor) %{host: actor_host} = URI.parse(actor)
reject_deletes = reject_deletes =
Config.get([:mrf_simple, :reject_deletes]) instance_list(:reject_deletes)
|> MRF.subdomains_regex() |> MRF.subdomains_regex()
if MRF.subdomain_match?(reject_deletes, actor_host) do if MRF.subdomain_match?(reject_deletes, actor_host) do
@ -253,14 +258,42 @@ def filter(object), do: {:ok, object}
@impl true @impl true
def describe do def describe do
exclusions = Config.get([:mrf, :transparency_exclusions]) exclusions = Config.get([:mrf, :transparency_exclusions]) |> MRF.instance_list_from_tuples()
mrf_simple_excluded =
Config.get(:mrf_simple)
|> Enum.map(fn {rule, instances} ->
{rule, Enum.reject(instances, fn {host, _} -> host in exclusions end)}
end)
mrf_simple = mrf_simple =
Config.get(:mrf_simple) mrf_simple_excluded
|> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn v -> v in exclusions end)} end) |> Enum.map(fn {rule, instances} ->
|> Enum.into(%{}) {rule, Enum.map(instances, fn {host, _} -> host end)}
end)
|> Map.new()
{:ok, %{mrf_simple: mrf_simple}} # This is for backwards compatibility. We originally didn't sent
# extra info like a reason why an instance was rejected/quarantined/etc.
# Because we didn't want to break backwards compatibility it was decided
# to add an extra "info" key.
mrf_simple_info =
mrf_simple_excluded
|> Enum.map(fn {rule, instances} ->
{rule, Enum.reject(instances, fn {_, reason} -> reason == "" end)}
end)
|> Enum.reject(fn {_, instances} -> instances == [] end)
|> Enum.map(fn {rule, instances} ->
instances =
instances
|> Enum.map(fn {host, reason} -> {host, %{"reason" => reason}} end)
|> Map.new()
{rule, instances}
end)
|> Map.new()
{:ok, %{mrf_simple: mrf_simple, mrf_simple_info: mrf_simple_info}}
end end
@impl true @impl true
@ -270,70 +303,67 @@ def config_description do
related_policy: "Pleroma.Web.ActivityPub.MRF.SimplePolicy", related_policy: "Pleroma.Web.ActivityPub.MRF.SimplePolicy",
label: "MRF Simple", label: "MRF Simple",
description: "Simple ingress policies", description: "Simple ingress policies",
children: [ children:
[
%{ %{
key: :media_removal, key: :media_removal,
type: {:list, :string}, description:
description: "List of instances to strip media attachments from", "List of instances to strip media attachments from and the reason for doing so"
suggestions: ["example.com", "*.example.com"]
}, },
%{ %{
key: :media_nsfw, key: :media_nsfw,
label: "Media NSFW", label: "Media NSFW",
type: {:list, :string}, description:
description: "List of instances to tag all media as NSFW (sensitive) from", "List of instances to tag all media as NSFW (sensitive) from and the reason for doing so"
suggestions: ["example.com", "*.example.com"]
}, },
%{ %{
key: :federated_timeline_removal, key: :federated_timeline_removal,
type: {:list, :string},
description: description:
"List of instances to remove from the Federated (aka The Whole Known Network) Timeline", "List of instances to remove from the Federated (aka The Whole Known Network) Timeline and the reason for doing so"
suggestions: ["example.com", "*.example.com"]
}, },
%{ %{
key: :reject, key: :reject,
type: {:list, :string}, description:
description: "List of instances to reject activities from (except deletes)", "List of instances to reject activities from (except deletes) and the reason for doing so"
suggestions: ["example.com", "*.example.com"]
}, },
%{ %{
key: :accept, key: :accept,
type: {:list, :string}, description:
description: "List of instances to only accept activities from (except deletes)", "List of instances to only accept activities from (except deletes) and the reason for doing so"
suggestions: ["example.com", "*.example.com"]
}, },
%{ %{
key: :followers_only, key: :followers_only,
type: {:list, :string}, description:
description: "Force posts from the given instances to be visible by followers only", "Force posts from the given instances to be visible by followers only and the reason for doing so"
suggestions: ["example.com", "*.example.com"]
}, },
%{ %{
key: :report_removal, key: :report_removal,
type: {:list, :string}, description: "List of instances to reject reports from and the reason for doing so"
description: "List of instances to reject reports from",
suggestions: ["example.com", "*.example.com"]
}, },
%{ %{
key: :avatar_removal, key: :avatar_removal,
type: {:list, :string}, description: "List of instances to strip avatars from and the reason for doing so"
description: "List of instances to strip avatars from",
suggestions: ["example.com", "*.example.com"]
}, },
%{ %{
key: :banner_removal, key: :banner_removal,
type: {:list, :string}, description: "List of instances to strip banners from and the reason for doing so"
description: "List of instances to strip banners from",
suggestions: ["example.com", "*.example.com"]
}, },
%{ %{
key: :reject_deletes, key: :reject_deletes,
type: {:list, :string}, description: "List of instances to reject deletions from and the reason for doing so"
description: "List of instances to reject deletions from",
suggestions: ["example.com", "*.example.com"]
} }
] ]
|> Enum.map(fn setting ->
Map.merge(
setting,
%{
type: {:list, :tuple},
key_placeholder: "instance",
value_placeholder: "reason",
suggestions: [{"example.com", "Some reason"}, {"*.example.com", "Another reason"}]
}
)
end)
} }
end end
end end

View file

@ -92,6 +92,51 @@ def filter(%{"object" => %{"emoji" => foreign_emojis, "actor" => actor}} = messa
def filter(message), do: {:ok, message} def filter(message), do: {:ok, message}
@impl true
@spec config_description :: %{
children: [
%{
description: <<_::272, _::_*256>>,
key: :hosts | :rejected_shortcodes | :size_limit,
suggestions: [any(), ...],
type: {:list, :string} | {:list, :string} | :integer
},
...
],
description: <<_::448>>,
key: :mrf_steal_emoji,
label: <<_::80>>,
related_policy: <<_::352>>
}
def config_description do
%{
key: :mrf_steal_emoji,
related_policy: "Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy",
label: "MRF Emojis",
description: "Steals emojis from selected instances when it sees them.",
children: [
%{
key: :hosts,
type: {:list, :string},
description: "List of hosts to steal emojis from",
suggestions: [""]
},
%{
key: :rejected_shortcodes,
type: {:list, :string},
description: "Regex-list of shortcodes to reject",
suggestions: [""]
},
%{
key: :size_limit,
type: :integer,
description: "File size limit (in bytes), checked before an emoji is saved to the disk",
suggestions: ["100000"]
}
]
}
end
@impl true @impl true
def describe do def describe do
{:ok, %{}} {:ok, %{}}

View file

@ -37,7 +37,7 @@ def filter(object), do: {:ok, object}
def describe do def describe do
mrf_user_allowlist = mrf_user_allowlist =
Config.get([:mrf_user_allowlist], []) Config.get([:mrf_user_allowlist], [])
|> Enum.into(%{}, fn {k, v} -> {k, length(v)} end) |> Map.new(fn {k, v} -> {k, length(v)} end)
{:ok, %{mrf_user_allowlist: mrf_user_allowlist}} {:ok, %{mrf_user_allowlist: mrf_user_allowlist}}
end end

View file

@ -39,7 +39,7 @@ def filter(message), do: {:ok, message}
@impl true @impl true
def describe, def describe,
do: {:ok, %{mrf_vocabulary: Pleroma.Config.get(:mrf_vocabulary) |> Enum.into(%{})}} do: {:ok, %{mrf_vocabulary: Pleroma.Config.get(:mrf_vocabulary) |> Map.new()}}
@impl true @impl true
def config_description do def config_description do

View file

@ -213,6 +213,7 @@ def stringify_keys(%{__struct__: _} = object) do
def stringify_keys(object) when is_map(object) do def stringify_keys(object) when is_map(object) do
object object
|> Enum.filter(fn {_, v} -> v != nil end)
|> Map.new(fn {key, val} -> {to_string(key), stringify_keys(val)} end) |> Map.new(fn {key, val} -> {to_string(key), stringify_keys(val)} end)
end end

View file

@ -112,6 +112,7 @@ defp should_federate?(inbox, public) do
quarantined_instances = quarantined_instances =
Config.get([:instance, :quarantined_instances], []) Config.get([:instance, :quarantined_instances], [])
|> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples()
|> Pleroma.Web.ActivityPub.MRF.subdomains_regex() |> Pleroma.Web.ActivityPub.MRF.subdomains_regex()
!Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host) !Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host)

View file

@ -10,7 +10,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
collection, and so on. collection, and so on.
""" """
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Activity.Ir.Topics
alias Pleroma.Chat alias Pleroma.Chat
alias Pleroma.Chat.MessageReference alias Pleroma.Chat.MessageReference
alias Pleroma.FollowingRelationship alias Pleroma.FollowingRelationship
@ -24,6 +23,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Push alias Pleroma.Web.Push
alias Pleroma.Web.Streamer alias Pleroma.Web.Streamer
alias Pleroma.Workers.PollWorker
require Logger require Logger
@ -195,7 +195,7 @@ def handle(%{data: %{"type" => "Like"}} = object, meta) do
# - Set up notifications # - Set up notifications
@impl true @impl true
def handle(%{data: %{"type" => "Create"}} = activity, meta) do def handle(%{data: %{"type" => "Create"}} = activity, meta) do
with {:ok, object, meta} <- handle_object_creation(meta[:object_data], meta), with {:ok, object, meta} <- handle_object_creation(meta[:object_data], activity, meta),
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
{:ok, notifications} = Notification.create_notifications(activity, do_send: false) {:ok, notifications} = Notification.create_notifications(activity, do_send: false)
{:ok, _user} = ActivityPub.increase_note_count_if_public(user, object) {:ok, _user} = ActivityPub.increase_note_count_if_public(user, object)
@ -225,6 +225,8 @@ def handle(%{data: %{"type" => "Create"}} = activity, meta) do
meta meta
|> add_notifications(notifications) |> add_notifications(notifications)
ap_streamer().stream_out(activity)
{:ok, activity, meta} {:ok, activity, meta}
else else
e -> Repo.rollback(e) e -> Repo.rollback(e)
@ -245,9 +247,7 @@ def handle(%{data: %{"type" => "Announce"}} = object, meta) do
if !User.is_internal_user?(user) do if !User.is_internal_user?(user) do
Notification.create_notifications(object) Notification.create_notifications(object)
object ap_streamer().stream_out(object)
|> Topics.get_activity_topics()
|> Streamer.stream(object)
end end
{:ok, object, meta} {:ok, object, meta}
@ -389,7 +389,7 @@ def handle(object, meta) do
{:ok, object, meta} {:ok, object, meta}
end end
def handle_object_creation(%{"type" => "ChatMessage"} = object, meta) do def handle_object_creation(%{"type" => "ChatMessage"} = object, _activity, meta) do
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
actor = User.get_cached_by_ap_id(object.data["actor"]) actor = User.get_cached_by_ap_id(object.data["actor"])
recipient = User.get_cached_by_ap_id(hd(object.data["to"])) recipient = User.get_cached_by_ap_id(hd(object.data["to"]))
@ -424,7 +424,14 @@ def handle_object_creation(%{"type" => "ChatMessage"} = object, meta) do
end end
end end
def handle_object_creation(%{"type" => "Answer"} = object_map, meta) do def handle_object_creation(%{"type" => "Question"} = object, activity, meta) do
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
PollWorker.schedule_poll_end(activity)
{:ok, object, meta}
end
end
def handle_object_creation(%{"type" => "Answer"} = object_map, _activity, meta) do
with {:ok, object, meta} <- Pipeline.common_pipeline(object_map, meta) do with {:ok, object, meta} <- Pipeline.common_pipeline(object_map, meta) do
Object.increase_vote_count( Object.increase_vote_count(
object.data["inReplyTo"], object.data["inReplyTo"],
@ -436,15 +443,15 @@ def handle_object_creation(%{"type" => "Answer"} = object_map, meta) do
end end
end end
def handle_object_creation(%{"type" => objtype} = object, meta) def handle_object_creation(%{"type" => objtype} = object, _activity, meta)
when objtype in ~w[Audio Video Question Event Article Note Page] do when objtype in ~w[Audio Video Event Article Note Page] do
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
{:ok, object, meta} {:ok, object, meta}
end end
end end
# Nothing to do # Nothing to do
def handle_object_creation(object, meta) do def handle_object_creation(object, _activity, meta) do
{:ok, object, meta} {:ok, object, meta}
end end

View file

@ -35,6 +35,12 @@ def install(%{body_params: params} = conn, _params) do
end end
defp installed do defp installed do
File.ls!(Pleroma.Frontend.dir()) frontend_directory = Pleroma.Frontend.dir()
if File.exists?(frontend_directory) do
File.ls!(frontend_directory)
else
[]
end
end end
end end

View file

@ -13,7 +13,9 @@ def extract_report_info(
account = User.get_cached_by_ap_id(account_ap_id) account = User.get_cached_by_ap_id(account_ap_id)
statuses = statuses =
Enum.map(status_ap_ids, fn status_ap_ids
|> Enum.reject(&is_nil(&1))
|> Enum.map(fn
act when is_map(act) -> Activity.get_by_ap_id_with_object(act["id"]) act when is_map(act) -> Activity.get_by_ap_id_with_object(act["id"])
act when is_binary(act) -> Activity.get_by_ap_id_with_object(act) act when is_binary(act) -> Activity.get_by_ap_id_with_object(act)
end) end)

View file

@ -195,7 +195,8 @@ defp notification_type do
"pleroma:chat_mention", "pleroma:chat_mention",
"pleroma:report", "pleroma:report",
"move", "move",
"follow_request" "follow_request",
"poll"
], ],
description: """ description: """
The type of event that resulted in the notification. The type of event that resulted in the notification.

View file

@ -8,6 +8,8 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
alias Pleroma.Web.ApiSpec.Schemas.ApiError alias Pleroma.Web.ApiSpec.Schemas.ApiError
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
import Pleroma.Web.ApiSpec.Helpers
def open_api_operation(action) do def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation") operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, []) apply(__MODULE__, operation, [])
@ -63,17 +65,7 @@ def change_password_operation do
summary: "Change account password", summary: "Change account password",
security: [%{"oAuth" => ["write:accounts"]}], security: [%{"oAuth" => ["write:accounts"]}],
operationId: "UtilController.change_password", operationId: "UtilController.change_password",
parameters: [ requestBody: request_body("Parameters", change_password_request(), required: true),
Operation.parameter(:password, :query, :string, "Current password", required: true),
Operation.parameter(:new_password, :query, :string, "New password", required: true),
Operation.parameter(
:new_password_confirmation,
:query,
:string,
"New password, confirmation",
required: true
)
],
responses: %{ responses: %{
200 => 200 =>
Operation.response("Success", "application/json", %Schema{ Operation.response("Success", "application/json", %Schema{
@ -86,17 +78,30 @@ def change_password_operation do
} }
end end
defp change_password_request do
%Schema{
title: "ChangePasswordRequest",
description: "POST body for changing the account's passowrd",
type: :object,
required: [:password, :new_password, :new_password_confirmation],
properties: %{
password: %Schema{type: :string, description: "Current password"},
new_password: %Schema{type: :string, description: "New password"},
new_password_confirmation: %Schema{
type: :string,
description: "New password, confirmation"
}
}
}
end
def change_email_operation do def change_email_operation do
%Operation{ %Operation{
tags: ["Account credentials"], tags: ["Account credentials"],
summary: "Change account email", summary: "Change account email",
security: [%{"oAuth" => ["write:accounts"]}], security: [%{"oAuth" => ["write:accounts"]}],
operationId: "UtilController.change_email", operationId: "UtilController.change_email",
parameters: [ requestBody: request_body("Parameters", change_email_request(), required: true),
Operation.parameter(:password, :query, :string, "Current password", required: true),
Operation.parameter(:email, :query, :string, "New email", required: true)
],
requestBody: nil,
responses: %{ responses: %{
200 => 200 =>
Operation.response("Success", "application/json", %Schema{ Operation.response("Success", "application/json", %Schema{
@ -109,6 +114,19 @@ def change_email_operation do
} }
end end
defp change_email_request do
%Schema{
title: "ChangeEmailRequest",
description: "POST body for changing the account's email",
type: :object,
required: [:email, :password],
properties: %{
email: %Schema{type: :string, description: "New email"},
password: %Schema{type: :string, description: "Current password"}
}
}
end
def update_notificaton_settings_operation do def update_notificaton_settings_operation do
%Operation{ %Operation{
tags: ["Accounts"], tags: ["Accounts"],

View file

@ -6,6 +6,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Conversation.Participation alias Pleroma.Conversation.Participation
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.CommonAPI.Utils
@ -213,8 +214,10 @@ defp object(draft) do
emoji = Map.merge(emoji, summary_emoji) emoji = Map.merge(emoji, summary_emoji)
{:ok, note_data, _meta} = Builder.note(draft)
object = object =
Utils.make_note_data(draft) note_data
|> Map.put("emoji", emoji) |> Map.put("emoji", emoji)
|> Map.put("source", draft.status) |> Map.put("source", draft.status)
|> Map.put("generator", draft.params[:generator]) |> Map.put("generator", draft.params[:generator])

View file

@ -291,33 +291,6 @@ def format_input(text, "text/markdown", options) do
|> Formatter.html_escape("text/html") |> Formatter.html_escape("text/html")
end end
def make_note_data(%ActivityDraft{} = draft) do
%{
"type" => "Note",
"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(draft.in_reply_to)
|> Map.merge(draft.extra)
end
defp add_in_reply_to(object, nil), do: object
defp add_in_reply_to(object, in_reply_to) do
with %Object{} = in_reply_to_object <- Object.normalize(in_reply_to, fetch: false) do
Map.put(object, "inReplyTo", in_reply_to_object.data["id"])
else
_ -> object
end
end
def format_naive_asctime(date) do def format_naive_asctime(date) do
date |> DateTime.from_naive!("Etc/UTC") |> format_asctime date |> DateTime.from_naive!("Etc/UTC") |> format_asctime
end end
@ -412,19 +385,14 @@ def maybe_notify_mentioned_recipients(
def maybe_notify_mentioned_recipients(recipients, _), do: recipients def maybe_notify_mentioned_recipients(recipients, _), do: recipients
# Do not notify subscribers if author is making a reply
def maybe_notify_subscribers(recipients, %Activity{
object: %Object{data: %{"inReplyTo" => _ap_id}}
}) do
recipients
end
def maybe_notify_subscribers( def maybe_notify_subscribers(
recipients, recipients,
%Activity{data: %{"actor" => actor, "type" => type}} = activity %Activity{data: %{"actor" => actor, "type" => "Create"}} = activity
) ) do
when type == "Create" do # Do not notify subscribers if author is making a reply
with %User{} = user <- User.get_cached_by_ap_id(actor) do with %Object{data: object} <- Object.normalize(activity, fetch: false),
nil <- object["inReplyTo"],
%User{} = user <- User.get_cached_by_ap_id(actor) do
subscriber_ids = subscriber_ids =
user user
|> User.subscriber_users() |> User.subscriber_users()

View file

@ -50,6 +50,7 @@ def index(conn, %{account_id: account_id} = params) do
favourite favourite
move move
pleroma:emoji_reaction pleroma:emoji_reaction
poll
} }
def index(%{assigns: %{user: user}} = conn, params) do def index(%{assigns: %{user: user}} = conn, params) do
params = params =

View file

@ -95,7 +95,20 @@ def federation do
{:ok, data} = MRF.describe() {:ok, data} = MRF.describe()
data data
|> Map.merge(%{quarantined_instances: quarantined}) |> Map.put(
:quarantined_instances,
Enum.map(quarantined, fn {instance, _reason} -> instance end)
)
# This is for backwards compatibility. We originally didn't sent
# extra info like a reason why an instance was rejected/quarantined/etc.
# Because we didn't want to break backwards compatibility it was decided
# to add an extra "info" key.
|> Map.put(:quarantined_instances_info, %{
"quarantined_instances" =>
quarantined
|> Enum.map(fn {instance, reason} -> {instance, %{"reason" => reason}} end)
|> Map.new()
})
else else
%{} %{}
end end

View file

@ -112,6 +112,9 @@ def render(
"move" -> "move" ->
put_target(response, activity, reading_user, %{}) put_target(response, activity, reading_user, %{})
"poll" ->
put_status(response, activity, reading_user, status_render_opts)
"pleroma:emoji_reaction" -> "pleroma:emoji_reaction" ->
response response
|> put_status(parent_activity_fn.(), reading_user, status_render_opts) |> put_status(parent_activity_fn.(), reading_user, status_render_opts)

View file

@ -65,10 +65,18 @@ defp get_context_id(%{data: %{"context" => context}}) when is_binary(context),
defp get_context_id(_), do: nil defp get_context_id(_), do: nil
defp reblogged?(activity, user) do # Check if the user reblogged this status
object = Object.normalize(activity, fetch: false) || %{} defp reblogged?(activity, %User{ap_id: ap_id}) do
present?(user && user.ap_id in (object.data["announcements"] || [])) with %Object{data: %{"announcements" => announcements}} when is_list(announcements) <-
Object.normalize(activity, fetch: false) do
ap_id in announcements
else
_ -> false
end end
end
# False if the user is logged out
defp reblogged?(_activity, _user), do: false
def render("index.json", opts) do def render("index.json", opts) do
reading_user = opts[:for] reading_user = opts[:for]

View file

@ -124,8 +124,8 @@ def build_content(notification, actor, object, mastodon_type) do
def format_body(activity, actor, object, mastodon_type \\ nil) def format_body(activity, actor, object, mastodon_type \\ nil)
def format_body(_activity, actor, %{data: %{"type" => "ChatMessage", "content" => content}}, _) do def format_body(_activity, actor, %{data: %{"type" => "ChatMessage"} = data}, _) do
case content do case data["content"] do
nil -> "@#{actor.nickname}: (Attachment)" nil -> "@#{actor.nickname}: (Attachment)"
content -> "@#{actor.nickname}: #{Utils.scrub_html_and_truncate(content, 80)}" content -> "@#{actor.nickname}: #{Utils.scrub_html_and_truncate(content, 80)}"
end end

View file

@ -26,7 +26,7 @@ defmodule Pleroma.Web.Push.Subscription do
end end
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
@supported_alert_types ~w[follow favourite mention reblog pleroma:chat_mention pleroma:emoji_reaction]a @supported_alert_types ~w[follow favourite mention reblog poll pleroma:chat_mention pleroma:emoji_reaction]a
defp alerts(%{data: %{alerts: alerts}}) do defp alerts(%{data: %{alerts: alerts}}) do
alerts = Map.take(alerts, @supported_alert_types) alerts = Map.take(alerts, @supported_alert_types)

View file

@ -81,17 +81,13 @@ def update_notificaton_settings(%{assigns: %{user: user}} = conn, params) do
end end
end end
def change_password(%{assigns: %{user: user}} = conn, %{ def change_password(%{assigns: %{user: user}, body_params: body_params} = conn, %{}) do
password: password, case CommonAPI.Utils.confirm_current_password(user, body_params.password) do
new_password: new_password,
new_password_confirmation: new_password_confirmation
}) do
case CommonAPI.Utils.confirm_current_password(user, password) do
{:ok, user} -> {:ok, user} ->
with {:ok, _user} <- with {:ok, _user} <-
User.reset_password(user, %{ User.reset_password(user, %{
password: new_password, password: body_params.new_password,
password_confirmation: new_password_confirmation password_confirmation: body_params.new_password_confirmation
}) do }) do
json(conn, %{status: "success"}) json(conn, %{status: "success"})
else else
@ -108,10 +104,10 @@ def change_password(%{assigns: %{user: user}} = conn, %{
end end
end end
def change_email(%{assigns: %{user: user}} = conn, %{password: password, email: email}) do def change_email(%{assigns: %{user: user}, body_params: body_params} = conn, %{}) do
case CommonAPI.Utils.confirm_current_password(user, password) do case CommonAPI.Utils.confirm_current_password(user, body_params.password) do
{:ok, user} -> {:ok, user} ->
with {:ok, _user} <- User.change_email(user, email) do with {:ok, _user} <- User.change_email(user, body_params.email) do
json(conn, %{status: "success"}) json(conn, %{status: "success"})
else else
{:error, changeset} -> {:error, changeset} ->

View file

@ -0,0 +1,45 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.PollWorker do
@moduledoc """
Generates notifications when a poll ends.
"""
use Pleroma.Workers.WorkerHelper, queue: "poll_notifications"
alias Pleroma.Activity
alias Pleroma.Notification
alias Pleroma.Object
@impl Oban.Worker
def perform(%Job{args: %{"op" => "poll_end", "activity_id" => activity_id}}) do
with %Activity{} = activity <- find_poll_activity(activity_id) do
Notification.create_poll_notifications(activity)
end
end
defp find_poll_activity(activity_id) do
with nil <- Activity.get_by_id(activity_id) do
{:error, :poll_activity_not_found}
end
end
def schedule_poll_end(%Activity{data: %{"type" => "Create"}, id: activity_id} = activity) do
with %Object{data: %{"type" => "Question", "closed" => closed}} when is_binary(closed) <-
Object.normalize(activity),
{:ok, end_time} <- NaiveDateTime.from_iso8601(closed),
:gt <- NaiveDateTime.compare(end_time, NaiveDateTime.utc_now()) do
%{
op: "poll_end",
activity_id: activity_id
}
|> new(scheduled_at: end_time)
|> Oban.insert()
else
_ -> {:error, activity}
end
end
def schedule_poll_end(activity), do: {:error, activity}
end

View file

@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do
def project do def project do
[ [
app: :pleroma, app: :pleroma,
version: version("2.3.50"), version: version("2.4.51"),
elixir: "~> 1.9", elixir: "~> 1.9",
elixirc_paths: elixirc_paths(Mix.env()), elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:phoenix, :gettext] ++ Mix.compilers(), compilers: [:phoenix, :gettext] ++ Mix.compilers(),

View file

@ -3,8 +3,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-05-13 16:37+0000\n" "POT-Creation-Date: 2020-05-13 16:37+0000\n"
"PO-Revision-Date: 2020-07-09 14:40+0000\n" "PO-Revision-Date: 2021-08-13 15:42+0000\n"
"Last-Translator: Ben Is <srsbzns@cock.li>\n" "Last-Translator: marcin mikołajczak <me@mkljczk.pl>\n"
"Language-Team: Polish <https://translate.pleroma.social/projects/pleroma/" "Language-Team: Polish <https://translate.pleroma.social/projects/pleroma/"
"pleroma/pl/>\n" "pleroma/pl/>\n"
"Language: pl\n" "Language: pl\n"
@ -13,7 +13,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
"|| n%100>=20) ? 1 : 2;\n" "|| n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 4.0.4\n" "X-Generator: Weblate 4.6.2\n"
## This file is a PO Template file. ## This file is a PO Template file.
## ##
@ -68,49 +68,49 @@ msgstr[2] "powinno mieć %{count} znaków"
msgid "should have %{count} item(s)" msgid "should have %{count} item(s)"
msgid_plural "should have %{count} item(s)" msgid_plural "should have %{count} item(s)"
msgstr[0] "" msgstr[0] "powinno zawierać %{count} element"
msgstr[1] "" msgstr[1] "powinno zawierać %{count} elementy"
msgstr[2] "" msgstr[2] "powinno zawierać %{count} elementów"
msgid "should be at least %{count} character(s)" msgid "should be at least %{count} character(s)"
msgid_plural "should be at least %{count} character(s)" msgid_plural "should be at least %{count} character(s)"
msgstr[0] "" msgstr[0] "powinno zawierać przynajmniej %{count} znak"
msgstr[1] "" msgstr[1] "powinno zawierać przynajmniej %{count} znaki"
msgstr[2] "" msgstr[2] "powinno zawierać przynajmniej %{count} znaków"
msgid "should have at least %{count} item(s)" msgid "should have at least %{count} item(s)"
msgid_plural "should have at least %{count} item(s)" msgid_plural "should have at least %{count} item(s)"
msgstr[0] "" msgstr[0] "powinno zawierać przynajmniej %{count} element"
msgstr[1] "" msgstr[1] "powinno zawierać przynajmniej %{count} elementy"
msgstr[2] "" msgstr[2] "powinno zawierać przynajmniej %{count} elementów"
msgid "should be at most %{count} character(s)" msgid "should be at most %{count} character(s)"
msgid_plural "should be at most %{count} character(s)" msgid_plural "should be at most %{count} character(s)"
msgstr[0] "" msgstr[0] "powinno zawierać najwyżej %{count} znak"
msgstr[1] "" msgstr[1] "powinno zawierać najwyżej %{count} znaki"
msgstr[2] "" msgstr[2] "powinno zawierać najwyżej %{count} znaków"
msgid "should have at most %{count} item(s)" msgid "should have at most %{count} item(s)"
msgid_plural "should have at most %{count} item(s)" msgid_plural "should have at most %{count} item(s)"
msgstr[0] "" msgstr[0] "powinno zawierać najwyżej %{count} element"
msgstr[1] "" msgstr[1] "powinno zawierać najwyżej %{count} elementy"
msgstr[2] "" msgstr[2] "powinno zawierać najwyżej %{count} elementów"
## From Ecto.Changeset.validate_number/3 ## From Ecto.Changeset.validate_number/3
msgid "must be less than %{number}" msgid "must be less than %{number}"
msgstr "" msgstr "musi wynosić mniej niż %{number}"
msgid "must be greater than %{number}" msgid "must be greater than %{number}"
msgstr "" msgstr "musi wynosić więcej niż %{number}"
msgid "must be less than or equal to %{number}" msgid "must be less than or equal to %{number}"
msgstr "" msgstr "musi być mniejsze lub równe %{number}"
msgid "must be greater than or equal to %{number}" msgid "must be greater than or equal to %{number}"
msgstr "" msgstr "musi być większe lub równe %{number}"
msgid "must be equal to %{number}" msgid "must be equal to %{number}"
msgstr "" msgstr "musi być równe %{number}"
#: lib/pleroma/web/common_api/common_api.ex:421 #: lib/pleroma/web/common_api/common_api.ex:421
#, elixir-format #, elixir-format
@ -152,7 +152,7 @@ msgstr "Nie znaleziono użytkownika"
#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:114 #: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:114
#, elixir-format #, elixir-format
msgid "Can't get favorites" msgid "Can't get favorites"
msgstr "" msgstr "Nie można uzyskać ulubionych"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:437 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:437
#, elixir-format #, elixir-format
@ -172,7 +172,7 @@ msgstr "Komentarz może mieć co najwyżej %{max_size} znaków"
#: lib/pleroma/config/config_db.ex:222 #: lib/pleroma/config/config_db.ex:222
#, elixir-format #, elixir-format
msgid "Config with params %{params} not found" msgid "Config with params %{params} not found"
msgstr "" msgstr "Nie znaleziono konfiguracji z parametrami %{params}"
#: lib/pleroma/web/common_api/common_api.ex:95 #: lib/pleroma/web/common_api/common_api.ex:95
#, elixir-format #, elixir-format
@ -213,38 +213,38 @@ msgstr "Nie udało się cofnąć powtórzenia"
#: lib/pleroma/web/common_api/common_api.ex:437 #: lib/pleroma/web/common_api/common_api.ex:437
#, elixir-format #, elixir-format
msgid "Could not update state" msgid "Could not update state"
msgstr "" msgstr "Nie można zaktualizować stanu"
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:202 #: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:202
#, elixir-format #, elixir-format
msgid "Error." msgid "Error."
msgstr "" msgstr "Błąd."
#: lib/pleroma/web/twitter_api/twitter_api.ex:106 #: lib/pleroma/web/twitter_api/twitter_api.ex:106
#, elixir-format #, elixir-format
msgid "Invalid CAPTCHA" msgid "Invalid CAPTCHA"
msgstr "" msgstr "Niewłaściwa CAPTCHA"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:117 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:117
#: lib/pleroma/web/oauth/oauth_controller.ex:569 #: lib/pleroma/web/oauth/oauth_controller.ex:569
#, elixir-format #, elixir-format
msgid "Invalid credentials" msgid "Invalid credentials"
msgstr "" msgstr "Nieprawidłowe dane uwierzytelniania"
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:38 #: lib/pleroma/plugs/ensure_authenticated_plug.ex:38
#, elixir-format #, elixir-format
msgid "Invalid credentials." msgid "Invalid credentials."
msgstr "" msgstr "Nieprawidłowe dane uwierzytelniania."
#: lib/pleroma/web/common_api/common_api.ex:265 #: lib/pleroma/web/common_api/common_api.ex:265
#, elixir-format #, elixir-format
msgid "Invalid indices" msgid "Invalid indices"
msgstr "" msgstr "Nieprawidłowe indeksy"
#: lib/pleroma/web/admin_api/admin_api_controller.ex:1147 #: lib/pleroma/web/admin_api/admin_api_controller.ex:1147
#, elixir-format #, elixir-format
msgid "Invalid parameters" msgid "Invalid parameters"
msgstr "" msgstr "Nieprawidłowe parametry"
#: lib/pleroma/web/common_api/utils.ex:411 #: lib/pleroma/web/common_api/utils.ex:411
#, elixir-format #, elixir-format
@ -307,7 +307,7 @@ msgstr "Coś się zepsuło"
#: lib/pleroma/web/common_api/activity_draft.ex:107 #: lib/pleroma/web/common_api/activity_draft.ex:107
#, elixir-format #, elixir-format
msgid "The message visibility must be direct" msgid "The message visibility must be direct"
msgstr "" msgstr "Widoczność wiadomości musi być „Bezpośrednia”"
#: lib/pleroma/web/common_api/utils.ex:566 #: lib/pleroma/web/common_api/utils.ex:566
#, elixir-format #, elixir-format
@ -317,17 +317,17 @@ msgstr "Ten status przekracza limit znaków"
#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31 #: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31
#, elixir-format #, elixir-format
msgid "This resource requires authentication." msgid "This resource requires authentication."
msgstr "" msgstr "Ten zasób wymaga uwierzytelnienia."
#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206 #: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206
#, elixir-format #, elixir-format
msgid "Throttled" msgid "Throttled"
msgstr "" msgstr "Ograniczono"
#: lib/pleroma/web/common_api/common_api.ex:266 #: lib/pleroma/web/common_api/common_api.ex:266
#, elixir-format #, elixir-format
msgid "Too many choices" msgid "Too many choices"
msgstr "" msgstr "Zbyt wiele wyborów"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:442 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:442
#, elixir-format #, elixir-format
@ -349,17 +349,18 @@ msgstr "Twoje konto jest obecnie nieaktywne"
#: lib/pleroma/web/oauth/oauth_controller.ex:332 #: lib/pleroma/web/oauth/oauth_controller.ex:332
#, elixir-format #, elixir-format
msgid "Your login is missing a confirmed e-mail address" msgid "Your login is missing a confirmed e-mail address"
msgstr "" msgstr "Twój adres e-mail nie został potwierdzony"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:389 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:389
#, elixir-format #, elixir-format
msgid "can't read inbox of %{nickname} as %{as_nickname}" msgid "can't read inbox of %{nickname} as %{as_nickname}"
msgstr "" msgstr "Nie można odczytać skrzynki odbiorczej %{nickname} jako %{as_nickname}"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:472 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:472
#, elixir-format #, elixir-format
msgid "can't update outbox of %{nickname} as %{as_nickname}" msgid "can't update outbox of %{nickname} as %{as_nickname}"
msgstr "" msgstr ""
"Nie można zaktualizować skrzynki nadawczcej %{nickname} jako %{as_nickname}"
#: lib/pleroma/web/common_api/common_api.ex:388 #: lib/pleroma/web/common_api/common_api.ex:388
#, elixir-format #, elixir-format
@ -405,12 +406,12 @@ msgstr "Nie udało się"
#: lib/pleroma/web/oauth/oauth_controller.ex:411 #: lib/pleroma/web/oauth/oauth_controller.ex:411
#, elixir-format #, elixir-format
msgid "Failed to authenticate: %{message}." msgid "Failed to authenticate: %{message}."
msgstr "" msgstr "Nie udało się uwierzytelnić: %{message}."
#: lib/pleroma/web/oauth/oauth_controller.ex:442 #: lib/pleroma/web/oauth/oauth_controller.ex:442
#, elixir-format #, elixir-format
msgid "Failed to set up user account." msgid "Failed to set up user account."
msgstr "" msgstr "Nie udało się skonfigurować konta użytkownika."
#: lib/pleroma/plugs/oauth_scopes_plug.ex:38 #: lib/pleroma/plugs/oauth_scopes_plug.ex:38
#, elixir-format #, elixir-format
@ -431,7 +432,7 @@ msgstr "Nieprawidłowa nazwa użytkownika lub hasło"
#: lib/pleroma/web/twitter_api/twitter_api.ex:118 #: lib/pleroma/web/twitter_api/twitter_api.ex:118
#, elixir-format #, elixir-format
msgid "Invalid answer data" msgid "Invalid answer data"
msgstr "" msgstr "Nieprawidłowe dane odpowiedzi"
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:128 #: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:128
#, elixir-format #, elixir-format
@ -441,7 +442,7 @@ msgstr "Nieobsługiwana wersja schematu Nodeinfo"
#: lib/pleroma/web/oauth/oauth_controller.ex:169 #: lib/pleroma/web/oauth/oauth_controller.ex:169
#, elixir-format #, elixir-format
msgid "This action is outside the authorized scopes" msgid "This action is outside the authorized scopes"
msgstr "" msgstr "Ta akcja wykracza poza dozwolone zakresy"
#: lib/pleroma/web/oauth/fallback_controller.ex:14 #: lib/pleroma/web/oauth/fallback_controller.ex:14
#, elixir-format #, elixir-format
@ -477,12 +478,12 @@ msgstr "Błąd CAPTCHA"
#: lib/pleroma/web/common_api/common_api.ex:200 #: lib/pleroma/web/common_api/common_api.ex:200
#, elixir-format #, elixir-format
msgid "Could not add reaction emoji" msgid "Could not add reaction emoji"
msgstr "" msgstr "Nie można dodać reakcji emoji"
#: lib/pleroma/web/common_api/common_api.ex:211 #: lib/pleroma/web/common_api/common_api.ex:211
#, elixir-format #, elixir-format
msgid "Could not remove reaction emoji" msgid "Could not remove reaction emoji"
msgstr "" msgstr "Nie można usunąć reakcji emoji"
#: lib/pleroma/web/twitter_api/twitter_api.ex:129 #: lib/pleroma/web/twitter_api/twitter_api.ex:129
#, elixir-format #, elixir-format
@ -535,6 +536,8 @@ msgstr "Wymagany reset hasła"
#, elixir-format #, elixir-format
msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped." msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
msgstr "" msgstr ""
"Naruszenie bezpieczeństwa: sprawdzanie zakresów OAuth nie zostało ani "
"wykonane, ani celowo pominięte."
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:28 #: lib/pleroma/plugs/ensure_authenticated_plug.ex:28
#, elixir-format #, elixir-format
@ -569,7 +572,7 @@ msgstr "Nieoczekiwany błąd podczas zmieniania metadanych paczki."
#: lib/pleroma/plugs/user_is_admin_plug.ex:21 #: lib/pleroma/plugs/user_is_admin_plug.ex:21
#, elixir-format #, elixir-format
msgid "User is not an admin." msgid "User is not an admin."
msgstr "" msgstr "Użytkownik nie jest administratorem."
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61 #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
#, elixir-format #, elixir-format

View file

@ -0,0 +1,40 @@
defmodule Pleroma.Repo.Migrations.SimplePolicyStringToTuple do
use Ecto.Migration
alias Pleroma.ConfigDB
def up, do: ConfigDB.get_by_params(%{group: :pleroma, key: :mrf_simple}) |> update_to_tuples
def down, do: ConfigDB.get_by_params(%{group: :pleroma, key: :mrf_simple}) |> update_to_strings
defp update_to_tuples(%{value: value}) do
new_value =
value
|> Enum.map(fn {k, v} ->
{k,
Enum.map(v, fn
{instance, reason} -> {instance, reason}
instance -> {instance, ""}
end)}
end)
ConfigDB.update_or_create(%{group: :pleroma, key: :mrf_simple, value: new_value})
end
defp update_to_tuples(nil), do: {:ok, nil}
defp update_to_strings(%{value: value}) do
new_value =
value
|> Enum.map(fn {k, v} ->
{k,
Enum.map(v, fn
{instance, _} -> instance
instance -> instance
end)}
end)
ConfigDB.update_or_create(%{group: :pleroma, key: :mrf_simple, value: new_value})
end
defp update_to_strings(nil), do: {:ok, nil}
end

View file

@ -0,0 +1,61 @@
defmodule Pleroma.Repo.Migrations.QuarantainedStringToTuple do
use Ecto.Migration
alias Pleroma.ConfigDB
def up,
do:
ConfigDB.get_by_params(%{group: :pleroma, key: :instance})
|> update_quarantined_instances_to_tuples
def down,
do:
ConfigDB.get_by_params(%{group: :pleroma, key: :instance})
|> update_quarantined_instances_to_strings
defp update_quarantined_instances_to_tuples(%{value: settings}) do
settings |> List.keyfind(:quarantined_instances, 0) |> update_to_tuples
end
defp update_quarantined_instances_to_tuples(nil), do: {:ok, nil}
defp update_to_tuples({:quarantined_instances, instance_list}) do
new_value =
instance_list
|> Enum.map(fn
{v, r} -> {v, r}
v -> {v, ""}
end)
ConfigDB.update_or_create(%{
group: :pleroma,
key: :instance,
value: [quarantined_instances: new_value]
})
end
defp update_to_tuples(nil), do: {:ok, nil}
defp update_quarantined_instances_to_strings(%{value: settings}) do
settings |> List.keyfind(:quarantined_instances, 0) |> update_to_strings
end
defp update_quarantined_instances_to_strings(nil), do: {:ok, nil}
defp update_to_strings({:quarantined_instances, instance_list}) do
new_value =
instance_list
|> Enum.map(fn
{v, _} -> v
v -> v
end)
ConfigDB.update_or_create(%{
group: :pleroma,
key: :instance,
value: [quarantined_instances: new_value]
})
end
defp update_to_strings(nil), do: {:ok, nil}
end

View file

@ -0,0 +1,61 @@
defmodule Pleroma.Repo.Migrations.TransparencyExclusionsStringToTuple do
use Ecto.Migration
alias Pleroma.ConfigDB
def up,
do:
ConfigDB.get_by_params(%{group: :pleroma, key: :mrf})
|> update_transparency_exclusions_instances_to_tuples
def down,
do:
ConfigDB.get_by_params(%{group: :pleroma, key: :mrf})
|> update_transparency_exclusions_instances_to_strings
defp update_transparency_exclusions_instances_to_tuples(%{value: settings}) do
settings |> List.keyfind(:transparency_exclusions, 0) |> update_to_tuples
end
defp update_transparency_exclusions_instances_to_tuples(nil), do: {:ok, nil}
defp update_to_tuples({:transparency_exclusions, instance_list}) do
new_value =
instance_list
|> Enum.map(fn
{v, r} -> {v, r}
v -> {v, ""}
end)
ConfigDB.update_or_create(%{
group: :pleroma,
key: :mrf,
value: [transparency_exclusions: new_value]
})
end
defp update_to_tuples(nil), do: {:ok, nil}
defp update_transparency_exclusions_instances_to_strings(%{value: settings}) do
settings |> List.keyfind(:transparency_exclusions, 0) |> update_to_strings
end
defp update_transparency_exclusions_instances_to_strings(nil), do: {:ok, nil}
defp update_to_strings({:transparency_exclusions, instance_list}) do
new_value =
instance_list
|> Enum.map(fn
{v, _} -> v
v -> v
end)
ConfigDB.update_or_create(%{
group: :pleroma,
key: :mrf,
value: [transparency_exclusions: new_value]
})
end
defp update_to_strings(nil), do: {:ok, nil}
end

View file

@ -0,0 +1,49 @@
defmodule Pleroma.Repo.Migrations.AddPollToNotificationsEnum do
use Ecto.Migration
@disable_ddl_transaction true
def up do
"""
alter type notification_type add value 'poll'
"""
|> execute()
end
def down do
alter table(:notifications) do
modify(:type, :string)
end
"""
delete from notifications where type = 'poll'
"""
|> execute()
"""
drop type if exists notification_type
"""
|> execute()
"""
create type notification_type as enum (
'follow',
'follow_request',
'mention',
'move',
'pleroma:emoji_reaction',
'pleroma:chat_mention',
'reblog',
'favourite',
'pleroma:report'
)
"""
|> execute()
"""
alter table notifications
alter column type type notification_type using (type::notification_type)
"""
|> execute()
end
end

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1 +1 @@
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=renderer content=webkit><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><title>Admin FE</title><link rel="shortcut icon" href=favicon.ico><link href=chunk-elementUI.f77689d7.css rel=stylesheet><link href=chunk-libs.5cf7f50a.css rel=stylesheet><link href=app.6fb984d1.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=static/js/runtime.6b30c658.js></script><script type=text/javascript src=static/js/chunk-elementUI.21957ec8.js></script><script type=text/javascript src=static/js/chunk-libs.5ca2c8e8.js></script><script type=text/javascript src=static/js/app.1428845f.js></script></body></html> <!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=renderer content=webkit><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><title>Admin FE</title><link rel="shortcut icon" href=favicon.ico><link href=chunk-elementUI.f1f2be85.css rel=stylesheet><link href=chunk-libs.74976a6a.css rel=stylesheet><link href=app.143a1409.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=static/js/runtime.2a586239.js></script><script type=text/javascript src=static/js/chunk-elementUI.4c32a355.js></script><script type=text/javascript src=static/js/chunk-libs.55b24a78.js></script><script type=text/javascript src=static/js/app.f02f5ebc.js></script></body></html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more