Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into pleroma-from/upstream-develop/tusooa/report-anon

This commit is contained in:
Lain Soykaf 2025-09-04 18:10:41 +04:00
commit 5bf1a384c7
116 changed files with 2748 additions and 752 deletions

View file

@ -1,4 +1,4 @@
image: git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.14.5-otp-25
image: git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.15.8-otp-26
variables: &global_variables
# Only used for the release
@ -14,9 +14,10 @@ variables: &global_variables
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "develop"
- if: $CI_COMMIT_BRANCH == "stable"
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
when: never
- if: $CI_COMMIT_BRANCH
cache: &global_cache_policy
key: $CI_JOB_IMAGE-$CI_COMMIT_SHORT_SHA
@ -71,7 +72,7 @@ check-changelog:
tags:
- amd64
build-1.14.5-otp-25:
build-1.15.8-otp-26:
extends:
- .build_changes_policy
- .using-ci-base
@ -119,7 +120,7 @@ benchmark:
- mix ecto.migrate
- mix pleroma.load_testing
unit-testing-1.14.5-otp-25:
unit-testing-1.15.8-otp-26:
extends:
- .build_changes_policy
- .using-ci-base
@ -131,10 +132,25 @@ unit-testing-1.14.5-otp-25:
- name: postgres:13-alpine
alias: postgres
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
before_script: &testing_before_script
- echo $MIX_ENV
- rm -rf _build/*/lib/pleroma
# Create a non-root user for running tests
- useradd -m -s /bin/bash testuser
# Install dependencies as root first
- mix deps.get
# Set proper ownership for everything
- chown -R testuser:testuser .
- chown -R testuser:testuser /root/.mix || true
- chown -R testuser:testuser /root/.hex || true
# Create user-specific directories
- su testuser -c "HOME=/home/testuser mix local.hex --force"
- su testuser -c "HOME=/home/testuser mix local.rebar --force"
script: &testing_script
- mix ecto.create
- mix ecto.migrate
- mix pleroma.test_runner --cover --preload-modules
# Run tests as non-root user
- su testuser -c "HOME=/home/testuser mix ecto.create"
- su testuser -c "HOME=/home/testuser mix ecto.migrate"
- su testuser -c "HOME=/home/testuser mix pleroma.test_runner --cover --preload-modules"
coverage: '/^Line total: ([^ ]*%)$/'
artifacts:
reports:
@ -150,6 +166,7 @@ unit-testing-1.18.3-otp-27:
image: git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.18.3-otp-27
cache: *testing_cache_policy
services: *testing_services
before_script: *testing_before_script
script: *testing_script
formatting-1.15:
@ -273,7 +290,7 @@ stop_review_app:
amd64:
stage: release
image:
name: hexpm/elixir-amd64:1.17.3-erlang-26.2.5.6-ubuntu-focal-20241011
name: hexpm/elixir-amd64:1.17.3-erlang-27.3.4.2-ubuntu-noble-20250716
only: &release-only
- stable@pleroma/pleroma
- develop@pleroma/pleroma
@ -300,7 +317,7 @@ amd64:
VIX_COMPILATION_MODE: PLATFORM_PROVIDED_LIBVIPS
DEBIAN_FRONTEND: noninteractive
before_script: &before-release
- apt-get update && apt-get install -y cmake libmagic-dev libvips-dev erlang-dev git
- apt-get update && apt-get install -y cmake libmagic-dev libvips-dev erlang-dev git build-essential
- echo "import Config" > config/prod.secret.exs
- mix local.hex --force
- mix local.rebar --force
@ -316,7 +333,7 @@ amd64-musl:
artifacts: *release-artifacts
only: *release-only
image:
name: hexpm/elixir-amd64:1.17.3-erlang-26.2.5.6-alpine-3.17.9
name: hexpm/elixir-amd64:1.17.3-erlang-27.3.4.2-alpine-3.22.1
tags:
- amd64
cache: *release-cache
@ -360,7 +377,7 @@ arm64:
tags:
- arm
image:
name: hexpm/elixir-arm64:1.17.3-erlang-26.2.5.6-ubuntu-focal-20241011
name: hexpm/elixir-arm64:1.17.3-erlang-27.3.4.2-ubuntu-noble-20250716
cache: *release-cache
variables: *release-variables
before_script: *before-release
@ -373,7 +390,7 @@ arm64-musl:
tags:
- arm
image:
name: hexpm/elixir-arm64:1.17.3-erlang-26.2.5.6-alpine-3.17.9
name: hexpm/elixir-arm64:1.17.3-erlang-27.3.4.2-alpine-3.22.1
cache: *release-cache
variables: *release-variables
before_script: *before-release-musl

View file

@ -1,10 +1,10 @@
# https://hub.docker.com/r/hexpm/elixir/tags
ARG ELIXIR_IMG=hexpm/elixir
ARG ELIXIR_VER=1.14.5
ARG ERLANG_VER=25.3.2.14
ARG ELIXIR_VER=1.17.3
ARG ERLANG_VER=26.2.5.6
ARG ALPINE_VER=3.17.9
FROM ${ELIXIR_IMG}:${ELIXIR_VER}-erlang-${ERLANG_VER}-alpine-${ALPINE_VER} as build
FROM ${ELIXIR_IMG}:${ELIXIR_VER}-erlang-${ERLANG_VER}-alpine-${ALPINE_VER} AS build
COPY . .
@ -15,6 +15,7 @@ RUN apk add git gcc g++ musl-dev make cmake file-dev vips-dev &&\
echo "import Config" > config/prod.secret.exs &&\
mix local.hex --force &&\
mix local.rebar --force &&\
mix deps.clean --all &&\
mix deps.get --only prod &&\
mkdir release &&\
mix release --path release

View file

@ -0,0 +1 @@
Admin API: Fixed self-revocation vulnerability where admins could accidentally revoke their own admin status via the single-user permission endpoint

View file

@ -0,0 +1 @@
- Fix building "captcha" library with OpenBSD make

View file

@ -0,0 +1 @@
Docs: Restore DB schema before data to avoid long restore times

View file

@ -0,0 +1 @@
Return 404 with a better error message instead of 400 when receiving an activity for a deactivated user

View file

@ -0,0 +1 @@
Deleting an instance queues individual jobs for each user that needs to be deleted from the server.

View file

View file

@ -0,0 +1 @@
Support Dislike activity, as sent by Mitra and Friendica, by changing it into a thumbs-down EmojiReact

View file

@ -0,0 +1 @@
Update Dockerfile to use Elixir 1.17.3, Erlang 26.2.5.6, and Alpine 3.17.9 to match CI release builds

View file

@ -0,0 +1 @@
Added a way to upload new packs from a URL or ZIP file via Admin API

View file

@ -0,0 +1 @@
Fix endorsement state display in relationship view

View file

1
changelog.d/gun.change Normal file
View file

@ -0,0 +1 @@
Update Cowboy, Gun, and Plug family of dependencies

View file

@ -0,0 +1 @@
Hashtag searches return real results based on words in your query

View file

@ -0,0 +1 @@
Fix ModerationLog FunctionClauseError for unknown actions

View file

@ -0,0 +1 @@
Added MRF.QuietReply which prevents replies to public posts from being published to the timelines

View file

View file

@ -0,0 +1 @@
Oban.Plugins.Lazarus to help recover stuck jobs from an unclean shutdown of Pleroma

View file

@ -0,0 +1 @@
Oban Notifier was changed to Oban.Notifiers.PG for performance and scalability benefits

View file

@ -0,0 +1 @@
Updated Postgrex library to 0.20.0

View file

@ -0,0 +1 @@
Improved the logic of how we determine if a server is unreachable.

View file

@ -0,0 +1 @@
Add only_reblogs parameter to account statuses API for filtering to show only reblogs/reposts

View file

@ -0,0 +1 @@
Change SMTP example to use the Mua adapter that works with OTP>25

1
changelog.d/tesla.change Normal file
View file

@ -0,0 +1 @@
Updated Tesla to 1.15.3

View file

@ -0,0 +1 @@
Allow Terms of Service panel behaviour to be configurable

View file

@ -0,0 +1 @@
Fix HTTP client making invalid requests due to no percent encoding processing or validation.

View file

@ -0,0 +1 @@
Enforce an exact domain match for WebFinger resolution

View file

@ -194,7 +194,6 @@ config :pleroma, :instance,
account_approval_required: false,
federating: true,
federation_incoming_replies_max_depth: 100,
federation_reachability_timeout_days: 7,
allow_relay: true,
public: true,
quarantined_instances: [],
@ -307,6 +306,7 @@ config :pleroma, :frontend_configurations,
collapseMessageWithSubject: false,
disableChat: false,
greentext: false,
embeddedToS: true,
hideFilteredStatuses: false,
hideMutedPosts: false,
hidePostStats: false,
@ -591,6 +591,7 @@ config :pleroma, Pleroma.User,
# value or it cannot enforce uniqueness.
config :pleroma, Oban,
repo: Pleroma.Repo,
notifier: Oban.Notifiers.PG,
log: false,
queues: [
activity_expiration: 10,
@ -601,7 +602,7 @@ config :pleroma, Oban,
search_indexing: [limit: 10, paused: true],
slow: 5
],
plugins: [{Oban.Plugins.Pruner, max_age: 900}],
plugins: [Oban.Plugins.Lazarus, {Oban.Plugins.Pruner, max_age: 900}],
crontab: [
{"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
{"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker},

View file

@ -1261,6 +1261,7 @@ config :pleroma, :config_description, [
background: "/static/aurora_borealis.jpg",
collapseMessageWithSubject: false,
greentext: false,
embeddedToS: true,
hideFilteredStatuses: false,
hideMutedPosts: false,
hidePostStats: false,
@ -1312,6 +1313,12 @@ config :pleroma, :config_description, [
type: :boolean,
description: "Enables green text on lines prefixed with the > character"
},
%{
key: :embeddedToS,
label: "Embedded ToS panel",
type: :boolean,
description: "Hide Terms of Service panel decorations on About and Registration pages"
},
%{
key: :hideFilteredStatuses,
label: "Hide Filtered Statuses",

View file

@ -2,28 +2,60 @@
## Backup
1. Stop the Pleroma service.
2. Go to the working directory of Pleroma (default is `/opt/pleroma`)
3. Run `sudo -Hu postgres pg_dump -d <pleroma_db> --format=custom -f </path/to/backup_location/pleroma.pgdump>` (make sure the postgres user has write access to the destination file)
1. Stop the Pleroma service:
```
# sudo systemctl stop pleroma
```
2. Go to the working directory of Pleroma (default is `/opt/pleroma`).
3. Run (make sure the postgres user has write access to the destination file):
```
# sudo -Hu postgres pg_dump -d <pleroma_db> -v --format=custom --compress=9 -f </path/to/backup_location/pleroma.pgdump>
```
4. Copy `pleroma.pgdump`, `config/prod.secret.exs`, `config/setup_db.psql` (if still available) and the `uploads` folder to your backup destination. If you have other modifications, copy those changes too.
5. Restart the Pleroma service.
5. Restart the Pleroma service:
```
# sudo systemctl start pleroma
```
## Restore/Move
1. Optionally reinstall Pleroma (either on the same server or on another server if you want to move servers).
2. Stop the Pleroma service.
3. Go to the working directory of Pleroma (default is `/opt/pleroma`)
2. Stop the Pleroma service:
```
# sudo systemctl stop pleroma
```
3. Go to the working directory of Pleroma (default is `/opt/pleroma`).
4. Copy the above mentioned files back to their original position.
5. Drop the existing database and user if restoring in-place. `sudo -Hu postgres psql -c 'DROP DATABASE <pleroma_db>;';` `sudo -Hu postgres psql -c 'DROP USER <pleroma_db>;'`
6. Restore the database schema and pleroma postgres role the with the original `setup_db.psql` if you have it: `sudo -Hu postgres psql -f config/setup_db.psql`.
5. Drop the existing database and user if restoring in-place:
```
# sudo -Hu postgres dropdb <pleroma_db>
# sudo -Hu postgres dropuser <pleroma_user>
```
6. Restore the database schema and pleroma database user the with the original `setup_db.psql` if you have it:
```
# sudo -Hu postgres psql -f config/setup_db.psql
```
Alternatively, run the `mix pleroma.instance gen` task again. You can ignore most of the questions, but make the database user, name, and password the same as found in your backup of `config/prod.secret.exs`. Then run the restoration of the pleroma role and schema with of the generated `config/setup_db.psql` as instructed above. You may delete the `config/generated_config.exs` file as it is not needed.
Alternatively, run the `mix pleroma.instance gen` task again. You can ignore most of the questions, but make the database user, name, and password the same as found in your backup of `config/prod.secret.exs`. Then run the restoration of the pleroma user and schema with the generated `config/setup_db.psql` as instructed above. You may delete the `config/generated_config.exs` file as it is not needed.
7. Now restore the Pleroma instance's data into the empty database schema: `sudo -Hu postgres pg_restore -d <pleroma_db> -v -1 </path/to/backup_location/pleroma.pgdump>`
8. If you installed a newer Pleroma version, you should run `mix ecto.migrate`[^1]. This task performs database migrations, if there were any.
9. Restart the Pleroma service.
10. Run `sudo -Hu postgres vacuumdb --all --analyze-in-stages`. This will quickly generate the statistics so that postgres can properly plan queries.
11. If setting up on a new server configure Nginx by using the `installation/pleroma.nginx` config sample or reference the Pleroma installation guide for your OS which contains the Nginx configuration instructions.
7. Now restore the Pleroma instance's schema into the empty database schema:
```
# sudo -Hu postgres pg_restore -d <pleroma_db> -v -s -1 </path/to/backup_location/pleroma.pgdump>
```
8. Now restore the Pleroma instance's data into the database:
```
# sudo -Hu postgres pg_restore -d <pleroma_db> -v -a -1 --disable-triggers </path/to/backup_location/pleroma.pgdump>
```
9. If you installed a newer Pleroma version, you should run `mix ecto.migrate`[^1]. This task performs database migrations, if there were any.
10. Generate the statistics so that PostgreSQL can properly plan queries:
```
# sudo -Hu postgres vacuumdb -v --all --analyze-in-stages
```
11. Restart the Pleroma service:
```
# sudo systemctl start pleroma
```
12. If setting up on a new server, configure Nginx by using your original configuration or by using the `installation/pleroma.nginx` config sample or reference the Pleroma installation guide for your OS which contains the Nginx configuration instructions.
[^1]: Prefix with `MIX_ENV=prod` to run it using the production config file.
@ -32,10 +64,26 @@
1. Optionally you can remove the users of your instance. This will trigger delete requests for their accounts and posts. Note that this is 'best effort' and doesn't mean that all traces of your instance will be gone from the fediverse.
* You can do this from the admin-FE where you can select all local users and delete the accounts using the *Moderate multiple users* dropdown.
* You can also list local users and delete them individually using the CLI tasks for [Managing users](./CLI_tasks/user.md).
2. Stop the Pleroma service `systemctl stop pleroma`
3. Disable pleroma from systemd `systemctl disable pleroma`
2. Stop the Pleroma service:
```
# systemctl stop pleroma
```
3. Disable pleroma from systemd:
```
# systemctl disable pleroma
```
4. Remove the files and folders you created during installation (see installation guide). This includes the pleroma, nginx and systemd files and folders.
5. Reload nginx now that the configuration is removed `systemctl reload nginx`
6. Remove the database and database user `sudo -Hu postgres psql -c 'DROP DATABASE <pleroma_db>;';` `sudo -Hu postgres psql -c 'DROP USER <pleroma_db>;'`
7. Remove the system user `userdel pleroma`
8. Remove the dependencies that you don't need anymore (see installation guide). Make sure you don't remove packages that are still needed for other software that you have running!
5. Reload nginx now that the configuration is removed:
```
# systemctl reload nginx
```
6. Remove the database and database user:
```
# sudo -Hu postgres dropdb <pleroma_db>
# sudo -Hu postgres dropuser <pleroma_user>
```
7. Remove the system user:
```
# userdel -r pleroma
```
8. Remove the dependencies that you don't need anymore (see installation guide). **Make sure you don't remove packages that are still needed for other software that you have running!**

View file

@ -28,6 +28,7 @@ Feel free to contact us to be added to this list!
### AndStatus
- Homepage: <http://andstatus.org/>
- Source Code: <https://github.com/andstatus/andstatus/>
- Contact: [@AndStatus@mastodon.social](https://mastodon.social/@AndStatus)
- Platforms: Android
- Features: MastoAPI, ActivityPub (Client-to-Server)
@ -40,8 +41,8 @@ Feel free to contact us to be added to this list!
### Fedilab
- Homepage: <https://fedilab.app/>
- Source Code: <https://framagit.org/tom79/fedilab/>
- Contact: [@fedilab@framapiaf.org](https://framapiaf.org/users/fedilab)
- Source Code: <https://codeberg.org/tom79/Fedilab>
- Contact: [@apps@toot.fedilab.app](https://toot.fedilab.app/@apps)
- Platforms: Android
- Features: MastoAPI, Streaming Ready, Moderation, Text Formatting
@ -51,8 +52,8 @@ Feel free to contact us to be added to this list!
- Features: MastoAPI, No Streaming
### Husky
- Source code: <https://git.mentality.rip/FWGS/Husky>
- Contact: [@Husky@enigmatic.observer](https://enigmatic.observer/users/Husky)
- Source code: <https://github.com/captainepoch/husky>
- Contact: [@husky@stereophonic.space](https://stereophonic.space/users/husky)
- Platforms: Android
- Features: MastoAPI, No Streaming, Emoji Reactions, Text Formatting, FE Stickers
@ -65,7 +66,7 @@ Feel free to contact us to be added to this list!
### Tusky
- Homepage: <https://tuskyapp.github.io/>
- Source Code: <https://github.com/tuskyapp/Tusky>
- Contact: [@ConnyDuck@mastodon.social](https://mastodon.social/users/ConnyDuck)
- Contact: [@Tusky@mastodon.social](https://mastodon.social/@Tusky)
- Platforms: Android
- Features: MastoAPI, No Streaming
@ -76,10 +77,10 @@ Feel free to contact us to be added to this list!
- Platform: Android
- Features: MastoAPI, No Streaming
### Indigenous
- Homepage: <https://indigenous.realize.be/>
- Source Code: <https://github.com/swentel/indigenous-android/>
- Contact: [@swentel@realize.be](https://realize.be)
### IndiePass
- Homepage: <https://indiepass.app/>
- Source Code: <https://github.com/IndiePass/indiepass-android>
- Contact: [@marksuth@mastodon.social](https://mastodon.social/@marksuth)
- Platforms: Android
- Features: MastoAPI, No Streaming

View file

@ -733,13 +733,11 @@ An example for SMTP adapter:
```elixir
config :pleroma, Pleroma.Emails.Mailer,
enabled: true,
adapter: Swoosh.Adapters.SMTP,
adapter: Swoosh.Adapters.Mua,
relay: "smtp.gmail.com",
username: "YOUR_USERNAME@gmail.com",
password: "YOUR_SMTP_PASSWORD",
auth: [username: "YOUR_USERNAME@gmail.com", password: "YOUR_SMTP_PASSWORD"],
port: 465,
ssl: true,
auth: :always
protocol: :ssl
```
An example for Mua adapter:

View file

@ -88,6 +88,7 @@ The `id` parameter can also be the `nickname` of the user. This only works in th
- `only_media`: include only statuses with media attached
- `with_muted`: include statuses/reactions from muted accounts
- `exclude_reblogs`: exclude reblogs
- `only_reblogs`: include only reblogs
- `exclude_replies`: exclude replies
- `exclude_visibilities`: exclude visibilities

View file

@ -14,7 +14,7 @@ Note: This article is potentially outdated because at this time we may not have
- PostgreSQL 11.0以上 (Ubuntu16.04では9.5しか提供されていないので,[](https://www.postgresql.org/download/linux/ubuntu/)こちらから新しいバージョンを入手してください)
- `postgresql-contrib` 11.0以上 (同上)
- Elixir 1.14 以上 ([Debianのリポジトリからインストールしないこと ここからインストールすること!](https://elixir-lang.org/install.html#unix-and-unix-like)。または [asdf](https://github.com/asdf-vm/asdf) をpleromaユーザーでインストールしてください)
- Elixir 1.15 以上 ([Debianのリポジトリからインストールしないこと ここからインストールすること!](https://elixir-lang.org/install.html#unix-and-unix-like)。または [asdf](https://github.com/asdf-vm/asdf) をpleromaユーザーでインストールしてください)
- `erlang-dev`
- `erlang-nox`
- `git`

View file

@ -1,7 +1,7 @@
## Required dependencies
* PostgreSQL >=11.0
* Elixir >=1.14.0 <1.19
* Elixir >=1.15.0 <1.19
* Erlang OTP >=23.0.0 (supported: <28)
* git
* file / libmagic

View file

@ -26,7 +26,11 @@ defmodule Mix.Pleroma do
Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
unless System.get_env("DEBUG") do
Logger.remove_backend(:console)
try do
Logger.remove_backend(:console)
catch
:exit, _ -> :ok
end
end
adapter = Application.get_env(:tesla, :adapter)

View file

@ -100,6 +100,7 @@ defmodule Pleroma.Constants do
"Add",
"Remove",
"Like",
"Dislike",
"Announce",
"Undo",
"Flag",
@ -115,6 +116,7 @@ defmodule Pleroma.Constants do
"Flag",
"Follow",
"Like",
"Dislike",
"EmojiReact",
"Announce"
]

View file

@ -225,6 +225,97 @@ defmodule Pleroma.Emoji.Pack do
end
end
def download_zip(name, opts \\ %{}) do
with :ok <- validate_not_empty([name]),
:ok <- validate_new_pack(name),
{:ok, archive_data} <- fetch_archive_data(opts),
pack_path <- path_join_name_safe(emoji_path(), name),
:ok <- create_pack_dir(pack_path),
:ok <- safe_unzip(archive_data, pack_path) do
ensure_pack_json(pack_path, archive_data, opts)
else
{:error, :empty_values} -> {:error, "Pack name cannot be empty"}
{:error, reason} when is_binary(reason) -> {:error, reason}
_ -> {:error, "Could not process pack"}
end
end
defp create_pack_dir(pack_path) do
case File.mkdir_p(pack_path) do
:ok -> :ok
{:error, _} -> {:error, "Could not create the pack directory"}
end
end
defp safe_unzip(archive_data, pack_path) do
case SafeZip.unzip_data(archive_data, pack_path) do
{:ok, _} -> :ok
{:error, reason} when is_binary(reason) -> {:error, reason}
_ -> {:error, "Could not unzip pack"}
end
end
defp validate_new_pack(name) do
pack_path = path_join_name_safe(emoji_path(), name)
if File.exists?(pack_path) do
{:error, "Pack already exists, refusing to import #{name}"}
else
:ok
end
end
defp fetch_archive_data(%{url: url}) do
case Pleroma.HTTP.get(url) do
{:ok, %{status: 200, body: data}} -> {:ok, data}
_ -> {:error, "Could not download pack"}
end
end
defp fetch_archive_data(%{file: %Plug.Upload{path: path}}) do
case File.read(path) do
{:ok, data} -> {:ok, data}
_ -> {:error, "Could not read the uploaded pack file"}
end
end
defp fetch_archive_data(_) do
{:error, "Neither file nor URL was present in the request"}
end
defp ensure_pack_json(pack_path, archive_data, opts) do
pack_json_path = Path.join(pack_path, "pack.json")
if not File.exists?(pack_json_path) do
create_pack_json(pack_path, pack_json_path, archive_data, opts)
end
:ok
end
defp create_pack_json(pack_path, pack_json_path, archive_data, opts) do
emoji_map =
Pleroma.Emoji.Loader.make_shortcode_to_file_map(
pack_path,
Map.get(opts, :exts, [".png", ".gif", ".jpg"])
)
archive_sha = :crypto.hash(:sha256, archive_data) |> Base.encode16()
pack_json = %{
pack: %{
license: Map.get(opts, :license, ""),
homepage: Map.get(opts, :homepage, ""),
description: Map.get(opts, :description, ""),
src: Map.get(opts, :url),
src_sha256: archive_sha
},
files: emoji_map
}
File.write!(pack_json_path, Jason.encode!(pack_json, pretty: true))
end
@spec download(String.t(), String.t(), String.t()) :: {:ok, t()} | {:error, atom()}
def download(name, url, as) do
uri = url |> String.trim() |> URI.parse()

View file

@ -22,14 +22,18 @@ defmodule Pleroma.Gopher.Server do
def init([ip, port]) do
Logger.info("Starting gopher server on #{port}")
:ranch.start_listener(
:gopher,
100,
:ranch_tcp,
[ip: ip, port: port],
__MODULE__.ProtocolHandler,
[]
)
{:ok, _pid} =
:ranch.start_listener(
:gopher,
:ranch_tcp,
%{
num_acceptors: 100,
max_connections: 100,
socket_opts: [ip: ip, port: port]
},
__MODULE__.ProtocolHandler,
[]
)
{:ok, %{ip: ip, port: port}}
end
@ -43,13 +47,13 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
def start_link(ref, socket, transport, opts) do
pid = spawn_link(__MODULE__, :init, [ref, socket, transport, opts])
def start_link(ref, transport, opts) do
pid = spawn_link(__MODULE__, :init, [ref, transport, opts])
{:ok, pid}
end
def init(ref, socket, transport, [] = _Opts) do
:ok = :ranch.accept_ack(ref)
def init(ref, transport, opts \\ []) do
{:ok, socket} = :ranch.handshake(ref, opts)
loop(socket, transport)
end

View file

@ -130,4 +130,66 @@ defmodule Pleroma.Hashtag do
end
def get_recipients_for_activity(_activity), do: []
def search(query, options \\ []) do
limit = Keyword.get(options, :limit, 20)
offset = Keyword.get(options, :offset, 0)
search_terms =
query
|> String.downcase()
|> String.trim()
|> String.split(~r/\s+/)
|> Enum.filter(&(&1 != ""))
|> Enum.map(&String.trim_leading(&1, "#"))
|> Enum.filter(&(&1 != ""))
if Enum.empty?(search_terms) do
[]
else
# Use PostgreSQL's ANY operator with array for efficient multi-term search
# This is much more efficient than multiple OR clauses
search_patterns = Enum.map(search_terms, &"%#{&1}%")
# Create ranking query that prioritizes exact matches and closer matches
# Use a subquery to properly handle computed columns in ORDER BY
base_query =
from(ht in Hashtag,
where: fragment("LOWER(?) LIKE ANY(?)", ht.name, ^search_patterns),
select: %{
name: ht.name,
# Ranking: exact matches get highest priority (0)
# then prefix matches (1), then contains (2)
match_rank:
fragment(
"""
CASE
WHEN LOWER(?) = ANY(?) THEN 0
WHEN LOWER(?) LIKE ANY(?) THEN 1
ELSE 2
END
""",
ht.name,
^search_terms,
ht.name,
^Enum.map(search_terms, &"#{&1}%")
),
# Secondary sort by name length (shorter names first)
name_length: fragment("LENGTH(?)", ht.name)
}
)
from(result in subquery(base_query),
order_by: [
asc: result.match_rank,
asc: result.name_length,
asc: result.name
],
limit: ^limit,
offset: ^offset
)
|> Repo.all()
|> Enum.map(& &1.name)
end
end
end

View file

@ -105,20 +105,57 @@ defmodule Pleroma.HTTP do
end
defp adapter_middlewares(Tesla.Adapter.Gun, extra_middleware) do
[Tesla.Middleware.FollowRedirects, Pleroma.Tesla.Middleware.ConnectionPool] ++
default_middleware() ++
[Pleroma.Tesla.Middleware.ConnectionPool] ++
extra_middleware
end
defp adapter_middlewares({Tesla.Adapter.Finch, _}, extra_middleware) do
[Tesla.Middleware.FollowRedirects] ++ extra_middleware
end
defp adapter_middlewares(_, extra_middleware) do
if Pleroma.Config.get(:env) == :test do
# Emulate redirects in test env, which are handled by adapters in other environments
[Tesla.Middleware.FollowRedirects]
else
extra_middleware
# A lot of tests are written expecting unencoded URLs
# and the burden of fixing that is high. Also it makes
# them hard to read. Tests will opt-in when we want to validate
# the encoding is being done correctly.
cond do
Pleroma.Config.get(:env) == :test and Pleroma.Config.get(:test_url_encoding) ->
default_middleware()
Pleroma.Config.get(:env) == :test ->
# Emulate redirects in test env, which are handled by adapters in other environments
[Tesla.Middleware.FollowRedirects]
# Hackney and Finch
true ->
default_middleware() ++ extra_middleware
end
end
defp default_middleware,
do: [Tesla.Middleware.FollowRedirects, Pleroma.Tesla.Middleware.EncodeUrl]
def encode_url(url) when is_binary(url) do
URI.parse(url)
|> then(fn parsed ->
path = encode_path(parsed.path)
query = encode_query(parsed.query)
%{parsed | path: path, query: query}
end)
|> URI.to_string()
end
defp encode_path(nil), do: nil
defp encode_path(path) when is_binary(path) do
path
|> URI.decode()
|> URI.encode()
end
defp encode_query(nil), do: nil
defp encode_query(query) when is_binary(query) do
query
|> URI.decode_query()
|> URI.encode_query()
end
end

View file

@ -15,25 +15,7 @@ defmodule Pleroma.Instances do
defdelegate set_unreachable(url_or_host, unreachable_since \\ nil), to: Instance
defdelegate get_consistently_unreachable, to: Instance
def set_consistently_unreachable(url_or_host),
do: set_unreachable(url_or_host, reachability_datetime_threshold())
def reachability_datetime_threshold do
federation_reachability_timeout_days =
Pleroma.Config.get([:instance, :federation_reachability_timeout_days], 0)
if federation_reachability_timeout_days > 0 do
NaiveDateTime.add(
NaiveDateTime.utc_now(),
-federation_reachability_timeout_days * 24 * 3600,
:second
)
else
~N[0000-01-01 00:00:00]
end
end
defdelegate get_unreachable, to: Instance
def host(url_or_host) when is_binary(url_or_host) do
if url_or_host =~ ~r/^http/i do
@ -42,4 +24,21 @@ defmodule Pleroma.Instances do
url_or_host
end
end
@doc "Schedules reachability checks for all unreachable instances"
def check_all_unreachable do
get_unreachable()
|> Enum.each(fn {domain, _} ->
Pleroma.Workers.ReachabilityWorker.new(%{"domain" => domain})
|> Oban.insert()
end)
end
@doc "Deletes all users and activities for unreachable instances"
def delete_all_unreachable do
get_unreachable()
|> Enum.each(fn {domain, _} ->
Instance.delete(domain)
end)
end
end

View file

@ -9,7 +9,6 @@ defmodule Pleroma.Instances.Instance do
alias Pleroma.Instances.Instance
alias Pleroma.Maps
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Workers.DeleteWorker
use Ecto.Schema
@ -51,7 +50,7 @@ defmodule Pleroma.Instances.Instance do
|> cast(params, [:software_name, :software_version, :software_repository])
end
def filter_reachable([]), do: %{}
def filter_reachable([]), do: []
def filter_reachable(urls_or_hosts) when is_list(urls_or_hosts) do
hosts =
@ -68,19 +67,15 @@ defmodule Pleroma.Instances.Instance do
)
|> Map.new(& &1)
reachability_datetime_threshold = Instances.reachability_datetime_threshold()
for entry <- Enum.filter(urls_or_hosts, &is_binary/1) do
host = host(entry)
unreachable_since = unreachable_since_by_host[host]
if !unreachable_since ||
NaiveDateTime.compare(unreachable_since, reachability_datetime_threshold) == :gt do
{entry, unreachable_since}
if is_nil(unreachable_since) do
entry
end
end
|> Enum.filter(& &1)
|> Map.new(& &1)
end
def reachable?(url_or_host) when is_binary(url_or_host) do
@ -88,7 +83,7 @@ defmodule Pleroma.Instances.Instance do
from(i in Instance,
where:
i.host == ^host(url_or_host) and
i.unreachable_since <= ^Instances.reachability_datetime_threshold(),
not is_nil(i.unreachable_since),
select: true
)
)
@ -97,9 +92,16 @@ defmodule Pleroma.Instances.Instance do
def reachable?(url_or_host) when is_binary(url_or_host), do: true
def set_reachable(url_or_host) when is_binary(url_or_host) do
%Instance{host: host(url_or_host)}
|> changeset(%{unreachable_since: nil})
|> Repo.insert(on_conflict: {:replace, [:unreachable_since]}, conflict_target: :host)
host = host(url_or_host)
result =
%Instance{host: host}
|> changeset(%{unreachable_since: nil})
|> Repo.insert(on_conflict: {:replace, [:unreachable_since]}, conflict_target: :host)
Pleroma.Workers.ReachabilityWorker.delete_jobs_for_host(host)
result
end
def set_reachable(_), do: {:error, nil}
@ -132,11 +134,9 @@ defmodule Pleroma.Instances.Instance do
def set_unreachable(_, _), do: {:error, nil}
def get_consistently_unreachable do
reachability_datetime_threshold = Instances.reachability_datetime_threshold()
def get_unreachable do
from(i in Instance,
where: ^reachability_datetime_threshold > i.unreachable_since,
where: not is_nil(i.unreachable_since),
order_by: i.unreachable_since,
select: {i.host, i.unreachable_since}
)
@ -296,20 +296,14 @@ defmodule Pleroma.Instances.Instance do
Deletes all users from an instance in a background task, thus also deleting
all of those users' activities and notifications.
"""
def delete_users_and_activities(host) when is_binary(host) do
def delete(host) when is_binary(host) do
DeleteWorker.new(%{"op" => "delete_instance", "host" => host})
|> Oban.insert()
end
def perform(:delete_instance, host) when is_binary(host) do
User.Query.build(%{nickname: "@#{host}"})
|> Repo.chunk_stream(100, :batches)
|> Stream.each(fn users ->
users
|> Enum.each(fn user ->
User.perform(:delete, user)
end)
end)
|> Stream.run()
@doc "Schedules reachability check for instance"
def check_unreachable(domain) when is_binary(domain) do
Pleroma.Workers.ReachabilityWorker.new(%{"domain" => domain})
|> Oban.insert()
end
end

View file

@ -25,7 +25,7 @@ defmodule Pleroma.Language.Translation.Provider do
@callback supported_languages(type :: :string | :target) ::
{:ok, [String.t()]} | {:error, atom()}
@callback languages_matrix() :: {:ok, Map.t()} | {:error, atom()}
@callback languages_matrix() :: {:ok, map()} | {:error, atom()}
@callback name() :: String.t()

View file

@ -575,6 +575,12 @@ defmodule Pleroma.ModerationLog do
"@#{actor_nickname} requested account backup for @#{user_nickname}"
end
def get_log_entry_message(%ModerationLog{data: data}) do
actor_name = get_in(data, ["actor", "nickname"]) || "unknown"
action = data["action"] || "unknown"
"@#{actor_name} performed action #{action}"
end
defp nicknames_to_string(nicknames) do
nicknames
|> Enum.map(&"@#{&1}")

View file

@ -4,7 +4,6 @@
defmodule Pleroma.Object.Fetcher do
alias Pleroma.HTTP
alias Pleroma.Instances
alias Pleroma.Maps
alias Pleroma.Object
alias Pleroma.Object.Containment
@ -19,8 +18,6 @@ defmodule Pleroma.Object.Fetcher do
require Logger
require Pleroma.Constants
@mix_env Mix.env()
@spec reinject_object(struct(), map()) :: {:ok, Object.t()} | {:error, any()}
defp reinject_object(%Object{data: %{}} = object, new_data) do
Logger.debug("Reinjecting object #{new_data["id"]}")
@ -152,10 +149,6 @@ defmodule Pleroma.Object.Fetcher do
{:ok, body} <- get_object(id),
{:ok, data} <- safe_json_decode(body),
:ok <- Containment.contain_origin_from_id(id, data) do
if not Instances.reachable?(id) do
Instances.set_reachable(id)
end
{:ok, data}
else
{:scheme, _} ->
@ -178,13 +171,8 @@ defmodule Pleroma.Object.Fetcher do
def fetch_and_contain_remote_object_from_id(_id),
do: {:error, "id must be a string"}
defp check_crossdomain_redirect(final_host, original_url)
# Handle the common case in tests where responses don't include URLs
if @mix_env == :test do
defp check_crossdomain_redirect(nil, _) do
{:cross_domain_redirect, false}
end
defp check_crossdomain_redirect(final_host, _original_url) when is_nil(final_host) do
{:cross_domain_redirect, false}
end
defp check_crossdomain_redirect(final_host, original_url) do

View file

@ -158,6 +158,8 @@ defmodule Pleroma.ReverseProxy do
Logger.debug("#{__MODULE__} #{method} #{url} #{inspect(headers)}")
method = method |> String.downcase() |> String.to_existing_atom()
url = maybe_encode_url(url)
case client().request(method, url, headers, "", opts) do
{:ok, code, headers, client} when code in @valid_resp_codes ->
{:ok, code, downcase_headers(headers), client}
@ -449,4 +451,18 @@ defmodule Pleroma.ReverseProxy do
_ -> delete_resp_header(conn, "content-length")
end
end
# Only when Tesla adapter is Hackney or Finch does the URL
# need encoding before Reverse Proxying as both end up
# using the raw Hackney client and cannot leverage our
# EncodeUrl Tesla middleware
# Also do it for test environment
defp maybe_encode_url(url) do
case Application.get_env(:tesla, :adapter) do
Tesla.Adapter.Hackney -> Pleroma.HTTP.encode_url(url)
{Tesla.Adapter.Finch, _} -> Pleroma.HTTP.encode_url(url)
Tesla.Mock -> Pleroma.HTTP.encode_url(url)
_ -> url
end
end
end

View file

@ -56,10 +56,6 @@ defmodule Pleroma.SafeZip do
{_, true} <- {:safe_path, safe_path?(path)} do
{:cont, {:ok, maybe_add_file(type, path, fl)}}
else
{:get_type, e} ->
{:halt,
{:error, "Couldn't determine file type of ZIP entry at #{path} (#{inspect(e)})"}}
{:type, _} ->
{:halt, {:error, "Potentially unsafe file type in ZIP at: #{path}"}}

View file

@ -157,26 +157,55 @@ defmodule Pleroma.Search.QdrantSearch do
end
defmodule Pleroma.Search.QdrantSearch.OpenAIClient do
use Tesla
alias Pleroma.Config.Getting, as: Config
plug(Tesla.Middleware.BaseUrl, Config.get([Pleroma.Search.QdrantSearch, :openai_url]))
plug(Tesla.Middleware.JSON)
def post(path, body) do
Tesla.post(client(), path, body)
end
plug(Tesla.Middleware.Headers, [
{"Authorization",
"Bearer #{Pleroma.Config.get([Pleroma.Search.QdrantSearch, :openai_api_key])}"}
])
defp client do
Tesla.client(middleware())
end
defp middleware do
[
{Tesla.Middleware.BaseUrl, Config.get([Pleroma.Search.QdrantSearch, :openai_url])},
Tesla.Middleware.JSON,
{Tesla.Middleware.Headers,
[
{"Authorization", "Bearer #{Config.get([Pleroma.Search.QdrantSearch, :openai_api_key])}"}
]}
]
end
end
defmodule Pleroma.Search.QdrantSearch.QdrantClient do
use Tesla
alias Pleroma.Config.Getting, as: Config
plug(Tesla.Middleware.BaseUrl, Config.get([Pleroma.Search.QdrantSearch, :qdrant_url]))
plug(Tesla.Middleware.JSON)
def delete(path) do
Tesla.delete(client(), path)
end
plug(Tesla.Middleware.Headers, [
{"api-key", Pleroma.Config.get([Pleroma.Search.QdrantSearch, :qdrant_api_key])}
])
def post(path, body) do
Tesla.post(client(), path, body)
end
def put(path, body) do
Tesla.put(client(), path, body)
end
defp client do
Tesla.client(middleware())
end
defp middleware do
[
{Tesla.Middleware.BaseUrl, Config.get([Pleroma.Search.QdrantSearch, :qdrant_url])},
Tesla.Middleware.JSON,
{Tesla.Middleware.Headers,
[
{"api-key", Pleroma.Config.get([Pleroma.Search.QdrantSearch, :qdrant_api_key])}
]}
]
end
end

View file

@ -0,0 +1,29 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2025 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Tesla.Middleware.EncodeUrl do
@moduledoc """
Middleware to encode URLs properly
We must decode and then re-encode to ensure correct encoding.
If we only encode it will re-encode each % as %25 causing a space
already encoded as %20 to be %2520.
Similar problem for query parameters which need spaces to be the + character
"""
@behaviour Tesla.Middleware
@impl Tesla.Middleware
def call(%Tesla.Env{url: url} = env, next, _) do
url = Pleroma.HTTP.encode_url(url)
env = %{env | url: url}
case Tesla.run(env, next) do
{:ok, env} -> {:ok, env}
err -> err
end
end
end

View file

@ -193,7 +193,8 @@ defmodule Pleroma.UserRelationship do
{[:mute], []}
nil ->
{[:block, :mute, :notification_mute, :reblog_mute], [:block, :inverse_subscription]}
{[:block, :mute, :notification_mute, :reblog_mute, :endorsement],
[:block, :inverse_subscription]}
unknown ->
raise "Unsupported :subset option value: #{inspect(unknown)}"

View file

@ -1063,6 +1063,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
from(activity in query, where: fragment("?->>'type' != 'Announce'", activity.data))
end
defp restrict_reblogs(query, %{only_reblogs: true}) do
from(activity in query, where: fragment("?->>'type' = 'Announce'", activity.data))
end
defp restrict_reblogs(query, _), do: query
defp restrict_muted(query, %{with_muted: true}), do: query

View file

@ -53,7 +53,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
)
plug(:log_inbox_metadata when action in [:inbox])
plug(:set_requester_reachable when action in [:inbox])
plug(:relay_active? when action in [:relay])
defp relay_active?(conn, _) do
@ -274,13 +273,37 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
end
def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
with %User{is_active: true} = recipient <- User.get_cached_by_nickname(nickname),
{:ok, %User{is_active: true} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
with {:recipient_exists, %User{} = recipient} <-
{:recipient_exists, User.get_cached_by_nickname(nickname)},
{:sender_exists, {:ok, %User{} = actor}} <-
{:sender_exists, User.get_or_fetch_by_ap_id(params["actor"])},
{:recipient_active, true} <- {:recipient_active, recipient.is_active},
{:sender_active, true} <- {:sender_active, actor.is_active},
true <- Utils.recipient_in_message(recipient, actor, params),
params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
Federator.incoming_ap_doc(params)
json(conn, "ok")
else
{:recipient_exists, _} ->
conn
|> put_status(:not_found)
|> json("User does not exist")
{:sender_exists, _} ->
conn
|> put_status(:not_found)
|> json("Sender does not exist")
{:recipient_active, _} ->
conn
|> put_status(:not_found)
|> json("User deactivated")
{:sender_active, _} ->
conn
|> put_status(:not_found)
|> json("Sender deactivated")
_ ->
conn
|> put_status(:bad_request)
@ -520,15 +543,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|> json(dgettext("errors", "error"))
end
defp set_requester_reachable(%Plug.Conn{} = conn, _) do
with actor <- conn.params["actor"],
true <- is_binary(actor) do
Pleroma.Instances.set_reachable(actor)
end
conn
end
defp log_inbox_metadata(%{params: %{"actor" => actor, "type" => type}} = conn, _) do
Logger.metadata(actor: actor, type: type)
conn

View file

@ -0,0 +1,61 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.QuietReply do
@moduledoc """
QuietReply alters the scope of activities from local users when replying by enforcing them to be "Unlisted" or "Quiet Public". This delivers the activity to all the expected recipients and instances, but it will not be published in the Federated / The Whole Known Network timelines. It will still be published to the Home timelines of the user's followers and visible to anyone who opens the thread.
"""
require Pleroma.Constants
alias Pleroma.User
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
@impl true
def history_awareness, do: :auto
@impl true
def filter(
%{
"type" => "Create",
"to" => to,
"cc" => cc,
"object" => %{
"actor" => actor,
"type" => "Note",
"inReplyTo" => in_reply_to
}
} = activity
) do
with true <- is_binary(in_reply_to),
true <- Pleroma.Constants.as_public() in to,
%User{follower_address: followers_collection, local: true} <-
User.get_by_ap_id(actor) do
updated_to =
[followers_collection | to]
|> Kernel.--([Pleroma.Constants.as_public()])
updated_cc =
[Pleroma.Constants.as_public() | cc]
|> Kernel.--([followers_collection])
updated_activity =
activity
|> Map.put("to", updated_to)
|> Map.put("cc", updated_cc)
|> put_in(["object", "to"], updated_to)
|> put_in(["object", "cc"], updated_cc)
{:ok, updated_activity}
else
_ -> {:ok, activity}
end
end
@impl true
def filter(activity), do: {:ok, activity}
@impl true
def describe, do: {:ok, %{}}
end

View file

@ -15,7 +15,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.RemoteReportPolicy do
else
{:local, true} -> {:ok, object}
{:reject, message} -> {:reject, message}
error -> {:reject, error}
end
end

View file

@ -171,17 +171,9 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
{"digest", p.digest}
]
) do
if not is_nil(p.unreachable_since) do
Instances.set_reachable(p.inbox)
end
result
else
{_post_result, %{status: code} = response} = e ->
if is_nil(p.unreachable_since) do
Instances.set_unreachable(p.inbox)
end
Logger.metadata(activity: p.activity_id, inbox: p.inbox, status: code)
Logger.error("Publisher failed to inbox #{p.inbox} with status #{code}")
@ -202,10 +194,6 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
connection_pool_snooze()
e ->
if is_nil(p.unreachable_since) do
Instances.set_unreachable(p.inbox)
end
Logger.metadata(activity: p.activity_id, inbox: p.inbox)
Logger.error("Publisher failed to inbox #{p.inbox} #{inspect(e)}")
{:error, e}
@ -317,7 +305,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
[priority_recipients, recipients] = recipients(actor, activity)
inboxes =
[priority_inboxes, other_inboxes] =
[priority_recipients, recipients]
|> Enum.map(fn recipients ->
recipients
@ -330,8 +318,8 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
end)
Repo.checkout(fn ->
Enum.each(inboxes, fn inboxes ->
Enum.each(inboxes, fn {inbox, unreachable_since} ->
Enum.each([priority_inboxes, other_inboxes], fn inboxes ->
Enum.each(inboxes, fn inbox ->
%User{ap_id: ap_id} = Enum.find(recipients, fn actor -> actor.inbox == inbox end)
# Get all the recipients on the same host and add them to cc. Otherwise, a remote
@ -341,8 +329,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
__MODULE__.enqueue_one(%{
inbox: inbox,
cc: cc,
activity_id: activity.id,
unreachable_since: unreachable_since
activity_id: activity.id
})
end)
end)
@ -375,12 +362,11 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|> Enum.each(fn {inboxes, priority} ->
inboxes
|> Instances.filter_reachable()
|> Enum.each(fn {inbox, unreachable_since} ->
|> Enum.each(fn inbox ->
__MODULE__.enqueue_one(
%{
inbox: inbox,
activity_id: activity.id,
unreachable_since: unreachable_since
activity_id: activity.id
},
priority: priority
)

View file

@ -664,6 +664,24 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
# Rewrite dislikes into the thumbs down emoji
defp handle_incoming_normalized(%{"type" => "Dislike"} = data, options) do
data
|> Map.put("type", "EmojiReact")
|> Map.put("content", "👎")
|> handle_incoming_normalized(options)
end
defp handle_incoming_normalized(
%{"type" => "Undo", "object" => %{"type" => "Dislike"}} = data,
options
) do
data
|> put_in(["object", "type"], "EmojiReact")
|> put_in(["object", "content"], "👎")
|> handle_incoming_normalized(options)
end
defp handle_incoming_normalized(_, _), do: :error
@spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil

View file

@ -240,6 +240,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
render_error(conn, :not_found, "No such permission_group")
end
def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
render_error(conn, :forbidden, "You can't revoke your own admin status.")
end
def right_delete(
%{assigns: %{user: admin}} = conn,
%{
@ -265,10 +269,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
json(conn, fields)
end
def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
render_error(conn, :forbidden, "You can't revoke your own admin status.")
end
@doc "Get a password reset token (base64 string) for given nickname"
def get_password_reset(conn, %{"nickname" => nickname}) do
(%User{local: true} = user) = User.get_cached_by_nickname(nickname)

View file

@ -49,7 +49,7 @@ defmodule Pleroma.Web.AdminAPI.InstanceController do
end
def delete(conn, %{"instance" => instance}) do
with {:ok, _job} <- Instance.delete_users_and_activities(instance) do
with {:ok, _job} <- Instance.delete(instance) do
json(conn, instance)
end
end

View file

@ -158,6 +158,6 @@ defmodule Pleroma.Web.ApiSpec do
}
}
# discover request/response schemas from path specs
|> OpenApiSpex.resolve_schema_modules()
|> then(&OpenApiSpex.resolve_schema_modules/1)
end
end

View file

@ -143,6 +143,12 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
"Include statuses from muted accounts."
),
Operation.parameter(:exclude_reblogs, :query, BooleanLike.schema(), "Exclude reblogs"),
Operation.parameter(
:only_reblogs,
:query,
BooleanLike.schema(),
"Include only reblogs"
),
Operation.parameter(:exclude_replies, :query, BooleanLike.schema(), "Exclude replies"),
Operation.parameter(
:exclude_visibilities,

View file

@ -127,6 +127,20 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do
}
end
def download_zip_operation do
%Operation{
tags: ["Emoji pack administration"],
summary: "Download a pack from a URL or an uploaded file",
operationId: "PleromaAPI.EmojiPackController.download_zip",
security: [%{"oAuth" => ["admin:write"]}],
requestBody: request_body("Parameters", download_zip_request(), required: true),
responses: %{
200 => ok_response(),
400 => Operation.response("Bad Request", "application/json", ApiError)
}
}
end
defp download_request do
%Schema{
type: :object,
@ -143,6 +157,25 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do
}
end
defp download_zip_request do
%Schema{
type: :object,
required: [:name],
properties: %{
url: %Schema{
type: :string,
format: :uri,
description: "URL of the file"
},
file: %Schema{
description: "The uploaded ZIP file",
type: :object
},
name: %Schema{type: :string, format: :uri, description: "Pack Name"}
}
}
end
def create_operation do
%Operation{
tags: ["Emoji pack administration"],

View file

@ -26,7 +26,11 @@ defmodule Pleroma.Web.ApiSpec.Scopes.Compiler do
end
def extract_all_scopes do
extract_all_scopes_from(Pleroma.Web.ApiSpec.spec())
try do
extract_all_scopes_from(Pleroma.Web.ApiSpec.spec())
catch
_, _ -> []
end
end
def extract_all_scopes_from(specs) do

View file

@ -5,6 +5,7 @@
defmodule Pleroma.Web.MastodonAPI.SearchController do
use Pleroma.Web, :controller
alias Pleroma.Hashtag
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ControllerHelper
@ -120,69 +121,14 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
defp resource_search(:v2, "hashtags", query, options) do
tags_path = Endpoint.url() <> "/tag/"
query
|> prepare_tags(options)
Hashtag.search(query, options)
|> Enum.map(fn tag ->
%{name: tag, url: tags_path <> tag}
end)
end
defp resource_search(:v1, "hashtags", query, options) do
prepare_tags(query, options)
end
defp prepare_tags(query, options) do
tags =
query
|> preprocess_uri_query()
|> String.split(~r/[^#\w]+/u, trim: true)
|> Enum.uniq_by(&String.downcase/1)
explicit_tags = Enum.filter(tags, fn tag -> String.starts_with?(tag, "#") end)
tags =
if Enum.any?(explicit_tags) do
explicit_tags
else
tags
end
tags = Enum.map(tags, fn tag -> String.trim_leading(tag, "#") end)
tags =
if Enum.empty?(explicit_tags) && !options[:skip_joined_tag] do
add_joined_tag(tags)
else
tags
end
Pleroma.Pagination.paginate_list(tags, options)
end
defp add_joined_tag(tags) do
tags
|> Kernel.++([joined_tag(tags)])
|> Enum.uniq_by(&String.downcase/1)
end
# If `query` is a URI, returns last component of its path, otherwise returns `query`
defp preprocess_uri_query(query) do
if query =~ ~r/https?:\/\// do
query
|> String.trim_trailing("/")
|> URI.parse()
|> Map.get(:path)
|> String.split("/")
|> Enum.at(-1)
else
query
end
end
defp joined_tag(tags) do
tags
|> Enum.map(fn tag -> String.capitalize(tag) end)
|> Enum.join()
Hashtag.search(query, options)
end
defp with_fallback(f, fallback \\ []) do

View file

@ -584,6 +584,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
{:error, error} when error in [:unexpected_response, :quota_exceeded, :too_many_requests] ->
render_error(conn, :service_unavailable, "Translation service not available")
_ ->
render_error(conn, :internal_server_error, "Translation failed")
end
end

View file

@ -168,9 +168,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
UserRelationship.exists?(
user_relationships,
:endorsement,
target,
reading_user,
&User.endorses?(&2, &1)
target,
&User.endorses?(&1, &2)
)
}
end

View file

@ -16,6 +16,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
:import_from_filesystem,
:remote,
:download,
:download_zip,
:create,
:update,
:delete
@ -113,6 +114,27 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
end
end
def download_zip(
%{private: %{open_api_spex: %{body_params: params}}} = conn,
_
) do
name = Map.get(params, :name)
with :ok <- Pack.download_zip(name, params) do
json(conn, "ok")
else
{:error, error} when is_binary(error) ->
conn
|> put_status(:bad_request)
|> json(%{error: error})
{:error, _} ->
conn
|> put_status(:bad_request)
|> json(%{error: "Could not process pack"})
end
end
def download(
%{private: %{open_api_spex: %{body_params: %{url: url, name: name} = params}}} = conn,
_

View file

@ -13,7 +13,7 @@ defmodule Pleroma.Web.PleromaAPI.InstancesController do
def show(conn, _params) do
unreachable =
Instances.get_consistently_unreachable()
Instances.get_unreachable()
|> Map.new(fn {host, date} -> {host, to_string(date)} end)
json(conn, %{"unreachable" => unreachable})

View file

@ -466,6 +466,7 @@ defmodule Pleroma.Web.Router do
get("/import", EmojiPackController, :import_from_filesystem)
get("/remote", EmojiPackController, :remote)
post("/download", EmojiPackController, :download)
post("/download_zip", EmojiPackController, :download_zip)
post("/files", EmojiFileController, :create)
patch("/files", EmojiFileController, :update)

View file

@ -35,9 +35,9 @@ defmodule Pleroma.Web.WebFinger do
regex =
if webfinger_domain = Pleroma.Config.get([__MODULE__, :domain]) do
~r/(acct:)?(?<username>[a-z0-9A-Z_\.-]+)@(#{host}|#{webfinger_domain})/
~r/(acct:)?(?<username>[a-z0-9A-Z_\.-]+)@(#{host}|#{webfinger_domain})$/
else
~r/(acct:)?(?<username>[a-z0-9A-Z_\.-]+)@#{host}/
~r/(acct:)?(?<username>[a-z0-9A-Z_\.-]+)@#{host}$/
end
with %{"username" => username} <- Regex.named_captures(regex, resource),

View file

@ -3,7 +3,6 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.DeleteWorker do
alias Pleroma.Instances.Instance
alias Pleroma.User
use Oban.Worker, queue: :slow
@ -15,7 +14,27 @@ defmodule Pleroma.Workers.DeleteWorker do
end
def perform(%Job{args: %{"op" => "delete_instance", "host" => host}}) do
Instance.perform(:delete_instance, host)
# Schedule the per-user deletion jobs
Pleroma.Repo.transaction(fn ->
User.Query.build(%{nickname: "@#{host}"})
|> Pleroma.Repo.all()
|> Enum.each(fn user ->
%{"op" => "delete_user", "user_id" => user.id}
|> __MODULE__.new()
|> Oban.insert()
end)
# Delete the instance from the Instances table
case Pleroma.Repo.get_by(Pleroma.Instances.Instance, host: host) do
nil -> :ok
instance -> Pleroma.Repo.delete(instance)
end
# Delete any pending ReachabilityWorker jobs for this domain
Pleroma.Workers.ReachabilityWorker.delete_jobs_for_host(host)
:ok
end)
end
@impl true

View file

@ -4,9 +4,10 @@
defmodule Pleroma.Workers.PublisherWorker do
alias Pleroma.Activity
alias Pleroma.Instances
alias Pleroma.Web.Federator
use Oban.Worker, queue: :federator_outgoing, max_attempts: 5
use Oban.Worker, queue: :federator_outgoing, max_attempts: 13
@impl true
def perform(%Job{args: %{"op" => "publish", "activity_id" => activity_id}}) do
@ -14,9 +15,30 @@ defmodule Pleroma.Workers.PublisherWorker do
Federator.perform(:publish, activity)
end
def perform(%Job{args: %{"op" => "publish_one", "params" => params}}) do
def perform(%Job{args: %{"op" => "publish_one", "params" => params}} = job) do
params = Map.new(params, fn {k, v} -> {String.to_atom(k), v} end)
Federator.perform(:publish_one, params)
# Cancel / skip the job if this server believed to be unreachable now
if not Instances.reachable?(params.inbox) do
{:cancel, :unreachable}
else
case Federator.perform(:publish_one, params) do
{:ok, _} ->
:ok
{:error, _} = error ->
# Only mark as unreachable on final failure
if job.attempt == job.max_attempts do
Instances.set_unreachable(params.inbox)
end
error
error ->
# Unexpected error, may have been client side
error
end
end
end
@impl true

View file

@ -0,0 +1,116 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.ReachabilityWorker do
use Oban.Worker,
queue: :background,
max_attempts: 1,
unique: [period: :infinity, states: [:available, :scheduled], keys: [:domain]]
alias Pleroma.HTTP
alias Pleroma.Instances
import Ecto.Query
@impl true
def perform(%Oban.Job{args: %{"domain" => domain, "phase" => phase, "attempt" => attempt}}) do
case check_reachability(domain) do
:ok ->
Instances.set_reachable("https://#{domain}")
:ok
{:error, _} = error ->
handle_failed_attempt(domain, phase, attempt)
error
end
end
# New jobs enter here and are immediately re-scheduled for the first phase
@impl true
def perform(%Oban.Job{args: %{"domain" => domain}}) do
scheduled_at = DateTime.add(DateTime.utc_now(), 60, :second)
%{
"domain" => domain,
"phase" => "phase_1min",
"attempt" => 1
}
|> new(scheduled_at: scheduled_at, replace: true)
|> Oban.insert()
:ok
end
@impl true
def timeout(_job), do: :timer.seconds(5)
@doc "Deletes scheduled jobs to check reachability for specified instance"
def delete_jobs_for_host(host) do
Oban.Job
|> where(worker: "Pleroma.Workers.ReachabilityWorker")
|> where([j], j.args["domain"] == ^host)
|> Oban.delete_all_jobs()
end
defp check_reachability(domain) do
case HTTP.get("https://#{domain}/") do
{:ok, %{status: status}} when status in 200..299 ->
:ok
{:ok, %{status: _status}} ->
{:error, :unreachable}
{:error, _} = error ->
error
end
end
defp handle_failed_attempt(_domain, "final", _attempt), do: :ok
defp handle_failed_attempt(domain, phase, attempt) do
{interval_minutes, max_attempts, next_phase} = get_phase_config(phase)
if attempt >= max_attempts do
# Move to next phase
schedule_next_phase(domain, next_phase)
else
# Retry same phase with incremented attempt
schedule_retry(domain, phase, attempt + 1, interval_minutes)
end
end
defp get_phase_config("phase_1min"), do: {1, 4, "phase_15min"}
defp get_phase_config("phase_15min"), do: {15, 4, "phase_1hour"}
defp get_phase_config("phase_1hour"), do: {60, 4, "phase_8hour"}
defp get_phase_config("phase_8hour"), do: {480, 4, "phase_24hour"}
defp get_phase_config("phase_24hour"), do: {1440, 4, "final"}
defp get_phase_config("final"), do: {nil, 0, nil}
defp schedule_next_phase(_domain, "final"), do: :ok
defp schedule_next_phase(domain, next_phase) do
{interval_minutes, _max_attempts, _next_phase} = get_phase_config(next_phase)
scheduled_at = DateTime.add(DateTime.utc_now(), interval_minutes * 60, :second)
%{
"domain" => domain,
"phase" => next_phase,
"attempt" => 1
}
|> new(scheduled_at: scheduled_at, replace: true)
|> Oban.insert()
end
def schedule_retry(domain, phase, attempt, interval_minutes) do
scheduled_at = DateTime.add(DateTime.utc_now(), interval_minutes * 60, :second)
%{
"domain" => domain,
"phase" => phase,
"attempt" => attempt
}
|> new(scheduled_at: scheduled_at, replace: true)
|> Oban.insert()
end
end

View file

@ -3,6 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.ReceiverWorker do
alias Pleroma.Instances
alias Pleroma.Signature
alias Pleroma.User
alias Pleroma.Web.Federator
@ -37,6 +38,11 @@ defmodule Pleroma.Workers.ReceiverWorker do
{:ok, _public_key} <- Signature.refetch_public_key(conn_data),
{:signature, true} <- {:signature, Signature.validate_signature(conn_data)},
{:ok, res} <- Federator.perform(:incoming_ap_doc, params) do
unless Instances.reachable?(params["actor"]) do
domain = URI.parse(params["actor"]).host
Oban.insert(Pleroma.Workers.ReachabilityWorker.new(%{"domain" => domain}))
end
{:ok, res}
else
e -> process_errors(e)
@ -45,6 +51,11 @@ defmodule Pleroma.Workers.ReceiverWorker do
def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do
with {:ok, res} <- Federator.perform(:incoming_ap_doc, params) do
unless Instances.reachable?(params["actor"]) do
domain = URI.parse(params["actor"]).host
Oban.insert(Pleroma.Workers.ReachabilityWorker.new(%{"domain" => domain}))
end
{:ok, res}
else
e -> process_errors(e)

View file

@ -3,6 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.RemoteFetcherWorker do
alias Pleroma.Instances
alias Pleroma.Object.Fetcher
use Oban.Worker, queue: :background, unique: [period: :infinity]
@ -11,6 +12,11 @@ defmodule Pleroma.Workers.RemoteFetcherWorker do
def perform(%Job{args: %{"op" => "fetch_remote", "id" => id} = args}) do
case Fetcher.fetch_object_from_id(id, depth: args["depth"]) do
{:ok, _object} ->
unless Instances.reachable?(id) do
# Mark the server as reachable since we successfully fetched an object
Instances.set_reachable(id)
end
:ok
{:allowed_depth, false} ->

22
mix.exs
View file

@ -5,7 +5,7 @@ defmodule Pleroma.Mixfile do
[
app: :pleroma,
version: version("2.9.1"),
elixir: "~> 1.14",
elixir: "~> 1.15",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: Mix.compilers(),
elixirc_options: [warnings_as_errors: warnings_as_errors(), prune_code_paths: false],
@ -128,15 +128,21 @@ defmodule Pleroma.Mixfile do
{:phoenix_ecto, "~> 4.4"},
{:ecto_sql, "~> 3.10"},
{:ecto_enum, "~> 1.4"},
{:postgrex, ">= 0.0.0"},
{:postgrex, ">= 0.20.0"},
{:phoenix_html, "~> 3.3"},
{:phoenix_live_view, "~> 0.19.0"},
{:phoenix_live_dashboard, "~> 0.8.0"},
{:telemetry_metrics, "~> 0.6"},
{:telemetry_poller, "~> 1.0"},
{:tzdata, "~> 1.0.3"},
{:plug_cowboy, "~> 2.5"},
{:oban, "~> 2.18.0"},
{:oban_plugins_lazarus,
git: "https://git.pleroma.social/pleroma/elixir-libraries/oban_plugins_lazarus.git",
ref: "e49fc355baaf0e435208bf5f534d31e26e897711"},
{:plug_cowboy, "~> 2.7"},
{:oban, "~> 2.19.0"},
{:oban_plugins_lazarus,
git: "https://git.pleroma.social/pleroma/elixir-libraries/oban_plugins_lazarus.git",
ref: "e49fc355baaf0e435208bf5f534d31e26e897711"},
{:gettext, "~> 0.20"},
{:bcrypt_elixir, "~> 2.2"},
{:trailing_format_plug, "~> 0.0.7"},
@ -146,8 +152,8 @@ defmodule Pleroma.Mixfile do
{:cachex, "~> 3.2"},
{:tesla, "~> 1.11"},
{:castore, "~> 1.0"},
{:cowlib, "~> 2.9", override: true},
{:gun, "~> 2.0.0-rc.1", override: true},
{:cowlib, "~> 2.15"},
{:gun, "~> 2.2"},
{:finch, "~> 0.15"},
{:jason, "~> 1.2"},
{:mogrify, "~> 0.9.0", override: "true"},
@ -184,11 +190,11 @@ defmodule Pleroma.Mixfile do
ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"},
{:captcha,
git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git",
ref: "6630c42aaaab124e697b4e513190c89d8b64e410"},
ref: "e7b7cc34cc16b383461b966484c297e4ec9aeef6"},
{:restarter, path: "./restarter"},
{:majic, "~> 1.0"},
{:open_api_spex, "~> 3.16"},
{:ecto_psql_extras, "~> 0.6"},
{:ecto_psql_extras, "~> 0.8"},
{:vix, "~> 0.26.0"},
{:elixir_make, "~> 0.7.7", override: true},
{:blurhash, "~> 0.1.0", hex: :rinpatch_blurhash},

130
mix.lock
View file

@ -1,122 +1,124 @@
%{
"accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"},
"argon2_elixir": {:hex, :argon2_elixir, "4.0.0", "7f6cd2e4a93a37f61d58a367d82f830ad9527082ff3c820b8197a8a736648941", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f9da27cf060c9ea61b1bd47837a28d7e48a8f6fa13a745e252556c14f9132c7f"},
"bandit": {:hex, :bandit, "1.5.5", "df28f1c41f745401fe9e85a6882033f5f3442ab6d30c8a2948554062a4ab56e0", [:mix], [{:hpax, "~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "f21579a29ea4bc08440343b2b5f16f7cddf2fea5725d31b72cf973ec729079e1"},
"argon2_elixir": {:hex, :argon2_elixir, "4.1.3", "4f28318286f89453364d7fbb53e03d4563fd7ed2438a60237eba5e426e97785f", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "7c295b8d8e0eaf6f43641698f962526cdf87c6feb7d14bd21e599271b510608c"},
"bandit": {:hex, :bandit, "1.5.7", "6856b1e1df4f2b0cb3df1377eab7891bec2da6a7fd69dc78594ad3e152363a50", [:mix], [{:hpax, "~> 1.0.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "f2dd92ae87d2cbea2fa9aa1652db157b6cba6c405cb44d4f6dd87abba41371cd"},
"base62": {:hex, :base62, "1.2.2", "85c6627eb609317b70f555294045895ffaaeb1758666ab9ef9ca38865b11e629", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "d41336bda8eaa5be197f1e4592400513ee60518e5b9f4dcf38f4b4dae6f377bb"},
"bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"},
"bcrypt_elixir": {:hex, :bcrypt_elixir, "2.3.1", "5114d780459a04f2b4aeef52307de23de961b69e13a5cd98a911e39fda13f420", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "42182d5f46764def15bf9af83739e3bf4ad22661b1c34fc3e88558efced07279"},
"benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"},
"benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"},
"blurhash": {:hex, :rinpatch_blurhash, "0.1.0", "01a888b0f5f1f382ab52e4396f01831cbe8486ea5828604c90f4dac533d39a4b", [:mix], [{:mogrify, "~> 0.8.0", [hex: :mogrify, repo: "hexpm", optional: true]}], "hexpm", "19911a5dcbb0acb9710169a72f702bce6cb048822b12de566ccd82b2cc42b907"},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"cachex": {:hex, :cachex, "3.6.0", "14a1bfbeee060dd9bec25a5b6f4e4691e3670ebda28c8ba2884b12fe30b36bf8", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "ebf24e373883bc8e0c8d894a63bbe102ae13d918f790121f5cfe6e485cc8e2e2"},
"calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"},
"captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "6630c42aaaab124e697b4e513190c89d8b64e410", [ref: "6630c42aaaab124e697b4e513190c89d8b64e410"]},
"castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"},
"cc_precompiler": {:hex, :cc_precompiler, "0.1.9", "e8d3364f310da6ce6463c3dd20cf90ae7bbecbf6c5203b98bf9b48035592649b", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "9dcab3d0f3038621f1601f13539e7a9ee99843862e66ad62827b0c42b2f58a54"},
"calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.1.201603 or ~> 0.5.20 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"},
"captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "e7b7cc34cc16b383461b966484c297e4ec9aeef6", [ref: "e7b7cc34cc16b383461b966484c297e4ec9aeef6"]},
"castore": {:hex, :castore, "1.0.15", "8aa930c890fe18b6fe0a0cff27b27d0d4d231867897bd23ea772dee561f032a3", [:mix], [], "hexpm", "96ce4c69d7d5d7a0761420ef743e2f4096253931a3ba69e5ff8ef1844fe446d3"},
"cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"},
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"},
"comeonin": {:hex, :comeonin, "5.5.1", "5113e5f3800799787de08a6e0db307133850e635d34e9fab23c70b6501669510", [:mix], [], "hexpm", "65aac8f19938145377cee73973f192c5645873dcf550a8a6b18187d17c13ccdb"},
"concurrent_limiter": {:hex, :concurrent_limiter, "0.1.1", "43ae1dc23edda1ab03dd66febc739c4ff710d047bb4d735754909f9a474ae01c", [:mix], [{:telemetry, "~> 0.3", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "53968ff238c0fbb4d7ed76ddb1af0be6f3b2f77909f6796e249e737c505a16eb"},
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
"cors_plug": {:hex, :cors_plug, "2.0.3", "316f806d10316e6d10f09473f19052d20ba0a0ce2a1d910ddf57d663dac402ae", [:mix], [{:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ee4ae1418e6ce117fc42c2ba3e6cbdca4e95ecd2fe59a05ec6884ca16d469aea"},
"covertool": {:hex, :covertool, "2.0.6", "4a291b4e3449025b0595d8f44c8d7635d4f48f033be2ce88d22a329f36f94a91", [:rebar3], [], "hexpm", "5db3fcd82180d8ea4ad857d4d1ab21a8d31b5aee0d60d2f6c0f9e25a411d1e21"},
"cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"},
"covertool": {:hex, :covertool, "2.0.7", "398be995c4cf1a2861174389b3577ca97beee43b60c8f1afcf510f1b07d32408", [:rebar3], [], "hexpm", "46158ed6e1a0df7c0a912e314c7b8e053bd74daa5fc6b790614922a155b5720c"},
"cowboy": {:hex, :cowboy, "2.13.0", "09d770dd5f6a22cc60c071f432cd7cb87776164527f205c5a6b0f24ff6b38990", [:make, :rebar3], [{:cowlib, ">= 2.14.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "e724d3a70995025d654c1992c7b11dbfea95205c047d86ff9bf1cda92ddc5614"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
"cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"},
"cowlib": {:hex, :cowlib, "2.15.0", "3c97a318a933962d1c12b96ab7c1d728267d2c523c25a5b57b0f93392b6e9e25", [:make, :rebar3], [], "hexpm", "4f00c879a64b4fe7c8fcb42a4281925e9ffdb928820b03c3ad325a617e857532"},
"credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"},
"crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
"custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},
"db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"},
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
"db_connection": {:hex, :db_connection, "2.8.0", "64fd82cfa6d8e25ec6660cea73e92a4cbc6a18b31343910427b702838c4b33b2", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "008399dae5eee1bf5caa6e86d204dcb44242c82b1ed5e22c881f2c34da201b15"},
"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
"dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"},
"dialyxir": {:hex, :dialyxir, "1.4.6", "7cca478334bf8307e968664343cbdb432ee95b4b68a9cba95bdabb0ad5bdfd9a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "8cf5615c5cd4c2da6c501faae642839c8405b49f8aa057ad4ae401cb808ef64d"},
"earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"},
"earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"},
"earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"},
"eblurhash": {:git, "https://github.com/zotonic/eblurhash.git", "bc37ceb426ef021ee9927fb249bb93f7059194ab", [ref: "bc37ceb426ef021ee9927fb249bb93f7059194ab"]},
"ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"},
"ecto": {:hex, :ecto, "3.13.2", "7d0c0863f3fc8d71d17fc3ad3b9424beae13f02712ad84191a826c7169484f01", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "669d9291370513ff56e7b7e7081b7af3283d02e046cf3d403053c557894a0b3e"},
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
"ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.15", "0fc29dbae0e444a29bd6abeee4cf3c4c037e692a272478a234a1cc765077dbb1", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "b6127f3a5c6fc3d84895e4768cc7c199f22b48b67d6c99b13fbf4a374e73f039"},
"ecto_sql": {:hex, :ecto_sql, "3.11.3", "4eb7348ff8101fbc4e6bbc5a4404a24fecbe73a3372d16569526b0cf34ebc195", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e5f36e3d736b99c7fee3e631333b8394ade4bafe9d96d35669fca2d81c2be928"},
"ecto_psql_extras": {:hex, :ecto_psql_extras, "0.8.8", "aa02529c97f69aed5722899f5dc6360128735a92dd169f23c5d50b1f7fdede08", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "> 0.16.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "04c63d92b141723ad6fed2e60a4b461ca00b3594d16df47bbc48f1f4534f2c49"},
"ecto_sql": {:hex, :ecto_sql, "3.13.2", "a07d2461d84107b3d037097c822ffdd36ed69d1cf7c0f70e12a3d1decf04e2e1", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "539274ab0ecf1a0078a6a72ef3465629e4d6018a3028095dc90f60a19c371717"},
"eimp": {:hex, :eimp, "1.0.14", "fc297f0c7e2700457a95a60c7010a5f1dcb768a083b6d53f49cd94ab95a28f22", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "501133f3112079b92d9e22da8b88bf4f0e13d4d67ae9c15c42c30bd25ceb83b6"},
"elixir_make": {:hex, :elixir_make, "0.7.8", "505026f266552ee5aabca0b9f9c229cbb496c689537c9f922f3eb5431157efc7", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "7a71945b913d37ea89b06966e1342c85cfe549b15e6d6d081e8081c493062c07"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"},
"esbuild": {:hex, :esbuild, "0.5.0", "d5bb08ff049d7880ee3609ed5c4b864bd2f46445ea40b16b4acead724fb4c4a3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "f183a0b332d963c4cfaf585477695ea59eef9a6f2204fdd0efa00e099694ffe5"},
"eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"},
"ex_aws": {:hex, :ex_aws, "2.1.9", "dc4865ecc20a05190a34a0ac5213e3e5e2b0a75a0c2835e923ae7bfeac5e3c31", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "3e6c776703c9076001fbe1f7c049535f042cb2afa0d2cbd3b47cbc4e92ac0d10"},
"ex_aws_s3": {:hex, :ex_aws_s3, "2.5.3", "422468e5c3e1a4da5298e66c3468b465cfd354b842e512cb1f6fbbe4e2f5bdaf", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "4f09dd372cc386550e484808c5ac5027766c8d0cd8271ccc578b82ee6ef4f3b8"},
"ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"},
"ex_doc": {:hex, :ex_doc, "0.31.1", "8a2355ac42b1cc7b2379da9e40243f2670143721dd50748bf6c3b1184dae2089", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "3178c3a407c557d8343479e1ff117a96fd31bafe52a039079593fb0524ef61b0"},
"ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"},
"ex_aws_s3": {:hex, :ex_aws_s3, "2.5.8", "5ee7407bc8252121ad28fba936b3b293f4ecef93753962351feb95b8a66096fa", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "84e512ca2e0ae6a6c497036dff06d4493ffb422cfe476acc811d7c337c16691c"},
"ex_const": {:hex, :ex_const, "0.3.0", "9d79516679991baf540ef445438eef1455ca91cf1a3c2680d8fb9e5bea2fe4de", [:mix], [], "hexpm", "76546322abb9e40ee4a2f454cf1c8a5b25c3672fa79bed1ea52c31e0d2428ca9"},
"ex_doc": {:hex, :ex_doc, "0.38.2", "504d25eef296b4dec3b8e33e810bc8b5344d565998cd83914ffe1b8503737c02", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "732f2d972e42c116a70802f9898c51b54916e542cc50968ac6980512ec90f42b"},
"ex_machina": {:hex, :ex_machina, "2.8.0", "a0e847b5712065055ec3255840e2c78ef9366634d62390839d4880483be38abe", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "79fe1a9c64c0c1c1fab6c4fa5d871682cb90de5885320c187d117004627a7729"},
"ex_syslogger": {:hex, :ex_syslogger, "1.5.2", "72b6aa2d47a236e999171f2e1ec18698740f40af0bd02c8c650bf5f1fd1bac79", [:mix], [{:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "ab9fab4136dbc62651ec6f16fa4842f10cf02ab4433fa3d0976c01be99398399"},
"exile": {:hex, :exile, "0.10.0", "b69e2d27a9af670b0f0a0898addca0eda78f6f5ba95ccfbc9bc6ccdd04925436", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "c62ee8fee565b5ac4a898d0dcd58d2b04fb5eec1655af1ddcc9eb582c6732c33"},
"expo": {:hex, :expo, "0.5.1", "249e826a897cac48f591deba863b26c16682b43711dd15ee86b92f25eafd96d9", [:mix], [], "hexpm", "68a4233b0658a3d12ee00d27d37d856b1ba48607e7ce20fd376958d0ba6ce92b"},
"fast_html": {:hex, :fast_html, "2.3.0", "08c1d8ead840dd3060ba02c761bed9f37f456a1ddfe30bcdcfee8f651cec06a6", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "f18e3c7668f82d3ae0b15f48d48feeb257e28aa5ab1b0dbf781c7312e5da029d"},
"fast_html": {:hex, :fast_html, "2.5.0", "23578c1c1fa03db6a3786d78218b2d944df34b0fc3729e72de912f390913c80f", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "69eb46ed98a5d9cca1ccd4a5ac94ce5dd626fc29513fbaa0a16cd8b2da67ae3e"},
"fast_sanitize": {:hex, :fast_sanitize, "0.2.3", "67b93dfb34e302bef49fec3aaab74951e0f0602fd9fa99085987af05bd91c7a5", [:mix], [{:fast_html, "~> 2.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "e8ad286d10d0386e15d67d0ee125245ebcfbc7d7290b08712ba9013c8c5e56e2"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"},
"finch": {:hex, :finch, "0.20.0", "5330aefb6b010f424dcbbc4615d914e9e3deae40095e73ab0c1bb0968933cadf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2658131a74d051aabfcba936093c903b8e89da9a1b63e430bee62045fa9b2ee2"},
"flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"},
"floki": {:hex, :floki, "0.35.2", "87f8c75ed8654b9635b311774308b2760b47e9a579dabf2e4d5f1e1d42c39e0b", [:mix], [], "hexpm", "6b05289a8e9eac475f644f09c2e4ba7e19201fd002b89c28c1293e7bd16773d9"},
"floki": {:hex, :floki, "0.38.0", "62b642386fa3f2f90713f6e231da0fa3256e41ef1089f83b6ceac7a3fd3abf33", [:mix], [], "hexpm", "a5943ee91e93fb2d635b612caf5508e36d37548e84928463ef9dd986f0d1abd9"},
"gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"},
"gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"},
"gun": {:hex, :gun, "2.0.1", "160a9a5394800fcba41bc7e6d421295cf9a7894c2252c0678244948e3336ad73", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "a10bc8d6096b9502205022334f719cc9a08d9adcfbfc0dbee9ef31b56274a20b"},
"hackney": {:hex, :hackney, "1.18.2", "d7ff544ddae5e1cb49e9cf7fa4e356d7f41b283989a1c304bfc47a8cc1cf966f", [:rebar3], [{:certifi, "~>2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "af94d5c9f97857db257090a4a10e5426ecb6f4918aa5cc666798566ae14b65fd"},
"hpax": {:hex, :hpax, "0.2.0", "5a58219adcb75977b2edce5eb22051de9362f08236220c9e859a47111c194ff5", [:mix], [], "hexpm", "bea06558cdae85bed075e6c036993d43cd54d447f76d8190a8db0dc5893fa2f1"},
"gun": {:hex, :gun, "2.2.0", "b8f6b7d417e277d4c2b0dc3c07dfdf892447b087f1cc1caff9c0f556b884e33d", [:make, :rebar3], [{:cowlib, ">= 2.15.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "76022700c64287feb4df93a1795cff6741b83fb37415c40c34c38d2a4645261a"},
"hackney": {:hex, :hackney, "1.18.2", "d7ff544ddae5e1cb49e9cf7fa4e356d7f41b283989a1c304bfc47a8cc1cf966f", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "af94d5c9f97857db257090a4a10e5426ecb6f4918aa5cc666798566ae14b65fd"},
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
"http_signatures": {:hex, :http_signatures, "0.1.2", "ed1cc7043abcf5bb4f30d68fb7bad9d618ec1a45c4ff6c023664e78b67d9c406", [:mix], [], "hexpm", "f08aa9ac121829dae109d608d83c84b940ef2f183ae50f2dd1e9a8bc619d8be7"},
"httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"inet_cidr": {:hex, :inet_cidr, "1.0.8", "d26bb7bdbdf21ae401ead2092bf2bb4bf57fe44a62f5eaa5025280720ace8a40", [:mix], [], "hexpm", "d5b26da66603bb56c933c65214c72152f0de9a6ea53618b56d63302a68f6a90e"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"joken": {:hex, :joken, "2.6.0", "b9dd9b6d52e3e6fcb6c65e151ad38bf4bc286382b5b6f97079c47ade6b1bcc6a", [:mix], [{:jose, "~> 1.11.5", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5a95b05a71cd0b54abd35378aeb1d487a23a52c324fa7efdffc512b655b5aaa7"},
"jose": {:hex, :jose, "1.11.6", "613fda82552128aa6fb804682e3a616f4bc15565a048dabd05b1ebd5827ed965", [:mix, :rebar3], [], "hexpm", "6275cb75504f9c1e60eeacb771adfeee4905a9e182103aa59b53fed651ff9738"},
"joken": {:hex, :joken, "2.6.2", "5daaf82259ca603af4f0b065475099ada1b2b849ff140ccd37f4b6828ca6892a", [:mix], [{:jose, "~> 1.11.10", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5134b5b0a6e37494e46dbf9e4dad53808e5e787904b7c73972651b51cce3d72b"},
"jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"},
"jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"},
"linkify": {:hex, :linkify, "0.5.3", "5f8143d8f61f5ff08d3aeeff47ef6509492b4948d8f08007fbf66e4d2246a7f2", [:mix], [], "hexpm", "3ef35a1377d47c25506e07c1c005ea9d38d700699d92ee92825f024434258177"},
"logger_backends": {:hex, :logger_backends, "1.0.0", "09c4fad6202e08cb0fbd37f328282f16539aca380f512523ce9472b28edc6bdf", [:mix], [], "hexpm", "1faceb3e7ec3ef66a8f5746c5afd020e63996df6fd4eb8cdb789e5665ae6c9ce"},
"mail": {:hex, :mail, "0.3.1", "cb0a14e4ed8904e4e5a08214e686ccf6f9099346885db17d8c309381f865cc5c", [:mix], [], "hexpm", "1db701e89865c1d5fa296b2b57b1cd587587cca8d8a1a22892b35ef5a8e352a6"},
"majic": {:hex, :majic, "1.0.0", "37e50648db5f5c2ff0c9fb46454d034d11596c03683807b9fb3850676ffdaab3", [:make, :mix], [{:elixir_make, "~> 0.6.1", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "7905858f76650d49695f14ea55cd9aaaee0c6654fa391671d4cf305c275a0a9e"},
"majic": {:hex, :majic, "1.1.1", "16092a3a3376cc5e13d207e82ec06e05a5561170465e50cc11cc4df8a5747938", [:make, :mix], [{:elixir_make, "~> 0.8.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "7fbb0372f0447b3f777056177d6ab3f009742e68474f850521ff56b84bd85b96"},
"makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.3", "d684f4bac8690e70b06eb52dad65d26de2eefa44cd19d64a8095e1417df7c8fd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "b78dc853d2e670ff6390b605d807263bf606da3c82be37f9d7f68635bd886fc9"},
"makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"},
"meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"},
"mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"},
"mint": {:hex, :mint, "1.6.1", "065e8a5bc9bbd46a41099dfea3e0656436c5cbcb6e741c80bd2bad5cd872446f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4fc518dcc191d02f433393a72a7ba3f6f94b101d094cb6bf532ea54c89423780"},
"mimerl": {:hex, :mimerl, "1.4.0", "3882a5ca67fbbe7117ba8947f27643557adec38fa2307490c4c4207624cb213b", [:rebar3], [], "hexpm", "13af15f9f68c65884ecca3a3891d50a7b57d82152792f3e19d88650aa126b144"},
"mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"},
"mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"},
"mock": {:hex, :mock, "0.3.8", "7046a306b71db2488ef54395eeb74df0a7f335a7caca4a3d3875d1fc81c884dd", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "7fa82364c97617d79bb7d15571193fc0c4fe5afd0c932cef09426b3ee6fe2022"},
"mock": {:hex, :mock, "0.3.9", "10e44ad1f5962480c5c9b9fa779c6c63de9bd31997c8e04a853ec990a9d841af", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "9e1b244c4ca2551bb17bb8415eed89e40ee1308e0fbaed0a4fdfe3ec8a4adbd3"},
"mogrify": {:hex, :mogrify, "0.9.3", "238c782f00271dace01369ad35ae2e9dd020feee3443b9299ea5ea6bed559841", [:mix], [], "hexpm", "0189b1e1de27455f2b9ae8cf88239cefd23d38de9276eb5add7159aea51731e6"},
"mox": {:hex, :mox, "1.1.0", "0f5e399649ce9ab7602f72e718305c0f9cdc351190f72844599545e4996af73c", [:mix], [], "hexpm", "d44474c50be02d5b72131070281a5d3895c0e7a95c780e90bc0cfe712f633a13"},
"mua": {:hex, :mua, "0.2.3", "46b29b7b2bb14105c0b7be9526f7c452df17a7841b30b69871c024a822ff551c", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "7fe861a87fcc06a980d3941bbcb2634e5f0f30fd6ad15ef6c0423ff9dc7e46de"},
"mox": {:hex, :mox, "1.2.0", "a2cd96b4b80a3883e3100a221e8adc1b98e4c3a332a8fc434c39526babafd5b3", [:mix], [{:nimble_ownership, "~> 1.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}], "hexpm", "c7b92b3cc69ee24a7eeeaf944cd7be22013c52fcb580c1f33f50845ec821089a"},
"mua": {:hex, :mua, "0.2.4", "a9172ab0a1ac8732cf2699d739ceac3febcb9b4ffc540260ad2e32c0b6632af9", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "e7e4dacd5ad65f13e3542772e74a159c00bd2d5579e729e9bb72d2c73a266fb7"},
"multipart": {:hex, :multipart, "0.4.0", "634880a2148d4555d050963373d0e3bbb44a55b2badd87fa8623166172e9cda0", [:mix], [{:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm", "3c5604bc2fb17b3137e5d2abdf5dacc2647e60c5cc6634b102cf1aef75a06f0a"},
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
"nimble_ownership": {:hex, :nimble_ownership, "1.0.1", "f69fae0cdd451b1614364013544e66e4f5d25f36a2056a9698b793305c5aa3a6", [:mix], [], "hexpm", "3825e461025464f519f3f3e4a1f9b68c47dc151369611629ad08b636b73bb22d"},
"nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"},
"nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"},
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
"nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
"oban": {:hex, :oban, "2.18.3", "1608c04f8856c108555c379f2f56bc0759149d35fa9d3b825cb8a6769f8ae926", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "36ca6ca84ef6518f9c2c759ea88efd438a3c81d667ba23b02b062a0aa785475e"},
"oban": {:hex, :oban, "2.19.4", "045adb10db1161dceb75c254782f97cdc6596e7044af456a59decb6d06da73c1", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:igniter, "~> 0.5", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5fcc6219e6464525b808d97add17896e724131f498444a292071bf8991c99f97"},
"oban_live_dashboard": {:hex, :oban_live_dashboard, "0.1.1", "8aa4ceaf381c818f7d5c8185cc59942b8ac82ef0cf559881aacf8d3f8ac7bdd3", [:mix], [{:oban, "~> 2.15", [hex: :oban, repo: "hexpm", optional: false]}, {:phoenix_live_dashboard, "~> 0.7", [hex: :phoenix_live_dashboard, repo: "hexpm", optional: false]}], "hexpm", "16dc4ce9c9a95aa2e655e35ed4e675652994a8def61731a18af85e230e1caa63"},
"oban_plugins_lazarus": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/oban_plugins_lazarus.git", "e49fc355baaf0e435208bf5f534d31e26e897711", [ref: "e49fc355baaf0e435208bf5f534d31e26e897711"]},
"octo_fetch": {:hex, :octo_fetch, "0.4.0", "074b5ecbc08be10b05b27e9db08bc20a3060142769436242702931c418695b19", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "cf8be6f40cd519d7000bb4e84adcf661c32e59369ca2827c4e20042eda7a7fc6"},
"open_api_spex": {:hex, :open_api_spex, "3.18.2", "8c855e83bfe8bf81603d919d6e892541eafece3720f34d1700b58024dadde247", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "aa3e6dcfc0ad6a02596b2172662da21c9dd848dac145ea9e603f54e3d81b8d2b"},
"open_api_spex": {:hex, :open_api_spex, "3.22.0", "fbf90dc82681dc042a4ee79853c8e989efbba73d9e87439085daf849bbf8bc20", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0 or ~> 6.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "dd751ddbdd709bb4a5313e9a24530da6e66594773c7242a0c2592cbd9f589063"},
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"},
"phoenix": {:git, "https://github.com/feld/phoenix", "fb6dc76c657422e49600896c64aab4253fceaef6", [branch: "v1.7.14-websocket-headers"]},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.4.3", "86e9878f833829c3f66da03d75254c155d91d72a201eb56ae83482328dc7ca93", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d36c401206f3011fefd63d04e8ef626ec8791975d9d107f9a0817d426f61ac07"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.6.5", "c4ef322acd15a574a8b1a08eff0ee0a85e73096b53ce1403b6563709f15e1cea", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "26ec3208eef407f31b748cadd044045c6fd485fbff168e35963d2f9dfff28d4b"},
"phoenix_html": {:hex, :phoenix_html, "3.3.4", "42a09fc443bbc1da37e372a5c8e6755d046f22b9b11343bf885067357da21cb3", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0249d3abec3714aff3415e7ee3d9786cb325be3151e6c4b3021502c585bf53fb"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.3", "7ff51c9b6609470f681fbea20578dede0e548302b0c8bdf338b5a753a4f045bf", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "f9470a0a8bae4f56430a23d42f977b5a6205fdba6559d76f932b876bfaec652d"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.7", "405880012cb4b706f26dd1c6349125bfc903fb9e44d1ea668adaf4e04d4884b7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "3a8625cab39ec261d48a13b7468dc619c0ede099601b084e343968309bd4d7d7"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.19.5", "6e730595e8e9b8c5da230a814e557768828fd8dfeeb90377d2d8dbb52d4ec00a", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b2eaa0dd3cfb9bd7fb949b88217df9f25aed915e986a28ad5c8a0d054e7ca9d3"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
"phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.1", "b74ccaa8046fbc388a62134360ee7d9742d5a8ae74063f34eb050279de7a99e1", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "4000eeba3f9d7d1a6bf56d2bd56733d5cadf41a7f0d8ffe5bb67e7d667e204a2"},
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
"phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"},
"plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"},
"plug_cowboy": {:hex, :plug_cowboy, "2.7.1", "87677ffe3b765bc96a89be7960f81703223fe2e21efa42c125fcd0127dd9d6b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "02dbd5f9ab571b864ae39418db7811618506256f6d13b4a45037e5fe78dc5de3"},
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
"plug": {:hex, :plug, "1.18.1", "5067f26f7745b7e31bc3368bc1a2b818b9779faa959b49c934c17730efc911cf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "57a57db70df2b422b564437d2d33cf8d33cd16339c1edb190cd11b1a3a546cc2"},
"plug_cowboy": {:hex, :plug_cowboy, "2.7.4", "729c752d17cf364e2b8da5bdb34fb5804f56251e88bb602aff48ae0bd8673d11", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "9b85632bd7012615bae0a5d70084deb1b25d2bcbb32cab82d1e9a1e023168aa3"},
"plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"},
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
"postgrex": {:hex, :postgrex, "0.17.5", "0483d054938a8dc069b21bdd636bf56c487404c241ce6c319c1f43588246b281", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "50b8b11afbb2c4095a3ba675b4f055c416d0f3d7de6633a595fc131a828a67eb"},
"postgrex": {:hex, :postgrex, "0.21.1", "2c5cc830ec11e7a0067dd4d623c049b3ef807e9507a424985b8dcf921224cd88", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "27d8d21c103c3cc68851b533ff99eef353e6a0ff98dc444ea751de43eb48bdac"},
"pot": {:hex, :pot, "1.0.2", "13abb849139fdc04ab8154986abbcb63bdee5de6ed2ba7e1713527e33df923dd", [:rebar3], [], "hexpm", "78fe127f5a4f5f919d6ea5a2a671827bd53eb9d37e5b4128c0ad3df99856c2e0"},
"prom_ex": {:hex, :prom_ex, "1.9.0", "63e6dda6c05cdeec1f26c48443dcc38ffd2118b3665ae8d2bd0e5b79f2aea03e", [:mix], [{:absinthe, ">= 1.6.0", [hex: :absinthe, repo: "hexpm", optional: true]}, {:broadway, ">= 1.0.2", [hex: :broadway, repo: "hexpm", optional: true]}, {:ecto, ">= 3.5.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:finch, "~> 0.15", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:oban, ">= 2.4.0", [hex: :oban, repo: "hexpm", optional: true]}, {:octo_fetch, "~> 0.3", [hex: :octo_fetch, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.5.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_live_view, ">= 0.14.0", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}, {:plug, ">= 1.12.1", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, "~> 2.5 or ~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:telemetry, ">= 1.0.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.0", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}, {:telemetry_poller, "~> 1.0", [hex: :telemetry_poller, repo: "hexpm", optional: false]}], "hexpm", "01f3d4f69ec93068219e686cc65e58a29c42bea5429a8ff4e2121f19db178ee6"},
"prom_ex": {:hex, :prom_ex, "1.9.0", "63e6dda6c05cdeec1f26c48443dcc38ffd2118b3665ae8d2bd0e5b79f2aea03e", [:mix], [{:absinthe, ">= 1.6.0", [hex: :absinthe, repo: "hexpm", optional: true]}, {:broadway, ">= 1.0.2", [hex: :broadway, repo: "hexpm", optional: true]}, {:ecto, ">= 3.5.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:finch, "~> 0.15", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:oban, ">= 2.4.0", [hex: :oban, repo: "hexpm", optional: true]}, {:octo_fetch, "~> 0.3", [hex: :octo_fetch, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.5.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_live_view, ">= 0.14.0", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}, {:plug, ">= 1.12.1", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, "~> 2.5", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:telemetry, ">= 1.0.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.0", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}, {:telemetry_poller, "~> 1.0", [hex: :telemetry_poller, repo: "hexpm", optional: false]}], "hexpm", "01f3d4f69ec93068219e686cc65e58a29c42bea5429a8ff4e2121f19db178ee6"},
"prometheus": {:hex, :prometheus, "4.10.0", "792adbf0130ff61b5fa8826f013772af24b6e57b984445c8d602c8a0355704a1", [:mix, :rebar3], [{:quantile_estimator, "~> 0.2.1", [hex: :quantile_estimator, repo: "hexpm", optional: false]}], "hexpm", "2a99bb6dce85e238c7236fde6b0064f9834dc420ddbd962aac4ea2a3c3d59384"},
"prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "8d66289f77f913b37eda81fd287340c17e61a447549deb28efc254532b2bed82"},
"prometheus_ex": {:git, "https://github.com/lanodan/prometheus.ex.git", "31f7fbe4b71b79ba27efc2a5085746c4011ceb8f", [branch: "fix/elixir-1.14"]},
@ -124,33 +126,33 @@
"prometheus_phx": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/prometheus-phx.git", "9cd8f248c9381ffedc799905050abce194a97514", [branch: "no-logging"]},
"prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm", "0273a6483ccb936d79ca19b0ab629aef0dba958697c94782bb728b920dfc6a79"},
"quantile_estimator": {:hex, :quantile_estimator, "0.2.1", "ef50a361f11b5f26b5f16d0696e46a9e4661756492c981f7b2229ef42ff1cd15", [:rebar3], [], "hexpm", "282a8a323ca2a845c9e6f787d166348f776c1d4a41ede63046d72d422e3da946"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"recon": {:hex, :recon, "2.5.4", "05dd52a119ee4059fa9daa1ab7ce81bc7a8161a2f12e9d42e9d551ffd2ba901c", [:mix, :rebar3], [], "hexpm", "e9ab01ac7fc8572e41eb59385efeb3fb0ff5bf02103816535bacaedf327d0263"},
"ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"},
"recon": {:hex, :recon, "2.5.6", "9052588e83bfedfd9b72e1034532aee2a5369d9d9343b61aeb7fbce761010741", [:mix, :rebar3], [], "hexpm", "96c6799792d735cc0f0fd0f86267e9d351e63339cbe03df9d162010cefc26bb0"},
"remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8", [ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"]},
"rustler": {:hex, :rustler, "0.30.0", "cefc49922132b072853fa9b0ca4dc2ffcb452f68fb73b779042b02d545e097fb", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "9ef1abb6a7dda35c47cfc649e6a5a61663af6cf842a55814a554a84607dee389"},
"sleeplocks": {:hex, :sleeplocks, "1.1.2", "d45aa1c5513da48c888715e3381211c859af34bee9b8290490e10c90bb6ff0ca", [:rebar3], [], "hexpm", "9fe5d048c5b781d6305c1a3a0f40bb3dfc06f49bf40571f3d2d0c57eaa7f59a5"},
"sleeplocks": {:hex, :sleeplocks, "1.1.3", "96a86460cc33b435c7310dbd27ec82ca2c1f24ae38e34f8edde97f756503441a", [:rebar3], [], "hexpm", "d3b3958552e6eb16f463921e70ae7c767519ef8f5be46d7696cc1ed649421321"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
"statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},
"sweet_xml": {:hex, :sweet_xml, "0.7.4", "a8b7e1ce7ecd775c7e8a65d501bc2cd933bff3a9c41ab763f5105688ef485d08", [:mix], [], "hexpm", "e7c4b0bdbf460c928234951def54fe87edf1a170f6896675443279e2dbeba167"},
"swoosh": {:hex, :swoosh, "1.16.9", "20c6a32ea49136a4c19f538e27739bb5070558c0fa76b8a95f4d5d5ca7d319a1", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.0", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "878b1a7a6c10ebbf725a3349363f48f79c5e3d792eb621643b0d276a38acc0a6"},
"statistex": {:hex, :statistex, "1.1.0", "7fec1eb2f580a0d2c1a05ed27396a084ab064a40cfc84246dbfb0c72a5c761e5", [:mix], [], "hexpm", "f5950ea26ad43246ba2cce54324ac394a4e7408fdcf98b8e230f503a0cba9cf5"},
"sweet_xml": {:hex, :sweet_xml, "0.7.5", "803a563113981aaac202a1dbd39771562d0ad31004ddbfc9b5090bdcd5605277", [:mix], [], "hexpm", "193b28a9b12891cae351d81a0cead165ffe67df1b73fe5866d10629f4faefb12"},
"swoosh": {:hex, :swoosh, "1.16.12", "cbb24ad512f2f7f24c7a469661c188a00a8c2cd64e0ab54acd1520f132092dfd", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0e262df1ae510d59eeaaa3db42189a2aa1b3746f73771eb2616fc3f7ee63cc20"},
"syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
"table_rex": {:hex, :table_rex, "4.0.0", "3c613a68ebdc6d4d1e731bc973c233500974ec3993c99fcdabb210407b90959b", [:mix], [], "hexpm", "c35c4d5612ca49ebb0344ea10387da4d2afe278387d4019e4d8111e815df8f55"},
"table_rex": {:hex, :table_rex, "4.1.0", "fbaa8b1ce154c9772012bf445bfb86b587430fb96f3b12022d3f35ee4a68c918", [:mix], [], "hexpm", "95932701df195d43bc2d1c6531178fc8338aa8f38c80f098504d529c43bc2601"},
"telemetry": {:hex, :telemetry, "1.0.0", "0f453a102cdf13d506b7c0ab158324c337c41f1cc7548f0bc0e130bbf0ae9452", [:rebar3], [], "hexpm", "73bc09fa59b4a0284efb4624335583c528e07ec9ae76aca96ea0673850aec57a"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"},
"telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.2.0", "b583c3f18508f5c5561b674d16cf5d9afd2ea3c04505b7d92baaeac93c1b8260", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "9cba950e1c4733468efbe3f821841f34ac05d28e7af7798622f88ecdbbe63ea3"},
"telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"},
"tesla": {:hex, :tesla, "1.11.0", "81b2b10213dddb27105ec6102d9eb0cc93d7097a918a0b1594f2dfd1a4601190", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "b83ab5d4c2d202e1ea2b7e17a49f788d49a699513d7c4f08f2aef2c281be69db"},
"thousand_island": {:hex, :thousand_island, "1.3.5", "6022b6338f1635b3d32406ff98d68b843ba73b3aa95cfc27154223244f3a6ca5", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2be6954916fdfe4756af3239fb6b6d75d0b8063b5df03ba76fd8a4c87849e180"},
"telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.2.1", "c9755987d7b959b557084e6990990cb96a50d6482c683fb9622a63837f3cd3d8", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "5e2c599da4983c4f88a33e9571f1458bf98b0cf6ba930f1dc3a6e8cf45d5afb6"},
"telemetry_poller": {:hex, :telemetry_poller, "1.3.0", "d5c46420126b5ac2d72bc6580fb4f537d35e851cc0f8dbd571acf6d6e10f5ec7", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "51f18bed7128544a50f75897db9974436ea9bfba560420b646af27a9a9b35211"},
"tesla": {:hex, :tesla, "1.15.3", "3a2b5c37f09629b8dcf5d028fbafc9143c0099753559d7fe567eaabfbd9b8663", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.21", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:mox, "~> 1.0", [hex: :mox, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "98bb3d4558abc67b92fb7be4cd31bb57ca8d80792de26870d362974b58caeda7"},
"thousand_island": {:hex, :thousand_island, "1.3.14", "ad45ebed2577b5437582bcc79c5eccd1e2a8c326abf6a3464ab6c06e2055a34a", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d0d24a929d31cdd1d7903a4fe7f2409afeedff092d277be604966cd6aa4307ef"},
"timex": {:hex, :timex, "3.7.7", "3ed093cae596a410759104d878ad7b38e78b7c2151c6190340835515d4a46b8a", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "0ec4b09f25fe311321f9fc04144a7e3affe48eb29481d7a5583849b6c4dfa0a7"},
"toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"},
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
"tzdata": {:hex, :tzdata, "1.0.5", "69f1ee029a49afa04ad77801febaf69385f3d3e3d1e4b56b9469025677b89a28", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "55519aa2a99e5d2095c1e61cc74c9be69688f8ab75c27da724eb8279ff402a5a"},
"ueberauth": {:hex, :ueberauth, "0.10.7", "5a31cbe11e7ce5c7484d745dc9e1f11948e89662f8510d03c616de03df581ebd", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "0bccf73e2ffd6337971340832947ba232877aa8122dba4c95be9f729c8987377"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
"ueberauth": {:hex, :ueberauth, "0.10.8", "ba78fbcbb27d811a6cd06ad851793aaf7d27c3b30c9e95349c2c362b344cd8f0", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f2d3172e52821375bccb8460e5fa5cb91cfd60b19b636b6e57e9759b6f8c10c1"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"},
"unsafe": {:hex, :unsafe, "1.0.2", "23c6be12f6c1605364801f4b47007c0c159497d0446ad378b5cf05f1855c0581", [:mix], [], "hexpm", "b485231683c3ab01a9cd44cb4a79f152c6f3bb87358439c6f68791b85c2df675"},
"vix": {:hex, :vix, "0.26.0", "027f10b6969b759318be84bd0bd8c88af877445e4e41cf96a0460392cea5399c", [:make, :mix], [{:castore, "~> 1.0 or ~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:cc_precompiler, "~> 0.2 or ~> 0.1.4", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.8 or ~> 0.7.3", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:kino, "~> 0.7", [hex: :kino, repo: "hexpm", optional: true]}], "hexpm", "71b0a79ae7f199cacfc8e679b0e4ba25ee47dc02e182c5b9097efb29fbe14efd"},
"vix": {:hex, :vix, "0.26.0", "027f10b6969b759318be84bd0bd8c88af877445e4e41cf96a0460392cea5399c", [:make, :mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:cc_precompiler, "~> 0.1.4 or ~> 0.2", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.7.3 or ~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:kino, "~> 0.7", [hex: :kino, repo: "hexpm", optional: true]}], "hexpm", "71b0a79ae7f199cacfc8e679b0e4ba25ee47dc02e182c5b9097efb29fbe14efd"},
"web_push_encryption": {:hex, :web_push_encryption, "0.3.1", "76d0e7375142dfee67391e7690e89f92578889cbcf2879377900b5620ee4708d", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.11.1", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "4f82b2e57622fb9337559058e8797cb0df7e7c9790793bdc4e40bc895f70e2a2"},
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
"websock_adapter": {:hex, :websock_adapter, "0.5.6", "0437fe56e093fd4ac422de33bf8fc89f7bc1416a3f2d732d8b2c8fd54792fe60", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "e04378d26b0af627817ae84c92083b7e97aca3121196679b73c73b99d0d133ea"},
"websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"},
"websockex": {:hex, :websockex, "0.4.3", "92b7905769c79c6480c02daacaca2ddd49de936d912976a4d3c923723b647bf0", [:mix], [], "hexpm", "95f2e7072b85a3a4cc385602d42115b73ce0b74a9121d0d6dbbf557645ac53e4"},
}

View file

@ -0,0 +1,76 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"Hashtag": "as:Hashtag",
"PropertyValue": "schema:PropertyValue",
"conversation": "ostatus:conversation",
"dfrn": "http://purl.org/macgirvin/dfrn/1.0/",
"diaspora": "https://diasporafoundation.org/ns/",
"directMessage": "litepub:directMessage",
"discoverable": "toot:discoverable",
"featured": {
"@id": "toot:featured",
"@type": "@id"
},
"litepub": "http://litepub.social/ns#",
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"ostatus": "http://ostatus.org#",
"quoteUrl": "as:quoteUrl",
"schema": "http://schema.org#",
"sensitive": "as:sensitive",
"toot": "http://joinmastodon.org/ns#",
"value": "schema:value",
"vcard": "http://www.w3.org/2006/vcard/ns#"
}
],
"actor": "https://my-place.social/profile/vaartis",
"cc": [
"https://my-place.social/followers/vaartis"
],
"id": "https://my-place.social/objects/e599373b-1368-4b20-cd24-837166957182/Undo",
"instrument": {
"id": "https://my-place.social/friendica",
"name": "Friendica 'Interrupted Fern' 2024.12-1576",
"type": "Application",
"url": "https://my-place.social"
},
"object": {
"actor": "https://my-place.social/profile/vaartis",
"cc": [
"https://my-place.social/followers/vaartis"
],
"diaspora:guid": "e599373b-1968-4b20-cd24-80d340160302",
"diaspora:like": "{\"author\":\"vaartis@my-place.social\",\"guid\":\"e599373b-1968-4b20-cd24-80d340160302\",\"parent_guid\":\"cd36feba-c31f3ed3fd5c064a-17c31593\",\"parent_type\":\"Post\",\"positive\":\"false\",\"author_signature\":\"xR2zLJNfc9Nhx1n8LLMWM1kde12my4cqamIsrH\\/UntKzuDwO4DuHBL0fkFhgC\\/dylxm4HqsHD45MQbtwQCVGq6WhC96TrbMuYEK61HIO23dTr3m+qJVtfdH4wyhUNHgiiYPhZpkLDfnR1JiRWmFTlmZC8q8JEkOB5IQsrWia2eOR6IsqDcdKO\\/Urgns9\\/BdQi8KnchBKSEFc1iUtcOEruvhunKGyW5zI\\/Rltfdz3xGH8tlw+YlMXeWXPnqgOJ9GzNA0lwG4U421L6yylYagW7oxIznnBLB4bO46vYZbgXZV1hiI9ZyveHOinLMY1QkmTj5CNvnx3\\/VJwLovd0v+0Nr2vu\\/3ftbpBXc6L1bsNjlRqtsfwJlcgl+tH1DC4W8tKf+Y3tdtzVw0CHXCuacxHLyq5wZd\\/5YfYR9SJQ\\/jInU4PHA5+hIE3PGqNUp5QfFE0umq56H7MQKsIPgM5mMV4fPAA8OpltuMVDvQYUxalrnvoTf00k90x1wCTK71\\/jQGh7r7PmGvSdfPr+65eVTjITD8\\/lTGIb8850v1fl3\\/i2R8Dv17jQIRyX5o9MXPSO6jHo4Swma5RzPA\\/0bRj6qRTyPkM1L9qEIr+2H2I7KKhT2ZE5GhAU7yI9A3VLBWzpTrUPMGbfpd1OjVTEqXAdMjpLDYI3Mh5zQ58p8xCzt+W+t0=\"}",
"id": "https://my-place.social/objects/e599373b-1368-4b20-cd24-837166957182",
"instrument": {
"id": "https://my-place.social/friendica",
"name": "Friendica 'Interrupted Fern' 2024.12-1576",
"type": "Application",
"url": "https://my-place.social"
},
"object": "https://pl.kotobank.ch/objects/301bce65-8a1b-4c49-a65c-fe2ce861a213",
"published": "2025-06-12T18:47:41Z",
"to": [
"https://pl.kotobank.ch/users/vaartis",
"https://mitra.social/users/silverpill",
"https://www.w3.org/ns/activitystreams#Public"
],
"type": "Dislike"
},
"published": "2025-06-12T18:41:25Z",
"signature": {
"created": "2025-06-12T18:44:16Z",
"creator": "https://my-place.social/profile/vaartis#main-key",
"nonce": "2d67847d4bd4b1b83a30d61eac6cdc7ad6b980df06a8b9b97217e1d8f7b6cf20",
"signatureValue": "LnoRMZuQGDvTICkShGBq28ynaj2lF1bViJFGS6n4gKn3IbxPWATHxao43gxWRc+HCTrHNg7quzgaW4+PYM7UVUz3jO+bjNKsN845nijOVdyFrPOXbuaij3KQh2OoHhFJWoV/ZQQTFF0kRK1qT4BwG+P8NqOOKAMv+Cw7ruQH+f2w7uDgcNIbCD1gLcwb6cw7WVe5qu8yMkKqp2kBdqW3RCsI85RmmFgwehDgH5nrX7ER1qbeLWrqy7echwD9/fO3rqAu13xDNyiGZHDT7JB3RUt0AyMm0XCfjbwSQ0n+MkYXgE4asvFz81+iiPCLt+6gePWAFc5odF1FxdySBpSuUOs4p92NzP9OhQ0c0qrqrzYI7aYklY7oMfxjkva+X+0bm3up+2IRJdnZa/pXlmwdcqTpyMr1sgzaexMUNBp3dq7zA51eEaakLDX3i2onXJowfmze3+6XgPAFHYamR+pRNtuEoY4uyYEK3fj5GgwJ4RtFJMYVoEs/Q8h3OgYRcK1FE9UlDjSqbQ7QIRn2Ib4wjgmkeM0vrHIwh/1CtqA/M/6WuYFzCaJBc8O9ykpK9ZMbw64ToQXKf2SqhZsDoyTWRWTO1PXOk1XCAAElUh8/WCyeghvgqLXn0LHov4lmBsHA5iMUcLqBKD3GJIHd+ExrOFxMZs4mBLLGyz0p5joJ3NY=",
"type": "RsaSignature2017"
},
"to": [
"https://pl.kotobank.ch/users/vaartis",
"https://mitra.social/users/silverpill",
"https://www.w3.org/ns/activitystreams#Public"
],
"type": "Undo"
}

56
test/fixtures/friendica-dislike.json vendored Normal file
View file

@ -0,0 +1,56 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"Hashtag": "as:Hashtag",
"PropertyValue": "schema:PropertyValue",
"conversation": "ostatus:conversation",
"dfrn": "http://purl.org/macgirvin/dfrn/1.0/",
"diaspora": "https://diasporafoundation.org/ns/",
"directMessage": "litepub:directMessage",
"discoverable": "toot:discoverable",
"featured": {
"@id": "toot:featured",
"@type": "@id"
},
"litepub": "http://litepub.social/ns#",
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"ostatus": "http://ostatus.org#",
"quoteUrl": "as:quoteUrl",
"schema": "http://schema.org#",
"sensitive": "as:sensitive",
"toot": "http://joinmastodon.org/ns#",
"value": "schema:value",
"vcard": "http://www.w3.org/2006/vcard/ns#"
}
],
"actor": "https://my-place.social/profile/vaartis",
"cc": [
"https://my-place.social/followers/vaartis"
],
"diaspora:guid": "e599373b-1968-4b20-cd24-80d340160302",
"diaspora:like": "{\"author\":\"vaartis@my-place.social\",\"guid\":\"e599373b-1968-4b20-cd24-80d340160302\",\"parent_guid\":\"cd36feba-c31f3ed3fd5c064a-17c31593\",\"parent_type\":\"Post\",\"positive\":\"false\",\"author_signature\":\"xR2zLJNfc9Nhx1n8LLMWM1kde12my4cqamIsrH\\/UntKzuDwO4DuHBL0fkFhgC\\/dylxm4HqsHD45MQbtwQCVGq6WhC96TrbMuYEK61HIO23dTr3m+qJVtfdH4wyhUNHgiiYPhZpkLDfnR1JiRWmFTlmZC8q8JEkOB5IQsrWia2eOR6IsqDcdKO\\/Urgns9\\/BdQi8KnchBKSEFc1iUtcOEruvhunKGyW5zI\\/Rltfdz3xGH8tlw+YlMXeWXPnqgOJ9GzNA0lwG4U421L6yylYagW7oxIznnBLB4bO46vYZbgXZV1hiI9ZyveHOinLMY1QkmTj5CNvnx3\\/VJwLovd0v+0Nr2vu\\/3ftbpBXc6L1bsNjlRqtsfwJlcgl+tH1DC4W8tKf+Y3tdtzVw0CHXCuacxHLyq5wZd\\/5YfYR9SJQ\\/jInU4PHA5+hIE3PGqNUp5QfFE0umq56H7MQKsIPgM5mMV4fPAA8OpltuMVDvQYUxalrnvoTf00k90x1wCTK71\\/jQGh7r7PmGvSdfPr+65eVTjITD8\\/lTGIb8850v1fl3\\/i2R8Dv17jQIRyX5o9MXPSO6jHo4Swma5RzPA\\/0bRj6qRTyPkM1L9qEIr+2H2I7KKhT2ZE5GhAU7yI9A3VLBWzpTrUPMGbfpd1OjVTEqXAdMjpLDYI3Mh5zQ58p8xCzt+W+t0=\"}",
"id": "https://my-place.social/objects/e599373b-1368-4b20-cd24-837166957182",
"instrument": {
"id": "https://my-place.social/friendica",
"name": "Friendica 'Interrupted Fern' 2024.12-1576",
"type": "Application",
"url": "https://my-place.social"
},
"object": "https://pl.kotobank.ch/objects/301bce65-8a1b-4c49-a65c-fe2ce861a213",
"published": "2025-06-12T18:47:41Z",
"signature": {
"created": "2025-06-12T18:47:42Z",
"creator": "https://my-place.social/profile/vaartis#main-key",
"nonce": "84e496f80b09d7a299c5cc89e8cadd13abf621b3a0a321684fa74278b68a6dd8",
"signatureValue": "qe2WxY+j7daIYLRadCctgal6A1s9XgoiMfM/8KjJm15w0sSizYYqruyQ5gS44e+cj5GHc9v5gP2ieod5v7eHAPzlcDI4bfkcyHVapAXTqU67ZebW+v6Q+21IMDgqrkYCv5TbV7LTxltW59dlqovpHE4TEe/M7xLKWJ3vVchRUcWqH9kDmak0nacoqYVAb5E9jYnQhUWPTCfPV82qQpeWQPOZ4iIvPw6rDkSSY5jL6bCogBZblHGpUjXfe/FPlacaCWiTQdoga3yOBXB1RYPw9nh5FI5Xkv/oi+52WmJrECinlD6AL8/BpiYvKz236zy7p/TR4BXlCx9RR/msjOnSabkQ4kmYFrRr80UDCGF+CdkdzLl8K9rSE3PbF1+nEqD7X0GOWn/DdtixqXJw6IR4bh32YW2SlcrSRBvI1p82Mv68BeqRaYqL6FAhKFwLhX5JpXngZ3k0g7rWWxc498voPWnFZDyCTRNxO9VIIUavDDEQ0BdFk6WDb8zx9tsAg8JoK57eVDcFly7tfVQffYiHpve06d8ag1DtzipqguRsURmuqpGNMq28XBTxwtrP2LnXXHKxoYN/YQ9cDnCKclbx7/uKmOVMLkLZlM0wAVoZpm5z2fG4voKqFiGZ1PoiFY2sN4URMArJtygV3PlTX4ASAQrak0ksvEo9msrBUD0Su9c=",
"type": "RsaSignature2017"
},
"to": [
"https://pl.kotobank.ch/users/vaartis",
"https://mitra.social/users/silverpill",
"https://www.w3.org/ns/activitystreams#Public"
],
"type": "Dislike"
}

View file

@ -42,9 +42,10 @@ defmodule Mix.Tasks.Pleroma.AppTest do
test "with errors" do
Mix.Tasks.Pleroma.App.run(["create"])
{:mix_shell, :error, ["Creating failed:"]}
{:mix_shell, :error, ["name: can't be blank"]}
{:mix_shell, :error, ["redirect_uris: can't be blank"]}
assert_receive {:mix_shell, :error, ["Creating failed:"]}
assert_receive {:mix_shell, :error, ["name: can't be blank"]}
assert_receive {:mix_shell, :error, ["redirect_uris: can't be blank"]}
end
defp assert_app(name, redirect, scopes) do

View file

@ -13,6 +13,9 @@ defmodule Pleroma.Emoji.PackTest do
)
setup do
# Reload emoji to ensure a clean state
Emoji.reload()
pack_path = Path.join(@emoji_path, "dump_pack")
File.mkdir(pack_path)

View file

@ -14,4 +14,133 @@ defmodule Pleroma.HashtagTest do
assert {:name, {"can't be blank", [validation: :required]}} in changeset.errors
end
end
describe "search_hashtags" do
test "searches hashtags by partial match" do
{:ok, _} = Hashtag.get_or_create_by_name("car")
{:ok, _} = Hashtag.get_or_create_by_name("racecar")
{:ok, _} = Hashtag.get_or_create_by_name("nascar")
{:ok, _} = Hashtag.get_or_create_by_name("bicycle")
results = Hashtag.search("car")
assert "car" in results
assert "racecar" in results
assert "nascar" in results
refute "bicycle" in results
results = Hashtag.search("race")
assert "racecar" in results
refute "car" in results
refute "nascar" in results
refute "bicycle" in results
results = Hashtag.search("nonexistent")
assert results == []
end
test "searches hashtags by multiple words in query" do
{:ok, _} = Hashtag.get_or_create_by_name("computer")
{:ok, _} = Hashtag.get_or_create_by_name("laptop")
{:ok, _} = Hashtag.get_or_create_by_name("desktop")
{:ok, _} = Hashtag.get_or_create_by_name("phone")
# Search for "new computer" - should return "computer"
results = Hashtag.search("new computer")
assert "computer" in results
refute "laptop" in results
refute "desktop" in results
refute "phone" in results
# Search for "computer laptop" - should return both
results = Hashtag.search("computer laptop")
assert "computer" in results
assert "laptop" in results
refute "desktop" in results
refute "phone" in results
# Search for "new phone" - should return "phone"
results = Hashtag.search("new phone")
assert "phone" in results
refute "computer" in results
refute "laptop" in results
refute "desktop" in results
end
test "supports pagination" do
{:ok, _} = Hashtag.get_or_create_by_name("alpha")
{:ok, _} = Hashtag.get_or_create_by_name("beta")
{:ok, _} = Hashtag.get_or_create_by_name("gamma")
{:ok, _} = Hashtag.get_or_create_by_name("delta")
results = Hashtag.search("a", limit: 2)
assert length(results) == 2
results = Hashtag.search("a", limit: 2, offset: 1)
assert length(results) == 2
end
test "handles matching many search terms" do
{:ok, _} = Hashtag.get_or_create_by_name("computer")
{:ok, _} = Hashtag.get_or_create_by_name("laptop")
{:ok, _} = Hashtag.get_or_create_by_name("phone")
{:ok, _} = Hashtag.get_or_create_by_name("tablet")
results = Hashtag.search("new fast computer laptop phone tablet device")
assert "computer" in results
assert "laptop" in results
assert "phone" in results
assert "tablet" in results
end
test "ranks results by match quality" do
{:ok, _} = Hashtag.get_or_create_by_name("my_computer")
{:ok, _} = Hashtag.get_or_create_by_name("computer_science")
{:ok, _} = Hashtag.get_or_create_by_name("computer")
results = Hashtag.search("computer")
# Exact match first
assert Enum.at(results, 0) == "computer"
# Prefix match would be next
assert Enum.at(results, 1) == "computer_science"
# worst match is last
assert Enum.at(results, 2) == "my_computer"
end
test "prioritizes shorter names when ranking is equal" do
# Create hashtags with same ranking but different lengths
{:ok, _} = Hashtag.get_or_create_by_name("car")
{:ok, _} = Hashtag.get_or_create_by_name("racecar")
{:ok, _} = Hashtag.get_or_create_by_name("nascar")
# Search for "car" - shorter names should come first
results = Hashtag.search("car")
# Shortest exact match first
assert Enum.at(results, 0) == "car"
assert "racecar" in results
assert "nascar" in results
end
test "handles hashtag symbols in search query" do
{:ok, _} = Hashtag.get_or_create_by_name("computer")
{:ok, _} = Hashtag.get_or_create_by_name("laptop")
{:ok, _} = Hashtag.get_or_create_by_name("phone")
results_with_hash = Hashtag.search("#computer #laptop")
results_without_hash = Hashtag.search("computer laptop")
assert results_with_hash == results_without_hash
results_mixed = Hashtag.search("#computer laptop #phone")
assert "computer" in results_mixed
assert "laptop" in results_mixed
assert "phone" in results_mixed
results_only_hash = Hashtag.search("#computer")
results_no_hash = Hashtag.search("computer")
assert results_only_hash == results_no_hash
end
end
end

View file

@ -25,6 +25,9 @@ defmodule Pleroma.HTTPTest do
%{method: :post, url: "http://example.com/world"} ->
%Tesla.Env{status: 200, body: "world"}
%{method: :get, url: "https://example.com/emoji/Pack%201/koronebless.png?foo=bar+baz"} ->
%Tesla.Env{status: 200, body: "emoji data"}
end)
:ok
@ -67,4 +70,20 @@ defmodule Pleroma.HTTPTest do
}
end
end
test "URL encoding properly encodes URLs with spaces" do
clear_config(:test_url_encoding, true)
url_with_space = "https://example.com/emoji/Pack 1/koronebless.png?foo=bar baz"
{:ok, result} = HTTP.get(url_with_space)
assert result.status == 200
properly_encoded_url = "https://example.com/emoji/Pack%201/koronebless.png?foo=bar+baz"
{:ok, result} = HTTP.get(properly_encoded_url)
assert result.status == 200
end
end

View file

@ -3,19 +3,15 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Instances.InstanceTest do
alias Pleroma.Instances
alias Pleroma.Instances.Instance
alias Pleroma.Repo
alias Pleroma.Tests.ObanHelpers
alias Pleroma.Web.CommonAPI
use Oban.Testing, repo: Pleroma.Repo
use Pleroma.DataCase
import ExUnit.CaptureLog
import Pleroma.Factory
setup_all do: clear_config([:instance, :federation_reachability_timeout_days], 1)
describe "set_reachable/1" do
test "clears `unreachable_since` of existing matching Instance record having non-nil `unreachable_since`" do
unreachable_since = NaiveDateTime.to_iso8601(NaiveDateTime.utc_now())
@ -31,6 +27,32 @@ defmodule Pleroma.Instances.InstanceTest do
assert {:ok, instance} = Instance.set_reachable(instance.host)
refute instance.unreachable_since
end
test "cancels all ReachabilityWorker jobs for the domain" do
domain = "cancelme.example.org"
insert(:instance, host: domain, unreachable_since: NaiveDateTime.utc_now())
# Insert a ReachabilityWorker job for this domain, scheduled 5 minutes in the future
scheduled_at = DateTime.add(DateTime.utc_now(), 300, :second)
{:ok, job} =
Pleroma.Workers.ReachabilityWorker.new(
%{"domain" => domain, "phase" => "phase_1min", "attempt" => 1},
scheduled_at: scheduled_at
)
|> Oban.insert()
# Ensure the job is present
job = Pleroma.Repo.get(Oban.Job, job.id)
assert job
# Call set_reachable, which should delete the job
assert {:ok, _} = Instance.set_reachable(domain)
# Reload the job and assert it is deleted
job = Pleroma.Repo.get(Oban.Job, job.id)
refute job
end
end
describe "set_unreachable/1" do
@ -145,7 +167,11 @@ defmodule Pleroma.Instances.InstanceTest do
end
test "Doesn't scrapes unreachable instances" do
instance = insert(:instance, unreachable_since: Instances.reachability_datetime_threshold())
instance =
insert(:instance,
unreachable_since: NaiveDateTime.utc_now() |> NaiveDateTime.add(-:timer.hours(24))
)
url = "https://" <> instance.host
assert capture_log(fn -> assert nil == Instance.get_or_update_favicon(URI.parse(url)) end) =~
@ -213,32 +239,44 @@ defmodule Pleroma.Instances.InstanceTest do
end
end
test "delete_users_and_activities/1 deletes remote instance users and activities" do
[mario, luigi, _peach, wario] =
users = [
insert(:user, nickname: "mario@mushroom.kingdom", name: "Mario"),
insert(:user, nickname: "luigi@mushroom.kingdom", name: "Luigi"),
insert(:user, nickname: "peach@mushroom.kingdom", name: "Peach"),
insert(:user, nickname: "wario@greedville.biz", name: "Wario")
]
test "delete/1 schedules a job to delete the instance and users" do
insert(:user, nickname: "mario@mushroom.kingdom", name: "Mario")
{:ok, post1} = CommonAPI.post(mario, %{status: "letsa go!"})
{:ok, post2} = CommonAPI.post(luigi, %{status: "itsa me... luigi"})
{:ok, post3} = CommonAPI.post(wario, %{status: "WHA-HA-HA!"})
{:ok, _job} = Instance.delete("mushroom.kingdom")
{:ok, job} = Instance.delete_users_and_activities("mushroom.kingdom")
:ok = ObanHelpers.perform(job)
assert_enqueued(
worker: Pleroma.Workers.DeleteWorker,
args: %{"op" => "delete_instance", "host" => "mushroom.kingdom"}
)
end
[mario, luigi, peach, wario] = Repo.reload(users)
describe "check_unreachable/1" do
test "schedules a ReachabilityWorker job for the given domain" do
domain = "test.example.com"
refute mario.is_active
refute luigi.is_active
refute peach.is_active
refute peach.name == "Peach"
# Call check_unreachable
assert {:ok, _job} = Instance.check_unreachable(domain)
assert wario.is_active
assert wario.name == "Wario"
# Verify that a ReachabilityWorker job was scheduled
jobs = all_enqueued(worker: Pleroma.Workers.ReachabilityWorker)
assert length(jobs) == 1
[job] = jobs
assert job.args["domain"] == domain
end
assert [nil, nil, %{}] = Repo.reload([post1, post2, post3])
test "handles multiple calls for the same domain (uniqueness enforced)" do
domain = "duplicate.example.com"
assert {:ok, _job1} = Instance.check_unreachable(domain)
# Second call for the same domain
assert {:ok, %Oban.Job{conflict?: true}} = Instance.check_unreachable(domain)
# Should only have one job due to uniqueness
jobs = all_enqueued(worker: Pleroma.Workers.ReachabilityWorker)
assert length(jobs) == 1
[job] = jobs
assert job.args["domain"] == domain
end
end
end

View file

@ -6,74 +6,42 @@ defmodule Pleroma.InstancesTest do
alias Pleroma.Instances
use Pleroma.DataCase
setup_all do: clear_config([:instance, :federation_reachability_timeout_days], 1)
use Oban.Testing, repo: Pleroma.Repo
describe "reachable?/1" do
test "returns `true` for host / url with unknown reachability status" do
assert Instances.reachable?("unknown.site")
assert Instances.reachable?("http://unknown.site")
end
test "returns `false` for host / url marked unreachable for at least `reachability_datetime_threshold()`" do
host = "consistently-unreachable.name"
Instances.set_consistently_unreachable(host)
refute Instances.reachable?(host)
refute Instances.reachable?("http://#{host}/path")
end
test "returns `true` for host / url marked unreachable for less than `reachability_datetime_threshold()`" do
url = "http://eventually-unreachable.name/path"
Instances.set_unreachable(url)
assert Instances.reachable?(url)
assert Instances.reachable?(URI.parse(url).host)
end
test "raises FunctionClauseError exception on non-binary input" do
assert_raise FunctionClauseError, fn -> Instances.reachable?(nil) end
assert_raise FunctionClauseError, fn -> Instances.reachable?(1) end
end
end
describe "filter_reachable/1" do
setup do
host = "consistently-unreachable.name"
url1 = "http://eventually-unreachable.com/path"
url2 = "http://domain.com/path"
unreachable_host = "consistently-unreachable.name"
reachable_host = "http://domain.com/path"
Instances.set_consistently_unreachable(host)
Instances.set_unreachable(url1)
Instances.set_unreachable(unreachable_host)
result = Instances.filter_reachable([host, url1, url2, nil])
%{result: result, url1: url1, url2: url2}
result = Instances.filter_reachable([unreachable_host, reachable_host, nil])
%{result: result, reachable_host: reachable_host, unreachable_host: unreachable_host}
end
test "returns a map with keys containing 'not marked consistently unreachable' elements of supplied list",
%{result: result, url1: url1, url2: url2} do
assert is_map(result)
assert Enum.sort([url1, url2]) == result |> Map.keys() |> Enum.sort()
test "returns a list of only reachable elements",
%{result: result, reachable_host: reachable_host} do
assert is_list(result)
assert [reachable_host] == result
end
test "returns a map with `unreachable_since` values for keys",
%{result: result, url1: url1, url2: url2} do
assert is_map(result)
assert %NaiveDateTime{} = result[url1]
assert is_nil(result[url2])
end
test "returns an empty map for empty list or list containing no hosts / url" do
assert %{} == Instances.filter_reachable([])
assert %{} == Instances.filter_reachable([nil])
test "returns an empty list when provided no data" do
assert [] == Instances.filter_reachable([])
assert [] == Instances.filter_reachable([nil])
end
end
describe "set_reachable/1" do
test "sets unreachable url or host reachable" do
host = "domain.com"
Instances.set_consistently_unreachable(host)
Instances.set_unreachable(host)
refute Instances.reachable?(host)
Instances.set_reachable(host)
@ -103,22 +71,68 @@ defmodule Pleroma.InstancesTest do
end
end
describe "set_consistently_unreachable/1" do
test "sets reachable url or host unreachable" do
url = "http://domain.com?q="
assert Instances.reachable?(url)
describe "check_all_unreachable/0" do
test "schedules ReachabilityWorker jobs for all unreachable instances" do
domain1 = "unreachable1.example.com"
domain2 = "unreachable2.example.com"
domain3 = "unreachable3.example.com"
Instances.set_consistently_unreachable(url)
refute Instances.reachable?(url)
Instances.set_unreachable(domain1)
Instances.set_unreachable(domain2)
Instances.set_unreachable(domain3)
Instances.check_all_unreachable()
# Verify that ReachabilityWorker jobs were scheduled for all unreachable domains
jobs = all_enqueued(worker: Pleroma.Workers.ReachabilityWorker)
assert length(jobs) == 3
domains = Enum.map(jobs, & &1.args["domain"])
assert domain1 in domains
assert domain2 in domains
assert domain3 in domains
end
test "keeps unreachable url or host unreachable" do
host = "site.name"
Instances.set_consistently_unreachable(host)
refute Instances.reachable?(host)
test "does not schedule jobs for reachable instances" do
unreachable_domain = "unreachable.example.com"
reachable_domain = "reachable.example.com"
Instances.set_consistently_unreachable(host)
refute Instances.reachable?(host)
Instances.set_unreachable(unreachable_domain)
Instances.set_reachable(reachable_domain)
Instances.check_all_unreachable()
# Verify that only one job was scheduled (for the unreachable domain)
jobs = all_enqueued(worker: Pleroma.Workers.ReachabilityWorker)
assert length(jobs) == 1
[job] = jobs
assert job.args["domain"] == unreachable_domain
end
end
test "delete_all_unreachable/0 schedules DeleteWorker jobs for all unreachable instances" do
domain1 = "unreachable1.example.com"
domain2 = "unreachable2.example.com"
domain3 = "unreachable3.example.com"
Instances.set_unreachable(domain1)
Instances.set_unreachable(domain2)
Instances.set_unreachable(domain3)
Instances.delete_all_unreachable()
# Verify that DeleteWorker jobs were scheduled for all unreachable domains
jobs = all_enqueued(worker: Pleroma.Workers.DeleteWorker)
assert length(jobs) == 3
domains = Enum.map(jobs, & &1.args["host"])
assert domain1 in domains
assert domain2 in domains
assert domain3 in domains
# Verify all jobs are delete_instance operations
Enum.each(jobs, fn job ->
assert job.args["op"] == "delete_instance"
end)
end
end

View file

@ -308,4 +308,37 @@ defmodule Pleroma.ModerationLogTest do
assert log.data["message"] == "@#{moderator.nickname} deleted status ##{note.id}"
end
end
describe "get_log_entry_message/1" do
setup do
moderator = insert(:user, is_moderator: true)
[moderator: moderator]
end
test "handles unknown action types gracefully", %{moderator: moderator} do
log_entry = %ModerationLog{
data: %{
"actor" => %{"nickname" => moderator.nickname},
"action" => "unknown_action",
"some_data" => "test_value"
}
}
assert ModerationLog.get_log_entry_message(log_entry) =~ moderator.nickname
assert ModerationLog.get_log_entry_message(log_entry) =~ "unknown_action"
end
test "handles malformed log entries gracefully" do
log_entry = %ModerationLog{
data: %{
"action" => "force_password_reset"
# Missing "actor" and "subject" fields
}
}
message = ModerationLog.get_log_entry_message(log_entry)
assert is_binary(message)
assert message =~ "force_password_reset"
end
end
end

View file

@ -6,7 +6,6 @@ defmodule Pleroma.Object.FetcherTest do
use Pleroma.DataCase
alias Pleroma.Activity
alias Pleroma.Instances
alias Pleroma.Object
alias Pleroma.Object.Fetcher
alias Pleroma.Web.ActivityPub.ObjectValidator
@ -250,17 +249,6 @@ defmodule Pleroma.Object.FetcherTest do
result = Fetcher.fetch_object_from_id("https://example.com/objects/no-content-type")
assert {:fetch, {:error, nil}} = result
end
test "it resets instance reachability on successful fetch" do
id = "http://mastodon.example.org/@admin/99541947525187367"
Instances.set_consistently_unreachable(id)
refute Instances.reachable?(id)
{:ok, _object} =
Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")
assert Instances.reachable?(id)
end
end
describe "implementation quirks" do

View file

@ -395,4 +395,40 @@ defmodule Pleroma.ReverseProxyTest do
assert Conn.get_resp_header(conn, "content-type") == ["application/octet-stream"]
end
end
# Hackey is used for Reverse Proxy when Hackney or Finch is the Tesla Adapter
# Gun is able to proxy through Tesla, so it does not need testing as the
# test cases in the Pleroma.HTTPTest module are sufficient
describe "Hackney URL encoding:" do
setup do
ClientMock
|> expect(:request, fn :get,
"https://example.com/emoji/Pack%201/koronebless.png?foo=bar+baz",
_headers,
_body,
_opts ->
{:ok, 200, [{"content-type", "image/png"}], "It works!"}
end)
|> stub(:stream_body, fn _ -> :done end)
|> stub(:close, fn _ -> :ok end)
:ok
end
test "properly encodes URLs with spaces", %{conn: conn} do
url_with_space = "https://example.com/emoji/Pack 1/koronebless.png?foo=bar baz"
result = ReverseProxy.call(conn, url_with_space)
assert result.status == 200
end
test "properly encoded URL should not be altered", %{conn: conn} do
properly_encoded_url = "https://example.com/emoji/Pack%201/koronebless.png?foo=bar+baz"
result = ReverseProxy.call(conn, properly_encoded_url)
assert result.status == 200
end
end
end

View file

@ -51,7 +51,7 @@ defmodule Pleroma.Search.QdrantSearchTest do
})
Config
|> expect(:get, 3, fn
|> expect(:get, 4, fn
[Pleroma.Search, :module], nil ->
QdrantSearch
@ -93,7 +93,7 @@ defmodule Pleroma.Search.QdrantSearchTest do
})
Config
|> expect(:get, 3, fn
|> expect(:get, 4, fn
[Pleroma.Search, :module], nil ->
QdrantSearch
@ -158,7 +158,7 @@ defmodule Pleroma.Search.QdrantSearchTest do
end)
Config
|> expect(:get, 6, fn
|> expect(:get, 7, fn
[Pleroma.Search, :module], nil ->
QdrantSearch

View file

@ -8,7 +8,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
alias Pleroma.Activity
alias Pleroma.Delivery
alias Pleroma.Instances
alias Pleroma.Object
alias Pleroma.Tests.ObanHelpers
alias Pleroma.User
@ -601,23 +600,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert Activity.get_by_ap_id(data["id"])
end
test "it clears `unreachable` federation status of the sender", %{conn: conn} do
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
sender_url = data["actor"]
Instances.set_consistently_unreachable(sender_url)
refute Instances.reachable?(sender_url)
conn =
conn
|> assign(:valid_signature, true)
|> put_req_header("content-type", "application/activity+json")
|> post("/inbox", data)
assert "ok" == json_response(conn, 200)
assert Instances.reachable?(sender_url)
end
test "accept follow activity", %{conn: conn} do
clear_config([:instance, :federating], true)
relay = Relay.get_actor()
@ -941,23 +923,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert Activity.get_by_ap_id(data["id"])
end
test "it rejects an invalid incoming activity", %{conn: conn, data: data} do
user = insert(:user, is_active: false)
data =
data
|> Map.put("bcc", [user.ap_id])
|> Kernel.put_in(["object", "bcc"], [user.ap_id])
conn =
conn
|> assign(:valid_signature, true)
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/inbox", data)
assert "Invalid request." == json_response(conn, 400)
end
test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
user = insert(:user)
@ -1108,24 +1073,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert response(conn, 200) =~ note_object.data["content"]
end
test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
user = insert(:user)
data = Map.put(data, "bcc", [user.ap_id])
sender_host = URI.parse(data["actor"]).host
Instances.set_consistently_unreachable(sender_host)
refute Instances.reachable?(sender_host)
conn =
conn
|> assign(:valid_signature, true)
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/inbox", data)
assert "ok" == json_response(conn, 200)
assert Instances.reachable?(sender_host)
end
test "it removes all follower collections but actor's", %{conn: conn} do
[actor, recipient] = insert_pair(:user)
@ -1340,6 +1287,50 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
assert Activity.get_by_ap_id(data["id"])
end
test "it returns an error when receiving an activity sent to a deactivated user", %{
conn: conn,
data: data
} do
user = insert(:user)
{:ok, _} = User.set_activation(user, false)
data =
data
|> Map.put("bcc", [user.ap_id])
|> Kernel.put_in(["object", "bcc"], [user.ap_id])
conn =
conn
|> assign(:valid_signature, true)
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/inbox", data)
assert "User deactivated" == json_response(conn, 404)
end
test "it returns an error when receiving an activity sent from a deactivated user", %{
conn: conn,
data: data
} do
sender = insert(:user)
user = insert(:user)
{:ok, _} = User.set_activation(sender, false)
data =
data
|> Map.put("bcc", [user.ap_id])
|> Map.put("actor", sender.ap_id)
|> Kernel.put_in(["object", "bcc"], [user.ap_id])
conn =
conn
|> assign(:valid_signature, true)
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/inbox", data)
assert "Sender deactivated" == json_response(conn, 404)
end
end
describe "GET /users/:nickname/outbox" do

View file

@ -1270,6 +1270,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert activity == expected_activity
end
test "includes only reblogs on request" do
user = insert(:user)
{:ok, _} = ActivityBuilder.insert(%{"type" => "Create"}, %{:user => user})
{:ok, expected_activity} = ActivityBuilder.insert(%{"type" => "Announce"}, %{:user => user})
[activity] = ActivityPub.fetch_user_activities(user, nil, %{only_reblogs: true})
assert activity == expected_activity
end
describe "irreversible filters" do
setup do
user = insert(:user)

View file

@ -0,0 +1,139 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.QuietReplyTest do
use Pleroma.DataCase
import Pleroma.Factory
require Pleroma.Constants
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.MRF.QuietReply
alias Pleroma.Web.CommonAPI
test "replying to public post is forced to be quiet" do
batman = insert(:user, nickname: "batman")
robin = insert(:user, nickname: "robin")
{:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!"})
reply = %{
"type" => "Create",
"actor" => robin.ap_id,
"to" => [
batman.ap_id,
Pleroma.Constants.as_public()
],
"cc" => [robin.follower_address],
"object" => %{
"type" => "Note",
"actor" => robin.ap_id,
"content" => "@batman Wait up, I forgot my spandex!",
"to" => [
batman.ap_id,
Pleroma.Constants.as_public()
],
"cc" => [robin.follower_address],
"inReplyTo" => Object.normalize(post).data["id"]
}
}
assert {:ok, filtered} = QuietReply.filter(reply)
assert batman.ap_id in filtered["to"]
assert batman.ap_id in filtered["object"]["to"]
assert robin.follower_address in filtered["to"]
assert robin.follower_address in filtered["object"]["to"]
assert Pleroma.Constants.as_public() in filtered["cc"]
assert Pleroma.Constants.as_public() in filtered["object"]["cc"]
end
test "replying to unlisted post is unmodified" do
batman = insert(:user, nickname: "batman")
robin = insert(:user, nickname: "robin")
{:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!", visibility: "private"})
reply = %{
"type" => "Create",
"actor" => robin.ap_id,
"to" => [batman.ap_id],
"cc" => [],
"object" => %{
"type" => "Note",
"actor" => robin.ap_id,
"content" => "@batman Wait up, I forgot my spandex!",
"to" => [batman.ap_id],
"cc" => [],
"inReplyTo" => Object.normalize(post).data["id"]
}
}
assert {:ok, filtered} = QuietReply.filter(reply)
assert match?(^filtered, reply)
end
test "replying direct is unmodified" do
batman = insert(:user, nickname: "batman")
robin = insert(:user, nickname: "robin")
{:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!"})
reply = %{
"type" => "Create",
"actor" => robin.ap_id,
"to" => [batman.ap_id],
"cc" => [],
"object" => %{
"type" => "Note",
"actor" => robin.ap_id,
"content" => "@batman Wait up, I forgot my spandex!",
"to" => [batman.ap_id],
"cc" => [],
"inReplyTo" => Object.normalize(post).data["id"]
}
}
assert {:ok, filtered} = QuietReply.filter(reply)
assert match?(^filtered, reply)
end
test "replying followers-only is unmodified" do
batman = insert(:user, nickname: "batman")
robin = insert(:user, nickname: "robin")
{:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!"})
reply = %{
"type" => "Create",
"actor" => robin.ap_id,
"to" => [batman.ap_id, robin.follower_address],
"cc" => [],
"object" => %{
"type" => "Note",
"actor" => robin.ap_id,
"content" => "@batman Wait up, I forgot my spandex!",
"to" => [batman.ap_id, robin.follower_address],
"cc" => [],
"inReplyTo" => Object.normalize(post).data["id"]
}
}
assert {:ok, filtered} = QuietReply.filter(reply)
assert match?(^filtered, reply)
end
test "non-reply posts are unmodified" do
batman = insert(:user, nickname: "batman")
{:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!"})
assert {:ok, filtered} = QuietReply.filter(post)
assert match?(^filtered, post)
end
end

View file

@ -6,13 +6,11 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
use Oban.Testing, repo: Pleroma.Repo
use Pleroma.Web.ConnCase
import ExUnit.CaptureLog
import Pleroma.Factory
import Tesla.Mock
import Mock
alias Pleroma.Activity
alias Pleroma.Instances
alias Pleroma.Object
alias Pleroma.Tests.ObanHelpers
alias Pleroma.Web.ActivityPub.Publisher
@ -167,115 +165,6 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
})
|> Publisher.publish_one()
end
test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is set",
Instances,
[:passthrough],
[] do
_actor = insert(:user)
inbox = "http://200.site/users/nick1/inbox"
activity = insert(:note_activity)
assert {:ok, _} =
Publisher.prepare_one(%{
inbox: inbox,
activity_id: activity.id,
unreachable_since: NaiveDateTime.utc_now() |> NaiveDateTime.to_string()
})
|> Publisher.publish_one()
assert called(Instances.set_reachable(inbox))
end
test_with_mock "does NOT call `Instances.set_reachable` on successful federation if `unreachable_since` is nil",
Instances,
[:passthrough],
[] do
_actor = insert(:user)
inbox = "http://200.site/users/nick1/inbox"
activity = insert(:note_activity)
assert {:ok, _} =
Publisher.prepare_one(%{
inbox: inbox,
activity_id: activity.id,
unreachable_since: nil
})
|> Publisher.publish_one()
refute called(Instances.set_reachable(inbox))
end
test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code",
Instances,
[:passthrough],
[] do
_actor = insert(:user)
inbox = "http://404.site/users/nick1/inbox"
activity = insert(:note_activity)
assert {:cancel, _} =
Publisher.prepare_one(%{inbox: inbox, activity_id: activity.id})
|> Publisher.publish_one()
assert called(Instances.set_unreachable(inbox))
end
test_with_mock "it calls `Instances.set_unreachable` on target inbox on request error of any kind",
Instances,
[:passthrough],
[] do
_actor = insert(:user)
inbox = "http://connrefused.site/users/nick1/inbox"
activity = insert(:note_activity)
assert capture_log(fn ->
assert {:error, _} =
Publisher.prepare_one(%{
inbox: inbox,
activity_id: activity.id
})
|> Publisher.publish_one()
end) =~ "connrefused"
assert called(Instances.set_unreachable(inbox))
end
test_with_mock "does NOT call `Instances.set_unreachable` if target is reachable",
Instances,
[:passthrough],
[] do
_actor = insert(:user)
inbox = "http://200.site/users/nick1/inbox"
activity = insert(:note_activity)
assert {:ok, _} =
Publisher.prepare_one(%{inbox: inbox, activity_id: activity.id})
|> Publisher.publish_one()
refute called(Instances.set_unreachable(inbox))
end
test_with_mock "does NOT call `Instances.set_unreachable` if target instance has non-nil `unreachable_since`",
Instances,
[:passthrough],
[] do
_actor = insert(:user)
inbox = "http://connrefused.site/users/nick1/inbox"
activity = insert(:note_activity)
assert capture_log(fn ->
assert {:error, _} =
Publisher.prepare_one(%{
inbox: inbox,
activity_id: activity.id,
unreachable_since: NaiveDateTime.utc_now() |> NaiveDateTime.to_string()
})
|> Publisher.publish_one()
end) =~ "connrefused"
refute called(Instances.set_unreachable(inbox))
end
end
describe "publish/2" do

View file

@ -143,4 +143,40 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.LikeHandlingTest do
assert {:ok, activity} = Transmogrifier.handle_incoming(data)
assert activity.data["type"] == "Like"
end
test "it changes incoming dislikes into emoji reactions" do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{status: "hello"})
data =
File.read!("test/fixtures/friendica-dislike.json")
|> Jason.decode!()
|> Map.put("object", activity.data["object"])
_actor = insert(:user, ap_id: data["actor"], local: false)
{:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
refute Enum.empty?(activity.recipients)
assert data["actor"] == "https://my-place.social/profile/vaartis"
assert data["type"] == "EmojiReact"
assert data["content"] == "👎"
assert data["id"] == "https://my-place.social/objects/e599373b-1368-4b20-cd24-837166957182"
assert data["object"] == activity.data["object"]
data =
File.read!("test/fixtures/friendica-dislike-undo.json")
|> Jason.decode!()
|> put_in(["object", "object"], activity.data["object"])
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
assert data["actor"] == "https://my-place.social/profile/vaartis"
assert data["type"] == "Undo"
assert data["object"] ==
"https://my-place.social/objects/e599373b-1368-4b20-cd24-837166957182"
end
end

View file

@ -321,6 +321,36 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} revoked admin role from @#{user_one.nickname}, @#{user_two.nickname}"
end
test "/:right DELETE, admin cannot revoke their own admin status (single)", %{
admin: admin,
conn: conn
} do
conn =
conn
|> put_req_header("accept", "application/json")
|> delete("/api/pleroma/admin/users/#{admin.nickname}/permission_group/admin")
assert json_response(conn, 403) == %{"error" => "You can't revoke your own admin status."}
end
test "/:right DELETE, admin cannot revoke their own admin status (multiple)", %{
admin: admin,
conn: conn
} do
user = insert(:user, is_admin: true)
conn =
conn
|> put_req_header("accept", "application/json")
|> delete("/api/pleroma/admin/users/permission_group/admin", %{
nicknames: [admin.nickname, user.nickname]
})
assert json_response(conn, 403) == %{
"error" => "You can't revoke your own admin/moderator status."
}
end
end
describe "/api/pleroma/admin/users/:nickname/password_reset" do

View file

@ -8,8 +8,6 @@ defmodule Pleroma.Web.AdminAPI.InstanceControllerTest do
import Pleroma.Factory
alias Pleroma.Repo
alias Pleroma.Tests.ObanHelpers
alias Pleroma.Web.CommonAPI
setup_all do
@ -69,19 +67,19 @@ defmodule Pleroma.Web.AdminAPI.InstanceControllerTest do
test "DELETE /instances/:instance", %{conn: conn} do
clear_config([:instance, :admin_privileges], [:instances_delete])
user = insert(:user, nickname: "lain@lain.com")
post = insert(:note_activity, user: user)
insert(:user, nickname: "lain@lain.com")
response =
conn
|> delete("/api/pleroma/admin/instances/lain.com")
|> json_response(200)
[:ok] = ObanHelpers.perform_all()
assert response == "lain.com"
refute Repo.reload(user).is_active
refute Repo.reload(post)
assert_enqueued(
worker: Pleroma.Workers.DeleteWorker,
args: %{"op" => "delete_instance", "host" => "lain.com"}
)
clear_config([:instance, :admin_privileges], [])

View file

@ -126,22 +126,17 @@ defmodule Pleroma.Web.FederatorTest do
inbox: inbox2
})
dt = NaiveDateTime.utc_now()
Instances.set_unreachable(inbox1, dt)
Instances.set_consistently_unreachable(URI.parse(inbox2).host)
Instances.set_unreachable(URI.parse(inbox2).host)
{:ok, _activity} =
CommonAPI.post(user, %{status: "HI @nick1@domain.com, @nick2@domain2.com!"})
expected_dt = NaiveDateTime.to_iso8601(dt)
ObanHelpers.perform(all_enqueued(worker: PublisherWorker))
assert ObanHelpers.member?(
%{
"op" => "publish_one",
"params" => %{"inbox" => inbox1, "unreachable_since" => expected_dt}
"params" => %{"inbox" => inbox1}
},
all_enqueued(worker: PublisherWorker)
)

View file

@ -469,6 +469,17 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
assert [%{"id" => ^post_id}] = json_response_and_validate_schema(conn, 200)
end
test "gets only a user's reblogs", %{user: user, conn: conn} do
{:ok, %{id: post_id}} = CommonAPI.post(user, %{status: "HI!!!"})
{:ok, %{id: reblog_id}} = CommonAPI.repeat(post_id, user)
conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?only_reblogs=true")
assert [%{"id" => ^reblog_id}] = json_response_and_validate_schema(conn, 200)
conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?only_reblogs=1")
assert [%{"id" => ^reblog_id}] = json_response_and_validate_schema(conn, 200)
end
test "filters user's statuses by a hashtag", %{user: user, conn: conn} do
{:ok, %{id: post_id}} = CommonAPI.post(user, %{status: "#hashtag"})
{:ok, _post} = CommonAPI.post(user, %{status: "hashtag"})

View file

@ -7,7 +7,6 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
alias Pleroma.Object
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Endpoint
import Pleroma.Factory
import ExUnit.CaptureLog
import Tesla.Mock
@ -66,9 +65,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
[account | _] = results["accounts"]
assert account["id"] == to_string(user_three.id)
assert results["hashtags"] == [
%{"name" => "private", "url" => "#{Endpoint.url()}/tag/private"}
]
assert results["hashtags"] == []
[status] = results["statuses"]
assert status["id"] == to_string(activity.id)
@ -77,9 +74,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
get(conn, "/api/v2/search?q=天子")
|> json_response_and_validate_schema(200)
assert results["hashtags"] == [
%{"name" => "天子", "url" => "#{Endpoint.url()}/tag/天子"}
]
assert results["hashtags"] == []
[status] = results["statuses"]
assert status["id"] == to_string(activity.id)
@ -130,84 +125,97 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
assert [] = results["statuses"]
end
test "constructs hashtags from search query", %{conn: conn} do
test "returns empty results when no hashtags match", %{conn: conn} do
results =
conn
|> get("/api/v2/search?#{URI.encode_query(%{q: "some text with #explicit #hashtags"})}")
|> get("/api/v2/search?#{URI.encode_query(%{q: "nonexistent"})}")
|> json_response_and_validate_schema(200)
assert results["hashtags"] == [
%{"name" => "explicit", "url" => "#{Endpoint.url()}/tag/explicit"},
%{"name" => "hashtags", "url" => "#{Endpoint.url()}/tag/hashtags"}
]
assert results["hashtags"] == []
end
test "searches hashtags by multiple words in query", %{conn: conn} do
user = insert(:user)
{:ok, _activity1} = CommonAPI.post(user, %{status: "This is my new #computer"})
{:ok, _activity2} = CommonAPI.post(user, %{status: "Check out this #laptop"})
{:ok, _activity3} = CommonAPI.post(user, %{status: "My #desktop setup"})
{:ok, _activity4} = CommonAPI.post(user, %{status: "New #phone arrived"})
results =
conn
|> get("/api/v2/search?#{URI.encode_query(%{q: "john doe JOHN DOE"})}")
|> get("/api/v2/search?#{URI.encode_query(%{q: "new computer"})}")
|> json_response_and_validate_schema(200)
assert results["hashtags"] == [
%{"name" => "john", "url" => "#{Endpoint.url()}/tag/john"},
%{"name" => "doe", "url" => "#{Endpoint.url()}/tag/doe"},
%{"name" => "JohnDoe", "url" => "#{Endpoint.url()}/tag/JohnDoe"}
]
hashtag_names = Enum.map(results["hashtags"], & &1["name"])
assert "computer" in hashtag_names
refute "laptop" in hashtag_names
refute "desktop" in hashtag_names
refute "phone" in hashtag_names
results =
conn
|> get("/api/v2/search?#{URI.encode_query(%{q: "accident-prone"})}")
|> get("/api/v2/search?#{URI.encode_query(%{q: "computer laptop"})}")
|> json_response_and_validate_schema(200)
assert results["hashtags"] == [
%{"name" => "accident", "url" => "#{Endpoint.url()}/tag/accident"},
%{"name" => "prone", "url" => "#{Endpoint.url()}/tag/prone"},
%{"name" => "AccidentProne", "url" => "#{Endpoint.url()}/tag/AccidentProne"}
]
results =
conn
|> get("/api/v2/search?#{URI.encode_query(%{q: "https://shpposter.club/users/shpuld"})}")
|> json_response_and_validate_schema(200)
assert results["hashtags"] == [
%{"name" => "shpuld", "url" => "#{Endpoint.url()}/tag/shpuld"}
]
results =
conn
|> get(
"/api/v2/search?#{URI.encode_query(%{q: "https://www.washingtonpost.com/sports/2020/06/10/" <> "nascar-ban-display-confederate-flag-all-events-properties/"})}"
)
|> json_response_and_validate_schema(200)
assert results["hashtags"] == [
%{"name" => "nascar", "url" => "#{Endpoint.url()}/tag/nascar"},
%{"name" => "ban", "url" => "#{Endpoint.url()}/tag/ban"},
%{"name" => "display", "url" => "#{Endpoint.url()}/tag/display"},
%{"name" => "confederate", "url" => "#{Endpoint.url()}/tag/confederate"},
%{"name" => "flag", "url" => "#{Endpoint.url()}/tag/flag"},
%{"name" => "all", "url" => "#{Endpoint.url()}/tag/all"},
%{"name" => "events", "url" => "#{Endpoint.url()}/tag/events"},
%{"name" => "properties", "url" => "#{Endpoint.url()}/tag/properties"},
%{
"name" => "NascarBanDisplayConfederateFlagAllEventsProperties",
"url" =>
"#{Endpoint.url()}/tag/NascarBanDisplayConfederateFlagAllEventsProperties"
}
]
hashtag_names = Enum.map(results["hashtags"], & &1["name"])
assert "computer" in hashtag_names
assert "laptop" in hashtag_names
refute "desktop" in hashtag_names
refute "phone" in hashtag_names
end
test "supports pagination of hashtags search results", %{conn: conn} do
user = insert(:user)
{:ok, _activity1} = CommonAPI.post(user, %{status: "First #alpha hashtag"})
{:ok, _activity2} = CommonAPI.post(user, %{status: "Second #beta hashtag"})
{:ok, _activity3} = CommonAPI.post(user, %{status: "Third #gamma hashtag"})
{:ok, _activity4} = CommonAPI.post(user, %{status: "Fourth #delta hashtag"})
results =
conn
|> get(
"/api/v2/search?#{URI.encode_query(%{q: "#some #text #with #hashtags", limit: 2, offset: 1})}"
)
|> get("/api/v2/search?#{URI.encode_query(%{q: "a", limit: 2, offset: 1})}")
|> json_response_and_validate_schema(200)
assert results["hashtags"] == [
%{"name" => "text", "url" => "#{Endpoint.url()}/tag/text"},
%{"name" => "with", "url" => "#{Endpoint.url()}/tag/with"}
]
hashtag_names = Enum.map(results["hashtags"], & &1["name"])
# Should return 2 hashtags (alpha, beta, gamma, delta all contain 'a')
# With offset 1, we skip the first one, so we get 2 of the remaining 3
assert length(hashtag_names) == 2
assert Enum.all?(hashtag_names, &String.contains?(&1, "a"))
end
test "searches real hashtags from database", %{conn: conn} do
user = insert(:user)
{:ok, _activity1} = CommonAPI.post(user, %{status: "Check out this #car"})
{:ok, _activity2} = CommonAPI.post(user, %{status: "Fast #racecar on the track"})
{:ok, _activity3} = CommonAPI.post(user, %{status: "NASCAR #nascar racing"})
results =
conn
|> get("/api/v2/search?#{URI.encode_query(%{q: "car"})}")
|> json_response_and_validate_schema(200)
hashtag_names = Enum.map(results["hashtags"], & &1["name"])
# Should return car, racecar, and nascar since they all contain "car"
assert "car" in hashtag_names
assert "racecar" in hashtag_names
assert "nascar" in hashtag_names
# Search for "race" - should return racecar
results =
conn
|> get("/api/v2/search?#{URI.encode_query(%{q: "race"})}")
|> json_response_and_validate_schema(200)
hashtag_names = Enum.map(results["hashtags"], & &1["name"])
assert "racecar" in hashtag_names
refute "car" in hashtag_names
refute "nascar" in hashtag_names
end
test "excludes a blocked users from search results", %{conn: conn} do
@ -314,7 +322,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
[account | _] = results["accounts"]
assert account["id"] == to_string(user_three.id)
assert results["hashtags"] == ["2hu"]
assert results["hashtags"] == []
[status] = results["statuses"]
assert status["id"] == to_string(activity.id)

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