Merge remote-tracking branch 'remotes/origin/develop' into 2168-media-preview-proxy

# Conflicts:
#	mix.lock
This commit is contained in:
Ivan Tashkinov 2020-05-14 20:19:56 +03:00
commit e4b12494d7
148 changed files with 3512 additions and 2103 deletions

View file

@ -48,6 +48,7 @@ benchmark:
unit-testing: unit-testing:
stage: test stage: test
retry: 2
cache: &testing_cache_policy cache: &testing_cache_policy
<<: *global_cache_policy <<: *global_cache_policy
policy: pull policy: pull
@ -80,6 +81,7 @@ unit-testing:
unit-testing-rum: unit-testing-rum:
stage: test stage: test
retry: 2
cache: *testing_cache_policy cache: *testing_cache_policy
services: services:
- name: minibikini/postgres-with-rum:12 - name: minibikini/postgres-with-rum:12

View file

@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- NodeInfo: `pleroma:api/v1/notifications:include_types_filter` to the `features` list. - NodeInfo: `pleroma:api/v1/notifications:include_types_filter` to the `features` list.
- NodeInfo: `pleroma_emoji_reactions` to the `features` list. - NodeInfo: `pleroma_emoji_reactions` to the `features` list.
- Configuration: `:restrict_unauthenticated` setting, restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses. - Configuration: `:restrict_unauthenticated` setting, restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses.
- Configuration: Add `:database_config_whitelist` setting to whitelist settings which can be configured from AdminFE.
- New HTTP adapter [gun](https://github.com/ninenines/gun). Gun adapter requires minimum OTP version of 22.2 otherwise Pleroma wont start. For hackney OTP update is not required. - New HTTP adapter [gun](https://github.com/ninenines/gun). Gun adapter requires minimum OTP version of 22.2 otherwise Pleroma wont start. For hackney OTP update is not required.
- Mix task to create trusted OAuth App. - Mix task to create trusted OAuth App.
- Notifications: Added `follow_request` notification type. - Notifications: Added `follow_request` notification type.
@ -40,24 +41,45 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Filtering of push notifications on activities from blocked domains - Filtering of push notifications on activities from blocked domains
- Resolving Peertube accounts with Webfinger - Resolving Peertube accounts with Webfinger
## [unreleased-patch] ## [Unreleased (patch)]
### Fixed
- Healthcheck reporting the number of memory currently used, rather than allocated in total
- `InsertSkeletonsForDeletedUsers` failing on some instances
## [2.0.3] - 2020-05-02
### Security ### Security
- Disallow re-registration of previously deleted users, which allowed viewing direct messages addressed to them - Disallow re-registration of previously deleted users, which allowed viewing direct messages addressed to them
- Mastodon API: Fix `POST /api/v1/follow_requests/:id/authorize` allowing to force a follow from a local user even if they didn't request to follow - Mastodon API: Fix `POST /api/v1/follow_requests/:id/authorize` allowing to force a follow from a local user even if they didn't request to follow
- CSP: Sandbox uploads
### Fixed ### Fixed
- Logger configuration through AdminFE - Notifications from blocked domains
- Potential federation issues with Mastodon versions before 3.0.0
- HTTP Basic Authentication permissions issue - HTTP Basic Authentication permissions issue
- Follow/Block imports not being able to find the user if the nickname started with an `@`
- Instance stats counting internal users
- Inability to run a From Source release without git
- ObjectAgePolicy didn't filter out old messages - ObjectAgePolicy didn't filter out old messages
- Transmogrifier: Keep object sensitive settings for outgoing representation (AP C2S) - `blob:` urls not being allowed by CSP
### Added ### Added
- NodeInfo: ObjectAgePolicy settings to the `federation` list. - NodeInfo: ObjectAgePolicy settings to the `federation` list.
- Follow request notifications
<details> <details>
<summary>API Changes</summary> <summary>API Changes</summary>
- Admin API: `GET /api/pleroma/admin/need_reboot`. - Admin API: `GET /api/pleroma/admin/need_reboot`.
</details> </details>
### Upgrade notes
1. Restart Pleroma
2. Run database migrations (inside Pleroma directory):
- OTP: `./bin/pleroma_ctl migrate`
- From Source: `mix ecto.migrate`
## [2.0.2] - 2020-04-08 ## [2.0.2] - 2020-04-08
### Added ### Added
- Support for Funkwhale's `Audio` activity - Support for Funkwhale's `Audio` activity

View file

@ -55,7 +55,7 @@ defp generate_user(i) do
name: "Test テスト User #{i}", name: "Test テスト User #{i}",
email: "user#{i}@example.com", email: "user#{i}@example.com",
nickname: "nick#{i}", nickname: "nick#{i}",
password_hash: Comeonin.Pbkdf2.hashpwsalt("test"), password_hash: Pbkdf2.hash_pwd_salt("test"),
bio: "Tester Number #{i}", bio: "Tester Number #{i}",
local: !remote local: !remote
} }

View file

@ -28,7 +28,8 @@
%{ %{
key: :filters, key: :filters,
type: {:list, :module}, type: {:list, :module},
description: "List of filter modules for uploads", description:
"List of filter modules for uploads. Module names are shortened (removed leading `Pleroma.Upload.Filter.` part), but on adding custom module you need to use full name.",
suggestions: suggestions:
Generator.list_modules_in_dir( Generator.list_modules_in_dir(
"lib/pleroma/upload/filter", "lib/pleroma/upload/filter",
@ -681,7 +682,8 @@
%{ %{
key: :federation_publisher_modules, key: :federation_publisher_modules,
type: {:list, :module}, type: {:list, :module},
description: "List of modules for federation publishing", description:
"List of modules for federation publishing. Module names are shortened (removed leading `Pleroma.Web.` part), but on adding custom module you need to use full name.",
suggestions: [ suggestions: [
Pleroma.Web.ActivityPub.Publisher Pleroma.Web.ActivityPub.Publisher
] ]
@ -694,7 +696,8 @@
%{ %{
key: :rewrite_policy, key: :rewrite_policy,
type: [:module, {:list, :module}], type: [:module, {:list, :module}],
description: "A list of MRF policies enabled", description:
"A list of enabled MRF policies. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name.",
suggestions: suggestions:
Generator.list_modules_in_dir( Generator.list_modules_in_dir(
"lib/pleroma/web/activity_pub/mrf", "lib/pleroma/web/activity_pub/mrf",
@ -2031,7 +2034,8 @@
%{ %{
key: :parsers, key: :parsers,
type: {:list, :module}, type: {:list, :module},
description: "List of Rich Media parsers.", description:
"List of Rich Media parsers. Module names are shortened (removed leading `Pleroma.Web.RichMedia.Parsers.` part), but on adding custom module you need to use full name.",
suggestions: [ suggestions: [
Pleroma.Web.RichMedia.Parsers.MetaTagsParser, Pleroma.Web.RichMedia.Parsers.MetaTagsParser,
Pleroma.Web.RichMedia.Parsers.OEmbed, Pleroma.Web.RichMedia.Parsers.OEmbed,
@ -2043,7 +2047,8 @@
key: :ttl_setters, key: :ttl_setters,
label: "TTL setters", label: "TTL setters",
type: {:list, :module}, type: {:list, :module},
description: "List of rich media TTL setters.", description:
"List of rich media TTL setters. Module names are shortened (removed leading `Pleroma.Web.RichMedia.Parser.` part), but on adding custom module you need to use full name.",
suggestions: [ suggestions: [
Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl
] ]
@ -2717,6 +2722,8 @@
%{ %{
key: :scrub_policy, key: :scrub_policy,
type: {:list, :module}, type: {:list, :module},
description:
"Module names are shortened (removed leading `Pleroma.HTML.` part), but on adding custom module you need to use full name.",
suggestions: [Pleroma.HTML.Transform.MediaProxy, Pleroma.HTML.Scrubber.Default] suggestions: [Pleroma.HTML.Transform.MediaProxy, Pleroma.HTML.Scrubber.Default]
} }
] ]

View file

@ -911,6 +911,21 @@ config :auto_linker,
Boolean, enables/disables in-database configuration. Read [Transfering the config to/from the database](../administration/CLI_tasks/config.md) for more information. Boolean, enables/disables in-database configuration. Read [Transfering the config to/from the database](../administration/CLI_tasks/config.md) for more information.
## :database_config_whitelist
List of valid configuration sections which are allowed to be configured from the
database. Settings stored in the database before the whitelist is configured are
still applied, so it is suggested to only use the whitelist on instances that
have not migrated the config to the database.
Example:
```elixir
config :pleroma, :database_config_whitelist, [
{:pleroma, :instance},
{:pleroma, Pleroma.Web.Metadata},
{:auto_linker}
]
```
### Multi-factor authentication - :two_factor_authentication ### Multi-factor authentication - :two_factor_authentication
* `totp` - a list containing TOTP configuration * `totp` - a list containing TOTP configuration

View file

@ -1,21 +1,45 @@
#!/sbin/openrc-run #!/sbin/openrc-run
supervisor=supervise-daemon
# Requires OpenRC >= 0.35
directory=/opt/pleroma
command=/usr/bin/mix
command_args="phx.server"
command_user=pleroma:pleroma command_user=pleroma:pleroma
command_background=1 command_background=1
export PORT=4000
export MIX_ENV=prod
# Ask process to terminate within 30 seconds, otherwise kill it # Ask process to terminate within 30 seconds, otherwise kill it
retry="SIGTERM/30/SIGKILL/5" retry="SIGTERM/30/SIGKILL/5"
pidfile="/var/run/pleroma.pid" pidfile="/var/run/pleroma.pid"
directory=/opt/pleroma
healthcheck_delay=60
healthcheck_timer=30
: ${pleroma_port:-4000}
# Needs OpenRC >= 0.42
#respawn_max=0
#respawn_delay=5
# put pleroma_console=YES in /etc/conf.d/pleroma if you want to be able to
# connect to pleroma via an elixir console
if yesno "${pleroma_console}"; then
command=elixir
command_args="--name pleroma@127.0.0.1 --erl '-kernel inet_dist_listen_min 9001 inet_dist_listen_max 9001 inet_dist_use_interface {127,0,0,1}' -S mix phx.server"
start_post() {
einfo "You can get a console by using this command as pleroma's user:"
einfo "iex --name console@127.0.0.1 --remsh pleroma@127.0.0.1"
}
else
command=/usr/bin/mix
command_args="phx.server"
fi
export MIX_ENV=prod
depend() { depend() {
need nginx postgresql need nginx postgresql
}
healthcheck() {
# put pleroma_health=YES in /etc/conf.d/pleroma if you want healthchecking
# and make sure you have curl installed
yesno "$pleroma_health" || return 0
curl -q "localhost:${pleroma_port}/api/pleroma/healthcheck"
} }

View file

@ -56,7 +56,7 @@ def start(_type, _args) do
if (major == 22 and minor < 2) or major < 22 do if (major == 22 and minor < 2) or major < 22 do
raise " raise "
!!!OTP VERSION WARNING!!! !!!OTP VERSION WARNING!!!
You are using gun adapter with OTP version #{version}, which doesn't support correct handling of unordered certificates chains. You are using gun adapter with OTP version #{version}, which doesn't support correct handling of unordered certificates chains. Please update your Erlang/OTP to at least 22.2.
" "
end end
else else

View file

@ -4,7 +4,7 @@
defmodule Pleroma.BBS.Authenticator do defmodule Pleroma.BBS.Authenticator do
use Sshd.PasswordAuthenticator use Sshd.PasswordAuthenticator
alias Comeonin.Pbkdf2 alias Pleroma.Plugs.AuthenticationPlug
alias Pleroma.User alias Pleroma.User
def authenticate(username, password) do def authenticate(username, password) do
@ -12,7 +12,7 @@ def authenticate(username, password) do
password = to_string(password) password = to_string(password)
with %User{} = user <- User.get_by_nickname(username) do with %User{} = user <- User.get_by_nickname(username) do
Pbkdf2.checkpw(password, user.password_hash) AuthenticationPlug.checkpw(password, user.password_hash)
else else
_e -> false _e -> false
end end

View file

@ -66,7 +66,7 @@ def handle_command(%{user: user} = state, "r " <> text) do
with %Activity{} <- Activity.get_by_id(activity_id), with %Activity{} <- Activity.get_by_id(activity_id),
{:ok, _activity} <- {:ok, _activity} <-
CommonAPI.post(user, %{"status" => rest, "in_reply_to_status_id" => activity_id}) do CommonAPI.post(user, %{status: rest, in_reply_to_status_id: activity_id}) do
IO.puts("Replied!") IO.puts("Replied!")
else else
_e -> IO.puts("Could not reply...") _e -> IO.puts("Could not reply...")
@ -78,7 +78,7 @@ def handle_command(%{user: user} = state, "r " <> text) do
def handle_command(%{user: user} = state, "p " <> text) do def handle_command(%{user: user} = state, "p " <> text) do
text = String.trim(text) text = String.trim(text)
with {:ok, _activity} <- CommonAPI.post(user, %{"status" => text}) do with {:ok, _activity} <- CommonAPI.post(user, %{status: text}) do
IO.puts("Posted!") IO.puts("Posted!")
else else
_e -> IO.puts("Could not post...") _e -> IO.puts("Could not post...")

View file

@ -17,7 +17,8 @@ defmodule Pleroma.Constants do
"announcement_count", "announcement_count",
"emoji", "emoji",
"context_id", "context_id",
"deleted_activity_id" "deleted_activity_id",
"pleroma_internal"
] ]
) )

View file

@ -18,7 +18,6 @@ def compile do
with config <- Pleroma.Config.Loader.read("config/description.exs") do with config <- Pleroma.Config.Loader.read("config/description.exs") do
config[:pleroma][:config_description] config[:pleroma][:config_description]
|> Pleroma.Docs.Generator.convert_to_strings() |> Pleroma.Docs.Generator.convert_to_strings()
|> Jason.encode!()
end end
end end
end end

View file

@ -29,7 +29,7 @@ defmodule Pleroma.Healthcheck do
@spec system_info() :: t() @spec system_info() :: t()
def system_info do def system_info do
%Healthcheck{ %Healthcheck{
memory_used: Float.round(:erlang.memory(:total) / 1024 / 1024, 2) memory_used: Float.round(:recon_alloc.memory(:allocated) / 1024 / 1024, 2)
} }
|> assign_db_info() |> assign_db_info()
|> assign_job_queue_stats() |> assign_job_queue_stats()

View file

@ -7,7 +7,6 @@ defmodule Pleroma.MFA do
The MFA context. The MFA context.
""" """
alias Comeonin.Pbkdf2
alias Pleroma.User alias Pleroma.User
alias Pleroma.MFA.BackupCodes alias Pleroma.MFA.BackupCodes
@ -72,7 +71,7 @@ def invalidate_backup_code(%User{} = user, hash_code) do
@spec generate_backup_codes(User.t()) :: {:ok, list(binary)} | {:error, String.t()} @spec generate_backup_codes(User.t()) :: {:ok, list(binary)} | {:error, String.t()}
def generate_backup_codes(%User{} = user) do def generate_backup_codes(%User{} = user) do
with codes <- BackupCodes.generate(), with codes <- BackupCodes.generate(),
hashed_codes <- Enum.map(codes, &Pbkdf2.hashpwsalt/1), hashed_codes <- Enum.map(codes, &Pbkdf2.hash_pwd_salt/1),
changeset <- Changeset.cast_backup_codes(user, hashed_codes), changeset <- Changeset.cast_backup_codes(user, hashed_codes),
{:ok, _} <- User.update_and_set_cache(changeset) do {:ok, _} <- User.update_and_set_cache(changeset) do
{:ok, codes} {:ok, codes}

View file

@ -3,7 +3,6 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Plugs.AuthenticationPlug do defmodule Pleroma.Plugs.AuthenticationPlug do
alias Comeonin.Pbkdf2
alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User alias Pleroma.User
@ -17,8 +16,13 @@ def checkpw(password, "$6" <> _ = password_hash) do
:crypt.crypt(password, password_hash) == password_hash :crypt.crypt(password, password_hash) == password_hash
end end
def checkpw(password, "$2" <> _ = password_hash) do
# Handle bcrypt passwords for Mastodon migration
Bcrypt.verify_pass(password, password_hash)
end
def checkpw(password, "$pbkdf2" <> _ = password_hash) do def checkpw(password, "$pbkdf2" <> _ = password_hash) do
Pbkdf2.checkpw(password, password_hash) Pbkdf2.verify_pass(password, password_hash)
end end
def checkpw(_password, _password_hash) do def checkpw(_password, _password_hash) do
@ -37,7 +41,7 @@ def call(
} = conn, } = conn,
_ _
) do ) do
if Pbkdf2.checkpw(password, password_hash) do if checkpw(password, password_hash) do
conn conn
|> assign(:user, auth_user) |> assign(:user, auth_user)
|> OAuthScopesPlug.skip_plug() |> OAuthScopesPlug.skip_plug()
@ -47,7 +51,7 @@ def call(
end end
def call(%{assigns: %{auth_credentials: %{password: _}}} = conn, _) do def call(%{assigns: %{auth_credentials: %{password: _}}} = conn, _) do
Pbkdf2.dummy_checkpw() Pbkdf2.no_user_verify()
conn conn
end end

View file

@ -40,7 +40,7 @@ defp with_media_attachments(
%{changes: %{params: %{"media_ids" => media_ids} = params}} = changeset %{changes: %{params: %{"media_ids" => media_ids} = params}} = changeset
) )
when is_list(media_ids) do when is_list(media_ids) do
media_attachments = Utils.attachments_from_ids(%{"media_ids" => media_ids}) media_attachments = Utils.attachments_from_ids(%{media_ids: media_ids})
params = params =
params params

View file

@ -134,7 +134,7 @@ defp prepare_upload(%Plug.Upload{} = file, opts) do
end end
end end
defp prepare_upload(%{"img" => "data:image/" <> image_data}, opts) do defp prepare_upload(%{img: "data:image/" <> image_data}, opts) do
parsed = Regex.named_captures(~r/(?<filetype>jpeg|png|gif);base64,(?<data>.*)/, image_data) parsed = Regex.named_captures(~r/(?<filetype>jpeg|png|gif);base64,(?<data>.*)/, image_data)
data = Base.decode64!(parsed["data"], ignore: :whitespace) data = Base.decode64!(parsed["data"], ignore: :whitespace)
hash = String.downcase(Base.encode16(:crypto.hash(:sha256, data))) hash = String.downcase(Base.encode16(:crypto.hash(:sha256, data)))

View file

@ -9,7 +9,6 @@ defmodule Pleroma.User do
import Ecto.Query import Ecto.Query
import Ecto, only: [assoc: 2] import Ecto, only: [assoc: 2]
alias Comeonin.Pbkdf2
alias Ecto.Multi alias Ecto.Multi
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Config alias Pleroma.Config
@ -1554,10 +1553,23 @@ def delete_user_activities(%User{ap_id: ap_id} = user) do
|> Stream.run() |> Stream.run()
end end
defp delete_activity(%{data: %{"type" => "Create", "object" => object}}, user) do defp delete_activity(%{data: %{"type" => "Create", "object" => object}} = activity, user) do
{:ok, delete_data, _} = Builder.delete(user, object) with {_, %Object{}} <- {:find_object, Object.get_by_ap_id(object)},
{:ok, delete_data, _} <- Builder.delete(user, object) do
Pipeline.common_pipeline(delete_data, local: user.local)
else
{:find_object, nil} ->
# We have the create activity, but not the object, it was probably pruned.
# Insert a tombstone and try again
with {:ok, tombstone_data, _} <- Builder.tombstone(user.ap_id, object),
{:ok, _tombstone} <- Object.create(tombstone_data) do
delete_activity(activity, user)
end
Pipeline.common_pipeline(delete_data, local: user.local) e ->
Logger.error("Could not delete #{object} created by #{activity.data["ap_id"]}")
Logger.error("Error: #{inspect(e)}")
end
end end
defp delete_activity(%{data: %{"type" => type}} = activity, user) defp delete_activity(%{data: %{"type" => type}} = activity, user)
@ -1913,7 +1925,7 @@ def get_ap_ids_by_nicknames(nicknames) do
defp put_password_hash( defp put_password_hash(
%Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
) do ) do
change(changeset, password_hash: Pbkdf2.hashpwsalt(password)) change(changeset, password_hash: Pbkdf2.hash_pwd_salt(password))
end end
defp put_password_hash(changeset), do: changeset defp put_password_hash(changeset), do: changeset

View file

@ -10,8 +10,8 @@ def post_welcome_message_to_user(user) do
with %User{} = sender_user <- welcome_user(), with %User{} = sender_user <- welcome_user(),
message when is_binary(message) <- welcome_message() do message when is_binary(message) <- welcome_message() do
CommonAPI.post(sender_user, %{ CommonAPI.post(sender_user, %{
"visibility" => "direct", visibility: "direct",
"status" => "@#{user.nickname}\n#{message}" status: "@#{user.nickname}\n#{message}"
}) })
else else
_ -> {:ok, nil} _ -> {:ok, nil}

View file

@ -439,7 +439,6 @@ def block(blocker, blocked, activity_id \\ nil, local \\ true) do
end end
defp do_block(blocker, blocked, activity_id, local) do defp do_block(blocker, blocked, activity_id, local) do
outgoing_blocks = Config.get([:activitypub, :outgoing_blocks])
unfollow_blocked = Config.get([:activitypub, :unfollow_blocked]) unfollow_blocked = Config.get([:activitypub, :unfollow_blocked])
if unfollow_blocked do if unfollow_blocked do
@ -447,8 +446,7 @@ defp do_block(blocker, blocked, activity_id, local) do
if follow_activity, do: unfollow(blocker, blocked, nil, local) if follow_activity, do: unfollow(blocker, blocked, nil, local)
end end
with true <- outgoing_blocks, with block_data <- make_block_data(blocker, blocked, activity_id),
block_data <- make_block_data(blocker, blocked, activity_id),
{:ok, activity} <- insert(block_data, local), {:ok, activity} <- insert(block_data, local),
_ <- notify_and_stream(activity), _ <- notify_and_stream(activity),
:ok <- maybe_federate(activity) do :ok <- maybe_federate(activity) do

View file

@ -62,6 +62,16 @@ def delete(actor, object_id) do
}, []} }, []}
end end
@spec tombstone(String.t(), String.t()) :: {:ok, map(), keyword()}
def tombstone(actor, id) do
{:ok,
%{
"id" => id,
"actor" => actor,
"type" => "Tombstone"
}, []}
end
@spec like(User.t(), Object.t()) :: {:ok, map(), keyword()} @spec like(User.t(), Object.t()) :: {:ok, map(), keyword()}
def like(actor, object) do def like(actor, object) do
with {:ok, data, meta} <- object_action(actor, object) do with {:ok, data, meta} <- object_action(actor, object) do

View file

@ -51,6 +51,7 @@ def add_deleted_activity_id(cng) do
Page Page
Question Question
Video Video
Tombstone
} }
def validate_data(cng) do def validate_data(cng) do
cng cng

View file

@ -14,7 +14,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.ObjectValidator alias Pleroma.Web.ActivityPub.ObjectValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
alias Pleroma.Web.ActivityPub.Pipeline alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.ActivityPub.Visibility
@ -590,6 +592,9 @@ def handle_incoming(
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"), {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]), %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept) do {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept) do
User.update_follower_count(followed)
User.update_following_count(follower)
ActivityPub.accept(%{ ActivityPub.accept(%{
to: follow_activity.data["to"], to: follow_activity.data["to"],
type: "Accept", type: "Accept",
@ -599,7 +604,8 @@ def handle_incoming(
activity_id: id activity_id: id
}) })
else else
_e -> :error _e ->
:error
end end
end end
@ -720,6 +726,19 @@ def handle_incoming(
) do ) do
with {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do with {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
{:ok, activity} {:ok, activity}
else
{:error, {:validate_object, _}} = e ->
# Check if we have a create activity for this
with {:ok, object_id} <- Types.ObjectID.cast(data["object"]),
%Activity{data: %{"actor" => actor}} <-
Activity.create_by_object_ap_id(object_id) |> Repo.one(),
# We have one, insert a tombstone and retry
{:ok, tombstone_data, _} <- Builder.tombstone(actor, object_id),
{:ok, _tombstone} <- Object.create(tombstone_data) do
handle_incoming(data)
else
_ -> e
end
end end
end end

View file

@ -6,6 +6,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
alias Ecto.Changeset alias Ecto.Changeset
alias Ecto.UUID alias Ecto.UUID
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Repo alias Pleroma.Repo
@ -169,8 +170,11 @@ def create_context(context) do
Enqueues an activity for federation if it's local Enqueues an activity for federation if it's local
""" """
@spec maybe_federate(any()) :: :ok @spec maybe_federate(any()) :: :ok
def maybe_federate(%Activity{local: true} = activity) do def maybe_federate(%Activity{local: true, data: %{"type" => type}} = activity) do
if Pleroma.Config.get!([:instance, :federating]) do outgoing_blocks = Config.get([:activitypub, :outgoing_blocks])
with true <- Config.get!([:instance, :federating]),
true <- type != "Block" || outgoing_blocks do
Pleroma.Web.Federator.publish(activity) Pleroma.Web.Federator.publish(activity)
end end

View file

@ -37,7 +37,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
require Logger require Logger
@descriptions_json Pleroma.Docs.JSON.compile() @descriptions Pleroma.Docs.JSON.compile()
@users_page_size 50 @users_page_size 50
plug( plug(
@ -844,15 +844,20 @@ def status_show(conn, %{"id" => id}) do
end end
def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
params =
params
|> Map.take(["sensitive", "visibility"])
|> Map.new(fn {key, value} -> {String.to_existing_atom(key), value} end)
with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
{:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"]) {:ok, sensitive} = Ecto.Type.cast(:boolean, params[:sensitive])
ModerationLog.insert_log(%{ ModerationLog.insert_log(%{
action: "status_update", action: "status_update",
actor: admin, actor: admin,
subject: activity, subject: activity,
sensitive: sensitive, sensitive: sensitive,
visibility: params["visibility"] visibility: params[:visibility]
}) })
conn conn
@ -892,9 +897,9 @@ def list_log(conn, params) do
end end
def config_descriptions(conn, _params) do def config_descriptions(conn, _params) do
conn descriptions = Enum.filter(@descriptions, &whitelisted_config?/1)
|> Plug.Conn.put_resp_content_type("application/json")
|> Plug.Conn.send_resp(200, @descriptions_json) json(conn, descriptions)
end end
def config_show(conn, %{"only_db" => true}) do def config_show(conn, %{"only_db" => true}) do
@ -949,7 +954,9 @@ def config_show(conn, _params) do
def config_update(conn, %{"configs" => configs}) do def config_update(conn, %{"configs" => configs}) do
with :ok <- configurable_from_database(conn) do with :ok <- configurable_from_database(conn) do
{_errors, results} = {_errors, results} =
Enum.map(configs, fn configs
|> Enum.filter(&whitelisted_config?/1)
|> Enum.map(fn
%{"group" => group, "key" => key, "delete" => true} = params -> %{"group" => group, "key" => key, "delete" => true} = params ->
ConfigDB.delete(%{group: group, key: key, subkeys: params["subkeys"]}) ConfigDB.delete(%{group: group, key: key, subkeys: params["subkeys"]})
@ -1011,6 +1018,28 @@ defp configurable_from_database(conn) do
end end
end end
defp whitelisted_config?(group, key) do
if whitelisted_configs = Config.get(:database_config_whitelist) do
Enum.any?(whitelisted_configs, fn
{whitelisted_group} ->
group == inspect(whitelisted_group)
{whitelisted_group, whitelisted_key} ->
group == inspect(whitelisted_group) && key == inspect(whitelisted_key)
end)
else
true
end
end
defp whitelisted_config?(%{"group" => group, "key" => key}) do
whitelisted_config?(group, key)
end
defp whitelisted_config?(%{:group => group} = config) do
whitelisted_config?(group, config[:key])
end
def reload_emoji(conn, _params) do def reload_emoji(conn, _params) do
Pleroma.Emoji.reload() Pleroma.Emoji.reload()

View file

@ -54,4 +54,8 @@ def empty_object_response do
def empty_array_response do def empty_array_response do
Operation.response("Empty array", "application/json", %Schema{type: :array, example: []}) Operation.response("Empty array", "application/json", %Schema{type: :array, example: []})
end end
def no_content_response do
Operation.response("No Content", "application/json", %Schema{type: :string, example: ""})
end
end end

View file

@ -0,0 +1,185 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
alias Pleroma.Web.ApiSpec.Schemas.ApiError
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
alias Pleroma.Web.ApiSpec.StatusOperation
import Pleroma.Web.ApiSpec.Helpers
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
def confirmation_resend_operation do
%Operation{
tags: ["Accounts"],
summary: "Resend confirmation email. Expects `email` or `nickname`",
operationId: "PleromaAPI.AccountController.confirmation_resend",
parameters: [
Operation.parameter(:email, :query, :string, "Email of that needs to be verified",
example: "cofe@cofe.io"
),
Operation.parameter(
:nickname,
:query,
:string,
"Nickname of user that needs to be verified",
example: "cofefe"
)
],
responses: %{
204 => no_content_response()
}
}
end
def update_avatar_operation do
%Operation{
tags: ["Accounts"],
summary: "Set/clear user avatar image",
operationId: "PleromaAPI.AccountController.update_avatar",
requestBody:
request_body("Parameters", update_avatar_or_background_request(), required: true),
security: [%{"oAuth" => ["write:accounts"]}],
responses: %{
200 => update_response(),
403 => Operation.response("Forbidden", "application/json", ApiError)
}
}
end
def update_banner_operation do
%Operation{
tags: ["Accounts"],
summary: "Set/clear user banner image",
operationId: "PleromaAPI.AccountController.update_banner",
requestBody: request_body("Parameters", update_banner_request(), required: true),
security: [%{"oAuth" => ["write:accounts"]}],
responses: %{
200 => update_response()
}
}
end
def update_background_operation do
%Operation{
tags: ["Accounts"],
summary: "Set/clear user background image",
operationId: "PleromaAPI.AccountController.update_background",
security: [%{"oAuth" => ["write:accounts"]}],
requestBody:
request_body("Parameters", update_avatar_or_background_request(), required: true),
responses: %{
200 => update_response()
}
}
end
def favourites_operation do
%Operation{
tags: ["Accounts"],
summary: "Returns favorites timeline of any user",
operationId: "PleromaAPI.AccountController.favourites",
parameters: [id_param() | pagination_params()],
security: [%{"oAuth" => ["read:favourites"]}],
responses: %{
200 =>
Operation.response(
"Array of Statuses",
"application/json",
StatusOperation.array_of_statuses()
),
403 => Operation.response("Forbidden", "application/json", ApiError),
404 => Operation.response("Not Found", "application/json", ApiError)
}
}
end
def subscribe_operation do
%Operation{
tags: ["Accounts"],
summary: "Subscribe to receive notifications for all statuses posted by a user",
operationId: "PleromaAPI.AccountController.subscribe",
parameters: [id_param()],
security: [%{"oAuth" => ["follow", "write:follows"]}],
responses: %{
200 => Operation.response("Relationship", "application/json", AccountRelationship),
404 => Operation.response("Not Found", "application/json", ApiError)
}
}
end
def unsubscribe_operation do
%Operation{
tags: ["Accounts"],
summary: "Unsubscribe to stop receiving notifications from user statuses",
operationId: "PleromaAPI.AccountController.unsubscribe",
parameters: [id_param()],
security: [%{"oAuth" => ["follow", "write:follows"]}],
responses: %{
200 => Operation.response("Relationship", "application/json", AccountRelationship),
404 => Operation.response("Not Found", "application/json", ApiError)
}
}
end
defp id_param do
Operation.parameter(:id, :path, FlakeID, "Account ID",
example: "9umDrYheeY451cQnEe",
required: true
)
end
defp update_avatar_or_background_request do
%Schema{
title: "PleromaAccountUpdateAvatarOrBackgroundRequest",
type: :object,
properties: %{
img: %Schema{
type: :string,
format: :binary,
description: "Image encoded using `multipart/form-data` or an empty string to clear"
}
}
}
end
defp update_banner_request do
%Schema{
title: "PleromaAccountUpdateBannerRequest",
type: :object,
properties: %{
banner: %Schema{
type: :string,
format: :binary,
description: "Image encoded using `multipart/form-data` or an empty string to clear"
}
}
}
end
defp update_response do
Operation.response("PleromaAccountUpdateResponse", "application/json", %Schema{
type: :object,
properties: %{
url: %Schema{
type: :string,
format: :uri,
nullable: true,
description: "Image URL"
}
},
example: %{
"url" =>
"https://cofe.party/media/9d0add56-bcb6-4c0f-8225-cbbd0b6dd773/13eadb6972c9ccd3f4ffa3b8196f0e0d38b4d2f27594457c52e52946c054cd9a.gif"
}
})
end
end

View file

@ -0,0 +1,499 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.StatusOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.AccountOperation
alias Pleroma.Web.ApiSpec.Schemas.ApiError
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
alias Pleroma.Web.ApiSpec.Schemas.ScheduledStatus
alias Pleroma.Web.ApiSpec.Schemas.Status
alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
import Pleroma.Web.ApiSpec.Helpers
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
def index_operation do
%Operation{
tags: ["Statuses"],
summary: "Get multiple statuses by IDs",
security: [%{"oAuth" => ["read:statuses"]}],
parameters: [
Operation.parameter(
:ids,
:query,
%Schema{type: :array, items: FlakeID},
"Array of status IDs"
)
],
operationId: "StatusController.index",
responses: %{
200 => Operation.response("Array of Status", "application/json", array_of_statuses())
}
}
end
def create_operation do
%Operation{
tags: ["Statuses"],
summary: "Publish new status",
security: [%{"oAuth" => ["write:statuses"]}],
description: "Post a new status",
operationId: "StatusController.create",
requestBody: request_body("Parameters", create_request(), required: true),
responses: %{
200 =>
Operation.response(
"Status. When `scheduled_at` is present, ScheduledStatus is returned instead",
"application/json",
%Schema{oneOf: [Status, ScheduledStatus]}
),
422 => Operation.response("Bad Request", "application/json", ApiError)
}
}
end
def show_operation do
%Operation{
tags: ["Statuses"],
summary: "View specific status",
description: "View information about a status",
operationId: "StatusController.show",
security: [%{"oAuth" => ["read:statuses"]}],
parameters: [id_param()],
responses: %{
200 => status_response(),
404 => Operation.response("Not Found", "application/json", ApiError)
}
}
end
def delete_operation do
%Operation{
tags: ["Statuses"],
summary: "Delete status",
security: [%{"oAuth" => ["write:statuses"]}],
description: "Delete one of your own statuses",
operationId: "StatusController.delete",
parameters: [id_param()],
responses: %{
200 => empty_object_response(),
403 => Operation.response("Forbidden", "application/json", ApiError),
404 => Operation.response("Not Found", "application/json", ApiError)
}
}
end
def reblog_operation do
%Operation{
tags: ["Statuses"],
summary: "Boost",
security: [%{"oAuth" => ["write:statuses"]}],
description: "Share a status",
operationId: "StatusController.reblog",
parameters: [id_param()],
requestBody:
request_body("Parameters", %Schema{
type: :object,
properties: %{
visibility: %Schema{allOf: [VisibilityScope], default: "public"}
}
}),
responses: %{
200 => status_response(),
404 => Operation.response("Not Found", "application/json", ApiError)
}
}
end
def unreblog_operation do
%Operation{
tags: ["Statuses"],
summary: "Undo boost",
security: [%{"oAuth" => ["write:statuses"]}],
description: "Undo a reshare of a status",
operationId: "StatusController.unreblog",
parameters: [id_param()],
responses: %{
200 => status_response(),
404 => Operation.response("Not Found", "application/json", ApiError)
}
}
end
def favourite_operation do
%Operation{
tags: ["Statuses"],
summary: "Favourite",
security: [%{"oAuth" => ["write:favourites"]}],
description: "Add a status to your favourites list",
operationId: "StatusController.favourite",
parameters: [id_param()],
responses: %{
200 => status_response(),
404 => Operation.response("Not Found", "application/json", ApiError)
}
}
end
def unfavourite_operation do
%Operation{
tags: ["Statuses"],
summary: "Undo favourite",
security: [%{"oAuth" => ["write:favourites"]}],
description: "Remove a status from your favourites list",
operationId: "StatusController.unfavourite",
parameters: [id_param()],
responses: %{
200 => status_response(),
404 => Operation.response("Not Found", "application/json", ApiError)
}
}
end
def pin_operation do
%Operation{
tags: ["Statuses"],
summary: "Pin to profile",
security: [%{"oAuth" => ["write:accounts"]}],
description: "Feature one of your own public statuses at the top of your profile",
operationId: "StatusController.pin",
parameters: [id_param()],
responses: %{
200 => status_response(),
400 => Operation.response("Error", "application/json", ApiError)
}
}
end
def unpin_operation do
%Operation{
tags: ["Statuses"],
summary: "Unpin to profile",
security: [%{"oAuth" => ["write:accounts"]}],
description: "Unfeature a status from the top of your profile",
operationId: "StatusController.unpin",
parameters: [id_param()],
responses: %{
200 => status_response(),
400 => Operation.response("Error", "application/json", ApiError)
}
}
end
def bookmark_operation do
%Operation{
tags: ["Statuses"],
summary: "Bookmark",
security: [%{"oAuth" => ["write:bookmarks"]}],
description: "Privately bookmark a status",
operationId: "StatusController.bookmark",
parameters: [id_param()],
responses: %{
200 => status_response()
}
}
end
def unbookmark_operation do
%Operation{
tags: ["Statuses"],
summary: "Undo bookmark",
security: [%{"oAuth" => ["write:bookmarks"]}],
description: "Remove a status from your private bookmarks",
operationId: "StatusController.unbookmark",
parameters: [id_param()],
responses: %{
200 => status_response()
}
}
end
def mute_conversation_operation do
%Operation{
tags: ["Statuses"],
summary: "Mute conversation",
security: [%{"oAuth" => ["write:mutes"]}],
description: "Do not receive notifications for the thread that this status is part of.",
operationId: "StatusController.mute_conversation",
parameters: [id_param()],
responses: %{
200 => status_response(),
400 => Operation.response("Error", "application/json", ApiError)
}
}
end
def unmute_conversation_operation do
%Operation{
tags: ["Statuses"],
summary: "Unmute conversation",
security: [%{"oAuth" => ["write:mutes"]}],
description:
"Start receiving notifications again for the thread that this status is part of",
operationId: "StatusController.unmute_conversation",
parameters: [id_param()],
responses: %{
200 => status_response(),
400 => Operation.response("Error", "application/json", ApiError)
}
}
end
def card_operation do
%Operation{
tags: ["Statuses"],
deprecated: true,
summary: "Preview card",
description: "Deprecated in favor of card property inlined on Status entity",
operationId: "StatusController.card",
parameters: [id_param()],
security: [%{"oAuth" => ["read:statuses"]}],
responses: %{
200 =>
Operation.response("Card", "application/json", %Schema{
type: :object,
nullable: true,
properties: %{
type: %Schema{type: :string, enum: ["link", "photo", "video", "rich"]},
provider_name: %Schema{type: :string, nullable: true},
provider_url: %Schema{type: :string, format: :uri},
url: %Schema{type: :string, format: :uri},
image: %Schema{type: :string, nullable: true, format: :uri},
title: %Schema{type: :string},
description: %Schema{type: :string}
}
})
}
}
end
def favourited_by_operation do
%Operation{
tags: ["Statuses"],
summary: "Favourited by",
description: "View who favourited a given status",
operationId: "StatusController.favourited_by",
security: [%{"oAuth" => ["read:accounts"]}],
parameters: [id_param()],
responses: %{
200 =>
Operation.response(
"Array of Accounts",
"application/json",
AccountOperation.array_of_accounts()
),
404 => Operation.response("Not Found", "application/json", ApiError)
}
}
end
def reblogged_by_operation do
%Operation{
tags: ["Statuses"],
summary: "Boosted by",
description: "View who boosted a given status",
operationId: "StatusController.reblogged_by",
security: [%{"oAuth" => ["read:accounts"]}],
parameters: [id_param()],
responses: %{
200 =>
Operation.response(
"Array of Accounts",
"application/json",
AccountOperation.array_of_accounts()
),
404 => Operation.response("Not Found", "application/json", ApiError)
}
}
end
def context_operation do
%Operation{
tags: ["Statuses"],
summary: "Parent and child statuses",
description: "View statuses above and below this status in the thread",
operationId: "StatusController.context",
security: [%{"oAuth" => ["read:statuses"]}],
parameters: [id_param()],
responses: %{
200 => Operation.response("Context", "application/json", context())
}
}
end
def favourites_operation do
%Operation{
tags: ["Statuses"],
summary: "Favourited statuses",
description: "Statuses the user has favourited",
operationId: "StatusController.favourites",
parameters: pagination_params(),
security: [%{"oAuth" => ["read:favourites"]}],
responses: %{
200 => Operation.response("Array of Statuses", "application/json", array_of_statuses())
}
}
end
def bookmarks_operation do
%Operation{
tags: ["Statuses"],
summary: "Bookmarked statuses",
description: "Statuses the user has bookmarked",
operationId: "StatusController.bookmarks",
parameters: [
Operation.parameter(:with_relationships, :query, BooleanLike, "Include relationships")
| pagination_params()
],
security: [%{"oAuth" => ["read:bookmarks"]}],
responses: %{
200 => Operation.response("Array of Statuses", "application/json", array_of_statuses())
}
}
end
def array_of_statuses do
%Schema{type: :array, items: Status, example: [Status.schema().example]}
end
defp create_request do
%Schema{
title: "StatusCreateRequest",
type: :object,
properties: %{
status: %Schema{
type: :string,
description:
"Text content of the status. If `media_ids` is provided, this becomes optional. Attaching a `poll` is optional while `status` is provided."
},
media_ids: %Schema{
type: :array,
items: %Schema{type: :string},
description: "Array of Attachment ids to be attached as media."
},
poll: %Schema{
type: :object,
required: [:options],
properties: %{
options: %Schema{
type: :array,
items: %Schema{type: :string},
description: "Array of possible answers. Must be provided with `poll[expires_in]`."
},
expires_in: %Schema{
type: :integer,
description:
"Duration the poll should be open, in seconds. Must be provided with `poll[options]`"
},
multiple: %Schema{type: :boolean, description: "Allow multiple choices?"},
hide_totals: %Schema{
type: :boolean,
description: "Hide vote counts until the poll ends?"
}
}
},
in_reply_to_id: %Schema{
allOf: [FlakeID],
description: "ID of the status being replied to, if status is a reply"
},
sensitive: %Schema{
type: :boolean,
description: "Mark status and attached media as sensitive?"
},
spoiler_text: %Schema{
type: :string,
description:
"Text to be shown as a warning or subject before the actual content. Statuses are generally collapsed behind this field."
},
scheduled_at: %Schema{
type: :string,
format: :"date-time",
nullable: true,
description:
"ISO 8601 Datetime at which to schedule a status. Providing this paramter will cause ScheduledStatus to be returned instead of Status. Must be at least 5 minutes in the future."
},
language: %Schema{type: :string, description: "ISO 639 language code for this status."},
# Pleroma-specific properties:
preview: %Schema{
type: :boolean,
description:
"If set to `true` the post won't be actually posted, but the status entitiy would still be rendered back. This could be useful for previewing rich text/custom emoji, for example"
},
content_type: %Schema{
type: :string,
description:
"The MIME type of the status, it is transformed into HTML by the backend. You can get the list of the supported MIME types with the nodeinfo endpoint."
},
to: %Schema{
type: :array,
items: %Schema{type: :string},
description:
"A list of nicknames (like `lain@soykaf.club` or `lain` on the local server) that will be used to determine who is going to be addressed by this post. Using this will disable the implicit addressing by mentioned names in the `status` body, only the people in the `to` list will be addressed. The normal rules for for post visibility are not affected by this and will still apply"
},
visibility: %Schema{
anyOf: [
VisibilityScope,
%Schema{type: :string, description: "`list:LIST_ID`", example: "LIST:123"}
],
description:
"Visibility of the posted status. Besides standard MastoAPI values (`direct`, `private`, `unlisted` or `public`) it can be used to address a List by setting it to `list:LIST_ID`"
},
expires_in: %Schema{
type: :integer,
description:
"The number of seconds the posted activity should expire in. When a posted activity expires it will be deleted from the server, and a delete request for it will be federated. This needs to be longer than an hour."
},
in_reply_to_conversation_id: %Schema{
type: :string,
description:
"Will reply to a given conversation, addressing only the people who are part of the recipient set of that conversation. Sets the visibility to `direct`."
}
},
example: %{
"status" => "What time is it?",
"sensitive" => "false",
"poll" => %{
"options" => ["Cofe", "Adventure"],
"expires_in" => 420
}
}
}
end
defp id_param do
Operation.parameter(:id, :path, FlakeID, "Status ID",
example: "9umDrYheeY451cQnEe",
required: true
)
end
defp status_response do
Operation.response("Status", "application/json", Status)
end
defp context do
%Schema{
title: "StatusContext",
description:
"Represents the tree around a given status. Used for reconstructing threads of statuses.",
type: :object,
required: [:ancestors, :descendants],
properties: %{
ancestors: array_of_statuses(),
descendants: array_of_statuses()
},
example: %{
"ancestors" => [Status.schema().example],
"descendants" => [Status.schema().example]
}
}
end
end

View file

@ -0,0 +1,199 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.TimelineOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.ApiError
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
alias Pleroma.Web.ApiSpec.Schemas.Status
alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
import Pleroma.Web.ApiSpec.Helpers
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
def home_operation do
%Operation{
tags: ["Timelines"],
summary: "Home timeline",
description: "View statuses from followed users",
security: [%{"oAuth" => ["read:statuses"]}],
parameters: [
local_param(),
with_muted_param(),
exclude_visibilities_param(),
reply_visibility_param(),
with_relationships_param() | pagination_params()
],
operationId: "TimelineController.home",
responses: %{
200 => Operation.response("Array of Status", "application/json", array_of_statuses())
}
}
end
def direct_operation do
%Operation{
tags: ["Timelines"],
summary: "Direct timeline",
description:
"View statuses with a “direct” privacy, from your account or in your notifications",
deprecated: true,
parameters: pagination_params(),
security: [%{"oAuth" => ["read:statuses"]}],
operationId: "TimelineController.direct",
responses: %{
200 => Operation.response("Array of Status", "application/json", array_of_statuses())
}
}
end
def public_operation do
%Operation{
tags: ["Timelines"],
summary: "Public timeline",
security: [%{"oAuth" => ["read:statuses"]}],
parameters: [
local_param(),
only_media_param(),
with_muted_param(),
exclude_visibilities_param(),
reply_visibility_param(),
with_relationships_param() | pagination_params()
],
operationId: "TimelineController.public",
responses: %{
200 => Operation.response("Array of Status", "application/json", array_of_statuses()),
401 => Operation.response("Error", "application/json", ApiError)
}
}
end
def hashtag_operation do
%Operation{
tags: ["Timelines"],
summary: "Hashtag timeline",
description: "View public statuses containing the given hashtag",
security: [%{"oAuth" => ["read:statuses"]}],
parameters: [
Operation.parameter(
:tag,
:path,
%Schema{type: :string},
"Content of a #hashtag, not including # symbol.",
required: true
),
Operation.parameter(
:any,
:query,
%Schema{type: :array, items: %Schema{type: :string}},
"Statuses that also includes any of these tags"
),
Operation.parameter(
:all,
:query,
%Schema{type: :array, items: %Schema{type: :string}},
"Statuses that also includes all of these tags"
),
Operation.parameter(
:none,
:query,
%Schema{type: :array, items: %Schema{type: :string}},
"Statuses that do not include these tags"
),
local_param(),
only_media_param(),
with_muted_param(),
exclude_visibilities_param(),
with_relationships_param() | pagination_params()
],
operationId: "TimelineController.hashtag",
responses: %{
200 => Operation.response("Array of Status", "application/json", array_of_statuses())
}
}
end
def list_operation do
%Operation{
tags: ["Timelines"],
summary: "List timeline",
description: "View statuses in the given list timeline",
security: [%{"oAuth" => ["read:lists"]}],
parameters: [
Operation.parameter(
:list_id,
:path,
%Schema{type: :string},
"Local ID of the list in the database",
required: true
),
with_muted_param(),
exclude_visibilities_param(),
with_relationships_param() | pagination_params()
],
operationId: "TimelineController.list",
responses: %{
200 => Operation.response("Array of Status", "application/json", array_of_statuses())
}
}
end
defp array_of_statuses do
%Schema{
title: "ArrayOfStatuses",
type: :array,
items: Status,
example: [Status.schema().example]
}
end
defp with_relationships_param do
Operation.parameter(:with_relationships, :query, BooleanLike, "Include relationships")
end
defp local_param do
Operation.parameter(
:local,
:query,
%Schema{allOf: [BooleanLike], default: false},
"Show only local statuses?"
)
end
defp with_muted_param do
Operation.parameter(:with_muted, :query, BooleanLike, "Includeactivities by muted users")
end
defp exclude_visibilities_param do
Operation.parameter(
:exclude_visibilities,
:query,
%Schema{type: :array, items: VisibilityScope},
"Exclude the statuses with the given visibilities"
)
end
defp reply_visibility_param do
Operation.parameter(
:reply_visibility,
:query,
%Schema{type: :string, enum: ["following", "self"]},
"Filter replies. Possible values: without parameter (default) shows all replies, `following` - replies directed to you or users you follow, `self` - replies directed to you."
)
end
defp only_media_param do
Operation.parameter(
:only_media,
:query,
%Schema{allOf: [BooleanLike], default: false},
"Show only statuses with media attached?"
)
end
end

View file

@ -19,60 +19,127 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
description: "Response schema for a status", description: "Response schema for a status",
type: :object, type: :object,
properties: %{ properties: %{
account: Account, account: %Schema{allOf: [Account], description: "The account that authored this status"},
application: %Schema{ application: %Schema{
description: "The application used to post this status",
type: :object, type: :object,
properties: %{ properties: %{
name: %Schema{type: :string}, name: %Schema{type: :string},
website: %Schema{type: :string, nullable: true, format: :uri} website: %Schema{type: :string, nullable: true, format: :uri}
} }
}, },
bookmarked: %Schema{type: :boolean}, bookmarked: %Schema{type: :boolean, description: "Have you bookmarked this status?"},
card: %Schema{ card: %Schema{
type: :object, type: :object,
nullable: true, nullable: true,
description: "Preview card for links included within status content",
required: [:url, :title, :description, :type],
properties: %{ properties: %{
type: %Schema{type: :string, enum: ["link", "photo", "video", "rich"]}, type: %Schema{
provider_name: %Schema{type: :string, nullable: true}, type: :string,
provider_url: %Schema{type: :string, format: :uri}, enum: ["link", "photo", "video", "rich"],
url: %Schema{type: :string, format: :uri}, description: "The type of the preview card"
image: %Schema{type: :string, nullable: true, format: :uri}, },
title: %Schema{type: :string}, provider_name: %Schema{
description: %Schema{type: :string} type: :string,
nullable: true,
description: "The provider of the original resource"
},
provider_url: %Schema{
type: :string,
format: :uri,
description: "A link to the provider of the original resource"
},
url: %Schema{type: :string, format: :uri, description: "Location of linked resource"},
image: %Schema{
type: :string,
nullable: true,
format: :uri,
description: "Preview thumbnail"
},
title: %Schema{type: :string, description: "Title of linked resource"},
description: %Schema{type: :string, description: "Description of preview"}
} }
}, },
content: %Schema{type: :string, format: :html}, content: %Schema{type: :string, format: :html, description: "HTML-encoded status content"},
created_at: %Schema{type: :string, format: "date-time"}, created_at: %Schema{
emojis: %Schema{type: :array, items: Emoji}, type: :string,
favourited: %Schema{type: :boolean}, format: "date-time",
favourites_count: %Schema{type: :integer}, description: "The date when this status was created"
},
emojis: %Schema{
type: :array,
items: Emoji,
description: "Custom emoji to be used when rendering status content"
},
favourited: %Schema{type: :boolean, description: "Have you favourited this status?"},
favourites_count: %Schema{
type: :integer,
description: "How many favourites this status has received"
},
id: FlakeID, id: FlakeID,
in_reply_to_account_id: %Schema{type: :string, nullable: true}, in_reply_to_account_id: %Schema{
in_reply_to_id: %Schema{type: :string, nullable: true}, allOf: [FlakeID],
language: %Schema{type: :string, nullable: true}, nullable: true,
description: "ID of the account being replied to"
},
in_reply_to_id: %Schema{
allOf: [FlakeID],
nullable: true,
description: "ID of the status being replied"
},
language: %Schema{
type: :string,
nullable: true,
description: "Primary language of this status"
},
media_attachments: %Schema{ media_attachments: %Schema{
type: :array, type: :array,
items: Attachment items: Attachment,
description: "Media that is attached to this status"
}, },
mentions: %Schema{ mentions: %Schema{
type: :array, type: :array,
description: "Mentions of users within the status content",
items: %Schema{ items: %Schema{
type: :object, type: :object,
properties: %{ properties: %{
id: %Schema{type: :string}, id: %Schema{allOf: [FlakeID], description: "The account id of the mentioned user"},
acct: %Schema{type: :string}, acct: %Schema{
username: %Schema{type: :string}, type: :string,
url: %Schema{type: :string, format: :uri} description:
"The webfinger acct: URI of the mentioned user. Equivalent to `username` for local users, or `username@domain` for remote users."
},
username: %Schema{type: :string, description: "The username of the mentioned user"},
url: %Schema{
type: :string,
format: :uri,
description: "The location of the mentioned user's profile"
}
} }
} }
}, },
muted: %Schema{type: :boolean}, muted: %Schema{
pinned: %Schema{type: :boolean}, type: :boolean,
description: "Have you muted notifications for this status's conversation?"
},
pinned: %Schema{
type: :boolean,
description: "Have you pinned this status? Only appears if the status is pinnable."
},
pleroma: %Schema{ pleroma: %Schema{
type: :object, type: :object,
properties: %{ properties: %{
content: %Schema{type: :object, additionalProperties: %Schema{type: :string}}, content: %Schema{
conversation_id: %Schema{type: :integer}, type: :object,
additionalProperties: %Schema{type: :string},
description:
"A map consisting of alternate representations of the `content` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain`"
},
conversation_id: %Schema{
type: :integer,
description: "The ID of the AP context the status is associated with (if any)"
},
direct_conversation_id: %Schema{ direct_conversation_id: %Schema{
type: :integer, type: :integer,
nullable: true, nullable: true,
@ -81,6 +148,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
}, },
emoji_reactions: %Schema{ emoji_reactions: %Schema{
type: :array, type: :array,
description:
"A list with emoji / reaction maps. Contains no information about the reacting users, for that use the /statuses/:id/reactions endpoint.",
items: %Schema{ items: %Schema{
type: :object, type: :object,
properties: %{ properties: %{
@ -90,27 +159,74 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
} }
} }
}, },
expires_at: %Schema{type: :string, format: "date-time", nullable: true}, expires_at: %Schema{
in_reply_to_account_acct: %Schema{type: :string, nullable: true}, type: :string,
local: %Schema{type: :boolean}, format: "date-time",
spoiler_text: %Schema{type: :object, additionalProperties: %Schema{type: :string}}, nullable: true,
thread_muted: %Schema{type: :boolean} description:
"A datetime (ISO 8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire"
},
in_reply_to_account_acct: %Schema{
type: :string,
nullable: true,
description: "The `acct` property of User entity for replied user (if any)"
},
local: %Schema{
type: :boolean,
description: "`true` if the post was made on the local instance"
},
spoiler_text: %Schema{
type: :object,
additionalProperties: %Schema{type: :string},
description:
"A map consisting of alternate representations of the `spoiler_text` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain`."
},
thread_muted: %Schema{
type: :boolean,
description: "`true` if the thread the post belongs to is muted"
}
} }
}, },
poll: %Schema{type: Poll, nullable: true}, poll: %Schema{allOf: [Poll], nullable: true, description: "The poll attached to the status"},
reblog: %Schema{ reblog: %Schema{
allOf: [%OpenApiSpex.Reference{"$ref": "#/components/schemas/Status"}], allOf: [%OpenApiSpex.Reference{"$ref": "#/components/schemas/Status"}],
nullable: true nullable: true,
description: "The status being reblogged"
},
reblogged: %Schema{type: :boolean, description: "Have you boosted this status?"},
reblogs_count: %Schema{
type: :integer,
description: "How many boosts this status has received"
},
replies_count: %Schema{
type: :integer,
description: "How many replies this status has received"
},
sensitive: %Schema{
type: :boolean,
description: "Is this status marked as sensitive content?"
},
spoiler_text: %Schema{
type: :string,
description:
"Subject or summary line, below which status content is collapsed until expanded"
}, },
reblogged: %Schema{type: :boolean},
reblogs_count: %Schema{type: :integer},
replies_count: %Schema{type: :integer},
sensitive: %Schema{type: :boolean},
spoiler_text: %Schema{type: :string},
tags: %Schema{type: :array, items: Tag}, tags: %Schema{type: :array, items: Tag},
uri: %Schema{type: :string, format: :uri}, uri: %Schema{
url: %Schema{type: :string, nullable: true, format: :uri}, type: :string,
visibility: VisibilityScope format: :uri,
description: "URI of the status used for federation"
},
url: %Schema{
type: :string,
nullable: true,
format: :uri,
description: "A link to the status's HTML representation"
},
visibility: %Schema{
allOf: [VisibilityScope],
description: "Visibility of this status"
}
}, },
example: %{ example: %{
"account" => %{ "account" => %{

View file

@ -9,6 +9,6 @@ defmodule Pleroma.Web.ApiSpec.Schemas.VisibilityScope do
title: "VisibilityScope", title: "VisibilityScope",
description: "Status visibility", description: "Status visibility",
type: :string, type: :string,
enum: ["public", "unlisted", "private", "direct"] enum: ["public", "unlisted", "private", "direct", "list"]
}) })
end end

View file

@ -3,9 +3,9 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Auth.TOTPAuthenticator do defmodule Pleroma.Web.Auth.TOTPAuthenticator do
alias Comeonin.Pbkdf2
alias Pleroma.MFA alias Pleroma.MFA
alias Pleroma.MFA.TOTP alias Pleroma.MFA.TOTP
alias Pleroma.Plugs.AuthenticationPlug
alias Pleroma.User alias Pleroma.User
@doc "Verify code or check backup code." @doc "Verify code or check backup code."
@ -31,7 +31,7 @@ def verify_recovery_code(
code code
) )
when is_list(codes) and is_binary(code) do when is_list(codes) and is_binary(code) do
hash_code = Enum.find(codes, fn hash -> Pbkdf2.checkpw(code, hash) end) hash_code = Enum.find(codes, fn hash -> AuthenticationPlug.checkpw(code, hash) end)
if hash_code do if hash_code do
MFA.invalidate_backup_code(user, hash_code) MFA.invalidate_backup_code(user, hash_code)

View file

@ -58,16 +58,16 @@ def create(user, params) do
end end
defp put_params(draft, params) do defp put_params(draft, params) do
params = Map.put_new(params, "in_reply_to_status_id", params["in_reply_to_id"]) params = Map.put_new(params, :in_reply_to_status_id, params[:in_reply_to_id])
%__MODULE__{draft | params: params} %__MODULE__{draft | params: params}
end end
defp status(%{params: %{"status" => status}} = draft) do defp status(%{params: %{status: status}} = draft) do
%__MODULE__{draft | status: String.trim(status)} %__MODULE__{draft | status: String.trim(status)}
end end
defp summary(%{params: params} = draft) do defp summary(%{params: params} = draft) do
%__MODULE__{draft | summary: Map.get(params, "spoiler_text", "")} %__MODULE__{draft | summary: Map.get(params, :spoiler_text, "")}
end end
defp full_payload(%{status: status, summary: summary} = draft) do defp full_payload(%{status: status, summary: summary} = draft) do
@ -84,20 +84,20 @@ defp attachments(%{params: params} = draft) do
%__MODULE__{draft | attachments: attachments} %__MODULE__{draft | attachments: attachments}
end end
defp in_reply_to(%{params: %{"in_reply_to_status_id" => ""}} = draft), do: draft defp in_reply_to(%{params: %{in_reply_to_status_id: ""}} = draft), do: draft
defp in_reply_to(%{params: %{"in_reply_to_status_id" => id}} = draft) when is_binary(id) do defp in_reply_to(%{params: %{in_reply_to_status_id: id}} = draft) when is_binary(id) do
%__MODULE__{draft | in_reply_to: Activity.get_by_id(id)} %__MODULE__{draft | in_reply_to: Activity.get_by_id(id)}
end end
defp in_reply_to(%{params: %{"in_reply_to_status_id" => %Activity{} = in_reply_to}} = draft) do defp in_reply_to(%{params: %{in_reply_to_status_id: %Activity{} = in_reply_to}} = draft) do
%__MODULE__{draft | in_reply_to: in_reply_to} %__MODULE__{draft | in_reply_to: in_reply_to}
end end
defp in_reply_to(draft), do: draft defp in_reply_to(draft), do: draft
defp in_reply_to_conversation(draft) do defp in_reply_to_conversation(draft) do
in_reply_to_conversation = Participation.get(draft.params["in_reply_to_conversation_id"]) in_reply_to_conversation = Participation.get(draft.params[:in_reply_to_conversation_id])
%__MODULE__{draft | in_reply_to_conversation: in_reply_to_conversation} %__MODULE__{draft | in_reply_to_conversation: in_reply_to_conversation}
end end
@ -112,7 +112,7 @@ defp visibility(%{params: params} = draft) do
end end
defp expires_at(draft) do defp expires_at(draft) do
case CommonAPI.check_expiry_date(draft.params["expires_in"]) do case CommonAPI.check_expiry_date(draft.params[:expires_in]) do
{:ok, expires_at} -> %__MODULE__{draft | expires_at: expires_at} {:ok, expires_at} -> %__MODULE__{draft | expires_at: expires_at}
{:error, message} -> add_error(draft, message) {:error, message} -> add_error(draft, message)
end end
@ -144,7 +144,7 @@ defp to_and_cc(draft) do
addressed_users = addressed_users =
draft.mentions draft.mentions
|> Enum.map(fn {_, mentioned_user} -> mentioned_user.ap_id end) |> Enum.map(fn {_, mentioned_user} -> mentioned_user.ap_id end)
|> Utils.get_addressed_users(draft.params["to"]) |> Utils.get_addressed_users(draft.params[:to])
{to, cc} = {to, cc} =
Utils.get_to_and_cc( Utils.get_to_and_cc(
@ -164,7 +164,7 @@ defp context(draft) do
end end
defp sensitive(draft) do defp sensitive(draft) do
sensitive = draft.params["sensitive"] || Enum.member?(draft.tags, {"#nsfw", "nsfw"}) sensitive = draft.params[:sensitive] || Enum.member?(draft.tags, {"#nsfw", "nsfw"})
%__MODULE__{draft | sensitive: sensitive} %__MODULE__{draft | sensitive: sensitive}
end end
@ -191,7 +191,7 @@ defp object(draft) do
end end
defp preview?(draft) do defp preview?(draft) do
preview? = Pleroma.Web.ControllerHelper.truthy_param?(draft.params["preview"]) preview? = Pleroma.Web.ControllerHelper.truthy_param?(draft.params[:preview])
%__MODULE__{draft | preview?: preview?} %__MODULE__{draft | preview?: preview?}
end end

View file

@ -83,33 +83,51 @@ def reject_follow_request(follower, followed) do
end end
def delete(activity_id, user) do def delete(activity_id, user) do
with {_, %Activity{data: %{"object" => _}} = activity} <- with {_, %Activity{data: %{"object" => _, "type" => "Create"}} = activity} <-
{:find_activity, Activity.get_by_id_with_object(activity_id)}, {:find_activity, Activity.get_by_id(activity_id)},
%Object{} = object <- Object.normalize(activity), {_, %Object{} = object, _} <-
{:find_object, Object.normalize(activity, false), activity},
true <- User.superuser?(user) || user.ap_id == object.data["actor"], true <- User.superuser?(user) || user.ap_id == object.data["actor"],
{:ok, delete_data, _} <- Builder.delete(user, object.data["id"]), {:ok, delete_data, _} <- Builder.delete(user, object.data["id"]),
{:ok, delete, _} <- Pipeline.common_pipeline(delete_data, local: true) do {:ok, delete, _} <- Pipeline.common_pipeline(delete_data, local: true) do
{:ok, delete} {:ok, delete}
else else
{:find_activity, _} -> {:error, :not_found} {:find_activity, _} ->
_ -> {:error, dgettext("errors", "Could not delete")} {:error, :not_found}
{:find_object, nil, %Activity{data: %{"actor" => actor, "object" => object}}} ->
# We have the create activity, but not the object, it was probably pruned.
# Insert a tombstone and try again
with {:ok, tombstone_data, _} <- Builder.tombstone(actor, object),
{:ok, _tombstone} <- Object.create(tombstone_data) do
delete(activity_id, user)
else
_ ->
Logger.error(
"Could not insert tombstone for missing object on deletion. Object is #{object}."
)
{:error, dgettext("errors", "Could not delete")}
end
_ ->
{:error, dgettext("errors", "Could not delete")}
end end
end end
def repeat(id, user, params \\ %{}) do def repeat(id, user, params \\ %{}) do
with {_, %Activity{data: %{"type" => "Create"}} = activity} <- with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id) do
{:find_activity, Activity.get_by_id(id)}, object = Object.normalize(activity)
object <- Object.normalize(activity), announce_activity = Utils.get_existing_announce(user.ap_id, object)
announce_activity <- Utils.get_existing_announce(user.ap_id, object), public = public_announce?(object, params)
public <- public_announce?(object, params) do
if announce_activity do if announce_activity do
{:ok, announce_activity, object} {:ok, announce_activity, object}
else else
ActivityPub.announce(user, object, nil, true, public) ActivityPub.announce(user, object, nil, true, public)
end end
else else
{:find_activity, _} -> {:error, :not_found} _ -> {:error, :not_found}
_ -> {:error, dgettext("errors", "Could not repeat")}
end end
end end
@ -267,7 +285,7 @@ defp normalize_and_validate_choices(choices, object) do
end end
end end
def public_announce?(_, %{"visibility" => visibility}) def public_announce?(_, %{visibility: visibility})
when visibility in ~w{public unlisted private direct}, when visibility in ~w{public unlisted private direct},
do: visibility in ~w(public unlisted) do: visibility in ~w(public unlisted)
@ -277,11 +295,11 @@ def public_announce?(object, _) do
def get_visibility(_, _, %Participation{}), do: {"direct", "direct"} def get_visibility(_, _, %Participation{}), do: {"direct", "direct"}
def get_visibility(%{"visibility" => visibility}, in_reply_to, _) def get_visibility(%{visibility: visibility}, in_reply_to, _)
when visibility in ~w{public unlisted private direct}, when visibility in ~w{public unlisted private direct},
do: {visibility, get_replied_to_visibility(in_reply_to)} do: {visibility, get_replied_to_visibility(in_reply_to)}
def get_visibility(%{"visibility" => "list:" <> list_id}, in_reply_to, _) do def get_visibility(%{visibility: "list:" <> list_id}, in_reply_to, _) do
visibility = {:list, String.to_integer(list_id)} visibility = {:list, String.to_integer(list_id)}
{visibility, get_replied_to_visibility(in_reply_to)} {visibility, get_replied_to_visibility(in_reply_to)}
end end
@ -339,7 +357,7 @@ def listen(user, %{"title" => _} = data) do
end end
end end
def post(user, %{"status" => _} = data) do def post(user, %{status: _} = data) do
with {:ok, draft} <- Pleroma.Web.CommonAPI.ActivityDraft.create(user, data) do with {:ok, draft} <- Pleroma.Web.CommonAPI.ActivityDraft.create(user, data) do
draft.changes draft.changes
|> ActivityPub.create(draft.preview?) |> ActivityPub.create(draft.preview?)
@ -448,11 +466,11 @@ def update_activity_scope(activity_id, opts \\ %{}) do
end end
end end
defp toggle_sensitive(activity, %{"sensitive" => sensitive}) when sensitive in ~w(true false) do defp toggle_sensitive(activity, %{sensitive: sensitive}) when sensitive in ~w(true false) do
toggle_sensitive(activity, %{"sensitive" => String.to_existing_atom(sensitive)}) toggle_sensitive(activity, %{sensitive: String.to_existing_atom(sensitive)})
end end
defp toggle_sensitive(%Activity{object: object} = activity, %{"sensitive" => sensitive}) defp toggle_sensitive(%Activity{object: object} = activity, %{sensitive: sensitive})
when is_boolean(sensitive) do when is_boolean(sensitive) do
new_data = Map.put(object.data, "sensitive", sensitive) new_data = Map.put(object.data, "sensitive", sensitive)
@ -466,7 +484,7 @@ defp toggle_sensitive(%Activity{object: object} = activity, %{"sensitive" => sen
defp toggle_sensitive(activity, _), do: {:ok, activity} defp toggle_sensitive(activity, _), do: {:ok, activity}
defp set_visibility(activity, %{"visibility" => visibility}) do defp set_visibility(activity, %{visibility: visibility}) do
Utils.update_activity_visibility(activity, visibility) Utils.update_activity_visibility(activity, visibility)
end end

View file

@ -22,11 +22,11 @@ defmodule Pleroma.Web.CommonAPI.Utils do
require Logger require Logger
require Pleroma.Constants require Pleroma.Constants
def attachments_from_ids(%{"media_ids" => ids, "descriptions" => desc} = _) do def attachments_from_ids(%{media_ids: ids, descriptions: desc}) do
attachments_from_ids_descs(ids, desc) attachments_from_ids_descs(ids, desc)
end end
def attachments_from_ids(%{"media_ids" => ids} = _) do def attachments_from_ids(%{media_ids: ids}) do
attachments_from_ids_no_descs(ids) attachments_from_ids_no_descs(ids)
end end
@ -37,11 +37,11 @@ def attachments_from_ids_no_descs([]), do: []
def attachments_from_ids_no_descs(ids) do def attachments_from_ids_no_descs(ids) do
Enum.map(ids, fn media_id -> Enum.map(ids, fn media_id ->
case Repo.get(Object, media_id) do case Repo.get(Object, media_id) do
%Object{data: data} = _ -> data %Object{data: data} -> data
_ -> nil _ -> nil
end end
end) end)
|> Enum.filter(& &1) |> Enum.reject(&is_nil/1)
end end
def attachments_from_ids_descs([], _), do: [] def attachments_from_ids_descs([], _), do: []
@ -51,14 +51,14 @@ def attachments_from_ids_descs(ids, descs_str) do
Enum.map(ids, fn media_id -> Enum.map(ids, fn media_id ->
case Repo.get(Object, media_id) do case Repo.get(Object, media_id) do
%Object{data: data} = _ -> %Object{data: data} ->
Map.put(data, "name", descs[media_id]) Map.put(data, "name", descs[media_id])
_ -> _ ->
nil nil
end end
end) end)
|> Enum.filter(& &1) |> Enum.reject(&is_nil/1)
end end
@spec get_to_and_cc( @spec get_to_and_cc(
@ -140,7 +140,7 @@ def make_poll_data(%{"poll" => %{"expires_in" => expires_in}} = data)
|> make_poll_data() |> make_poll_data()
end end
def make_poll_data(%{"poll" => %{"options" => options, "expires_in" => expires_in}} = data) def make_poll_data(%{poll: %{options: options, expires_in: expires_in}} = data)
when is_list(options) do when is_list(options) do
limits = Pleroma.Config.get([:instance, :poll_limits]) limits = Pleroma.Config.get([:instance, :poll_limits])
@ -163,7 +163,7 @@ def make_poll_data(%{"poll" => %{"options" => options, "expires_in" => expires_i
|> DateTime.add(expires_in) |> DateTime.add(expires_in)
|> DateTime.to_iso8601() |> DateTime.to_iso8601()
key = if truthy_param?(data["poll"]["multiple"]), do: "anyOf", else: "oneOf" key = if truthy_param?(data.poll[:multiple]), do: "anyOf", else: "oneOf"
poll = %{"type" => "Question", key => option_notes, "closed" => end_time} poll = %{"type" => "Question", key => option_notes, "closed" => end_time}
{:ok, {poll, emoji}} {:ok, {poll, emoji}}
@ -213,7 +213,7 @@ def make_content_html(
|> Map.get("attachment_links", Config.get([:instance, :attachment_links])) |> Map.get("attachment_links", Config.get([:instance, :attachment_links]))
|> truthy_param?() |> truthy_param?()
content_type = get_content_type(data["content_type"]) content_type = get_content_type(data[:content_type])
options = options =
if visibility == "direct" && Config.get([:instance, :safe_dm_mentions]) do if visibility == "direct" && Config.get([:instance, :safe_dm_mentions]) do

View file

@ -24,6 +24,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.ScheduledActivityView alias Pleroma.Web.MastodonAPI.ScheduledActivityView
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(:skip_plug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action in [:index, :show]) plug(:skip_plug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action in [:index, :show])
@unauthenticated_access %{fallback: :proceed_unauthenticated, scopes: []} @unauthenticated_access %{fallback: :proceed_unauthenticated, scopes: []}
@ -97,12 +98,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
action_fallback(Pleroma.Web.MastodonAPI.FallbackController) action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.StatusOperation
@doc """ @doc """
GET `/api/v1/statuses?ids[]=1&ids[]=2` GET `/api/v1/statuses?ids[]=1&ids[]=2`
`ids` query param is required `ids` query param is required
""" """
def index(%{assigns: %{user: user}} = conn, %{"ids" => ids} = params) do def index(%{assigns: %{user: user}} = conn, %{ids: ids} = params) do
limit = 100 limit = 100
activities = activities =
@ -125,21 +128,29 @@ def index(%{assigns: %{user: user}} = conn, %{"ids" => ids} = params) do
Creates a scheduled status when `scheduled_at` param is present and it's far enough Creates a scheduled status when `scheduled_at` param is present and it's far enough
""" """
def create( def create(
%{assigns: %{user: user}} = conn, %{
%{"status" => _, "scheduled_at" => scheduled_at} = params assigns: %{user: user},
body_params: %{status: _, scheduled_at: scheduled_at} = params
} = conn,
_
) )
when not is_nil(scheduled_at) do when not is_nil(scheduled_at) do
params = Map.put(params, "in_reply_to_status_id", params["in_reply_to_id"]) params = Map.put(params, :in_reply_to_status_id, params[:in_reply_to_id])
attrs = %{
params: Map.new(params, fn {key, value} -> {to_string(key), value} end),
scheduled_at: scheduled_at
}
with {:far_enough, true} <- {:far_enough, ScheduledActivity.far_enough?(scheduled_at)}, with {:far_enough, true} <- {:far_enough, ScheduledActivity.far_enough?(scheduled_at)},
attrs <- %{"params" => params, "scheduled_at" => scheduled_at},
{:ok, scheduled_activity} <- ScheduledActivity.create(user, attrs) do {:ok, scheduled_activity} <- ScheduledActivity.create(user, attrs) do
conn conn
|> put_view(ScheduledActivityView) |> put_view(ScheduledActivityView)
|> render("show.json", scheduled_activity: scheduled_activity) |> render("show.json", scheduled_activity: scheduled_activity)
else else
{:far_enough, _} -> {:far_enough, _} ->
create(conn, Map.drop(params, ["scheduled_at"])) params = Map.drop(params, [:scheduled_at])
create(%Plug.Conn{conn | body_params: params}, %{})
error -> error ->
error error
@ -151,8 +162,8 @@ def create(
Creates a regular status Creates a regular status
""" """
def create(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do def create(%{assigns: %{user: user}, body_params: %{status: _} = params} = conn, _) do
params = Map.put(params, "in_reply_to_status_id", params["in_reply_to_id"]) params = Map.put(params, :in_reply_to_status_id, params[:in_reply_to_id])
with {:ok, activity} <- CommonAPI.post(user, params) do with {:ok, activity} <- CommonAPI.post(user, params) do
try_render(conn, "show.json", try_render(conn, "show.json",
@ -169,12 +180,13 @@ def create(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do
end end
end end
def create(%{assigns: %{user: _user}} = conn, %{"media_ids" => _} = params) do def create(%{assigns: %{user: _user}, body_params: %{media_ids: _} = params} = conn, _) do
create(conn, Map.put(params, "status", "")) params = Map.put(params, :status, "")
create(%Plug.Conn{conn | body_params: params}, %{})
end end
@doc "GET /api/v1/statuses/:id" @doc "GET /api/v1/statuses/:id"
def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do def show(%{assigns: %{user: user}} = conn, %{id: id}) do
with %Activity{} = activity <- Activity.get_by_id_with_object(id), with %Activity{} = activity <- Activity.get_by_id_with_object(id),
true <- Visibility.visible_for_user?(activity, user) do true <- Visibility.visible_for_user?(activity, user) do
try_render(conn, "show.json", try_render(conn, "show.json",
@ -188,7 +200,7 @@ def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do
end end
@doc "DELETE /api/v1/statuses/:id" @doc "DELETE /api/v1/statuses/:id"
def delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do def delete(%{assigns: %{user: user}} = conn, %{id: id}) do
with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
json(conn, %{}) json(conn, %{})
else else
@ -198,7 +210,7 @@ def delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
end end
@doc "POST /api/v1/statuses/:id/reblog" @doc "POST /api/v1/statuses/:id/reblog"
def reblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id} = params) do def reblog(%{assigns: %{user: user}, body_params: params} = conn, %{id: ap_id_or_id}) do
with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user, params), with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user, params),
%Activity{} = announce <- Activity.normalize(announce.data) do %Activity{} = announce <- Activity.normalize(announce.data) do
try_render(conn, "show.json", %{activity: announce, for: user, as: :activity}) try_render(conn, "show.json", %{activity: announce, for: user, as: :activity})
@ -206,7 +218,7 @@ def reblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id} = params) do
end end
@doc "POST /api/v1/statuses/:id/unreblog" @doc "POST /api/v1/statuses/:id/unreblog"
def unreblog(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do def unreblog(%{assigns: %{user: user}} = conn, %{id: activity_id}) do
with {:ok, _unannounce} <- CommonAPI.unrepeat(activity_id, user), with {:ok, _unannounce} <- CommonAPI.unrepeat(activity_id, user),
%Activity{} = activity <- Activity.get_by_id(activity_id) do %Activity{} = activity <- Activity.get_by_id(activity_id) do
try_render(conn, "show.json", %{activity: activity, for: user, as: :activity}) try_render(conn, "show.json", %{activity: activity, for: user, as: :activity})
@ -214,7 +226,7 @@ def unreblog(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do
end end
@doc "POST /api/v1/statuses/:id/favourite" @doc "POST /api/v1/statuses/:id/favourite"
def favourite(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do def favourite(%{assigns: %{user: user}} = conn, %{id: activity_id}) do
with {:ok, _fav} <- CommonAPI.favorite(user, activity_id), with {:ok, _fav} <- CommonAPI.favorite(user, activity_id),
%Activity{} = activity <- Activity.get_by_id(activity_id) do %Activity{} = activity <- Activity.get_by_id(activity_id) do
try_render(conn, "show.json", activity: activity, for: user, as: :activity) try_render(conn, "show.json", activity: activity, for: user, as: :activity)
@ -222,7 +234,7 @@ def favourite(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do
end end
@doc "POST /api/v1/statuses/:id/unfavourite" @doc "POST /api/v1/statuses/:id/unfavourite"
def unfavourite(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do def unfavourite(%{assigns: %{user: user}} = conn, %{id: activity_id}) do
with {:ok, _unfav} <- CommonAPI.unfavorite(activity_id, user), with {:ok, _unfav} <- CommonAPI.unfavorite(activity_id, user),
%Activity{} = activity <- Activity.get_by_id(activity_id) do %Activity{} = activity <- Activity.get_by_id(activity_id) do
try_render(conn, "show.json", activity: activity, for: user, as: :activity) try_render(conn, "show.json", activity: activity, for: user, as: :activity)
@ -230,21 +242,21 @@ def unfavourite(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do
end end
@doc "POST /api/v1/statuses/:id/pin" @doc "POST /api/v1/statuses/:id/pin"
def pin(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do def pin(%{assigns: %{user: user}} = conn, %{id: ap_id_or_id}) do
with {:ok, activity} <- CommonAPI.pin(ap_id_or_id, user) do with {:ok, activity} <- CommonAPI.pin(ap_id_or_id, user) do
try_render(conn, "show.json", activity: activity, for: user, as: :activity) try_render(conn, "show.json", activity: activity, for: user, as: :activity)
end end
end end
@doc "POST /api/v1/statuses/:id/unpin" @doc "POST /api/v1/statuses/:id/unpin"
def unpin(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do def unpin(%{assigns: %{user: user}} = conn, %{id: ap_id_or_id}) do
with {:ok, activity} <- CommonAPI.unpin(ap_id_or_id, user) do with {:ok, activity} <- CommonAPI.unpin(ap_id_or_id, user) do
try_render(conn, "show.json", activity: activity, for: user, as: :activity) try_render(conn, "show.json", activity: activity, for: user, as: :activity)
end end
end end
@doc "POST /api/v1/statuses/:id/bookmark" @doc "POST /api/v1/statuses/:id/bookmark"
def bookmark(%{assigns: %{user: user}} = conn, %{"id" => id}) do def bookmark(%{assigns: %{user: user}} = conn, %{id: id}) do
with %Activity{} = activity <- Activity.get_by_id_with_object(id), with %Activity{} = activity <- Activity.get_by_id_with_object(id),
%User{} = user <- User.get_cached_by_nickname(user.nickname), %User{} = user <- User.get_cached_by_nickname(user.nickname),
true <- Visibility.visible_for_user?(activity, user), true <- Visibility.visible_for_user?(activity, user),
@ -254,7 +266,7 @@ def bookmark(%{assigns: %{user: user}} = conn, %{"id" => id}) do
end end
@doc "POST /api/v1/statuses/:id/unbookmark" @doc "POST /api/v1/statuses/:id/unbookmark"
def unbookmark(%{assigns: %{user: user}} = conn, %{"id" => id}) do def unbookmark(%{assigns: %{user: user}} = conn, %{id: id}) do
with %Activity{} = activity <- Activity.get_by_id_with_object(id), with %Activity{} = activity <- Activity.get_by_id_with_object(id),
%User{} = user <- User.get_cached_by_nickname(user.nickname), %User{} = user <- User.get_cached_by_nickname(user.nickname),
true <- Visibility.visible_for_user?(activity, user), true <- Visibility.visible_for_user?(activity, user),
@ -264,7 +276,7 @@ def unbookmark(%{assigns: %{user: user}} = conn, %{"id" => id}) do
end end
@doc "POST /api/v1/statuses/:id/mute" @doc "POST /api/v1/statuses/:id/mute"
def mute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do def mute_conversation(%{assigns: %{user: user}} = conn, %{id: id}) do
with %Activity{} = activity <- Activity.get_by_id(id), with %Activity{} = activity <- Activity.get_by_id(id),
{:ok, activity} <- CommonAPI.add_mute(user, activity) do {:ok, activity} <- CommonAPI.add_mute(user, activity) do
try_render(conn, "show.json", activity: activity, for: user, as: :activity) try_render(conn, "show.json", activity: activity, for: user, as: :activity)
@ -272,7 +284,7 @@ def mute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
end end
@doc "POST /api/v1/statuses/:id/unmute" @doc "POST /api/v1/statuses/:id/unmute"
def unmute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do def unmute_conversation(%{assigns: %{user: user}} = conn, %{id: id}) do
with %Activity{} = activity <- Activity.get_by_id(id), with %Activity{} = activity <- Activity.get_by_id(id),
{:ok, activity} <- CommonAPI.remove_mute(user, activity) do {:ok, activity} <- CommonAPI.remove_mute(user, activity) do
try_render(conn, "show.json", activity: activity, for: user, as: :activity) try_render(conn, "show.json", activity: activity, for: user, as: :activity)
@ -281,7 +293,7 @@ def unmute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
@doc "GET /api/v1/statuses/:id/card" @doc "GET /api/v1/statuses/:id/card"
@deprecated "https://github.com/tootsuite/mastodon/pull/11213" @deprecated "https://github.com/tootsuite/mastodon/pull/11213"
def card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do def card(%{assigns: %{user: user}} = conn, %{id: status_id}) do
with %Activity{} = activity <- Activity.get_by_id(status_id), with %Activity{} = activity <- Activity.get_by_id(status_id),
true <- Visibility.visible_for_user?(activity, user) do true <- Visibility.visible_for_user?(activity, user) do
data = Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) data = Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
@ -292,7 +304,7 @@ def card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do
end end
@doc "GET /api/v1/statuses/:id/favourited_by" @doc "GET /api/v1/statuses/:id/favourited_by"
def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do def favourited_by(%{assigns: %{user: user}} = conn, %{id: id}) do
with %Activity{} = activity <- Activity.get_by_id_with_object(id), with %Activity{} = activity <- Activity.get_by_id_with_object(id),
{:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)}, {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)},
%Object{data: %{"likes" => likes}} <- Object.normalize(activity) do %Object{data: %{"likes" => likes}} <- Object.normalize(activity) do
@ -312,7 +324,7 @@ def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
end end
@doc "GET /api/v1/statuses/:id/reblogged_by" @doc "GET /api/v1/statuses/:id/reblogged_by"
def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do def reblogged_by(%{assigns: %{user: user}} = conn, %{id: id}) do
with %Activity{} = activity <- Activity.get_by_id_with_object(id), with %Activity{} = activity <- Activity.get_by_id_with_object(id),
{:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)}, {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)},
%Object{data: %{"announcements" => announces, "id" => ap_id}} <- %Object{data: %{"announcements" => announces, "id" => ap_id}} <-
@ -344,7 +356,7 @@ def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
end end
@doc "GET /api/v1/statuses/:id/context" @doc "GET /api/v1/statuses/:id/context"
def context(%{assigns: %{user: user}} = conn, %{"id" => id}) do def context(%{assigns: %{user: user}} = conn, %{id: id}) do
with %Activity{} = activity <- Activity.get_by_id(id) do with %Activity{} = activity <- Activity.get_by_id(id) do
activities = activities =
ActivityPub.fetch_activities_for_context(activity.data["context"], %{ ActivityPub.fetch_activities_for_context(activity.data["context"], %{
@ -359,11 +371,12 @@ def context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
@doc "GET /api/v1/favourites" @doc "GET /api/v1/favourites"
def favourites(%{assigns: %{user: %User{} = user}} = conn, params) do def favourites(%{assigns: %{user: %User{} = user}} = conn, params) do
activities = params =
ActivityPub.fetch_favourites( params
user, |> Map.new(fn {key, value} -> {to_string(key), value} end)
Map.take(params, Pleroma.Pagination.page_keys()) |> Map.take(Pleroma.Pagination.page_keys())
)
activities = ActivityPub.fetch_favourites(user, params)
conn conn
|> add_link_headers(activities) |> add_link_headers(activities)

View file

@ -5,11 +5,26 @@
defmodule Pleroma.Web.MastodonAPI.SuggestionController do defmodule Pleroma.Web.MastodonAPI.SuggestionController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.Plugs.OAuthScopesPlug
require Logger require Logger
plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :index) plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["read"]} when action == :index)
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
def index_operation do
%OpenApiSpex.Operation{
tags: ["Suggestions"],
summary: "Follow suggestions (Not implemented)",
operationId: "SuggestionController.index",
responses: %{
200 => Pleroma.Web.ApiSpec.Helpers.empty_array_response()
}
}
end
@doc "GET /api/v1/suggestions" @doc "GET /api/v1/suggestions"
def index(conn, params), def index(conn, params),

View file

@ -6,7 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, import Pleroma.Web.ControllerHelper,
only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1, skip_relationships?: 1] only: [add_link_headers: 2, add_link_headers: 3, skip_relationships?: 1]
alias Pleroma.Pagination alias Pleroma.Pagination
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
@ -15,6 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(:skip_plug, EnsurePublicOrAuthenticatedPlug when action in [:public, :hashtag]) plug(:skip_plug, EnsurePublicOrAuthenticatedPlug when action in [:public, :hashtag])
# TODO: Replace with a macro when there is a Phoenix release with the following commit in it: # TODO: Replace with a macro when there is a Phoenix release with the following commit in it:
@ -37,10 +38,13 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
plug(:put_view, Pleroma.Web.MastodonAPI.StatusView) plug(:put_view, Pleroma.Web.MastodonAPI.StatusView)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.TimelineOperation
# GET /api/v1/timelines/home # GET /api/v1/timelines/home
def home(%{assigns: %{user: user}} = conn, params) do def home(%{assigns: %{user: user}} = conn, params) do
params = params =
params params
|> Map.new(fn {key, value} -> {to_string(key), value} end)
|> Map.put("type", ["Create", "Announce"]) |> Map.put("type", ["Create", "Announce"])
|> Map.put("blocking_user", user) |> Map.put("blocking_user", user)
|> Map.put("muting_user", user) |> Map.put("muting_user", user)
@ -68,6 +72,7 @@ def home(%{assigns: %{user: user}} = conn, params) do
def direct(%{assigns: %{user: user}} = conn, params) do def direct(%{assigns: %{user: user}} = conn, params) do
params = params =
params params
|> Map.new(fn {key, value} -> {to_string(key), value} end)
|> Map.put("type", "Create") |> Map.put("type", "Create")
|> Map.put("blocking_user", user) |> Map.put("blocking_user", user)
|> Map.put("user", user) |> Map.put("user", user)
@ -90,7 +95,9 @@ def direct(%{assigns: %{user: user}} = conn, params) do
# GET /api/v1/timelines/public # GET /api/v1/timelines/public
def public(%{assigns: %{user: user}} = conn, params) do def public(%{assigns: %{user: user}} = conn, params) do
local_only = truthy_param?(params["local"]) params = Map.new(params, fn {key, value} -> {to_string(key), value} end)
local_only = params["local"]
cfg_key = cfg_key =
if local_only do if local_only do
@ -157,8 +164,8 @@ defp hashtag_fetching(params, user, local_only) do
# GET /api/v1/timelines/tag/:tag # GET /api/v1/timelines/tag/:tag
def hashtag(%{assigns: %{user: user}} = conn, params) do def hashtag(%{assigns: %{user: user}} = conn, params) do
local_only = truthy_param?(params["local"]) params = Map.new(params, fn {key, value} -> {to_string(key), value} end)
local_only = params["local"]
activities = hashtag_fetching(params, user, local_only) activities = hashtag_fetching(params, user, local_only)
conn conn
@ -172,10 +179,11 @@ def hashtag(%{assigns: %{user: user}} = conn, params) do
end end
# GET /api/v1/timelines/list/:list_id # GET /api/v1/timelines/list/:list_id
def list(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params) do def list(%{assigns: %{user: user}} = conn, %{list_id: id} = params) do
with %Pleroma.List{title: _title, following: following} <- Pleroma.List.get(id, user) do with %Pleroma.List{title: _title, following: following} <- Pleroma.List.get(id, user) do
params = params =
params params
|> Map.new(fn {key, value} -> {to_string(key), value} end)
|> Map.put("type", "Create") |> Map.put("type", "Create")
|> Map.put("blocking_user", user) |> Map.put("blocking_user", user)
|> Map.put("user", user) |> Map.put("user", user)

View file

@ -261,7 +261,10 @@ defp do_render("show.json", %{user: user} = opts) do
defp prepare_user_bio(%User{bio: ""}), do: "" defp prepare_user_bio(%User{bio: ""}), do: ""
defp prepare_user_bio(%User{bio: bio}) when is_binary(bio) do defp prepare_user_bio(%User{bio: bio}) when is_binary(bio) do
bio |> String.replace(~r(<br */?>), "\n") |> Pleroma.HTML.strip_tags() bio
|> String.replace(~r(<br */?>), "\n")
|> Pleroma.HTML.strip_tags()
|> HtmlEntities.decode()
end end
defp prepare_user_bio(_), do: "" defp prepare_user_bio(_), do: ""
@ -334,7 +337,11 @@ defp maybe_put_role(data, %User{id: user_id} = user, %User{id: user_id}) do
defp maybe_put_role(data, _, _), do: data defp maybe_put_role(data, _, _), do: data
defp maybe_put_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do defp maybe_put_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do
Kernel.put_in(data, [:pleroma, :notification_settings], user.notification_settings) Kernel.put_in(
data,
[:pleroma, :notification_settings],
Map.from_struct(user.notification_settings)
)
end end
defp maybe_put_notification_settings(data, _, _), do: data defp maybe_put_notification_settings(data, _, _), do: data

View file

@ -12,31 +12,19 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
@behaviour :cowboy_websocket @behaviour :cowboy_websocket
# Client ping period.
@tick :timer.seconds(30)
# Cowboy timeout period. # Cowboy timeout period.
@timeout :timer.seconds(30) @timeout :timer.seconds(60)
# Hibernate every X messages # Hibernate every X messages
@hibernate_every 100 @hibernate_every 100
@streams [
"public",
"public:local",
"public:media",
"public:local:media",
"user",
"user:notification",
"direct",
"list",
"hashtag"
]
@anonymous_streams ["public", "public:local", "hashtag"]
def init(%{qs: qs} = req, state) do def init(%{qs: qs} = req, state) do
with params <- :cow_qs.parse_qs(qs), with params <- Enum.into(:cow_qs.parse_qs(qs), %{}),
sec_websocket <- :cowboy_req.header("sec-websocket-protocol", req, nil), sec_websocket <- :cowboy_req.header("sec-websocket-protocol", req, nil),
access_token <- List.keyfind(params, "access_token", 0), access_token <- Map.get(params, "access_token"),
{_, stream} <- List.keyfind(params, "stream", 0), {:ok, user} <- authenticate_request(access_token, sec_websocket),
{:ok, user} <- allow_request(stream, [access_token, sec_websocket]), {:ok, topic} <- Streamer.get_topic(Map.get(params, "stream"), user, params) do
topic when is_binary(topic) <- expand_topic(stream, params) do
req = req =
if sec_websocket do if sec_websocket do
:cowboy_req.set_resp_header("sec-websocket-protocol", sec_websocket, req) :cowboy_req.set_resp_header("sec-websocket-protocol", sec_websocket, req)
@ -44,16 +32,17 @@ def init(%{qs: qs} = req, state) do
req req
end end
{:cowboy_websocket, req, %{user: user, topic: topic, count: 0}, %{idle_timeout: @timeout}} {:cowboy_websocket, req, %{user: user, topic: topic, count: 0, timer: nil},
%{idle_timeout: @timeout}}
else else
{:error, code} -> {:error, :bad_topic} ->
Logger.debug("#{__MODULE__} denied connection: #{inspect(code)} - #{inspect(req)}") Logger.debug("#{__MODULE__} bad topic #{inspect(req)}")
{:ok, req} = :cowboy_req.reply(code, req) {:ok, req} = :cowboy_req.reply(404, req)
{:ok, req, state} {:ok, req, state}
error -> {:error, :unauthorized} ->
Logger.debug("#{__MODULE__} denied connection: #{inspect(error)} - #{inspect(req)}") Logger.debug("#{__MODULE__} authentication error: #{inspect(req)}")
{:ok, req} = :cowboy_req.reply(400, req) {:ok, req} = :cowboy_req.reply(401, req)
{:ok, req, state} {:ok, req, state}
end end
end end
@ -66,11 +55,18 @@ def websocket_init(state) do
) )
Streamer.add_socket(state.topic, state.user) Streamer.add_socket(state.topic, state.user)
{:ok, state} {:ok, %{state | timer: timer()}}
end
# Client's Pong frame.
def websocket_handle(:pong, state) do
if state.timer, do: Process.cancel_timer(state.timer)
{:ok, %{state | timer: timer()}}
end end
# We never receive messages. # We never receive messages.
def websocket_handle(_frame, state) do def websocket_handle(frame, state) do
Logger.error("#{__MODULE__} received frame: #{inspect(frame)}")
{:ok, state} {:ok, state}
end end
@ -94,6 +90,14 @@ def websocket_info({:text, message}, state) do
end end
end end
# Ping tick. We don't re-queue a timer there, it is instead queued when :pong is received.
# As we hibernate there, reset the count to 0.
# If the client misses :pong, Cowboy will automatically timeout the connection after
# `@idle_timeout`.
def websocket_info(:tick, state) do
{:reply, :ping, %{state | timer: nil, count: 0}, :hibernate}
end
def terminate(reason, _req, state) do def terminate(reason, _req, state) do
Logger.debug( Logger.debug(
"#{__MODULE__} terminating websocket connection for user #{ "#{__MODULE__} terminating websocket connection for user #{
@ -106,47 +110,24 @@ def terminate(reason, _req, state) do
end end
# Public streams without authentication. # Public streams without authentication.
defp allow_request(stream, [nil, nil]) when stream in @anonymous_streams do defp authenticate_request(nil, nil) do
{:ok, nil} {:ok, nil}
end end
# Authenticated streams. # Authenticated streams.
defp allow_request(stream, [access_token, sec_websocket]) when stream in @streams do defp authenticate_request(access_token, sec_websocket) do
token = token = access_token || sec_websocket
with {"access_token", token} <- access_token do
token
else
_ -> sec_websocket
end
with true <- is_bitstring(token), with true <- is_bitstring(token),
%Token{user_id: user_id} <- Repo.get_by(Token, token: token), %Token{user_id: user_id} <- Repo.get_by(Token, token: token),
user = %User{} <- User.get_cached_by_id(user_id) do user = %User{} <- User.get_cached_by_id(user_id) do
{:ok, user} {:ok, user}
else else
_ -> {:error, 403} _ -> {:error, :unauthorized}
end end
end end
# Not authenticated. defp timer do
defp allow_request(stream, _) when stream in @streams, do: {:error, 403} Process.send_after(self(), :tick, @tick)
# No matching stream.
defp allow_request(_, _), do: {:error, 404}
defp expand_topic("hashtag", params) do
case List.keyfind(params, "tag", 0) do
{_, tag} -> "hashtag:#{tag}"
_ -> nil
end
end end
defp expand_topic("list", params) do
case List.keyfind(params, "list", 0) do
{_, list} -> "list:#{list}"
_ -> nil
end
end
defp expand_topic(topic, _), do: topic
end end

View file

@ -5,7 +5,7 @@
defmodule Pleroma.Web.MongooseIM.MongooseIMController do defmodule Pleroma.Web.MongooseIM.MongooseIMController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Comeonin.Pbkdf2 alias Pleroma.Plugs.AuthenticationPlug
alias Pleroma.Plugs.RateLimiter alias Pleroma.Plugs.RateLimiter
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
@ -28,7 +28,7 @@ def user_exists(conn, %{"user" => username}) do
def check_password(conn, %{"user" => username, "pass" => password}) do def check_password(conn, %{"user" => username, "pass" => password}) do
with %User{password_hash: password_hash, deactivated: false} <- with %User{password_hash: password_hash, deactivated: false} <-
Repo.get_by(User, nickname: username, local: true), Repo.get_by(User, nickname: username, local: true),
true <- Pbkdf2.checkpw(password, password_hash) do true <- AuthenticationPlug.checkpw(password, password_hash) do
conn conn
|> json(true) |> json(true)
else else

View file

@ -18,6 +18,13 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
require Pleroma.Constants require Pleroma.Constants
plug(
OpenApiSpex.Plug.PutApiSpec,
[module: Pleroma.Web.ApiSpec] when action == :confirmation_resend
)
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug( plug(
:skip_plug, :skip_plug,
[OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :confirmation_resend [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :confirmation_resend
@ -49,9 +56,11 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
plug(:assign_account_by_id when action in [:favourites, :subscribe, :unsubscribe]) plug(:assign_account_by_id when action in [:favourites, :subscribe, :unsubscribe])
plug(:put_view, Pleroma.Web.MastodonAPI.AccountView) plug(:put_view, Pleroma.Web.MastodonAPI.AccountView)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaAccountOperation
@doc "POST /api/v1/pleroma/accounts/confirmation_resend" @doc "POST /api/v1/pleroma/accounts/confirmation_resend"
def confirmation_resend(conn, params) do def confirmation_resend(conn, params) do
nickname_or_email = params["email"] || params["nickname"] nickname_or_email = params[:email] || params[:nickname]
with %User{} = user <- User.get_by_nickname_or_email(nickname_or_email), with %User{} = user <- User.get_by_nickname_or_email(nickname_or_email),
{:ok, _} <- User.try_send_confirmation_email(user) do {:ok, _} <- User.try_send_confirmation_email(user) do
@ -60,7 +69,7 @@ def confirmation_resend(conn, params) do
end end
@doc "PATCH /api/v1/pleroma/accounts/update_avatar" @doc "PATCH /api/v1/pleroma/accounts/update_avatar"
def update_avatar(%{assigns: %{user: user}} = conn, %{"img" => ""}) do def update_avatar(%{assigns: %{user: user}, body_params: %{img: ""}} = conn, _) do
{:ok, _user} = {:ok, _user} =
user user
|> Changeset.change(%{avatar: nil}) |> Changeset.change(%{avatar: nil})
@ -69,7 +78,7 @@ def update_avatar(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
json(conn, %{url: nil}) json(conn, %{url: nil})
end end
def update_avatar(%{assigns: %{user: user}} = conn, params) do def update_avatar(%{assigns: %{user: user}, body_params: params} = conn, _params) do
{:ok, %{data: data}} = ActivityPub.upload(params, type: :avatar) {:ok, %{data: data}} = ActivityPub.upload(params, type: :avatar)
{:ok, _user} = user |> Changeset.change(%{avatar: data}) |> User.update_and_set_cache() {:ok, _user} = user |> Changeset.change(%{avatar: data}) |> User.update_and_set_cache()
%{"url" => [%{"href" => href} | _]} = data %{"url" => [%{"href" => href} | _]} = data
@ -78,14 +87,14 @@ def update_avatar(%{assigns: %{user: user}} = conn, params) do
end end
@doc "PATCH /api/v1/pleroma/accounts/update_banner" @doc "PATCH /api/v1/pleroma/accounts/update_banner"
def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do def update_banner(%{assigns: %{user: user}, body_params: %{banner: ""}} = conn, _) do
with {:ok, _user} <- User.update_banner(user, %{}) do with {:ok, _user} <- User.update_banner(user, %{}) do
json(conn, %{url: nil}) json(conn, %{url: nil})
end end
end end
def update_banner(%{assigns: %{user: user}} = conn, params) do def update_banner(%{assigns: %{user: user}, body_params: params} = conn, _) do
with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner), with {:ok, object} <- ActivityPub.upload(%{img: params[:banner]}, type: :banner),
{:ok, _user} <- User.update_banner(user, object.data) do {:ok, _user} <- User.update_banner(user, object.data) do
%{"url" => [%{"href" => href} | _]} = object.data %{"url" => [%{"href" => href} | _]} = object.data
@ -94,13 +103,13 @@ def update_banner(%{assigns: %{user: user}} = conn, params) do
end end
@doc "PATCH /api/v1/pleroma/accounts/update_background" @doc "PATCH /api/v1/pleroma/accounts/update_background"
def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do def update_background(%{assigns: %{user: user}, body_params: %{img: ""}} = conn, _) do
with {:ok, _user} <- User.update_background(user, %{}) do with {:ok, _user} <- User.update_background(user, %{}) do
json(conn, %{url: nil}) json(conn, %{url: nil})
end end
end end
def update_background(%{assigns: %{user: user}} = conn, params) do def update_background(%{assigns: %{user: user}, body_params: params} = conn, _) do
with {:ok, object} <- ActivityPub.upload(params, type: :background), with {:ok, object} <- ActivityPub.upload(params, type: :background),
{:ok, _user} <- User.update_background(user, object.data) do {:ok, _user} <- User.update_background(user, object.data) do
%{"url" => [%{"href" => href} | _]} = object.data %{"url" => [%{"href" => href} | _]} = object.data
@ -117,6 +126,7 @@ def favourites(%{assigns: %{account: %{hide_favorites: true}}} = conn, _params)
def favourites(%{assigns: %{user: for_user, account: user}} = conn, params) do def favourites(%{assigns: %{user: for_user, account: user}} = conn, params) do
params = params =
params params
|> Map.new(fn {key, value} -> {to_string(key), value} end)
|> Map.put("type", "Create") |> Map.put("type", "Create")
|> Map.put("favorited_by", user.ap_id) |> Map.put("favorited_by", user.ap_id)
|> Map.put("blocking_user", for_user) |> Map.put("blocking_user", for_user)

View file

@ -21,12 +21,68 @@ defmodule Pleroma.Web.Streamer do
def registry, do: @registry def registry, do: @registry
def add_socket(topic, %User{} = user) do @public_streams ["public", "public:local", "public:media", "public:local:media"]
if should_env_send?(), do: Registry.register(@registry, user_topic(topic, user), true) @user_streams ["user", "user:notification", "direct"]
@doc "Expands and authorizes a stream, and registers the process for streaming."
@spec get_topic_and_add_socket(stream :: String.t(), User.t() | nil, Map.t() | nil) ::
{:ok, topic :: String.t()} | {:error, :bad_topic} | {:error, :unauthorized}
def get_topic_and_add_socket(stream, user, params \\ %{}) do
case get_topic(stream, user, params) do
{:ok, topic} -> add_socket(topic, user)
error -> error
end
end end
def add_socket(topic, _) do @doc "Expand and authorizes a stream"
if should_env_send?(), do: Registry.register(@registry, topic, false) @spec get_topic(stream :: String.t(), User.t() | nil, Map.t()) ::
{:ok, topic :: String.t()} | {:error, :bad_topic}
def get_topic(stream, user, params \\ %{})
# Allow all public steams.
def get_topic(stream, _, _) when stream in @public_streams do
{:ok, stream}
end
# Allow all hashtags streams.
def get_topic("hashtag", _, %{"tag" => tag}) do
{:ok, "hashtag:" <> tag}
end
# Expand user streams.
def get_topic(stream, %User{} = user, _) when stream in @user_streams do
{:ok, stream <> ":" <> to_string(user.id)}
end
def get_topic(stream, _, _) when stream in @user_streams do
{:error, :unauthorized}
end
# List streams.
def get_topic("list", %User{} = user, %{"list" => id}) do
if Pleroma.List.get(id, user) do
{:ok, "list:" <> to_string(id)}
else
{:error, :bad_topic}
end
end
def get_topic("list", _, _) do
{:error, :unauthorized}
end
def get_topic(_, _, _) do
{:error, :bad_topic}
end
@doc "Registers the process for streaming. Use `get_topic/3` to get the full authorized topic."
def add_socket(topic, user) do
if should_env_send?() do
auth? = if user, do: true
Registry.register(@registry, topic, auth?)
end
{:ok, topic}
end end
def remove_socket(topic) do def remove_socket(topic) do
@ -231,13 +287,4 @@ def should_env_send?, do: false
true -> true ->
def should_env_send?, do: true def should_env_send?, do: true
end end
defp user_topic(topic, user)
when topic in ~w[user user:notification direct] do
"#{topic}:#{user.id}"
end
defp user_topic(topic, _) do
topic
end
end end

View file

@ -30,6 +30,8 @@ def perform(%{"activity_id" => activity_id}, _job) do
end end
defp post_activity(%ScheduledActivity{user_id: user_id, params: params} = scheduled_activity) do defp post_activity(%ScheduledActivity{user_id: user_id, params: params} = scheduled_activity) do
params = Map.new(params, fn {key, value} -> {String.to_existing_atom(key), value} end)
with {:delete, {:ok, _}} <- {:delete, ScheduledActivity.delete(scheduled_activity)}, with {:delete, {:ok, _}} <- {:delete, ScheduledActivity.delete(scheduled_activity)},
{:user, %User{} = user} <- {:user, User.get_cached_by_id(user_id)}, {:user, %User{} = user} <- {:user, User.get_cached_by_id(user_id)},
{:post, {:ok, _}} <- {:post, CommonAPI.post(user, params)} do {:post, {:ok, _}} <- {:post, CommonAPI.post(user, params)} do

17
mix.exs
View file

@ -36,7 +36,7 @@ def project do
releases: [ releases: [
pleroma: [ pleroma: [
include_executables_for: [:unix], include_executables_for: [:unix],
applications: [ex_syslogger: :load, syslog: :load], applications: [ex_syslogger: :load, syslog: :load, eldap: :transient],
steps: [:assemble, &put_otp_version/1, &copy_files/1, &copy_nginx_config/1] steps: [:assemble, &put_otp_version/1, &copy_files/1, &copy_nginx_config/1]
] ]
] ]
@ -72,7 +72,14 @@ def copy_nginx_config(%{path: target_path} = release) do
def application do def application do
[ [
mod: {Pleroma.Application, []}, mod: {Pleroma.Application, []},
extra_applications: [:logger, :runtime_tools, :comeonin, :quack, :fast_sanitize, :ssl], extra_applications: [
:logger,
:runtime_tools,
:comeonin,
:quack,
:fast_sanitize,
:ssl
],
included_applications: [:ex_syslogger] included_applications: [:ex_syslogger]
] ]
end end
@ -119,8 +126,8 @@ defp deps do
{:postgrex, ">= 0.13.5"}, {:postgrex, ">= 0.13.5"},
{:oban, "~> 1.2"}, {:oban, "~> 1.2"},
{:gettext, "~> 0.15"}, {:gettext, "~> 0.15"},
{:comeonin, "~> 4.1.1"}, {:pbkdf2_elixir, "~> 1.0"},
{:pbkdf2_elixir, "~> 0.12.3"}, {:bcrypt_elixir, "~> 2.0"},
{:trailing_format_plug, "~> 0.0.7"}, {:trailing_format_plug, "~> 0.0.7"},
{:fast_sanitize, "~> 0.1"}, {:fast_sanitize, "~> 0.1"},
{:html_entities, "~> 0.5", override: true}, {:html_entities, "~> 0.5", override: true},
@ -193,7 +200,7 @@ defp deps do
{:restarter, path: "./restarter"}, {:restarter, path: "./restarter"},
{:open_api_spex, {:open_api_spex,
git: "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", git: "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git",
ref: "b862ebd78de0df95875cf46feb6e9607130dc2a8"} ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"}
] ++ oauth_deps() ] ++ oauth_deps()
end end

View file

@ -5,6 +5,7 @@
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"}, "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"},
"bbcode": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/bbcode.git", "f2d267675e9a7e1ad1ea9beb4cc23382762b66c2", [ref: "v0.2.0"]}, "bbcode": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/bbcode.git", "f2d267675e9a7e1ad1ea9beb4cc23382762b66c2", [ref: "v0.2.0"]},
"bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"}, "bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"},
"bcrypt_elixir": {:hex, :bcrypt_elixir, "2.2.0", "3df902b81ce7fa8867a2ae30d20a1da6877a2c056bfb116fd0bc8a5f0190cea4", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "762be3fcb779f08207531bc6612cca480a338e4b4357abb49f5ce00240a77d1e"},
"benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm", "3ad58ae787e9c7c94dd7ceda3b587ec2c64604563e049b2a0e8baafae832addb"}, "benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm", "3ad58ae787e9c7c94dd7ceda3b587ec2c64604563e049b2a0e8baafae832addb"},
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
"cachex": {:hex, :cachex, "3.2.0", "a596476c781b0646e6cb5cd9751af2e2974c3e0d5498a8cab71807618b74fe2f", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "aef93694067a43697ae0531727e097754a9e992a1e7946296f5969d6dd9ac986"}, "cachex": {:hex, :cachex, "3.2.0", "a596476c781b0646e6cb5cd9751af2e2974c3e0d5498a8cab71807618b74fe2f", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "aef93694067a43697ae0531727e097754a9e992a1e7946296f5969d6dd9ac986"},
@ -13,7 +14,7 @@
"castore": {:hex, :castore, "0.1.5", "591c763a637af2cc468a72f006878584bc6c306f8d111ef8ba1d4c10e0684010", [:mix], [], "hexpm", "6db356b2bc6cc22561e051ff545c20ad064af57647e436650aa24d7d06cd941a"}, "castore": {:hex, :castore, "0.1.5", "591c763a637af2cc468a72f006878584bc6c306f8d111ef8ba1d4c10e0684010", [:mix], [], "hexpm", "6db356b2bc6cc22561e051ff545c20ad064af57647e436650aa24d7d06cd941a"},
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "805abd97539caf89ec6d4732c91e62ba9da0cda51ac462380bbd28ee697a8c42"}, "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "805abd97539caf89ec6d4732c91e62ba9da0cda51ac462380bbd28ee697a8c42"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"comeonin": {:hex, :comeonin, "4.1.2", "3eb5620fd8e35508991664b4c2b04dd41e52f1620b36957be837c1d7784b7592", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm", "d8700a0ca4dbb616c22c9b3f6dd539d88deaafec3efe66869d6370c9a559b3e9"}, "comeonin": {:hex, :comeonin, "5.3.1", "7fe612b739c78c9c1a75186ef2d322ce4d25032d119823269d0aa1e2f1e20025", [:mix], [], "hexpm", "d6222483060c17f0977fad1b7401ef0c5863c985a64352755f366aee3799c245"},
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"},
"cors_plug": {:hex, :cors_plug, "1.5.2", "72df63c87e4f94112f458ce9d25800900cc88608c1078f0e4faddf20933eda6e", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "9af027d20dc12dd0c4345a6b87247e0c62965871feea0bfecf9764648b02cc69"}, "cors_plug": {:hex, :cors_plug, "1.5.2", "72df63c87e4f94112f458ce9d25800900cc88608c1078f0e4faddf20933eda6e", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "9af027d20dc12dd0c4345a6b87247e0c62965871feea0bfecf9764648b02cc69"},
"cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"}, "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"},
@ -30,6 +31,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_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.3.4", "aa18af12eb875fbcda2f75e608b3bd534ebf020fc4f6448e4672fcdcbb081244", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4 or ~> 3.3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5eccbdbf92e3c6f213007a82d5dbba4cd9bb659d1a21331f89f408e4c0efd7a8"}, "ecto_sql": {:hex, :ecto_sql, "3.3.4", "aa18af12eb875fbcda2f75e608b3bd534ebf020fc4f6448e4672fcdcbb081244", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4 or ~> 3.3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5eccbdbf92e3c6f213007a82d5dbba4cd9bb659d1a21331f89f408e4c0efd7a8"},
"eimp": {:hex, :eimp, "1.0.14", "fc297f0c7e2700457a95a60c7010a5f1dcb768a083b6d53f49cd94ab95a28f22", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "501133f3112079b92d9e22da8b88bf4f0e13d4d67ae9c15c42c30bd25ceb83b6"}, "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.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"},
"esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm", "d73e341e3009d390aa36387dc8862860bf9f874c94d9fd92ade2926376f49981"}, "esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm", "d73e341e3009d390aa36387dc8862860bf9f874c94d9fd92ade2926376f49981"},
"eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm", "b14f1dc204321429479c569cfbe8fb287541184ed040956c8862cb7a677b8406"}, "eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm", "b14f1dc204321429479c569cfbe8fb287541184ed040956c8862cb7a677b8406"},
"ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"}, "ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"},
@ -75,10 +77,10 @@
"nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"}, "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"},
"nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]}, "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
"oban": {:hex, :oban, "1.2.0", "7cca94d341be43d220571e28f69131c4afc21095b25257397f50973d3fc59b07", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ba5f8b3f7d76967b3e23cf8014f6a13e4ccb33431e4808f036709a7f822362ee"}, "oban": {:hex, :oban, "1.2.0", "7cca94d341be43d220571e28f69131c4afc21095b25257397f50973d3fc59b07", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ba5f8b3f7d76967b3e23cf8014f6a13e4ccb33431e4808f036709a7f822362ee"},
"open_api_spex": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", "b862ebd78de0df95875cf46feb6e9607130dc2a8", [ref: "b862ebd78de0df95875cf46feb6e9607130dc2a8"]}, "open_api_spex": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", "f296ac0924ba3cf79c7a588c4c252889df4c2edd", [ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"]},
"p1_utils": {:hex, :p1_utils, "1.0.18", "3fe224de5b2e190d730a3c5da9d6e8540c96484cf4b4692921d1e28f0c32b01c", [:rebar3], [], "hexpm", "1fc8773a71a15553b179c986b22fbeead19b28fe486c332d4929700ffeb71f88"}, "p1_utils": {:hex, :p1_utils, "1.0.18", "3fe224de5b2e190d730a3c5da9d6e8540c96484cf4b4692921d1e28f0c32b01c", [:rebar3], [], "hexpm", "1fc8773a71a15553b179c986b22fbeead19b28fe486c332d4929700ffeb71f88"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.4", "8dd29ed783f2e12195d7e0a4640effc0a7c37e6537da491f1db01839eee6d053", [:mix], [], "hexpm", "595d09db74cb093b1903381c9de423276a931a2480a46a1a5dc7f932a2a6375b"}, "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"},
"phoenix": {:hex, :phoenix, "1.4.13", "67271ad69b51f3719354604f4a3f968f83aa61c19199343656c9caee057ff3b8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ab765a0feddb81fc62e2116c827b5f068df85159c162bee760745276ad7ddc1b"}, "phoenix": {:hex, :phoenix, "1.4.13", "67271ad69b51f3719354604f4a3f968f83aa61c19199343656c9caee057ff3b8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ab765a0feddb81fc62e2116c827b5f068df85159c162bee760745276ad7ddc1b"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"},
"phoenix_html": {:hex, :phoenix_html, "2.14.0", "d8c6bc28acc8e65f8ea0080ee05aa13d912c8758699283b8d3427b655aabe284", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b0bb30eda478a06dbfbe96728061a93833db3861a49ccb516f839ecb08493fbb"}, "phoenix_html": {:hex, :phoenix_html, "2.14.0", "d8c6bc28acc8e65f8ea0080ee05aa13d912c8758699283b8d3427b655aabe284", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b0bb30eda478a06dbfbe96728061a93833db3861a49ccb516f839ecb08493fbb"},

View file

@ -1,465 +0,0 @@
## `msgid`s in this file come from POT (.pot) files.
##
## Do not add, change, or remove `msgid`s manually here as
## they're tied to the ones in the corresponding POT file
## (with the same domain).
##
## Use `mix gettext.extract --merge` or `mix gettext.merge`
## to merge POT files into PO files.
msgid ""
msgstr ""
"Language: en\n"
## From Ecto.Changeset.cast/4
msgid "can't be blank"
msgstr ""
## From Ecto.Changeset.unique_constraint/3
msgid "has already been taken"
msgstr ""
## From Ecto.Changeset.put_change/3
msgid "is invalid"
msgstr ""
## From Ecto.Changeset.validate_format/3
msgid "has invalid format"
msgstr ""
## From Ecto.Changeset.validate_subset/3
msgid "has an invalid entry"
msgstr ""
## From Ecto.Changeset.validate_exclusion/3
msgid "is reserved"
msgstr ""
## From Ecto.Changeset.validate_confirmation/3
msgid "does not match confirmation"
msgstr ""
## From Ecto.Changeset.no_assoc_constraint/3
msgid "is still associated with this entry"
msgstr ""
msgid "are still associated with this entry"
msgstr ""
## From Ecto.Changeset.validate_length/3
msgid "should be %{count} character(s)"
msgid_plural "should be %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should have %{count} item(s)"
msgid_plural "should have %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be at least %{count} character(s)"
msgid_plural "should be at least %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should have at least %{count} item(s)"
msgid_plural "should have at least %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be at most %{count} character(s)"
msgid_plural "should be at most %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should have at most %{count} item(s)"
msgid_plural "should have at most %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
## From Ecto.Changeset.validate_number/3
msgid "must be less than %{number}"
msgstr ""
msgid "must be greater than %{number}"
msgstr ""
msgid "must be less than or equal to %{number}"
msgstr ""
msgid "must be greater than or equal to %{number}"
msgstr ""
msgid "must be equal to %{number}"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:381
msgid "Account not found"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:153
msgid "Already voted"
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:263
msgid "Bad request"
msgstr ""
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:254
msgid "Can't delete object"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:569
msgid "Can't delete this post"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1731
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1737
msgid "Can't display this activity"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:195
msgid "Can't find user"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1148
msgid "Can't get favorites"
msgstr ""
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:263
msgid "Can't like object"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:518
msgid "Cannot post an empty status without attachments"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:461
msgid "Comment must be up to %{max_size} characters"
msgstr ""
#, elixir-format
#: lib/pleroma/web/admin_api/config.ex:63
msgid "Config with params %{params} not found"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:78
msgid "Could not delete"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:110
msgid "Could not favorite"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:310
msgid "Could not pin"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:89
msgid "Could not repeat"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:120
msgid "Could not unfavorite"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:327
msgid "Could not unpin"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:99
msgid "Could not unrepeat"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:392
msgid "Could not update state"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1271
msgid "Error."
msgstr ""
#, elixir-format
#: lib/pleroma/captcha/kocaptcha.ex:36
msgid "Invalid CAPTCHA"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1700
#: lib/pleroma/web/oauth/oauth_controller.ex:465
msgid "Invalid credentials"
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:20
msgid "Invalid credentials."
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:154
msgid "Invalid indices"
msgstr ""
#, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:411
msgid "Invalid parameters"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:377
msgid "Invalid password."
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:163
msgid "Invalid request"
msgstr ""
#, elixir-format
#: lib/pleroma/captcha/kocaptcha.ex:16
msgid "Kocaptcha service unavailable"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1696
msgid "Missing parameters"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:496
msgid "No such conversation"
msgstr ""
#, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:163
#: lib/pleroma/web/admin_api/admin_api_controller.ex:206
msgid "No such permission_group"
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/uploaded_media.ex:69
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:311
#: lib/pleroma/web/admin_api/admin_api_controller.ex:399
#: lib/pleroma/web/mastodon_api/subscription_controller.ex:63
#: lib/pleroma/web/ostatus/ostatus_controller.ex:248
msgid "Not found"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:152
msgid "Poll's author can't vote"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:443
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:444
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:473
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:476
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1180
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1564
msgid "Record not found"
msgstr ""
#, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:417
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1570
#: lib/pleroma/web/mastodon_api/subscription_controller.ex:69
#: lib/pleroma/web/ostatus/ostatus_controller.ex:252
msgid "Something went wrong"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:253
msgid "The message visibility must be direct"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:521
msgid "The status is over the character limit"
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:27
msgid "This resource requires authentication."
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/rate_limiter.ex:89
msgid "Throttled"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:155
msgid "Too many choices"
msgstr ""
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:268
msgid "Unhandled activity type"
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/user_is_admin_plug.ex:20
msgid "User is not admin."
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:380
msgid "Valid `account_id` required"
msgstr ""
#, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:185
msgid "You can't revoke your own admin status."
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:216
msgid "Your account is currently disabled"
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:158
#: lib/pleroma/web/oauth/oauth_controller.ex:213
msgid "Your login is missing a confirmed e-mail address"
msgstr ""
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:221
msgid "can't read inbox of %{nickname} as %{as_nickname}"
msgstr ""
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:297
msgid "can't update outbox of %{nickname} as %{as_nickname}"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:335
msgid "conversation is already muted"
msgstr ""
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:192
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:317
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1196
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1247
msgid "error"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:789
msgid "mascots can only be images"
msgstr ""
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:34
msgid "not found"
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:298
msgid "Bad OAuth request."
msgstr ""
#, elixir-format
#: lib/pleroma/captcha/captcha.ex:92
msgid "CAPTCHA already used"
msgstr ""
#, elixir-format
#: lib/pleroma/captcha/captcha.ex:89
msgid "CAPTCHA expired"
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/uploaded_media.ex:50
msgid "Failed"
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:314
msgid "Failed to authenticate: %{message}."
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:345
msgid "Failed to set up user account."
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/oauth_scopes_plug.ex:37
msgid "Insufficient permissions: %{permissions}."
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/uploaded_media.ex:89
msgid "Internal Error"
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/fallback_controller.ex:22
#: lib/pleroma/web/oauth/fallback_controller.ex:29
msgid "Invalid Username/Password"
msgstr ""
#, elixir-format
#: lib/pleroma/captcha/captcha.ex:107
msgid "Invalid answer data"
msgstr ""
#, elixir-format
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:204
msgid "Nodeinfo schema version not handled"
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:145
msgid "This action is outside the authorized scopes"
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/fallback_controller.ex:14
msgid "Unknown error, please check the details and try again."
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:93
#: lib/pleroma/web/oauth/oauth_controller.ex:131
msgid "Unlisted redirect_uri."
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:294
msgid "Unsupported OAuth provider: %{provider}."
msgstr ""
#, elixir-format
#: lib/pleroma/uploaders/uploader.ex:71
msgid "Uploader callback timeout"
msgstr ""
#, elixir-format
#: lib/pleroma/web/uploader_controller.ex:11
#: lib/pleroma/web/uploader_controller.ex:23
msgid "bad request"
msgstr ""

View file

@ -90,326 +90,312 @@ msgid "must be equal to %{number}"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:381 #: lib/pleroma/web/common_api/common_api.ex:421
msgid "Account not found" msgid "Account not found"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:153 #: lib/pleroma/web/common_api/common_api.ex:249
msgid "Already voted" msgid "Already voted"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:263 #: lib/pleroma/web/oauth/oauth_controller.ex:360
msgid "Bad request" msgid "Bad request"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:254 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:425
msgid "Can't delete object" msgid "Can't delete object"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:569 #: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:196
msgid "Can't delete this post" msgid "Can't delete this post"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1731 #: lib/pleroma/web/controller_helper.ex:95
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1737 #: lib/pleroma/web/controller_helper.ex:101
msgid "Can't display this activity" msgid "Can't display this activity"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:195 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:227
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:254
msgid "Can't find user" msgid "Can't find user"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1148 #: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:114
msgid "Can't get favorites" msgid "Can't get favorites"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:263 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:437
msgid "Can't like object" msgid "Can't like object"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/utils.ex:518 #: lib/pleroma/web/common_api/utils.ex:556
msgid "Cannot post an empty status without attachments" msgid "Cannot post an empty status without attachments"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/utils.ex:461 #: lib/pleroma/web/common_api/utils.ex:504
msgid "Comment must be up to %{max_size} characters" msgid "Comment must be up to %{max_size} characters"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/admin_api/config.ex:63 #: lib/pleroma/config/config_db.ex:222
msgid "Config with params %{params} not found" msgid "Config with params %{params} not found"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:78 #: lib/pleroma/web/common_api/common_api.ex:95
msgid "Could not delete" msgid "Could not delete"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:110 #: lib/pleroma/web/common_api/common_api.ex:141
msgid "Could not favorite" msgid "Could not favorite"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:310 #: lib/pleroma/web/common_api/common_api.ex:370
msgid "Could not pin" msgid "Could not pin"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:89 #: lib/pleroma/web/common_api/common_api.ex:112
msgid "Could not repeat" msgid "Could not repeat"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:120 #: lib/pleroma/web/common_api/common_api.ex:188
msgid "Could not unfavorite" msgid "Could not unfavorite"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:327 #: lib/pleroma/web/common_api/common_api.ex:380
msgid "Could not unpin" msgid "Could not unpin"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:99 #: lib/pleroma/web/common_api/common_api.ex:126
msgid "Could not unrepeat" msgid "Could not unrepeat"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:392 #: lib/pleroma/web/common_api/common_api.ex:428
#: lib/pleroma/web/common_api/common_api.ex:437
msgid "Could not update state" msgid "Could not update state"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1271 #: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:202
msgid "Error." msgid "Error."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/captcha/kocaptcha.ex:36 #: lib/pleroma/web/twitter_api/twitter_api.ex:106
msgid "Invalid CAPTCHA" msgid "Invalid CAPTCHA"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1700 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:117
#: lib/pleroma/web/oauth/oauth_controller.ex:465 #: lib/pleroma/web/oauth/oauth_controller.ex:569
msgid "Invalid credentials" msgid "Invalid credentials"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:20 #: lib/pleroma/plugs/ensure_authenticated_plug.ex:38
msgid "Invalid credentials." msgid "Invalid credentials."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:154 #: lib/pleroma/web/common_api/common_api.ex:265
msgid "Invalid indices" msgid "Invalid indices"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:411 #: lib/pleroma/web/admin_api/admin_api_controller.ex:1147
msgid "Invalid parameters" msgid "Invalid parameters"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/utils.ex:377 #: lib/pleroma/web/common_api/utils.ex:411
msgid "Invalid password." msgid "Invalid password."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:163 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:187
msgid "Invalid request" msgid "Invalid request"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/captcha/kocaptcha.ex:16 #: lib/pleroma/web/twitter_api/twitter_api.ex:109
msgid "Kocaptcha service unavailable" msgid "Kocaptcha service unavailable"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1696 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:113
msgid "Missing parameters" msgid "Missing parameters"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/utils.ex:496 #: lib/pleroma/web/common_api/utils.ex:540
msgid "No such conversation" msgid "No such conversation"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:163 #: lib/pleroma/web/admin_api/admin_api_controller.ex:439
#: lib/pleroma/web/admin_api/admin_api_controller.ex:206 #: lib/pleroma/web/admin_api/admin_api_controller.ex:465 lib/pleroma/web/admin_api/admin_api_controller.ex:507
msgid "No such permission_group" msgid "No such permission_group"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/plugs/uploaded_media.ex:69 #: lib/pleroma/plugs/uploaded_media.ex:74
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:311 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:485 lib/pleroma/web/admin_api/admin_api_controller.ex:1135
#: lib/pleroma/web/admin_api/admin_api_controller.ex:399 #: lib/pleroma/web/feed/user_controller.ex:73 lib/pleroma/web/ostatus/ostatus_controller.ex:143
#: lib/pleroma/web/mastodon_api/subscription_controller.ex:63
#: lib/pleroma/web/ostatus/ostatus_controller.ex:248
msgid "Not found" msgid "Not found"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:152 #: lib/pleroma/web/common_api/common_api.ex:241
msgid "Poll's author can't vote" msgid "Poll's author can't vote"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:443 #: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:444 #: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:473 #: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:50 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:290
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:476 #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1180
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1564
msgid "Record not found" msgid "Record not found"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:417 #: lib/pleroma/web/admin_api/admin_api_controller.ex:1153
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1570 #: lib/pleroma/web/feed/user_controller.ex:79 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:32
#: lib/pleroma/web/mastodon_api/subscription_controller.ex:69 #: lib/pleroma/web/ostatus/ostatus_controller.ex:149
#: lib/pleroma/web/ostatus/ostatus_controller.ex:252
msgid "Something went wrong" msgid "Something went wrong"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:253 #: lib/pleroma/web/common_api/activity_draft.ex:107
msgid "The message visibility must be direct" msgid "The message visibility must be direct"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/utils.ex:521 #: lib/pleroma/web/common_api/utils.ex:566
msgid "The status is over the character limit" msgid "The status is over the character limit"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:27 #: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31
msgid "This resource requires authentication." msgid "This resource requires authentication."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/plugs/rate_limiter.ex:89 #: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206
msgid "Throttled" msgid "Throttled"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:155 #: lib/pleroma/web/common_api/common_api.ex:266
msgid "Too many choices" msgid "Too many choices"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:268 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:442
msgid "Unhandled activity type" msgid "Unhandled activity type"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/plugs/user_is_admin_plug.ex:20 #: lib/pleroma/web/admin_api/admin_api_controller.ex:536
msgid "User is not admin."
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:380
msgid "Valid `account_id` required"
msgstr ""
#, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:185
msgid "You can't revoke your own admin status." msgid "You can't revoke your own admin status."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:216 #: lib/pleroma/web/oauth/oauth_controller.ex:218
#: lib/pleroma/web/oauth/oauth_controller.ex:309
msgid "Your account is currently disabled" msgid "Your account is currently disabled"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:158 #: lib/pleroma/web/oauth/oauth_controller.ex:180
#: lib/pleroma/web/oauth/oauth_controller.ex:213 #: lib/pleroma/web/oauth/oauth_controller.ex:332
msgid "Your login is missing a confirmed e-mail address" msgid "Your login is missing a confirmed e-mail address"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:221 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:389
msgid "can't read inbox of %{nickname} as %{as_nickname}" msgid "can't read inbox of %{nickname} as %{as_nickname}"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:297 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:472
msgid "can't update outbox of %{nickname} as %{as_nickname}" msgid "can't update outbox of %{nickname} as %{as_nickname}"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:335 #: lib/pleroma/web/common_api/common_api.ex:388
msgid "conversation is already muted" msgid "conversation is already muted"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:192 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:316
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:317 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:491
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1196
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1247
msgid "error" msgid "error"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:789 #: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:29
msgid "mascots can only be images" msgid "mascots can only be images"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:34 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:60
msgid "not found" msgid "not found"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:298 #: lib/pleroma/web/oauth/oauth_controller.ex:395
msgid "Bad OAuth request." msgid "Bad OAuth request."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/captcha/captcha.ex:92 #: lib/pleroma/web/twitter_api/twitter_api.ex:115
msgid "CAPTCHA already used" msgid "CAPTCHA already used"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/captcha/captcha.ex:89 #: lib/pleroma/web/twitter_api/twitter_api.ex:112
msgid "CAPTCHA expired" msgid "CAPTCHA expired"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/plugs/uploaded_media.ex:50 #: lib/pleroma/plugs/uploaded_media.ex:55
msgid "Failed" msgid "Failed"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:314 #: lib/pleroma/web/oauth/oauth_controller.ex:411
msgid "Failed to authenticate: %{message}." msgid "Failed to authenticate: %{message}."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:345 #: lib/pleroma/web/oauth/oauth_controller.ex:442
msgid "Failed to set up user account." msgid "Failed to set up user account."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/plugs/oauth_scopes_plug.ex:37 #: lib/pleroma/plugs/oauth_scopes_plug.ex:38
msgid "Insufficient permissions: %{permissions}." msgid "Insufficient permissions: %{permissions}."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/plugs/uploaded_media.ex:89 #: lib/pleroma/plugs/uploaded_media.ex:94
msgid "Internal Error" msgid "Internal Error"
msgstr "" msgstr ""
@ -420,17 +406,17 @@ msgid "Invalid Username/Password"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/captcha/captcha.ex:107 #: lib/pleroma/web/twitter_api/twitter_api.ex:118
msgid "Invalid answer data" msgid "Invalid answer data"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:204 #: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:128
msgid "Nodeinfo schema version not handled" msgid "Nodeinfo schema version not handled"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:145 #: lib/pleroma/web/oauth/oauth_controller.ex:169
msgid "This action is outside the authorized scopes" msgid "This action is outside the authorized scopes"
msgstr "" msgstr ""
@ -440,23 +426,139 @@ msgid "Unknown error, please check the details and try again."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:93 #: lib/pleroma/web/oauth/oauth_controller.ex:116
#: lib/pleroma/web/oauth/oauth_controller.ex:131 #: lib/pleroma/web/oauth/oauth_controller.ex:155
msgid "Unlisted redirect_uri." msgid "Unlisted redirect_uri."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:294 #: lib/pleroma/web/oauth/oauth_controller.ex:391
msgid "Unsupported OAuth provider: %{provider}." msgid "Unsupported OAuth provider: %{provider}."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/uploaders/uploader.ex:71 #: lib/pleroma/uploaders/uploader.ex:72
msgid "Uploader callback timeout" msgid "Uploader callback timeout"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/uploader_controller.ex:11
#: lib/pleroma/web/uploader_controller.ex:23 #: lib/pleroma/web/uploader_controller.ex:23
msgid "bad request" msgid "bad request"
msgstr "" msgstr ""
#, elixir-format
#: lib/pleroma/web/twitter_api/twitter_api.ex:103
msgid "CAPTCHA Error"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:200
msgid "Could not add reaction emoji"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:211
msgid "Could not remove reaction emoji"
msgstr ""
#, elixir-format
#: lib/pleroma/web/twitter_api/twitter_api.ex:129
msgid "Invalid CAPTCHA (Missing parameter: %{name})"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92
msgid "List not found"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:124
msgid "Missing parameter: %{name}"
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:207
#: lib/pleroma/web/oauth/oauth_controller.ex:322
msgid "Password reset is required"
msgstr ""
#, elixir-format
#: lib/pleroma/tests/auth_test_controller.ex:9
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/admin_api_controller.ex:6
#: lib/pleroma/web/controller_helper.ex:6 lib/pleroma/web/fallback_redirect_controller.ex:6
#: lib/pleroma/web/feed/tag_controller.ex:6 lib/pleroma/web/feed/user_controller.ex:6
#: lib/pleroma/web/mailer/subscription_controller.ex:2 lib/pleroma/web/masto_fe_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/app_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/auth_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/filter_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/instance_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/marker_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex:14 lib/pleroma/web/mastodon_api/controllers/media_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/notification_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/report_controller.ex:8 lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/search_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:7 lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:6 lib/pleroma/web/media_proxy/media_proxy_controller.ex:6
#: lib/pleroma/web/mongooseim/mongoose_im_controller.ex:6 lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:6
#: lib/pleroma/web/oauth/fallback_controller.ex:6 lib/pleroma/web/oauth/mfa_controller.ex:10
#: lib/pleroma/web/oauth/oauth_controller.ex:6 lib/pleroma/web/ostatus/ostatus_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:2
#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex:7 lib/pleroma/web/static_fe/static_fe_controller.ex:6
#: lib/pleroma/web/twitter_api/controllers/password_controller.ex:10 lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex:6
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:6 lib/pleroma/web/twitter_api/twitter_api_controller.ex:6
#: lib/pleroma/web/uploader_controller.ex:6 lib/pleroma/web/web_finger/web_finger_controller.ex:6
msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:28
msgid "Two-factor authentication enabled, you must use a access token."
msgstr ""
#, elixir-format
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:210
msgid "Unexpected error occurred while adding file to pack."
msgstr ""
#, elixir-format
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:138
msgid "Unexpected error occurred while creating pack."
msgstr ""
#, elixir-format
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:278
msgid "Unexpected error occurred while removing file from pack."
msgstr ""
#, elixir-format
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:250
msgid "Unexpected error occurred while updating file in pack."
msgstr ""
#, elixir-format
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:179
msgid "Unexpected error occurred while updating pack metadata."
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/user_is_admin_plug.ex:40
msgid "User is not an admin or OAuth admin scope is not granted."
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
msgid "Web push subscription is disabled on this Pleroma instance"
msgstr ""
#, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:502
msgid "You can't revoke your own admin/moderator status."
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:105
msgid "authorization required for timeline view"
msgstr ""

View file

@ -8,8 +8,16 @@
## to merge POT files into PO files. ## to merge POT files into PO files.
msgid "" msgid ""
msgstr "" msgstr ""
"PO-Revision-Date: 2020-05-12 15:52+0000\n"
"Last-Translator: Haelwenn (lanodan) Monnier "
"<contact+translate.pleroma.social@hacktivis.me>\n"
"Language-Team: French <https://translate.pleroma.social/projects/pleroma/"
"pleroma/fr/>\n"
"Language: fr\n" "Language: fr\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n" "Content-Type: text/plain; charset=UTF-8\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 4.0.4\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "can't be blank" msgid "can't be blank"
msgstr "ne peut être vide" msgstr "ne peut être vide"
@ -35,10 +43,10 @@ msgid "does not match confirmation"
msgstr "ne correspondent pas" msgstr "ne correspondent pas"
msgid "is still associated with this entry" msgid "is still associated with this entry"
msgstr "" msgstr "est toujours associé à cette entrée"
msgid "are still associated with this entry" msgid "are still associated with this entry"
msgstr "" msgstr "sont toujours associés à cette entrée"
msgid "should be %{count} character(s)" msgid "should be %{count} character(s)"
msgid_plural "should be %{count} character(s)" msgid_plural "should be %{count} character(s)"
@ -85,375 +93,375 @@ msgstr "doit être supérieur ou égal à %{number}"
msgid "must be equal to %{number}" msgid "must be equal to %{number}"
msgstr "doit égal à %{number}" msgstr "doit égal à %{number}"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:381 #: lib/pleroma/web/common_api/common_api.ex:381
#, elixir-format
msgid "Account not found" msgid "Account not found"
msgstr "Compte non trouvé" msgstr "Compte non trouvé"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:153 #: lib/pleroma/web/common_api/common_api.ex:153
#, elixir-format
msgid "Already voted" msgid "Already voted"
msgstr "A déjà voté" msgstr "A déjà voté"
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:263 #: lib/pleroma/web/oauth/oauth_controller.ex:263
#, elixir-format
msgid "Bad request" msgid "Bad request"
msgstr "Requête Invalide" msgstr "Requête Invalide"
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:254 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:254
#, elixir-format
msgid "Can't delete object" msgid "Can't delete object"
msgstr "Ne peut supprimer cet objet" msgstr "Ne peut supprimer cet objet"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:569 #: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:569
#, elixir-format
msgid "Can't delete this post" msgid "Can't delete this post"
msgstr "Ne peut supprimer ce message" msgstr "Ne peut supprimer ce message"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1731 #: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1731
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1737 #: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1737
#, elixir-format
msgid "Can't display this activity" msgid "Can't display this activity"
msgstr "Ne peut afficher cette activitée" msgstr "Ne peut afficher cette activitée"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:195 #: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:195
#, elixir-format
msgid "Can't find user" msgid "Can't find user"
msgstr "Compte non trouvé" msgstr "Compte non trouvé"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1148 #: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1148
#, elixir-format
msgid "Can't get favorites" msgid "Can't get favorites"
msgstr "Favoris non trouvables" msgstr "Favoris non trouvables"
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:263 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:263
#, elixir-format
msgid "Can't like object" msgid "Can't like object"
msgstr "Ne peut aimer cet objet" msgstr "Ne peut aimer cet objet"
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:518 #: lib/pleroma/web/common_api/utils.ex:518
#, elixir-format
msgid "Cannot post an empty status without attachments" msgid "Cannot post an empty status without attachments"
msgstr "Ne peut envoyer un status vide sans attachements" msgstr "Ne peut envoyer un status vide sans attachements"
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:461 #: lib/pleroma/web/common_api/utils.ex:461
#, elixir-format
msgid "Comment must be up to %{max_size} characters" msgid "Comment must be up to %{max_size} characters"
msgstr "Le commentaire ne doit faire plus de %{max_size} charactères" msgstr "Le commentaire ne doit faire plus de %{max_size} charactères"
#, elixir-format
#: lib/pleroma/web/admin_api/config.ex:63 #: lib/pleroma/web/admin_api/config.ex:63
#, elixir-format
msgid "Config with params %{params} not found" msgid "Config with params %{params} not found"
msgstr "Configuration avec les paramètres %{params} non trouvée" msgstr "Configuration avec les paramètres %{params} non trouvée"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:78 #: lib/pleroma/web/common_api/common_api.ex:78
#, elixir-format
msgid "Could not delete" msgid "Could not delete"
msgstr "Échec de la suppression" msgstr "Échec de la suppression"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:110 #: lib/pleroma/web/common_api/common_api.ex:110
#, elixir-format
msgid "Could not favorite" msgid "Could not favorite"
msgstr "Échec de mise en favoris" msgstr "Échec de mise en favoris"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:310 #: lib/pleroma/web/common_api/common_api.ex:310
#, elixir-format
msgid "Could not pin" msgid "Could not pin"
msgstr "Échec de l'épinglage" msgstr "Échec de l'épinglage"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:89 #: lib/pleroma/web/common_api/common_api.ex:89
#, elixir-format
msgid "Could not repeat" msgid "Could not repeat"
msgstr "Échec de création la répétition" msgstr "Échec de création la répétition"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:120 #: lib/pleroma/web/common_api/common_api.ex:120
#, elixir-format
msgid "Could not unfavorite" msgid "Could not unfavorite"
msgstr "Échec de suppression des favoris" msgstr "Échec de suppression des favoris"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:327 #: lib/pleroma/web/common_api/common_api.ex:327
#, elixir-format
msgid "Could not unpin" msgid "Could not unpin"
msgstr "Échec du dépinglage" msgstr "Échec du dépinglage"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:99 #: lib/pleroma/web/common_api/common_api.ex:99
#, elixir-format
msgid "Could not unrepeat" msgid "Could not unrepeat"
msgstr "Échec de suppression de la répétition" msgstr "Échec de suppression de la répétition"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:392 #: lib/pleroma/web/common_api/common_api.ex:392
#, elixir-format
msgid "Could not update state" msgid "Could not update state"
msgstr "Échec de la mise à jour du status" msgstr "Échec de la mise à jour du status"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1271 #: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1271
#, elixir-format
msgid "Error." msgid "Error."
msgstr "Erreur." msgstr "Erreur."
#, elixir-format
#: lib/pleroma/captcha/kocaptcha.ex:36 #: lib/pleroma/captcha/kocaptcha.ex:36
#, elixir-format
msgid "Invalid CAPTCHA" msgid "Invalid CAPTCHA"
msgstr "CAPTCHA invalide" msgstr "CAPTCHA invalide"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1700 #: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1700
#: lib/pleroma/web/oauth/oauth_controller.ex:465 #: lib/pleroma/web/oauth/oauth_controller.ex:465
#, elixir-format
msgid "Invalid credentials" msgid "Invalid credentials"
msgstr "Paramètres d'authentification invalides" msgstr "Paramètres d'authentification invalides"
#, elixir-format
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:20 #: lib/pleroma/plugs/ensure_authenticated_plug.ex:20
#, elixir-format
msgid "Invalid credentials." msgid "Invalid credentials."
msgstr "Paramètres d'authentification invalides." msgstr "Paramètres d'authentification invalides."
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:154 #: lib/pleroma/web/common_api/common_api.ex:154
#, elixir-format
msgid "Invalid indices" msgid "Invalid indices"
msgstr "Indices invalides" msgstr "Indices invalides"
#, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:411 #: lib/pleroma/web/admin_api/admin_api_controller.ex:411
#, elixir-format
msgid "Invalid parameters" msgid "Invalid parameters"
msgstr "Paramètres invalides" msgstr "Paramètres invalides"
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:377 #: lib/pleroma/web/common_api/utils.ex:377
#, elixir-format
msgid "Invalid password." msgid "Invalid password."
msgstr "Mot de passe invalide." msgstr "Mot de passe invalide."
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:163 #: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:163
#, elixir-format
msgid "Invalid request" msgid "Invalid request"
msgstr "Requête invalide" msgstr "Requête invalide"
#, elixir-format
#: lib/pleroma/captcha/kocaptcha.ex:16 #: lib/pleroma/captcha/kocaptcha.ex:16
#, elixir-format
msgid "Kocaptcha service unavailable" msgid "Kocaptcha service unavailable"
msgstr "Service Kocaptcha non disponible" msgstr "Service Kocaptcha non disponible"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1696 #: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1696
#, elixir-format
msgid "Missing parameters" msgid "Missing parameters"
msgstr "Paramètres manquants" msgstr "Paramètres manquants"
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:496 #: lib/pleroma/web/common_api/utils.ex:496
#, elixir-format
msgid "No such conversation" msgid "No such conversation"
msgstr "Conversation inconnue" msgstr "Conversation inconnue"
#, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:163 #: lib/pleroma/web/admin_api/admin_api_controller.ex:163
#: lib/pleroma/web/admin_api/admin_api_controller.ex:206 #: lib/pleroma/web/admin_api/admin_api_controller.ex:206
#, elixir-format
msgid "No such permission_group" msgid "No such permission_group"
msgstr "Groupe de permission inconnu" msgstr "Groupe de permission inconnu"
#, elixir-format
#: lib/pleroma/plugs/uploaded_media.ex:69 #: lib/pleroma/plugs/uploaded_media.ex:69
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:311 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:311
#: lib/pleroma/web/admin_api/admin_api_controller.ex:399 #: lib/pleroma/web/admin_api/admin_api_controller.ex:399
#: lib/pleroma/web/mastodon_api/subscription_controller.ex:63 #: lib/pleroma/web/mastodon_api/subscription_controller.ex:63
#: lib/pleroma/web/ostatus/ostatus_controller.ex:248 #: lib/pleroma/web/ostatus/ostatus_controller.ex:248
#, elixir-format
msgid "Not found" msgid "Not found"
msgstr "Non Trouvé" msgstr "Non Trouvé"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:152 #: lib/pleroma/web/common_api/common_api.ex:152
#, elixir-format
msgid "Poll's author can't vote" msgid "Poll's author can't vote"
msgstr "L'auteur·rice d'un sondage ne peut voter" msgstr "L'auteur·rice d'un sondage ne peut voter"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:443 #: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:443
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:444 #: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:444
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:473 #: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:473
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:476 #: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:476
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1180 #: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1180
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1564 #: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1564
#, elixir-format
msgid "Record not found" msgid "Record not found"
msgstr "Enregistrement non trouvé" msgstr "Enregistrement non trouvé"
#, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:417 #: lib/pleroma/web/admin_api/admin_api_controller.ex:417
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1570 #: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1570
#: lib/pleroma/web/mastodon_api/subscription_controller.ex:69 #: lib/pleroma/web/mastodon_api/subscription_controller.ex:69
#: lib/pleroma/web/ostatus/ostatus_controller.ex:252 #: lib/pleroma/web/ostatus/ostatus_controller.ex:252
#, elixir-format
msgid "Something went wrong" msgid "Something went wrong"
msgstr "Erreur inconnue" msgstr "Erreur inconnue"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:253 #: lib/pleroma/web/common_api/common_api.ex:253
#, elixir-format
msgid "The message visibility must be direct" msgid "The message visibility must be direct"
msgstr "La visibilitée du message doit être « direct »" msgstr "La visibilitée du message doit être « direct »"
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:521 #: lib/pleroma/web/common_api/utils.ex:521
#, elixir-format
msgid "The status is over the character limit" msgid "The status is over the character limit"
msgstr "Le status est au-delà de la limite de charactères" msgstr "Le status est au-delà de la limite de charactères"
#, elixir-format
#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:27 #: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:27
#, elixir-format
msgid "This resource requires authentication." msgid "This resource requires authentication."
msgstr "Cette resource nécessite une authentification." msgstr "Cette resource nécessite une authentification."
#, elixir-format
#: lib/pleroma/plugs/rate_limiter.ex:89 #: lib/pleroma/plugs/rate_limiter.ex:89
#, elixir-format
msgid "Throttled" msgid "Throttled"
msgstr "Limité" msgstr "Limité"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:155 #: lib/pleroma/web/common_api/common_api.ex:155
#, elixir-format
msgid "Too many choices" msgid "Too many choices"
msgstr "Trop de choix" msgstr "Trop de choix"
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:268 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:268
#, elixir-format
msgid "Unhandled activity type" msgid "Unhandled activity type"
msgstr "Type d'activitée non-gérée" msgstr "Type d'activitée non-gérée"
#, elixir-format
#: lib/pleroma/plugs/user_is_admin_plug.ex:20 #: lib/pleroma/plugs/user_is_admin_plug.ex:20
#, elixir-format
msgid "User is not admin." msgid "User is not admin."
msgstr "Le compte n'est pas admin." msgstr "Le compte n'est pas admin."
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:380 #: lib/pleroma/web/common_api/common_api.ex:380
#, elixir-format
msgid "Valid `account_id` required" msgid "Valid `account_id` required"
msgstr "Un `account_id` valide est requis" msgstr "Un `account_id` valide est requis"
#, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:185 #: lib/pleroma/web/admin_api/admin_api_controller.ex:185
#, elixir-format
msgid "You can't revoke your own admin status." msgid "You can't revoke your own admin status."
msgstr "Vous ne pouvez révoquer votre propre status d'admin." msgstr "Vous ne pouvez révoquer votre propre status d'admin."
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:216 #: lib/pleroma/web/oauth/oauth_controller.ex:216
#, elixir-format
msgid "Your account is currently disabled" msgid "Your account is currently disabled"
msgstr "Votre compte est actuellement désactivé" msgstr "Votre compte est actuellement désactivé"
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:158 #: lib/pleroma/web/oauth/oauth_controller.ex:158
#: lib/pleroma/web/oauth/oauth_controller.ex:213 #: lib/pleroma/web/oauth/oauth_controller.ex:213
#, elixir-format
msgid "Your login is missing a confirmed e-mail address" msgid "Your login is missing a confirmed e-mail address"
msgstr "Une confirmation de l'addresse de couriel est requise pour l'authentification" msgstr "Une confirmation de l'addresse de couriel est requise pour l'authentification"
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:221 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:221
#, elixir-format
msgid "can't read inbox of %{nickname} as %{as_nickname}" msgid "can't read inbox of %{nickname} as %{as_nickname}"
msgstr "Ne peut lire la boite de réception de %{nickname} en tant que %{as_nickname}" msgstr "Ne peut lire la boite de réception de %{nickname} en tant que %{as_nickname}"
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:297 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:297
#, elixir-format
msgid "can't update outbox of %{nickname} as %{as_nickname}" msgid "can't update outbox of %{nickname} as %{as_nickname}"
msgstr "Ne peut poster dans la boite d'émission de %{nickname} en tant que %{as_nickname}" msgstr "Ne peut poster dans la boite d'émission de %{nickname} en tant que %{as_nickname}"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:335 #: lib/pleroma/web/common_api/common_api.ex:335
#, elixir-format
msgid "conversation is already muted" msgid "conversation is already muted"
msgstr "la conversation est déjà baillonée" msgstr "la conversation est déjà baillonée"
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:192 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:192
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:317 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:317
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1196 #: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1196
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1247 #: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1247
#, elixir-format
msgid "error" msgid "error"
msgstr "erreur" msgstr "erreur"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:789 #: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:789
#, elixir-format
msgid "mascots can only be images" msgid "mascots can only be images"
msgstr "les mascottes ne peuvent être que des images" msgstr "les mascottes ne peuvent être que des images"
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:34 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:34
#, elixir-format
msgid "not found" msgid "not found"
msgstr "non trouvé" msgstr "non trouvé"
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:298 #: lib/pleroma/web/oauth/oauth_controller.ex:298
#, elixir-format
msgid "Bad OAuth request." msgid "Bad OAuth request."
msgstr "Requête OAuth invalide." msgstr "Requête OAuth invalide."
#, elixir-format
#: lib/pleroma/captcha/captcha.ex:92 #: lib/pleroma/captcha/captcha.ex:92
#, elixir-format
msgid "CAPTCHA already used" msgid "CAPTCHA already used"
msgstr "CAPTCHA déjà utilisé" msgstr "CAPTCHA déjà utilisé"
#, elixir-format
#: lib/pleroma/captcha/captcha.ex:89 #: lib/pleroma/captcha/captcha.ex:89
#, elixir-format
msgid "CAPTCHA expired" msgid "CAPTCHA expired"
msgstr "CAPTCHA expiré" msgstr "CAPTCHA expiré"
#, elixir-format
#: lib/pleroma/plugs/uploaded_media.ex:50 #: lib/pleroma/plugs/uploaded_media.ex:50
#, elixir-format
msgid "Failed" msgid "Failed"
msgstr "Échec" msgstr "Échec"
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:314 #: lib/pleroma/web/oauth/oauth_controller.ex:314
msgid "Failed to authenticate: %{message}."
msgstr "Échec de l'authentification: %{message}"
#, elixir-format #, elixir-format
msgid "Failed to authenticate: %{message}."
msgstr "Échec de l'authentification : %{message}."
#: lib/pleroma/web/oauth/oauth_controller.ex:345 #: lib/pleroma/web/oauth/oauth_controller.ex:345
#, elixir-format
msgid "Failed to set up user account." msgid "Failed to set up user account."
msgstr "Échec de création de votre compte." msgstr "Échec de création de votre compte."
#, elixir-format
#: lib/pleroma/plugs/oauth_scopes_plug.ex:37 #: lib/pleroma/plugs/oauth_scopes_plug.ex:37
msgid "Insufficient permissions: %{permissions}."
msgstr "Permissions insuffisantes: %{permissions}."
#, elixir-format #, elixir-format
msgid "Insufficient permissions: %{permissions}."
msgstr "Permissions insuffisantes : %{permissions}."
#: lib/pleroma/plugs/uploaded_media.ex:89 #: lib/pleroma/plugs/uploaded_media.ex:89
#, elixir-format
msgid "Internal Error" msgid "Internal Error"
msgstr "Erreur interne" msgstr "Erreur interne"
#, elixir-format
#: lib/pleroma/web/oauth/fallback_controller.ex:22 #: lib/pleroma/web/oauth/fallback_controller.ex:22
#: lib/pleroma/web/oauth/fallback_controller.ex:29 #: lib/pleroma/web/oauth/fallback_controller.ex:29
#, elixir-format
msgid "Invalid Username/Password" msgid "Invalid Username/Password"
msgstr "Nom d'utilisateur/mot de passe invalide" msgstr "Nom d'utilisateur/mot de passe invalide"
#, elixir-format
#: lib/pleroma/captcha/captcha.ex:107 #: lib/pleroma/captcha/captcha.ex:107
#, elixir-format
msgid "Invalid answer data" msgid "Invalid answer data"
msgstr "Réponse invalide" msgstr "Réponse invalide"
#, elixir-format
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:204 #: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:204
#, elixir-format
msgid "Nodeinfo schema version not handled" msgid "Nodeinfo schema version not handled"
msgstr "Version du schéma nodeinfo non géré" msgstr "Version du schéma nodeinfo non géré"
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:145 #: lib/pleroma/web/oauth/oauth_controller.ex:145
msgid "This action is outside the authorized scopes"
msgstr "Cette action est en dehors des authorisations" # "scopes" ?
#, elixir-format #, elixir-format
msgid "This action is outside the authorized scopes"
msgstr "Cette action est en dehors des authorisations" # "scopes"
#: lib/pleroma/web/oauth/fallback_controller.ex:14 #: lib/pleroma/web/oauth/fallback_controller.ex:14
#, elixir-format
msgid "Unknown error, please check the details and try again." msgid "Unknown error, please check the details and try again."
msgstr "Erreur inconnue, veuillez vérifier les détails et réessayer." msgstr "Erreur inconnue, veuillez vérifier les détails et réessayer."
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:93 #: lib/pleroma/web/oauth/oauth_controller.ex:93
#: lib/pleroma/web/oauth/oauth_controller.ex:131 #: lib/pleroma/web/oauth/oauth_controller.ex:131
#, elixir-format
msgid "Unlisted redirect_uri." msgid "Unlisted redirect_uri."
msgstr "redirect_uri non listé." msgstr "redirect_uri non listé."
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:294 #: lib/pleroma/web/oauth/oauth_controller.ex:294
#, elixir-format
msgid "Unsupported OAuth provider: %{provider}." msgid "Unsupported OAuth provider: %{provider}."
msgstr "Fournisseur OAuth non supporté : %{provider}." msgstr "Fournisseur OAuth non supporté : %{provider}."
#, elixir-format
#: lib/pleroma/uploaders/uploader.ex:71 #: lib/pleroma/uploaders/uploader.ex:71
msgid "Uploader callback timeout"
msgstr ""
## msgstr "Attente écoulée"
#, elixir-format #, elixir-format
msgid "Uploader callback timeout"
msgstr "Temps d'attente du téléverseur écoulé"
## msgstr "Attente écoulée"
#: lib/pleroma/web/uploader_controller.ex:11 #: lib/pleroma/web/uploader_controller.ex:11
#: lib/pleroma/web/uploader_controller.ex:23 #: lib/pleroma/web/uploader_controller.ex:23
#, elixir-format
msgid "bad request" msgid "bad request"
msgstr "requête invalide" msgstr "requête invalide"

View file

@ -32,9 +32,13 @@ defp update_markers do
|> Map.put_new(:updated_at, now) |> Map.put_new(:updated_at, now)
end) end)
Repo.insert_all("markers", markers_attrs, markers_attrs
on_conflict: {:replace, [:last_read_id]}, |> Enum.chunk_every(1000)
conflict_target: [:user_id, :timeline] |> Enum.each(fn markers_attrs_chunked ->
) Repo.insert_all("markers", markers_attrs_chunked,
on_conflict: {:replace, [:last_read_id]},
conflict_target: [:user_id, :timeline]
)
end)
end end
end end

View file

@ -30,7 +30,7 @@ def change do
Repo, Repo,
"select distinct unnest(nonexistent_locals.recipients) from activities, lateral (select array_agg(recipient) as recipients from unnest(activities.recipients) as recipient where recipient similar to '#{ "select distinct unnest(nonexistent_locals.recipients) from activities, lateral (select array_agg(recipient) as recipients from unnest(activities.recipients) as recipient where recipient similar to '#{
instance_uri instance_uri
}/users/[A-Za-z0-9]*' and not(recipient in (select ap_id from users where local = true))) nonexistent_locals;", }/users/[A-Za-z0-9]*' and not(recipient in (select ap_id from users))) nonexistent_locals;",
[], [],
timeout: :infinity timeout: :infinity
) )

View file

@ -1 +1 @@
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,user-scalable=no"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link href=/static/css/vendors~app.18fea621d430000acc27.css rel=stylesheet><link href=/static/css/app.613cef07981cd95ccceb.css rel=stylesheet><link href=/static/fontello.1588419330867.css rel=stylesheet></head><body class=hidden><noscript>To use Pleroma, please enable JavaScript.</noscript><div id=app></div><script type=text/javascript src=/static/js/vendors~app.8aa781e6dd81307f544b.js></script><script type=text/javascript src=/static/js/app.fa89b90e606f4facd209.js></script></body></html> <!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,user-scalable=no"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link href=/static/css/vendors~app.18fea621d430000acc27.css rel=stylesheet><link href=/static/css/app.613cef07981cd95ccceb.css rel=stylesheet><link href=/static/fontello.1588947937982.css rel=stylesheet></head><body class=hidden><noscript>To use Pleroma, please enable JavaScript.</noscript><div id=app></div><script type=text/javascript src=/static/js/vendors~app.561a1c605d1dfb0e6f74.js></script><script type=text/javascript src=/static/js/app.996428ccaaaa7f28cb8d.js></script></body></html>

View file

@ -114,6 +114,8 @@
<glyph glyph-name="thumbs-up-alt" unicode="&#xf164;" d="M143 107q0 15-11 25t-25 11q-15 0-25-11t-11-25q0-15 11-25t25-11q15 0 25 11t11 25z m89 286v-357q0-15-10-25t-26-11h-160q-15 0-25 11t-11 25v357q0 14 11 25t25 10h160q15 0 26-10t10-25z m661 0q0-48-31-83 9-25 9-43 1-42-24-76 9-31 0-66-9-31-31-52 5-62-27-101-36-43-110-44h-72q-37 0-80 9t-68 16-67 22q-69 24-88 25-15 0-25 11t-11 25v357q0 14 10 25t24 11q13 1 42 33t57 67q38 49 56 67 10 10 17 27t10 27 8 34q4 22 7 34t11 29 19 28q10 11 25 11 25 0 46-6t33-15 22-22 14-25 7-28 2-25 1-22q0-21-6-43t-10-33-16-31q-1-4-5-10t-6-13-5-13h155q43 0 75-32t32-75z" horiz-adv-x="928.6" /> <glyph glyph-name="thumbs-up-alt" unicode="&#xf164;" d="M143 107q0 15-11 25t-25 11q-15 0-25-11t-11-25q0-15 11-25t25-11q15 0 25 11t11 25z m89 286v-357q0-15-10-25t-26-11h-160q-15 0-25 11t-11 25v357q0 14 11 25t25 10h160q15 0 26-10t10-25z m661 0q0-48-31-83 9-25 9-43 1-42-24-76 9-31 0-66-9-31-31-52 5-62-27-101-36-43-110-44h-72q-37 0-80 9t-68 16-67 22q-69 24-88 25-15 0-25 11t-11 25v357q0 14 10 25t24 11q13 1 42 33t57 67q38 49 56 67 10 10 17 27t10 27 8 34q4 22 7 34t11 29 19 28q10 11 25 11 25 0 46-6t33-15 22-22 14-25 7-28 2-25 1-22q0-21-6-43t-10-33-16-31q-1-4-5-10t-6-13-5-13h155q43 0 75-32t32-75z" horiz-adv-x="928.6" />
<glyph glyph-name="share" unicode="&#xf1e0;" d="M679 286q74 0 126-53t52-126-52-126-126-53-127 53-52 126q0 7 1 19l-201 100q-51-48-121-48-75 0-127 53t-52 126 52 126 127 53q70 0 121-48l201 100q-1 12-1 19 0 74 52 126t127 53 126-53 52-126-52-126-126-53q-71 0-122 48l-201-100q1-12 1-19t-1-19l201-100q51 48 122 48z" horiz-adv-x="857.1" />
<glyph glyph-name="binoculars" unicode="&#xf1e5;" d="M393 678v-428q0-15-11-25t-25-11v-321q0-15-10-25t-26-11h-285q-15 0-25 11t-11 25v285l139 488q4 12 17 12h237z m178 0v-392h-142v392h142z m429-500v-285q0-15-11-25t-25-11h-285q-15 0-25 11t-11 25v321q-15 0-25 11t-11 25v428h237q13 0 17-12z m-589 661v-125h-197v125q0 8 5 13t13 5h161q8 0 13-5t5-13z m375 0v-125h-197v125q0 8 5 13t13 5h161q8 0 13-5t5-13z" horiz-adv-x="1000" /> <glyph glyph-name="binoculars" unicode="&#xf1e5;" d="M393 678v-428q0-15-11-25t-25-11v-321q0-15-10-25t-26-11h-285q-15 0-25 11t-11 25v285l139 488q4 12 17 12h237z m178 0v-392h-142v392h142z m429-500v-285q0-15-11-25t-25-11h-285q-15 0-25 11t-11 25v321q-15 0-25 11t-11 25v428h237q13 0 17-12z m-589 661v-125h-197v125q0 8 5 13t13 5h161q8 0 13-5t5-13z m375 0v-125h-197v125q0 8 5 13t13 5h161q8 0 13-5t5-13z" horiz-adv-x="1000" />
<glyph glyph-name="user-plus" unicode="&#xf234;" d="M393 357q-89 0-152 63t-62 151 62 152 152 63 151-63 63-152-63-151-151-63z m536-71h196q7 0 13-6t5-12v-107q0-8-5-13t-13-5h-196v-197q0-7-6-12t-12-6h-107q-8 0-13 6t-5 12v197h-197q-7 0-12 5t-6 13v107q0 7 6 12t12 6h197v196q0 7 5 13t13 5h107q7 0 12-5t6-13v-196z m-411-125q0-29 21-51t50-21h143v-133q-38-28-95-28h-488q-67 0-108 39t-41 106q0 30 2 58t8 61 15 60 24 55 34 45 48 30 62 11q11 0 22-10 44-34 86-51t92-17 92 17 86 51q11 10 22 10 73 0 121-54h-125q-29 0-50-21t-21-50v-107z" horiz-adv-x="1142.9" /> <glyph glyph-name="user-plus" unicode="&#xf234;" d="M393 357q-89 0-152 63t-62 151 62 152 152 63 151-63 63-152-63-151-151-63z m536-71h196q7 0 13-6t5-12v-107q0-8-5-13t-13-5h-196v-197q0-7-6-12t-12-6h-107q-8 0-13 6t-5 12v197h-197q-7 0-12 5t-6 13v107q0 7 6 12t12 6h197v196q0 7 5 13t13 5h107q7 0 12-5t6-13v-196z m-411-125q0-29 21-51t50-21h143v-133q-38-28-95-28h-488q-67 0-108 39t-41 106q0 30 2 58t8 61 15 60 24 55 34 45 48 30 62 11q11 0 22-10 44-34 86-51t92-17 92 17 86 51q11 10 22 10 73 0 121-54h-125q-29 0-50-21t-21-50v-107z" horiz-adv-x="1142.9" />

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Binary file not shown.

View file

@ -346,6 +346,12 @@
"code": 59427, "code": 59427,
"src": "fontawesome" "src": "fontawesome"
}, },
{
"uid": "4aad6bb50b02c18508aae9cbe14e784e",
"css": "share",
"code": 61920,
"src": "fontawesome"
},
{ {
"uid": "8b80d36d4ef43889db10bc1f0dc9a862", "uid": "8b80d36d4ef43889db10bc1f0dc9a862",
"css": "user", "css": "user",

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -11,6 +11,11 @@ defmodule Pleroma.ActivityTest do
alias Pleroma.ThreadMute alias Pleroma.ThreadMute
import Pleroma.Factory import Pleroma.Factory
setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
:ok
end
test "returns an activity by it's AP id" do test "returns an activity by it's AP id" do
activity = insert(:note_activity) activity = insert(:note_activity)
found_activity = Activity.get_by_ap_id(activity.data["id"]) found_activity = Activity.get_by_ap_id(activity.data["id"])
@ -107,8 +112,6 @@ test "when association is not loaded" do
describe "search" do describe "search" do
setup do setup do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
user = insert(:user) user = insert(:user)
params = %{ params = %{
@ -125,8 +128,8 @@ test "when association is not loaded" do
"to" => ["https://www.w3.org/ns/activitystreams#Public"] "to" => ["https://www.w3.org/ns/activitystreams#Public"]
} }
{:ok, local_activity} = Pleroma.Web.CommonAPI.post(user, %{"status" => "find me!"}) {:ok, local_activity} = Pleroma.Web.CommonAPI.post(user, %{status: "find me!"})
{:ok, japanese_activity} = Pleroma.Web.CommonAPI.post(user, %{"status" => "更新情報"}) {:ok, japanese_activity} = Pleroma.Web.CommonAPI.post(user, %{status: "更新情報"})
{:ok, job} = Pleroma.Web.Federator.incoming_ap_doc(params) {:ok, job} = Pleroma.Web.Federator.incoming_ap_doc(params)
{:ok, remote_activity} = ObanHelpers.perform(job) {:ok, remote_activity} = ObanHelpers.perform(job)
@ -225,8 +228,8 @@ test "get_by_id/1" do
test "all_by_actor_and_id/2" do test "all_by_actor_and_id/2" do
user = insert(:user) user = insert(:user)
{:ok, %{id: id1}} = Pleroma.Web.CommonAPI.post(user, %{"status" => "cofe"}) {:ok, %{id: id1}} = Pleroma.Web.CommonAPI.post(user, %{status: "cofe"})
{:ok, %{id: id2}} = Pleroma.Web.CommonAPI.post(user, %{"status" => "cofefe"}) {:ok, %{id: id2}} = Pleroma.Web.CommonAPI.post(user, %{status: "cofefe"})
assert [] == Activity.all_by_actor_and_id(user, []) assert [] == Activity.all_by_actor_and_id(user, [])

View file

@ -21,8 +21,8 @@ test "getting the home timeline" do
{:ok, user} = User.follow(user, followed) {:ok, user} = User.follow(user, followed)
{:ok, _first} = CommonAPI.post(user, %{"status" => "hey"}) {:ok, _first} = CommonAPI.post(user, %{status: "hey"})
{:ok, _second} = CommonAPI.post(followed, %{"status" => "hello"}) {:ok, _second} = CommonAPI.post(followed, %{status: "hello"})
output = output =
capture_io(fn -> capture_io(fn ->
@ -62,7 +62,7 @@ test "replying" do
user = insert(:user) user = insert(:user)
another_user = insert(:user) another_user = insert(:user)
{:ok, activity} = CommonAPI.post(another_user, %{"status" => "this is a test post"}) {:ok, activity} = CommonAPI.post(another_user, %{status: "this is a test post"})
activity_object = Object.normalize(activity) activity_object = Object.normalize(activity)
output = output =

View file

@ -11,7 +11,7 @@ defmodule Pleroma.BookmarkTest do
describe "create/2" do describe "create/2" do
test "with valid params" do test "with valid params" do
user = insert(:user) user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "Some cool information"}) {:ok, activity} = CommonAPI.post(user, %{status: "Some cool information"})
{:ok, bookmark} = Bookmark.create(user.id, activity.id) {:ok, bookmark} = Bookmark.create(user.id, activity.id)
assert bookmark.user_id == user.id assert bookmark.user_id == user.id
assert bookmark.activity_id == activity.id assert bookmark.activity_id == activity.id
@ -32,7 +32,7 @@ test "with invalid params" do
test "with valid params" do test "with valid params" do
user = insert(:user) user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "Some cool information"}) {:ok, activity} = CommonAPI.post(user, %{status: "Some cool information"})
{:ok, _bookmark} = Bookmark.create(user.id, activity.id) {:ok, _bookmark} = Bookmark.create(user.id, activity.id)
{:ok, _deleted_bookmark} = Bookmark.destroy(user.id, activity.id) {:ok, _deleted_bookmark} = Bookmark.destroy(user.id, activity.id)
@ -45,7 +45,7 @@ test "gets a bookmark" do
{:ok, activity} = {:ok, activity} =
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"status" => status:
"Scientists Discover The Secret Behind Tenshi Eating A Corndog Being So Cute Science Daily" "Scientists Discover The Secret Behind Tenshi Eating A Corndog Being So Cute Science Daily"
}) })

View file

@ -43,11 +43,9 @@ test "get_all_as_keyword/0" do
{ConfigDB.from_string(saved.key), ConfigDB.from_binary(saved.value)} {ConfigDB.from_string(saved.key), ConfigDB.from_binary(saved.value)}
] ]
assert config[:quack] == [ assert config[:quack][:level] == :info
level: :info, assert config[:quack][:meta] == [:none]
meta: [:none], assert config[:quack][:webhook_url] == "https://hooks.slack.com/services/KEY/some_val"
webhook_url: "https://hooks.slack.com/services/KEY/some_val"
]
end end
describe "update_or_create/1" do describe "update_or_create/1" do

View file

@ -16,7 +16,7 @@ test "getting a participation will also preload things" do
other_user = insert(:user) other_user = insert(:user)
{:ok, _activity} = {:ok, _activity} =
CommonAPI.post(user, %{"status" => "Hey @#{other_user.nickname}.", "visibility" => "direct"}) CommonAPI.post(user, %{status: "Hey @#{other_user.nickname}.", visibility: "direct"})
[participation] = Participation.for_user(user) [participation] = Participation.for_user(user)
@ -30,7 +30,7 @@ test "for a new conversation or a reply, it doesn't mark the author's participat
other_user = insert(:user) other_user = insert(:user)
{:ok, _} = {:ok, _} =
CommonAPI.post(user, %{"status" => "Hey @#{other_user.nickname}.", "visibility" => "direct"}) CommonAPI.post(user, %{status: "Hey @#{other_user.nickname}.", visibility: "direct"})
user = User.get_cached_by_id(user.id) user = User.get_cached_by_id(user.id)
other_user = User.get_cached_by_id(other_user.id) other_user = User.get_cached_by_id(other_user.id)
@ -43,9 +43,9 @@ test "for a new conversation or a reply, it doesn't mark the author's participat
{:ok, _} = {:ok, _} =
CommonAPI.post(other_user, %{ CommonAPI.post(other_user, %{
"status" => "Hey @#{user.nickname}.", status: "Hey @#{user.nickname}.",
"visibility" => "direct", visibility: "direct",
"in_reply_to_conversation_id" => participation.id in_reply_to_conversation_id: participation.id
}) })
user = User.get_cached_by_id(user.id) user = User.get_cached_by_id(user.id)
@ -64,7 +64,7 @@ test "for a new conversation, it sets the recipents of the participation" do
third_user = insert(:user) third_user = insert(:user)
{:ok, activity} = {:ok, activity} =
CommonAPI.post(user, %{"status" => "Hey @#{other_user.nickname}.", "visibility" => "direct"}) CommonAPI.post(user, %{status: "Hey @#{other_user.nickname}.", visibility: "direct"})
user = User.get_cached_by_id(user.id) user = User.get_cached_by_id(user.id)
other_user = User.get_cached_by_id(other_user.id) other_user = User.get_cached_by_id(other_user.id)
@ -79,9 +79,9 @@ test "for a new conversation, it sets the recipents of the participation" do
{:ok, _activity} = {:ok, _activity} =
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"in_reply_to_status_id" => activity.id, in_reply_to_status_id: activity.id,
"status" => "Hey @#{third_user.nickname}.", status: "Hey @#{third_user.nickname}.",
"visibility" => "direct" visibility: "direct"
}) })
[participation] = Participation.for_user(user) [participation] = Participation.for_user(user)
@ -154,14 +154,14 @@ test "it marks all the user's participations as read" do
test "gets all the participations for a user, ordered by updated at descending" do test "gets all the participations for a user, ordered by updated at descending" do
user = insert(:user) user = insert(:user)
{:ok, activity_one} = CommonAPI.post(user, %{"status" => "x", "visibility" => "direct"}) {:ok, activity_one} = CommonAPI.post(user, %{status: "x", visibility: "direct"})
{:ok, activity_two} = CommonAPI.post(user, %{"status" => "x", "visibility" => "direct"}) {:ok, activity_two} = CommonAPI.post(user, %{status: "x", visibility: "direct"})
{:ok, activity_three} = {:ok, activity_three} =
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"status" => "x", status: "x",
"visibility" => "direct", visibility: "direct",
"in_reply_to_status_id" => activity_one.id in_reply_to_status_id: activity_one.id
}) })
# Offset participations because the accuracy of updated_at is down to a second # Offset participations because the accuracy of updated_at is down to a second
@ -201,7 +201,7 @@ test "gets all the participations for a user, ordered by updated at descending"
test "Doesn't die when the conversation gets empty" do test "Doesn't die when the conversation gets empty" do
user = insert(:user) user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"}) {:ok, activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
[participation] = Participation.for_user_with_last_activity_id(user) [participation] = Participation.for_user_with_last_activity_id(user)
assert participation.last_activity_id == activity.id assert participation.last_activity_id == activity.id
@ -215,7 +215,7 @@ test "it sets recipients, always keeping the owner of the participation even whe
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, _activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"}) {:ok, _activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
[participation] = Participation.for_user_with_last_activity_id(user) [participation] = Participation.for_user_with_last_activity_id(user)
participation = Repo.preload(participation, :recipients) participation = Repo.preload(participation, :recipients)
@ -239,26 +239,26 @@ test "when the user blocks a recipient, the existing conversations with them are
{:ok, _direct1} = {:ok, _direct1} =
CommonAPI.post(third_user, %{ CommonAPI.post(third_user, %{
"status" => "Hi @#{blocker.nickname}", status: "Hi @#{blocker.nickname}",
"visibility" => "direct" visibility: "direct"
}) })
{:ok, _direct2} = {:ok, _direct2} =
CommonAPI.post(third_user, %{ CommonAPI.post(third_user, %{
"status" => "Hi @#{blocker.nickname}, @#{blocked.nickname}", status: "Hi @#{blocker.nickname}, @#{blocked.nickname}",
"visibility" => "direct" visibility: "direct"
}) })
{:ok, _direct3} = {:ok, _direct3} =
CommonAPI.post(blocked, %{ CommonAPI.post(blocked, %{
"status" => "Hi @#{blocker.nickname}", status: "Hi @#{blocker.nickname}",
"visibility" => "direct" visibility: "direct"
}) })
{:ok, _direct4} = {:ok, _direct4} =
CommonAPI.post(blocked, %{ CommonAPI.post(blocked, %{
"status" => "Hi @#{blocker.nickname}, @#{third_user.nickname}", status: "Hi @#{blocker.nickname}, @#{third_user.nickname}",
"visibility" => "direct" visibility: "direct"
}) })
assert [%{read: false}, %{read: false}, %{read: false}, %{read: false}] = assert [%{read: false}, %{read: false}, %{read: false}, %{read: false}] =
@ -293,8 +293,8 @@ test "the new conversation with the blocked user is not marked as unread " do
# When the blocked user is the author # When the blocked user is the author
{:ok, _direct1} = {:ok, _direct1} =
CommonAPI.post(blocked, %{ CommonAPI.post(blocked, %{
"status" => "Hi @#{blocker.nickname}", status: "Hi @#{blocker.nickname}",
"visibility" => "direct" visibility: "direct"
}) })
assert [%{read: true}] = Participation.for_user(blocker) assert [%{read: true}] = Participation.for_user(blocker)
@ -303,8 +303,8 @@ test "the new conversation with the blocked user is not marked as unread " do
# When the blocked user is a recipient # When the blocked user is a recipient
{:ok, _direct2} = {:ok, _direct2} =
CommonAPI.post(third_user, %{ CommonAPI.post(third_user, %{
"status" => "Hi @#{blocker.nickname}, @#{blocked.nickname}", status: "Hi @#{blocker.nickname}, @#{blocked.nickname}",
"visibility" => "direct" visibility: "direct"
}) })
assert [%{read: true}, %{read: true}] = Participation.for_user(blocker) assert [%{read: true}, %{read: true}] = Participation.for_user(blocker)
@ -321,8 +321,8 @@ test "the conversation with the blocked user is not marked as unread on a reply"
{:ok, _direct1} = {:ok, _direct1} =
CommonAPI.post(blocker, %{ CommonAPI.post(blocker, %{
"status" => "Hi @#{third_user.nickname}, @#{blocked.nickname}", status: "Hi @#{third_user.nickname}, @#{blocked.nickname}",
"visibility" => "direct" visibility: "direct"
}) })
{:ok, _user_relationship} = User.block(blocker, blocked) {:ok, _user_relationship} = User.block(blocker, blocked)
@ -334,9 +334,9 @@ test "the conversation with the blocked user is not marked as unread on a reply"
# When it's a reply from the blocked user # When it's a reply from the blocked user
{:ok, _direct2} = {:ok, _direct2} =
CommonAPI.post(blocked, %{ CommonAPI.post(blocked, %{
"status" => "reply", status: "reply",
"visibility" => "direct", visibility: "direct",
"in_reply_to_conversation_id" => blocked_participation.id in_reply_to_conversation_id: blocked_participation.id
}) })
assert [%{read: true}] = Participation.for_user(blocker) assert [%{read: true}] = Participation.for_user(blocker)
@ -347,9 +347,9 @@ test "the conversation with the blocked user is not marked as unread on a reply"
# When it's a reply from the third user # When it's a reply from the third user
{:ok, _direct3} = {:ok, _direct3} =
CommonAPI.post(third_user, %{ CommonAPI.post(third_user, %{
"status" => "reply", status: "reply",
"visibility" => "direct", visibility: "direct",
"in_reply_to_conversation_id" => third_user_participation.id in_reply_to_conversation_id: third_user_participation.id
}) })
assert [%{read: true}] = Participation.for_user(blocker) assert [%{read: true}] = Participation.for_user(blocker)

View file

@ -18,7 +18,7 @@ test "it goes through old direct conversations" do
other_user = insert(:user) other_user = insert(:user)
{:ok, _activity} = {:ok, _activity} =
CommonAPI.post(user, %{"visibility" => "direct", "status" => "hey @#{other_user.nickname}"}) CommonAPI.post(user, %{visibility: "direct", status: "hey @#{other_user.nickname}"})
Pleroma.Tests.ObanHelpers.perform_all() Pleroma.Tests.ObanHelpers.perform_all()
@ -46,7 +46,7 @@ test "it creates a conversation for given ap_id" do
test "public posts don't create conversations" do test "public posts don't create conversations" do
user = insert(:user) user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "Hey"}) {:ok, activity} = CommonAPI.post(user, %{status: "Hey"})
object = Pleroma.Object.normalize(activity) object = Pleroma.Object.normalize(activity)
context = object.data["context"] context = object.data["context"]
@ -62,7 +62,7 @@ test "it creates or updates a conversation and participations for a given DM" do
tridi = insert(:user) tridi = insert(:user)
{:ok, activity} = {:ok, activity} =
CommonAPI.post(har, %{"status" => "Hey @#{jafnhar.nickname}", "visibility" => "direct"}) CommonAPI.post(har, %{status: "Hey @#{jafnhar.nickname}", visibility: "direct"})
object = Pleroma.Object.normalize(activity) object = Pleroma.Object.normalize(activity)
context = object.data["context"] context = object.data["context"]
@ -81,9 +81,9 @@ test "it creates or updates a conversation and participations for a given DM" do
{:ok, activity} = {:ok, activity} =
CommonAPI.post(jafnhar, %{ CommonAPI.post(jafnhar, %{
"status" => "Hey @#{har.nickname}", status: "Hey @#{har.nickname}",
"visibility" => "direct", visibility: "direct",
"in_reply_to_status_id" => activity.id in_reply_to_status_id: activity.id
}) })
object = Pleroma.Object.normalize(activity) object = Pleroma.Object.normalize(activity)
@ -105,9 +105,9 @@ test "it creates or updates a conversation and participations for a given DM" do
{:ok, activity} = {:ok, activity} =
CommonAPI.post(tridi, %{ CommonAPI.post(tridi, %{
"status" => "Hey @#{har.nickname}", status: "Hey @#{har.nickname}",
"visibility" => "direct", visibility: "direct",
"in_reply_to_status_id" => activity.id in_reply_to_status_id: activity.id
}) })
object = Pleroma.Object.normalize(activity) object = Pleroma.Object.normalize(activity)
@ -149,14 +149,14 @@ test "create_or_bump_for returns the conversation with participations" do
jafnhar = insert(:user, local: false) jafnhar = insert(:user, local: false)
{:ok, activity} = {:ok, activity} =
CommonAPI.post(har, %{"status" => "Hey @#{jafnhar.nickname}", "visibility" => "direct"}) CommonAPI.post(har, %{status: "Hey @#{jafnhar.nickname}", visibility: "direct"})
{:ok, conversation} = Conversation.create_or_bump_for(activity) {:ok, conversation} = Conversation.create_or_bump_for(activity)
assert length(conversation.participations) == 2 assert length(conversation.participations) == 2
{:ok, activity} = {:ok, activity} =
CommonAPI.post(har, %{"status" => "Hey @#{jafnhar.nickname}", "visibility" => "public"}) CommonAPI.post(har, %{status: "Hey @#{jafnhar.nickname}", visibility: "public"})
assert {:error, _} = Conversation.create_or_bump_for(activity) assert {:error, _} = Conversation.create_or_bump_for(activity)
end end

View file

@ -171,7 +171,7 @@ test "extracts the url" do
{:ok, activity} = {:ok, activity} =
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"status" => status:
"I think I just found the best github repo https://github.com/komeiji-satori/Dress" "I think I just found the best github repo https://github.com/komeiji-satori/Dress"
}) })
@ -186,7 +186,7 @@ test "skips mentions" do
{:ok, activity} = {:ok, activity} =
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"status" => status:
"@#{other_user.nickname} install misskey! https://github.com/syuilo/misskey/blob/develop/docs/setup.en.md" "@#{other_user.nickname} install misskey! https://github.com/syuilo/misskey/blob/develop/docs/setup.en.md"
}) })
@ -203,8 +203,7 @@ test "skips hashtags" do
{:ok, activity} = {:ok, activity} =
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"status" => status: "#cofe https://www.pixiv.net/member_illust.php?mode=medium&illust_id=72255140"
"#cofe https://www.pixiv.net/member_illust.php?mode=medium&illust_id=72255140"
}) })
object = Object.normalize(activity) object = Object.normalize(activity)
@ -218,9 +217,9 @@ test "skips microformats hashtags" do
{:ok, activity} = {:ok, activity} =
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"status" => status:
"<a href=\"https://pleroma.gov/tags/cofe\" rel=\"tag\">#cofe</a> https://www.pixiv.net/member_illust.php?mode=medium&illust_id=72255140", "<a href=\"https://pleroma.gov/tags/cofe\" rel=\"tag\">#cofe</a> https://www.pixiv.net/member_illust.php?mode=medium&illust_id=72255140",
"content_type" => "text/html" content_type: "text/html"
}) })
object = Object.normalize(activity) object = Object.normalize(activity)
@ -232,8 +231,7 @@ test "skips microformats hashtags" do
test "does not crash when there is an HTML entity in a link" do test "does not crash when there is an HTML entity in a link" do
user = insert(:user) user = insert(:user)
{:ok, activity} = {:ok, activity} = CommonAPI.post(user, %{status: "\"http://cofe.com/?boomer=ok&foo=bar\""})
CommonAPI.post(user, %{"status" => "\"http://cofe.com/?boomer=ok&foo=bar\""})
object = Object.normalize(activity) object = Object.normalize(activity)

View file

@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTTP.ConnectionTest do defmodule Pleroma.HTTP.ConnectionTest do
use ExUnit.Case, async: true use ExUnit.Case
use Pleroma.Tests.Helpers use Pleroma.Tests.Helpers
import ExUnit.CaptureLog import ExUnit.CaptureLog

View file

@ -3,23 +3,19 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTTP.RequestBuilderTest do defmodule Pleroma.HTTP.RequestBuilderTest do
use ExUnit.Case, async: true use ExUnit.Case
use Pleroma.Tests.Helpers use Pleroma.Tests.Helpers
alias Pleroma.Config
alias Pleroma.HTTP.Request alias Pleroma.HTTP.Request
alias Pleroma.HTTP.RequestBuilder alias Pleroma.HTTP.RequestBuilder
describe "headers/2" do describe "headers/2" do
setup do: clear_config([:http, :send_user_agent])
setup do: clear_config([:http, :user_agent])
test "don't send pleroma user agent" do test "don't send pleroma user agent" do
assert RequestBuilder.headers(%Request{}, []) == %Request{headers: []} assert RequestBuilder.headers(%Request{}, []) == %Request{headers: []}
end end
test "send pleroma user agent" do test "send pleroma user agent" do
Config.put([:http, :send_user_agent], true) clear_config([:http, :send_user_agent], true)
Config.put([:http, :user_agent], :default) clear_config([:http, :user_agent], :default)
assert RequestBuilder.headers(%Request{}, []) == %Request{ assert RequestBuilder.headers(%Request{}, []) == %Request{
headers: [{"user-agent", Pleroma.Application.user_agent()}] headers: [{"user-agent", Pleroma.Application.user_agent()}]
@ -27,8 +23,8 @@ test "send pleroma user agent" do
end end
test "send custom user agent" do test "send custom user agent" do
Config.put([:http, :send_user_agent], true) clear_config([:http, :send_user_agent], true)
Config.put([:http, :user_agent], "totally-not-pleroma") clear_config([:http, :user_agent], "totally-not-pleroma")
assert RequestBuilder.headers(%Request{}, []) == %Request{ assert RequestBuilder.headers(%Request{}, []) == %Request{
headers: [{"user-agent", "totally-not-pleroma"}] headers: [{"user-agent", "totally-not-pleroma"}]

View file

@ -32,7 +32,7 @@ def start_socket(qs \\ nil, headers \\ []) do
test "refuses invalid requests" do test "refuses invalid requests" do
capture_log(fn -> capture_log(fn ->
assert {:error, {400, _}} = start_socket() assert {:error, {404, _}} = start_socket()
assert {:error, {404, _}} = start_socket("?stream=ncjdk") assert {:error, {404, _}} = start_socket("?stream=ncjdk")
Process.sleep(30) Process.sleep(30)
end) end)
@ -40,8 +40,8 @@ test "refuses invalid requests" do
test "requires authentication and a valid token for protected streams" do test "requires authentication and a valid token for protected streams" do
capture_log(fn -> capture_log(fn ->
assert {:error, {403, _}} = start_socket("?stream=user&access_token=aaaaaaaaaaaa") assert {:error, {401, _}} = start_socket("?stream=user&access_token=aaaaaaaaaaaa")
assert {:error, {403, _}} = start_socket("?stream=user") assert {:error, {401, _}} = start_socket("?stream=user")
Process.sleep(30) Process.sleep(30)
end) end)
end end
@ -55,7 +55,7 @@ test "allows public streams without authentication" do
test "receives well formatted events" do test "receives well formatted events" do
user = insert(:user) user = insert(:user)
{:ok, _} = start_socket("?stream=public") {:ok, _} = start_socket("?stream=public")
{:ok, activity} = CommonAPI.post(user, %{"status" => "nice echo chamber"}) {:ok, activity} = CommonAPI.post(user, %{status: "nice echo chamber"})
assert_receive {:text, raw_json}, 1_000 assert_receive {:text, raw_json}, 1_000
assert {:ok, json} = Jason.decode(raw_json) assert {:ok, json} = Jason.decode(raw_json)
@ -100,7 +100,7 @@ test "accepts the 'user' stream", %{token: token} = _state do
assert {:ok, _} = start_socket("?stream=user&access_token=#{token.token}") assert {:ok, _} = start_socket("?stream=user&access_token=#{token.token}")
assert capture_log(fn -> assert capture_log(fn ->
assert {:error, {403, "Forbidden"}} = start_socket("?stream=user") assert {:error, {401, _}} = start_socket("?stream=user")
Process.sleep(30) Process.sleep(30)
end) =~ ":badarg" end) =~ ":badarg"
end end
@ -109,7 +109,7 @@ test "accepts the 'user:notification' stream", %{token: token} = _state do
assert {:ok, _} = start_socket("?stream=user:notification&access_token=#{token.token}") assert {:ok, _} = start_socket("?stream=user:notification&access_token=#{token.token}")
assert capture_log(fn -> assert capture_log(fn ->
assert {:error, {403, "Forbidden"}} = start_socket("?stream=user:notification") assert {:error, {401, _}} = start_socket("?stream=user:notification")
Process.sleep(30) Process.sleep(30)
end) =~ ":badarg" end) =~ ":badarg"
end end
@ -118,7 +118,7 @@ test "accepts valid token on Sec-WebSocket-Protocol header", %{token: token} do
assert {:ok, _} = start_socket("?stream=user", [{"Sec-WebSocket-Protocol", token.token}]) assert {:ok, _} = start_socket("?stream=user", [{"Sec-WebSocket-Protocol", token.token}])
assert capture_log(fn -> assert capture_log(fn ->
assert {:error, {403, "Forbidden"}} = assert {:error, {401, _}} =
start_socket("?stream=user", [{"Sec-WebSocket-Protocol", "I am a friend"}]) start_socket("?stream=user", [{"Sec-WebSocket-Protocol", "I am a friend"}])
Process.sleep(30) Process.sleep(30)

View file

@ -6,7 +6,6 @@ defmodule Pleroma.MFATest do
use Pleroma.DataCase use Pleroma.DataCase
import Pleroma.Factory import Pleroma.Factory
alias Comeonin.Pbkdf2
alias Pleroma.MFA alias Pleroma.MFA
describe "mfa_settings" do describe "mfa_settings" do
@ -31,8 +30,8 @@ test "returns backup codes" do
{:ok, [code1, code2]} = MFA.generate_backup_codes(user) {:ok, [code1, code2]} = MFA.generate_backup_codes(user)
updated_user = refresh_record(user) updated_user = refresh_record(user)
[hash1, hash2] = updated_user.multi_factor_authentication_settings.backup_codes [hash1, hash2] = updated_user.multi_factor_authentication_settings.backup_codes
assert Pbkdf2.checkpw(code1, hash1) assert Pbkdf2.verify_pass(code1, hash1)
assert Pbkdf2.checkpw(code2, hash2) assert Pbkdf2.verify_pass(code2, hash2)
end end
end end

View file

@ -25,7 +25,7 @@ test "creates a notification for an emoji reaction" do
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "yeah"}) {:ok, activity} = CommonAPI.post(user, %{status: "yeah"})
{:ok, activity} = CommonAPI.react_with_emoji(activity.id, other_user, "") {:ok, activity} = CommonAPI.react_with_emoji(activity.id, other_user, "")
{:ok, [notification]} = Notification.create_notifications(activity) {:ok, [notification]} = Notification.create_notifications(activity)
@ -40,7 +40,7 @@ test "notifies someone when they are directly addressed" do
{:ok, activity} = {:ok, activity} =
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"status" => "hey @#{other_user.nickname} and @#{third_user.nickname}" status: "hey @#{other_user.nickname} and @#{third_user.nickname}"
}) })
{:ok, [notification, other_notification]} = Notification.create_notifications(activity) {:ok, [notification, other_notification]} = Notification.create_notifications(activity)
@ -60,7 +60,7 @@ test "it creates a notification for subscribed users" do
User.subscribe(subscriber, user) User.subscribe(subscriber, user)
{:ok, status} = CommonAPI.post(user, %{"status" => "Akariiiin"}) {:ok, status} = CommonAPI.post(user, %{status: "Akariiiin"})
{:ok, [notification]} = Notification.create_notifications(status) {:ok, [notification]} = Notification.create_notifications(status)
assert notification.user_id == subscriber.id assert notification.user_id == subscriber.id
@ -73,12 +73,12 @@ test "does not create a notification for subscribed users if status is a reply"
User.subscribe(subscriber, other_user) User.subscribe(subscriber, other_user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) {:ok, activity} = CommonAPI.post(user, %{status: "test post"})
{:ok, _reply_activity} = {:ok, _reply_activity} =
CommonAPI.post(other_user, %{ CommonAPI.post(other_user, %{
"status" => "test reply", status: "test reply",
"in_reply_to_status_id" => activity.id in_reply_to_status_id: activity.id
}) })
user_notifications = Notification.for_user(user) user_notifications = Notification.for_user(user)
@ -98,7 +98,7 @@ test "does not create a notification for subscribed users if status is a reply"
blocker = insert(:user) blocker = insert(:user)
{:ok, _user_relationship} = User.block(blocker, user) {:ok, _user_relationship} = User.block(blocker, user)
{:ok, _activity} = CommonAPI.post(user, %{"status" => "hey @#{blocker.nickname}!"}) {:ok, _activity} = CommonAPI.post(user, %{status: "hey @#{blocker.nickname}!"})
blocker_id = blocker.id blocker_id = blocker.id
assert [%Notification{user_id: ^blocker_id}] = Repo.all(Notification) assert [%Notification{user_id: ^blocker_id}] = Repo.all(Notification)
@ -113,7 +113,7 @@ test "does not create a notification for subscribed users if status is a reply"
muter = insert(:user) muter = insert(:user)
{:ok, _user_relationships} = User.mute(muter, user) {:ok, _user_relationships} = User.mute(muter, user)
{:ok, _activity} = CommonAPI.post(user, %{"status" => "hey @#{muter.nickname}!"}) {:ok, _activity} = CommonAPI.post(user, %{status: "hey @#{muter.nickname}!"})
muter_id = muter.id muter_id = muter.id
assert [%Notification{user_id: ^muter_id}] = Repo.all(Notification) assert [%Notification{user_id: ^muter_id}] = Repo.all(Notification)
@ -127,14 +127,14 @@ test "does not create a notification for subscribed users if status is a reply"
user = insert(:user) user = insert(:user)
thread_muter = insert(:user) thread_muter = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{thread_muter.nickname}!"}) {:ok, activity} = CommonAPI.post(user, %{status: "hey @#{thread_muter.nickname}!"})
{:ok, _} = CommonAPI.add_mute(thread_muter, activity) {:ok, _} = CommonAPI.add_mute(thread_muter, activity)
{:ok, _same_context_activity} = {:ok, _same_context_activity} =
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"status" => "hey-hey-hey @#{thread_muter.nickname}!", status: "hey-hey-hey @#{thread_muter.nickname}!",
"in_reply_to_status_id" => activity.id in_reply_to_status_id: activity.id
}) })
[pre_mute_notification, post_mute_notification] = [pre_mute_notification, post_mute_notification] =
@ -170,13 +170,13 @@ test "it creates a notification for user and send to the 'user' and the 'user:no
task = task =
Task.async(fn -> Task.async(fn ->
Streamer.add_socket("user", user) Streamer.get_topic_and_add_socket("user", user)
assert_receive {:render_with_user, _, _, _}, 4_000 assert_receive {:render_with_user, _, _, _}, 4_000
end) end)
task_user_notification = task_user_notification =
Task.async(fn -> Task.async(fn ->
Streamer.add_socket("user:notification", user) Streamer.get_topic_and_add_socket("user:notification", user)
assert_receive {:render_with_user, _, _, _}, 4_000 assert_receive {:render_with_user, _, _, _}, 4_000
end) end)
@ -202,7 +202,7 @@ test "it creates a notification for the user if the user mutes the activity auth
muted = insert(:user) muted = insert(:user)
{:ok, _} = User.mute(muter, muted) {:ok, _} = User.mute(muter, muted)
muter = Repo.get(User, muter.id) muter = Repo.get(User, muter.id)
{:ok, activity} = CommonAPI.post(muted, %{"status" => "Hi @#{muter.nickname}"}) {:ok, activity} = CommonAPI.post(muted, %{status: "Hi @#{muter.nickname}"})
assert Notification.create_notification(activity, muter) assert Notification.create_notification(activity, muter)
end end
@ -213,7 +213,7 @@ test "notification created if user is muted without notifications" do
{:ok, _user_relationships} = User.mute(muter, muted, false) {:ok, _user_relationships} = User.mute(muter, muted, false)
{:ok, activity} = CommonAPI.post(muted, %{"status" => "Hi @#{muter.nickname}"}) {:ok, activity} = CommonAPI.post(muted, %{status: "Hi @#{muter.nickname}"})
assert Notification.create_notification(activity, muter) assert Notification.create_notification(activity, muter)
end end
@ -221,13 +221,13 @@ test "notification created if user is muted without notifications" do
test "it creates a notification for an activity from a muted thread" do test "it creates a notification for an activity from a muted thread" do
muter = insert(:user) muter = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, activity} = CommonAPI.post(muter, %{"status" => "hey"}) {:ok, activity} = CommonAPI.post(muter, %{status: "hey"})
CommonAPI.add_mute(muter, activity) CommonAPI.add_mute(muter, activity)
{:ok, activity} = {:ok, activity} =
CommonAPI.post(other_user, %{ CommonAPI.post(other_user, %{
"status" => "Hi @#{muter.nickname}", status: "Hi @#{muter.nickname}",
"in_reply_to_status_id" => activity.id in_reply_to_status_id: activity.id
}) })
assert Notification.create_notification(activity, muter) assert Notification.create_notification(activity, muter)
@ -240,7 +240,7 @@ test "it disables notifications from followers" do
insert(:user, notification_settings: %Pleroma.User.NotificationSetting{followers: false}) insert(:user, notification_settings: %Pleroma.User.NotificationSetting{followers: false})
User.follow(follower, followed) User.follow(follower, followed)
{:ok, activity} = CommonAPI.post(follower, %{"status" => "hey @#{followed.nickname}"}) {:ok, activity} = CommonAPI.post(follower, %{status: "hey @#{followed.nickname}"})
refute Notification.create_notification(activity, followed) refute Notification.create_notification(activity, followed)
end end
@ -252,7 +252,7 @@ test "it disables notifications from non-followers" do
notification_settings: %Pleroma.User.NotificationSetting{non_followers: false} notification_settings: %Pleroma.User.NotificationSetting{non_followers: false}
) )
{:ok, activity} = CommonAPI.post(follower, %{"status" => "hey @#{followed.nickname}"}) {:ok, activity} = CommonAPI.post(follower, %{status: "hey @#{followed.nickname}"})
refute Notification.create_notification(activity, followed) refute Notification.create_notification(activity, followed)
end end
@ -263,7 +263,7 @@ test "it disables notifications from people the user follows" do
followed = insert(:user) followed = insert(:user)
User.follow(follower, followed) User.follow(follower, followed)
follower = Repo.get(User, follower.id) follower = Repo.get(User, follower.id)
{:ok, activity} = CommonAPI.post(followed, %{"status" => "hey @#{follower.nickname}"}) {:ok, activity} = CommonAPI.post(followed, %{status: "hey @#{follower.nickname}"})
refute Notification.create_notification(activity, follower) refute Notification.create_notification(activity, follower)
end end
@ -272,7 +272,7 @@ test "it disables notifications from people the user does not follow" do
insert(:user, notification_settings: %Pleroma.User.NotificationSetting{non_follows: false}) insert(:user, notification_settings: %Pleroma.User.NotificationSetting{non_follows: false})
followed = insert(:user) followed = insert(:user)
{:ok, activity} = CommonAPI.post(followed, %{"status" => "hey @#{follower.nickname}"}) {:ok, activity} = CommonAPI.post(followed, %{status: "hey @#{follower.nickname}"})
refute Notification.create_notification(activity, follower) refute Notification.create_notification(activity, follower)
end end
@ -289,7 +289,7 @@ test "it doesn't create duplicate notifications for follow+subscribed users" do
{:ok, _, _, _} = CommonAPI.follow(subscriber, user) {:ok, _, _, _} = CommonAPI.follow(subscriber, user)
User.subscribe(subscriber, user) User.subscribe(subscriber, user)
{:ok, status} = CommonAPI.post(user, %{"status" => "Akariiiin"}) {:ok, status} = CommonAPI.post(user, %{status: "Akariiiin"})
{:ok, [_notif]} = Notification.create_notifications(status) {:ok, [_notif]} = Notification.create_notifications(status)
end end
@ -299,7 +299,7 @@ test "it doesn't create subscription notifications if the recipient cannot see t
User.subscribe(subscriber, user) User.subscribe(subscriber, user)
{:ok, status} = CommonAPI.post(user, %{"status" => "inwisible", "visibility" => "direct"}) {:ok, status} = CommonAPI.post(user, %{status: "inwisible", visibility: "direct"})
assert {:ok, []} == Notification.create_notifications(status) assert {:ok, []} == Notification.create_notifications(status)
end end
@ -370,7 +370,7 @@ test "it gets a notification that belongs to the user" do
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{other_user.nickname}"}) {:ok, activity} = CommonAPI.post(user, %{status: "hey @#{other_user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity) {:ok, [notification]} = Notification.create_notifications(activity)
{:ok, notification} = Notification.get(other_user, notification.id) {:ok, notification} = Notification.get(other_user, notification.id)
@ -382,7 +382,7 @@ test "it returns error if the notification doesn't belong to the user" do
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{other_user.nickname}"}) {:ok, activity} = CommonAPI.post(user, %{status: "hey @#{other_user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity) {:ok, [notification]} = Notification.create_notifications(activity)
{:error, _notification} = Notification.get(user, notification.id) {:error, _notification} = Notification.get(user, notification.id)
@ -394,7 +394,7 @@ test "it dismisses a notification that belongs to the user" do
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{other_user.nickname}"}) {:ok, activity} = CommonAPI.post(user, %{status: "hey @#{other_user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity) {:ok, [notification]} = Notification.create_notifications(activity)
{:ok, notification} = Notification.dismiss(other_user, notification.id) {:ok, notification} = Notification.dismiss(other_user, notification.id)
@ -406,7 +406,7 @@ test "it returns error if the notification doesn't belong to the user" do
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{other_user.nickname}"}) {:ok, activity} = CommonAPI.post(user, %{status: "hey @#{other_user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity) {:ok, [notification]} = Notification.create_notifications(activity)
{:error, _notification} = Notification.dismiss(user, notification.id) {:error, _notification} = Notification.dismiss(user, notification.id)
@ -421,14 +421,14 @@ test "it clears all notifications belonging to the user" do
{:ok, activity} = {:ok, activity} =
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"status" => "hey @#{other_user.nickname} and @#{third_user.nickname} !" status: "hey @#{other_user.nickname} and @#{third_user.nickname} !"
}) })
{:ok, _notifs} = Notification.create_notifications(activity) {:ok, _notifs} = Notification.create_notifications(activity)
{:ok, activity} = {:ok, activity} =
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"status" => "hey again @#{other_user.nickname} and @#{third_user.nickname} !" status: "hey again @#{other_user.nickname} and @#{third_user.nickname} !"
}) })
{:ok, _notifs} = Notification.create_notifications(activity) {:ok, _notifs} = Notification.create_notifications(activity)
@ -446,12 +446,12 @@ test "it sets all notifications as read up to a specified notification ID" do
{:ok, _activity} = {:ok, _activity} =
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"status" => "hey @#{other_user.nickname}!" status: "hey @#{other_user.nickname}!"
}) })
{:ok, _activity} = {:ok, _activity} =
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"status" => "hey again @#{other_user.nickname}!" status: "hey again @#{other_user.nickname}!"
}) })
[n2, n1] = notifs = Notification.for_user(other_user) [n2, n1] = notifs = Notification.for_user(other_user)
@ -461,7 +461,7 @@ test "it sets all notifications as read up to a specified notification ID" do
{:ok, _activity} = {:ok, _activity} =
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"status" => "hey yet again @#{other_user.nickname}!" status: "hey yet again @#{other_user.nickname}!"
}) })
Notification.set_read_up_to(other_user, n2.id) Notification.set_read_up_to(other_user, n2.id)
@ -500,7 +500,7 @@ test "Returns recent notifications" do
Enum.each(0..10, fn i -> Enum.each(0..10, fn i ->
{:ok, _activity} = {:ok, _activity} =
CommonAPI.post(user1, %{ CommonAPI.post(user1, %{
"status" => "hey ##{i} @#{user2.nickname}!" status: "hey ##{i} @#{user2.nickname}!"
}) })
end) end)
@ -536,7 +536,7 @@ test "it sends notifications to addressed users in new messages" do
{:ok, activity} = {:ok, activity} =
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"status" => "hey @#{other_user.nickname}!" status: "hey @#{other_user.nickname}!"
}) })
{enabled_receivers, _disabled_receivers} = Notification.get_notified_from_activity(activity) {enabled_receivers, _disabled_receivers} = Notification.get_notified_from_activity(activity)
@ -605,7 +605,7 @@ test "it does not send notification to mentioned users in likes" do
{:ok, activity_one} = {:ok, activity_one} =
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"status" => "hey @#{other_user.nickname}!" status: "hey @#{other_user.nickname}!"
}) })
{:ok, activity_two} = CommonAPI.favorite(third_user, activity_one.id) {:ok, activity_two} = CommonAPI.favorite(third_user, activity_one.id)
@ -623,7 +623,7 @@ test "it only notifies the post's author in likes" do
{:ok, activity_one} = {:ok, activity_one} =
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"status" => "hey @#{other_user.nickname}!" status: "hey @#{other_user.nickname}!"
}) })
{:ok, like_data, _} = Builder.like(third_user, activity_one.object) {:ok, like_data, _} = Builder.like(third_user, activity_one.object)
@ -645,7 +645,7 @@ test "it does not send notification to mentioned users in announces" do
{:ok, activity_one} = {:ok, activity_one} =
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"status" => "hey @#{other_user.nickname}!" status: "hey @#{other_user.nickname}!"
}) })
{:ok, activity_two, _} = CommonAPI.repeat(activity_one.id, third_user) {:ok, activity_two, _} = CommonAPI.repeat(activity_one.id, third_user)
@ -661,7 +661,7 @@ test "it returns blocking recipient in disabled recipients list" do
other_user = insert(:user) other_user = insert(:user)
{:ok, _user_relationship} = User.block(other_user, user) {:ok, _user_relationship} = User.block(other_user, user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{other_user.nickname}!"}) {:ok, activity} = CommonAPI.post(user, %{status: "hey @#{other_user.nickname}!"})
{enabled_receivers, disabled_receivers} = Notification.get_notified_from_activity(activity) {enabled_receivers, disabled_receivers} = Notification.get_notified_from_activity(activity)
@ -674,7 +674,7 @@ test "it returns notification-muting recipient in disabled recipients list" do
other_user = insert(:user) other_user = insert(:user)
{:ok, _user_relationships} = User.mute(other_user, user) {:ok, _user_relationships} = User.mute(other_user, user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{other_user.nickname}!"}) {:ok, activity} = CommonAPI.post(user, %{status: "hey @#{other_user.nickname}!"})
{enabled_receivers, disabled_receivers} = Notification.get_notified_from_activity(activity) {enabled_receivers, disabled_receivers} = Notification.get_notified_from_activity(activity)
@ -686,14 +686,14 @@ test "it returns thread-muting recipient in disabled recipients list" do
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{other_user.nickname}!"}) {:ok, activity} = CommonAPI.post(user, %{status: "hey @#{other_user.nickname}!"})
{:ok, _} = CommonAPI.add_mute(other_user, activity) {:ok, _} = CommonAPI.add_mute(other_user, activity)
{:ok, same_context_activity} = {:ok, same_context_activity} =
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"status" => "hey-hey-hey @#{other_user.nickname}!", status: "hey-hey-hey @#{other_user.nickname}!",
"in_reply_to_status_id" => activity.id in_reply_to_status_id: activity.id
}) })
{enabled_receivers, disabled_receivers} = {enabled_receivers, disabled_receivers} =
@ -710,7 +710,7 @@ test "it returns non-following domain-blocking recipient in disabled recipients
{:ok, other_user} = User.block_domain(other_user, blocked_domain) {:ok, other_user} = User.block_domain(other_user, blocked_domain)
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{other_user.nickname}!"}) {:ok, activity} = CommonAPI.post(user, %{status: "hey @#{other_user.nickname}!"})
{enabled_receivers, disabled_receivers} = Notification.get_notified_from_activity(activity) {enabled_receivers, disabled_receivers} = Notification.get_notified_from_activity(activity)
@ -726,7 +726,7 @@ test "it returns following domain-blocking recipient in enabled recipients list"
{:ok, other_user} = User.block_domain(other_user, blocked_domain) {:ok, other_user} = User.block_domain(other_user, blocked_domain)
{:ok, other_user} = User.follow(other_user, user) {:ok, other_user} = User.follow(other_user, user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{other_user.nickname}!"}) {:ok, activity} = CommonAPI.post(user, %{status: "hey @#{other_user.nickname}!"})
{enabled_receivers, disabled_receivers} = Notification.get_notified_from_activity(activity) {enabled_receivers, disabled_receivers} = Notification.get_notified_from_activity(activity)
@ -740,7 +740,7 @@ test "liking an activity results in 1 notification, then 0 if the activity is de
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) {:ok, activity} = CommonAPI.post(user, %{status: "test post"})
assert Enum.empty?(Notification.for_user(user)) assert Enum.empty?(Notification.for_user(user))
@ -757,7 +757,7 @@ test "liking an activity results in 1 notification, then 0 if the activity is un
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) {:ok, activity} = CommonAPI.post(user, %{status: "test post"})
assert Enum.empty?(Notification.for_user(user)) assert Enum.empty?(Notification.for_user(user))
@ -774,7 +774,7 @@ test "repeating an activity results in 1 notification, then 0 if the activity is
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) {:ok, activity} = CommonAPI.post(user, %{status: "test post"})
assert Enum.empty?(Notification.for_user(user)) assert Enum.empty?(Notification.for_user(user))
@ -791,7 +791,7 @@ test "repeating an activity results in 1 notification, then 0 if the activity is
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) {:ok, activity} = CommonAPI.post(user, %{status: "test post"})
assert Enum.empty?(Notification.for_user(user)) assert Enum.empty?(Notification.for_user(user))
@ -808,7 +808,7 @@ test "liking an activity which is already deleted does not generate a notificati
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) {:ok, activity} = CommonAPI.post(user, %{status: "test post"})
assert Enum.empty?(Notification.for_user(user)) assert Enum.empty?(Notification.for_user(user))
@ -825,7 +825,7 @@ test "repeating an activity which is already deleted does not generate a notific
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) {:ok, activity} = CommonAPI.post(user, %{status: "test post"})
assert Enum.empty?(Notification.for_user(user)) assert Enum.empty?(Notification.for_user(user))
@ -842,13 +842,13 @@ test "replying to a deleted post without tagging does not generate a notificatio
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) {:ok, activity} = CommonAPI.post(user, %{status: "test post"})
{:ok, _deletion_activity} = CommonAPI.delete(activity.id, user) {:ok, _deletion_activity} = CommonAPI.delete(activity.id, user)
{:ok, _reply_activity} = {:ok, _reply_activity} =
CommonAPI.post(other_user, %{ CommonAPI.post(other_user, %{
"status" => "test reply", status: "test reply",
"in_reply_to_status_id" => activity.id in_reply_to_status_id: activity.id
}) })
assert Enum.empty?(Notification.for_user(user)) assert Enum.empty?(Notification.for_user(user))
@ -859,7 +859,7 @@ test "notifications are deleted if a local user is deleted" do
other_user = insert(:user) other_user = insert(:user)
{:ok, _activity} = {:ok, _activity} =
CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}", "visibility" => "direct"}) CommonAPI.post(user, %{status: "hi @#{other_user.nickname}", visibility: "direct"})
refute Enum.empty?(Notification.for_user(other_user)) refute Enum.empty?(Notification.for_user(other_user))
@ -970,7 +970,7 @@ test "it returns notifications for muted user without notifications" do
muted = insert(:user) muted = insert(:user)
{:ok, _user_relationships} = User.mute(user, muted, false) {:ok, _user_relationships} = User.mute(user, muted, false)
{:ok, _activity} = CommonAPI.post(muted, %{"status" => "hey @#{user.nickname}"}) {:ok, _activity} = CommonAPI.post(muted, %{status: "hey @#{user.nickname}"})
assert length(Notification.for_user(user)) == 1 assert length(Notification.for_user(user)) == 1
end end
@ -980,7 +980,7 @@ test "it doesn't return notifications for muted user with notifications" do
muted = insert(:user) muted = insert(:user)
{:ok, _user_relationships} = User.mute(user, muted) {:ok, _user_relationships} = User.mute(user, muted)
{:ok, _activity} = CommonAPI.post(muted, %{"status" => "hey @#{user.nickname}"}) {:ok, _activity} = CommonAPI.post(muted, %{status: "hey @#{user.nickname}"})
assert Notification.for_user(user) == [] assert Notification.for_user(user) == []
end end
@ -990,7 +990,7 @@ test "it doesn't return notifications for blocked user" do
blocked = insert(:user) blocked = insert(:user)
{:ok, _user_relationship} = User.block(user, blocked) {:ok, _user_relationship} = User.block(user, blocked)
{:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"}) {:ok, _activity} = CommonAPI.post(blocked, %{status: "hey @#{user.nickname}"})
assert Notification.for_user(user) == [] assert Notification.for_user(user) == []
end end
@ -1000,7 +1000,7 @@ test "it doesn't return notifications for domain-blocked non-followed user" do
blocked = insert(:user, ap_id: "http://some-domain.com") blocked = insert(:user, ap_id: "http://some-domain.com")
{:ok, user} = User.block_domain(user, "some-domain.com") {:ok, user} = User.block_domain(user, "some-domain.com")
{:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"}) {:ok, _activity} = CommonAPI.post(blocked, %{status: "hey @#{user.nickname}"})
assert Notification.for_user(user) == [] assert Notification.for_user(user) == []
end end
@ -1012,7 +1012,7 @@ test "it returns notifications for domain-blocked but followed user" do
{:ok, user} = User.block_domain(user, "some-domain.com") {:ok, user} = User.block_domain(user, "some-domain.com")
{:ok, _} = User.follow(user, blocked) {:ok, _} = User.follow(user, blocked)
{:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"}) {:ok, _activity} = CommonAPI.post(blocked, %{status: "hey @#{user.nickname}"})
assert length(Notification.for_user(user)) == 1 assert length(Notification.for_user(user)) == 1
end end
@ -1021,7 +1021,7 @@ test "it doesn't return notifications for muted thread" do
user = insert(:user) user = insert(:user)
another_user = insert(:user) another_user = insert(:user)
{:ok, activity} = CommonAPI.post(another_user, %{"status" => "hey @#{user.nickname}"}) {:ok, activity} = CommonAPI.post(another_user, %{status: "hey @#{user.nickname}"})
{:ok, _} = Pleroma.ThreadMute.add_mute(user.id, activity.data["context"]) {:ok, _} = Pleroma.ThreadMute.add_mute(user.id, activity.data["context"])
assert Notification.for_user(user) == [] assert Notification.for_user(user) == []
@ -1032,7 +1032,7 @@ test "it returns notifications from a muted user when with_muted is set" do
muted = insert(:user) muted = insert(:user)
{:ok, _user_relationships} = User.mute(user, muted) {:ok, _user_relationships} = User.mute(user, muted)
{:ok, _activity} = CommonAPI.post(muted, %{"status" => "hey @#{user.nickname}"}) {:ok, _activity} = CommonAPI.post(muted, %{status: "hey @#{user.nickname}"})
assert length(Notification.for_user(user, %{with_muted: true})) == 1 assert length(Notification.for_user(user, %{with_muted: true})) == 1
end end
@ -1042,7 +1042,7 @@ test "it doesn't return notifications from a blocked user when with_muted is set
blocked = insert(:user) blocked = insert(:user)
{:ok, _user_relationship} = User.block(user, blocked) {:ok, _user_relationship} = User.block(user, blocked)
{:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"}) {:ok, _activity} = CommonAPI.post(blocked, %{status: "hey @#{user.nickname}"})
assert Enum.empty?(Notification.for_user(user, %{with_muted: true})) assert Enum.empty?(Notification.for_user(user, %{with_muted: true}))
end end
@ -1053,7 +1053,7 @@ test "when with_muted is set, " <>
blocked = insert(:user, ap_id: "http://some-domain.com") blocked = insert(:user, ap_id: "http://some-domain.com")
{:ok, user} = User.block_domain(user, "some-domain.com") {:ok, user} = User.block_domain(user, "some-domain.com")
{:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"}) {:ok, _activity} = CommonAPI.post(blocked, %{status: "hey @#{user.nickname}"})
assert Enum.empty?(Notification.for_user(user, %{with_muted: true})) assert Enum.empty?(Notification.for_user(user, %{with_muted: true}))
end end
@ -1062,7 +1062,7 @@ test "it returns notifications from muted threads when with_muted is set" do
user = insert(:user) user = insert(:user)
another_user = insert(:user) another_user = insert(:user)
{:ok, activity} = CommonAPI.post(another_user, %{"status" => "hey @#{user.nickname}"}) {:ok, activity} = CommonAPI.post(another_user, %{status: "hey @#{user.nickname}"})
{:ok, _} = Pleroma.ThreadMute.add_mute(user.id, activity.data["context"]) {:ok, _} = Pleroma.ThreadMute.add_mute(user.id, activity.data["context"])
assert length(Notification.for_user(user, %{with_muted: true})) == 1 assert length(Notification.for_user(user, %{with_muted: true})) == 1

View file

@ -16,7 +16,7 @@ defmodule Pleroma.Plugs.AuthenticationPlugTest do
user = %User{ user = %User{
id: 1, id: 1,
name: "dude", name: "dude",
password_hash: Comeonin.Pbkdf2.hashpwsalt("guy") password_hash: Pbkdf2.hash_pwd_salt("guy")
} }
conn = conn =
@ -79,6 +79,13 @@ test "check sha512-crypt hash" do
assert AuthenticationPlug.checkpw("password", hash) assert AuthenticationPlug.checkpw("password", hash)
end end
test "check bcrypt hash" do
hash = "$2a$10$uyhC/R/zoE1ndwwCtMusK.TLVzkQ/Ugsbqp3uXI.CTTz0gBw.24jS"
assert AuthenticationPlug.checkpw("password", hash)
refute AuthenticationPlug.checkpw("password1", hash)
end
test "it returns false when hash invalid" do test "it returns false when hash invalid" do
hash = hash =
"psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1" "psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"

View file

@ -22,26 +22,26 @@ test "on new status" do
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
CommonAPI.post(user, %{"visibility" => "public", "status" => "hey"}) CommonAPI.post(user, %{visibility: "public", status: "hey"})
Enum.each(0..1, fn _ -> Enum.each(0..1, fn _ ->
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"visibility" => "unlisted", visibility: "unlisted",
"status" => "hey" status: "hey"
}) })
end) end)
Enum.each(0..2, fn _ -> Enum.each(0..2, fn _ ->
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"visibility" => "direct", visibility: "direct",
"status" => "hey @#{other_user.nickname}" status: "hey @#{other_user.nickname}"
}) })
end) end)
Enum.each(0..3, fn _ -> Enum.each(0..3, fn _ ->
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"visibility" => "private", visibility: "private",
"status" => "hey" status: "hey"
}) })
end) end)
@ -51,7 +51,7 @@ test "on new status" do
test "on status delete" do test "on status delete" do
user = insert(:user) user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"visibility" => "public", "status" => "hey"}) {:ok, activity} = CommonAPI.post(user, %{visibility: "public", status: "hey"})
assert %{public: 1} = Pleroma.Stats.get_status_visibility_count() assert %{public: 1} = Pleroma.Stats.get_status_visibility_count()
CommonAPI.delete(activity.id, user) CommonAPI.delete(activity.id, user)
assert %{public: 0} = Pleroma.Stats.get_status_visibility_count() assert %{public: 0} = Pleroma.Stats.get_status_visibility_count()
@ -59,16 +59,16 @@ test "on status delete" do
test "on status visibility update" do test "on status visibility update" do
user = insert(:user) user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"visibility" => "public", "status" => "hey"}) {:ok, activity} = CommonAPI.post(user, %{visibility: "public", status: "hey"})
assert %{public: 1, private: 0} = Pleroma.Stats.get_status_visibility_count() assert %{public: 1, private: 0} = Pleroma.Stats.get_status_visibility_count()
{:ok, _} = CommonAPI.update_activity_scope(activity.id, %{"visibility" => "private"}) {:ok, _} = CommonAPI.update_activity_scope(activity.id, %{visibility: "private"})
assert %{public: 0, private: 1} = Pleroma.Stats.get_status_visibility_count() assert %{public: 0, private: 1} = Pleroma.Stats.get_status_visibility_count()
end end
test "doesn't count unrelated activities" do test "doesn't count unrelated activities" do
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"visibility" => "public", "status" => "hey"}) {:ok, activity} = CommonAPI.post(user, %{visibility: "public", status: "hey"})
_ = CommonAPI.follow(user, other_user) _ = CommonAPI.follow(user, other_user)
CommonAPI.favorite(other_user, activity.id) CommonAPI.favorite(other_user, activity.id)
CommonAPI.repeat(activity.id, other_user) CommonAPI.repeat(activity.id, other_user)

View file

@ -7,7 +7,7 @@ def build(data \\ %{}) do
email: "test@example.org", email: "test@example.org",
name: "Test Name", name: "Test Name",
nickname: "testname", nickname: "testname",
password_hash: Comeonin.Pbkdf2.hashpwsalt("test"), password_hash: Pbkdf2.hash_pwd_salt("test"),
bio: "A tester.", bio: "A tester.",
ap_id: "some id", ap_id: "some id",
last_digest_emailed_at: NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second), last_digest_emailed_at: NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second),

View file

@ -29,7 +29,7 @@ def user_factory do
name: sequence(:name, &"Test テスト User #{&1}"), name: sequence(:name, &"Test テスト User #{&1}"),
email: sequence(:email, &"user#{&1}@example.com"), email: sequence(:email, &"user#{&1}@example.com"),
nickname: sequence(:nickname, &"nick#{&1}"), nickname: sequence(:nickname, &"nick#{&1}"),
password_hash: Comeonin.Pbkdf2.hashpwsalt("test"), password_hash: Pbkdf2.hash_pwd_salt("test"),
bio: sequence(:bio, &"Tester Number #{&1}"), bio: sequence(:bio, &"Tester Number #{&1}"),
last_digest_emailed_at: NaiveDateTime.utc_now(), last_digest_emailed_at: NaiveDateTime.utc_now(),
last_refreshed_at: NaiveDateTime.utc_now(), last_refreshed_at: NaiveDateTime.utc_now(),

View file

@ -13,11 +13,11 @@ defmodule Mix.Tasks.Pleroma.CountStatusesTest do
test "counts statuses" do test "counts statuses" do
user = insert(:user) user = insert(:user)
{:ok, _} = CommonAPI.post(user, %{"status" => "test"}) {:ok, _} = CommonAPI.post(user, %{status: "test"})
{:ok, _} = CommonAPI.post(user, %{"status" => "test2"}) {:ok, _} = CommonAPI.post(user, %{status: "test2"})
user2 = insert(:user) user2 = insert(:user)
{:ok, _} = CommonAPI.post(user2, %{"status" => "test3"}) {:ok, _} = CommonAPI.post(user2, %{status: "test3"})
user = refresh_record(user) user = refresh_record(user)
user2 = refresh_record(user2) user2 = refresh_record(user2)

View file

@ -26,7 +26,7 @@ defmodule Mix.Tasks.Pleroma.DatabaseTest do
describe "running remove_embedded_objects" do describe "running remove_embedded_objects" do
test "it replaces objects with references" do test "it replaces objects with references" do
user = insert(:user) user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "test"}) {:ok, activity} = CommonAPI.post(user, %{status: "test"})
new_data = Map.put(activity.data, "object", activity.object.data) new_data = Map.put(activity.data, "object", activity.object.data)
{:ok, activity} = {:ok, activity} =
@ -99,8 +99,8 @@ test "following and followers count are updated" do
test "it turns OrderedCollection likes into empty arrays" do test "it turns OrderedCollection likes into empty arrays" do
[user, user2] = insert_pair(:user) [user, user2] = insert_pair(:user)
{:ok, %{id: id, object: object}} = CommonAPI.post(user, %{"status" => "test"}) {:ok, %{id: id, object: object}} = CommonAPI.post(user, %{status: "test"})
{:ok, %{object: object2}} = CommonAPI.post(user, %{"status" => "test test"}) {:ok, %{object: object2}} = CommonAPI.post(user, %{status: "test test"})
CommonAPI.favorite(user2, id) CommonAPI.favorite(user2, id)

View file

@ -25,7 +25,7 @@ test "Sends digest to the given user" do
Enum.each(0..10, fn i -> Enum.each(0..10, fn i ->
{:ok, _activity} = {:ok, _activity} =
CommonAPI.post(user1, %{ CommonAPI.post(user1, %{
"status" => "hey ##{i} @#{user2.nickname}!" status: "hey ##{i} @#{user2.nickname}!"
}) })
end) end)

View file

@ -12,26 +12,26 @@ test "counts statuses" do
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
CommonAPI.post(user, %{"visibility" => "public", "status" => "hey"}) CommonAPI.post(user, %{visibility: "public", status: "hey"})
Enum.each(0..1, fn _ -> Enum.each(0..1, fn _ ->
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"visibility" => "unlisted", visibility: "unlisted",
"status" => "hey" status: "hey"
}) })
end) end)
Enum.each(0..2, fn _ -> Enum.each(0..2, fn _ ->
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"visibility" => "direct", visibility: "direct",
"status" => "hey @#{other_user.nickname}" status: "hey @#{other_user.nickname}"
}) })
end) end)
Enum.each(0..3, fn _ -> Enum.each(0..3, fn _ ->
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"visibility" => "private", visibility: "private",
"status" => "hey" status: "hey"
}) })
end) end)

View file

@ -3,9 +3,12 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.UserTest do defmodule Mix.Tasks.Pleroma.UserTest do
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.Tests.ObanHelpers alias Pleroma.Tests.ObanHelpers
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.OAuth.Authorization alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token
@ -103,6 +106,28 @@ test "user is deleted" do
end end
end end
test "a remote user's create activity is deleted when the object has been pruned" do
user = insert(:user)
{:ok, post} = CommonAPI.post(user, %{status: "uguu"})
object = Object.normalize(post)
Object.prune(object)
with_mock Pleroma.Web.Federator,
publish: fn _ -> nil end do
Mix.Tasks.Pleroma.User.run(["rm", user.nickname])
ObanHelpers.perform_all()
assert_received {:mix_shell, :info, [message]}
assert message =~ " deleted"
assert %{deactivated: true} = User.get_by_nickname(user.nickname)
assert called(Pleroma.Web.Federator.publish(:_))
end
refute Activity.get_by_id(post.id)
end
test "no user to delete" do test "no user to delete" do
Mix.Tasks.Pleroma.User.run(["rm", "nonexistent"]) Mix.Tasks.Pleroma.User.run(["rm", "nonexistent"])

View file

@ -58,7 +58,7 @@ test "it returns path with bucket namespace when namespace is set" do
name: "image-tet.jpg", name: "image-tet.jpg",
content_type: "image/jpg", content_type: "image/jpg",
path: "test_folder/image-tet.jpg", path: "test_folder/image-tet.jpg",
tempfile: Path.absname("test/fixtures/image_tmp.jpg") tempfile: Path.absname("test/instance_static/add/shortcode.png")
} }
[file_upload: file_upload] [file_upload: file_upload]

View file

@ -990,7 +990,7 @@ test "works for announces" do
actor = insert(:user) actor = insert(:user)
user = insert(:user, local: true) user = insert(:user, local: true)
{:ok, activity} = CommonAPI.post(actor, %{"status" => "hello"}) {:ok, activity} = CommonAPI.post(actor, %{status: "hello"})
{:ok, announce, _} = CommonAPI.repeat(activity.id, user) {:ok, announce, _} = CommonAPI.repeat(activity.id, user)
recipients = User.get_recipients_from_activity(announce) recipients = User.get_recipients_from_activity(announce)
@ -1007,7 +1007,7 @@ test "get recipients" do
{:ok, activity} = {:ok, activity} =
CommonAPI.post(actor, %{ CommonAPI.post(actor, %{
"status" => "hey @#{addressed.nickname} @#{addressed_remote.nickname}" status: "hey @#{addressed.nickname} @#{addressed_remote.nickname}"
}) })
assert Enum.map([actor, addressed], & &1.ap_id) -- assert Enum.map([actor, addressed], & &1.ap_id) --
@ -1029,7 +1029,7 @@ test "has following" do
{:ok, activity} = {:ok, activity} =
CommonAPI.post(actor, %{ CommonAPI.post(actor, %{
"status" => "hey @#{addressed.nickname}" status: "hey @#{addressed.nickname}"
}) })
assert Enum.map([actor, addressed], & &1.ap_id) -- assert Enum.map([actor, addressed], & &1.ap_id) --
@ -1090,7 +1090,7 @@ test "hide a user's statuses from timelines and notifications" do
{:ok, user2} = User.follow(user2, user) {:ok, user2} = User.follow(user2, user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{user2.nickname}"}) {:ok, activity} = CommonAPI.post(user, %{status: "hey @#{user2.nickname}"})
activity = Repo.preload(activity, :bookmark) activity = Repo.preload(activity, :bookmark)
@ -1126,7 +1126,7 @@ test "hide a user's statuses from timelines and notifications" do
setup do: clear_config([:instance, :federating]) setup do: clear_config([:instance, :federating])
test ".delete_user_activities deletes all create activities", %{user: user} do test ".delete_user_activities deletes all create activities", %{user: user} do
{:ok, activity} = CommonAPI.post(user, %{"status" => "2hu"}) {:ok, activity} = CommonAPI.post(user, %{status: "2hu"})
User.delete_user_activities(user) User.delete_user_activities(user)
@ -1411,7 +1411,7 @@ test "Only includes users who has no recent activity" do
{:ok, _} = {:ok, _} =
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"status" => "hey @#{to.nickname}" status: "hey @#{to.nickname}"
}) })
end) end)
@ -1443,12 +1443,12 @@ test "Only includes users with no read notifications" do
Enum.each(recipients, fn to -> Enum.each(recipients, fn to ->
{:ok, _} = {:ok, _} =
CommonAPI.post(sender, %{ CommonAPI.post(sender, %{
"status" => "hey @#{to.nickname}" status: "hey @#{to.nickname}"
}) })
{:ok, _} = {:ok, _} =
CommonAPI.post(sender, %{ CommonAPI.post(sender, %{
"status" => "hey again @#{to.nickname}" status: "hey again @#{to.nickname}"
}) })
end) end)

View file

@ -341,7 +341,7 @@ test "it caches a response", %{conn: conn} do
test "cached purged after activity deletion", %{conn: conn} do test "cached purged after activity deletion", %{conn: conn} do
user = insert(:user) user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "cofe"}) {:ok, activity} = CommonAPI.post(user, %{status: "cofe"})
uuid = String.split(activity.data["id"], "/") |> List.last() uuid = String.split(activity.data["id"], "/") |> List.last()

View file

@ -32,7 +32,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
describe "streaming out participations" do describe "streaming out participations" do
test "it streams them out" do test "it streams them out" do
user = insert(:user) user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"}) {:ok, activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
{:ok, conversation} = Pleroma.Conversation.create_or_bump_for(activity) {:ok, conversation} = Pleroma.Conversation.create_or_bump_for(activity)
@ -56,8 +56,8 @@ test "streams them out on activity creation" do
stream: fn _, _ -> nil end do stream: fn _, _ -> nil end do
{:ok, activity} = {:ok, activity} =
CommonAPI.post(user_one, %{ CommonAPI.post(user_one, %{
"status" => "@#{user_two.nickname}", status: "@#{user_two.nickname}",
"visibility" => "direct" visibility: "direct"
}) })
conversation = conversation =
@ -74,15 +74,13 @@ test "streams them out on activity creation" do
test "it restricts by the appropriate visibility" do test "it restricts by the appropriate visibility" do
user = insert(:user) user = insert(:user)
{:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"}) {:ok, public_activity} = CommonAPI.post(user, %{status: ".", visibility: "public"})
{:ok, direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"}) {:ok, direct_activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
{:ok, unlisted_activity} = {:ok, unlisted_activity} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"})
CommonAPI.post(user, %{"status" => ".", "visibility" => "unlisted"})
{:ok, private_activity} = {:ok, private_activity} = CommonAPI.post(user, %{status: ".", visibility: "private"})
CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
activities = activities =
ActivityPub.fetch_activities([], %{:visibility => "direct", "actor_id" => user.ap_id}) ActivityPub.fetch_activities([], %{:visibility => "direct", "actor_id" => user.ap_id})
@ -118,15 +116,13 @@ test "it restricts by the appropriate visibility" do
test "it excludes by the appropriate visibility" do test "it excludes by the appropriate visibility" do
user = insert(:user) user = insert(:user)
{:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"}) {:ok, public_activity} = CommonAPI.post(user, %{status: ".", visibility: "public"})
{:ok, direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"}) {:ok, direct_activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
{:ok, unlisted_activity} = {:ok, unlisted_activity} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"})
CommonAPI.post(user, %{"status" => ".", "visibility" => "unlisted"})
{:ok, private_activity} = {:ok, private_activity} = CommonAPI.post(user, %{status: ".", visibility: "private"})
CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
activities = activities =
ActivityPub.fetch_activities([], %{ ActivityPub.fetch_activities([], %{
@ -193,9 +189,9 @@ test "it returns a user that is invisible" do
test "it fetches the appropriate tag-restricted posts" do test "it fetches the appropriate tag-restricted posts" do
user = insert(:user) user = insert(:user)
{:ok, status_one} = CommonAPI.post(user, %{"status" => ". #test"}) {:ok, status_one} = CommonAPI.post(user, %{status: ". #test"})
{:ok, status_two} = CommonAPI.post(user, %{"status" => ". #essais"}) {:ok, status_two} = CommonAPI.post(user, %{status: ". #essais"})
{:ok, status_three} = CommonAPI.post(user, %{"status" => ". #test #reject"}) {:ok, status_three} = CommonAPI.post(user, %{status: ". #test #reject"})
fetch_one = ActivityPub.fetch_activities([], %{"type" => "Create", "tag" => "test"}) fetch_one = ActivityPub.fetch_activities([], %{"type" => "Create", "tag" => "test"})
@ -432,26 +428,26 @@ test "increases user note count only for public activities" do
{:ok, _} = {:ok, _} =
CommonAPI.post(User.get_cached_by_id(user.id), %{ CommonAPI.post(User.get_cached_by_id(user.id), %{
"status" => "1", status: "1",
"visibility" => "public" visibility: "public"
}) })
{:ok, _} = {:ok, _} =
CommonAPI.post(User.get_cached_by_id(user.id), %{ CommonAPI.post(User.get_cached_by_id(user.id), %{
"status" => "2", status: "2",
"visibility" => "unlisted" visibility: "unlisted"
}) })
{:ok, _} = {:ok, _} =
CommonAPI.post(User.get_cached_by_id(user.id), %{ CommonAPI.post(User.get_cached_by_id(user.id), %{
"status" => "2", status: "2",
"visibility" => "private" visibility: "private"
}) })
{:ok, _} = {:ok, _} =
CommonAPI.post(User.get_cached_by_id(user.id), %{ CommonAPI.post(User.get_cached_by_id(user.id), %{
"status" => "3", status: "3",
"visibility" => "direct" visibility: "direct"
}) })
user = User.get_cached_by_id(user.id) user = User.get_cached_by_id(user.id)
@ -462,27 +458,27 @@ test "increases replies count" do
user = insert(:user) user = insert(:user)
user2 = insert(:user) user2 = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"}) {:ok, activity} = CommonAPI.post(user, %{status: "1", visibility: "public"})
ap_id = activity.data["id"] ap_id = activity.data["id"]
reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id} reply_data = %{status: "1", in_reply_to_status_id: activity.id}
# public # public
{:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public")) {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, :visibility, "public"))
assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
assert object.data["repliesCount"] == 1 assert object.data["repliesCount"] == 1
# unlisted # unlisted
{:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted")) {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, :visibility, "unlisted"))
assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
assert object.data["repliesCount"] == 2 assert object.data["repliesCount"] == 2
# private # private
{:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private")) {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, :visibility, "private"))
assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
assert object.data["repliesCount"] == 2 assert object.data["repliesCount"] == 2
# direct # direct
{:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct")) {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, :visibility, "direct"))
assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
assert object.data["repliesCount"] == 2 assert object.data["repliesCount"] == 2
end end
@ -569,13 +565,13 @@ test "doesn't return transitive interactions concerning blocked users" do
{:ok, _user_relationship} = User.block(blocker, blockee) {:ok, _user_relationship} = User.block(blocker, blockee)
{:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey!"}) {:ok, activity_one} = CommonAPI.post(friend, %{status: "hey!"})
{:ok, activity_two} = CommonAPI.post(friend, %{"status" => "hey! @#{blockee.nickname}"}) {:ok, activity_two} = CommonAPI.post(friend, %{status: "hey! @#{blockee.nickname}"})
{:ok, activity_three} = CommonAPI.post(blockee, %{"status" => "hey! @#{friend.nickname}"}) {:ok, activity_three} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"})
{:ok, activity_four} = CommonAPI.post(blockee, %{"status" => "hey! @#{blocker.nickname}"}) {:ok, activity_four} = CommonAPI.post(blockee, %{status: "hey! @#{blocker.nickname}"})
activities = ActivityPub.fetch_activities([], %{"blocking_user" => blocker}) activities = ActivityPub.fetch_activities([], %{"blocking_user" => blocker})
@ -592,9 +588,9 @@ test "doesn't return announce activities concerning blocked users" do
{:ok, _user_relationship} = User.block(blocker, blockee) {:ok, _user_relationship} = User.block(blocker, blockee)
{:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey!"}) {:ok, activity_one} = CommonAPI.post(friend, %{status: "hey!"})
{:ok, activity_two} = CommonAPI.post(blockee, %{"status" => "hey! @#{friend.nickname}"}) {:ok, activity_two} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"})
{:ok, activity_three, _} = CommonAPI.repeat(activity_two.id, friend) {:ok, activity_three, _} = CommonAPI.repeat(activity_two.id, friend)
@ -774,10 +770,9 @@ test "excludes reblogs on request" do
test "doesn't retrieve unlisted activities" do test "doesn't retrieve unlisted activities" do
user = insert(:user) user = insert(:user)
{:ok, _unlisted_activity} = {:ok, _unlisted_activity} = CommonAPI.post(user, %{status: "yeah", visibility: "unlisted"})
CommonAPI.post(user, %{"status" => "yeah", "visibility" => "unlisted"})
{:ok, listed_activity} = CommonAPI.post(user, %{"status" => "yeah"}) {:ok, listed_activity} = CommonAPI.post(user, %{status: "yeah"})
[activity] = ActivityPub.fetch_public_activities() [activity] = ActivityPub.fetch_public_activities()
@ -912,7 +907,7 @@ test "reverts annouce from object on error" do
describe "announcing a private object" do describe "announcing a private object" do
test "adds an announce activity to the db if the audience is not widened" do test "adds an announce activity to the db if the audience is not widened" do
user = insert(:user) user = insert(:user)
{:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"}) {:ok, note_activity} = CommonAPI.post(user, %{status: ".", visibility: "private"})
object = Object.normalize(note_activity) object = Object.normalize(note_activity)
{:ok, announce_activity, object} = ActivityPub.announce(user, object, nil, true, false) {:ok, announce_activity, object} = ActivityPub.announce(user, object, nil, true, false)
@ -926,7 +921,7 @@ test "adds an announce activity to the db if the audience is not widened" do
test "does not add an announce activity to the db if the audience is widened" do test "does not add an announce activity to the db if the audience is widened" do
user = insert(:user) user = insert(:user)
{:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"}) {:ok, note_activity} = CommonAPI.post(user, %{status: ".", visibility: "private"})
object = Object.normalize(note_activity) object = Object.normalize(note_activity)
assert {:error, _} = ActivityPub.announce(user, object, nil, true, true) assert {:error, _} = ActivityPub.announce(user, object, nil, true, true)
@ -935,7 +930,7 @@ test "does not add an announce activity to the db if the audience is widened" do
test "does not add an announce activity to the db if the announcer is not the author" do test "does not add an announce activity to the db if the announcer is not the author" do
user = insert(:user) user = insert(:user)
announcer = insert(:user) announcer = insert(:user)
{:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"}) {:ok, note_activity} = CommonAPI.post(user, %{status: ".", visibility: "private"})
object = Object.normalize(note_activity) object = Object.normalize(note_activity)
assert {:error, _} = ActivityPub.announce(announcer, object, nil, true, false) assert {:error, _} = ActivityPub.announce(announcer, object, nil, true, false)
@ -956,7 +951,7 @@ test "copies the file to the configured folder" do
test "works with base64 encoded images" do test "works with base64 encoded images" do
file = %{ file = %{
"img" => data_uri() img: data_uri()
} }
{:ok, %Object{}} = ActivityPub.upload(file) {:ok, %Object{}} = ActivityPub.upload(file)
@ -1061,14 +1056,38 @@ test "reverts block activity on error" do
end end
test "creates a block activity" do test "creates a block activity" do
clear_config([:instance, :federating], true)
blocker = insert(:user) blocker = insert(:user)
blocked = insert(:user) blocked = insert(:user)
{:ok, activity} = ActivityPub.block(blocker, blocked) with_mock Pleroma.Web.Federator,
publish: fn _ -> nil end do
{:ok, activity} = ActivityPub.block(blocker, blocked)
assert activity.data["type"] == "Block" assert activity.data["type"] == "Block"
assert activity.data["actor"] == blocker.ap_id assert activity.data["actor"] == blocker.ap_id
assert activity.data["object"] == blocked.ap_id assert activity.data["object"] == blocked.ap_id
assert called(Pleroma.Web.Federator.publish(activity))
end
end
test "works with outgoing blocks disabled, but doesn't federate" do
clear_config([:instance, :federating], true)
clear_config([:activitypub, :outgoing_blocks], false)
blocker = insert(:user)
blocked = insert(:user)
with_mock Pleroma.Web.Federator,
publish: fn _ -> nil end do
{:ok, activity} = ActivityPub.block(blocker, blocked)
assert activity.data["type"] == "Block"
assert activity.data["actor"] == blocker.ap_id
assert activity.data["object"] == blocked.ap_id
refute called(Pleroma.Web.Federator.publish(:_))
end
end end
end end
@ -1087,23 +1106,22 @@ test "it filters broken threads" do
{:ok, user3} = User.follow(user3, user2) {:ok, user3} = User.follow(user3, user2)
assert User.following?(user3, user2) assert User.following?(user3, user2)
{:ok, public_activity} = CommonAPI.post(user3, %{"status" => "hi 1"}) {:ok, public_activity} = CommonAPI.post(user3, %{status: "hi 1"})
{:ok, private_activity_1} = {:ok, private_activity_1} = CommonAPI.post(user3, %{status: "hi 2", visibility: "private"})
CommonAPI.post(user3, %{"status" => "hi 2", "visibility" => "private"})
{:ok, private_activity_2} = {:ok, private_activity_2} =
CommonAPI.post(user2, %{ CommonAPI.post(user2, %{
"status" => "hi 3", status: "hi 3",
"visibility" => "private", visibility: "private",
"in_reply_to_status_id" => private_activity_1.id in_reply_to_status_id: private_activity_1.id
}) })
{:ok, private_activity_3} = {:ok, private_activity_3} =
CommonAPI.post(user3, %{ CommonAPI.post(user3, %{
"status" => "hi 4", status: "hi 4",
"visibility" => "private", visibility: "private",
"in_reply_to_status_id" => private_activity_2.id in_reply_to_status_id: private_activity_2.id
}) })
activities = activities =
@ -1153,9 +1171,9 @@ test "returned pinned statuses" do
Config.put([:instance, :max_pinned_statuses], 3) Config.put([:instance, :max_pinned_statuses], 3)
user = insert(:user) user = insert(:user)
{:ok, activity_one} = CommonAPI.post(user, %{"status" => "HI!!!"}) {:ok, activity_one} = CommonAPI.post(user, %{status: "HI!!!"})
{:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"}) {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
{:ok, activity_three} = CommonAPI.post(user, %{"status" => "HI!!!"}) {:ok, activity_three} = CommonAPI.post(user, %{status: "HI!!!"})
CommonAPI.pin(activity_one.id, user) CommonAPI.pin(activity_one.id, user)
user = refresh_record(user) user = refresh_record(user)
@ -1176,7 +1194,7 @@ test "returned pinned statuses" do
reporter = insert(:user) reporter = insert(:user)
target_account = insert(:user) target_account = insert(:user)
content = "foobar" content = "foobar"
{:ok, activity} = CommonAPI.post(target_account, %{"status" => content}) {:ok, activity} = CommonAPI.post(target_account, %{status: content})
context = Utils.generate_context_id() context = Utils.generate_context_id()
reporter_ap_id = reporter.ap_id reporter_ap_id = reporter.ap_id
@ -1272,8 +1290,7 @@ test "fetch_activities/2 returns activities addressed to a list " do
{:ok, list} = Pleroma.List.create("foo", user) {:ok, list} = Pleroma.List.create("foo", user)
{:ok, list} = Pleroma.List.follow(list, member) {:ok, list} = Pleroma.List.follow(list, member)
{:ok, activity} = {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
activity = Repo.preload(activity, :bookmark) activity = Repo.preload(activity, :bookmark)
activity = %Activity{activity | thread_muted?: !!activity.thread_muted?} activity = %Activity{activity | thread_muted?: !!activity.thread_muted?}
@ -1291,8 +1308,8 @@ test "fetches private posts for followed users" do
{:ok, activity} = {:ok, activity} =
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"status" => "thought I looked cute might delete later :3", status: "thought I looked cute might delete later :3",
"visibility" => "private" visibility: "private"
}) })
[result] = ActivityPub.fetch_activities_bounded([user.follower_address], []) [result] = ActivityPub.fetch_activities_bounded([user.follower_address], [])
@ -1301,12 +1318,12 @@ test "fetches private posts for followed users" do
test "fetches only public posts for other users" do test "fetches only public posts for other users" do
user = insert(:user) user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe", "visibility" => "public"}) {:ok, activity} = CommonAPI.post(user, %{status: "#cofe", visibility: "public"})
{:ok, _private_activity} = {:ok, _private_activity} =
CommonAPI.post(user, %{ CommonAPI.post(user, %{
"status" => "why is tenshi eating a corndog so cute?", status: "why is tenshi eating a corndog so cute?",
"visibility" => "private" visibility: "private"
}) })
[result] = ActivityPub.fetch_activities_bounded([], [user.follower_address]) [result] = ActivityPub.fetch_activities_bounded([], [user.follower_address])
@ -1434,11 +1451,11 @@ test "returns a favourite activities sorted by adds to favorite" do
other_user = insert(:user) other_user = insert(:user)
user1 = insert(:user) user1 = insert(:user)
user2 = insert(:user) user2 = insert(:user)
{:ok, a1} = CommonAPI.post(user1, %{"status" => "bla"}) {:ok, a1} = CommonAPI.post(user1, %{status: "bla"})
{:ok, _a2} = CommonAPI.post(user2, %{"status" => "traps are happy"}) {:ok, _a2} = CommonAPI.post(user2, %{status: "traps are happy"})
{:ok, a3} = CommonAPI.post(user2, %{"status" => "Trees Are "}) {:ok, a3} = CommonAPI.post(user2, %{status: "Trees Are "})
{:ok, a4} = CommonAPI.post(user2, %{"status" => "Agent Smith "}) {:ok, a4} = CommonAPI.post(user2, %{status: "Agent Smith "})
{:ok, a5} = CommonAPI.post(user1, %{"status" => "Red or Blue "}) {:ok, a5} = CommonAPI.post(user1, %{status: "Red or Blue "})
{:ok, _} = CommonAPI.favorite(user, a4.id) {:ok, _} = CommonAPI.favorite(user, a4.id)
{:ok, _} = CommonAPI.favorite(other_user, a3.id) {:ok, _} = CommonAPI.favorite(other_user, a3.id)
@ -1518,10 +1535,9 @@ test "old user must be in the new user's `also_known_as` list" do
test "doesn't retrieve replies activities with exclude_replies" do test "doesn't retrieve replies activities with exclude_replies" do
user = insert(:user) user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "yeah"}) {:ok, activity} = CommonAPI.post(user, %{status: "yeah"})
{:ok, _reply} = {:ok, _reply} = CommonAPI.post(user, %{status: "yeah", in_reply_to_status_id: activity.id})
CommonAPI.post(user, %{"status" => "yeah", "in_reply_to_status_id" => activity.id})
[result] = ActivityPub.fetch_public_activities(%{"exclude_replies" => "true"}) [result] = ActivityPub.fetch_public_activities(%{"exclude_replies" => "true"})
@ -1834,84 +1850,84 @@ defp public_messages(_) do
{:ok, u2} = User.follow(u2, u3) {:ok, u2} = User.follow(u2, u3)
{:ok, u3} = User.follow(u3, u2) {:ok, u3} = User.follow(u3, u2)
{:ok, a1} = CommonAPI.post(u1, %{"status" => "Status"}) {:ok, a1} = CommonAPI.post(u1, %{status: "Status"})
{:ok, r1_1} = {:ok, r1_1} =
CommonAPI.post(u2, %{ CommonAPI.post(u2, %{
"status" => "@#{u1.nickname} reply from u2 to u1", status: "@#{u1.nickname} reply from u2 to u1",
"in_reply_to_status_id" => a1.id in_reply_to_status_id: a1.id
}) })
{:ok, r1_2} = {:ok, r1_2} =
CommonAPI.post(u3, %{ CommonAPI.post(u3, %{
"status" => "@#{u1.nickname} reply from u3 to u1", status: "@#{u1.nickname} reply from u3 to u1",
"in_reply_to_status_id" => a1.id in_reply_to_status_id: a1.id
}) })
{:ok, r1_3} = {:ok, r1_3} =
CommonAPI.post(u4, %{ CommonAPI.post(u4, %{
"status" => "@#{u1.nickname} reply from u4 to u1", status: "@#{u1.nickname} reply from u4 to u1",
"in_reply_to_status_id" => a1.id in_reply_to_status_id: a1.id
}) })
{:ok, a2} = CommonAPI.post(u2, %{"status" => "Status"}) {:ok, a2} = CommonAPI.post(u2, %{status: "Status"})
{:ok, r2_1} = {:ok, r2_1} =
CommonAPI.post(u1, %{ CommonAPI.post(u1, %{
"status" => "@#{u2.nickname} reply from u1 to u2", status: "@#{u2.nickname} reply from u1 to u2",
"in_reply_to_status_id" => a2.id in_reply_to_status_id: a2.id
}) })
{:ok, r2_2} = {:ok, r2_2} =
CommonAPI.post(u3, %{ CommonAPI.post(u3, %{
"status" => "@#{u2.nickname} reply from u3 to u2", status: "@#{u2.nickname} reply from u3 to u2",
"in_reply_to_status_id" => a2.id in_reply_to_status_id: a2.id
}) })
{:ok, r2_3} = {:ok, r2_3} =
CommonAPI.post(u4, %{ CommonAPI.post(u4, %{
"status" => "@#{u2.nickname} reply from u4 to u2", status: "@#{u2.nickname} reply from u4 to u2",
"in_reply_to_status_id" => a2.id in_reply_to_status_id: a2.id
}) })
{:ok, a3} = CommonAPI.post(u3, %{"status" => "Status"}) {:ok, a3} = CommonAPI.post(u3, %{status: "Status"})
{:ok, r3_1} = {:ok, r3_1} =
CommonAPI.post(u1, %{ CommonAPI.post(u1, %{
"status" => "@#{u3.nickname} reply from u1 to u3", status: "@#{u3.nickname} reply from u1 to u3",
"in_reply_to_status_id" => a3.id in_reply_to_status_id: a3.id
}) })
{:ok, r3_2} = {:ok, r3_2} =
CommonAPI.post(u2, %{ CommonAPI.post(u2, %{
"status" => "@#{u3.nickname} reply from u2 to u3", status: "@#{u3.nickname} reply from u2 to u3",
"in_reply_to_status_id" => a3.id in_reply_to_status_id: a3.id
}) })
{:ok, r3_3} = {:ok, r3_3} =
CommonAPI.post(u4, %{ CommonAPI.post(u4, %{
"status" => "@#{u3.nickname} reply from u4 to u3", status: "@#{u3.nickname} reply from u4 to u3",
"in_reply_to_status_id" => a3.id in_reply_to_status_id: a3.id
}) })
{:ok, a4} = CommonAPI.post(u4, %{"status" => "Status"}) {:ok, a4} = CommonAPI.post(u4, %{status: "Status"})
{:ok, r4_1} = {:ok, r4_1} =
CommonAPI.post(u1, %{ CommonAPI.post(u1, %{
"status" => "@#{u4.nickname} reply from u1 to u4", status: "@#{u4.nickname} reply from u1 to u4",
"in_reply_to_status_id" => a4.id in_reply_to_status_id: a4.id
}) })
{:ok, r4_2} = {:ok, r4_2} =
CommonAPI.post(u2, %{ CommonAPI.post(u2, %{
"status" => "@#{u4.nickname} reply from u2 to u4", status: "@#{u4.nickname} reply from u2 to u4",
"in_reply_to_status_id" => a4.id in_reply_to_status_id: a4.id
}) })
{:ok, r4_3} = {:ok, r4_3} =
CommonAPI.post(u3, %{ CommonAPI.post(u3, %{
"status" => "@#{u4.nickname} reply from u3 to u4", status: "@#{u4.nickname} reply from u3 to u4",
"in_reply_to_status_id" => a4.id in_reply_to_status_id: a4.id
}) })
{:ok, {:ok,
@ -1935,68 +1951,68 @@ defp private_messages(_) do
{:ok, u2} = User.follow(u2, u3) {:ok, u2} = User.follow(u2, u3)
{:ok, u3} = User.follow(u3, u2) {:ok, u3} = User.follow(u3, u2)
{:ok, a1} = CommonAPI.post(u1, %{"status" => "Status", "visibility" => "private"}) {:ok, a1} = CommonAPI.post(u1, %{status: "Status", visibility: "private"})
{:ok, r1_1} = {:ok, r1_1} =
CommonAPI.post(u2, %{ CommonAPI.post(u2, %{
"status" => "@#{u1.nickname} reply from u2 to u1", status: "@#{u1.nickname} reply from u2 to u1",
"in_reply_to_status_id" => a1.id, in_reply_to_status_id: a1.id,
"visibility" => "private" visibility: "private"
}) })
{:ok, r1_2} = {:ok, r1_2} =
CommonAPI.post(u3, %{ CommonAPI.post(u3, %{
"status" => "@#{u1.nickname} reply from u3 to u1", status: "@#{u1.nickname} reply from u3 to u1",
"in_reply_to_status_id" => a1.id, in_reply_to_status_id: a1.id,
"visibility" => "private" visibility: "private"
}) })
{:ok, r1_3} = {:ok, r1_3} =
CommonAPI.post(u4, %{ CommonAPI.post(u4, %{
"status" => "@#{u1.nickname} reply from u4 to u1", status: "@#{u1.nickname} reply from u4 to u1",
"in_reply_to_status_id" => a1.id, in_reply_to_status_id: a1.id,
"visibility" => "private" visibility: "private"
}) })
{:ok, a2} = CommonAPI.post(u2, %{"status" => "Status", "visibility" => "private"}) {:ok, a2} = CommonAPI.post(u2, %{status: "Status", visibility: "private"})
{:ok, r2_1} = {:ok, r2_1} =
CommonAPI.post(u1, %{ CommonAPI.post(u1, %{
"status" => "@#{u2.nickname} reply from u1 to u2", status: "@#{u2.nickname} reply from u1 to u2",
"in_reply_to_status_id" => a2.id, in_reply_to_status_id: a2.id,
"visibility" => "private" visibility: "private"
}) })
{:ok, r2_2} = {:ok, r2_2} =
CommonAPI.post(u3, %{ CommonAPI.post(u3, %{
"status" => "@#{u2.nickname} reply from u3 to u2", status: "@#{u2.nickname} reply from u3 to u2",
"in_reply_to_status_id" => a2.id, in_reply_to_status_id: a2.id,
"visibility" => "private" visibility: "private"
}) })
{:ok, a3} = CommonAPI.post(u3, %{"status" => "Status", "visibility" => "private"}) {:ok, a3} = CommonAPI.post(u3, %{status: "Status", visibility: "private"})
{:ok, r3_1} = {:ok, r3_1} =
CommonAPI.post(u1, %{ CommonAPI.post(u1, %{
"status" => "@#{u3.nickname} reply from u1 to u3", status: "@#{u3.nickname} reply from u1 to u3",
"in_reply_to_status_id" => a3.id, in_reply_to_status_id: a3.id,
"visibility" => "private" visibility: "private"
}) })
{:ok, r3_2} = {:ok, r3_2} =
CommonAPI.post(u2, %{ CommonAPI.post(u2, %{
"status" => "@#{u3.nickname} reply from u2 to u3", status: "@#{u3.nickname} reply from u2 to u3",
"in_reply_to_status_id" => a3.id, in_reply_to_status_id: a3.id,
"visibility" => "private" visibility: "private"
}) })
{:ok, a4} = CommonAPI.post(u4, %{"status" => "Status", "visibility" => "private"}) {:ok, a4} = CommonAPI.post(u4, %{status: "Status", visibility: "private"})
{:ok, r4_1} = {:ok, r4_1} =
CommonAPI.post(u1, %{ CommonAPI.post(u1, %{
"status" => "@#{u4.nickname} reply from u1 to u4", status: "@#{u4.nickname} reply from u1 to u4",
"in_reply_to_status_id" => a4.id, in_reply_to_status_id: a4.id,
"visibility" => "private" visibility: "private"
}) })
{:ok, {:ok,

View file

@ -13,7 +13,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
describe "EmojiReacts" do describe "EmojiReacts" do
setup do setup do
user = insert(:user) user = insert(:user)
{:ok, post_activity} = CommonAPI.post(user, %{"status" => "uguu"}) {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
object = Pleroma.Object.get_by_ap_id(post_activity.data["object"]) object = Pleroma.Object.get_by_ap_id(post_activity.data["object"])
@ -53,7 +53,7 @@ test "it is not valid with a non-emoji content field", %{valid_emoji_react: vali
describe "Undos" do describe "Undos" do
setup do setup do
user = insert(:user) user = insert(:user)
{:ok, post_activity} = CommonAPI.post(user, %{"status" => "uguu"}) {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
{:ok, like} = CommonAPI.favorite(user, post_activity.id) {:ok, like} = CommonAPI.favorite(user, post_activity.id)
{:ok, valid_like_undo, []} = Builder.undo(user, like) {:ok, valid_like_undo, []} = Builder.undo(user, like)
@ -93,7 +93,7 @@ test "it does not validate if the object is missing", %{valid_like_undo: valid_l
describe "deletes" do describe "deletes" do
setup do setup do
user = insert(:user) user = insert(:user)
{:ok, post_activity} = CommonAPI.post(user, %{"status" => "cancel me daddy"}) {:ok, post_activity} = CommonAPI.post(user, %{status: "cancel me daddy"})
{:ok, valid_post_delete, _} = Builder.delete(user, post_activity.data["object"]) {:ok, valid_post_delete, _} = Builder.delete(user, post_activity.data["object"])
{:ok, valid_user_delete, _} = Builder.delete(user, user.ap_id) {:ok, valid_user_delete, _} = Builder.delete(user, user.ap_id)
@ -185,7 +185,7 @@ test "it's valid if the actor of the object is a local superuser",
describe "likes" do describe "likes" do
setup do setup do
user = insert(:user) user = insert(:user)
{:ok, post_activity} = CommonAPI.post(user, %{"status" => "uguu"}) {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
valid_like = %{ valid_like = %{
"to" => [user.ap_id], "to" => [user.ap_id],

View file

@ -25,17 +25,58 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, op} = CommonAPI.post(other_user, %{"status" => "big oof"}) {:ok, op} = CommonAPI.post(other_user, %{status: "big oof"})
{:ok, post} = CommonAPI.post(user, %{"status" => "hey", "in_reply_to_id" => op}) {:ok, post} = CommonAPI.post(user, %{status: "hey", in_reply_to_id: op})
{:ok, favorite} = CommonAPI.favorite(user, post.id)
object = Object.normalize(post) object = Object.normalize(post)
{:ok, delete_data, _meta} = Builder.delete(user, object.data["id"]) {:ok, delete_data, _meta} = Builder.delete(user, object.data["id"])
{:ok, delete_user_data, _meta} = Builder.delete(user, user.ap_id) {:ok, delete_user_data, _meta} = Builder.delete(user, user.ap_id)
{:ok, delete, _meta} = ActivityPub.persist(delete_data, local: true) {:ok, delete, _meta} = ActivityPub.persist(delete_data, local: true)
{:ok, delete_user, _meta} = ActivityPub.persist(delete_user_data, local: true) {:ok, delete_user, _meta} = ActivityPub.persist(delete_user_data, local: true)
%{user: user, delete: delete, post: post, object: object, delete_user: delete_user, op: op}
%{
user: user,
delete: delete,
post: post,
object: object,
delete_user: delete_user,
op: op,
favorite: favorite
}
end end
test "it handles object deletions", %{ test "it handles object deletions", %{
delete: delete,
post: post,
object: object,
user: user,
op: op,
favorite: favorite
} do
with_mock Pleroma.Web.ActivityPub.ActivityPub, [:passthrough],
stream_out: fn _ -> nil end,
stream_out_participations: fn _, _ -> nil end do
{:ok, delete, _} = SideEffects.handle(delete)
user = User.get_cached_by_ap_id(object.data["actor"])
assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out(delete))
assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out_participations(object, user))
end
object = Object.get_by_id(object.id)
assert object.data["type"] == "Tombstone"
refute Activity.get_by_id(post.id)
refute Activity.get_by_id(favorite.id)
user = User.get_by_id(user.id)
assert user.note_count == 0
object = Object.normalize(op.data["object"], false)
assert object.data["repliesCount"] == 0
end
test "it handles object deletions when the object itself has been pruned", %{
delete: delete, delete: delete,
post: post, post: post,
object: object, object: object,
@ -77,7 +118,7 @@ test "it handles user deletions", %{delete_user: delete, user: user} do
poster = insert(:user) poster = insert(:user)
user = insert(:user) user = insert(:user)
{:ok, post} = CommonAPI.post(poster, %{"status" => "hey"}) {:ok, post} = CommonAPI.post(poster, %{status: "hey"})
{:ok, emoji_react_data, []} = Builder.emoji_react(user, post.object, "👌") {:ok, emoji_react_data, []} = Builder.emoji_react(user, post.object, "👌")
{:ok, emoji_react, _meta} = ActivityPub.persist(emoji_react_data, local: true) {:ok, emoji_react, _meta} = ActivityPub.persist(emoji_react_data, local: true)
@ -103,7 +144,7 @@ test "creates a notification", %{emoji_react: emoji_react, poster: poster} do
setup do setup do
poster = insert(:user) poster = insert(:user)
user = insert(:user) user = insert(:user)
{:ok, post} = CommonAPI.post(poster, %{"status" => "hey"}) {:ok, post} = CommonAPI.post(poster, %{status: "hey"})
{:ok, like} = CommonAPI.favorite(user, post.id) {:ok, like} = CommonAPI.favorite(user, post.id)
{:ok, reaction} = CommonAPI.react_with_emoji(post.id, user, "👍") {:ok, reaction} = CommonAPI.react_with_emoji(post.id, user, "👍")
{:ok, announce, _} = CommonAPI.repeat(post.id, user) {:ok, announce, _} = CommonAPI.repeat(post.id, user)
@ -203,7 +244,7 @@ test "deletes the original like", %{like_undo: like_undo, like: like} do
setup do setup do
poster = insert(:user) poster = insert(:user)
user = insert(:user) user = insert(:user)
{:ok, post} = CommonAPI.post(poster, %{"status" => "hey"}) {:ok, post} = CommonAPI.post(poster, %{status: "hey"})
{:ok, like_data, _meta} = Builder.like(user, post.object) {:ok, like_data, _meta} = Builder.like(user, post.object)
{:ok, like, _meta} = ActivityPub.persist(like_data, local: true) {:ok, like, _meta} = ActivityPub.persist(like_data, local: true)

View file

@ -44,6 +44,34 @@ test "it works for incoming deletes" do
assert object.data["type"] == "Tombstone" assert object.data["type"] == "Tombstone"
end end
test "it works for incoming when the object has been pruned" do
activity = insert(:note_activity)
{:ok, object} =
Object.normalize(activity.data["object"])
|> Repo.delete()
Cachex.del(:object_cache, "object:#{object.data["id"]}")
deleting_user = insert(:user)
data =
File.read!("test/fixtures/mastodon-delete.json")
|> Poison.decode!()
|> Map.put("actor", deleting_user.ap_id)
|> put_in(["object", "id"], activity.data["object"])
{:ok, %Activity{actor: actor, local: false, data: %{"id" => id}}} =
Transmogrifier.handle_incoming(data)
assert id == data["id"]
# We delete the Create activity because we base our timelines on it.
# This should be changed after we unify objects and activities
refute Activity.get_by_id(activity.id)
assert actor == deleting_user.ap_id
end
test "it fails for incoming deletes with spoofed origin" do test "it fails for incoming deletes with spoofed origin" do
activity = insert(:note_activity) activity = insert(:note_activity)
%{ap_id: ap_id} = insert(:user, ap_id: "https://gensokyo.2hu/users/raymoo") %{ap_id: ap_id} = insert(:user, ap_id: "https://gensokyo.2hu/users/raymoo")

View file

@ -15,7 +15,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.EmojiReactHandlingTest do
test "it works for incoming emoji reactions" do test "it works for incoming emoji reactions" do
user = insert(:user) user = insert(:user)
other_user = insert(:user, local: false) other_user = insert(:user, local: false)
{:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) {:ok, activity} = CommonAPI.post(user, %{status: "hello"})
data = data =
File.read!("test/fixtures/emoji-reaction.json") File.read!("test/fixtures/emoji-reaction.json")
@ -40,7 +40,7 @@ test "it works for incoming emoji reactions" do
test "it reject invalid emoji reactions" do test "it reject invalid emoji reactions" do
user = insert(:user) user = insert(:user)
other_user = insert(:user, local: false) other_user = insert(:user, local: false)
{:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) {:ok, activity} = CommonAPI.post(user, %{status: "hello"})
data = data =
File.read!("test/fixtures/emoji-reaction-too-long.json") File.read!("test/fixtures/emoji-reaction-too-long.json")

View file

@ -14,7 +14,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.LikeHandlingTest do
test "it works for incoming likes" do test "it works for incoming likes" do
user = insert(:user) user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) {:ok, activity} = CommonAPI.post(user, %{status: "hello"})
data = data =
File.read!("test/fixtures/mastodon-like.json") File.read!("test/fixtures/mastodon-like.json")
@ -36,7 +36,7 @@ test "it works for incoming likes" do
test "it works for incoming misskey likes, turning them into EmojiReacts" do test "it works for incoming misskey likes, turning them into EmojiReacts" do
user = insert(:user) user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) {:ok, activity} = CommonAPI.post(user, %{status: "hello"})
data = data =
File.read!("test/fixtures/misskey-like.json") File.read!("test/fixtures/misskey-like.json")
@ -57,7 +57,7 @@ test "it works for incoming misskey likes, turning them into EmojiReacts" do
test "it works for incoming misskey likes that contain unicode emojis, turning them into EmojiReacts" do test "it works for incoming misskey likes that contain unicode emojis, turning them into EmojiReacts" do
user = insert(:user) user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) {:ok, activity} = CommonAPI.post(user, %{status: "hello"})
data = data =
File.read!("test/fixtures/misskey-like.json") File.read!("test/fixtures/misskey-like.json")

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