forked from AkkomaGang/akkoma
Merge branch 'develop' into gun
This commit is contained in:
commit
f497cf2f7c
43 changed files with 1522 additions and 318 deletions
|
@ -78,6 +78,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Mastodon API: User timelines will now respect blocks, unless you are getting the user timeline of somebody you blocked (which would be empty otherwise).
|
- Mastodon API: User timelines will now respect blocks, unless you are getting the user timeline of somebody you blocked (which would be empty otherwise).
|
||||||
- Mastodon API: Favoriting / Repeating a post multiple times will now return the identical response every time. Before, executing that action twice would return an error ("already favorited") on the second try.
|
- Mastodon API: Favoriting / Repeating a post multiple times will now return the identical response every time. Before, executing that action twice would return an error ("already favorited") on the second try.
|
||||||
- Mastodon API: Limit timeline requests to 3 per timeline per 500ms per user/ip by default.
|
- Mastodon API: Limit timeline requests to 3 per timeline per 500ms per user/ip by default.
|
||||||
|
- Admin API: `PATCH /api/pleroma/admin/users/:nickname/credentials` and `GET /api/pleroma/admin/users/:nickname/credentials`
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -22,9 +22,10 @@ def generate_like_activities(user, posts) do
|
||||||
|
|
||||||
def generate_users(opts) do
|
def generate_users(opts) do
|
||||||
IO.puts("Starting generating #{opts[:users_max]} users...")
|
IO.puts("Starting generating #{opts[:users_max]} users...")
|
||||||
{time, _} = :timer.tc(fn -> do_generate_users(opts) end)
|
{time, users} = :timer.tc(fn -> do_generate_users(opts) end)
|
||||||
|
|
||||||
IO.puts("Inserting users take #{to_sec(time)} sec.\n")
|
IO.puts("Inserting users took #{to_sec(time)} sec.\n")
|
||||||
|
users
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_generate_users(opts) do
|
defp do_generate_users(opts) do
|
||||||
|
|
76
benchmarks/mix/tasks/pleroma/benchmarks/timelines.ex
Normal file
76
benchmarks/mix/tasks/pleroma/benchmarks/timelines.ex
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
defmodule Mix.Tasks.Pleroma.Benchmarks.Timelines do
|
||||||
|
use Mix.Task
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.LoadTesting.Generator
|
||||||
|
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
def run(_args) do
|
||||||
|
Mix.Pleroma.start_pleroma()
|
||||||
|
|
||||||
|
# Cleaning tables
|
||||||
|
clean_tables()
|
||||||
|
|
||||||
|
[{:ok, user} | users] = Generator.generate_users(users_max: 1000)
|
||||||
|
|
||||||
|
# Let the user make 100 posts
|
||||||
|
|
||||||
|
1..100
|
||||||
|
|> Enum.each(fn i -> CommonAPI.post(user, %{"status" => to_string(i)}) end)
|
||||||
|
|
||||||
|
# Let 10 random users post
|
||||||
|
posts =
|
||||||
|
users
|
||||||
|
|> Enum.take_random(10)
|
||||||
|
|> Enum.map(fn {:ok, random_user} ->
|
||||||
|
{:ok, activity} = CommonAPI.post(random_user, %{"status" => "."})
|
||||||
|
activity
|
||||||
|
end)
|
||||||
|
|
||||||
|
# let our user repeat them
|
||||||
|
posts
|
||||||
|
|> Enum.each(fn activity ->
|
||||||
|
CommonAPI.repeat(activity.id, user)
|
||||||
|
end)
|
||||||
|
|
||||||
|
Benchee.run(
|
||||||
|
%{
|
||||||
|
"user timeline, no followers" => fn reading_user ->
|
||||||
|
conn =
|
||||||
|
Phoenix.ConnTest.build_conn()
|
||||||
|
|> Plug.Conn.assign(:user, reading_user)
|
||||||
|
|> Plug.Conn.assign(:skip_link_headers, true)
|
||||||
|
|
||||||
|
Pleroma.Web.MastodonAPI.AccountController.statuses(conn, %{"id" => user.id})
|
||||||
|
end
|
||||||
|
},
|
||||||
|
inputs: %{"user" => user, "no user" => nil},
|
||||||
|
time: 60
|
||||||
|
)
|
||||||
|
|
||||||
|
users
|
||||||
|
|> Enum.each(fn {:ok, follower} -> Pleroma.User.follow(follower, user) end)
|
||||||
|
|
||||||
|
Benchee.run(
|
||||||
|
%{
|
||||||
|
"user timeline, all following" => fn reading_user ->
|
||||||
|
conn =
|
||||||
|
Phoenix.ConnTest.build_conn()
|
||||||
|
|> Plug.Conn.assign(:user, reading_user)
|
||||||
|
|> Plug.Conn.assign(:skip_link_headers, true)
|
||||||
|
|
||||||
|
Pleroma.Web.MastodonAPI.AccountController.statuses(conn, %{"id" => user.id})
|
||||||
|
end
|
||||||
|
},
|
||||||
|
inputs: %{"user" => user, "no user" => nil},
|
||||||
|
time: 60
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp clean_tables do
|
||||||
|
IO.puts("Deleting old data...\n")
|
||||||
|
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE users CASCADE;")
|
||||||
|
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE activities CASCADE;")
|
||||||
|
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE objects CASCADE;")
|
||||||
|
end
|
||||||
|
end
|
|
@ -2442,7 +2442,7 @@
|
||||||
%{
|
%{
|
||||||
key: :relations_actions,
|
key: :relations_actions,
|
||||||
type: [:tuple, {:list, :tuple}],
|
type: [:tuple, {:list, :tuple}],
|
||||||
description: "For actions on relations with all users (follow, unfollow)",
|
description: "For actions on relationships with all users (follow, unfollow)",
|
||||||
suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
|
suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
|
|
|
@ -414,6 +414,83 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
- `nicknames`
|
- `nicknames`
|
||||||
- Response: none (code `204`)
|
- Response: none (code `204`)
|
||||||
|
|
||||||
|
## `GET /api/pleroma/admin/users/:nickname/credentials`
|
||||||
|
|
||||||
|
### Get the user's email, password, display and settings-related fields
|
||||||
|
|
||||||
|
- Params:
|
||||||
|
- `nickname`
|
||||||
|
|
||||||
|
- Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"actor_type": "Person",
|
||||||
|
"allow_following_move": true,
|
||||||
|
"avatar": "https://pleroma.social/media/7e8e7508fd545ef580549b6881d80ec0ff2c81ed9ad37b9bdbbdf0e0d030159d.jpg",
|
||||||
|
"background": "https://pleroma.social/media/4de34c0bd10970d02cbdef8972bef0ebbf55f43cadc449554d4396156162fe9a.jpg",
|
||||||
|
"banner": "https://pleroma.social/media/8d92ba2bd244b613520abf557dd448adcd30f5587022813ee9dd068945986946.jpg",
|
||||||
|
"bio": "bio",
|
||||||
|
"default_scope": "public",
|
||||||
|
"discoverable": false,
|
||||||
|
"email": "user@example.com",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "example",
|
||||||
|
"value": "<a href=\"https://example.com\" rel=\"ugc\">https://example.com</a>"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"hide_favorites": false,
|
||||||
|
"hide_followers": false,
|
||||||
|
"hide_followers_count": false,
|
||||||
|
"hide_follows": false,
|
||||||
|
"hide_follows_count": false,
|
||||||
|
"id": "9oouHaEEUR54hls968",
|
||||||
|
"locked": true,
|
||||||
|
"name": "user",
|
||||||
|
"no_rich_text": true,
|
||||||
|
"pleroma_settings_store": {},
|
||||||
|
"raw_fields": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "example",
|
||||||
|
"value": "https://example.com"
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"show_role": true,
|
||||||
|
"skip_thread_containment": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## `PATCH /api/pleroma/admin/users/:nickname/credentials`
|
||||||
|
|
||||||
|
### Change the user's email, password, display and settings-related fields
|
||||||
|
|
||||||
|
- Params:
|
||||||
|
- `email`
|
||||||
|
- `password`
|
||||||
|
- `name`
|
||||||
|
- `bio`
|
||||||
|
- `avatar`
|
||||||
|
- `locked`
|
||||||
|
- `no_rich_text`
|
||||||
|
- `default_scope`
|
||||||
|
- `banner`
|
||||||
|
- `hide_follows`
|
||||||
|
- `hide_followers`
|
||||||
|
- `hide_followers_count`
|
||||||
|
- `hide_follows_count`
|
||||||
|
- `hide_favorites`
|
||||||
|
- `allow_following_move`
|
||||||
|
- `background`
|
||||||
|
- `show_role`
|
||||||
|
- `skip_thread_containment`
|
||||||
|
- `fields`
|
||||||
|
- `discoverable`
|
||||||
|
- `actor_type`
|
||||||
|
|
||||||
|
- Response: none (code `200`)
|
||||||
|
|
||||||
## `GET /api/pleroma/admin/reports`
|
## `GET /api/pleroma/admin/reports`
|
||||||
|
|
||||||
### Get a list of reports
|
### Get a list of reports
|
||||||
|
|
|
@ -95,6 +95,17 @@ def with_preloaded_object(query, join_type \\ :inner) do
|
||||||
|> preload([activity, object: object], object: object)
|
|> preload([activity, object: object], object: object)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Note: applies to fake activities (ActivityPub.Utils.get_notified_from_object/1 etc.)
|
||||||
|
def user_actor(%Activity{actor: nil}), do: nil
|
||||||
|
|
||||||
|
def user_actor(%Activity{} = activity) do
|
||||||
|
with %User{} <- activity.user_actor do
|
||||||
|
activity.user_actor
|
||||||
|
else
|
||||||
|
_ -> User.get_cached_by_ap_id(activity.actor)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def with_joined_user_actor(query, join_type \\ :inner) do
|
def with_joined_user_actor(query, join_type \\ :inner) do
|
||||||
join(query, join_type, [activity], u in User,
|
join(query, join_type, [activity], u in User,
|
||||||
on: u.ap_id == activity.actor,
|
on: u.ap_id == activity.actor,
|
||||||
|
|
|
@ -35,6 +35,13 @@ def by_author(query \\ Activity, %User{ap_id: ap_id}) do
|
||||||
from(a in query, where: a.actor == ^ap_id)
|
from(a in query, where: a.actor == ^ap_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def find_by_object_ap_id(activities, object_ap_id) do
|
||||||
|
Enum.find(
|
||||||
|
activities,
|
||||||
|
&(object_ap_id in [is_map(&1.data["object"]) && &1.data["object"]["id"], &1.data["object"]])
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
@spec by_object_id(query, String.t() | [String.t()]) :: query
|
@spec by_object_id(query, String.t() | [String.t()]) :: query
|
||||||
def by_object_id(query \\ Activity, object_id)
|
def by_object_id(query \\ Activity, object_id)
|
||||||
|
|
||||||
|
|
|
@ -129,21 +129,18 @@ def for_user(user, params \\ %{}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def restrict_recipients(query, user, %{"recipients" => user_ids}) do
|
def restrict_recipients(query, user, %{"recipients" => user_ids}) do
|
||||||
user_ids =
|
user_binary_ids =
|
||||||
[user.id | user_ids]
|
[user.id | user_ids]
|
||||||
|> Enum.uniq()
|
|> Enum.uniq()
|
||||||
|> Enum.reduce([], fn user_id, acc ->
|
|> User.binary_id()
|
||||||
{:ok, user_id} = FlakeId.Ecto.CompatType.dump(user_id)
|
|
||||||
[user_id | acc]
|
|
||||||
end)
|
|
||||||
|
|
||||||
conversation_subquery =
|
conversation_subquery =
|
||||||
__MODULE__
|
__MODULE__
|
||||||
|> group_by([p], p.conversation_id)
|
|> group_by([p], p.conversation_id)
|
||||||
|> having(
|
|> having(
|
||||||
[p],
|
[p],
|
||||||
count(p.user_id) == ^length(user_ids) and
|
count(p.user_id) == ^length(user_binary_ids) and
|
||||||
fragment("array_agg(?) @> ?", p.user_id, ^user_ids)
|
fragment("array_agg(?) @> ?", p.user_id, ^user_binary_ids)
|
||||||
)
|
)
|
||||||
|> select([p], %{id: p.conversation_id})
|
|> select([p], %{id: p.conversation_id})
|
||||||
|
|
||||||
|
|
|
@ -129,4 +129,32 @@ def move_following(origin, target) do
|
||||||
move_following(origin, target)
|
move_following(origin, target)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def all_between_user_sets(
|
||||||
|
source_users,
|
||||||
|
target_users
|
||||||
|
)
|
||||||
|
when is_list(source_users) and is_list(target_users) do
|
||||||
|
source_user_ids = User.binary_id(source_users)
|
||||||
|
target_user_ids = User.binary_id(target_users)
|
||||||
|
|
||||||
|
__MODULE__
|
||||||
|
|> where(
|
||||||
|
fragment(
|
||||||
|
"(follower_id = ANY(?) AND following_id = ANY(?)) OR \
|
||||||
|
(follower_id = ANY(?) AND following_id = ANY(?))",
|
||||||
|
^source_user_ids,
|
||||||
|
^target_user_ids,
|
||||||
|
^target_user_ids,
|
||||||
|
^source_user_ids
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|> Repo.all()
|
||||||
|
end
|
||||||
|
|
||||||
|
def find(following_relationships, follower, following) do
|
||||||
|
Enum.find(following_relationships, fn
|
||||||
|
fr -> fr.follower_id == follower.id and fr.following_id == following.id
|
||||||
|
end)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -605,6 +605,17 @@ def get_log_entry_message(%ModerationLog{
|
||||||
}"
|
}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||||
|
def get_log_entry_message(%ModerationLog{
|
||||||
|
data: %{
|
||||||
|
"actor" => %{"nickname" => actor_nickname},
|
||||||
|
"action" => "updated_users",
|
||||||
|
"subject" => subjects
|
||||||
|
}
|
||||||
|
}) do
|
||||||
|
"@#{actor_nickname} updated users: #{users_to_nicknames_string(subjects)}"
|
||||||
|
end
|
||||||
|
|
||||||
defp nicknames_to_string(nicknames) do
|
defp nicknames_to_string(nicknames) do
|
||||||
nicknames
|
nicknames
|
||||||
|> Enum.map(&"@#{&1}")
|
|> Enum.map(&"@#{&1}")
|
||||||
|
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Notification do
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Pagination
|
alias Pleroma.Pagination
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.ThreadMute
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.CommonAPI.Utils
|
alias Pleroma.Web.CommonAPI.Utils
|
||||||
alias Pleroma.Web.Push
|
alias Pleroma.Web.Push
|
||||||
|
@ -17,6 +18,7 @@ defmodule Pleroma.Notification do
|
||||||
|
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@type t :: %__MODULE__{}
|
@type t :: %__MODULE__{}
|
||||||
|
@ -37,11 +39,11 @@ def changeset(%Notification{} = notification, attrs) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp for_user_query_ap_id_opts(user, opts) do
|
defp for_user_query_ap_id_opts(user, opts) do
|
||||||
ap_id_relations =
|
ap_id_relationships =
|
||||||
[:block] ++
|
[:block] ++
|
||||||
if opts[@include_muted_option], do: [], else: [:notification_mute]
|
if opts[@include_muted_option], do: [], else: [:notification_mute]
|
||||||
|
|
||||||
preloaded_ap_ids = User.outgoing_relations_ap_ids(user, ap_id_relations)
|
preloaded_ap_ids = User.outgoing_relationships_ap_ids(user, ap_id_relationships)
|
||||||
|
|
||||||
exclude_blocked_opts = Map.merge(%{blocked_users_ap_ids: preloaded_ap_ids[:block]}, opts)
|
exclude_blocked_opts = Map.merge(%{blocked_users_ap_ids: preloaded_ap_ids[:block]}, opts)
|
||||||
|
|
||||||
|
@ -100,7 +102,7 @@ defp exclude_notification_muted(query, user, opts) do
|
||||||
|
|
||||||
query
|
query
|
||||||
|> where([n, a], a.actor not in ^notification_muted_ap_ids)
|
|> where([n, a], a.actor not in ^notification_muted_ap_ids)
|
||||||
|> join(:left, [n, a], tm in Pleroma.ThreadMute,
|
|> join(:left, [n, a], tm in ThreadMute,
|
||||||
on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data)
|
on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data)
|
||||||
)
|
)
|
||||||
|> where([n, a, o, tm], is_nil(tm.user_id))
|
|> where([n, a, o, tm], is_nil(tm.user_id))
|
||||||
|
@ -275,58 +277,111 @@ def dismiss(%{id: user_id} = _user, id) do
|
||||||
def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity) do
|
def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity) do
|
||||||
object = Object.normalize(activity)
|
object = Object.normalize(activity)
|
||||||
|
|
||||||
unless object && object.data["type"] == "Answer" do
|
if object && object.data["type"] == "Answer" do
|
||||||
users = get_notified_from_activity(activity)
|
|
||||||
notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
|
|
||||||
{:ok, notifications}
|
|
||||||
else
|
|
||||||
{:ok, []}
|
{:ok, []}
|
||||||
|
else
|
||||||
|
do_create_notifications(activity)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_notifications(%Activity{data: %{"type" => type}} = activity)
|
def create_notifications(%Activity{data: %{"type" => type}} = activity)
|
||||||
when type in ["Like", "Announce", "Follow", "Move", "EmojiReact"] do
|
when type in ["Like", "Announce", "Follow", "Move", "EmojiReact"] do
|
||||||
notifications =
|
do_create_notifications(activity)
|
||||||
activity
|
|
||||||
|> get_notified_from_activity()
|
|
||||||
|> Enum.map(&create_notification(activity, &1))
|
|
||||||
|
|
||||||
{:ok, notifications}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_notifications(_), do: {:ok, []}
|
def create_notifications(_), do: {:ok, []}
|
||||||
|
|
||||||
|
defp do_create_notifications(%Activity{} = activity) do
|
||||||
|
{enabled_receivers, disabled_receivers} = get_notified_from_activity(activity)
|
||||||
|
potential_receivers = enabled_receivers ++ disabled_receivers
|
||||||
|
|
||||||
|
notifications =
|
||||||
|
Enum.map(potential_receivers, fn user ->
|
||||||
|
do_send = user in enabled_receivers
|
||||||
|
create_notification(activity, user, do_send)
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, notifications}
|
||||||
|
end
|
||||||
|
|
||||||
# TODO move to sql, too.
|
# TODO move to sql, too.
|
||||||
def create_notification(%Activity{} = activity, %User{} = user) do
|
def create_notification(%Activity{} = activity, %User{} = user, do_send \\ true) do
|
||||||
unless skip?(activity, user) do
|
unless skip?(activity, user) do
|
||||||
notification = %Notification{user_id: user.id, activity: activity}
|
notification = %Notification{user_id: user.id, activity: activity}
|
||||||
{:ok, notification} = Repo.insert(notification)
|
{:ok, notification} = Repo.insert(notification)
|
||||||
|
|
||||||
["user", "user:notification"]
|
if do_send do
|
||||||
|> Streamer.stream(notification)
|
Streamer.stream(["user", "user:notification"], notification)
|
||||||
|
Push.send(notification)
|
||||||
|
end
|
||||||
|
|
||||||
Push.send(notification)
|
|
||||||
notification
|
notification
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns a tuple with 2 elements:
|
||||||
|
{enabled notification receivers, currently disabled receivers (blocking / [thread] muting)}
|
||||||
|
|
||||||
|
NOTE: might be called for FAKE Activities, see ActivityPub.Utils.get_notified_from_object/1
|
||||||
|
"""
|
||||||
def get_notified_from_activity(activity, local_only \\ true)
|
def get_notified_from_activity(activity, local_only \\ true)
|
||||||
|
|
||||||
def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, local_only)
|
def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, local_only)
|
||||||
when type in ["Create", "Like", "Announce", "Follow", "Move", "EmojiReact"] do
|
when type in ["Create", "Like", "Announce", "Follow", "Move", "EmojiReact"] do
|
||||||
[]
|
potential_receiver_ap_ids =
|
||||||
|> Utils.maybe_notify_to_recipients(activity)
|
[]
|
||||||
|> Utils.maybe_notify_mentioned_recipients(activity)
|
|> Utils.maybe_notify_to_recipients(activity)
|
||||||
|> Utils.maybe_notify_subscribers(activity)
|
|> Utils.maybe_notify_mentioned_recipients(activity)
|
||||||
|> Utils.maybe_notify_followers(activity)
|
|> Utils.maybe_notify_subscribers(activity)
|
||||||
|> Enum.uniq()
|
|> Utils.maybe_notify_followers(activity)
|
||||||
|> User.get_users_from_set(local_only)
|
|> Enum.uniq()
|
||||||
|
|
||||||
|
# Since even subscribers and followers can mute / thread-mute, filtering all above AP IDs
|
||||||
|
notification_enabled_ap_ids =
|
||||||
|
potential_receiver_ap_ids
|
||||||
|
|> exclude_relationship_restricted_ap_ids(activity)
|
||||||
|
|> exclude_thread_muter_ap_ids(activity)
|
||||||
|
|
||||||
|
potential_receivers =
|
||||||
|
potential_receiver_ap_ids
|
||||||
|
|> Enum.uniq()
|
||||||
|
|> User.get_users_from_set(local_only)
|
||||||
|
|
||||||
|
notification_enabled_users =
|
||||||
|
Enum.filter(potential_receivers, fn u -> u.ap_id in notification_enabled_ap_ids end)
|
||||||
|
|
||||||
|
{notification_enabled_users, potential_receivers -- notification_enabled_users}
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_notified_from_activity(_, _local_only), do: []
|
def get_notified_from_activity(_, _local_only), do: {[], []}
|
||||||
|
|
||||||
|
@doc "Filters out AP IDs of users basing on their relationships with activity actor user"
|
||||||
|
def exclude_relationship_restricted_ap_ids([], _activity), do: []
|
||||||
|
|
||||||
|
def exclude_relationship_restricted_ap_ids(ap_ids, %Activity{} = activity) do
|
||||||
|
relationship_restricted_ap_ids =
|
||||||
|
activity
|
||||||
|
|> Activity.user_actor()
|
||||||
|
|> User.incoming_relationships_ungrouped_ap_ids([
|
||||||
|
:block,
|
||||||
|
:notification_mute
|
||||||
|
])
|
||||||
|
|
||||||
|
Enum.uniq(ap_ids) -- relationship_restricted_ap_ids
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "Filters out AP IDs of users who mute activity thread"
|
||||||
|
def exclude_thread_muter_ap_ids([], _activity), do: []
|
||||||
|
|
||||||
|
def exclude_thread_muter_ap_ids(ap_ids, %Activity{} = activity) do
|
||||||
|
thread_muter_ap_ids = ThreadMute.muter_ap_ids(activity.data["context"])
|
||||||
|
|
||||||
|
Enum.uniq(ap_ids) -- thread_muter_ap_ids
|
||||||
|
end
|
||||||
|
|
||||||
@spec skip?(Activity.t(), User.t()) :: boolean()
|
@spec skip?(Activity.t(), User.t()) :: boolean()
|
||||||
def skip?(activity, user) do
|
def skip?(%Activity{} = activity, %User{} = user) do
|
||||||
[
|
[
|
||||||
:self,
|
:self,
|
||||||
:followers,
|
:followers,
|
||||||
|
@ -335,18 +390,20 @@ def skip?(activity, user) do
|
||||||
:non_follows,
|
:non_follows,
|
||||||
:recently_followed
|
:recently_followed
|
||||||
]
|
]
|
||||||
|> Enum.any?(&skip?(&1, activity, user))
|
|> Enum.find(&skip?(&1, activity, user))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def skip?(_, _), do: false
|
||||||
|
|
||||||
@spec skip?(atom(), Activity.t(), User.t()) :: boolean()
|
@spec skip?(atom(), Activity.t(), User.t()) :: boolean()
|
||||||
def skip?(:self, activity, user) do
|
def skip?(:self, %Activity{} = activity, %User{} = user) do
|
||||||
activity.data["actor"] == user.ap_id
|
activity.data["actor"] == user.ap_id
|
||||||
end
|
end
|
||||||
|
|
||||||
def skip?(
|
def skip?(
|
||||||
:followers,
|
:followers,
|
||||||
activity,
|
%Activity{} = activity,
|
||||||
%{notification_settings: %{followers: false}} = user
|
%User{notification_settings: %{followers: false}} = user
|
||||||
) do
|
) do
|
||||||
actor = activity.data["actor"]
|
actor = activity.data["actor"]
|
||||||
follower = User.get_cached_by_ap_id(actor)
|
follower = User.get_cached_by_ap_id(actor)
|
||||||
|
@ -355,15 +412,19 @@ def skip?(
|
||||||
|
|
||||||
def skip?(
|
def skip?(
|
||||||
:non_followers,
|
:non_followers,
|
||||||
activity,
|
%Activity{} = activity,
|
||||||
%{notification_settings: %{non_followers: false}} = user
|
%User{notification_settings: %{non_followers: false}} = user
|
||||||
) do
|
) do
|
||||||
actor = activity.data["actor"]
|
actor = activity.data["actor"]
|
||||||
follower = User.get_cached_by_ap_id(actor)
|
follower = User.get_cached_by_ap_id(actor)
|
||||||
!User.following?(follower, user)
|
!User.following?(follower, user)
|
||||||
end
|
end
|
||||||
|
|
||||||
def skip?(:follows, activity, %{notification_settings: %{follows: false}} = user) do
|
def skip?(
|
||||||
|
:follows,
|
||||||
|
%Activity{} = activity,
|
||||||
|
%User{notification_settings: %{follows: false}} = user
|
||||||
|
) do
|
||||||
actor = activity.data["actor"]
|
actor = activity.data["actor"]
|
||||||
followed = User.get_cached_by_ap_id(actor)
|
followed = User.get_cached_by_ap_id(actor)
|
||||||
User.following?(user, followed)
|
User.following?(user, followed)
|
||||||
|
@ -371,15 +432,16 @@ def skip?(:follows, activity, %{notification_settings: %{follows: false}} = user
|
||||||
|
|
||||||
def skip?(
|
def skip?(
|
||||||
:non_follows,
|
:non_follows,
|
||||||
activity,
|
%Activity{} = activity,
|
||||||
%{notification_settings: %{non_follows: false}} = user
|
%User{notification_settings: %{non_follows: false}} = user
|
||||||
) do
|
) do
|
||||||
actor = activity.data["actor"]
|
actor = activity.data["actor"]
|
||||||
followed = User.get_cached_by_ap_id(actor)
|
followed = User.get_cached_by_ap_id(actor)
|
||||||
!User.following?(user, followed)
|
!User.following?(user, followed)
|
||||||
end
|
end
|
||||||
|
|
||||||
def skip?(:recently_followed, %{data: %{"type" => "Follow"}} = activity, user) do
|
# To do: consider defining recency in hours and checking FollowingRelationship with a single SQL
|
||||||
|
def skip?(:recently_followed, %Activity{data: %{"type" => "Follow"}} = activity, %User{} = user) do
|
||||||
actor = activity.data["actor"]
|
actor = activity.data["actor"]
|
||||||
|
|
||||||
Notification.for_user(user)
|
Notification.for_user(user)
|
||||||
|
|
|
@ -9,7 +9,8 @@ defmodule Pleroma.ThreadMute do
|
||||||
alias Pleroma.ThreadMute
|
alias Pleroma.ThreadMute
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
require Ecto.Query
|
import Ecto.Changeset
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
schema "thread_mutes" do
|
schema "thread_mutes" do
|
||||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||||
|
@ -18,19 +19,44 @@ defmodule Pleroma.ThreadMute do
|
||||||
|
|
||||||
def changeset(mute, params \\ %{}) do
|
def changeset(mute, params \\ %{}) do
|
||||||
mute
|
mute
|
||||||
|> Ecto.Changeset.cast(params, [:user_id, :context])
|
|> cast(params, [:user_id, :context])
|
||||||
|> Ecto.Changeset.foreign_key_constraint(:user_id)
|
|> foreign_key_constraint(:user_id)
|
||||||
|> Ecto.Changeset.unique_constraint(:user_id, name: :unique_index)
|
|> unique_constraint(:user_id, name: :unique_index)
|
||||||
end
|
end
|
||||||
|
|
||||||
def query(user_id, context) do
|
def query(user_id, context) do
|
||||||
{:ok, user_id} = FlakeId.Ecto.CompatType.dump(user_id)
|
user_binary_id = User.binary_id(user_id)
|
||||||
|
|
||||||
ThreadMute
|
ThreadMute
|
||||||
|> Ecto.Query.where(user_id: ^user_id)
|
|> where(user_id: ^user_binary_id)
|
||||||
|> Ecto.Query.where(context: ^context)
|
|> where(context: ^context)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def muters_query(context) do
|
||||||
|
ThreadMute
|
||||||
|
|> join(:inner, [tm], u in assoc(tm, :user))
|
||||||
|
|> where([tm], tm.context == ^context)
|
||||||
|
|> select([tm, u], u.ap_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def muter_ap_ids(context, ap_ids \\ nil)
|
||||||
|
|
||||||
|
# Note: applies to fake activities (ActivityPub.Utils.get_notified_from_object/1 etc.)
|
||||||
|
def muter_ap_ids(context, _ap_ids) when is_nil(context), do: []
|
||||||
|
|
||||||
|
def muter_ap_ids(context, ap_ids) do
|
||||||
|
context
|
||||||
|
|> muters_query()
|
||||||
|
|> maybe_filter_on_ap_id(ap_ids)
|
||||||
|
|> Repo.all()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_filter_on_ap_id(query, ap_ids) when is_list(ap_ids) do
|
||||||
|
where(query, [tm, u], u.ap_id in ^ap_ids)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_filter_on_ap_id(query, _ap_ids), do: query
|
||||||
|
|
||||||
def add_mute(user_id, context) do
|
def add_mute(user_id, context) do
|
||||||
%ThreadMute{}
|
%ThreadMute{}
|
||||||
|> changeset(%{user_id: user_id, context: context})
|
|> changeset(%{user_id: user_id, context: context})
|
||||||
|
@ -42,8 +68,8 @@ def remove_mute(user_id, context) do
|
||||||
|> Repo.delete_all()
|
|> Repo.delete_all()
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_muted(user_id, context) do
|
def exists?(user_id, context) do
|
||||||
query(user_id, context)
|
query(user_id, context)
|
||||||
|> Repo.all()
|
|> Repo.exists?()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -150,22 +150,26 @@ defmodule Pleroma.User do
|
||||||
{outgoing_relation, outgoing_relation_target},
|
{outgoing_relation, outgoing_relation_target},
|
||||||
{incoming_relation, incoming_relation_source}
|
{incoming_relation, incoming_relation_source}
|
||||||
]} <- @user_relationships_config do
|
]} <- @user_relationships_config do
|
||||||
# Definitions of `has_many :blocker_blocks`, `has_many :muter_mutes` etc.
|
# Definitions of `has_many` relations: :blocker_blocks, :muter_mutes, :reblog_muter_mutes,
|
||||||
|
# :notification_muter_mutes, :subscribee_subscriptions
|
||||||
has_many(outgoing_relation, UserRelationship,
|
has_many(outgoing_relation, UserRelationship,
|
||||||
foreign_key: :source_id,
|
foreign_key: :source_id,
|
||||||
where: [relationship_type: relationship_type]
|
where: [relationship_type: relationship_type]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Definitions of `has_many :blockee_blocks`, `has_many :mutee_mutes` etc.
|
# Definitions of `has_many` relations: :blockee_blocks, :mutee_mutes, :reblog_mutee_mutes,
|
||||||
|
# :notification_mutee_mutes, :subscriber_subscriptions
|
||||||
has_many(incoming_relation, UserRelationship,
|
has_many(incoming_relation, UserRelationship,
|
||||||
foreign_key: :target_id,
|
foreign_key: :target_id,
|
||||||
where: [relationship_type: relationship_type]
|
where: [relationship_type: relationship_type]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Definitions of `has_many :blocked_users`, `has_many :muted_users` etc.
|
# Definitions of `has_many` relations: :blocked_users, :muted_users, :reblog_muted_users,
|
||||||
|
# :notification_muted_users, :subscriber_users
|
||||||
has_many(outgoing_relation_target, through: [outgoing_relation, :target])
|
has_many(outgoing_relation_target, through: [outgoing_relation, :target])
|
||||||
|
|
||||||
# Definitions of `has_many :blocker_users`, `has_many :muter_users` etc.
|
# Definitions of `has_many` relations: :blocker_users, :muter_users, :reblog_muter_users,
|
||||||
|
# :notification_muter_users, :subscribee_users
|
||||||
has_many(incoming_relation_source, through: [incoming_relation, :source])
|
has_many(incoming_relation_source, through: [incoming_relation, :source])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -185,7 +189,9 @@ defmodule Pleroma.User do
|
||||||
|
|
||||||
for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
|
for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
|
||||||
@user_relationships_config do
|
@user_relationships_config do
|
||||||
# Definitions of `blocked_users_relation/1`, `muted_users_relation/1`, etc.
|
# `def blocked_users_relation/2`, `def muted_users_relation/2`,
|
||||||
|
# `def reblog_muted_users_relation/2`, `def notification_muted_users/2`,
|
||||||
|
# `def subscriber_users/2`
|
||||||
def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
|
def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
|
||||||
target_users_query = assoc(user, unquote(outgoing_relation_target))
|
target_users_query = assoc(user, unquote(outgoing_relation_target))
|
||||||
|
|
||||||
|
@ -196,7 +202,8 @@ def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Definitions of `blocked_users/1`, `muted_users/1`, etc.
|
# `def blocked_users/2`, `def muted_users/2`, `def reblog_muted_users/2`,
|
||||||
|
# `def notification_muted_users/2`, `def subscriber_users/2`
|
||||||
def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
|
def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
|
||||||
__MODULE__
|
__MODULE__
|
||||||
|> apply(unquote(:"#{outgoing_relation_target}_relation"), [
|
|> apply(unquote(:"#{outgoing_relation_target}_relation"), [
|
||||||
|
@ -206,7 +213,8 @@ def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
# Definitions of `blocked_users_ap_ids/1`, `muted_users_ap_ids/1`, etc.
|
# `def blocked_users_ap_ids/2`, `def muted_users_ap_ids/2`, `def reblog_muted_users_ap_ids/2`,
|
||||||
|
# `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`
|
||||||
def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
|
def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
|
||||||
__MODULE__
|
__MODULE__
|
||||||
|> apply(unquote(:"#{outgoing_relation_target}_relation"), [
|
|> apply(unquote(:"#{outgoing_relation_target}_relation"), [
|
||||||
|
@ -218,6 +226,24 @@ def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Dumps Flake Id to SQL-compatible format (16-byte UUID).
|
||||||
|
E.g. "9pQtDGXuq4p3VlcJEm" -> <<0, 0, 1, 110, 179, 218, 42, 92, 213, 41, 44, 227, 95, 213, 0, 0>>
|
||||||
|
"""
|
||||||
|
def binary_id(source_id) when is_binary(source_id) do
|
||||||
|
with {:ok, dumped_id} <- FlakeId.Ecto.CompatType.dump(source_id) do
|
||||||
|
dumped_id
|
||||||
|
else
|
||||||
|
_ -> source_id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def binary_id(source_ids) when is_list(source_ids) do
|
||||||
|
Enum.map(source_ids, &binary_id/1)
|
||||||
|
end
|
||||||
|
|
||||||
|
def binary_id(%User{} = user), do: binary_id(user.id)
|
||||||
|
|
||||||
@doc "Returns status account"
|
@doc "Returns status account"
|
||||||
@spec account_status(User.t()) :: account_status()
|
@spec account_status(User.t()) :: account_status()
|
||||||
def account_status(%User{deactivated: true}), do: :deactivated
|
def account_status(%User{deactivated: true}), do: :deactivated
|
||||||
|
@ -292,24 +318,6 @@ def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
|
||||||
def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
|
def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
|
||||||
def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
|
def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
|
||||||
|
|
||||||
def follow_state(%User{} = user, %User{} = target) do
|
|
||||||
case Utils.fetch_latest_follow(user, target) do
|
|
||||||
%{data: %{"state" => state}} -> state
|
|
||||||
# Ideally this would be nil, but then Cachex does not commit the value
|
|
||||||
_ -> false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_cached_follow_state(user, target) do
|
|
||||||
key = "follow_state:#{user.ap_id}|#{target.ap_id}"
|
|
||||||
Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
|
|
||||||
def set_follow_state_cache(user_ap_id, target_ap_id, state) do
|
|
||||||
Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
|
@spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
|
||||||
def restrict_deactivated(query) do
|
def restrict_deactivated(query) do
|
||||||
from(u in query, where: u.deactivated != ^true)
|
from(u in query, where: u.deactivated != ^true)
|
||||||
|
@ -428,9 +436,55 @@ def update_changeset(struct, params \\ %{}) do
|
||||||
|> validate_format(:nickname, local_nickname_regex())
|
|> validate_format(:nickname, local_nickname_regex())
|
||||||
|> validate_length(:bio, max: bio_limit)
|
|> validate_length(:bio, max: bio_limit)
|
||||||
|> validate_length(:name, min: 1, max: name_limit)
|
|> validate_length(:name, min: 1, max: name_limit)
|
||||||
|
|> put_fields()
|
||||||
|
|> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
|
||||||
|
|> put_change_if_present(:avatar, &put_upload(&1, :avatar))
|
||||||
|
|> put_change_if_present(:banner, &put_upload(&1, :banner))
|
||||||
|
|> put_change_if_present(:background, &put_upload(&1, :background))
|
||||||
|
|> put_change_if_present(
|
||||||
|
:pleroma_settings_store,
|
||||||
|
&{:ok, Map.merge(struct.pleroma_settings_store, &1)}
|
||||||
|
)
|
||||||
|> validate_fields(false)
|
|> validate_fields(false)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp put_fields(changeset) do
|
||||||
|
if raw_fields = get_change(changeset, :raw_fields) do
|
||||||
|
raw_fields =
|
||||||
|
raw_fields
|
||||||
|
|> Enum.filter(fn %{"name" => n} -> n != "" end)
|
||||||
|
|
||||||
|
fields =
|
||||||
|
raw_fields
|
||||||
|
|> Enum.map(fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end)
|
||||||
|
|
||||||
|
changeset
|
||||||
|
|> put_change(:raw_fields, raw_fields)
|
||||||
|
|> put_change(:fields, fields)
|
||||||
|
else
|
||||||
|
changeset
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp put_change_if_present(changeset, map_field, value_function) do
|
||||||
|
if value = get_change(changeset, map_field) do
|
||||||
|
with {:ok, new_value} <- value_function.(value) do
|
||||||
|
put_change(changeset, map_field, new_value)
|
||||||
|
else
|
||||||
|
_ -> changeset
|
||||||
|
end
|
||||||
|
else
|
||||||
|
changeset
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp put_upload(value, type) do
|
||||||
|
with %Plug.Upload{} <- value,
|
||||||
|
{:ok, object} <- ActivityPub.upload(value, type: type) do
|
||||||
|
{:ok, object.data}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
|
def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
|
||||||
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
|
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
|
||||||
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
|
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
|
||||||
|
@ -474,6 +528,27 @@ def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
|
||||||
|> validate_fields(remote?)
|
|> validate_fields(remote?)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_as_admin_changeset(struct, params) do
|
||||||
|
struct
|
||||||
|
|> update_changeset(params)
|
||||||
|
|> cast(params, [:email])
|
||||||
|
|> delete_change(:also_known_as)
|
||||||
|
|> unique_constraint(:email)
|
||||||
|
|> validate_format(:email, @email_regex)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec update_as_admin(%User{}, map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
|
||||||
|
def update_as_admin(user, params) do
|
||||||
|
params = Map.put(params, "password_confirmation", params["password"])
|
||||||
|
changeset = update_as_admin_changeset(user, params)
|
||||||
|
|
||||||
|
if params["password"] do
|
||||||
|
reset_password(user, changeset, params)
|
||||||
|
else
|
||||||
|
User.update_and_set_cache(changeset)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def password_update_changeset(struct, params) do
|
def password_update_changeset(struct, params) do
|
||||||
struct
|
struct
|
||||||
|> cast(params, [:password, :password_confirmation])
|
|> cast(params, [:password, :password_confirmation])
|
||||||
|
@ -484,10 +559,14 @@ def password_update_changeset(struct, params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
|
@spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
|
||||||
def reset_password(%User{id: user_id} = user, data) do
|
def reset_password(%User{} = user, params) do
|
||||||
|
reset_password(user, user, params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def reset_password(%User{id: user_id} = user, struct, params) do
|
||||||
multi =
|
multi =
|
||||||
Multi.new()
|
Multi.new()
|
||||||
|> Multi.update(:user, password_update_changeset(user, data))
|
|> Multi.update(:user, password_update_changeset(struct, params))
|
||||||
|> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
|
|> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
|
||||||
|> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
|
|> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
|
||||||
|
|
||||||
|
@ -692,7 +771,14 @@ def unfollow(%User{} = follower, %User{} = followed) do
|
||||||
|
|
||||||
def get_follow_state(%User{} = follower, %User{} = following) do
|
def get_follow_state(%User{} = follower, %User{} = following) do
|
||||||
following_relationship = FollowingRelationship.get(follower, following)
|
following_relationship = FollowingRelationship.get(follower, following)
|
||||||
|
get_follow_state(follower, following, following_relationship)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_follow_state(
|
||||||
|
%User{} = follower,
|
||||||
|
%User{} = following,
|
||||||
|
following_relationship
|
||||||
|
) do
|
||||||
case {following_relationship, following.local} do
|
case {following_relationship, following.local} do
|
||||||
{nil, false} ->
|
{nil, false} ->
|
||||||
case Utils.fetch_latest_follow(follower, following) do
|
case Utils.fetch_latest_follow(follower, following) do
|
||||||
|
@ -1225,13 +1311,15 @@ def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns map of outgoing (blocked, muted etc.) relations' user AP IDs by relation type.
|
Returns map of outgoing (blocked, muted etc.) relationships' user AP IDs by relation type.
|
||||||
E.g. `outgoing_relations_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
|
E.g. `outgoing_relationships_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
|
||||||
"""
|
"""
|
||||||
@spec outgoing_relations_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
|
@spec outgoing_relationships_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
|
||||||
def outgoing_relations_ap_ids(_, []), do: %{}
|
def outgoing_relationships_ap_ids(_user, []), do: %{}
|
||||||
|
|
||||||
def outgoing_relations_ap_ids(%User{} = user, relationship_types)
|
def outgoing_relationships_ap_ids(nil, _relationship_types), do: %{}
|
||||||
|
|
||||||
|
def outgoing_relationships_ap_ids(%User{} = user, relationship_types)
|
||||||
when is_list(relationship_types) do
|
when is_list(relationship_types) do
|
||||||
db_result =
|
db_result =
|
||||||
user
|
user
|
||||||
|
@ -1250,6 +1338,30 @@ def outgoing_relations_ap_ids(%User{} = user, relationship_types)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def incoming_relationships_ungrouped_ap_ids(user, relationship_types, ap_ids \\ nil)
|
||||||
|
|
||||||
|
def incoming_relationships_ungrouped_ap_ids(_user, [], _ap_ids), do: []
|
||||||
|
|
||||||
|
def incoming_relationships_ungrouped_ap_ids(nil, _relationship_types, _ap_ids), do: []
|
||||||
|
|
||||||
|
def incoming_relationships_ungrouped_ap_ids(%User{} = user, relationship_types, ap_ids)
|
||||||
|
when is_list(relationship_types) do
|
||||||
|
user
|
||||||
|
|> assoc(:incoming_relationships)
|
||||||
|
|> join(:inner, [user_rel], u in assoc(user_rel, :source))
|
||||||
|
|> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
|
||||||
|
|> maybe_filter_on_ap_id(ap_ids)
|
||||||
|
|> select([user_rel, u], u.ap_id)
|
||||||
|
|> distinct(true)
|
||||||
|
|> Repo.all()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_filter_on_ap_id(query, ap_ids) when is_list(ap_ids) do
|
||||||
|
where(query, [user_rel, u], u.ap_id in ^ap_ids)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_filter_on_ap_id(query, _ap_ids), do: query
|
||||||
|
|
||||||
def deactivate_async(user, status \\ true) do
|
def deactivate_async(user, status \\ true) do
|
||||||
BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
|
BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
|
||||||
end
|
end
|
||||||
|
@ -1660,8 +1772,12 @@ def all_superusers do
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def muting_reblogs?(%User{} = user, %User{} = target) do
|
||||||
|
UserRelationship.reblog_mute_exists?(user, target)
|
||||||
|
end
|
||||||
|
|
||||||
def showing_reblogs?(%User{} = user, %User{} = target) do
|
def showing_reblogs?(%User{} = user, %User{} = target) do
|
||||||
not UserRelationship.reblog_mute_exists?(user, target)
|
not muting_reblogs?(user, target)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -1867,6 +1983,17 @@ def fields(%{fields: nil}), do: []
|
||||||
|
|
||||||
def fields(%{fields: fields}), do: fields
|
def fields(%{fields: fields}), do: fields
|
||||||
|
|
||||||
|
def sanitized_fields(%User{} = user) do
|
||||||
|
user
|
||||||
|
|> User.fields()
|
||||||
|
|> Enum.map(fn %{"name" => name, "value" => value} ->
|
||||||
|
%{
|
||||||
|
"name" => name,
|
||||||
|
"value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
def validate_fields(changeset, remote? \\ false) do
|
def validate_fields(changeset, remote? \\ false) do
|
||||||
limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
|
limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
|
||||||
limit = Pleroma.Config.get([:instance, limit_name], 0)
|
limit = Pleroma.Config.get([:instance, limit_name], 0)
|
||||||
|
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.UserRelationship do
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
|
alias Pleroma.FollowingRelationship
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.UserRelationship
|
alias Pleroma.UserRelationship
|
||||||
|
@ -21,19 +22,26 @@ defmodule Pleroma.UserRelationship do
|
||||||
end
|
end
|
||||||
|
|
||||||
for relationship_type <- Keyword.keys(UserRelationshipTypeEnum.__enum_map__()) do
|
for relationship_type <- Keyword.keys(UserRelationshipTypeEnum.__enum_map__()) do
|
||||||
# Definitions of `create_block/2`, `create_mute/2` etc.
|
# `def create_block/2`, `def create_mute/2`, `def create_reblog_mute/2`,
|
||||||
|
# `def create_notification_mute/2`, `def create_inverse_subscription/2`
|
||||||
def unquote(:"create_#{relationship_type}")(source, target),
|
def unquote(:"create_#{relationship_type}")(source, target),
|
||||||
do: create(unquote(relationship_type), source, target)
|
do: create(unquote(relationship_type), source, target)
|
||||||
|
|
||||||
# Definitions of `delete_block/2`, `delete_mute/2` etc.
|
# `def delete_block/2`, `def delete_mute/2`, `def delete_reblog_mute/2`,
|
||||||
|
# `def delete_notification_mute/2`, `def delete_inverse_subscription/2`
|
||||||
def unquote(:"delete_#{relationship_type}")(source, target),
|
def unquote(:"delete_#{relationship_type}")(source, target),
|
||||||
do: delete(unquote(relationship_type), source, target)
|
do: delete(unquote(relationship_type), source, target)
|
||||||
|
|
||||||
# Definitions of `block_exists?/2`, `mute_exists?/2` etc.
|
# `def block_exists?/2`, `def mute_exists?/2`, `def reblog_mute_exists?/2`,
|
||||||
|
# `def notification_mute_exists?/2`, `def inverse_subscription_exists?/2`
|
||||||
def unquote(:"#{relationship_type}_exists?")(source, target),
|
def unquote(:"#{relationship_type}_exists?")(source, target),
|
||||||
do: exists?(unquote(relationship_type), source, target)
|
do: exists?(unquote(relationship_type), source, target)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def user_relationship_types, do: Keyword.keys(user_relationship_mappings())
|
||||||
|
|
||||||
|
def user_relationship_mappings, do: UserRelationshipTypeEnum.__enum_map__()
|
||||||
|
|
||||||
def changeset(%UserRelationship{} = user_relationship, params \\ %{}) do
|
def changeset(%UserRelationship{} = user_relationship, params \\ %{}) do
|
||||||
user_relationship
|
user_relationship
|
||||||
|> cast(params, [:relationship_type, :source_id, :target_id])
|
|> cast(params, [:relationship_type, :source_id, :target_id])
|
||||||
|
@ -72,6 +80,73 @@ def delete(relationship_type, %User{} = source, %User{} = target) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def dictionary(
|
||||||
|
source_users,
|
||||||
|
target_users,
|
||||||
|
source_to_target_rel_types \\ nil,
|
||||||
|
target_to_source_rel_types \\ nil
|
||||||
|
)
|
||||||
|
when is_list(source_users) and is_list(target_users) do
|
||||||
|
source_user_ids = User.binary_id(source_users)
|
||||||
|
target_user_ids = User.binary_id(target_users)
|
||||||
|
|
||||||
|
get_rel_type_codes = fn rel_type -> user_relationship_mappings()[rel_type] end
|
||||||
|
|
||||||
|
source_to_target_rel_types =
|
||||||
|
Enum.map(source_to_target_rel_types || user_relationship_types(), &get_rel_type_codes.(&1))
|
||||||
|
|
||||||
|
target_to_source_rel_types =
|
||||||
|
Enum.map(target_to_source_rel_types || user_relationship_types(), &get_rel_type_codes.(&1))
|
||||||
|
|
||||||
|
__MODULE__
|
||||||
|
|> where(
|
||||||
|
fragment(
|
||||||
|
"(source_id = ANY(?) AND target_id = ANY(?) AND relationship_type = ANY(?)) OR \
|
||||||
|
(source_id = ANY(?) AND target_id = ANY(?) AND relationship_type = ANY(?))",
|
||||||
|
^source_user_ids,
|
||||||
|
^target_user_ids,
|
||||||
|
^source_to_target_rel_types,
|
||||||
|
^target_user_ids,
|
||||||
|
^source_user_ids,
|
||||||
|
^target_to_source_rel_types
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|> select([ur], [ur.relationship_type, ur.source_id, ur.target_id])
|
||||||
|
|> Repo.all()
|
||||||
|
end
|
||||||
|
|
||||||
|
def exists?(dictionary, rel_type, source, target, func) do
|
||||||
|
cond do
|
||||||
|
is_nil(source) or is_nil(target) ->
|
||||||
|
false
|
||||||
|
|
||||||
|
dictionary ->
|
||||||
|
[rel_type, source.id, target.id] in dictionary
|
||||||
|
|
||||||
|
true ->
|
||||||
|
func.(source, target)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc ":relationships option for StatusView / AccountView / NotificationView"
|
||||||
|
def view_relationships_option(nil = _reading_user, _actors) do
|
||||||
|
%{user_relationships: [], following_relationships: []}
|
||||||
|
end
|
||||||
|
|
||||||
|
def view_relationships_option(%User{} = reading_user, actors) do
|
||||||
|
user_relationships =
|
||||||
|
UserRelationship.dictionary(
|
||||||
|
[reading_user],
|
||||||
|
actors,
|
||||||
|
[:block, :mute, :notification_mute, :reblog_mute],
|
||||||
|
[:block, :inverse_subscription]
|
||||||
|
)
|
||||||
|
|
||||||
|
following_relationships = FollowingRelationship.all_between_user_sets([reading_user], actors)
|
||||||
|
|
||||||
|
%{user_relationships: user_relationships, following_relationships: following_relationships}
|
||||||
|
end
|
||||||
|
|
||||||
defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do
|
defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do
|
||||||
changeset
|
changeset
|
||||||
|> validate_change(:target_id, fn _, target_id ->
|
|> validate_change(:target_id, fn _, target_id ->
|
||||||
|
|
|
@ -503,8 +503,7 @@ def follow(follower, followed, activity_id \\ nil, local \\ true) do
|
||||||
defp do_follow(follower, followed, activity_id, local) do
|
defp do_follow(follower, followed, activity_id, local) do
|
||||||
with data <- make_follow_data(follower, followed, activity_id),
|
with data <- make_follow_data(follower, followed, activity_id),
|
||||||
{:ok, activity} <- insert(data, local),
|
{:ok, activity} <- insert(data, local),
|
||||||
:ok <- maybe_federate(activity),
|
:ok <- maybe_federate(activity) do
|
||||||
_ <- User.set_follow_state_cache(follower.ap_id, followed.ap_id, activity.data["state"]) do
|
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
{:error, error} -> Repo.rollback(error)
|
{:error, error} -> Repo.rollback(error)
|
||||||
|
@ -584,6 +583,16 @@ defp do_delete(%Object{data: %{"id" => id, "actor" => actor}} = object, options)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp do_delete(%Object{data: %{"type" => "Tombstone", "id" => ap_id}}, _) do
|
||||||
|
activity =
|
||||||
|
ap_id
|
||||||
|
|> Activity.Queries.by_object_id()
|
||||||
|
|> Activity.Queries.by_type("Delete")
|
||||||
|
|> Repo.one()
|
||||||
|
|
||||||
|
{:ok, activity}
|
||||||
|
end
|
||||||
|
|
||||||
@spec block(User.t(), User.t(), String.t() | nil, boolean()) ::
|
@spec block(User.t(), User.t(), String.t() | nil, boolean()) ::
|
||||||
{:ok, Activity.t()} | {:error, any()}
|
{:ok, Activity.t()} | {:error, any()}
|
||||||
def block(blocker, blocked, activity_id \\ nil, local \\ true) do
|
def block(blocker, blocked, activity_id \\ nil, local \\ true) do
|
||||||
|
@ -1230,17 +1239,17 @@ defp maybe_order(query, _), do: query
|
||||||
|
|
||||||
defp fetch_activities_query_ap_ids_ops(opts) do
|
defp fetch_activities_query_ap_ids_ops(opts) do
|
||||||
source_user = opts["muting_user"]
|
source_user = opts["muting_user"]
|
||||||
ap_id_relations = if source_user, do: [:mute, :reblog_mute], else: []
|
ap_id_relationships = if source_user, do: [:mute, :reblog_mute], else: []
|
||||||
|
|
||||||
ap_id_relations =
|
ap_id_relationships =
|
||||||
ap_id_relations ++
|
ap_id_relationships ++
|
||||||
if opts["blocking_user"] && opts["blocking_user"] == source_user do
|
if opts["blocking_user"] && opts["blocking_user"] == source_user do
|
||||||
[:block]
|
[:block]
|
||||||
else
|
else
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
|
|
||||||
preloaded_ap_ids = User.outgoing_relations_ap_ids(source_user, ap_id_relations)
|
preloaded_ap_ids = User.outgoing_relationships_ap_ids(source_user, ap_id_relationships)
|
||||||
|
|
||||||
restrict_blocked_opts = Map.merge(%{"blocked_users_ap_ids" => preloaded_ap_ids[:block]}, opts)
|
restrict_blocked_opts = Map.merge(%{"blocked_users_ap_ids" => preloaded_ap_ids[:block]}, opts)
|
||||||
restrict_muted_opts = Map.merge(%{"muted_users_ap_ids" => preloaded_ap_ids[:mute]}, opts)
|
restrict_muted_opts = Map.merge(%{"muted_users_ap_ids" => preloaded_ap_ids[:mute]}, opts)
|
||||||
|
|
|
@ -229,7 +229,8 @@ def fix_url(%{"url" => url} = object) when is_map(url) do
|
||||||
Map.put(object, "url", url["href"])
|
Map.put(object, "url", url["href"])
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_url(%{"type" => "Video", "url" => url} = object) when is_list(url) do
|
def fix_url(%{"type" => object_type, "url" => url} = object)
|
||||||
|
when object_type in ["Video", "Audio"] and is_list(url) do
|
||||||
first_element = Enum.at(url, 0)
|
first_element = Enum.at(url, 0)
|
||||||
|
|
||||||
link_element = Enum.find(url, fn x -> is_map(x) and x["mimeType"] == "text/html" end)
|
link_element = Enum.find(url, fn x -> is_map(x) and x["mimeType"] == "text/html" end)
|
||||||
|
@ -398,7 +399,7 @@ def handle_incoming(
|
||||||
%{"type" => "Create", "object" => %{"type" => objtype} = object} = data,
|
%{"type" => "Create", "object" => %{"type" => objtype} = object} = data,
|
||||||
options
|
options
|
||||||
)
|
)
|
||||||
when objtype in ["Article", "Event", "Note", "Video", "Page", "Question", "Answer"] do
|
when objtype in ["Article", "Event", "Note", "Video", "Page", "Question", "Answer", "Audio"] do
|
||||||
actor = Containment.get_actor(data)
|
actor = Containment.get_actor(data)
|
||||||
|
|
||||||
data =
|
data =
|
||||||
|
@ -1108,13 +1109,11 @@ def add_hashtags(object) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_mention_tags(object) do
|
def add_mention_tags(object) do
|
||||||
mentions =
|
{enabled_receivers, disabled_receivers} = Utils.get_notified_from_object(object)
|
||||||
object
|
potential_receivers = enabled_receivers ++ disabled_receivers
|
||||||
|> Utils.get_notified_from_object()
|
mentions = Enum.map(potential_receivers, &build_mention_tag/1)
|
||||||
|> Enum.map(&build_mention_tag/1)
|
|
||||||
|
|
||||||
tags = object["tag"] || []
|
tags = object["tag"] || []
|
||||||
|
|
||||||
Map.put(object, "tag", tags ++ mentions)
|
Map.put(object, "tag", tags ++ mentions)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -440,22 +440,19 @@ def update_follow_state_for_all(
|
||||||
|> update(set: [data: fragment("jsonb_set(data, '{state}', ?)", ^state)])
|
|> update(set: [data: fragment("jsonb_set(data, '{state}', ?)", ^state)])
|
||||||
|> Repo.update_all([])
|
|> Repo.update_all([])
|
||||||
|
|
||||||
User.set_follow_state_cache(actor, object, state)
|
|
||||||
|
|
||||||
activity = Activity.get_by_id(activity.id)
|
activity = Activity.get_by_id(activity.id)
|
||||||
|
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_follow_state(
|
def update_follow_state(
|
||||||
%Activity{data: %{"actor" => actor, "object" => object}} = activity,
|
%Activity{} = activity,
|
||||||
state
|
state
|
||||||
) do
|
) do
|
||||||
new_data = Map.put(activity.data, "state", state)
|
new_data = Map.put(activity.data, "state", state)
|
||||||
changeset = Changeset.change(activity, data: new_data)
|
changeset = Changeset.change(activity, data: new_data)
|
||||||
|
|
||||||
with {:ok, activity} <- Repo.update(changeset) do
|
with {:ok, activity} <- Repo.update(changeset) do
|
||||||
User.set_follow_state_cache(actor, object, state)
|
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -38,7 +38,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||||
plug(
|
plug(
|
||||||
OAuthScopesPlug,
|
OAuthScopesPlug,
|
||||||
%{scopes: ["read:accounts"], admin: true}
|
%{scopes: ["read:accounts"], admin: true}
|
||||||
when action in [:list_users, :user_show, :right_get]
|
when action in [:list_users, :user_show, :right_get, :show_user_credentials]
|
||||||
)
|
)
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
|
@ -54,7 +54,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||||
:tag_users,
|
:tag_users,
|
||||||
:untag_users,
|
:untag_users,
|
||||||
:right_add,
|
:right_add,
|
||||||
:right_delete
|
:right_delete,
|
||||||
|
:update_user_credentials
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -658,6 +659,52 @@ def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nic
|
||||||
json_response(conn, :no_content, "")
|
json_response(conn, :no_content, "")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc "Show a given user's credentials"
|
||||||
|
def show_user_credentials(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
|
||||||
|
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
|
||||||
|
conn
|
||||||
|
|> put_view(AccountView)
|
||||||
|
|> render("credentials.json", %{user: user, for: admin})
|
||||||
|
else
|
||||||
|
_ -> {:error, :not_found}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "Updates a given user"
|
||||||
|
def update_user_credentials(
|
||||||
|
%{assigns: %{user: admin}} = conn,
|
||||||
|
%{"nickname" => nickname} = params
|
||||||
|
) do
|
||||||
|
with {_, user} <- {:user, User.get_cached_by_nickname(nickname)},
|
||||||
|
{:ok, _user} <-
|
||||||
|
User.update_as_admin(user, params) do
|
||||||
|
ModerationLog.insert_log(%{
|
||||||
|
actor: admin,
|
||||||
|
subject: [user],
|
||||||
|
action: "updated_users"
|
||||||
|
})
|
||||||
|
|
||||||
|
if params["password"] do
|
||||||
|
User.force_password_reset_async(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
ModerationLog.insert_log(%{
|
||||||
|
actor: admin,
|
||||||
|
subject: [user],
|
||||||
|
action: "force_password_reset"
|
||||||
|
})
|
||||||
|
|
||||||
|
json(conn, %{status: "success"})
|
||||||
|
else
|
||||||
|
{:error, changeset} ->
|
||||||
|
{_, {error, _}} = Enum.at(changeset.errors, 0)
|
||||||
|
json(conn, %{error: "New password #{error}."})
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
json(conn, %{error: "Unable to change password."})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def list_reports(conn, params) do
|
def list_reports(conn, params) do
|
||||||
{page, page_size} = page_params(params)
|
{page, page_size} = page_params(params)
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,43 @@ def render("index.json", %{users: users}) do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render("credentials.json", %{user: user, for: for_user}) do
|
||||||
|
user = User.sanitize_html(user, User.html_filter_policy(for_user))
|
||||||
|
avatar = User.avatar_url(user) |> MediaProxy.url()
|
||||||
|
banner = User.banner_url(user) |> MediaProxy.url()
|
||||||
|
background = image_url(user.background) |> MediaProxy.url()
|
||||||
|
|
||||||
|
user
|
||||||
|
|> Map.take([
|
||||||
|
:id,
|
||||||
|
:bio,
|
||||||
|
:email,
|
||||||
|
:fields,
|
||||||
|
:name,
|
||||||
|
:nickname,
|
||||||
|
:locked,
|
||||||
|
:no_rich_text,
|
||||||
|
:default_scope,
|
||||||
|
:hide_follows,
|
||||||
|
:hide_followers_count,
|
||||||
|
:hide_follows_count,
|
||||||
|
:hide_followers,
|
||||||
|
:hide_favorites,
|
||||||
|
:allow_following_move,
|
||||||
|
:show_role,
|
||||||
|
:skip_thread_containment,
|
||||||
|
:pleroma_settings_store,
|
||||||
|
:raw_fields,
|
||||||
|
:discoverable,
|
||||||
|
:actor_type
|
||||||
|
])
|
||||||
|
|> Map.merge(%{
|
||||||
|
"avatar" => avatar,
|
||||||
|
"banner" => banner,
|
||||||
|
"background" => background
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
def render("show.json", %{user: user}) do
|
def render("show.json", %{user: user}) do
|
||||||
avatar = User.avatar_url(user) |> MediaProxy.url()
|
avatar = User.avatar_url(user) |> MediaProxy.url()
|
||||||
display_name = Pleroma.HTML.strip_tags(user.name || user.nickname)
|
display_name = Pleroma.HTML.strip_tags(user.name || user.nickname)
|
||||||
|
@ -104,4 +141,7 @@ defp parse_error(errors) do
|
||||||
""
|
""
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
|
||||||
|
defp image_url(_), do: nil
|
||||||
end
|
end
|
||||||
|
|
|
@ -358,7 +358,7 @@ def remove_mute(user, activity) do
|
||||||
def thread_muted?(%{id: nil} = _user, _activity), do: false
|
def thread_muted?(%{id: nil} = _user, _activity), do: false
|
||||||
|
|
||||||
def thread_muted?(user, activity) do
|
def thread_muted?(user, activity) do
|
||||||
ThreadMute.check_muted(user.id, activity.data["context"]) != []
|
ThreadMute.exists?(user.id, activity.data["context"])
|
||||||
end
|
end
|
||||||
|
|
||||||
def report(user, %{"account_id" => account_id} = data) do
|
def report(user, %{"account_id" => account_id} = data) do
|
||||||
|
|
|
@ -34,7 +34,12 @@ defp param_to_integer(val, default) when is_binary(val) do
|
||||||
|
|
||||||
defp param_to_integer(_, default), do: default
|
defp param_to_integer(_, default), do: default
|
||||||
|
|
||||||
def add_link_headers(conn, activities, extra_params \\ %{}) do
|
def add_link_headers(conn, activities, extra_params \\ %{})
|
||||||
|
|
||||||
|
def add_link_headers(%{assigns: %{skip_link_headers: true}} = conn, _activities, _extra_params),
|
||||||
|
do: conn
|
||||||
|
|
||||||
|
def add_link_headers(conn, activities, extra_params) do
|
||||||
case List.last(activities) do
|
case List.last(activities) do
|
||||||
%{id: max_id} ->
|
%{id: max_id} ->
|
||||||
params =
|
params =
|
||||||
|
|
|
@ -8,7 +8,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
||||||
import Pleroma.Web.ControllerHelper,
|
import Pleroma.Web.ControllerHelper,
|
||||||
only: [add_link_headers: 2, truthy_param?: 1, assign_account_by_id: 2, json_response: 3]
|
only: [add_link_headers: 2, truthy_param?: 1, assign_account_by_id: 2, json_response: 3]
|
||||||
|
|
||||||
alias Pleroma.Emoji
|
|
||||||
alias Pleroma.Plugs.OAuthScopesPlug
|
alias Pleroma.Plugs.OAuthScopesPlug
|
||||||
alias Pleroma.Plugs.RateLimiter
|
alias Pleroma.Plugs.RateLimiter
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
@ -63,11 +62,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
||||||
when action not in [:create, :show, :statuses]
|
when action not in [:create, :show, :statuses]
|
||||||
)
|
)
|
||||||
|
|
||||||
@relations [:follow, :unfollow]
|
@relationship_actions [:follow, :unfollow]
|
||||||
@needs_account ~W(followers following lists follow unfollow mute unmute block unblock)a
|
@needs_account ~W(followers following lists follow unfollow mute unmute block unblock)a
|
||||||
|
|
||||||
plug(RateLimiter, [name: :relations_id_action, params: ["id", "uri"]] when action in @relations)
|
plug(
|
||||||
plug(RateLimiter, [name: :relations_actions] when action in @relations)
|
RateLimiter,
|
||||||
|
[name: :relation_id_action, params: ["id", "uri"]] when action in @relationship_actions
|
||||||
|
)
|
||||||
|
|
||||||
|
plug(RateLimiter, [name: :relations_actions] when action in @relationship_actions)
|
||||||
plug(RateLimiter, [name: :app_account_creation] when action == :create)
|
plug(RateLimiter, [name: :app_account_creation] when action == :create)
|
||||||
plug(:assign_account_by_id when action in @needs_account)
|
plug(:assign_account_by_id when action in @needs_account)
|
||||||
|
|
||||||
|
@ -140,17 +143,6 @@ def verify_credentials(%{assigns: %{user: user}} = conn, _) do
|
||||||
def update_credentials(%{assigns: %{user: original_user}} = conn, params) do
|
def update_credentials(%{assigns: %{user: original_user}} = conn, params) do
|
||||||
user = original_user
|
user = original_user
|
||||||
|
|
||||||
params =
|
|
||||||
if Map.has_key?(params, "fields_attributes") do
|
|
||||||
Map.update!(params, "fields_attributes", fn fields ->
|
|
||||||
fields
|
|
||||||
|> normalize_fields_attributes()
|
|
||||||
|> Enum.filter(fn %{"name" => n} -> n != "" end)
|
|
||||||
end)
|
|
||||||
else
|
|
||||||
params
|
|
||||||
end
|
|
||||||
|
|
||||||
user_params =
|
user_params =
|
||||||
[
|
[
|
||||||
:no_rich_text,
|
:no_rich_text,
|
||||||
|
@ -169,46 +161,20 @@ def update_credentials(%{assigns: %{user: original_user}} = conn, params) do
|
||||||
add_if_present(acc, params, to_string(key), key, &{:ok, truthy_param?(&1)})
|
add_if_present(acc, params, to_string(key), key, &{:ok, truthy_param?(&1)})
|
||||||
end)
|
end)
|
||||||
|> add_if_present(params, "display_name", :name)
|
|> add_if_present(params, "display_name", :name)
|
||||||
|> add_if_present(params, "note", :bio, fn value -> {:ok, User.parse_bio(value, user)} end)
|
|> add_if_present(params, "note", :bio)
|
||||||
|> add_if_present(params, "avatar", :avatar, fn value ->
|
|> add_if_present(params, "avatar", :avatar)
|
||||||
with %Plug.Upload{} <- value,
|
|> add_if_present(params, "header", :banner)
|
||||||
{:ok, object} <- ActivityPub.upload(value, type: :avatar) do
|
|> add_if_present(params, "pleroma_background_image", :background)
|
||||||
{:ok, object.data}
|
|> add_if_present(
|
||||||
end
|
params,
|
||||||
end)
|
"fields_attributes",
|
||||||
|> add_if_present(params, "header", :banner, fn value ->
|
:raw_fields,
|
||||||
with %Plug.Upload{} <- value,
|
&{:ok, normalize_fields_attributes(&1)}
|
||||||
{:ok, object} <- ActivityPub.upload(value, type: :banner) do
|
)
|
||||||
{:ok, object.data}
|
|> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store)
|
||||||
end
|
|
||||||
end)
|
|
||||||
|> add_if_present(params, "pleroma_background_image", :background, fn value ->
|
|
||||||
with %Plug.Upload{} <- value,
|
|
||||||
{:ok, object} <- ActivityPub.upload(value, type: :background) do
|
|
||||||
{:ok, object.data}
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|> add_if_present(params, "fields_attributes", :fields, fn fields ->
|
|
||||||
fields = Enum.map(fields, fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end)
|
|
||||||
|
|
||||||
{:ok, fields}
|
|
||||||
end)
|
|
||||||
|> add_if_present(params, "fields_attributes", :raw_fields)
|
|
||||||
|> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store, fn value ->
|
|
||||||
{:ok, Map.merge(user.pleroma_settings_store, value)}
|
|
||||||
end)
|
|
||||||
|> add_if_present(params, "default_scope", :default_scope)
|
|> add_if_present(params, "default_scope", :default_scope)
|
||||||
|> add_if_present(params, "actor_type", :actor_type)
|
|> add_if_present(params, "actor_type", :actor_type)
|
||||||
|
|
||||||
emojis_text = (user_params["display_name"] || "") <> (user_params["note"] || "")
|
|
||||||
|
|
||||||
user_emojis =
|
|
||||||
user
|
|
||||||
|> Map.get(:emoji, [])
|
|
||||||
|> Enum.concat(Emoji.Formatter.get_emoji_map(emojis_text))
|
|
||||||
|> Enum.dedup()
|
|
||||||
|
|
||||||
user_params = Map.put(user_params, :emoji, user_emojis)
|
|
||||||
changeset = User.update_changeset(user, user_params)
|
changeset = User.update_changeset(user, user_params)
|
||||||
|
|
||||||
with {:ok, user} <- User.update_and_set_cache(changeset) do
|
with {:ok, user} <- User.update_and_set_cache(changeset) do
|
||||||
|
|
|
@ -5,12 +5,28 @@
|
||||||
defmodule Pleroma.Web.MastodonAPI.AccountView do
|
defmodule Pleroma.Web.MastodonAPI.AccountView do
|
||||||
use Pleroma.Web, :view
|
use Pleroma.Web, :view
|
||||||
|
|
||||||
|
alias Pleroma.FollowingRelationship
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.UserRelationship
|
||||||
alias Pleroma.Web.CommonAPI.Utils
|
alias Pleroma.Web.CommonAPI.Utils
|
||||||
alias Pleroma.Web.MastodonAPI.AccountView
|
alias Pleroma.Web.MastodonAPI.AccountView
|
||||||
alias Pleroma.Web.MediaProxy
|
alias Pleroma.Web.MediaProxy
|
||||||
|
|
||||||
def render("index.json", %{users: users} = opts) do
|
def render("index.json", %{users: users} = opts) do
|
||||||
|
relationships_opt =
|
||||||
|
cond do
|
||||||
|
Map.has_key?(opts, :relationships) ->
|
||||||
|
opts[:relationships]
|
||||||
|
|
||||||
|
is_nil(opts[:for]) ->
|
||||||
|
UserRelationship.view_relationships_option(nil, [])
|
||||||
|
|
||||||
|
true ->
|
||||||
|
UserRelationship.view_relationships_option(opts[:for], users)
|
||||||
|
end
|
||||||
|
|
||||||
|
opts = Map.put(opts, :relationships, relationships_opt)
|
||||||
|
|
||||||
users
|
users
|
||||||
|> render_many(AccountView, "show.json", opts)
|
|> render_many(AccountView, "show.json", opts)
|
||||||
|> Enum.filter(&Enum.any?/1)
|
|> Enum.filter(&Enum.any?/1)
|
||||||
|
@ -35,34 +51,107 @@ def render("relationship.json", %{user: nil, target: _target}) do
|
||||||
%{}
|
%{}
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("relationship.json", %{user: %User{} = user, target: %User{} = target}) do
|
def render(
|
||||||
follow_state = User.get_cached_follow_state(user, target)
|
"relationship.json",
|
||||||
|
%{user: %User{} = reading_user, target: %User{} = target} = opts
|
||||||
|
) do
|
||||||
|
user_relationships = get_in(opts, [:relationships, :user_relationships])
|
||||||
|
following_relationships = get_in(opts, [:relationships, :following_relationships])
|
||||||
|
|
||||||
requested =
|
follow_state =
|
||||||
if follow_state && !User.following?(user, target) do
|
if following_relationships do
|
||||||
follow_state == "pending"
|
user_to_target_following_relation =
|
||||||
|
FollowingRelationship.find(following_relationships, reading_user, target)
|
||||||
|
|
||||||
|
User.get_follow_state(reading_user, target, user_to_target_following_relation)
|
||||||
else
|
else
|
||||||
false
|
User.get_follow_state(reading_user, target)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
followed_by =
|
||||||
|
if following_relationships do
|
||||||
|
case FollowingRelationship.find(following_relationships, target, reading_user) do
|
||||||
|
%{state: "accept"} -> true
|
||||||
|
_ -> false
|
||||||
|
end
|
||||||
|
else
|
||||||
|
User.following?(target, reading_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
# NOTE: adjust UserRelationship.view_relationships_option/2 on new relation-related flags
|
||||||
%{
|
%{
|
||||||
id: to_string(target.id),
|
id: to_string(target.id),
|
||||||
following: User.following?(user, target),
|
following: follow_state == "accept",
|
||||||
followed_by: User.following?(target, user),
|
followed_by: followed_by,
|
||||||
blocking: User.blocks_user?(user, target),
|
blocking:
|
||||||
blocked_by: User.blocks_user?(target, user),
|
UserRelationship.exists?(
|
||||||
muting: User.mutes?(user, target),
|
user_relationships,
|
||||||
muting_notifications: User.muted_notifications?(user, target),
|
:block,
|
||||||
subscribing: User.subscribed_to?(user, target),
|
reading_user,
|
||||||
requested: requested,
|
target,
|
||||||
domain_blocking: User.blocks_domain?(user, target),
|
&User.blocks_user?(&1, &2)
|
||||||
showing_reblogs: User.showing_reblogs?(user, target),
|
),
|
||||||
|
blocked_by:
|
||||||
|
UserRelationship.exists?(
|
||||||
|
user_relationships,
|
||||||
|
:block,
|
||||||
|
target,
|
||||||
|
reading_user,
|
||||||
|
&User.blocks_user?(&1, &2)
|
||||||
|
),
|
||||||
|
muting:
|
||||||
|
UserRelationship.exists?(
|
||||||
|
user_relationships,
|
||||||
|
:mute,
|
||||||
|
reading_user,
|
||||||
|
target,
|
||||||
|
&User.mutes?(&1, &2)
|
||||||
|
),
|
||||||
|
muting_notifications:
|
||||||
|
UserRelationship.exists?(
|
||||||
|
user_relationships,
|
||||||
|
:notification_mute,
|
||||||
|
reading_user,
|
||||||
|
target,
|
||||||
|
&User.muted_notifications?(&1, &2)
|
||||||
|
),
|
||||||
|
subscribing:
|
||||||
|
UserRelationship.exists?(
|
||||||
|
user_relationships,
|
||||||
|
:inverse_subscription,
|
||||||
|
target,
|
||||||
|
reading_user,
|
||||||
|
&User.subscribed_to?(&2, &1)
|
||||||
|
),
|
||||||
|
requested: follow_state == "pending",
|
||||||
|
domain_blocking: User.blocks_domain?(reading_user, target),
|
||||||
|
showing_reblogs:
|
||||||
|
not UserRelationship.exists?(
|
||||||
|
user_relationships,
|
||||||
|
:reblog_mute,
|
||||||
|
reading_user,
|
||||||
|
target,
|
||||||
|
&User.muting_reblogs?(&1, &2)
|
||||||
|
),
|
||||||
endorsed: false
|
endorsed: false
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("relationships.json", %{user: user, targets: targets}) do
|
def render("relationships.json", %{user: user, targets: targets} = opts) do
|
||||||
render_many(targets, AccountView, "relationship.json", user: user, as: :target)
|
relationships_opt =
|
||||||
|
cond do
|
||||||
|
Map.has_key?(opts, :relationships) ->
|
||||||
|
opts[:relationships]
|
||||||
|
|
||||||
|
is_nil(opts[:for]) ->
|
||||||
|
UserRelationship.view_relationships_option(nil, [])
|
||||||
|
|
||||||
|
true ->
|
||||||
|
UserRelationship.view_relationships_option(user, targets)
|
||||||
|
end
|
||||||
|
|
||||||
|
render_opts = %{as: :target, user: user, relationships: relationships_opt}
|
||||||
|
render_many(targets, AccountView, "relationship.json", render_opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_render("show.json", %{user: user} = opts) do
|
defp do_render("show.json", %{user: user} = opts) do
|
||||||
|
@ -100,7 +189,12 @@ defp do_render("show.json", %{user: user} = opts) do
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
relationship = render("relationship.json", %{user: opts[:for], target: user})
|
relationship =
|
||||||
|
render("relationship.json", %{
|
||||||
|
user: opts[:for],
|
||||||
|
target: user,
|
||||||
|
relationships: opts[:relationships]
|
||||||
|
})
|
||||||
|
|
||||||
%{
|
%{
|
||||||
id: to_string(user.id),
|
id: to_string(user.id),
|
||||||
|
@ -122,7 +216,7 @@ defp do_render("show.json", %{user: user} = opts) do
|
||||||
fields: user.fields,
|
fields: user.fields,
|
||||||
bot: bot,
|
bot: bot,
|
||||||
source: %{
|
source: %{
|
||||||
note: Pleroma.HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
|
note: (user.bio || "") |> String.replace(~r(<br */?>), "\n") |> Pleroma.HTML.strip_tags(),
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
fields: user.raw_fields,
|
fields: user.raw_fields,
|
||||||
pleroma: %{
|
pleroma: %{
|
||||||
|
|
|
@ -8,24 +8,86 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.UserRelationship
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.MastodonAPI.AccountView
|
alias Pleroma.Web.MastodonAPI.AccountView
|
||||||
alias Pleroma.Web.MastodonAPI.NotificationView
|
alias Pleroma.Web.MastodonAPI.NotificationView
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
|
|
||||||
def render("index.json", %{notifications: notifications, for: user}) do
|
def render("index.json", %{notifications: notifications, for: reading_user} = opts) do
|
||||||
safe_render_many(notifications, NotificationView, "show.json", %{for: user})
|
activities = Enum.map(notifications, & &1.activity)
|
||||||
|
|
||||||
|
parent_activities =
|
||||||
|
activities
|
||||||
|
|> Enum.filter(
|
||||||
|
&(Activity.mastodon_notification_type(&1) in [
|
||||||
|
"favourite",
|
||||||
|
"reblog",
|
||||||
|
"pleroma:emoji_reaction"
|
||||||
|
])
|
||||||
|
)
|
||||||
|
|> Enum.map(& &1.data["object"])
|
||||||
|
|> Activity.create_by_object_ap_id()
|
||||||
|
|> Activity.with_preloaded_object(:left)
|
||||||
|
|> Pleroma.Repo.all()
|
||||||
|
|
||||||
|
relationships_opt =
|
||||||
|
cond do
|
||||||
|
Map.has_key?(opts, :relationships) ->
|
||||||
|
opts[:relationships]
|
||||||
|
|
||||||
|
is_nil(opts[:for]) ->
|
||||||
|
UserRelationship.view_relationships_option(nil, [])
|
||||||
|
|
||||||
|
true ->
|
||||||
|
move_activities_targets =
|
||||||
|
activities
|
||||||
|
|> Enum.filter(&(Activity.mastodon_notification_type(&1) == "move"))
|
||||||
|
|> Enum.map(&User.get_cached_by_ap_id(&1.data["target"]))
|
||||||
|
|
||||||
|
actors =
|
||||||
|
activities
|
||||||
|
|> Enum.map(fn a -> User.get_cached_by_ap_id(a.data["actor"]) end)
|
||||||
|
|> Enum.filter(& &1)
|
||||||
|
|> Kernel.++(move_activities_targets)
|
||||||
|
|
||||||
|
UserRelationship.view_relationships_option(reading_user, actors)
|
||||||
|
end
|
||||||
|
|
||||||
|
opts = %{
|
||||||
|
for: reading_user,
|
||||||
|
parent_activities: parent_activities,
|
||||||
|
relationships: relationships_opt
|
||||||
|
}
|
||||||
|
|
||||||
|
safe_render_many(notifications, NotificationView, "show.json", opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("show.json", %{
|
def render(
|
||||||
notification: %Notification{activity: activity} = notification,
|
"show.json",
|
||||||
for: user
|
%{
|
||||||
}) do
|
notification: %Notification{activity: activity} = notification,
|
||||||
|
for: reading_user
|
||||||
|
} = opts
|
||||||
|
) do
|
||||||
actor = User.get_cached_by_ap_id(activity.data["actor"])
|
actor = User.get_cached_by_ap_id(activity.data["actor"])
|
||||||
parent_activity = Activity.get_create_by_object_ap_id(activity.data["object"])
|
|
||||||
|
parent_activity_fn = fn ->
|
||||||
|
if opts[:parent_activities] do
|
||||||
|
Activity.Queries.find_by_object_ap_id(opts[:parent_activities], activity.data["object"])
|
||||||
|
else
|
||||||
|
Activity.get_create_by_object_ap_id(activity.data["object"])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
mastodon_type = Activity.mastodon_notification_type(activity)
|
mastodon_type = Activity.mastodon_notification_type(activity)
|
||||||
|
|
||||||
with %{id: _} = account <- AccountView.render("show.json", %{user: actor, for: user}) do
|
with %{id: _} = account <-
|
||||||
|
AccountView.render("show.json", %{
|
||||||
|
user: actor,
|
||||||
|
for: reading_user,
|
||||||
|
relationships: opts[:relationships]
|
||||||
|
}) do
|
||||||
response = %{
|
response = %{
|
||||||
id: to_string(notification.id),
|
id: to_string(notification.id),
|
||||||
type: mastodon_type,
|
type: mastodon_type,
|
||||||
|
@ -36,24 +98,28 @@ def render("show.json", %{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
render_opts = %{relationships: opts[:relationships]}
|
||||||
|
|
||||||
case mastodon_type do
|
case mastodon_type do
|
||||||
"mention" ->
|
"mention" ->
|
||||||
put_status(response, activity, user)
|
put_status(response, activity, reading_user, render_opts)
|
||||||
|
|
||||||
"favourite" ->
|
"favourite" ->
|
||||||
put_status(response, parent_activity, user)
|
put_status(response, parent_activity_fn.(), reading_user, render_opts)
|
||||||
|
|
||||||
"reblog" ->
|
"reblog" ->
|
||||||
put_status(response, parent_activity, user)
|
put_status(response, parent_activity_fn.(), reading_user, render_opts)
|
||||||
|
|
||||||
"move" ->
|
"move" ->
|
||||||
put_target(response, activity, user)
|
put_target(response, activity, reading_user, render_opts)
|
||||||
|
|
||||||
"follow" ->
|
"follow" ->
|
||||||
response
|
response
|
||||||
|
|
||||||
"pleroma:emoji_reaction" ->
|
"pleroma:emoji_reaction" ->
|
||||||
put_status(response, parent_activity, user) |> put_emoji(activity)
|
response
|
||||||
|
|> put_status(parent_activity_fn.(), reading_user, render_opts)
|
||||||
|
|> put_emoji(activity)
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
nil
|
nil
|
||||||
|
@ -64,16 +130,21 @@ def render("show.json", %{
|
||||||
end
|
end
|
||||||
|
|
||||||
defp put_emoji(response, activity) do
|
defp put_emoji(response, activity) do
|
||||||
response
|
Map.put(response, :emoji, activity.data["content"])
|
||||||
|> Map.put(:emoji, activity.data["content"])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp put_status(response, activity, user) do
|
defp put_status(response, activity, reading_user, opts) do
|
||||||
Map.put(response, :status, StatusView.render("show.json", %{activity: activity, for: user}))
|
status_render_opts = Map.merge(opts, %{activity: activity, for: reading_user})
|
||||||
|
status_render = StatusView.render("show.json", status_render_opts)
|
||||||
|
|
||||||
|
Map.put(response, :status, status_render)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp put_target(response, activity, user) do
|
defp put_target(response, activity, reading_user, opts) do
|
||||||
target = User.get_cached_by_ap_id(activity.data["target"])
|
target_user = User.get_cached_by_ap_id(activity.data["target"])
|
||||||
Map.put(response, :target, AccountView.render("show.json", %{user: target, for: user}))
|
target_render_opts = Map.merge(opts, %{user: target_user, for: reading_user})
|
||||||
|
target_render = AccountView.render("show.json", target_render_opts)
|
||||||
|
|
||||||
|
Map.put(response, :target, target_render)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,6 +13,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.UserRelationship
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.CommonAPI.Utils
|
alias Pleroma.Web.CommonAPI.Utils
|
||||||
alias Pleroma.Web.MastodonAPI.AccountView
|
alias Pleroma.Web.MastodonAPI.AccountView
|
||||||
|
@ -71,10 +72,41 @@ defp reblogged?(activity, user) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("index.json", opts) do
|
def render("index.json", opts) do
|
||||||
replied_to_activities = get_replied_to_activities(opts.activities)
|
# To do: check AdminAPIControllerTest on the reasons behind nil activities in the list
|
||||||
opts = Map.put(opts, :replied_to_activities, replied_to_activities)
|
activities = Enum.filter(opts.activities, & &1)
|
||||||
|
replied_to_activities = get_replied_to_activities(activities)
|
||||||
|
|
||||||
safe_render_many(opts.activities, StatusView, "show.json", opts)
|
parent_activities =
|
||||||
|
activities
|
||||||
|
|> Enum.filter(&(&1.data["type"] == "Announce" && &1.data["object"]))
|
||||||
|
|> Enum.map(&Object.normalize(&1).data["id"])
|
||||||
|
|> Activity.create_by_object_ap_id()
|
||||||
|
|> Activity.with_preloaded_object(:left)
|
||||||
|
|> Activity.with_preloaded_bookmark(opts[:for])
|
||||||
|
|> Activity.with_set_thread_muted_field(opts[:for])
|
||||||
|
|> Repo.all()
|
||||||
|
|
||||||
|
relationships_opt =
|
||||||
|
cond do
|
||||||
|
Map.has_key?(opts, :relationships) ->
|
||||||
|
opts[:relationships]
|
||||||
|
|
||||||
|
is_nil(opts[:for]) ->
|
||||||
|
UserRelationship.view_relationships_option(nil, [])
|
||||||
|
|
||||||
|
true ->
|
||||||
|
actors = Enum.map(activities ++ parent_activities, &get_user(&1.data["actor"]))
|
||||||
|
|
||||||
|
UserRelationship.view_relationships_option(opts[:for], actors)
|
||||||
|
end
|
||||||
|
|
||||||
|
opts =
|
||||||
|
opts
|
||||||
|
|> Map.put(:replied_to_activities, replied_to_activities)
|
||||||
|
|> Map.put(:parent_activities, parent_activities)
|
||||||
|
|> Map.put(:relationships, relationships_opt)
|
||||||
|
|
||||||
|
safe_render_many(activities, StatusView, "show.json", opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(
|
def render(
|
||||||
|
@ -85,17 +117,25 @@ def render(
|
||||||
created_at = Utils.to_masto_date(activity.data["published"])
|
created_at = Utils.to_masto_date(activity.data["published"])
|
||||||
activity_object = Object.normalize(activity)
|
activity_object = Object.normalize(activity)
|
||||||
|
|
||||||
reblogged_activity =
|
reblogged_parent_activity =
|
||||||
Activity.create_by_object_ap_id(activity_object.data["id"])
|
if opts[:parent_activities] do
|
||||||
|> Activity.with_preloaded_bookmark(opts[:for])
|
Activity.Queries.find_by_object_ap_id(
|
||||||
|> Activity.with_set_thread_muted_field(opts[:for])
|
opts[:parent_activities],
|
||||||
|> Repo.one()
|
activity_object.data["id"]
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Activity.create_by_object_ap_id(activity_object.data["id"])
|
||||||
|
|> Activity.with_preloaded_bookmark(opts[:for])
|
||||||
|
|> Activity.with_set_thread_muted_field(opts[:for])
|
||||||
|
|> Repo.one()
|
||||||
|
end
|
||||||
|
|
||||||
reblogged = render("show.json", Map.put(opts, :activity, reblogged_activity))
|
reblog_rendering_opts = Map.put(opts, :activity, reblogged_parent_activity)
|
||||||
|
reblogged = render("show.json", reblog_rendering_opts)
|
||||||
|
|
||||||
favorited = opts[:for] && opts[:for].ap_id in (activity_object.data["likes"] || [])
|
favorited = opts[:for] && opts[:for].ap_id in (activity_object.data["likes"] || [])
|
||||||
|
|
||||||
bookmarked = Activity.get_bookmark(reblogged_activity, opts[:for]) != nil
|
bookmarked = Activity.get_bookmark(reblogged_parent_activity, opts[:for]) != nil
|
||||||
|
|
||||||
mentions =
|
mentions =
|
||||||
activity.recipients
|
activity.recipients
|
||||||
|
@ -107,7 +147,12 @@ def render(
|
||||||
id: to_string(activity.id),
|
id: to_string(activity.id),
|
||||||
uri: activity_object.data["id"],
|
uri: activity_object.data["id"],
|
||||||
url: activity_object.data["id"],
|
url: activity_object.data["id"],
|
||||||
account: AccountView.render("show.json", %{user: user, for: opts[:for]}),
|
account:
|
||||||
|
AccountView.render("show.json", %{
|
||||||
|
user: user,
|
||||||
|
for: opts[:for],
|
||||||
|
relationships: opts[:relationships]
|
||||||
|
}),
|
||||||
in_reply_to_id: nil,
|
in_reply_to_id: nil,
|
||||||
in_reply_to_account_id: nil,
|
in_reply_to_account_id: nil,
|
||||||
reblog: reblogged,
|
reblog: reblogged,
|
||||||
|
@ -116,7 +161,7 @@ def render(
|
||||||
reblogs_count: 0,
|
reblogs_count: 0,
|
||||||
replies_count: 0,
|
replies_count: 0,
|
||||||
favourites_count: 0,
|
favourites_count: 0,
|
||||||
reblogged: reblogged?(reblogged_activity, opts[:for]),
|
reblogged: reblogged?(reblogged_parent_activity, opts[:for]),
|
||||||
favourited: present?(favorited),
|
favourited: present?(favorited),
|
||||||
bookmarked: present?(bookmarked),
|
bookmarked: present?(bookmarked),
|
||||||
muted: false,
|
muted: false,
|
||||||
|
@ -183,9 +228,10 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
||||||
end
|
end
|
||||||
|
|
||||||
thread_muted? =
|
thread_muted? =
|
||||||
case activity.thread_muted? do
|
cond do
|
||||||
thread_muted? when is_boolean(thread_muted?) -> thread_muted?
|
is_nil(opts[:for]) -> false
|
||||||
nil -> (opts[:for] && CommonAPI.thread_muted?(opts[:for], activity)) || false
|
is_boolean(activity.thread_muted?) -> activity.thread_muted?
|
||||||
|
true -> CommonAPI.thread_muted?(opts[:for], activity)
|
||||||
end
|
end
|
||||||
|
|
||||||
attachment_data = object.data["attachment"] || []
|
attachment_data = object.data["attachment"] || []
|
||||||
|
@ -253,11 +299,26 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
||||||
_ -> []
|
_ -> []
|
||||||
end
|
end
|
||||||
|
|
||||||
|
muted =
|
||||||
|
thread_muted? ||
|
||||||
|
UserRelationship.exists?(
|
||||||
|
get_in(opts, [:relationships, :user_relationships]),
|
||||||
|
:mute,
|
||||||
|
opts[:for],
|
||||||
|
user,
|
||||||
|
fn for_user, user -> User.mutes?(for_user, user) end
|
||||||
|
)
|
||||||
|
|
||||||
%{
|
%{
|
||||||
id: to_string(activity.id),
|
id: to_string(activity.id),
|
||||||
uri: object.data["id"],
|
uri: object.data["id"],
|
||||||
url: url,
|
url: url,
|
||||||
account: AccountView.render("show.json", %{user: user, for: opts[:for]}),
|
account:
|
||||||
|
AccountView.render("show.json", %{
|
||||||
|
user: user,
|
||||||
|
for: opts[:for],
|
||||||
|
relationships: opts[:relationships]
|
||||||
|
}),
|
||||||
in_reply_to_id: reply_to && to_string(reply_to.id),
|
in_reply_to_id: reply_to && to_string(reply_to.id),
|
||||||
in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id),
|
in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id),
|
||||||
reblog: nil,
|
reblog: nil,
|
||||||
|
@ -270,7 +331,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
||||||
reblogged: reblogged?(activity, opts[:for]),
|
reblogged: reblogged?(activity, opts[:for]),
|
||||||
favourited: present?(favorited),
|
favourited: present?(favorited),
|
||||||
bookmarked: present?(bookmarked),
|
bookmarked: present?(bookmarked),
|
||||||
muted: thread_muted? || User.mutes?(opts[:for], user),
|
muted: muted,
|
||||||
pinned: pinned?(activity, user),
|
pinned: pinned?(activity, user),
|
||||||
sensitive: sensitive,
|
sensitive: sensitive,
|
||||||
spoiler_text: summary,
|
spoiler_text: summary,
|
||||||
|
@ -421,7 +482,7 @@ def get_reply_to(%{data: %{"object" => _object}} = activity, _) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_content(%{data: %{"type" => object_type}} = object)
|
def render_content(%{data: %{"type" => object_type}} = object)
|
||||||
when object_type in ["Video", "Event"] do
|
when object_type in ["Video", "Event", "Audio"] do
|
||||||
with name when not is_nil(name) and name != "" <- object.data["name"] do
|
with name when not is_nil(name) and name != "" <- object.data["name"] do
|
||||||
"<p><a href=\"#{object.data["id"]}\">#{name}</a></p>#{object.data["content"]}"
|
"<p><a href=\"#{object.data["id"]}\">#{name}</a></p>#{object.data["content"]}"
|
||||||
else
|
else
|
||||||
|
|
|
@ -173,6 +173,8 @@ defmodule Pleroma.Web.Router do
|
||||||
|
|
||||||
get("/users/:nickname/password_reset", AdminAPIController, :get_password_reset)
|
get("/users/:nickname/password_reset", AdminAPIController, :get_password_reset)
|
||||||
patch("/users/force_password_reset", AdminAPIController, :force_password_reset)
|
patch("/users/force_password_reset", AdminAPIController, :force_password_reset)
|
||||||
|
get("/users/:nickname/credentials", AdminAPIController, :show_user_credentials)
|
||||||
|
patch("/users/:nickname/credentials", AdminAPIController, :update_user_credentials)
|
||||||
|
|
||||||
get("/users", AdminAPIController, :list_users)
|
get("/users", AdminAPIController, :list_users)
|
||||||
get("/users/:nickname", AdminAPIController, :user_show)
|
get("/users/:nickname", AdminAPIController, :user_show)
|
||||||
|
|
|
@ -60,7 +60,9 @@ defp represent(%Activity{object: %Object{data: data}} = activity, selected) do
|
||||||
|
|
||||||
content =
|
content =
|
||||||
if data["content"] do
|
if data["content"] do
|
||||||
Pleroma.HTML.filter_tags(data["content"])
|
data["content"]
|
||||||
|
|> Pleroma.HTML.filter_tags()
|
||||||
|
|> Pleroma.Emoji.Formatter.emojify(Map.get(data, "emoji", %{}))
|
||||||
else
|
else
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
|
@ -130,7 +130,7 @@ defp do_stream(%{topic: topic, item: item}) do
|
||||||
|
|
||||||
defp should_send?(%User{} = user, %Activity{} = item) do
|
defp should_send?(%User{} = user, %Activity{} = item) do
|
||||||
%{block: blocked_ap_ids, mute: muted_ap_ids, reblog_mute: reblog_muted_ap_ids} =
|
%{block: blocked_ap_ids, mute: muted_ap_ids, reblog_mute: reblog_muted_ap_ids} =
|
||||||
User.outgoing_relations_ap_ids(user, [:block, :mute, :reblog_mute])
|
User.outgoing_relationships_ap_ids(user, [:block, :mute, :reblog_mute])
|
||||||
|
|
||||||
recipient_blocks = MapSet.new(blocked_ap_ids ++ muted_ap_ids)
|
recipient_blocks = MapSet.new(blocked_ap_ids ++ muted_ap_ids)
|
||||||
recipients = MapSet.new(item.recipients)
|
recipients = MapSet.new(item.recipients)
|
||||||
|
|
2
mix.exs
2
mix.exs
|
@ -63,7 +63,7 @@ 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],
|
extra_applications: [:logger, :runtime_tools, :comeonin, :quack, :fast_sanitize, :ssl],
|
||||||
included_applications: [:ex_syslogger]
|
included_applications: [:ex_syslogger]
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
Binary file not shown.
44
test/fixtures/tesla_mock/funkwhale_audio.json
vendored
Normal file
44
test/fixtures/tesla_mock/funkwhale_audio.json
vendored
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"id": "https://channels.tests.funkwhale.audio/federation/music/uploads/42342395-0208-4fee-a38d-259a6dae0871",
|
||||||
|
"type": "Audio",
|
||||||
|
"name": "Compositions - Test Audio for Pleroma",
|
||||||
|
"attributedTo": "https://channels.tests.funkwhale.audio/federation/actors/compositions",
|
||||||
|
"published": "2020-03-11T10:01:52.714918+00:00",
|
||||||
|
"to": "https://www.w3.org/ns/activitystreams#Public",
|
||||||
|
"url": [
|
||||||
|
{
|
||||||
|
"type": "Link",
|
||||||
|
"mimeType": "audio/ogg",
|
||||||
|
"href": "https://channels.tests.funkwhale.audio/api/v1/listen/3901e5d8-0445-49d5-9711-e096cf32e515/?upload=42342395-0208-4fee-a38d-259a6dae0871&download=false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Link",
|
||||||
|
"mimeType": "text/html",
|
||||||
|
"href": "https://channels.tests.funkwhale.audio/library/tracks/74"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"content": "<p>This is a test Audio for Pleroma.</p>",
|
||||||
|
"mediaType": "text/html",
|
||||||
|
"tag": [
|
||||||
|
{
|
||||||
|
"type": "Hashtag",
|
||||||
|
"name": "#funkwhale"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Hashtag",
|
||||||
|
"name": "#test"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Hashtag",
|
||||||
|
"name": "#tests"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"summary": "#funkwhale #test #tests",
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://w3id.org/security/v1",
|
||||||
|
{
|
||||||
|
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
44
test/fixtures/tesla_mock/funkwhale_channel.json
vendored
Normal file
44
test/fixtures/tesla_mock/funkwhale_channel.json
vendored
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"id": "https://channels.tests.funkwhale.audio/federation/actors/compositions",
|
||||||
|
"outbox": "https://channels.tests.funkwhale.audio/federation/actors/compositions/outbox",
|
||||||
|
"inbox": "https://channels.tests.funkwhale.audio/federation/actors/compositions/inbox",
|
||||||
|
"preferredUsername": "compositions",
|
||||||
|
"type": "Person",
|
||||||
|
"name": "Compositions",
|
||||||
|
"followers": "https://channels.tests.funkwhale.audio/federation/actors/compositions/followers",
|
||||||
|
"following": "https://channels.tests.funkwhale.audio/federation/actors/compositions/following",
|
||||||
|
"manuallyApprovesFollowers": false,
|
||||||
|
"url": [
|
||||||
|
{
|
||||||
|
"type": "Link",
|
||||||
|
"href": "https://channels.tests.funkwhale.audio/channels/compositions",
|
||||||
|
"mediaType": "text/html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Link",
|
||||||
|
"href": "https://channels.tests.funkwhale.audio/api/v1/channels/compositions/rss",
|
||||||
|
"mediaType": "application/rss+xml"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"icon": {
|
||||||
|
"type": "Image",
|
||||||
|
"url": "https://channels.tests.funkwhale.audio/media/attachments/75/b4/f1/nosmile.jpeg",
|
||||||
|
"mediaType": "image/jpeg"
|
||||||
|
},
|
||||||
|
"summary": "<p>I'm testing federation with the fediverse :)</p>",
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://w3id.org/security/v1",
|
||||||
|
{
|
||||||
|
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"publicKey": {
|
||||||
|
"owner": "https://channels.tests.funkwhale.audio/federation/actors/compositions",
|
||||||
|
"publicKeyPem": "-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAv25u57oZfVLV3KltS+HcsdSx9Op4MmzIes1J8Wu8s0KbdXf2zEwS\nsVqyHgs/XCbnzsR3FqyJTo46D2BVnvZcuU5srNcR2I2HMaqQ0oVdnATE4K6KdcgV\nN+98pMWo56B8LTgE1VpvqbsrXLi9jCTzjrkebVMOP+ZVu+64v1qdgddseblYMnBZ\nct0s7ONbHnqrWlTGf5wES1uIZTVdn5r4MduZG+Uenfi1opBS0lUUxfWdW9r0oF2b\nyneZUyaUCbEroeKbqsweXCWVgnMarUOsgqC42KM4cf95lySSwTSaUtZYIbTw7s9W\n2jveU/rVg8BYZu5JK5obgBoxtlUeUoSswwIDAQAB\n-----END RSA PUBLIC KEY-----\n",
|
||||||
|
"id": "https://channels.tests.funkwhale.audio/federation/actors/compositions#main-key"
|
||||||
|
},
|
||||||
|
"endpoints": {
|
||||||
|
"sharedInbox": "https://channels.tests.funkwhale.audio/federation/shared/inbox"
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,12 +6,14 @@ defmodule Pleroma.NotificationTest do
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
import Mock
|
||||||
|
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Tests.ObanHelpers
|
alias Pleroma.Tests.ObanHelpers
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Web.Push
|
||||||
alias Pleroma.Web.Streamer
|
alias Pleroma.Web.Streamer
|
||||||
|
|
||||||
describe "create_notifications" do
|
describe "create_notifications" do
|
||||||
|
@ -80,6 +82,80 @@ test "does not create a notification for subscribed users if status is a reply"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "CommonApi.post/2 notification-related functionality" do
|
||||||
|
test_with_mock "creates but does NOT send notification to blocker user",
|
||||||
|
Push,
|
||||||
|
[:passthrough],
|
||||||
|
[] do
|
||||||
|
user = insert(:user)
|
||||||
|
blocker = insert(:user)
|
||||||
|
{:ok, _user_relationship} = User.block(blocker, user)
|
||||||
|
|
||||||
|
{:ok, _activity} = CommonAPI.post(user, %{"status" => "hey @#{blocker.nickname}!"})
|
||||||
|
|
||||||
|
blocker_id = blocker.id
|
||||||
|
assert [%Notification{user_id: ^blocker_id}] = Repo.all(Notification)
|
||||||
|
refute called(Push.send(:_))
|
||||||
|
end
|
||||||
|
|
||||||
|
test_with_mock "creates but does NOT send notification to notification-muter user",
|
||||||
|
Push,
|
||||||
|
[:passthrough],
|
||||||
|
[] do
|
||||||
|
user = insert(:user)
|
||||||
|
muter = insert(:user)
|
||||||
|
{:ok, _user_relationships} = User.mute(muter, user)
|
||||||
|
|
||||||
|
{:ok, _activity} = CommonAPI.post(user, %{"status" => "hey @#{muter.nickname}!"})
|
||||||
|
|
||||||
|
muter_id = muter.id
|
||||||
|
assert [%Notification{user_id: ^muter_id}] = Repo.all(Notification)
|
||||||
|
refute called(Push.send(:_))
|
||||||
|
end
|
||||||
|
|
||||||
|
test_with_mock "creates but does NOT send notification to thread-muter user",
|
||||||
|
Push,
|
||||||
|
[:passthrough],
|
||||||
|
[] do
|
||||||
|
user = insert(:user)
|
||||||
|
thread_muter = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{thread_muter.nickname}!"})
|
||||||
|
|
||||||
|
{:ok, _} = CommonAPI.add_mute(thread_muter, activity)
|
||||||
|
|
||||||
|
{:ok, _same_context_activity} =
|
||||||
|
CommonAPI.post(user, %{
|
||||||
|
"status" => "hey-hey-hey @#{thread_muter.nickname}!",
|
||||||
|
"in_reply_to_status_id" => activity.id
|
||||||
|
})
|
||||||
|
|
||||||
|
[pre_mute_notification, post_mute_notification] =
|
||||||
|
Repo.all(from(n in Notification, where: n.user_id == ^thread_muter.id, order_by: n.id))
|
||||||
|
|
||||||
|
pre_mute_notification_id = pre_mute_notification.id
|
||||||
|
post_mute_notification_id = post_mute_notification.id
|
||||||
|
|
||||||
|
assert called(
|
||||||
|
Push.send(
|
||||||
|
:meck.is(fn
|
||||||
|
%Notification{id: ^pre_mute_notification_id} -> true
|
||||||
|
_ -> false
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
refute called(
|
||||||
|
Push.send(
|
||||||
|
:meck.is(fn
|
||||||
|
%Notification{id: ^post_mute_notification_id} -> true
|
||||||
|
_ -> false
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "create_notification" do
|
describe "create_notification" do
|
||||||
@tag needs_streamer: true
|
@tag needs_streamer: true
|
||||||
test "it creates a notification for user and send to the 'user' and the 'user:notification' stream" do
|
test "it creates a notification for user and send to the 'user' and the 'user:notification' stream" do
|
||||||
|
@ -382,7 +458,7 @@ test "Returns recent notifications" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "notification target determination" do
|
describe "notification target determination / get_notified_from_activity/2" do
|
||||||
test "it sends notifications to addressed users in new messages" do
|
test "it sends notifications to addressed users in new messages" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
|
@ -392,7 +468,9 @@ test "it sends notifications to addressed users in new messages" do
|
||||||
"status" => "hey @#{other_user.nickname}!"
|
"status" => "hey @#{other_user.nickname}!"
|
||||||
})
|
})
|
||||||
|
|
||||||
assert other_user in Notification.get_notified_from_activity(activity)
|
{enabled_receivers, _disabled_receivers} = Notification.get_notified_from_activity(activity)
|
||||||
|
|
||||||
|
assert other_user in enabled_receivers
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it sends notifications to mentioned users in new messages" do
|
test "it sends notifications to mentioned users in new messages" do
|
||||||
|
@ -420,7 +498,9 @@ test "it sends notifications to mentioned users in new messages" do
|
||||||
|
|
||||||
{:ok, activity} = Transmogrifier.handle_incoming(create_activity)
|
{:ok, activity} = Transmogrifier.handle_incoming(create_activity)
|
||||||
|
|
||||||
assert other_user in Notification.get_notified_from_activity(activity)
|
{enabled_receivers, _disabled_receivers} = Notification.get_notified_from_activity(activity)
|
||||||
|
|
||||||
|
assert other_user in enabled_receivers
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it does not send notifications to users who are only cc in new messages" do
|
test "it does not send notifications to users who are only cc in new messages" do
|
||||||
|
@ -442,7 +522,9 @@ test "it does not send notifications to users who are only cc in new messages" d
|
||||||
|
|
||||||
{:ok, activity} = Transmogrifier.handle_incoming(create_activity)
|
{:ok, activity} = Transmogrifier.handle_incoming(create_activity)
|
||||||
|
|
||||||
assert other_user not in Notification.get_notified_from_activity(activity)
|
{enabled_receivers, _disabled_receivers} = Notification.get_notified_from_activity(activity)
|
||||||
|
|
||||||
|
assert other_user not in enabled_receivers
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it does not send notification to mentioned users in likes" do
|
test "it does not send notification to mentioned users in likes" do
|
||||||
|
@ -457,7 +539,10 @@ test "it does not send notification to mentioned users in likes" do
|
||||||
|
|
||||||
{:ok, activity_two, _} = CommonAPI.favorite(activity_one.id, third_user)
|
{:ok, activity_two, _} = CommonAPI.favorite(activity_one.id, third_user)
|
||||||
|
|
||||||
assert other_user not in Notification.get_notified_from_activity(activity_two)
|
{enabled_receivers, _disabled_receivers} =
|
||||||
|
Notification.get_notified_from_activity(activity_two)
|
||||||
|
|
||||||
|
assert other_user not in enabled_receivers
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it does not send notification to mentioned users in announces" do
|
test "it does not send notification to mentioned users in announces" do
|
||||||
|
@ -472,7 +557,57 @@ test "it does not send notification to mentioned users in announces" do
|
||||||
|
|
||||||
{:ok, activity_two, _} = CommonAPI.repeat(activity_one.id, third_user)
|
{:ok, activity_two, _} = CommonAPI.repeat(activity_one.id, third_user)
|
||||||
|
|
||||||
assert other_user not in Notification.get_notified_from_activity(activity_two)
|
{enabled_receivers, _disabled_receivers} =
|
||||||
|
Notification.get_notified_from_activity(activity_two)
|
||||||
|
|
||||||
|
assert other_user not in enabled_receivers
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it returns blocking recipient in disabled recipients list" do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
{:ok, _user_relationship} = User.block(other_user, user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{other_user.nickname}!"})
|
||||||
|
|
||||||
|
{enabled_receivers, disabled_receivers} = Notification.get_notified_from_activity(activity)
|
||||||
|
|
||||||
|
assert [] == enabled_receivers
|
||||||
|
assert [other_user] == disabled_receivers
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it returns notification-muting recipient in disabled recipients list" do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
{:ok, _user_relationships} = User.mute(other_user, user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{other_user.nickname}!"})
|
||||||
|
|
||||||
|
{enabled_receivers, disabled_receivers} = Notification.get_notified_from_activity(activity)
|
||||||
|
|
||||||
|
assert [] == enabled_receivers
|
||||||
|
assert [other_user] == disabled_receivers
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it returns thread-muting recipient in disabled recipients list" do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{other_user.nickname}!"})
|
||||||
|
|
||||||
|
{:ok, _} = CommonAPI.add_mute(other_user, activity)
|
||||||
|
|
||||||
|
{:ok, same_context_activity} =
|
||||||
|
CommonAPI.post(user, %{
|
||||||
|
"status" => "hey-hey-hey @#{other_user.nickname}!",
|
||||||
|
"in_reply_to_status_id" => activity.id
|
||||||
|
})
|
||||||
|
|
||||||
|
{enabled_receivers, disabled_receivers} =
|
||||||
|
Notification.get_notified_from_activity(same_context_activity)
|
||||||
|
|
||||||
|
assert [other_user] == disabled_receivers
|
||||||
|
refute other_user in enabled_receivers
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -736,7 +871,7 @@ test "it doesn't return notifications for blocked user" do
|
||||||
assert Notification.for_user(user) == []
|
assert Notification.for_user(user) == []
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it doesn't return notificatitons for blocked domain" do
|
test "it doesn't return notifications for blocked domain" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
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")
|
||||||
|
|
|
@ -1283,6 +1283,21 @@ def get("https://patch.cx/users/rin", _, _, _) do
|
||||||
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/rin.json")}}
|
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/rin.json")}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get(
|
||||||
|
"https://channels.tests.funkwhale.audio/federation/music/uploads/42342395-0208-4fee-a38d-259a6dae0871",
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
_
|
||||||
|
) do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/funkwhale_audio.json")}}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get("https://channels.tests.funkwhale.audio/federation/actors/compositions", _, _, _) do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/funkwhale_channel.json")}}
|
||||||
|
end
|
||||||
|
|
||||||
def get("http://example.com/rel_me/error", _, _, _) do
|
def get("http://example.com/rel_me/error", _, _, _) do
|
||||||
{:ok, %Tesla.Env{status: 404, body: ""}}
|
{:ok, %Tesla.Env{status: 404, body: ""}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -86,7 +86,7 @@ test "returns invisible actor" do
|
||||||
{:ok, user: insert(:user)}
|
{:ok, user: insert(:user)}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "outgoing_relations_ap_ids/1", %{user: user} do
|
test "outgoing_relationships_ap_ids/1", %{user: user} do
|
||||||
rel_types = [:block, :mute, :notification_mute, :reblog_mute, :inverse_subscription]
|
rel_types = [:block, :mute, :notification_mute, :reblog_mute, :inverse_subscription]
|
||||||
|
|
||||||
ap_ids_by_rel =
|
ap_ids_by_rel =
|
||||||
|
@ -124,10 +124,10 @@ test "outgoing_relations_ap_ids/1", %{user: user} do
|
||||||
assert ap_ids_by_rel[:inverse_subscription] ==
|
assert ap_ids_by_rel[:inverse_subscription] ==
|
||||||
Enum.sort(Enum.map(User.subscriber_users(user), & &1.ap_id))
|
Enum.sort(Enum.map(User.subscriber_users(user), & &1.ap_id))
|
||||||
|
|
||||||
outgoing_relations_ap_ids = User.outgoing_relations_ap_ids(user, rel_types)
|
outgoing_relationships_ap_ids = User.outgoing_relationships_ap_ids(user, rel_types)
|
||||||
|
|
||||||
assert ap_ids_by_rel ==
|
assert ap_ids_by_rel ==
|
||||||
Enum.into(outgoing_relations_ap_ids, %{}, fn {k, v} -> {k, Enum.sort(v)} end)
|
Enum.into(outgoing_relationships_ap_ids, %{}, fn {k, v} -> {k, Enum.sort(v)} end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1425,6 +1425,12 @@ test "it creates a delete activity and deletes the original object" do
|
||||||
assert Repo.get(Object, object.id).data["type"] == "Tombstone"
|
assert Repo.get(Object, object.id).data["type"] == "Tombstone"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it doesn't fail when an activity was already deleted" do
|
||||||
|
{:ok, delete} = insert(:note_activity) |> Object.normalize() |> ActivityPub.delete()
|
||||||
|
|
||||||
|
assert {:ok, ^delete} = delete |> Object.normalize() |> ActivityPub.delete()
|
||||||
|
end
|
||||||
|
|
||||||
test "decrements user note count only for public activities" do
|
test "decrements user note count only for public activities" do
|
||||||
user = insert(:user, note_count: 10)
|
user = insert(:user, note_count: 10)
|
||||||
|
|
||||||
|
|
|
@ -3356,6 +3356,75 @@ test "returns log filtered by search", %{conn: conn, moderator: moderator} do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "GET /users/:nickname/credentials" do
|
||||||
|
test "gets the user credentials", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials")
|
||||||
|
|
||||||
|
response = assert json_response(conn, 200)
|
||||||
|
assert response["email"] == user.email
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns 403 if requested by a non-admin" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/pleroma/admin/users/#{user.nickname}/credentials")
|
||||||
|
|
||||||
|
assert json_response(conn, :forbidden)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "PATCH /users/:nickname/credentials" do
|
||||||
|
test "changes password and email", %{conn: conn, admin: admin} do
|
||||||
|
user = insert(:user)
|
||||||
|
assert user.password_reset_pending == false
|
||||||
|
|
||||||
|
conn =
|
||||||
|
patch(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials", %{
|
||||||
|
"password" => "new_password",
|
||||||
|
"email" => "new_email@example.com",
|
||||||
|
"name" => "new_name"
|
||||||
|
})
|
||||||
|
|
||||||
|
assert json_response(conn, 200) == %{"status" => "success"}
|
||||||
|
|
||||||
|
ObanHelpers.perform_all()
|
||||||
|
|
||||||
|
updated_user = User.get_by_id(user.id)
|
||||||
|
|
||||||
|
assert updated_user.email == "new_email@example.com"
|
||||||
|
assert updated_user.name == "new_name"
|
||||||
|
assert updated_user.password_hash != user.password_hash
|
||||||
|
assert updated_user.password_reset_pending == true
|
||||||
|
|
||||||
|
[log_entry2, log_entry1] = ModerationLog |> Repo.all() |> Enum.sort()
|
||||||
|
|
||||||
|
assert ModerationLog.get_log_entry_message(log_entry1) ==
|
||||||
|
"@#{admin.nickname} updated users: @#{user.nickname}"
|
||||||
|
|
||||||
|
assert ModerationLog.get_log_entry_message(log_entry2) ==
|
||||||
|
"@#{admin.nickname} forced password reset for users: @#{user.nickname}"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns 403 if requested by a non-admin" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> patch("/api/pleroma/admin/users/#{user.nickname}/credentials", %{
|
||||||
|
"password" => "new_password",
|
||||||
|
"email" => "new_email@example.com",
|
||||||
|
"name" => "new_name"
|
||||||
|
})
|
||||||
|
|
||||||
|
assert json_response(conn, :forbidden)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "PATCH /users/:nickname/force_password_reset" do
|
describe "PATCH /users/:nickname/force_password_reset" do
|
||||||
test "sets password_reset_pending to true", %{conn: conn} do
|
test "sets password_reset_pending to true", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
|
@ -76,7 +76,7 @@ test "updates the user's bio", %{conn: conn} do
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
patch(conn, "/api/v1/accounts/update_credentials", %{
|
patch(conn, "/api/v1/accounts/update_credentials", %{
|
||||||
"note" => "I drink #cofe with @#{user2.nickname}"
|
"note" => "I drink #cofe with @#{user2.nickname}\n\nsuya.."
|
||||||
})
|
})
|
||||||
|
|
||||||
assert user_data = json_response(conn, 200)
|
assert user_data = json_response(conn, 200)
|
||||||
|
@ -84,7 +84,7 @@ test "updates the user's bio", %{conn: conn} do
|
||||||
assert user_data["note"] ==
|
assert user_data["note"] ==
|
||||||
~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe">#cofe</a> with <span class="h-card"><a data-user="#{
|
~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe">#cofe</a> with <span class="h-card"><a data-user="#{
|
||||||
user2.id
|
user2.id
|
||||||
}" class="u-url mention" href="#{user2.ap_id}" rel="ugc">@<span>#{user2.nickname}</span></a></span>)
|
}" class="u-url mention" href="#{user2.ap_id}" rel="ugc">@<span>#{user2.nickname}</span></a></span><br/><br/>suya..)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "updates the user's locking status", %{conn: conn} do
|
test "updates the user's locking status", %{conn: conn} do
|
||||||
|
@ -118,6 +118,18 @@ test "updates the user's hide_followers status", %{conn: conn} do
|
||||||
assert user_data["pleroma"]["hide_followers"] == true
|
assert user_data["pleroma"]["hide_followers"] == true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "updates the user's discoverable status", %{conn: conn} do
|
||||||
|
assert %{"source" => %{"pleroma" => %{"discoverable" => true}}} =
|
||||||
|
conn
|
||||||
|
|> patch("/api/v1/accounts/update_credentials", %{discoverable: "true"})
|
||||||
|
|> json_response(:ok)
|
||||||
|
|
||||||
|
assert %{"source" => %{"pleroma" => %{"discoverable" => false}}} =
|
||||||
|
conn
|
||||||
|
|> patch("/api/v1/accounts/update_credentials", %{discoverable: "false"})
|
||||||
|
|> json_response(:ok)
|
||||||
|
end
|
||||||
|
|
||||||
test "updates the user's hide_followers_count and hide_follows_count", %{conn: conn} do
|
test "updates the user's hide_followers_count and hide_follows_count", %{conn: conn} do
|
||||||
conn =
|
conn =
|
||||||
patch(conn, "/api/v1/accounts/update_credentials", %{
|
patch(conn, "/api/v1/accounts/update_credentials", %{
|
||||||
|
|
|
@ -21,9 +21,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
|
||||||
setup do: oauth_access(["read:statuses"])
|
setup do: oauth_access(["read:statuses"])
|
||||||
|
|
||||||
test "the home timeline", %{user: user, conn: conn} do
|
test "the home timeline", %{user: user, conn: conn} do
|
||||||
following = insert(:user)
|
following = insert(:user, nickname: "followed")
|
||||||
|
third_user = insert(:user, nickname: "repeated")
|
||||||
|
|
||||||
{:ok, _activity} = CommonAPI.post(following, %{"status" => "test"})
|
{:ok, _activity} = CommonAPI.post(following, %{"status" => "post"})
|
||||||
|
{:ok, activity} = CommonAPI.post(third_user, %{"status" => "repeated post"})
|
||||||
|
{:ok, _, _} = CommonAPI.repeat(activity.id, following)
|
||||||
|
|
||||||
ret_conn = get(conn, "/api/v1/timelines/home")
|
ret_conn = get(conn, "/api/v1/timelines/home")
|
||||||
|
|
||||||
|
@ -31,9 +34,54 @@ test "the home timeline", %{user: user, conn: conn} do
|
||||||
|
|
||||||
{:ok, _user} = User.follow(user, following)
|
{:ok, _user} = User.follow(user, following)
|
||||||
|
|
||||||
conn = get(conn, "/api/v1/timelines/home")
|
ret_conn = get(conn, "/api/v1/timelines/home")
|
||||||
|
|
||||||
assert [%{"content" => "test"}] = json_response(conn, :ok)
|
assert [
|
||||||
|
%{
|
||||||
|
"reblog" => %{
|
||||||
|
"content" => "repeated post",
|
||||||
|
"account" => %{
|
||||||
|
"pleroma" => %{
|
||||||
|
"relationship" => %{"following" => false, "followed_by" => false}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"account" => %{"pleroma" => %{"relationship" => %{"following" => true}}}
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"content" => "post",
|
||||||
|
"account" => %{
|
||||||
|
"acct" => "followed",
|
||||||
|
"pleroma" => %{"relationship" => %{"following" => true}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
] = json_response(ret_conn, :ok)
|
||||||
|
|
||||||
|
{:ok, _user} = User.follow(third_user, user)
|
||||||
|
|
||||||
|
ret_conn = get(conn, "/api/v1/timelines/home")
|
||||||
|
|
||||||
|
assert [
|
||||||
|
%{
|
||||||
|
"reblog" => %{
|
||||||
|
"content" => "repeated post",
|
||||||
|
"account" => %{
|
||||||
|
"acct" => "repeated",
|
||||||
|
"pleroma" => %{
|
||||||
|
"relationship" => %{"following" => false, "followed_by" => true}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"account" => %{"pleroma" => %{"relationship" => %{"following" => true}}}
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"content" => "post",
|
||||||
|
"account" => %{
|
||||||
|
"acct" => "followed",
|
||||||
|
"pleroma" => %{"relationship" => %{"following" => true}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
] = json_response(ret_conn, :ok)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "the home timeline when the direct messages are excluded", %{user: user, conn: conn} do
|
test "the home timeline when the direct messages are excluded", %{user: user, conn: conn} do
|
||||||
|
|
|
@ -4,8 +4,11 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
|
defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.UserRelationship
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.MastodonAPI.AccountView
|
alias Pleroma.Web.MastodonAPI.AccountView
|
||||||
|
|
||||||
|
@ -32,7 +35,8 @@ test "Represent a user account" do
|
||||||
background: background_image,
|
background: background_image,
|
||||||
nickname: "shp@shitposter.club",
|
nickname: "shp@shitposter.club",
|
||||||
name: ":karjalanpiirakka: shp",
|
name: ":karjalanpiirakka: shp",
|
||||||
bio: "<script src=\"invalid-html\"></script><span>valid html</span>",
|
bio:
|
||||||
|
"<script src=\"invalid-html\"></script><span>valid html</span>. a<br>b<br/>c<br >d<br />f",
|
||||||
inserted_at: ~N[2017-08-15 15:47:06.597036]
|
inserted_at: ~N[2017-08-15 15:47:06.597036]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -46,7 +50,7 @@ test "Represent a user account" do
|
||||||
followers_count: 3,
|
followers_count: 3,
|
||||||
following_count: 0,
|
following_count: 0,
|
||||||
statuses_count: 5,
|
statuses_count: 5,
|
||||||
note: "<span>valid html</span>",
|
note: "<span>valid html</span>. a<br/>b<br/>c<br/>d<br/>f",
|
||||||
url: user.ap_id,
|
url: user.ap_id,
|
||||||
avatar: "http://localhost:4001/images/avi.png",
|
avatar: "http://localhost:4001/images/avi.png",
|
||||||
avatar_static: "http://localhost:4001/images/avi.png",
|
avatar_static: "http://localhost:4001/images/avi.png",
|
||||||
|
@ -63,7 +67,7 @@ test "Represent a user account" do
|
||||||
fields: [],
|
fields: [],
|
||||||
bot: false,
|
bot: false,
|
||||||
source: %{
|
source: %{
|
||||||
note: "valid html",
|
note: "valid html. a\nb\nc\nd\nf",
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
pleroma: %{
|
pleroma: %{
|
||||||
actor_type: "Person",
|
actor_type: "Person",
|
||||||
|
@ -181,6 +185,29 @@ test "Represent a smaller mention" do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "relationship" do
|
describe "relationship" do
|
||||||
|
defp test_relationship_rendering(user, other_user, expected_result) do
|
||||||
|
opts = %{user: user, target: other_user, relationships: nil}
|
||||||
|
assert expected_result == AccountView.render("relationship.json", opts)
|
||||||
|
|
||||||
|
relationships_opt = UserRelationship.view_relationships_option(user, [other_user])
|
||||||
|
opts = Map.put(opts, :relationships, relationships_opt)
|
||||||
|
assert expected_result == AccountView.render("relationship.json", opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
@blank_response %{
|
||||||
|
following: false,
|
||||||
|
followed_by: false,
|
||||||
|
blocking: false,
|
||||||
|
blocked_by: false,
|
||||||
|
muting: false,
|
||||||
|
muting_notifications: false,
|
||||||
|
subscribing: false,
|
||||||
|
requested: false,
|
||||||
|
domain_blocking: false,
|
||||||
|
showing_reblogs: true,
|
||||||
|
endorsed: false
|
||||||
|
}
|
||||||
|
|
||||||
test "represent a relationship for the following and followed user" do
|
test "represent a relationship for the following and followed user" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
|
@ -191,23 +218,21 @@ test "represent a relationship for the following and followed user" do
|
||||||
{:ok, _user_relationships} = User.mute(user, other_user, true)
|
{:ok, _user_relationships} = User.mute(user, other_user, true)
|
||||||
{:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, other_user)
|
{:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, other_user)
|
||||||
|
|
||||||
expected = %{
|
expected =
|
||||||
id: to_string(other_user.id),
|
Map.merge(
|
||||||
following: true,
|
@blank_response,
|
||||||
followed_by: true,
|
%{
|
||||||
blocking: false,
|
following: true,
|
||||||
blocked_by: false,
|
followed_by: true,
|
||||||
muting: true,
|
muting: true,
|
||||||
muting_notifications: true,
|
muting_notifications: true,
|
||||||
subscribing: true,
|
subscribing: true,
|
||||||
requested: false,
|
showing_reblogs: false,
|
||||||
domain_blocking: false,
|
id: to_string(other_user.id)
|
||||||
showing_reblogs: false,
|
}
|
||||||
endorsed: false
|
)
|
||||||
}
|
|
||||||
|
|
||||||
assert expected ==
|
test_relationship_rendering(user, other_user, expected)
|
||||||
AccountView.render("relationship.json", %{user: user, target: other_user})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "represent a relationship for the blocking and blocked user" do
|
test "represent a relationship for the blocking and blocked user" do
|
||||||
|
@ -219,23 +244,13 @@ test "represent a relationship for the blocking and blocked user" do
|
||||||
{:ok, _user_relationship} = User.block(user, other_user)
|
{:ok, _user_relationship} = User.block(user, other_user)
|
||||||
{:ok, _user_relationship} = User.block(other_user, user)
|
{:ok, _user_relationship} = User.block(other_user, user)
|
||||||
|
|
||||||
expected = %{
|
expected =
|
||||||
id: to_string(other_user.id),
|
Map.merge(
|
||||||
following: false,
|
@blank_response,
|
||||||
followed_by: false,
|
%{following: false, blocking: true, blocked_by: true, id: to_string(other_user.id)}
|
||||||
blocking: true,
|
)
|
||||||
blocked_by: true,
|
|
||||||
muting: false,
|
|
||||||
muting_notifications: false,
|
|
||||||
subscribing: false,
|
|
||||||
requested: false,
|
|
||||||
domain_blocking: false,
|
|
||||||
showing_reblogs: true,
|
|
||||||
endorsed: false
|
|
||||||
}
|
|
||||||
|
|
||||||
assert expected ==
|
test_relationship_rendering(user, other_user, expected)
|
||||||
AccountView.render("relationship.json", %{user: user, target: other_user})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "represent a relationship for the user blocking a domain" do
|
test "represent a relationship for the user blocking a domain" do
|
||||||
|
@ -244,8 +259,13 @@ test "represent a relationship for the user blocking a domain" do
|
||||||
|
|
||||||
{:ok, user} = User.block_domain(user, "bad.site")
|
{:ok, user} = User.block_domain(user, "bad.site")
|
||||||
|
|
||||||
assert %{domain_blocking: true, blocking: false} =
|
expected =
|
||||||
AccountView.render("relationship.json", %{user: user, target: other_user})
|
Map.merge(
|
||||||
|
@blank_response,
|
||||||
|
%{domain_blocking: true, blocking: false, id: to_string(other_user.id)}
|
||||||
|
)
|
||||||
|
|
||||||
|
test_relationship_rendering(user, other_user, expected)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "represent a relationship for the user with a pending follow request" do
|
test "represent a relationship for the user with a pending follow request" do
|
||||||
|
@ -256,23 +276,13 @@ test "represent a relationship for the user with a pending follow request" do
|
||||||
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)
|
||||||
|
|
||||||
expected = %{
|
expected =
|
||||||
id: to_string(other_user.id),
|
Map.merge(
|
||||||
following: false,
|
@blank_response,
|
||||||
followed_by: false,
|
%{requested: true, following: false, id: to_string(other_user.id)}
|
||||||
blocking: false,
|
)
|
||||||
blocked_by: false,
|
|
||||||
muting: false,
|
|
||||||
muting_notifications: false,
|
|
||||||
subscribing: false,
|
|
||||||
requested: true,
|
|
||||||
domain_blocking: false,
|
|
||||||
showing_reblogs: true,
|
|
||||||
endorsed: false
|
|
||||||
}
|
|
||||||
|
|
||||||
assert expected ==
|
test_relationship_rendering(user, other_user, expected)
|
||||||
AccountView.render("relationship.json", %{user: user, target: other_user})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,21 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
defp test_notifications_rendering(notifications, user, expected_result) do
|
||||||
|
result = NotificationView.render("index.json", %{notifications: notifications, for: user})
|
||||||
|
|
||||||
|
assert expected_result == result
|
||||||
|
|
||||||
|
result =
|
||||||
|
NotificationView.render("index.json", %{
|
||||||
|
notifications: notifications,
|
||||||
|
for: user,
|
||||||
|
relationships: nil
|
||||||
|
})
|
||||||
|
|
||||||
|
assert expected_result == result
|
||||||
|
end
|
||||||
|
|
||||||
test "Mention notification" do
|
test "Mention notification" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
mentioned_user = insert(:user)
|
mentioned_user = insert(:user)
|
||||||
|
@ -32,10 +47,7 @@ test "Mention notification" do
|
||||||
created_at: Utils.to_masto_date(notification.inserted_at)
|
created_at: Utils.to_masto_date(notification.inserted_at)
|
||||||
}
|
}
|
||||||
|
|
||||||
result =
|
test_notifications_rendering([notification], mentioned_user, [expected])
|
||||||
NotificationView.render("index.json", %{notifications: [notification], for: mentioned_user})
|
|
||||||
|
|
||||||
assert [expected] == result
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Favourite notification" do
|
test "Favourite notification" do
|
||||||
|
@ -55,9 +67,7 @@ test "Favourite notification" do
|
||||||
created_at: Utils.to_masto_date(notification.inserted_at)
|
created_at: Utils.to_masto_date(notification.inserted_at)
|
||||||
}
|
}
|
||||||
|
|
||||||
result = NotificationView.render("index.json", %{notifications: [notification], for: user})
|
test_notifications_rendering([notification], user, [expected])
|
||||||
|
|
||||||
assert [expected] == result
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Reblog notification" do
|
test "Reblog notification" do
|
||||||
|
@ -77,9 +87,7 @@ test "Reblog notification" do
|
||||||
created_at: Utils.to_masto_date(notification.inserted_at)
|
created_at: Utils.to_masto_date(notification.inserted_at)
|
||||||
}
|
}
|
||||||
|
|
||||||
result = NotificationView.render("index.json", %{notifications: [notification], for: user})
|
test_notifications_rendering([notification], user, [expected])
|
||||||
|
|
||||||
assert [expected] == result
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Follow notification" do
|
test "Follow notification" do
|
||||||
|
@ -96,16 +104,12 @@ test "Follow notification" do
|
||||||
created_at: Utils.to_masto_date(notification.inserted_at)
|
created_at: Utils.to_masto_date(notification.inserted_at)
|
||||||
}
|
}
|
||||||
|
|
||||||
result =
|
test_notifications_rendering([notification], followed, [expected])
|
||||||
NotificationView.render("index.json", %{notifications: [notification], for: followed})
|
|
||||||
|
|
||||||
assert [expected] == result
|
|
||||||
|
|
||||||
User.perform(:delete, follower)
|
User.perform(:delete, follower)
|
||||||
notification = Notification |> Repo.one() |> Repo.preload(:activity)
|
notification = Notification |> Repo.one() |> Repo.preload(:activity)
|
||||||
|
|
||||||
assert [] ==
|
test_notifications_rendering([notification], followed, [])
|
||||||
NotificationView.render("index.json", %{notifications: [notification], for: followed})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag capture_log: true
|
@tag capture_log: true
|
||||||
|
@ -144,8 +148,7 @@ test "Move notification" do
|
||||||
created_at: Utils.to_masto_date(notification.inserted_at)
|
created_at: Utils.to_masto_date(notification.inserted_at)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert [expected] ==
|
test_notifications_rendering([notification], follower, [expected])
|
||||||
NotificationView.render("index.json", %{notifications: [notification], for: follower})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "EmojiReact notification" do
|
test "EmojiReact notification" do
|
||||||
|
@ -171,7 +174,6 @@ test "EmojiReact notification" do
|
||||||
created_at: Utils.to_masto_date(notification.inserted_at)
|
created_at: Utils.to_masto_date(notification.inserted_at)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert expected ==
|
test_notifications_rendering([notification], user, [expected])
|
||||||
NotificationView.render("show.json", %{notification: notification, for: user})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,10 +12,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.UserRelationship
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.CommonAPI.Utils
|
alias Pleroma.Web.CommonAPI.Utils
|
||||||
alias Pleroma.Web.MastodonAPI.AccountView
|
alias Pleroma.Web.MastodonAPI.AccountView
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
import Tesla.Mock
|
import Tesla.Mock
|
||||||
|
|
||||||
|
@ -229,12 +231,21 @@ test "tells if the message is muted for some reason" do
|
||||||
{:ok, _user_relationships} = User.mute(user, other_user)
|
{:ok, _user_relationships} = User.mute(user, other_user)
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"})
|
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"})
|
||||||
status = StatusView.render("show.json", %{activity: activity})
|
|
||||||
|
|
||||||
|
relationships_opt = UserRelationship.view_relationships_option(user, [other_user])
|
||||||
|
|
||||||
|
opts = %{activity: activity}
|
||||||
|
status = StatusView.render("show.json", opts)
|
||||||
assert status.muted == false
|
assert status.muted == false
|
||||||
|
|
||||||
status = StatusView.render("show.json", %{activity: activity, for: user})
|
status = StatusView.render("show.json", Map.put(opts, :relationships, relationships_opt))
|
||||||
|
assert status.muted == false
|
||||||
|
|
||||||
|
for_opts = %{activity: activity, for: user}
|
||||||
|
status = StatusView.render("show.json", for_opts)
|
||||||
|
assert status.muted == true
|
||||||
|
|
||||||
|
status = StatusView.render("show.json", Map.put(for_opts, :relationships, relationships_opt))
|
||||||
assert status.muted == true
|
assert status.muted == true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -437,6 +448,22 @@ test "a peertube video" do
|
||||||
assert length(represented[:media_attachments]) == 1
|
assert length(represented[:media_attachments]) == 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "funkwhale audio" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, object} =
|
||||||
|
Pleroma.Object.Fetcher.fetch_object_from_id(
|
||||||
|
"https://channels.tests.funkwhale.audio/federation/music/uploads/42342395-0208-4fee-a38d-259a6dae0871"
|
||||||
|
)
|
||||||
|
|
||||||
|
%Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
|
||||||
|
|
||||||
|
represented = StatusView.render("show.json", %{for: user, activity: activity})
|
||||||
|
|
||||||
|
assert represented[:id] == to_string(activity.id)
|
||||||
|
assert length(represented[:media_attachments]) == 1
|
||||||
|
end
|
||||||
|
|
||||||
test "a Mobilizon event" do
|
test "a Mobilizon event" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
|
|
|
@ -575,7 +575,7 @@ test "redirects with oauth authorization, " <>
|
||||||
# In case scope param is missing, expecting _all_ app-supported scopes to be granted
|
# In case scope param is missing, expecting _all_ app-supported scopes to be granted
|
||||||
for user <- [non_admin, admin],
|
for user <- [non_admin, admin],
|
||||||
{requested_scopes, expected_scopes} <-
|
{requested_scopes, expected_scopes} <-
|
||||||
%{scopes_subset => scopes_subset, nil => app_scopes} do
|
%{scopes_subset => scopes_subset, nil: app_scopes} do
|
||||||
conn =
|
conn =
|
||||||
post(
|
post(
|
||||||
build_conn(),
|
build_conn(),
|
||||||
|
|
Loading…
Reference in a new issue