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

This commit is contained in:
Ilja 2021-01-09 17:28:58 +01:00
commit 95d5589df6
319 changed files with 2493 additions and 1285 deletions

1
.gitignore vendored
View File

@ -3,6 +3,7 @@
/db
/deps
/*.ez
/test/instance
/test/uploads
/.elixir_ls
/test/fixtures/DSCN0010_tmp.jpg

View File

@ -10,24 +10,28 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Polls now always return a `voters_count`, even if they are single-choice.
- Admin Emails: The ap id is used as the user link in emails now.
- Improved registration workflow for email confirmation and account approval modes.
- **Breaking:** Changed `mix pleroma.user toggle_confirmed` to `mix pleroma.user confirm`
- Search: When using Postgres 11+, Pleroma will use the `websearch_to_tsvector` function to parse search queries.
- Emoji: Support the full Unicode 13.1 set of Emoji for reactions, plus regional indicators.
- **Breaking** Besides only the instance, entries for simple_policy, transparency_exclusions and quarantined_instances now contain a reason as well.
- **Breaking** Besides only the instance, entries for simple_policy, transparency_exclusions and
- Admin API: Reports now ordered by newest
### Added
- Reports now generate notifications for admins and mods.
- Experimental websocket-based federation between Pleroma instances.
- Support for local-only statuses
- Support for local-only statuses.
- Support pagination of blocks and mutes.
- Account backup.
- Configuration: Add `:instance, autofollowing_nicknames` setting to provide a way to make accounts automatically follow new users that register on the local Pleroma instance.
- Ability to view remote timelines, with ex. `/api/v1/timelines/public?instance=lain.com` and streams `public:remote` and `public:remote:media`.
- The site title is now injected as a `title` tag like preloads or metadata.
- Password reset tokens now are not accepted after a certain age.
- Mix tasks to help with displaying and removing ConfigDB entries. See `mix pleroma.config`
- Mix tasks to help with displaying and removing ConfigDB entries. See `mix pleroma.config`.
- OAuth form improvements: users are remembered by their cookie, the CSS is overridable by the admin, and the style has been improved.
- OAuth improvements and fixes: more secure session-based authentication (by token that could be revoked anytime), ability to revoke belonging OAuth token from any client etc.
- Ability to set ActivityPub aliases for follower migration.
<details>
<summary>API Changes</summary>
@ -35,13 +39,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Pleroma API: Add `idempotency_key` to the chat message entity that can be used for optimistic message sending.
- Pleroma API: (`GET /api/v1/pleroma/federation_status`) Add a way to get a list of unreachable instances.
- Mastodon API: User and conversation mutes can now auto-expire if `expires_in` parameter was given while adding the mute.
- Admin API: An endpoint to manage frontends
- Streaming API: Add follow relationships updates
- Admin API: An endpoint to manage frontends.
- Streaming API: Add follow relationships updates.
</details>
### Fixed
- Users with `is_discoverable` field set to false (default value) will appear in in-service search results but be hidden from external services (search bots etc.).
- Streaming API: Posts and notifications are not dropped, when CLI task is executing.
- Creating incorrect IPv4 address-style HTTP links when encountering certain numbers.
<details>
<summary>API Changes</summary>
@ -51,24 +57,35 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Unreleased (Patch)
### Fixed
- Fix ability to update Pleroma Chat push notifications with PUT /api/v1/push/subscription and alert type pleroma:chat_mention
- Emoji Reaction activity filtering from blocked and muted accounts.
- StealEmojiPolicy creates dir for emojis, if it doesn't exist.
## [2.2.1] - 2020-12-22
### Changed
- Updated Pleroma FE
### Fixed
- Config generation: rename `Pleroma.Upload.Filter.ExifTool` to `Pleroma.Upload.Filter.Exiftool`.
- Search: RUM index search speed has been fixed.
- S3 Uploads with Elixir 1.11.
- Emoji Reaction activity filtering from blocked and muted accounts.
- Mix task pleroma.user delete_activities for source installations.
- Fix ability to update Pleroma Chat push notifications with PUT /api/v1/push/subscription and alert type pleroma:chat_mention
- Forwarded reports duplication from Pleroma instances.
- Search: RUM index search speed has been fixed.
- Rich Media Previews sometimes showed the wrong preview due to a bug following redirects.
- Fixes for the autolinker.
- Forwarded reports duplication from Pleroma instances.
<details>
<summary>API</summary>
- Statuses were not displayed for Mastodon forwarded reports.
- <details>
<summary>API</summary>
- Statuses were not displayed for Mastodon forwarded reports.
</details>
</details>
### Upgrade notes
1. Restart Pleroma
## [2.2.0] - 2020-11-12
@ -106,7 +123,7 @@ switched to a new configuration mechanism, however it was not officially removed
- Media preview proxy (requires `ffmpeg` and `ImageMagick` to be installed and media proxy to be enabled; see `:media_preview_proxy` config for more details).
- Mix tasks for controlling user account confirmation status in bulk (`mix pleroma.user confirm_all` and `mix pleroma.user unconfirm_all`)
- Mix task for sending confirmation emails to all unconfirmed users (`mix pleroma.email send_confirmation_mails`)
- Mix task for sending confirmation emails to all unconfirmed users (`mix pleroma.email resend_confirmation_emails`)
- Mix task option for force-unfollowing relays
- App metrics: ability to restrict access to specified IP whitelist.

View File

@ -47,7 +47,6 @@ use Mix.Config
config :pleroma, ecto_repos: [Pleroma.Repo]
config :pleroma, Pleroma.Repo,
types: Pleroma.PostgresTypes,
telemetry_event: [Pleroma.Repo.Instrumenter],
migration_lock: nil
@ -64,14 +63,6 @@ config :pleroma, Pleroma.Upload,
filters: [Pleroma.Upload.Filter.Dedupe],
link_name: false,
proxy_remote: false,
proxy_opts: [
redirect_on_failure: false,
max_body_length: 25 * 1_048_576,
http: [
follow_redirect: true,
pool: :upload
]
],
filename_display_max_length: 30,
default_description: nil
@ -306,7 +297,7 @@ config :pleroma, :frontend_configurations,
hideSitename: false,
hideUserStats: false,
loginMethod: "password",
logo: "/static/logo.png",
logo: "/static/logo.svg",
logoMargin: ".1em",
logoMask: true,
minimalScopesMode: false,
@ -343,8 +334,8 @@ config :pleroma, :assets,
config :pleroma, :manifest,
icons: [
%{
src: "/static/logo.png",
type: "image/png"
src: "/static/logo.svg",
type: "image/svg+xml"
}
],
theme_color: "#282c37",
@ -648,7 +639,7 @@ config :pleroma, :email_notifications,
}
config :pleroma, :oauth2,
token_expires_in: 3600 * 24 * 30,
token_expires_in: 3600 * 24 * 365 * 100,
issue_new_refresh_token: true,
clean_expired_tokens: false

View File

@ -101,74 +101,10 @@ config :pleroma, :config_description, [
%{
key: :proxy_remote,
type: :boolean,
description:
"If enabled, requests to media stored using a remote uploader will be proxied instead of being redirected"
},
%{
key: :proxy_opts,
label: "Proxy Options",
type: :keyword,
description: "Options for Pleroma.ReverseProxy",
suggestions: [
redirect_on_failure: false,
max_body_length: 25 * 1_048_576,
http: [
follow_redirect: true,
pool: :media
]
],
children: [
%{
key: :redirect_on_failure,
type: :boolean,
description:
"Redirects the client to the real remote URL if there's any HTTP errors. " <>
"Any error during body processing will not be redirected as the response is chunked."
},
%{
key: :max_body_length,
type: :integer,
description:
"Limits the content length to be approximately the " <>
"specified length. It is validated with the `content-length` header and also verified when proxying."
},
%{
key: :http,
label: "HTTP",
type: :keyword,
description: "HTTP options",
children: [
%{
key: :adapter,
type: :keyword,
description: "Adapter specific options",
children: [
%{
key: :ssl_options,
type: :keyword,
label: "SSL Options",
description: "SSL options for HTTP adapter",
children: [
%{
key: :versions,
type: {:list, :atom},
description: "List of TLS versions to use",
suggestions: [:tlsv1, ":tlsv1.1", ":tlsv1.2"]
}
]
}
]
},
%{
key: :proxy_url,
label: "Proxy URL",
type: [:string, :tuple],
description: "Proxy URL",
suggestions: ["127.0.0.1:8123", {:socks5, :localhost, 9050}]
}
]
}
]
description: """
Proxy requests to the remote uploader.\n
Useful if media upload endpoint is not internet accessible.
"""
},
%{
key: :filename_display_max_length,
@ -1256,7 +1192,7 @@ config :pleroma, :config_description, [
hideSitename: false,
hideUserStats: false,
loginMethod: "password",
logo: "/static/logo.png",
logo: "/static/logo.svg",
logoMargin: ".1em",
logoMask: true,
minimalScopesMode: false,
@ -1342,7 +1278,7 @@ config :pleroma, :config_description, [
key: :logo,
type: {:string, :image},
description: "URL of the logo, defaults to Pleroma's logo",
suggestions: ["/static/logo.png"]
suggestions: ["/static/logo.svg"]
},
%{
key: :logoMargin,
@ -1552,7 +1488,7 @@ config :pleroma, :config_description, [
%{
key: :enabled,
type: :boolean,
description: "Enables proxying of remote media to the instance's proxy"
description: "Enables proxying of remote media via the instance's proxy"
},
%{
key: :base_url,
@ -1589,80 +1525,41 @@ config :pleroma, :config_description, [
},
%{
key: :proxy_opts,
label: "Proxy Options",
label: "Advanced MediaProxy Options",
type: :keyword,
description: "Options for Pleroma.ReverseProxy",
description: "Internal Pleroma.ReverseProxy settings",
suggestions: [
redirect_on_failure: false,
max_body_length: 25 * 1_048_576,
max_read_duration: 30_000,
http: [
follow_redirect: true,
pool: :media
]
max_read_duration: 30_000
],
children: [
%{
key: :redirect_on_failure,
type: :boolean,
description:
"Redirects the client to the real remote URL if there's any HTTP errors. " <>
"Any error during body processing will not be redirected as the response is chunked."
description: """
Redirects the client to the origin server upon encountering HTTP errors.\n
Note that files larger than Max Body Length will trigger an error. (e.g., Peertube videos)\n\n
**WARNING:** This setting will allow larger files to be accessed, but exposes the\n
IP addresses of your users to the other servers, bypassing the MediaProxy.
"""
},
%{
key: :max_body_length,
type: :integer,
description:
"Limits the content length to be approximately the " <>
"specified length. It is validated with the `content-length` header and also verified when proxying."
description: "Maximum file size allowed through the Pleroma MediaProxy cache."
},
%{
key: :max_read_duration,
type: :integer,
description: "Timeout (in milliseconds) of GET request to remote URI."
},
%{
key: :http,
label: "HTTP",
type: :keyword,
description: "HTTP options",
children: [
%{
key: :adapter,
type: :keyword,
description: "Adapter specific options",
children: [
%{
key: :ssl_options,
type: :keyword,
label: "SSL Options",
description: "SSL options for HTTP adapter",
children: [
%{
key: :versions,
type: {:list, :atom},
description: "List of TLS version to use",
suggestions: [:tlsv1, ":tlsv1.1", ":tlsv1.2"]
}
]
}
]
},
%{
key: :proxy_url,
label: "Proxy URL",
type: [:string, :tuple],
description: "Proxy URL",
suggestions: ["127.0.0.1:8123", {:socks5, :localhost, 9050}]
}
]
description: "Timeout (in milliseconds) of GET request to the remote URI."
}
]
},
%{
key: :whitelist,
type: {:list, :string},
description: "List of hosts with scheme to bypass the mediaproxy",
description: "List of hosts with scheme to bypass the MediaProxy",
suggestions: ["http://example.com"]
}
]
@ -1955,14 +1852,8 @@ config :pleroma, :config_description, [
group: :pleroma,
key: Oban,
type: :group,
description: """
[Oban](https://github.com/sorentwo/oban) asynchronous job processor configuration.
Note: if you are running PostgreSQL in [`silent_mode`](https://postgresqlco.nf/en/doc/param/silent_mode?version=9.1),
it's advised to set [`log_destination`](https://postgresqlco.nf/en/doc/param/log_destination?version=9.1) to `syslog`,
otherwise `postmaster.log` file may grow because of "you don't own a lock of type ShareLock" warnings
(see https://github.com/sorentwo/oban/issues/52).
""",
description:
"[Oban](https://github.com/sorentwo/oban) asynchronous job processor configuration.",
children: [
%{
key: :log,

View File

@ -1,2 +1,4 @@
firefox, /emoji/Firefox.gif, Gif,Fun
blank, /emoji/blank.png, Fun
dinosaur, /emoji/dino walking.gif, Gif
external_emoji, https://example.com/emoji.png

View File

@ -47,7 +47,10 @@ config :pleroma, Pleroma.Repo,
password: "postgres",
database: "pleroma_test",
hostname: System.get_env("DB_HOST") || "localhost",
pool: Ecto.Adapters.SQL.Sandbox
pool: Ecto.Adapters.SQL.Sandbox,
pool_size: 50
config :pleroma, :dangerzone, override_repo_pool_size: true
# Reduce hash rounds for testing
config :pbkdf2_elixir, rounds: 1
@ -121,6 +124,20 @@ config :tzdata, :autoupdate, :disabled
config :pleroma, :mrf, policies: []
config :pleroma, :pipeline,
object_validator: Pleroma.Web.ActivityPub.ObjectValidatorMock,
mrf: Pleroma.Web.ActivityPub.MRFMock,
activity_pub: Pleroma.Web.ActivityPub.ActivityPubMock,
side_effects: Pleroma.Web.ActivityPub.SideEffectsMock,
federator: Pleroma.Web.FederatorMock,
config: Pleroma.ConfigMock
config :pleroma, :cachex, provider: Pleroma.CachexMock
config :pleroma, :side_effects,
ap_streamer: Pleroma.Web.ActivityPub.ActivityPubMock,
logger: Pleroma.LoggerMock
if File.exists?("./config/test.secret.exs") do
import_config "test.secret.exs"
else

View File

@ -1123,6 +1123,7 @@ Loads json generated from `config/descriptions.exs`.
```json
[
{
"id": 1234,
"data": {
"actor": {
"id": 1,

View File

@ -206,6 +206,7 @@ Additional parameters can be added to the JSON body/Form data:
- `pleroma_settings_store` - Opaque user settings to be saved on the backend.
- `skip_thread_containment` - if true, skip filtering out broken threads
- `allow_following_move` - if true, allows automatically follow moved following accounts
- `also_known_as` - array of ActivityPub IDs, needed for following move
- `pleroma_background_image` - sets the background image of the user. Can be set to "" (an empty string) to reset.
- `discoverable` - if true, external services (search bots) etc. are allowed to index / list the account (regardless of this setting, user will still appear in regular search results).
- `actor_type` - the type of this account.

View File

@ -16,8 +16,7 @@
mix pleroma.email test [--to <destination email address>]
```
Example:
Example:
=== "OTP"
@ -36,11 +35,11 @@ Example:
=== "OTP"
```sh
./bin/pleroma_ctl email send_confirmation_mails
./bin/pleroma_ctl email resend_confirmation_emails
```
=== "From Source"
```sh
mix pleroma.email send_confirmation_mails
mix pleroma.email resend_confirmation_emails
```

View File

@ -264,13 +264,13 @@
=== "OTP"
```sh
./bin/pleroma_ctl user toggle_confirmed <nickname>
./bin/pleroma_ctl user confirm <nickname>
```
=== "From Source"
```sh
mix pleroma.user toggle_confirmed <nickname>
mix pleroma.user confirm <nickname>
```
## Set confirmation status for all regular active users

View File

@ -9,29 +9,32 @@ static_dir="instance/static"
# project_branch="pleroma"
# static_dir="priv/static"
if [[ ! -d "${static_dir}" ]]
if [ ! -d "${static_dir}" ]
then
echo "Error: ${static_dir} directory is missing, are you sure you are running this script at the root of pleromas repository?"
exit 1
fi
last_modified="$(curl -s -I 'https://git.pleroma.social/api/v4/projects/'${project_id}'/jobs/artifacts/'${project_branch}'/download?job=build' | grep '^Last-Modified:' | cut -d: -f2-)"
last_modified="$(curl --fail -s -I 'https://git.pleroma.social/api/v4/projects/'${project_id}'/jobs/artifacts/'${project_branch}'/download?job=build' | grep '^Last-Modified:' | cut -d: -f2-)"
echo "branch:${project_branch}"
echo "Last-Modified:${last_modified}"
artifact="mastofe.zip"
if [[ -e mastofe.timestamp ]] && [[ "${last_modified}" != "" ]]
if [ "${last_modified}x" = "x" ]
then
if [[ "$(cat mastofe.timestamp)" == "${last_modified}" ]]
then
echo "MastoFE is up-to-date, exiting…"
exit 0
fi
echo "ERROR: Couldn't get the modification date of the latest build archive, maybe it expired, exiting..."
exit 1
fi
curl -c - "https://git.pleroma.social/api/v4/projects/${project_id}/jobs/artifacts/${project_branch}/download?job=build" -o "${artifact}" || exit
if [ -e mastofe.timestamp ] && [ "$(cat mastofe.timestamp)" = "${last_modified}" ]
then
echo "MastoFE is up-to-date, exiting..."
exit 0
fi
curl --fail -c - "https://git.pleroma.social/api/v4/projects/${project_id}/jobs/artifacts/${project_branch}/download?job=build" -o "${artifact}" || exit
# TODO: Update the emoji as well
rm -fr "${static_dir}/sw.js" "${static_dir}/packs" || exit

View File

@ -12,7 +12,8 @@ defmodule Mix.Pleroma do
:cachex,
:flake_id,
:swoosh,
:timex
:timex,
:fast_html
]
@cachex_children ["object", "user", "scrubber", "web_resp"]
@doc "Common functions to be reused in mix tasks"
@ -37,12 +38,23 @@ defmodule Mix.Pleroma do
Enum.each(apps, &Application.ensure_all_started/1)
oban_config = [
crontab: [],
repo: Pleroma.Repo,
log: false,
queues: [],
plugins: []
]
children =
[
Pleroma.Repo,
Pleroma.Emoji,
{Pleroma.Config.TransferTask, false},
Pleroma.Web.Endpoint,
{Oban, Pleroma.Config.get(Oban)}
{Oban, oban_config},
{Majic.Pool,
[name: Pleroma.MajicPool, pool_size: Pleroma.Config.get([:majic_pool, :size], 2)]}
] ++
http_children(adapter)

View File

@ -48,9 +48,15 @@ defmodule Mix.Tasks.Pleroma.Database do
def run(["update_users_following_followers_counts"]) do
start_pleroma()
User
|> Repo.all()
|> Enum.each(&User.update_follower_count/1)
Repo.transaction(
fn ->
from(u in User, select: u)
|> Repo.stream()
|> Stream.each(&User.update_follower_count/1)
|> Stream.run()
end,
timeout: :infinity
)
end
def run(["prune_objects" | args]) do

View File

@ -161,12 +161,21 @@ defmodule Mix.Tasks.Pleroma.Instance do
)
|> Path.expand()
{strip_uploads_message, strip_uploads_default} =
if Pleroma.Utils.command_available?("exiftool") do
{"Do you want to strip location (GPS) data from uploaded images? This requires exiftool, it was detected as installed. (y/n)",
"y"}
else
{"Do you want to strip location (GPS) data from uploaded images? This requires exiftool, it was detected as not installed, please install it if you answer yes. (y/n)",
"n"}
end
strip_uploads =
get_option(
options,
:strip_uploads,
"Do you want to strip location (GPS) data from uploaded images? (y/n)",
"y"
strip_uploads_message,
strip_uploads_default
) === "y"
anonymize_uploads =
@ -253,7 +262,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
else
shell_error(
"The task would have overwritten the following files:\n" <>
(Enum.map(paths, &"- #{&1}\n") |> Enum.join("")) <>
(Enum.map(will_overwrite, &"- #{&1}\n") |> Enum.join("")) <>
"Rerun with `--force` to overwrite them."
)
end

View File

@ -345,11 +345,11 @@ defmodule Mix.Tasks.Pleroma.User do
end
end
def run(["toggle_confirmed", nickname]) do
def run(["confirm", nickname]) do
start_pleroma()
with %User{} = user <- User.get_cached_by_nickname(nickname) do
{:ok, user} = User.toggle_confirmation(user)
{:ok, user} = User.confirm(user)
message = if user.confirmation_pending, do: "needs", else: "doesn't need"

View File

@ -24,6 +24,8 @@ defmodule Pleroma.Activity do
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
schema "activities" do
field(:data, :map)
field(:local, :boolean, default: true)
@ -272,7 +274,7 @@ defmodule Pleroma.Activity do
defp get_in_reply_to_activity_from_object(_), do: nil
def get_in_reply_to_activity(%Activity{} = activity) do
get_in_reply_to_activity_from_object(Object.normalize(activity))
get_in_reply_to_activity_from_object(Object.normalize(activity, fetch: false))
end
def normalize(obj) when is_map(obj), do: get_by_ap_id_with_object(obj["id"])
@ -298,7 +300,7 @@ defmodule Pleroma.Activity do
defp purge_web_resp_cache(%Activity{} = activity) do
%{path: path} = URI.parse(activity.data["id"])
Cachex.del(:web_resp_cache, path)
@cachex.del(:web_resp_cache, path)
activity
end

View File

@ -8,7 +8,7 @@ defmodule Pleroma.Activity.Ir.Topics do
def get_activity_topics(activity) do
activity
|> Object.normalize()
|> Object.normalize(fetch: false)
|> generate_topics(activity)
|> List.flatten()
end

19
lib/pleroma/caching.ex Normal file
View File

@ -0,0 +1,19 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Caching do
@callback get!(Cachex.cache(), any()) :: any()
@callback get(Cachex.cache(), any()) :: {atom(), any()}
@callback put(Cachex.cache(), any(), any(), Keyword.t()) :: {Cachex.status(), boolean()}
@callback put(Cachex.cache(), any(), any()) :: {Cachex.status(), boolean()}
@callback fetch!(Cachex.cache(), any(), function() | nil) :: any()
# @callback del(Cachex.cache(), any(), Keyword.t()) :: {Cachex.status(), boolean()}
@callback del(Cachex.cache(), any()) :: {Cachex.status(), boolean()}
@callback stream!(Cachex.cache(), any()) :: Enumerable.t()
@callback expire_at(Cachex.cache(), binary(), number()) :: {Cachex.status(), boolean()}
@callback exists?(Cachex.cache(), any()) :: {Cachex.status(), boolean()}
@callback execute!(Cachex.cache(), function()) :: any()
@callback get_and_update(Cachex.cache(), any(), function()) ::
{:commit | :ignore, any()}
end

View File

@ -7,6 +7,8 @@ defmodule Pleroma.Captcha do
alias Plug.Crypto.KeyGenerator
alias Plug.Crypto.MessageEncryptor
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
@doc """
Ask the configured captcha service for a new captcha
"""
@ -86,7 +88,7 @@ defmodule Pleroma.Captcha do
end
defp validate_usage(token) do
if is_nil(Cachex.get!(:used_captcha_cache, token)) do
if is_nil(@cachex.get!(:used_captcha_cache, token)) do
:ok
else
{:error, :already_used}
@ -95,7 +97,7 @@ defmodule Pleroma.Captcha do
defp mark_captcha_as_used(token) do
ttl = seconds_valid() |> :timer.seconds()
Cachex.put(:used_captcha_cache, token, true, ttl: ttl)
@cachex.put(:used_captcha_cache, token, true, ttl: ttl)
end
defp method, do: Pleroma.Config.get!([__MODULE__, :method])

View File

@ -3,14 +3,18 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Config do
@behaviour Pleroma.Config.Getting
defmodule Error do
defexception [:message]
end
@impl true
def get(key), do: get(key, nil)
@impl true
def get([key], default), do: get(key, default)
@impl true
def get([_ | _] = path, default) do
case fetch(path) do
{:ok, value} -> value
@ -18,6 +22,7 @@ defmodule Pleroma.Config do
end
end
@impl true
def get(key, default) do
Application.get_env(:pleroma, key, default)
end

View File

@ -0,0 +1,8 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Config.Getting do
@callback get(any()) :: any()
@callback get(any(), any()) :: any()
end

View File

@ -5,6 +5,7 @@
defmodule Pleroma.Conversation do
alias Pleroma.Conversation.Participation
alias Pleroma.Conversation.Participation.RecipientShip
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
use Ecto.Schema
@ -58,7 +59,7 @@ defmodule Pleroma.Conversation do
def create_or_bump_for(activity, opts \\ []) do
with true <- Pleroma.Web.ActivityPub.Visibility.is_direct?(activity),
"Create" <- activity.data["type"],
object <- Pleroma.Object.normalize(activity),
%Object{} = object <- Object.normalize(activity, fetch: false),
true <- object.data["type"] in ["Note", "Question"],
ap_id when is_binary(ap_id) and byte_size(ap_id) > 0 <- object.data["context"] do
{:ok, conversation} = create_for_ap_id(ap_id)

View File

@ -93,6 +93,19 @@ defmodule Pleroma.Emails.UserEmail do
|> html_body(html_body)
end
def approval_pending_email(user) do
html_body = """
<h3>Awaiting Approval</h3>
<p>Your account at #{instance_name()} is being reviewed by staff. You will receive another email once your account is approved.</p>
"""
new()
|> to(recipient(user))
|> from(sender())
|> subject("Your account is awaiting approval")
|> html_body(html_body)
end
@doc """
Email used in digest email notifications
Includes Mentions and New Followers data
@ -106,7 +119,7 @@ defmodule Pleroma.Emails.UserEmail do
notifications
|> Enum.filter(&(&1.activity.data["type"] == "Create"))
|> Enum.map(fn notification ->
object = Pleroma.Object.normalize(notification.activity)
object = Pleroma.Object.normalize(notification.activity, fetch: false)
if not is_nil(object) do
object = update_in(object.data["content"], &format_links/1)
@ -129,7 +142,7 @@ defmodule Pleroma.Emails.UserEmail do
if not is_nil(from) do
%{
data: notification,
object: Pleroma.Object.normalize(notification.activity),
object: Pleroma.Object.normalize(notification.activity, fetch: false),
from: User.get_by_ap_id(notification.activity.actor)
}
end
@ -151,7 +164,7 @@ defmodule Pleroma.Emails.UserEmail do
logo_path =
if is_nil(logo) do
Path.join(:code.priv_dir(:pleroma), "static/static/logo.png")
Path.join(:code.priv_dir(:pleroma), "static/static/logo.svg")
else
Path.join(Config.get([:instance, :static_dir]), logo)
end
@ -162,7 +175,7 @@ defmodule Pleroma.Emails.UserEmail do
|> subject("Your digest from #{instance_name()}")
|> put_layout(false)
|> render_body("digest.html", html_data)
|> attachment(Swoosh.Attachment.new(logo_path, filename: "logo.png", type: :inline))
|> attachment(Swoosh.Attachment.new(logo_path, filename: "logo.svg", type: :inline))
end
end

View File

@ -5,6 +5,7 @@
defmodule Pleroma.Emoji.Formatter do
alias Pleroma.Emoji
alias Pleroma.HTML
alias Pleroma.Web
alias Pleroma.Web.MediaProxy
def emojify(text) do
@ -43,7 +44,7 @@ defmodule Pleroma.Emoji.Formatter do
Emoji.get_all()
|> Enum.filter(fn {emoji, %Emoji{}} -> String.contains?(text, ":#{emoji}:") end)
|> Enum.reduce(%{}, fn {name, %Emoji{file: file}}, acc ->
Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}")
Map.put(acc, name, to_string(URI.merge(Web.base_url(), file)))
end)
end

View File

@ -20,16 +20,18 @@ defmodule Pleroma.Emoji.Pack do
name: String.t()
}
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
alias Pleroma.Emoji
alias Pleroma.Emoji.Pack
alias Pleroma.Utils
@spec create(String.t()) :: {:ok, t()} | {:error, File.posix()} | {:error, :empty_values}
def create(name) do
with :ok <- validate_not_empty([name]),
dir <- Path.join(emoji_path(), name),
:ok <- File.mkdir(dir) do
%__MODULE__{pack_file: Path.join(dir, "pack.json")}
|> save_pack()
save_pack(%__MODULE__{pack_file: Path.join(dir, "pack.json")})
end
end
@ -62,10 +64,9 @@ defmodule Pleroma.Emoji.Pack do
@spec delete(String.t()) ::
{:ok, [binary()]} | {:error, File.posix(), binary()} | {:error, :empty_values}
def delete(name) do
with :ok <- validate_not_empty([name]) do
emoji_path()
|> Path.join(name)
|> File.rm_rf()
with :ok <- validate_not_empty([name]),
pack_path <- Path.join(emoji_path(), name) do
File.rm_rf(pack_path)
end
end
@ -94,7 +95,7 @@ defmodule Pleroma.Emoji.Pack do
def add_file(%Pack{} = pack, _, _, %Plug.Upload{content_type: "application/zip"} = file) do
with {:ok, zip_files} <- :zip.table(to_charlist(file.path)),
[_ | _] = emojies <- unpack_zip_emojies(zip_files),
{:ok, tmp_dir} <- Pleroma.Utils.tmp_dir("emoji") do
{:ok, tmp_dir} <- Utils.tmp_dir("emoji") do
try do
{:ok, _emoji_files} =
:zip.unzip(
@ -282,18 +283,21 @@ defmodule Pleroma.Emoji.Pack do
end
end
@spec load_pack(String.t()) :: {:ok, t()} | {:error, :not_found}
@spec load_pack(String.t()) :: {:ok, t()} | {:error, :file.posix()}
def load_pack(name) do
pack_file = Path.join([emoji_path(), name, "pack.json"])
if File.exists?(pack_file) do
with {:ok, _} <- File.stat(pack_file),
{:ok, pack_data} <- File.read(pack_file) do
pack =
pack_file
|> File.read!()
|> from_json()
|> Map.put(:pack_file, pack_file)
|> Map.put(:path, Path.dirname(pack_file))
|> Map.put(:name, name)
from_json(
pack_data,
%{
pack_file: pack_file,
path: Path.dirname(pack_file),
name: name
}
)
files_count =
pack.files
@ -301,8 +305,6 @@ defmodule Pleroma.Emoji.Pack do
|> length()
{:ok, Map.put(pack, :files_count, files_count)}
else
{:error, :not_found}
end
end
@ -415,7 +417,7 @@ defmodule Pleroma.Emoji.Pack do
ttl_per_file = Pleroma.Config.get!([:emoji, :shared_pack_cache_seconds_per_file])
overall_ttl = :timer.seconds(ttl_per_file * Enum.count(files))
Cachex.put!(
@cachex.put(
:emoji_packs_cache,
pack.name,
# if pack.json MD5 changes, the cache is not valid anymore
@ -434,10 +436,17 @@ defmodule Pleroma.Emoji.Pack do
end
end
defp from_json(json) do
defp from_json(json, attrs) do
map = Jason.decode!(json)
struct(__MODULE__, %{files: map["files"], pack: map["pack"]})
pack_attrs =
attrs
|> Map.merge(%{
files: map["files"],
pack: map["pack"]
})
struct(__MODULE__, pack_attrs)
end
defp validate_shareable_packs_available(uri) do
@ -491,10 +500,10 @@ defmodule Pleroma.Emoji.Pack do
end
defp create_subdirs(file_path) do
if String.contains?(file_path, "/") do
file_path
|> Path.dirname()
|> File.mkdir_p!()
with true <- String.contains?(file_path, "/"),
path <- Path.dirname(file_path),
false <- File.exists?(path) do
File.mkdir_p!(path)
end
end
@ -518,10 +527,15 @@ defmodule Pleroma.Emoji.Pack do
defp get_filename(pack, shortcode) do
with %{^shortcode => filename} when is_binary(filename) <- pack.files,
true <- pack.path |> Path.join(filename) |> File.exists?() do
file_path <- Path.join(pack.path, filename),
{:ok, _} <- File.stat(file_path) do
{:ok, filename}
else
_ -> {:error, :doesnt_exist}
{:error, _} = error ->
error
_ ->
{:error, :doesnt_exist}
end
end
@ -606,7 +620,7 @@ defmodule Pleroma.Emoji.Pack do
defp fetch_archive(pack) do
hash = :crypto.hash(:md5, File.read!(pack.pack_file))
case Cachex.get!(:emoji_packs_cache, pack.name) do
case @cachex.get!(:emoji_packs_cache, pack.name) do
%{hash: ^hash, pack_data: archive} -> archive
_ -> create_archive_and_cache(pack, hash)
end

View File

@ -76,7 +76,7 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do
|> Enum.map(fn activity ->
user = User.get_cached_by_ap_id(activity.data["actor"])
object = Object.normalize(activity)
object = Object.normalize(activity, fetch: false)
like_count = object.data["like_count"] || 0
announcement_count = object.data["announcement_count"] || 0

View File

@ -6,6 +6,8 @@ defmodule Pleroma.HTML do
# Scrubbers are compiled on boot so they can be configured in OTP releases
# @on_load :compile_scrubbers
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
def compile_scrubbers do
dir = Path.join(:code.priv_dir(:pleroma), "scrubbers")
@ -56,8 +58,8 @@ defmodule Pleroma.HTML do
) do
key = "#{key}#{generate_scrubber_signature(scrubbers)}|#{activity.id}"
Cachex.fetch!(:scrubber_cache, key, fn _key ->
object = Pleroma.Object.normalize(activity)
@cachex.fetch!(:scrubber_cache, key, fn _key ->
object = Pleroma.Object.normalize(activity, fetch: false)
ensure_scrubbed_html(content, scrubbers, object.data["fake"] || false, callback)
end)
end
@ -105,7 +107,7 @@ defmodule Pleroma.HTML do
unless object.data["fake"] do
key = "URL|#{object.id}"
Cachex.fetch!(:scrubber_cache, key, fn _key ->
@cachex.fetch!(:scrubber_cache, key, fn _key ->
{:commit, {:ok, extract_first_external_url(content)}}
end)
else

View File

@ -2,8 +2,6 @@
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
Postgrex.Types.define(
Pleroma.PostgresTypes,
[] ++ Ecto.Adapters.Postgres.extensions(),
json: Jason
)
defmodule Pleroma.Logging do
@callback error(String.t()) :: any()
end

View File

@ -358,7 +358,7 @@ defmodule Pleroma.Notification do
def create_notifications(activity, options \\ [])
def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity, options) do
object = Object.normalize(activity, false)
object = Object.normalize(activity, fetch: false)
if object && object.data["type"] == "Answer" do
{:ok, []}
@ -625,7 +625,7 @@ defmodule Pleroma.Notification do
def skip?(:filtered, %{data: %{"type" => type}}, _) when type in ["Follow", "Move"], do: false
def skip?(:filtered, activity, user) do
object = Object.normalize(activity)
object = Object.normalize(activity, fetch: false)
cond do
is_nil(object) ->

View File

@ -23,6 +23,8 @@ defmodule Pleroma.Object do
@derive {Jason.Encoder, only: [:data]}
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
schema "objects" do
field(:data, :map)
@ -106,39 +108,42 @@ defmodule Pleroma.Object do
Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}")
end
def normalize(_, fetch_remote \\ true, options \\ [])
def normalize(_, options \\ [fetch: false])
# If we pass an Activity to Object.normalize(), we can try to use the preloaded object.
# Use this whenever possible, especially when walking graphs in an O(N) loop!
def normalize(%Object{} = object, _, _), do: object
def normalize(%Activity{object: %Object{} = object}, _, _), do: object
def normalize(%Object{} = object, _), do: object
def normalize(%Activity{object: %Object{} = object}, _), do: object
# A hack for fake activities
def normalize(%Activity{data: %{"object" => %{"fake" => true} = data}}, _, _) do
def normalize(%Activity{data: %{"object" => %{"fake" => true} = data}}, _) do
%Object{id: "pleroma:fake_object_id", data: data}
end
# No preloaded object
def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}, fetch_remote, _) do
def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}, options) do
warn_on_no_object_preloaded(ap_id)
normalize(ap_id, fetch_remote)
normalize(ap_id, options)
end
# No preloaded object
def normalize(%Activity{data: %{"object" => ap_id}}, fetch_remote, _) do
def normalize(%Activity{data: %{"object" => ap_id}}, options) do
warn_on_no_object_preloaded(ap_id)
normalize(ap_id, fetch_remote)
normalize(ap_id, options)
end
# Old way, try fetching the object through cache.
def normalize(%{"id" => ap_id}, fetch_remote, _), do: normalize(ap_id, fetch_remote)
def normalize(ap_id, false, _) when is_binary(ap_id), do: get_cached_by_ap_id(ap_id)
def normalize(%{"id" => ap_id}, options), do: normalize(ap_id, options)
def normalize(ap_id, true, options) when is_binary(ap_id) do
Fetcher.fetch_object_from_id!(ap_id, options)
def normalize(ap_id, options) when is_binary(ap_id) do
if Keyword.get(options, :fetch) do
Fetcher.fetch_object_from_id!(ap_id, options)
else
get_cached_by_ap_id(ap_id)
end
end
def normalize(_, _, _), do: nil
def normalize(_, _), do: nil
# Owned objects can only be accessed by their owner
def authorize_access(%Object{data: %{"actor" => actor}}, %User{ap_id: ap_id}) do
@ -156,9 +161,9 @@ defmodule Pleroma.Object do
def get_cached_by_ap_id(ap_id) do
key = "object:#{ap_id}"
with {:ok, nil} <- Cachex.get(:object_cache, key),
with {:ok, nil} <- @cachex.get(:object_cache, key),
object when not is_nil(object) <- get_by_ap_id(ap_id),
{:ok, true} <- Cachex.put(:object_cache, key, object) do
{:ok, true} <- @cachex.put(:object_cache, key, object) do
object
else
{:ok, object} -> object
@ -216,13 +221,13 @@ defmodule Pleroma.Object do
end
def invalid_object_cache(%Object{data: %{"id" => id}}) do
with {:ok, true} <- Cachex.del(:object_cache, "object:#{id}") do
Cachex.del(:web_resp_cache, URI.parse(id).path)
with {:ok, true} <- @cachex.del(:object_cache, "object:#{id}") do
@cachex.del(:web_resp_cache, URI.parse(id).path)
end
end
def set_cache(%Object{data: %{"id" => ap_id}} = object) do
Cachex.put(:object_cache, "object:#{ap_id}", object)
@cachex.put(:object_cache, "object:#{ap_id}", object)
{:ok, object}
end
@ -283,7 +288,7 @@ defmodule Pleroma.Object do
end
def increase_vote_count(ap_id, name, actor) do
with %Object{} = object <- Object.normalize(ap_id),
with %Object{} = object <- Object.normalize(ap_id, fetch: false),
"Question" <- object.data["type"] do
key = if poll_is_multiple?(object), do: "anyOf", else: "oneOf"
@ -324,7 +329,7 @@ defmodule Pleroma.Object do
end
def replies(object, opts \\ []) do
object = Object.normalize(object)
object = Object.normalize(object, fetch: false)
query =
Object

View File

@ -83,13 +83,13 @@ defmodule Pleroma.Object.Fetcher do
with {_, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)},
{_, true} <- {:allowed_depth, Federator.allowed_thread_distance?(options[:depth])},
{_, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)},
{_, nil} <- {:normalize, Object.normalize(data, false)},
{_, nil} <- {:normalize, Object.normalize(data, fetch: false)},
params <- prepare_activity_params(data),
{_, :ok} <- {:containment, Containment.contain_origin(id, params)},
{_, {:ok, activity}} <-
{:transmogrifier, Transmogrifier.handle_incoming(params, options)},
{_, _data, %Object{} = object} <-
{:object, data, Object.normalize(activity, false)} do
{:object, data, Object.normalize(activity, fetch: false)} do
{:ok, object}
else
{:allowed_depth, false} ->

View File

@ -17,6 +17,8 @@ defmodule Pleroma.ReverseProxy do
@failed_request_ttl :timer.seconds(60)
@methods ~w(GET HEAD)
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
def max_read_duration_default, do: @max_read_duration
def default_cache_control_header, do: @default_cache_control_header
@ -107,7 +109,7 @@ defmodule Pleroma.ReverseProxy do
opts
end
with {:ok, nil} <- Cachex.get(:failed_proxy_url_cache, url),
with {:ok, nil} <- @cachex.get(:failed_proxy_url_cache, url),
{:ok, code, headers, client} <- request(method, url, req_headers, client_opts),
:ok <-
header_length_constraint(
@ -427,6 +429,6 @@ defmodule Pleroma.ReverseProxy do
nil
end
Cachex.put(:failed_proxy_url_cache, url, true, ttl: ttl)
@cachex.put(:failed_proxy_url_cache, url, true, ttl: ttl)
end
end

View File

@ -81,6 +81,8 @@ defmodule Pleroma.User do
]
]
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
schema "users" do
field(:bio, :string, default: "")
field(:raw_bio, :string)
@ -140,7 +142,7 @@ defmodule Pleroma.User do
field(:allow_following_move, :boolean, default: true)
field(:skip_thread_containment, :boolean, default: false)
field(:actor_type, :string, default: "Person")
field(:also_known_as, {:array, :string}, default: [])
field(:also_known_as, {:array, ObjectValidators.ObjectID}, default: [])
field(:inbox, :string)
field(:shared_inbox, :string)
field(:accepts_chat_messages, :boolean, default: nil)
@ -246,13 +248,13 @@ defmodule Pleroma.User do
end
def cached_blocked_users_ap_ids(user) do
Cachex.fetch!(:user_cache, "blocked_users_ap_ids:#{user.ap_id}", fn _ ->
@cachex.fetch!(:user_cache, "blocked_users_ap_ids:#{user.ap_id}", fn _ ->
blocked_users_ap_ids(user)
end)
end
def cached_muted_users_ap_ids(user) do
Cachex.fetch!(:user_cache, "muted_users_ap_ids:#{user.ap_id}", fn _ ->
@cachex.fetch!(:user_cache, "muted_users_ap_ids:#{user.ap_id}", fn _ ->
muted_users_ap_ids(user)
end)
end
@ -513,6 +515,7 @@ defmodule Pleroma.User do
:hide_follows_count,
:hide_favorites,
:allow_following_move,
:also_known_as,
:background,
:show_role,
:skip_thread_containment,
@ -521,7 +524,6 @@ defmodule Pleroma.User do
:pleroma_settings_store,
:is_discoverable,
:actor_type,
:also_known_as,
:accepts_chat_messages
]
)
@ -806,18 +808,50 @@ defmodule Pleroma.User do
end
end
def post_register_action(%User{} = user) do
def post_register_action(%User{confirmation_pending: true} = user) do
with {:ok, _} <- try_send_confirmation_email(user) do
{:ok, user}
end
end
def post_register_action(%User{approval_pending: true} = user) do
with {:ok, _} <- send_user_approval_email(user),
{:ok, _} <- send_admin_approval_emails(user) do
{:ok, user}
end
end
def post_register_action(%User{approval_pending: false, confirmation_pending: false} = user) do
with {:ok, user} <- autofollow_users(user),
{:ok, _} <- autofollowing_users(user),
{:ok, user} <- set_cache(user),
{:ok, _} <- send_welcome_email(user),
{:ok, _} <- send_welcome_message(user),
{:ok, _} <- send_welcome_chat_message(user),
{:ok, _} <- try_send_confirmation_email(user) do
{:ok, _} <- send_welcome_chat_message(user) do
{:ok, user}
end
end
defp send_user_approval_email(user) do
user
|> Pleroma.Emails.UserEmail.approval_pending_email()
|> Pleroma.Emails.Mailer.deliver_async()
{:ok, :enqueued}
end
defp send_admin_approval_emails(user) do
all_superusers()
|> Enum.filter(fn user -> not is_nil(user.email) end)
|> Enum.each(fn superuser ->
superuser
|> Pleroma.Emails.AdminEmail.new_unapproved_registration(user)
|> Pleroma.Emails.Mailer.deliver_async()
end)
{:ok, :enqueued}
end
def send_welcome_message(user) do
if User.WelcomeMessage.enabled?() do
User.WelcomeMessage.post_message(user)
@ -1016,9 +1050,9 @@ defmodule Pleroma.User do
def set_cache({:error, err}), do: {:error, err}
def set_cache(%User{} = user) do
Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
Cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user))
@cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
@cachex.put(:user_cache, "nickname:#{user.nickname}", user)
@cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user))
{:ok, user}
end
@ -1041,26 +1075,26 @@ defmodule Pleroma.User do
@spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()]
def get_cached_user_friends_ap_ids(user) do
Cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ ->
@cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ ->
get_user_friends_ap_ids(user)
end)
end
def invalidate_cache(user) do
Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
Cachex.del(:user_cache, "nickname:#{user.nickname}")
Cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
Cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
Cachex.del(:user_cache, "muted_users_ap_ids:#{user.ap_id}")
@cachex.del(:user_cache, "ap_id:#{user.ap_id}")
@cachex.del(:user_cache, "nickname:#{user.nickname}")
@cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
@cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
@cachex.del(:user_cache, "muted_users_ap_ids:#{user.ap_id}")
end
@spec get_cached_by_ap_id(String.t()) :: User.t() | nil
def get_cached_by_ap_id(ap_id) do
key = "ap_id:#{ap_id}"
with {:ok, nil} <- Cachex.get(:user_cache, key),
with {:ok, nil} <- @cachex.get(:user_cache, key),
user when not is_nil(user) <- get_by_ap_id(ap_id),
{:ok, true} <- Cachex.put(:user_cache, key, user) do
{:ok, true} <- @cachex.put(:user_cache, key, user) do
user
else
{:ok, user} -> user
@ -1072,11 +1106,11 @@ defmodule Pleroma.User do
key = "id:#{id}"
ap_id =
Cachex.fetch!(:user_cache, key, fn _ ->
@cachex.fetch!(:user_cache, key, fn _ ->
user = get_by_id(id)
if user do
Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
@cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
{:commit, user.ap_id}
else
{:ignore, ""}
@ -1089,7 +1123,7 @@ defmodule Pleroma.User do
def get_cached_by_nickname(nickname) do
key = "nickname:#{nickname}"
Cachex.fetch!(:user_cache, key, fn ->
@cachex.fetch!(:user_cache, key, fn _ ->
case get_or_fetch_by_nickname(nickname) do
{:ok, user} -> {:commit, user}
{:error, _error} -> {:ignore, nil}
@ -1358,7 +1392,7 @@ defmodule Pleroma.User do
)
end
Cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
@cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
{:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
end
@ -1368,7 +1402,7 @@ defmodule Pleroma.User do
with {:ok, user_mute} <- UserRelationship.delete_mute(muter, mutee),
{:ok, user_notification_mute} <-
UserRelationship.delete_notification_mute(muter, mutee) do
Cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
@cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
{:ok, [user_mute, user_notification_mute]}
end
end
@ -1590,11 +1624,34 @@ defmodule Pleroma.User do
end)
end
def approve(%User{} = user) do
change(user, approval_pending: false)
|> update_and_set_cache()
def approve(%User{approval_pending: true} = user) do
with chg <- change(user, approval_pending: false),
{:ok, user} <- update_and_set_cache(chg) do
post_register_action(user)
{:ok, user}
end
end
def approve(%User{} = user), do: {:ok, user}
def confirm(users) when is_list(users) do
Repo.transaction(fn ->
Enum.map(users, fn user ->
with {:ok, user} <- confirm(user), do: user
end)
end)
end
def confirm(%User{confirmation_pending: true} = user) do
with chg <- confirmation_changeset(user, need_confirmation: false),
{:ok, user} <- update_and_set_cache(chg) do
post_register_action(user)
{:ok, user}
end
end
def confirm(%User{} = user), do: {:ok, user}
def update_notification_settings(%User{} = user, settings) do
user
|> cast(%{notification_settings: settings}, [])
@ -2081,18 +2138,6 @@ defmodule Pleroma.User do
updated_user
end
@spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
def toggle_confirmation(%User{} = user) do
user
|> confirmation_changeset(need_confirmation: !user.confirmation_pending)
|> update_and_set_cache()
end
@spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
def toggle_confirmation(users) do
Enum.map(users, &toggle_confirmation/1)
end
@spec need_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
def need_confirmation(%User{} = user, bool) do
user
@ -2365,7 +2410,7 @@ defmodule Pleroma.User do
{:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
defp add_to_block(%User{} = user, %User{} = blocked) do
with {:ok, relationship} <- UserRelationship.create_block(user, blocked) do
Cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
@cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
{:ok, relationship}
end
end
@ -2374,7 +2419,7 @@ defmodule Pleroma.User do
{:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
defp remove_from_block(%User{} = user, %User{} = blocked) do
with {:ok, relationship} <- UserRelationship.delete_block(user, blocked) do
Cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
@cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
{:ok, relationship}
end
end

View File

@ -3,6 +3,14 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Utils do
@posix_error_codes ~w(
eacces eagain ebadf ebadmsg ebusy edeadlk edeadlock edquot eexist efault
efbig eftype eintr einval eio eisdir eloop emfile emlink emultihop
enametoolong enfile enobufs enodev enolck enolink enoent enomem enospc
enosr enostr enosys enotblk enotdir enotsup enxio eopnotsupp eoverflow
eperm epipe erange erofs espipe esrch estale etxtbsy exdev
)a
def compile_dir(dir) when is_binary(dir) do
dir
|> File.ls!()
@ -44,4 +52,12 @@ defmodule Pleroma.Utils do
error -> error
end
end
@spec posix_error_message(atom()) :: binary()
def posix_error_message(code) when code in @posix_error_codes do
error_message = Gettext.dgettext(Pleroma.Web.Gettext, "posix_errors", "#{code}")
"(POSIX error: #{error_message})"
end
def posix_error_message(_), do: ""
end

View File

@ -32,6 +32,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
require Logger
require Pleroma.Constants
@behaviour Pleroma.Web.ActivityPub.ActivityPub.Persisting
@behaviour Pleroma.Web.ActivityPub.ActivityPub.Streaming
defp get_recipients(%{"type" => "Create"} = data) do
to = Map.get(data, "to", [])
cc = Map.get(data, "cc", [])
@ -85,13 +88,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp increase_replies_count_if_reply(_create_data), do: :noop
@object_types ~w[ChatMessage Question Answer Audio Video Event Article]
@spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()}
@impl true
def persist(%{"type" => type} = object, meta) when type in @object_types do
with {:ok, object} <- Object.create(object) do
{:ok, object, meta}
end
end
@impl true
def persist(object, meta) do
with local <- Keyword.fetch!(meta, :local),
{recipients, _, _} <- get_recipients(object),
@ -221,6 +225,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
Streamer.stream("participation", participations)
end
@impl true
def stream_out_participations(%Object{data: %{"context" => context}}, user) do
with %Conversation{} = conversation <- Conversation.get_for_ap_id(context) do
conversation = Repo.preload(conversation, :participations)
@ -237,8 +242,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
@impl true
def stream_out_participations(_, _), do: :noop
@impl true
def stream_out(%Activity{data: %{"type" => data_type}} = activity)
when data_type in ["Create", "Announce", "Delete"] do
activity
@ -246,6 +253,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> Streamer.stream(activity)
end
@impl true
def stream_out(_activity) do
:noop
end
@ -600,12 +608,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> Map.put(:muting_user, reading_user)
end
pagination_type = Map.get(params, :pagination_type) || :keyset
%{
godmode: params[:godmode],
reading_user: reading_user
}
|> user_activities_recipients()
|> fetch_activities(params)
|> fetch_activities(params, pagination_type)
|> Enum.reverse()
end

View File

@ -0,0 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ActivityPub.Persisting do
@callback persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()}
end

View File

@ -0,0 +1,12 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ActivityPub.Streaming do
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.User
@callback stream_out(Activity.t()) :: any()
@callback stream_out_participations(Object.t(), User.t()) :: any()
end

View File

@ -128,7 +128,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
end
defp maybe_set_tracking_data(conn, %Activity{data: %{"type" => "Create"}} = activity) do
object_id = Object.normalize(activity).id
object_id = Object.normalize(activity, fetch: false).id
assign(conn, :tracking_fun_data, object_id)
end
@ -434,7 +434,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
end
defp handle_user_activity(%User{} = user, %{"type" => "Delete"} = params) do
with %Object{} = object <- Object.normalize(params["object"]),
with %Object{} = object <- Object.normalize(params["object"], fetch: false),
true <- user.is_moderator || user.ap_id == object.data["actor"],
{:ok, delete_data, _} <- Builder.delete(user, object.data["id"]),
{:ok, delete, _} <- Pipeline.common_pipeline(delete_data, local: true) do
@ -445,7 +445,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
end
defp handle_user_activity(%User{} = user, %{"type" => "Like"} = params) do
with %Object{} = object <- Object.normalize(params["object"]),
with %Object{} = object <- Object.normalize(params["object"], fetch: false),
{_, {:ok, like_object, meta}} <- {:build_object, Builder.like(user, object)},
{_, {:ok, %Activity{} = activity, _meta}} <-
{:common_pipeline,

View File

@ -80,7 +80,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
@spec delete(User.t(), String.t()) :: {:ok, map(), keyword()}
def delete(actor, object_id) do
object = Object.normalize(object_id, false)
object = Object.normalize(object_id, fetch: false)
user = !object && User.get_cached_by_ap_id(object_id)

View File

@ -5,6 +5,8 @@
defmodule Pleroma.Web.ActivityPub.MRF do
require Logger
@behaviour Pleroma.Web.ActivityPub.MRF.PipelineFiltering
@mrf_config_descriptions [
%{
group: :pleroma,
@ -72,6 +74,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do
def filter(%{} = object), do: get_policies() |> filter(object)
@impl true
def pipeline_filter(%{} = message, meta) do
object = meta[:object_data]
ap_id = message["object"]

View File

@ -31,7 +31,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do
when is_map(child_object) do
child =
child_object["inReplyTo"]
|> Object.normalize(child_object["inReplyTo"])
|> Object.normalize(fetch: false)
|> filter_by_summary(child_object)
object = Map.put(object, "object", child)

View File

@ -0,0 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.PipelineFiltering do
@callback pipeline_filter(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
end

View File

@ -10,73 +10,75 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
@moduledoc "Detect new emojis by their shortcode and steals them"
@behaviour Pleroma.Web.ActivityPub.MRF
defp remote_host?(host), do: host != Config.get([Pleroma.Web.Endpoint, :url, :host])
defp accept_host?(host), do: host in Config.get([:mrf_steal_emoji, :hosts], [])
defp steal_emoji({shortcode, url}) do
defp steal_emoji({shortcode, url}, emoji_dir_path) do
url = Pleroma.Web.MediaProxy.url(url)
{:ok, response} = Pleroma.HTTP.get(url)
size_limit = Config.get([:mrf_steal_emoji, :size_limit], 50_000)
if byte_size(response.body) <= size_limit do
emoji_dir_path =
Config.get(
[:mrf_steal_emoji, :path],
Path.join(Config.get([:instance, :static_dir]), "emoji/stolen")
with {:ok, %{status: status} = response} when status in 200..299 <- Pleroma.HTTP.get(url) do
size_limit = Config.get([:mrf_steal_emoji, :size_limit], 50_000)
if byte_size(response.body) <= size_limit do
extension =
url
|> URI.parse()
|> Map.get(:path)
|> Path.basename()
|> Path.extname()
file_path = Path.join(emoji_dir_path, shortcode <> (extension || ".png"))
case File.write(file_path, response.body) do
:ok ->
shortcode
e ->
Logger.warn("MRF.StealEmojiPolicy: Failed to write to #{file_path}: #{inspect(e)}")
nil
end
else
Logger.debug(
"MRF.StealEmojiPolicy: :#{shortcode}: at #{url} (#{byte_size(response.body)} B) over size limit (#{
size_limit
} B)"
)
extension =
url
|> URI.parse()
|> Map.get(:path)
|> Path.basename()
|> Path.extname()
file_path = Path.join([emoji_dir_path, shortcode <> (extension || ".png")])
try do
:ok = File.write(file_path, response.body)
shortcode
rescue
e ->
Logger.warn("MRF.StealEmojiPolicy: Failed to write to #{file_path}: #{inspect(e)}")
nil
nil
end
else
Logger.debug(
"MRF.StealEmojiPolicy: :#{shortcode}: at #{url} (#{byte_size(response.body)} B) over size limit (#{
size_limit
} B)"
)
nil
e ->
Logger.warn("MRF.StealEmojiPolicy: Failed to fetch #{url}: #{inspect(e)}")
nil
end
rescue
e ->
Logger.warn("MRF.StealEmojiPolicy: Failed to fetch #{url}: #{inspect(e)}")
nil
end
@impl true
def filter(%{"object" => %{"emoji" => foreign_emojis, "actor" => actor}} = message) do
host = URI.parse(actor).host
if remote_host?(host) and accept_host?(host) do
if host != Pleroma.Web.Endpoint.host() and accept_host?(host) do
installed_emoji = Pleroma.Emoji.get_all() |> Enum.map(fn {k, _} -> k end)
emoji_dir_path =
Config.get(
[:mrf_steal_emoji, :path],
Path.join(Config.get([:instance, :static_dir]), "emoji/stolen")
)
File.mkdir_p(emoji_dir_path)
new_emojis =
foreign_emojis
|> Enum.filter(fn {shortcode, _url} -> shortcode not in installed_emoji end)
|> Enum.reject(fn {shortcode, _url} -> shortcode in installed_emoji end)
|> Enum.filter(fn {shortcode, _url} ->
reject_emoji? =
Config.get([:mrf_steal_emoji, :rejected_shortcodes], [])
[:mrf_steal_emoji, :rejected_shortcodes]
|> Config.get([])
|> Enum.find(false, fn regex -> String.match?(shortcode, regex) end)
!reject_emoji?
end)
|> Enum.map(&steal_emoji(&1))
|> Enum.map(&steal_emoji(&1, emoji_dir_path))
|> Enum.filter(& &1)
if !Enum.empty?(new_emojis) do

View File

@ -9,6 +9,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
the system.
"""
@behaviour Pleroma.Web.ActivityPub.ObjectValidator.Validating
alias Pleroma.Activity
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
@ -32,7 +34,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator
@spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
@impl true
def validate(object, meta)
def validate(%{"type" => type} = object, meta)
@ -286,7 +288,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
def fetch_actor_and_object(object) do
fetch_actor(object)
Object.normalize(object["object"], true)
Object.normalize(object["object"], fetch: true)
:ok
end
end

View File

@ -0,0 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidator.Validating do
@callback validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
end

View File

@ -14,12 +14,19 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.Federator
@side_effects Config.get([:pipeline, :side_effects], SideEffects)
@federator Config.get([:pipeline, :federator], Federator)
@object_validator Config.get([:pipeline, :object_validator], ObjectValidator)
@mrf Config.get([:pipeline, :mrf], MRF)
@activity_pub Config.get([:pipeline, :activity_pub], ActivityPub)
@config Config.get([:pipeline, :config], Config)
@spec common_pipeline(map(), keyword()) ::
{:ok, Activity.t() | Object.t(), keyword()} | {:error, any()}
def common_pipeline(object, meta) do
case Repo.transaction(fn -> do_common_pipeline(object, meta) end) do
{:ok, {:ok, activity, meta}} ->
SideEffects.handle_after_transaction(meta)
@side_effects.handle_after_transaction(meta)
{:ok, activity, meta}
{:ok, value} ->
@ -35,13 +42,13 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
def do_common_pipeline(object, meta) do
with {_, {:ok, validated_object, meta}} <-
{:validate_object, ObjectValidator.validate(object, meta)},
{:validate_object, @object_validator.validate(object, meta)},
{_, {:ok, mrfd_object, meta}} <-
{:mrf_object, MRF.pipeline_filter(validated_object, meta)},
{:mrf_object, @mrf.pipeline_filter(validated_object, meta)},
{_, {:ok, activity, meta}} <-
{:persist_object, ActivityPub.persist(mrfd_object, meta)},
{:persist_object, @activity_pub.persist(mrfd_object, meta)},
{_, {:ok, activity, meta}} <-
{:execute_side_effects, SideEffects.handle(activity, meta)},
{:execute_side_effects, @side_effects.handle(activity, meta)},
{_, {:ok, _}} <- {:federation, maybe_federate(activity, meta)} do
{:ok, activity, meta}
else
@ -54,7 +61,7 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
defp maybe_federate(%Activity{} = activity, meta) do
with {:ok, local} <- Keyword.fetch(meta, :local) do
do_not_federate = meta[:do_not_federate] || !Config.get([:instance, :federating])
do_not_federate = meta[:do_not_federate] || !@config.get([:instance, :federating])
if !do_not_federate and local and not Visibility.is_local_public?(activity) do
activity =
@ -64,7 +71,7 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
activity
end
Federator.publish(activity)
@federator.publish(activity)
{:ok, :federated}
else
{:ok, :not_federated}

View File

@ -130,7 +130,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
fetchers =
with %Activity{data: %{"type" => "Delete"}} <- activity,
%Object{id: object_id} <- Object.normalize(activity),
%Object{id: object_id} <- Object.normalize(activity, fetch: false),
fetchers <- User.get_delivered_users_by_object_id(object_id),
_ <- Delivery.delete_all_by_object_id(object_id) do
fetchers

View File

@ -27,11 +27,19 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
require Logger
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
@ap_streamer Pleroma.Config.get([:side_effects, :ap_streamer], ActivityPub)
@logger Pleroma.Config.get([:side_effects, :logger], Logger)
@behaviour Pleroma.Web.ActivityPub.SideEffects.Handling
@impl true
def handle(object, meta \\ [])
# Task this handles
# - Follows
# - Sends a notification
@impl true
def handle(
%{
data: %{
@ -59,6 +67,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
# - Rejects all existing follow activities for this person
# - Updates the follow state
# - Dismisses notification
@impl true
def handle(
%{
data: %{
@ -85,6 +94,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
# - Follows if possible
# - Sends a notification
# - Generates accept or reject if appropriate
@impl true
def handle(
%{
data: %{
@ -126,6 +136,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
# Tasks this handles:
# - Unfollow and block
@impl true
def handle(
%{data: %{"type" => "Block", "object" => blocked_user, "actor" => blocking_user}} =
object,
@ -144,6 +155,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
#
# For a local user, we also get a changeset with the full information, so we
# can update non-federating, non-activitypub settings as well.
@impl true
def handle(%{data: %{"type" => "Update", "object" => updated_object}} = object, meta) do
if changeset = Keyword.get(meta, :user_update_changeset) do
changeset
@ -162,6 +174,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
# Tasks this handles:
# - Add like to object
# - Set up notification
@impl true
def handle(%{data: %{"type" => "Like"}} = object, meta) do
liked_object = Object.get_by_ap_id(object.data["object"])
Utils.add_like_to_object(object, liked_object)
@ -179,6 +192,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
# - Increase replies count
# - Set up ActivityExpiration
# - Set up notifications
@impl true
def handle(%{data: %{"type" => "Create"}} = activity, meta) do
with {:ok, object, meta} <- handle_object_creation(meta[:object_data], meta),
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
@ -207,6 +221,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
# - Add announce to object
# - Set up notification
# - Stream out the announce
@impl true
def handle(%{data: %{"type" => "Announce"}} = object, meta) do
announced_object = Object.get_by_ap_id(object.data["object"])
user = User.get_cached_by_ap_id(object.data["actor"])
@ -224,6 +239,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
{:ok, object, meta}
end
@impl true
def handle(%{data: %{"type" => "Undo", "object" => undone_object}} = object, meta) do
with undone_object <- Activity.get_by_ap_id(undone_object),
:ok <- handle_undoing(undone_object) do
@ -234,6 +250,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
# Tasks this handles:
# - Add reaction to object
# - Set up notification
@impl true
def handle(%{data: %{"type" => "EmojiReact"}} = object, meta) do
reacted_object = Object.get_by_ap_id(object.data["object"])
Utils.add_emoji_reaction_to_object(object, reacted_object)
@ -250,9 +267,10 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
# - Reduce the user note count
# - Reduce the reply count
# - Stream out the activity
@impl true
def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, meta) do
deleted_object =
Object.normalize(deleted_object, false) ||
Object.normalize(deleted_object, fetch: false) ||
User.get_cached_by_ap_id(deleted_object)
result =
@ -271,12 +289,12 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
MessageReference.delete_for_object(deleted_object)
ActivityPub.stream_out(object)
ActivityPub.stream_out_participations(deleted_object, user)
@ap_streamer.stream_out(object)
@ap_streamer.stream_out_participations(deleted_object, user)
:ok
else
{:actor, _} ->
Logger.error("The object doesn't have an actor: #{inspect(deleted_object)}")
@logger.error("The object doesn't have an actor: #{inspect(deleted_object)}")
:no_object_actor
end
@ -295,6 +313,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
end
# Nothing to do
@impl true
def handle(object, meta) do
{:ok, object, meta}
end
@ -312,7 +331,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
{:ok, chat} = Chat.bump_or_create(user.id, other_user.ap_id)
{:ok, cm_ref} = MessageReference.create(chat, object, user.ap_id != actor.ap_id)
Cachex.put(
@cachex.put(
:chat_message_id_idempotency_key_cache,
cm_ref.id,
meta[:idempotency_key]
@ -439,6 +458,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|> Keyword.put(:notifications, notifications ++ existing)
end
@impl true
def handle_after_transaction(meta) do
meta
|> send_notifications()

View File

@ -0,0 +1,8 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.SideEffects.Handling do
@callback handle(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
@callback handle_after_transaction(map()) :: map()
end

View File

@ -653,7 +653,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
@spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil
def get_obj_helper(id, options \\ []) do
case Object.normalize(id, true, options) do
options = Keyword.put(options, :fetch, true)
case Object.normalize(id, options) do
%Object{} = object -> {:ok, object}
_ -> nil
end
@ -672,7 +674,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"actor" => attributed_to,
"object" => data
}) do
{:ok, Object.normalize(activity)}
{:ok, Object.normalize(activity, fetch: false)}
else
_ -> get_obj_helper(object_id)
end
@ -763,7 +765,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
when activity_type in ["Create", "Listen"] do
object =
object_id
|> Object.normalize()
|> Object.normalize(fetch: false)
|> Map.get(:data)
|> prepare_object
@ -779,7 +781,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def prepare_outgoing(%{"type" => "Announce", "actor" => ap_id, "object" => object_id} = data) do
object =
object_id
|> Object.normalize()
|> Object.normalize(fetch: false)
data =
if Visibility.is_private?(object) && object.data["actor"] == ap_id do
@ -919,7 +921,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
defp build_emoji_tag({name, url}) do
%{
"icon" => %{"url" => url, "type" => "Image"},
"icon" => %{"url" => "#{URI.encode(url)}", "type" => "Image"},
"name" => ":" <> name <> ":",
"type" => "Emoji",
"updated" => "1970-01-01T00:00:00Z",

View File

@ -18,7 +18,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do
def render("object.json", %{object: %Activity{data: %{"type" => activity_type}} = activity})
when activity_type in ["Create", "Listen"] do
base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header()
object = Object.normalize(activity)
object = Object.normalize(activity, fetch: false)
additional =
Transmogrifier.prepare_object(activity.data)
@ -29,7 +29,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do
def render("object.json", %{object: %Activity{} = activity}) do
base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header()
object = Object.normalize(activity)
object = Object.normalize(activity, fetch: false)
additional =
Transmogrifier.prepare_object(activity.data)

View File

@ -112,7 +112,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"tag" => emoji_tags,
# Note: key name is indeed "discoverable" (not an error)
"discoverable" => user.is_discoverable,
"capabilities" => capabilities
"capabilities" => capabilities,
"alsoKnownAs" => user.also_known_as
}
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))

View File

@ -103,13 +103,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
godmode = params["godmode"] == "true" || params["godmode"] == true
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
{_, page_size} = page_params(params)
{page, page_size} = page_params(params)
activities =
ActivityPub.fetch_user_activities(user, nil, %{
limit: page_size,
offset: (page - 1) * page_size,
godmode: godmode,
exclude_reblogs: not with_reblogs
exclude_reblogs: not with_reblogs,
pagination_type: :offset
})
conn
@ -415,7 +417,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
User.toggle_confirmation(users)
User.confirm(users)
ModerationLog.insert_log(%{actor: admin, subject: users, action: "confirm_email"})

View File

@ -9,6 +9,8 @@ defmodule Pleroma.Web.AdminAPI.MediaProxyCacheController do
alias Pleroma.Web.MediaProxy
alias Pleroma.Web.Plugs.OAuthScopesPlug
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(
@ -38,7 +40,7 @@ defmodule Pleroma.Web.AdminAPI.MediaProxyCacheController do
defp fetch_entries(params) do
MediaProxy.cache_table()
|> Cachex.stream!(Cachex.Query.create(true, :key))
|> @cachex.stream!(Cachex.Query.create(true, :key))
|> filter_entries(params[:query])
end

View File

@ -69,6 +69,7 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
%{
"id" => user.id,
"email" => user.email,
"avatar" => avatar,
"nickname" => user.nickname,
"display_name" => display_name,

View File

@ -21,6 +21,7 @@ defmodule Pleroma.Web.AdminAPI.ModerationLogView do
|> DateTime.to_unix()
%{
id: log_entry.id,
data: log_entry.data,
time: time,
message: ModerationLog.get_log_entry_message(log_entry)

View File

@ -19,8 +19,7 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
reports:
reports[:items]
|> Enum.map(&Report.extract_report_info/1)
|> Enum.map(&render(__MODULE__, "show.json", &1))
|> Enum.reverse(),
|> Enum.map(&render(__MODULE__, "show.json", &1)),
total: reports[:total]
}
end

View File

@ -614,6 +614,12 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
nullable: true,
description: "Allows automatically follow moved following accounts"
},
also_known_as: %Schema{
type: :array,
items: %Schema{type: :string},
nullable: true,
description: "List of alternate ActivityPub IDs"
},
pleroma_background_image: %Schema{
type: :string,
nullable: true,
@ -644,6 +650,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
pleroma_settings_store: %{"pleroma-fe" => %{"key" => "val"}},
skip_thread_containment: false,
allow_following_move: false,
also_known_as: ["https://foo.bar/users/foo"],
discoverable: false,
actor_type: "Person"
}

View File

@ -27,7 +27,8 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiFileOperation do
422 => Operation.response("Unprocessable Entity", "application/json", ApiError),
404 => Operation.response("Not Found", "application/json", ApiError),
400 => Operation.response("Bad Request", "application/json", ApiError),
409 => Operation.response("Conflict", "application/json", ApiError)
409 => Operation.response("Conflict", "application/json", ApiError),
500 => Operation.response("Error", "application/json", ApiError)
}
}
end

View File

@ -169,7 +169,8 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do
responses: %{
200 => ok_response(),
400 => Operation.response("Bad Request", "application/json", ApiError),
404 => Operation.response("Not Found", "application/json", ApiError)
404 => Operation.response("Not Found", "application/json", ApiError),
500 => Operation.response("Error", "application/json", ApiError)
}
}
end
@ -184,7 +185,8 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do
parameters: [name_param()],
responses: %{
200 => Operation.response("Metadata", "application/json", metadata()),
400 => Operation.response("Bad Request", "application/json", ApiError)
400 => Operation.response("Bad Request", "application/json", ApiError),
500 => Operation.response("Error", "application/json", ApiError)
}
}
end

View File

@ -40,6 +40,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
pleroma: %Schema{
type: :object,
properties: %{
ap_id: %Schema{type: :string},
also_known_as: %Schema{type: :array, items: %Schema{type: :string}},
allow_following_move: %Schema{
type: :boolean,
description: "whether the user allows automatically follow moved following accounts"

View File

@ -142,7 +142,7 @@ defmodule Pleroma.Web.CommonAPI do
with {_, %Activity{data: %{"object" => _, "type" => "Create"}} = activity} <-
{:find_activity, Activity.get_by_id(activity_id)},
{_, %Object{} = object, _} <-
{:find_object, Object.normalize(activity, false), activity},
{:find_object, Object.normalize(activity, fetch: false), activity},
true <- User.superuser?(user) || user.ap_id == object.data["actor"],
{:ok, delete_data, _} <- Builder.delete(user, object.data["id"]),
{:ok, delete, _} <- Pipeline.common_pipeline(delete_data, local: true) do
@ -173,7 +173,7 @@ defmodule Pleroma.Web.CommonAPI do
def repeat(id, user, params \\ %{}) do
with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id),
object = %Object{} <- Object.normalize(activity, false),
object = %Object{} <- Object.normalize(activity, fetch: false),
{_, nil} <- {:existing_announce, Utils.get_existing_announce(user.ap_id, object)},
public = public_announce?(object, params),
{:ok, announce, _} <- Builder.announce(user, object, public: public),
@ -191,7 +191,7 @@ defmodule Pleroma.Web.CommonAPI do
def unrepeat(id, user) do
with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
{:find_activity, Activity.get_by_id(id)},
%Object{} = note <- Object.normalize(activity, false),
%Object{} = note <- Object.normalize(activity, fetch: false),
%Activity{} = announce <- Utils.get_existing_announce(user.ap_id, note),
{:ok, undo, _} <- Builder.undo(user, announce),
{:ok, activity, _} <- Pipeline.common_pipeline(undo, local: true) do
@ -253,7 +253,7 @@ defmodule Pleroma.Web.CommonAPI do
def unfavorite(id, user) do
with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
{:find_activity, Activity.get_by_id(id)},
%Object{} = note <- Object.normalize(activity, false),
%Object{} = note <- Object.normalize(activity, fetch: false),
%Activity{} = like <- Utils.get_existing_like(user.ap_id, note),
{:ok, undo, _} <- Builder.undo(user, like),
{:ok, activity, _} <- Pipeline.common_pipeline(undo, local: true) do
@ -266,7 +266,7 @@ defmodule Pleroma.Web.CommonAPI do
def react_with_emoji(id, user, emoji) do
with %Activity{} = activity <- Activity.get_by_id(id),
object <- Object.normalize(activity),
object <- Object.normalize(activity, fetch: false),
{:ok, emoji_react, _} <- Builder.emoji_react(user, object, emoji),
{:ok, activity, _} <- Pipeline.common_pipeline(emoji_react, local: true) do
{:ok, activity}
@ -377,7 +377,7 @@ defmodule Pleroma.Web.CommonAPI do
def get_replied_to_visibility(nil), do: nil
def get_replied_to_visibility(activity) do
with %Object{} = object <- Object.normalize(activity) do
with %Object{} = object <- Object.normalize(activity, fetch: false) do
Visibility.get_visibility(object)
end
end

View File

@ -319,7 +319,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
defp add_in_reply_to(object, nil), do: object
defp add_in_reply_to(object, in_reply_to) do
with %Object{} = in_reply_to_object <- Object.normalize(in_reply_to) do
with %Object{} = in_reply_to_object <- Object.normalize(in_reply_to, fetch: false) do
Map.put(object, "inReplyTo", in_reply_to_object.data["id"])
else
_ -> object
@ -399,7 +399,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
%Activity{data: %{"to" => _to, "type" => type} = data} = activity
)
when type == "Create" do
object = Object.normalize(activity, false)
object = Object.normalize(activity, fetch: false)
object_data =
cond do

View File

@ -31,7 +31,7 @@ defmodule Pleroma.Web.EmbedController do
end
defp get_counts(%Activity{} = activity) do
%Object{data: data} = Object.normalize(activity)
%Object{data: data} = Object.normalize(activity, fetch: false)
%{
likes: Map.get(data, "like_count", 0),

View File

@ -15,6 +15,8 @@ defmodule Pleroma.Web.Federator do
require Logger
@behaviour Pleroma.Web.Federator.Publishing
@doc """
Returns `true` if the distance to target object does not exceed max configured value.
Serves to prevent fetching of very long threads, especially useful on smaller instances.
@ -39,10 +41,12 @@ defmodule Pleroma.Web.Federator do
ReceiverWorker.enqueue("incoming_ap_doc", %{"params" => params})
end
@impl true
def publish(%{id: "pleroma:fakeid"} = activity) do
perform(:publish, activity)
end
@impl true
def publish(activity) do
PublisherWorker.enqueue("publish", %{"activity_id" => activity.id})
end

View File

@ -0,0 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Federator.Publishing do
@callback publish(map()) :: any()
end

View File

@ -23,7 +23,7 @@ defmodule Pleroma.Web.Feed.FeedView do
def pub_date(%DateTime{} = date), do: Timex.format!(date, "{RFC822}")
def prepare_activity(activity, opts \\ []) do
object = Object.normalize(activity)
object = Object.normalize(activity, fetch: false)
actor =
if opts[:actor] do
@ -51,7 +51,7 @@ defmodule Pleroma.Web.Feed.FeedView do
def feed_logo do
case Pleroma.Config.get([:feed, :logo]) do
nil ->
"#{Pleroma.Web.base_url()}/static/logo.png"
"#{Pleroma.Web.base_url()}/static/logo.svg"
logo ->
"#{Pleroma.Web.base_url()}#{logo}"

View File

@ -184,6 +184,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
:show_role,
:skip_thread_containment,
:allow_following_move,
:also_known_as,
:accepts_chat_messages
]
|> Enum.reduce(%{}, fn key, acc ->
@ -207,6 +208,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
if bot, do: {:ok, "Service"}, else: {:ok, "Person"}
end)
|> Maps.put_if_present(:actor_type, params[:actor_type])
|> Maps.put_if_present(:also_known_as, params[:also_known_as])
# Note: param name is indeed :locked (not an error)
|> Maps.put_if_present(:is_locked, params[:locked])
# Note: param name is indeed :discoverable (not an error)

View File

@ -26,6 +26,8 @@ defmodule Pleroma.Web.MastodonAPI.PollController do
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PollOperation
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
@doc "GET /api/v1/polls/:id"
def show(%{assigns: %{user: user}} = conn, %{id: id}) do
with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60),
@ -55,7 +57,7 @@ defmodule Pleroma.Web.MastodonAPI.PollController do
defp get_cached_vote_or_vote(user, object, choices) do
idempotency_key = "polls:#{user.id}:#{object.data["id"]}"
Cachex.fetch!(:idempotency_cache, idempotency_key, fn ->
@cachex.fetch!(:idempotency_cache, idempotency_key, fn _ ->
case CommonAPI.vote(user, object, choices) do
{:error, _message} = res -> {:ignore, res}
res -> {:commit, res}

View File

@ -318,7 +318,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
with true <- Pleroma.Config.get([:instance, :show_reactions]),
%Activity{} = activity <- Activity.get_by_id_with_object(id),
{:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)},
%Object{data: %{"likes" => likes}} <- Object.normalize(activity) do
%Object{data: %{"likes" => likes}} <- Object.normalize(activity, fetch: false) do
users =
User
|> Ecto.Query.where([u], u.ap_id in ^likes)
@ -339,7 +339,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
{:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)},
%Object{data: %{"announcements" => announces, "id" => ap_id}} <-
Object.normalize(activity) do
Object.normalize(activity, fetch: false) do
announces =
"Announce"
|> Activity.Queries.by_type()

View File

@ -187,18 +187,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
header_static = User.banner_url(user) |> MediaProxy.preview_url(static: true)
following_count =
if !user.hide_follows_count or !user.hide_follows or opts[:for] == user do
user.following_count || 0
else
0
end
if !user.hide_follows_count or !user.hide_follows or opts[:for] == user,
do: user.following_count,
else: 0
followers_count =
if !user.hide_followers_count or !user.hide_followers or opts[:for] == user do
user.follower_count || 0
else
0
end
if !user.hide_followers_count or !user.hide_followers or opts[:for] == user,
do: user.follower_count,
else: 0
bot = user.actor_type == "Service"
@ -269,6 +265,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
# Pleroma extension
pleroma: %{
ap_id: user.ap_id,
also_known_as: user.also_known_as,
confirmation_pending: user.confirmation_pending,
tags: user.tags,
hide_followers_count: user.hide_followers_count,

View File

@ -139,7 +139,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
end
defp put_chat_message(response, activity, reading_user, opts) do
object = Object.normalize(activity)
object = Object.normalize(activity, fetch: false)
author = User.get_cached_by_ap_id(object.data["actor"])
chat = Pleroma.Chat.get(reading_user.id, author.ap_id)
cm_ref = MessageReference.for_chat_and_object(chat, object)

View File

@ -41,7 +41,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
activities
|> Enum.map(fn
%{data: %{"type" => "Create"}} = activity ->
object = Object.normalize(activity)
object = Object.normalize(activity, fetch: false)
object && object.data["inReplyTo"] != "" && object.data["inReplyTo"]
_ ->
@ -51,7 +51,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|> Activity.create_by_object_ap_id_with_object()
|> Repo.all()
|> Enum.reduce(%{}, fn activity, acc ->
object = Object.normalize(activity)
object = Object.normalize(activity, fetch: false)
if object, do: Map.put(acc, object.data["id"], activity), else: acc
end)
end
@ -65,7 +65,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
defp get_context_id(_), do: nil
defp reblogged?(activity, user) do
object = Object.normalize(activity) || %{}
object = Object.normalize(activity, fetch: false) || %{}
present?(user && user.ap_id in (object.data["announcements"] || []))
end
@ -84,7 +84,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
parent_activities =
activities
|> Enum.filter(&(&1.data["type"] == "Announce" && &1.data["object"]))
|> Enum.map(&Object.normalize(&1).data["id"])
|> Enum.map(&Object.normalize(&1, fetch: false).data["id"])
|> Activity.create_by_object_ap_id()
|> Activity.with_preloaded_object(:left)
|> Activity.with_preloaded_bookmark(reading_user)
@ -124,7 +124,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
) do
user = CommonAPI.get_user(activity.data["actor"])
created_at = Utils.to_masto_date(activity.data["published"])
activity_object = Object.normalize(activity)
activity_object = Object.normalize(activity, fetch: false)
reblogged_parent_activity =
if opts[:parent_activities] do
@ -193,7 +193,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
end
def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do
object = Object.normalize(activity)
object = Object.normalize(activity, fetch: false)
user = CommonAPI.get_user(activity.data["actor"])
user_follower_address = user.follower_address
@ -451,7 +451,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
end
def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do
object = Object.normalize(activity)
object = Object.normalize(activity, fetch: false)
with nil <- replied_to_activities[object.data["inReplyTo"]] do
# If user didn't participate in the thread
@ -460,7 +460,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
end
def get_reply_to(%{data: %{"object" => _object}} = activity, _) do
object = Object.normalize(activity)
object = Object.normalize(activity, fetch: false)
if object.data["inReplyTo"] && object.data["inReplyTo"] != "" do
Activity.get_create_by_object_ap_id(object.data["inReplyTo"])

View File

@ -12,29 +12,31 @@ defmodule Pleroma.Web.MediaProxy do
@base64_opts [padding: false]
@cache_table :banned_urls_cache
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
def cache_table, do: @cache_table
@spec in_banned_urls(String.t()) :: boolean()
def in_banned_urls(url), do: elem(Cachex.exists?(@cache_table, url(url)), 1)
def in_banned_urls(url), do: elem(@cachex.exists?(@cache_table, url(url)), 1)
def remove_from_banned_urls(urls) when is_list(urls) do
Cachex.execute!(@cache_table, fn cache ->
Enum.each(Invalidation.prepare_urls(urls), &Cachex.del(cache, &1))
@cachex.execute!(@cache_table, fn cache ->
Enum.each(Invalidation.prepare_urls(urls), &@cachex.del(cache, &1))
end)
end
def remove_from_banned_urls(url) when is_binary(url) do
Cachex.del(@cache_table, url(url))
@cachex.del(@cache_table, url(url))
end
def put_in_banned_urls(urls) when is_list(urls) do
Cachex.execute!(@cache_table, fn cache ->
Enum.each(Invalidation.prepare_urls(urls), &Cachex.put(cache, &1, true))
@cachex.execute!(@cache_table, fn cache ->
Enum.each(Invalidation.prepare_urls(urls), &@cachex.put(cache, &1, true))
end)
end
def put_in_banned_urls(url) when is_binary(url) do
Cachex.put(@cache_table, url(url), true)
@cachex.put(@cache_table, url(url), true)
end
def url(url) when is_nil(url) or url == "", do: nil

View File

@ -74,14 +74,14 @@ defmodule Pleroma.Web.OStatus.OStatusController do
cond do
format in ["json", "activity+json"] ->
if activity.local do
%{data: %{"id" => redirect_url}} = Object.normalize(activity)
%{data: %{"id" => redirect_url}} = Object.normalize(activity, fetch: false)
redirect(conn, external: redirect_url)
else
{:error, :not_found}
end
activity.data["type"] == "Create" ->
%Object{} = object = Object.normalize(activity)
%Object{} = object = Object.normalize(activity, fetch: false)
RedirectController.redirector_with_meta(
conn,
@ -112,7 +112,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do
with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id_with_object(id),
true <- Visibility.is_public?(activity),
{_, true} <- {:visible?, Visibility.visible_for_user?(activity, _reading_user = nil)},
%Object{} = object <- Object.normalize(activity),
%Object{} = object <- Object.normalize(activity, fetch: false),
%{data: %{"attachment" => [%{"url" => [url | _]} | _]}} <- object,
true <- String.starts_with?(url["mediaType"], ["audio", "video"]) do
conn

View File

@ -82,7 +82,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
media_id: params[:media_id],
idempotency_key: idempotency_key(conn)
),
message <- Object.normalize(activity, false),
message <- Object.normalize(activity, fetch: false),
cm_ref <- MessageReference.for_chat_and_object(chat, message) do
conn
|> put_view(MessageReferenceView)

View File

@ -42,7 +42,10 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do
|> json(%{error: "pack name, shortcode or filename cannot be empty"})
{:error, _} = error ->
handle_error(conn, error, %{pack_name: pack_name})
handle_error(conn, error, %{
pack_name: pack_name,
message: "Unexpected error occurred while adding file to pack."
})
end
end
@ -69,7 +72,11 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do
|> json(%{error: "new_shortcode or new_filename cannot be empty"})
{:error, _} = error ->
handle_error(conn, error, %{pack_name: pack_name, code: shortcode})
handle_error(conn, error, %{
pack_name: pack_name,
code: shortcode,
message: "Unexpected error occurred while updating."
})
end
end
@ -84,7 +91,11 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do
|> json(%{error: "pack name or shortcode cannot be empty"})
{:error, _} = error ->
handle_error(conn, error, %{pack_name: pack_name, code: shortcode})
handle_error(conn, error, %{
pack_name: pack_name,
code: shortcode,
message: "Unexpected error occurred while deleting emoji file."
})
end
end
@ -94,18 +105,24 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do
|> json(%{error: "Emoji \"#{emoji_code}\" does not exist"})
end
defp handle_error(conn, {:error, :not_found}, %{pack_name: pack_name}) do
defp handle_error(conn, {:error, :enoent}, %{pack_name: pack_name}) do
conn
|> put_status(:not_found)
|> json(%{error: "pack \"#{pack_name}\" is not found"})
end
defp handle_error(conn, {:error, _}, _) do
render_error(
conn,
:internal_server_error,
"Unexpected error occurred while adding file to pack."
)
defp handle_error(conn, {:error, error}, opts) do
message =
[
Map.get(opts, :message, "Unexpected error occurred."),
Pleroma.Utils.posix_error_message(error)
]
|> Enum.join(" ")
|> String.trim()
conn
|> put_status(:internal_server_error)
|> json(%{error: message})
end
defp get_filename(%Plug.Upload{filename: filename}), do: filename

View File

@ -71,7 +71,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
with {:ok, pack} <- Pack.show(name: name, page: page, page_size: page_size) do
json(conn, pack)
else
{:error, :not_found} ->
{:error, :enoent} ->
conn
|> put_status(:not_found)
|> json(%{error: "Pack #{name} does not exist"})
@ -80,6 +80,17 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
conn
|> put_status(:bad_request)
|> json(%{error: "pack name cannot be empty"})
{:error, error} ->
error_message =
add_posix_error(
"Failed to get the contents of the `#{name}` pack.",
error
)
conn
|> put_status(:internal_server_error)
|> json(%{error: error_message})
end
end
@ -95,7 +106,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
"Pack #{name} cannot be downloaded from this instance, either pack sharing was disabled for this pack or some files are missing"
})
{:error, :not_found} ->
{:error, :enoent} ->
conn
|> put_status(:not_found)
|> json(%{error: "Pack #{name} does not exist"})
@ -116,10 +127,10 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
|> put_status(:internal_server_error)
|> json(%{error: "SHA256 for the pack doesn't match the one sent by the server"})
{:error, e} ->
{:error, error} ->
conn
|> put_status(:internal_server_error)
|> json(%{error: e})
|> json(%{error: error})
end
end
@ -139,12 +150,16 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
|> put_status(:bad_request)
|> json(%{error: "pack name cannot be empty"})
{:error, _} ->
render_error(
conn,
:internal_server_error,
"Unexpected error occurred while creating pack."
)
{:error, error} ->
error_message =
add_posix_error(
"Unexpected error occurred while creating pack.",
error
)
conn
|> put_status(:internal_server_error)
|> json(%{error: error_message})
end
end
@ -164,10 +179,12 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
|> put_status(:bad_request)
|> json(%{error: "pack name cannot be empty"})
{:error, _, _} ->
{:error, error, _} ->
error_message = add_posix_error("Couldn't delete the `#{name}` pack", error)
conn
|> put_status(:internal_server_error)
|> json(%{error: "Couldn't delete the pack #{name}"})
|> json(%{error: error_message})
end
end
@ -180,12 +197,16 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
|> put_status(:bad_request)
|> json(%{error: "The fallback archive does not have all files specified in pack.json"})
{:error, _} ->
render_error(
conn,
:internal_server_error,
"Unexpected error occurred while updating pack metadata."
)
{:error, error} ->
error_message =
add_posix_error(
"Unexpected error occurred while updating pack metadata.",
error
)
conn
|> put_status(:internal_server_error)
|> json(%{error: error_message})
end
end
@ -204,4 +225,10 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
|> json(%{error: "Error accessing emoji pack directory"})
end
end
defp add_posix_error(msg, error) do
[msg, Pleroma.Utils.posix_error_message(error)]
|> Enum.join(" ")
|> String.trim()
end
end

View File

@ -29,7 +29,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do
with true <- Pleroma.Config.get([:instance, :show_reactions]),
%Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
%Object{data: %{"reactions" => reactions}} when is_list(reactions) <-
Object.normalize(activity) do
Object.normalize(activity, fetch: false) do
reactions =
reactions
|> filter(params)

View File

@ -10,6 +10,7 @@ defmodule Pleroma.Web.PleromaAPI.BackupView do
def render("show.json", %{backup: %Backup{} = backup}) do
%{
id: backup.id,
content_type: backup.content_type,
url: download_url(backup),
file_size: backup.file_size,

View File

@ -10,6 +10,8 @@ defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceView do
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.StatusView
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
def render(
"show.json",
%{
@ -51,7 +53,7 @@ defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceView do
end
defp put_idempotency_key(data) do
with {:ok, idempotency_key} <- Cachex.get(:chat_message_id_idempotency_key_cache, data.id) do
with {:ok, idempotency_key} <- @cachex.get(:chat_message_id_idempotency_key_cache, data.id) do
data
|> Maps.put_if_present(:idempotency_key, idempotency_key)
else

View File

@ -15,7 +15,7 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleView do
alias Pleroma.Web.MastodonAPI.AccountView
def render("show.json", %{activity: %Activity{data: %{"type" => "Listen"}} = activity} = opts) do
object = Object.normalize(activity)
object = Object.normalize(activity, fetch: false)
user = CommonAPI.get_user(activity.data["actor"])
created_at = Utils.to_masto_date(activity.data["published"])

View File

@ -41,6 +41,8 @@ defmodule Pleroma.Web.Plugs.Cache do
@defaults %{ttl: nil, query_params: true}
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
@impl true
def init([]), do: @defaults
@ -53,7 +55,7 @@ defmodule Pleroma.Web.Plugs.Cache do
def call(%{method: "GET"} = conn, opts) do
key = cache_key(conn, opts)
case Cachex.get(:web_resp_cache, key) do
case @cachex.get(:web_resp_cache, key) do
{:ok, nil} ->
cache_resp(conn, opts)
@ -97,11 +99,11 @@ defmodule Pleroma.Web.Plugs.Cache do
conn =
unless opts[:tracking_fun] do
Cachex.put(:web_resp_cache, key, {content_type, body}, ttl: ttl)
@cachex.put(:web_resp_cache, key, {content_type, body}, ttl: ttl)
conn
else
tracking_fun_data = Map.get(conn.assigns, :tracking_fun_data, nil)
Cachex.put(:web_resp_cache, key, {content_type, body, tracking_fun_data}, ttl: ttl)
@cachex.put(:web_resp_cache, key, {content_type, body, tracking_fun_data}, ttl: ttl)
opts.tracking_fun.(conn, tracking_fun_data)
end

View File

@ -8,6 +8,8 @@ defmodule Pleroma.Web.Plugs.IdempotencyPlug do
@behaviour Plug
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
@impl true
def init(opts), do: opts
@ -25,7 +27,7 @@ defmodule Pleroma.Web.Plugs.IdempotencyPlug do
def call(conn, _), do: conn
def process_request(conn, key) do
case Cachex.get(:idempotency_cache, key) do
case @cachex.get(:idempotency_cache, key) do
{:ok, nil} ->
cache_resposnse(conn, key)
@ -43,7 +45,7 @@ defmodule Pleroma.Web.Plugs.IdempotencyPlug do
content_type = get_content_type(conn)
record = {request_id, content_type, conn.status, conn.resp_body}
{:ok, _} = Cachex.put(:idempotency_cache, key, record)
{:ok, _} = @cachex.put(:idempotency_cache, key, record)
conn
|> put_resp_header("idempotency-key", key)

View File

@ -72,6 +72,8 @@ defmodule Pleroma.Web.Plugs.RateLimiter do
require Logger
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
@doc false
def init(plug_opts) do
plug_opts
@ -124,7 +126,7 @@ defmodule Pleroma.Web.Plugs.RateLimiter do
key_name = make_key_name(action_settings)
limit = get_limits(action_settings)
case Cachex.get(bucket_name, key_name) do
case @cachex.get(bucket_name, key_name) do
{:error, :no_cache} ->
@inspect_bucket_not_found
@ -157,7 +159,7 @@ defmodule Pleroma.Web.Plugs.RateLimiter do
key_name = make_key_name(action_settings)
limit = get_limits(action_settings)
case Cachex.get_and_update(bucket_name, key_name, &increment_value(&1, limit)) do
case @cachex.get_and_update(bucket_name, key_name, &increment_value(&1, limit)) do
{:commit, value} ->
{:ok, value}

View File

@ -87,8 +87,15 @@ defmodule Pleroma.Web.Plugs.UploadedMedia do
end
defp get_media(conn, {:url, url}, true, _) do
proxy_opts = [
http: [
follow_redirect: true,
pool: :upload
]
]
conn
|> Pleroma.ReverseProxy.call(url, Pleroma.Config.get([Pleroma.Upload, :proxy_opts], []))
|> Pleroma.ReverseProxy.call(url, proxy_opts)
end
defp get_media(conn, {:url, url}, _, _) do

View File

@ -32,7 +32,7 @@ defmodule Pleroma.Web.Push.Impl do
mastodon_type = notification.type
gcm_api_key = Application.get_env(:web_push_encryption, :gcm_api_key)
avatar_url = User.avatar_url(actor)
object = Object.normalize(activity, false)
object = Object.normalize(activity, fetch: false)
user = User.get_cached_by_id(user_id)
direct_conversation_id = Activity.direct_conversation_id(activity, user)

View File

@ -12,8 +12,9 @@ defmodule Pleroma.Web.RelMe do
if Pleroma.Config.get(:env) == :test do
def parse(url) when is_binary(url), do: parse_url(url)
else
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
def parse(url) when is_binary(url) do
Cachex.fetch!(:rel_me_cache, url, fn _ ->
@cachex.fetch!(:rel_me_cache, url, fn _ ->
{:commit, parse_url(url)}
end)
rescue

View File

@ -69,7 +69,7 @@ defmodule Pleroma.Web.RichMedia.Helpers do
def fetch_data_for_activity(%Activity{data: %{"type" => "Create"}} = activity) do
with true <- Config.get([:rich_media, :enabled]),
%Object{} = object <- Object.normalize(activity) do
%Object{} = object <- Object.normalize(activity, fetch: false) do
fetch_data_for_object(object)
else
_ -> %{}

View File

@ -5,6 +5,8 @@
defmodule Pleroma.Web.RichMedia.Parser do
require Logger
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
defp parsers do
Pleroma.Config.get([:rich_media, :parsers])
end
@ -24,7 +26,7 @@ defmodule Pleroma.Web.RichMedia.Parser do
end
defp get_cached_or_parse(url) do
case Cachex.fetch(:rich_media_cache, url, fn ->
case @cachex.fetch(:rich_media_cache, url, fn ->
case parse_url(url) do
{:ok, _} = res ->
{:commit, res}
@ -64,7 +66,7 @@ defmodule Pleroma.Web.RichMedia.Parser do
defp set_error_ttl(url, _reason) do
ttl = Pleroma.Config.get([:rich_media, :failure_backoff], 60_000)
Cachex.expire(:rich_media_cache, url, ttl)
@cachex.expire(:rich_media_cache, url, ttl)
:ok
end
@ -106,7 +108,7 @@ defmodule Pleroma.Web.RichMedia.Parser do
{:ok, ttl} when is_number(ttl) ->
ttl = ttl * 1000
case Cachex.expire_at(:rich_media_cache, url, ttl) do
case @cachex.expire_at(:rich_media_cache, url, ttl) do
{:ok, true} -> {:ok, ttl}
{:ok, false} -> {:error, :no_key}
end

View File

@ -122,7 +122,7 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
end
defp get_counts(%Activity{} = activity) do
%Object{data: data} = Object.normalize(activity)
%Object{data: data} = Object.normalize(activity, fetch: false)
%{
likes: data["like_count"] || 0,

View File

@ -151,7 +151,7 @@ defmodule Pleroma.Web.Streamer do
recipients = MapSet.new(item.recipients)
domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
with parent <- Object.normalize(item) || item,
with parent <- Object.normalize(item, fetch: false) || item,
true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(item.actor not in &1)),
true <- item.data["type"] != "Announce" || item.actor not in reblog_muted_ap_ids,
true <-

View File

@ -126,7 +126,7 @@
<div align="center" class="img-container center"
style="padding-right: 0px;padding-left: 0px;">
<!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr style="line-height:0px"><td style="padding-right: 0px;padding-left: 0px;" align="center"><![endif]--><img
align="center" alt="Image" border="0" class="center" src="cid:logo.png"
align="center" alt="Image" border="0" class="center" src="cid:logo.svg"
style="text-decoration: none; -ms-interpolation-mode: bicubic; border: 0; height: 80px; width: auto; max-height: 80px; display: block;"
title="Image" height="80" />
<!--[if mso]></td></tr></table><![endif]-->

View File

@ -31,10 +31,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
with %User{} = user <- User.get_cached_by_id(uid),
true <- user.local and user.confirmation_pending and user.confirmation_token == token,
{:ok, _} <-
user
|> User.confirmation_changeset(need_confirmation: false)
|> User.update_and_set_cache() do
{:ok, _} <- User.confirm(user) do
redirect(conn, to: "/")
end
end

View File

@ -45,7 +45,6 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
case User.register(changeset) do
{:ok, user} ->
maybe_notify_admins(user)
{:ok, user}
{:error, changeset} ->
@ -58,18 +57,6 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
end
end
defp maybe_notify_admins(%User{} = account) do
if Pleroma.Config.get([:instance, :account_approval_required]) do
User.all_superusers()
|> Enum.filter(fn user -> not is_nil(user.email) end)
|> Enum.each(fn superuser ->
superuser
|> Pleroma.Emails.AdminEmail.new_unapproved_registration(account)
|> Pleroma.Emails.Mailer.deliver_async()
end)
end
end
def password_reset(nickname_or_email) do
with true <- is_binary(nickname_or_email),
%User{local: true, email: email, deactivated: false} = user when is_binary(email) <-

View File

@ -58,12 +58,16 @@ defmodule Pleroma.Web.WebFinger do
] ++ Publisher.gather_webfinger_links(user)
end
defp gather_aliases(%User{} = user) do
[user.ap_id | user.also_known_as]
end
def represent_user(user, "JSON") do
{:ok, user} = User.ensure_keys_present(user)
%{
"subject" => "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}",
"aliases" => [user.ap_id],
"aliases" => gather_aliases(user),
"links" => gather_links(user)
}
end
@ -71,6 +75,11 @@ defmodule Pleroma.Web.WebFinger do
def represent_user(user, "XML") do
{:ok, user} = User.ensure_keys_present(user)
aliases =
user
|> gather_aliases()
|> Enum.map(&{:Alias, &1})
links =
gather_links(user)
|> Enum.map(fn link -> {:Link, link} end)
@ -79,9 +88,8 @@ defmodule Pleroma.Web.WebFinger do
:XRD,
%{xmlns: "http://docs.oasis-open.org/ns/xri/xrd-1.0"},
[
{:Subject, "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}"},
{:Alias, user.ap_id}
] ++ links
{:Subject, "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}"}
] ++ aliases ++ links
}
|> XmlBuilder.to_doc()
end
@ -116,6 +124,9 @@ defmodule Pleroma.Web.WebFinger do
{"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", "self"} ->
Map.put(data, "ap_id", link["href"])
{nil, "http://ostatus.org/schema/1.0/subscribe"} ->
Map.put(data, "subscribe_address", link["template"])
_ ->
Logger.debug("Unhandled type: #{inspect(link["type"])}")
data

10
mix.exs
View File

@ -22,7 +22,7 @@ defmodule Pleroma.Mixfile do
docs: [
source_url_pattern:
"https://git.pleroma.social/pleroma/pleroma/blob/develop/%{path}#L%{line}",
logo: "priv/static/static/logo.png",
logo: "priv/static/images/logo.png",
extras: ["README.md", "CHANGELOG.md"] ++ Path.wildcard("docs/**/*.md"),
groups_for_extras: [
"Installation manuals": Path.wildcard("docs/installation/*.md"),
@ -147,8 +147,8 @@ defmodule Pleroma.Mixfile do
{:earmark, "1.4.3"},
{:bbcode_pleroma, "~> 0.2.0"},
{:crypt,
git: "https://github.com/msantos/crypt.git",
ref: "f63a705f92c26955977ee62a313012e309a4d77a"},
git: "https://git.pleroma.social/pleroma/elixir-libraries/crypt.git",
ref: "cf2aa3f11632e8b0634810a15b3e612c7526f6a3"},
{:cors_plug, "~> 2.0"},
{:web_push_encryption, "~> 0.3"},
{:swoosh, "~> 1.0"},
@ -158,7 +158,7 @@ defmodule Pleroma.Mixfile do
{:floki, "~> 0.27"},
{:timex, "~> 3.6"},
{:ueberauth, "~> 0.4"},
{:linkify, "~> 0.4.0"},
{:linkify, "~> 0.4.1"},
{:http_signatures, "~> 0.1.0"},
{:telemetry, "~> 0.3"},
{:poolboy, "~> 1.5"},
@ -211,7 +211,7 @@ defmodule Pleroma.Mixfile do
git: "https://git.pleroma.social/pleroma/elixir-libraries/hackney.git",
ref: "7d7119f0651515d6d7669c78393fd90950a3ec6e",
override: true},
{:mox, "~> 0.5", only: :test},
{:mox, "~> 1.0", only: :test},
{:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test}
] ++ oauth_deps()
end

View File

@ -22,7 +22,7 @@
"cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"},
"credo": {:hex, :credo, "1.4.1", "16392f1edd2cdb1de9fe4004f5ab0ae612c92e230433968eab00aafd976282fc", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "155f8a2989ad77504de5d8291fa0d41320fdcaa6a1030472e9967f285f8c7692"},
"crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
"crypt": {:git, "https://github.com/msantos/crypt.git", "f63a705f92c26955977ee62a313012e309a4d77a", [ref: "f63a705f92c26955977ee62a313012e309a4d77a"]},
"crypt": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/crypt.git", "cf2aa3f11632e8b0634810a15b3e612c7526f6a3", [ref: "cf2aa3f11632e8b0634810a15b3e612c7526f6a3"]},
"custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},
"db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"},
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
@ -33,7 +33,7 @@
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
"ecto_sql": {:hex, :ecto_sql, "3.4.5", "30161f81b167d561a9a2df4329c10ae05ff36eca7ccc84628f2c8b9fa1e43323", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31990c6a3579b36a3c0841d34a94c275e727de8b84f58509da5f1b2032c98ac2"},
"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.6.1", "8faa29a5597faba999aeeb72bbb9c91694ef8068f0131192fb199f98d32994ef", [:mix], [], "hexpm", "35d33270680f8d839a4003c3e9f43afb595310a592405a00afc12de4c7f55a18"},
"elixir_make": {:hex, :elixir_make, "0.6.2", "7dffacd77dec4c37b39af867cedaabb0b59f6a871f89722c25b28fcd4bd70530", [:mix], [], "hexpm", "03e49eadda22526a7e5279d53321d1cced6552f344ba4e03e619063de75348d9"},
"esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm", "d73e341e3009d390aa36387dc8862860bf9f874c94d9fd92ade2926376f49981"},
"eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm", "b14f1dc204321429479c569cfbe8fb287541184ed040956c8862cb7a677b8406"},
"ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"},
@ -65,7 +65,7 @@
"jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"},
"jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"},
"libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"},
"linkify": {:hex, :linkify, "0.4.0", "7845b6ac33050a41acaf9318923ce6e7f3854418be9a5f22184de103f7a68ff9", [:mix], [], "hexpm", "a0ceb4c78591fecccf1d99fecc10c13dba75a307c663c80e28af9e2cdd9776ee"},
"linkify": {:hex, :linkify, "0.4.1", "f881eb3429ae88010cf736e6fb3eed406c187bcdd544902ec937496636b7c7b3", [:mix], [], "hexpm", "ce98693f54ae9ace59f2f7a8aed3de2ef311381a8ce7794804bd75484c371dda"},
"majic": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/majic.git", "4c692e544b28d1f5e543fb8a44be090f8cd96f80", [ref: "4c692e544b28d1f5e543fb8a44be090f8cd96f80"]},
"makeup": {:hex, :makeup, "1.0.3", "e339e2f766d12e7260e6672dd4047405963c5ec99661abdc432e6ec67d29ef95", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "2e9b4996d11832947731f7608fed7ad2f9443011b3b479ae288011265cdd3dad"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"},
@ -76,7 +76,7 @@
"mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"},
"mock": {:hex, :mock, "0.3.5", "feb81f52b8dcf0a0d65001d2fec459f6b6a8c22562d94a965862f6cc066b5431", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "6fae404799408300f863550392635d8f7e3da6b71abdd5c393faf41b131c8728"},
"mogrify": {:hex, :mogrify, "0.7.4", "9b2496dde44b1ce12676f85d7dc531900939e6367bc537c7243a1b089435b32d", [:mix], [], "hexpm", "50d79e337fba6bc95bfbef918058c90f50b17eed9537771e61d4619488f099c3"},
"mox": {:hex, :mox, "0.5.2", "55a0a5ba9ccc671518d068c8dddd20eeb436909ea79d1799e2209df7eaa98b6c", [:mix], [], "hexpm", "df4310628cd628ee181df93f50ddfd07be3e5ecc30232d3b6aadf30bdfe6092b"},
"mox": {:hex, :mox, "1.0.0", "4b3c7005173f47ff30641ba044eb0fe67287743eec9bd9545e37f3002b0a9f8b", [:mix], [], "hexpm", "201b0a20b7abdaaab083e9cf97884950f8a30a1350a1da403b3145e213c6f4df"},
"myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]},
"nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"},
"nimble_pool": {:hex, :nimble_pool, "0.1.0", "ffa9d5be27eee2b00b0c634eb649aa27f97b39186fec3c493716c2a33e784ec6", [:mix], [], "hexpm", "343a1eaa620ddcf3430a83f39f2af499fe2370390d4f785cd475b4df5acaf3f9"},

View File

@ -0,0 +1,141 @@
## This file is a PO Template file.
msgid "eperm"
msgstr "Operation not permitted"
msgid "eacces"
msgstr "Permission denied"
msgid "eagain"
msgstr "Resource temporarily unavailable"
msgid "ebadf"
msgstr "Bad file descriptor"
msgid "ebadmsg"
msgstr "Bad message"
msgid "ebusy"
msgstr "Device or resource busy"
msgid "edeadlk"
msgstr "Resource deadlock avoided"
msgid "edeadlock"
msgstr "Resource deadlock avoided"
msgid "edquot"
msgstr "Disk quota exceeded"
msgid "eexist"
msgstr "File exists"
msgid "efault"
msgstr "Bad address"
msgid "efbig"
msgstr "File is too large"
msgid "eftype"
msgstr "Inappropriate file type or format"
msgid "eintr"
msgstr "Interrupted system call"
msgid "einval"
msgstr "Invalid argument"
msgid "eio"
msgstr "Input/output error"
msgid "eisdir"
msgstr "Illegal operation on a directory"
msgid "eloop"
msgstr "Too many levels of symbolic links"
msgid "emfile"
msgstr "Too many open files"
msgid "emlink"
msgstr "Too many links"
msgid "emultihop"
msgstr "Multihop attempted"
msgid "enametoolong"
msgstr "File name is too long"
msgid "enfile"
msgstr "Too many open files in system"
msgid "enobufs"
msgstr "No buffer space available"
msgid "enodev"
msgstr "No such device"
msgid "enolck"
msgstr "No locks available"
msgid "enolink"
msgstr "Link has been severed"
msgid "enoent"
msgstr "No such file or directory"
msgid "enomem"
msgstr "Cannot allocate memory"
msgid "enospc"
msgstr "No space left on device"
msgid "enosr"
msgstr "Out of streams resources"
msgid "enostr"
msgstr "Device is not a stream"
msgid "enosys"
msgstr "Function not implemented"
msgid "enotblk"
msgstr "Block device required"
msgid "enotdir"
msgstr "Not a directory"
msgid "enotsup"
msgstr "Operation not supported"
msgid "enxio"
msgstr "No such device or address"
msgid "eopnotsupp"
msgstr "Operation not supported"
msgid "eoverflow"
msgstr "Value too large for defined data type"
msgid "epipe"
msgstr "Broken pipe"
msgid "erange"
msgstr "Numerical result out of range"
msgid "erofs"
msgstr "Read-only file system"
msgid "espipe"
msgstr "Illegal seek"
msgid "esrch"
msgstr "No such process"
msgid "estale"
msgstr "Stale file handle"
msgid "etxtbsy"
msgstr "Text file busy"
msgid "exdev"
msgstr "Invalid cross-device link"

View File

@ -0,0 +1,149 @@
## This file is a PO Template file.
##
## `msgid`s here are often extracted from source code.
## Add new translations manually only if they're dynamic
## translations that can't be statically extracted.
##
## Run `mix gettext.extract` to bring this file up to
## date. Leave `msgstr`s empty as changing them here as no
## effect: edit them in PO (`.po`) files instead.
msgid "eperm"
msgstr ""
msgid "eacces"
msgstr ""
msgid "eagain"
msgstr ""
msgid "ebadf"
msgstr ""
msgid "ebadmsg"
msgstr ""
msgid "ebusy"
msgstr ""
msgid "edeadlk"
msgstr ""
msgid "edeadlock"
msgstr ""
msgid "edquot"
msgstr ""
msgid "eexist"
msgstr ""
msgid "efault"
msgstr ""
msgid "efbig"
msgstr ""
msgid "eftype"
msgstr ""
msgid "eintr"
msgstr ""
msgid "einval"
msgstr ""
msgid "eio"
msgstr ""
msgid "eisdir"
msgstr ""
msgid "eloop"
msgstr ""
msgid "emfile"
msgstr ""
msgid "emlink"
msgstr ""
msgid "emultihop"
msgstr ""
msgid "enametoolong"
msgstr ""
msgid "enfile"
msgstr ""
msgid "enobufs"
msgstr ""
msgid "enodev"
msgstr ""
msgid "enolck"
msgstr ""
msgid "enolink"
msgstr ""
msgid "enoent"
msgstr ""
msgid "enomem"
msgstr ""
msgid "enospc"
msgstr ""
msgid "enosr"
msgstr ""
msgid "enostr"
msgstr ""
msgid "enosys"
msgstr ""
msgid "enotblk"
msgstr ""
msgid "enotdir"
msgstr ""
msgid "enotsup"
msgstr ""
msgid "enxio"
msgstr ""
msgid "eopnotsupp"
msgstr ""
msgid "eoverflow"
msgstr ""
msgid "epipe"
msgstr ""
msgid "erange"
msgstr ""
msgid "erofs"
msgstr ""
msgid "espipe"
msgstr ""
msgid "esrch"
msgstr ""
msgid "estale"
msgstr ""
msgid "etxtbsy"
msgstr ""
msgid "exdev"
msgstr ""

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