Merge remote-tracking branch 'origin/develop' into merge-ogp-twitter-parsers
This commit is contained in:
commit
58e4e3db8b
|
@ -50,6 +50,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Filtering of push notifications on activities from blocked domains
|
- Filtering of push notifications on activities from blocked domains
|
||||||
- Resolving Peertube accounts with Webfinger
|
- Resolving Peertube accounts with Webfinger
|
||||||
- `blob:` urls not being allowed by connect-src CSP
|
- `blob:` urls not being allowed by connect-src CSP
|
||||||
|
- Mastodon API: fix `GET /api/v1/notifications` not returning the full result set
|
||||||
|
|
||||||
## [Unreleased (patch)]
|
## [Unreleased (patch)]
|
||||||
|
|
||||||
|
|
|
@ -1623,14 +1623,12 @@
|
||||||
# %{
|
# %{
|
||||||
# group: :pleroma,
|
# group: :pleroma,
|
||||||
# key: :mrf_user_allowlist,
|
# key: :mrf_user_allowlist,
|
||||||
# type: :group,
|
# type: :map,
|
||||||
# description:
|
# description:
|
||||||
# "The keys in this section are the domain names that the policy should apply to." <>
|
# "The keys in this section are the domain names that the policy should apply to." <>
|
||||||
# " Each key should be assigned a list of users that should be allowed through by their ActivityPub ID",
|
# " Each key should be assigned a list of users that should be allowed through by their ActivityPub ID",
|
||||||
# children: [
|
|
||||||
# ["example.org": ["https://example.org/users/admin"]],
|
|
||||||
# suggestions: [
|
# suggestions: [
|
||||||
# ["example.org": ["https://example.org/users/admin"]]
|
# %{"example.org" => ["https://example.org/users/admin"]}
|
||||||
# ]
|
# ]
|
||||||
# ]
|
# ]
|
||||||
# },
|
# },
|
||||||
|
|
|
@ -138,8 +138,9 @@ their ActivityPub ID.
|
||||||
An example:
|
An example:
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
config :pleroma, :mrf_user_allowlist,
|
config :pleroma, :mrf_user_allowlist, %{
|
||||||
"example.org": ["https://example.org/users/admin"]
|
"example.org" => ["https://example.org/users/admin"]
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### :mrf_object_age
|
#### :mrf_object_age
|
||||||
|
|
|
@ -37,18 +37,17 @@ server {
|
||||||
|
|
||||||
listen 443 ssl http2;
|
listen 443 ssl http2;
|
||||||
listen [::]:443 ssl http2;
|
listen [::]:443 ssl http2;
|
||||||
ssl_session_timeout 5m;
|
ssl_session_timeout 1d;
|
||||||
|
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
|
||||||
|
ssl_session_tickets off;
|
||||||
|
|
||||||
ssl_trusted_certificate /etc/letsencrypt/live/example.tld/chain.pem;
|
ssl_trusted_certificate /etc/letsencrypt/live/example.tld/chain.pem;
|
||||||
ssl_certificate /etc/letsencrypt/live/example.tld/fullchain.pem;
|
ssl_certificate /etc/letsencrypt/live/example.tld/fullchain.pem;
|
||||||
ssl_certificate_key /etc/letsencrypt/live/example.tld/privkey.pem;
|
ssl_certificate_key /etc/letsencrypt/live/example.tld/privkey.pem;
|
||||||
|
|
||||||
# Add TLSv1.0 to support older devices
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
ssl_protocols TLSv1.2;
|
|
||||||
# Uncomment line below if you want to support older devices (Before Android 4.4.2, IE 8, etc.)
|
|
||||||
# ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES";
|
|
||||||
ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
|
ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
|
||||||
ssl_prefer_server_ciphers on;
|
ssl_prefer_server_ciphers off;
|
||||||
# In case of an old server with an OpenSSL version of 1.0.2 or below,
|
# In case of an old server with an OpenSSL version of 1.0.2 or below,
|
||||||
# leave only prime256v1 or comment out the following line.
|
# leave only prime256v1 or comment out the following line.
|
||||||
ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1;
|
ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1;
|
||||||
|
|
|
@ -31,6 +31,10 @@ defmodule Pleroma.Activity do
|
||||||
field(:recipients, {:array, :string}, default: [])
|
field(:recipients, {:array, :string}, default: [])
|
||||||
field(:thread_muted?, :boolean, virtual: true)
|
field(:thread_muted?, :boolean, virtual: true)
|
||||||
|
|
||||||
|
# A field that can be used if you need to join some kind of other
|
||||||
|
# id to order / paginate this field by
|
||||||
|
field(:pagination_id, :string, virtual: true)
|
||||||
|
|
||||||
# This is a fake relation,
|
# This is a fake relation,
|
||||||
# do not use outside of with_preloaded_user_actor/with_joined_user_actor
|
# do not use outside of with_preloaded_user_actor/with_joined_user_actor
|
||||||
has_one(:user_actor, User, on_delete: :nothing, foreign_key: :id)
|
has_one(:user_actor, User, on_delete: :nothing, foreign_key: :id)
|
||||||
|
|
|
@ -4,9 +4,10 @@
|
||||||
|
|
||||||
defmodule Pleroma.Config.DeprecationWarnings do
|
defmodule Pleroma.Config.DeprecationWarnings do
|
||||||
require Logger
|
require Logger
|
||||||
|
alias Pleroma.Config
|
||||||
|
|
||||||
def check_hellthread_threshold do
|
def check_hellthread_threshold do
|
||||||
if Pleroma.Config.get([:mrf_hellthread, :threshold]) do
|
if Config.get([:mrf_hellthread, :threshold]) do
|
||||||
Logger.warn("""
|
Logger.warn("""
|
||||||
!!!DEPRECATION WARNING!!!
|
!!!DEPRECATION WARNING!!!
|
||||||
You are using the old configuration mechanism for the hellthread filter. Please check config.md.
|
You are using the old configuration mechanism for the hellthread filter. Please check config.md.
|
||||||
|
@ -14,7 +15,29 @@ def check_hellthread_threshold do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def mrf_user_allowlist do
|
||||||
|
config = Config.get(:mrf_user_allowlist)
|
||||||
|
|
||||||
|
if config && Enum.any?(config, fn {k, _} -> is_atom(k) end) do
|
||||||
|
rewritten =
|
||||||
|
Enum.reduce(Config.get(:mrf_user_allowlist), Map.new(), fn {k, v}, acc ->
|
||||||
|
Map.put(acc, to_string(k), v)
|
||||||
|
end)
|
||||||
|
|
||||||
|
Config.put(:mrf_user_allowlist, rewritten)
|
||||||
|
|
||||||
|
Logger.error("""
|
||||||
|
!!!DEPRECATION WARNING!!!
|
||||||
|
As of Pleroma 2.0.7, the `mrf_user_allowlist` setting changed of format.
|
||||||
|
Pleroma 2.1 will remove support for the old format. Please change your configuration to match this:
|
||||||
|
|
||||||
|
config :pleroma, :mrf_user_allowlist, #{inspect(rewritten, pretty: true)}
|
||||||
|
""")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def warn do
|
def warn do
|
||||||
check_hellthread_threshold()
|
check_hellthread_threshold()
|
||||||
|
mrf_user_allowlist()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -166,8 +166,16 @@ defp exclude_visibility(query, %{exclude_visibilities: visibility})
|
||||||
query
|
query
|
||||||
|> join(:left, [n, a], mutated_activity in Pleroma.Activity,
|
|> join(:left, [n, a], mutated_activity in Pleroma.Activity,
|
||||||
on:
|
on:
|
||||||
fragment("?->>'context'", a.data) ==
|
fragment(
|
||||||
fragment("?->>'context'", mutated_activity.data) and
|
"COALESCE((?->'object')->>'id', ?->>'object')",
|
||||||
|
a.data,
|
||||||
|
a.data
|
||||||
|
) ==
|
||||||
|
fragment(
|
||||||
|
"COALESCE((?->'object')->>'id', ?->>'object')",
|
||||||
|
mutated_activity.data,
|
||||||
|
mutated_activity.data
|
||||||
|
) and
|
||||||
fragment("(?->>'type' = 'Like' or ?->>'type' = 'Announce')", a.data, a.data) and
|
fragment("(?->>'type' = 'Like' or ?->>'type' = 'Announce')", a.data, a.data) and
|
||||||
fragment("?->>'type'", mutated_activity.data) == "Create",
|
fragment("?->>'type'", mutated_activity.data) == "Create",
|
||||||
as: :mutated_activity
|
as: :mutated_activity
|
||||||
|
@ -541,6 +549,7 @@ def exclude_thread_muter_ap_ids(ap_ids, %Activity{} = activity) do
|
||||||
def skip?(%Activity{} = activity, %User{} = user) do
|
def skip?(%Activity{} = activity, %User{} = user) do
|
||||||
[
|
[
|
||||||
:self,
|
:self,
|
||||||
|
:invisible,
|
||||||
:followers,
|
:followers,
|
||||||
:follows,
|
:follows,
|
||||||
:non_followers,
|
:non_followers,
|
||||||
|
@ -557,6 +566,12 @@ def skip?(:self, %Activity{} = activity, %User{} = user) do
|
||||||
activity.data["actor"] == user.ap_id
|
activity.data["actor"] == user.ap_id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def skip?(:invisible, %Activity{} = activity, _) do
|
||||||
|
actor = activity.data["actor"]
|
||||||
|
user = User.get_cached_by_ap_id(actor)
|
||||||
|
User.invisible?(user)
|
||||||
|
end
|
||||||
|
|
||||||
def skip?(
|
def skip?(
|
||||||
:followers,
|
:followers,
|
||||||
%Activity{} = activity,
|
%Activity{} = activity,
|
||||||
|
|
|
@ -1488,6 +1488,7 @@ def perform(:delete, %User{} = user) do
|
||||||
end)
|
end)
|
||||||
|
|
||||||
delete_user_activities(user)
|
delete_user_activities(user)
|
||||||
|
delete_notifications_from_user_activities(user)
|
||||||
|
|
||||||
delete_outgoing_pending_follow_requests(user)
|
delete_outgoing_pending_follow_requests(user)
|
||||||
|
|
||||||
|
@ -1576,6 +1577,13 @@ def follow_import(%User{} = follower, followed_identifiers)
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
|
||||||
|
Notification
|
||||||
|
|> join(:inner, [n], activity in assoc(n, :activity))
|
||||||
|
|> where([n, a], fragment("? = ?", a.actor, ^ap_id))
|
||||||
|
|> Repo.delete_all()
|
||||||
|
end
|
||||||
|
|
||||||
def delete_user_activities(%User{ap_id: ap_id} = user) do
|
def delete_user_activities(%User{ap_id: ap_id} = user) do
|
||||||
ap_id
|
ap_id
|
||||||
|> Activity.Queries.by_actor()
|
|> Activity.Queries.by_actor()
|
||||||
|
|
|
@ -32,25 +32,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
require Logger
|
require Logger
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
|
|
||||||
# For Announce activities, we filter the recipients based on following status for any actors
|
|
||||||
# that match actual users. See issue #164 for more information about why this is necessary.
|
|
||||||
defp get_recipients(%{"type" => "Announce"} = data) do
|
|
||||||
to = Map.get(data, "to", [])
|
|
||||||
cc = Map.get(data, "cc", [])
|
|
||||||
bcc = Map.get(data, "bcc", [])
|
|
||||||
actor = User.get_cached_by_ap_id(data["actor"])
|
|
||||||
|
|
||||||
recipients =
|
|
||||||
Enum.filter(Enum.concat([to, cc, bcc]), fn recipient ->
|
|
||||||
case User.get_cached_by_ap_id(recipient) do
|
|
||||||
nil -> true
|
|
||||||
user -> User.following?(user, actor)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
{recipients, to, cc}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp get_recipients(%{"type" => "Create"} = data) do
|
defp get_recipients(%{"type" => "Create"} = data) do
|
||||||
to = Map.get(data, "to", [])
|
to = Map.get(data, "to", [])
|
||||||
cc = Map.get(data, "cc", [])
|
cc = Map.get(data, "cc", [])
|
||||||
|
@ -721,6 +702,26 @@ defp user_activities_recipients(%{reading_user: reading_user}) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp restrict_announce_object_actor(_query, %{announce_filtering_user: _, skip_preload: true}) do
|
||||||
|
raise "Can't use the child object without preloading!"
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_announce_object_actor(query, %{announce_filtering_user: %{ap_id: actor}}) do
|
||||||
|
from(
|
||||||
|
[activity, object] in query,
|
||||||
|
where:
|
||||||
|
fragment(
|
||||||
|
"?->>'type' != ? or ?->>'actor' != ?",
|
||||||
|
activity.data,
|
||||||
|
"Announce",
|
||||||
|
object.data,
|
||||||
|
^actor
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_announce_object_actor(query, _), do: query
|
||||||
|
|
||||||
defp restrict_since(query, %{since_id: ""}), do: query
|
defp restrict_since(query, %{since_id: ""}), do: query
|
||||||
|
|
||||||
defp restrict_since(query, %{since_id: since_id}) do
|
defp restrict_since(query, %{since_id: since_id}) do
|
||||||
|
@ -1144,6 +1145,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|
||||||
|> restrict_pinned(opts)
|
|> restrict_pinned(opts)
|
||||||
|> restrict_muted_reblogs(restrict_muted_reblogs_opts)
|
|> restrict_muted_reblogs(restrict_muted_reblogs_opts)
|
||||||
|> restrict_instance(opts)
|
|> restrict_instance(opts)
|
||||||
|
|> restrict_announce_object_actor(opts)
|
||||||
|> Activity.restrict_deactivated_users()
|
|> Activity.restrict_deactivated_users()
|
||||||
|> exclude_poll_votes(opts)
|
|> exclude_poll_votes(opts)
|
||||||
|> exclude_chat_messages(opts)
|
|> exclude_chat_messages(opts)
|
||||||
|
@ -1170,12 +1172,11 @@ def fetch_favourites(user, params \\ %{}, pagination \\ :keyset) do
|
||||||
|> Activity.Queries.by_type("Like")
|
|> Activity.Queries.by_type("Like")
|
||||||
|> Activity.with_joined_object()
|
|> Activity.with_joined_object()
|
||||||
|> Object.with_joined_activity()
|
|> Object.with_joined_activity()
|
||||||
|> select([_like, object, activity], %{activity | object: object})
|
|> select([like, object, activity], %{activity | object: object, pagination_id: like.id})
|
||||||
|> order_by([like, _, _], desc_nulls_last: like.id)
|
|> order_by([like, _, _], desc_nulls_last: like.id)
|
||||||
|> Pagination.fetch_paginated(
|
|> Pagination.fetch_paginated(
|
||||||
Map.merge(params, %{skip_order: true}),
|
Map.merge(params, %{skip_order: true}),
|
||||||
pagination,
|
pagination
|
||||||
:object_activity
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ def filter(%{"actor" => actor} = object) do
|
||||||
|
|
||||||
allow_list =
|
allow_list =
|
||||||
Config.get(
|
Config.get(
|
||||||
[:mrf_user_allowlist, String.to_atom(actor_info.host)],
|
[:mrf_user_allowlist, actor_info.host],
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -333,7 +333,8 @@ def favourites_operation do
|
||||||
%Operation{
|
%Operation{
|
||||||
tags: ["Statuses"],
|
tags: ["Statuses"],
|
||||||
summary: "Favourited statuses",
|
summary: "Favourited statuses",
|
||||||
description: "Statuses the user has favourited",
|
description:
|
||||||
|
"Statuses the user has favourited. Please note that you have to use the link headers to paginate this. You can not build the query parameters yourself.",
|
||||||
operationId: "StatusController.favourites",
|
operationId: "StatusController.favourites",
|
||||||
parameters: pagination_params(),
|
parameters: pagination_params(),
|
||||||
security: [%{"oAuth" => ["read:favourites"]}],
|
security: [%{"oAuth" => ["read:favourites"]}],
|
||||||
|
|
|
@ -57,35 +57,36 @@ def add_link_headers(conn, activities, extra_params) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@id_keys Pagination.page_keys() -- ["limit", "order"]
|
||||||
|
defp build_pagination_fields(conn, min_id, max_id, extra_params) do
|
||||||
|
params =
|
||||||
|
conn.params
|
||||||
|
|> Map.drop(Map.keys(conn.path_params))
|
||||||
|
|> Map.merge(extra_params)
|
||||||
|
|> Map.drop(@id_keys)
|
||||||
|
|
||||||
|
%{
|
||||||
|
"next" => current_url(conn, Map.put(params, :max_id, max_id)),
|
||||||
|
"prev" => current_url(conn, Map.put(params, :min_id, min_id)),
|
||||||
|
"id" => current_url(conn)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def get_pagination_fields(conn, activities, extra_params \\ %{}) do
|
def get_pagination_fields(conn, activities, extra_params \\ %{}) do
|
||||||
case List.last(activities) do
|
case List.last(activities) do
|
||||||
%{id: max_id} ->
|
%{pagination_id: max_id} when not is_nil(max_id) ->
|
||||||
params =
|
%{pagination_id: min_id} =
|
||||||
conn.params
|
|
||||||
|> Map.drop(Map.keys(conn.path_params))
|
|
||||||
|> Map.merge(extra_params)
|
|
||||||
|> Map.drop(Pagination.page_keys() -- ["limit", "order"])
|
|
||||||
|
|
||||||
min_id =
|
|
||||||
activities
|
activities
|
||||||
|> List.first()
|
|> List.first()
|
||||||
|> Map.get(:id)
|
|
||||||
|
|
||||||
fields = %{
|
build_pagination_fields(conn, min_id, max_id, extra_params)
|
||||||
"next" => current_url(conn, Map.put(params, :max_id, max_id)),
|
|
||||||
"prev" => current_url(conn, Map.put(params, :min_id, min_id))
|
|
||||||
}
|
|
||||||
|
|
||||||
# Generating an `id` without already present pagination keys would
|
%{id: max_id} ->
|
||||||
# need a query-restriction with an `q.id >= ^id` or `q.id <= ^id`
|
%{id: min_id} =
|
||||||
# instead of the `q.id > ^min_id` and `q.id < ^max_id`.
|
activities
|
||||||
# This is because we only have ids present inside of the page, while
|
|> List.first()
|
||||||
# `min_id`, `since_id` and `max_id` requires to know one outside of it.
|
|
||||||
if Map.take(conn.params, Pagination.page_keys() -- ["limit", "order"]) != [] do
|
build_pagination_fields(conn, min_id, max_id, extra_params)
|
||||||
Map.put(fields, "id", current_url(conn, conn.params))
|
|
||||||
else
|
|
||||||
fields
|
|
||||||
end
|
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
%{}
|
%{}
|
||||||
|
|
|
@ -48,6 +48,7 @@ def home(%{assigns: %{user: user}} = conn, params) do
|
||||||
|> Map.put(:blocking_user, user)
|
|> Map.put(:blocking_user, user)
|
||||||
|> Map.put(:muting_user, user)
|
|> Map.put(:muting_user, user)
|
||||||
|> Map.put(:reply_filtering_user, user)
|
|> Map.put(:reply_filtering_user, user)
|
||||||
|
|> Map.put(:announce_filtering_user, user)
|
||||||
|> Map.put(:user, user)
|
|> Map.put(:user, user)
|
||||||
|
|
||||||
activities =
|
activities =
|
||||||
|
|
|
@ -46,6 +46,7 @@ def render("index.json", %{notifications: notifications, for: reading_user} = op
|
||||||
activities
|
activities
|
||||||
|> Enum.filter(&(&1.data["type"] == "Move"))
|
|> Enum.filter(&(&1.data["type"] == "Move"))
|
||||||
|> Enum.map(&User.get_cached_by_ap_id(&1.data["target"]))
|
|> Enum.map(&User.get_cached_by_ap_id(&1.data["target"]))
|
||||||
|
|> Enum.filter(& &1)
|
||||||
|
|
||||||
actors =
|
actors =
|
||||||
activities
|
activities
|
||||||
|
@ -84,50 +85,45 @@ def render(
|
||||||
# Note: :relationships contain user mutes (needed for :muted flag in :status)
|
# Note: :relationships contain user mutes (needed for :muted flag in :status)
|
||||||
status_render_opts = %{relationships: opts[:relationships]}
|
status_render_opts = %{relationships: opts[:relationships]}
|
||||||
|
|
||||||
with %{id: _} = account <-
|
account =
|
||||||
AccountView.render(
|
AccountView.render(
|
||||||
"show.json",
|
"show.json",
|
||||||
%{user: actor, for: reading_user}
|
%{user: actor, for: reading_user}
|
||||||
) do
|
)
|
||||||
response = %{
|
|
||||||
id: to_string(notification.id),
|
response = %{
|
||||||
type: notification.type,
|
id: to_string(notification.id),
|
||||||
created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at),
|
type: notification.type,
|
||||||
account: account,
|
created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at),
|
||||||
pleroma: %{
|
account: account,
|
||||||
is_seen: notification.seen
|
pleroma: %{
|
||||||
}
|
is_seen: notification.seen
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case notification.type do
|
case notification.type do
|
||||||
"mention" ->
|
"mention" ->
|
||||||
put_status(response, activity, reading_user, status_render_opts)
|
put_status(response, activity, reading_user, status_render_opts)
|
||||||
|
|
||||||
"favourite" ->
|
"favourite" ->
|
||||||
put_status(response, parent_activity_fn.(), reading_user, status_render_opts)
|
put_status(response, parent_activity_fn.(), reading_user, status_render_opts)
|
||||||
|
|
||||||
"reblog" ->
|
"reblog" ->
|
||||||
put_status(response, parent_activity_fn.(), reading_user, status_render_opts)
|
put_status(response, parent_activity_fn.(), reading_user, status_render_opts)
|
||||||
|
|
||||||
"move" ->
|
"move" ->
|
||||||
put_target(response, activity, reading_user, %{})
|
put_target(response, activity, reading_user, %{})
|
||||||
|
|
||||||
"pleroma:emoji_reaction" ->
|
"pleroma:emoji_reaction" ->
|
||||||
response
|
response
|
||||||
|> put_status(parent_activity_fn.(), reading_user, status_render_opts)
|
|> put_status(parent_activity_fn.(), reading_user, status_render_opts)
|
||||||
|> put_emoji(activity)
|
|> put_emoji(activity)
|
||||||
|
|
||||||
"pleroma:chat_mention" ->
|
"pleroma:chat_mention" ->
|
||||||
put_chat_message(response, activity, reading_user, status_render_opts)
|
put_chat_message(response, activity, reading_user, status_render_opts)
|
||||||
|
|
||||||
type when type in ["follow", "follow_request"] ->
|
type when type in ["follow", "follow_request"] ->
|
||||||
response
|
response
|
||||||
|
|
||||||
_ ->
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
else
|
|
||||||
_ -> nil
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -377,8 +377,8 @@ def render("card.json", %{rich_media: rich_media, page_url: page_url}) do
|
||||||
page_url_data = URI.parse(page_url)
|
page_url_data = URI.parse(page_url)
|
||||||
|
|
||||||
page_url_data =
|
page_url_data =
|
||||||
if rich_media[:url] != nil do
|
if is_binary(rich_media["url"]) do
|
||||||
URI.merge(page_url_data, URI.parse(rich_media[:url]))
|
URI.merge(page_url_data, URI.parse(rich_media["url"]))
|
||||||
else
|
else
|
||||||
page_url_data
|
page_url_data
|
||||||
end
|
end
|
||||||
|
@ -386,11 +386,9 @@ def render("card.json", %{rich_media: rich_media, page_url: page_url}) do
|
||||||
page_url = page_url_data |> to_string
|
page_url = page_url_data |> to_string
|
||||||
|
|
||||||
image_url =
|
image_url =
|
||||||
if rich_media[:image] != nil do
|
if is_binary(rich_media["image"]) do
|
||||||
URI.merge(page_url_data, URI.parse(rich_media[:image]))
|
URI.merge(page_url_data, URI.parse(rich_media["image"]))
|
||||||
|> to_string
|
|> to_string
|
||||||
else
|
|
||||||
nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
%{
|
%{
|
||||||
|
@ -399,8 +397,8 @@ def render("card.json", %{rich_media: rich_media, page_url: page_url}) do
|
||||||
provider_url: page_url_data.scheme <> "://" <> page_url_data.host,
|
provider_url: page_url_data.scheme <> "://" <> page_url_data.host,
|
||||||
url: page_url,
|
url: page_url,
|
||||||
image: image_url |> MediaProxy.url(),
|
image: image_url |> MediaProxy.url(),
|
||||||
title: rich_media[:title] || "",
|
title: rich_media["title"] || "",
|
||||||
description: rich_media[:description] || "",
|
description: rich_media["description"] || "",
|
||||||
pleroma: %{
|
pleroma: %{
|
||||||
opengraph: rich_media
|
opengraph: rich_media
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ defmodule Pleroma.Web.RichMedia.Helpers do
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Web.RichMedia.Parser
|
alias Pleroma.Web.RichMedia.Parser
|
||||||
|
|
||||||
@spec validate_page_url(any()) :: :ok | :error
|
@spec validate_page_url(URI.t() | binary()) :: :ok | :error
|
||||||
defp validate_page_url(page_url) when is_binary(page_url) do
|
defp validate_page_url(page_url) when is_binary(page_url) do
|
||||||
validate_tld = Application.get_env(:auto_linker, :opts)[:validate_tld]
|
validate_tld = Application.get_env(:auto_linker, :opts)[:validate_tld]
|
||||||
|
|
||||||
|
@ -18,8 +18,8 @@ defp validate_page_url(page_url) when is_binary(page_url) do
|
||||||
|> parse_uri(page_url)
|
|> parse_uri(page_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp validate_page_url(%URI{host: host, scheme: scheme, authority: authority})
|
defp validate_page_url(%URI{host: host, scheme: "https", authority: authority})
|
||||||
when scheme == "https" and not is_nil(authority) do
|
when is_binary(authority) do
|
||||||
cond do
|
cond do
|
||||||
host in Config.get([:rich_media, :ignore_hosts], []) ->
|
host in Config.get([:rich_media, :ignore_hosts], []) ->
|
||||||
:error
|
:error
|
||||||
|
|
|
@ -91,7 +91,7 @@ defp parse_url(url) do
|
||||||
html
|
html
|
||||||
|> parse_html()
|
|> parse_html()
|
||||||
|> maybe_parse()
|
|> maybe_parse()
|
||||||
|> Map.put(:url, url)
|
|> Map.put("url", url)
|
||||||
|> clean_parsed_data()
|
|> clean_parsed_data()
|
||||||
|> check_parsed_data()
|
|> check_parsed_data()
|
||||||
rescue
|
rescue
|
||||||
|
@ -111,8 +111,8 @@ defp maybe_parse(html) do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp check_parsed_data(%{title: title} = data)
|
defp check_parsed_data(%{"title" => title} = data)
|
||||||
when is_binary(title) and byte_size(title) > 0 do
|
when is_binary(title) and title != "" do
|
||||||
{:ok, data}
|
{:ok, data}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -123,11 +123,7 @@ defp check_parsed_data(data) do
|
||||||
defp clean_parsed_data(data) do
|
defp clean_parsed_data(data) do
|
||||||
data
|
data
|
||||||
|> Enum.reject(fn {key, val} ->
|
|> Enum.reject(fn {key, val} ->
|
||||||
with {:ok, _} <- Jason.encode(%{key => val}) do
|
not match?({:ok, _}, Jason.encode(%{key => val}))
|
||||||
false
|
|
||||||
else
|
|
||||||
_ -> true
|
|
||||||
end
|
|
||||||
end)
|
end)
|
||||||
|> Map.new()
|
|> Map.new()
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,19 +22,19 @@ defp normalize_attributes(html_node, prefix, key_name, value_name) do
|
||||||
{_tag, attributes, _children} = html_node
|
{_tag, attributes, _children} = html_node
|
||||||
|
|
||||||
data =
|
data =
|
||||||
Enum.into(attributes, %{}, fn {name, value} ->
|
Map.new(attributes, fn {name, value} ->
|
||||||
{name, String.trim_leading(value, "#{prefix}:")}
|
{name, String.trim_leading(value, "#{prefix}:")}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
%{String.to_atom(data[key_name]) => data[value_name]}
|
%{data[key_name] => data[value_name]}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_put_title(%{title: _} = meta, _), do: meta
|
defp maybe_put_title(%{"title" => _} = meta, _), do: meta
|
||||||
|
|
||||||
defp maybe_put_title(meta, html) when meta != %{} do
|
defp maybe_put_title(meta, html) when meta != %{} do
|
||||||
case get_page_title(html) do
|
case get_page_title(html) do
|
||||||
"" -> meta
|
"" -> meta
|
||||||
title -> Map.put_new(meta, :title, title)
|
title -> Map.put_new(meta, "title", title)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
defmodule Pleroma.Web.RichMedia.Parsers.OEmbed do
|
defmodule Pleroma.Web.RichMedia.Parsers.OEmbed do
|
||||||
def parse(html, _data) do
|
def parse(html, _data) do
|
||||||
with elements = [_ | _] <- get_discovery_data(html),
|
with elements = [_ | _] <- get_discovery_data(html),
|
||||||
{:ok, oembed_url} <- get_oembed_url(elements),
|
oembed_url when is_binary(oembed_url) <- get_oembed_url(elements),
|
||||||
{:ok, oembed_data} <- get_oembed_data(oembed_url) do
|
{:ok, oembed_data} <- get_oembed_data(oembed_url) do
|
||||||
oembed_data
|
oembed_data
|
||||||
else
|
else
|
||||||
|
@ -17,19 +17,13 @@ defp get_discovery_data(html) do
|
||||||
html |> Floki.find("link[type='application/json+oembed']")
|
html |> Floki.find("link[type='application/json+oembed']")
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_oembed_url(nodes) do
|
defp get_oembed_url([{"link", attributes, _children} | _]) do
|
||||||
{"link", attributes, _children} = nodes |> hd()
|
Enum.find_value(attributes, fn {k, v} -> if k == "href", do: v end)
|
||||||
|
|
||||||
{:ok, Enum.into(attributes, %{})["href"]}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_oembed_data(url) do
|
defp get_oembed_data(url) do
|
||||||
{:ok, %Tesla.Env{body: json}} = Pleroma.HTTP.get(url, [], adapter: [pool: :media])
|
with {:ok, %Tesla.Env{body: json}} <- Pleroma.HTTP.get(url, [], adapter: [pool: :media]) do
|
||||||
|
Jason.decode(json)
|
||||||
{:ok, data} = Jason.decode(json)
|
end
|
||||||
|
|
||||||
data = data |> Map.new(fn {k, v} -> {String.to_atom(k), v} end)
|
|
||||||
|
|
||||||
{:ok, data}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.DeleteNotificationsFromInvisibleUsers do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
import Ecto.Query
|
||||||
|
alias Pleroma.Repo
|
||||||
|
|
||||||
|
def up do
|
||||||
|
Pleroma.Notification
|
||||||
|
|> join(:inner, [n], activity in assoc(n, :activity))
|
||||||
|
|> where(
|
||||||
|
[n, a],
|
||||||
|
fragment("? in (SELECT ap_id FROM users WHERE invisible = true)", a.actor)
|
||||||
|
)
|
||||||
|
|> Repo.delete_all()
|
||||||
|
end
|
||||||
|
|
||||||
|
def down, do: :ok
|
||||||
|
end
|
|
@ -306,6 +306,14 @@ test "it doesn't create subscription notifications if the recipient cannot see t
|
||||||
|
|
||||||
assert {:ok, []} == Notification.create_notifications(status)
|
assert {:ok, []} == Notification.create_notifications(status)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it disables notifications from people who are invisible" do
|
||||||
|
author = insert(:user, invisible: true)
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, status} = CommonAPI.post(author, %{status: "hey @#{user.nickname}"})
|
||||||
|
refute Notification.create_notification(status, user)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "follow / follow_request notifications" do
|
describe "follow / follow_request notifications" do
|
||||||
|
|
|
@ -574,7 +574,7 @@ test "doesn't return transitive interactions concerning blocked users" do
|
||||||
refute Enum.member?(activities, activity_four)
|
refute Enum.member?(activities, activity_four)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "doesn't return announce activities concerning blocked users" do
|
test "doesn't return announce activities with blocked users in 'to'" do
|
||||||
blocker = insert(:user)
|
blocker = insert(:user)
|
||||||
blockee = insert(:user)
|
blockee = insert(:user)
|
||||||
friend = insert(:user)
|
friend = insert(:user)
|
||||||
|
@ -596,6 +596,39 @@ test "doesn't return announce activities concerning blocked users" do
|
||||||
refute Enum.member?(activities, activity_three.id)
|
refute Enum.member?(activities, activity_three.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "doesn't return announce activities with blocked users in 'cc'" do
|
||||||
|
blocker = insert(:user)
|
||||||
|
blockee = insert(:user)
|
||||||
|
friend = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _user_relationship} = User.block(blocker, blockee)
|
||||||
|
|
||||||
|
{:ok, activity_one} = CommonAPI.post(friend, %{status: "hey!"})
|
||||||
|
|
||||||
|
{:ok, activity_two} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"})
|
||||||
|
|
||||||
|
assert object = Pleroma.Object.normalize(activity_two)
|
||||||
|
|
||||||
|
data = %{
|
||||||
|
"actor" => friend.ap_id,
|
||||||
|
"object" => object.data["id"],
|
||||||
|
"context" => object.data["context"],
|
||||||
|
"type" => "Announce",
|
||||||
|
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
|
"cc" => [blockee.ap_id]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:ok, activity_three} = ActivityPub.insert(data)
|
||||||
|
|
||||||
|
activities =
|
||||||
|
ActivityPub.fetch_activities([], %{blocking_user: blocker})
|
||||||
|
|> Enum.map(fn act -> act.id end)
|
||||||
|
|
||||||
|
assert Enum.member?(activities, activity_one.id)
|
||||||
|
refute Enum.member?(activities, activity_two.id)
|
||||||
|
refute Enum.member?(activities, activity_three.id)
|
||||||
|
end
|
||||||
|
|
||||||
test "doesn't return activities from blocked domains" do
|
test "doesn't return activities from blocked domains" do
|
||||||
domain = "dogwhistle.zone"
|
domain = "dogwhistle.zone"
|
||||||
domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
|
domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
|
||||||
|
@ -1643,6 +1676,40 @@ test "home timeline with reply_visibility `self`", %{
|
||||||
|
|
||||||
assert Enum.all?(visible_ids, &(&1 in activities_ids))
|
assert Enum.all?(visible_ids, &(&1 in activities_ids))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "filtering out announces where the user is the actor of the announced message" do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
third_user = insert(:user)
|
||||||
|
User.follow(user, other_user)
|
||||||
|
|
||||||
|
{:ok, post} = CommonAPI.post(user, %{status: "yo"})
|
||||||
|
{:ok, other_post} = CommonAPI.post(third_user, %{status: "yo"})
|
||||||
|
{:ok, _announce} = CommonAPI.repeat(post.id, other_user)
|
||||||
|
{:ok, _announce} = CommonAPI.repeat(post.id, third_user)
|
||||||
|
{:ok, announce} = CommonAPI.repeat(other_post.id, other_user)
|
||||||
|
|
||||||
|
params = %{
|
||||||
|
type: ["Announce"]
|
||||||
|
}
|
||||||
|
|
||||||
|
results =
|
||||||
|
[user.ap_id | User.following(user)]
|
||||||
|
|> ActivityPub.fetch_activities(params)
|
||||||
|
|
||||||
|
assert length(results) == 3
|
||||||
|
|
||||||
|
params = %{
|
||||||
|
type: ["Announce"],
|
||||||
|
announce_filtering_user: user
|
||||||
|
}
|
||||||
|
|
||||||
|
[result] =
|
||||||
|
[user.ap_id | User.following(user)]
|
||||||
|
|> ActivityPub.fetch_activities(params)
|
||||||
|
|
||||||
|
assert result.id == announce.id
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "replies filtering with private messages" do
|
describe "replies filtering with private messages" do
|
||||||
|
|
|
@ -7,7 +7,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicyTest do
|
||||||
|
|
||||||
alias Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy
|
alias Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy
|
||||||
|
|
||||||
setup do: clear_config([:mrf_user_allowlist, :localhost])
|
setup do: clear_config(:mrf_user_allowlist)
|
||||||
|
|
||||||
test "pass filter if allow list is empty" do
|
test "pass filter if allow list is empty" do
|
||||||
actor = insert(:user)
|
actor = insert(:user)
|
||||||
|
@ -17,14 +17,14 @@ test "pass filter if allow list is empty" do
|
||||||
|
|
||||||
test "pass filter if allow list isn't empty and user in allow list" do
|
test "pass filter if allow list isn't empty and user in allow list" do
|
||||||
actor = insert(:user)
|
actor = insert(:user)
|
||||||
Pleroma.Config.put([:mrf_user_allowlist, :localhost], [actor.ap_id, "test-ap-id"])
|
Pleroma.Config.put([:mrf_user_allowlist], %{"localhost" => [actor.ap_id, "test-ap-id"]})
|
||||||
message = %{"actor" => actor.ap_id}
|
message = %{"actor" => actor.ap_id}
|
||||||
assert UserAllowListPolicy.filter(message) == {:ok, message}
|
assert UserAllowListPolicy.filter(message) == {:ok, message}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "rejected if allow list isn't empty and user not in allow list" do
|
test "rejected if allow list isn't empty and user not in allow list" do
|
||||||
actor = insert(:user)
|
actor = insert(:user)
|
||||||
Pleroma.Config.put([:mrf_user_allowlist, :localhost], ["test-ap-id"])
|
Pleroma.Config.put([:mrf_user_allowlist], %{"localhost" => ["test-ap-id"]})
|
||||||
message = %{"actor" => actor.ap_id}
|
message = %{"actor" => actor.ap_id}
|
||||||
assert UserAllowListPolicy.filter(message) == {:reject, nil}
|
assert UserAllowListPolicy.filter(message) == {:reject, nil}
|
||||||
end
|
end
|
||||||
|
|
|
@ -313,6 +313,33 @@ test "filters notifications for Announce activities" do
|
||||||
assert public_activity.id in activity_ids
|
assert public_activity.id in activity_ids
|
||||||
refute unlisted_activity.id in activity_ids
|
refute unlisted_activity.id in activity_ids
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "doesn't return less than the requested amount of records when the user's reply is liked" do
|
||||||
|
user = insert(:user)
|
||||||
|
%{user: other_user, conn: conn} = oauth_access(["read:notifications"])
|
||||||
|
|
||||||
|
{:ok, mention} =
|
||||||
|
CommonAPI.post(user, %{status: "@#{other_user.nickname}", visibility: "public"})
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{status: ".", visibility: "public"})
|
||||||
|
|
||||||
|
{:ok, reply} =
|
||||||
|
CommonAPI.post(other_user, %{
|
||||||
|
status: ".",
|
||||||
|
visibility: "public",
|
||||||
|
in_reply_to_status_id: activity.id
|
||||||
|
})
|
||||||
|
|
||||||
|
{:ok, _favorite} = CommonAPI.favorite(user, reply.id)
|
||||||
|
|
||||||
|
activity_ids =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/notifications?exclude_visibilities[]=direct&limit=2")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|> Enum.map(& &1["status"]["id"])
|
||||||
|
|
||||||
|
assert [reply.id, mention.id] == activity_ids
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "filters notifications using exclude_types" do
|
test "filters notifications using exclude_types" do
|
||||||
|
|
|
@ -1541,14 +1541,49 @@ test "context" do
|
||||||
} = response
|
} = response
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "favorites paginate correctly" do
|
||||||
|
%{user: user, conn: conn} = oauth_access(["read:favourites"])
|
||||||
|
other_user = insert(:user)
|
||||||
|
{:ok, first_post} = CommonAPI.post(other_user, %{status: "bla"})
|
||||||
|
{:ok, second_post} = CommonAPI.post(other_user, %{status: "bla"})
|
||||||
|
{:ok, third_post} = CommonAPI.post(other_user, %{status: "bla"})
|
||||||
|
|
||||||
|
{:ok, _first_favorite} = CommonAPI.favorite(user, third_post.id)
|
||||||
|
{:ok, _second_favorite} = CommonAPI.favorite(user, first_post.id)
|
||||||
|
{:ok, third_favorite} = CommonAPI.favorite(user, second_post.id)
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/favourites?limit=1")
|
||||||
|
|
||||||
|
assert [%{"id" => post_id}] = json_response_and_validate_schema(result, 200)
|
||||||
|
assert post_id == second_post.id
|
||||||
|
|
||||||
|
# Using the header for pagination works correctly
|
||||||
|
[next, _] = get_resp_header(result, "link") |> hd() |> String.split(", ")
|
||||||
|
[_, max_id] = Regex.run(~r/max_id=(.*)>;/, next)
|
||||||
|
|
||||||
|
assert max_id == third_favorite.id
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/favourites?max_id=#{max_id}")
|
||||||
|
|
||||||
|
assert [%{"id" => first_post_id}, %{"id" => third_post_id}] =
|
||||||
|
json_response_and_validate_schema(result, 200)
|
||||||
|
|
||||||
|
assert first_post_id == first_post.id
|
||||||
|
assert third_post_id == third_post.id
|
||||||
|
end
|
||||||
|
|
||||||
test "returns the favorites of a user" do
|
test "returns the favorites of a user" do
|
||||||
%{user: user, conn: conn} = oauth_access(["read:favourites"])
|
%{user: user, conn: conn} = oauth_access(["read:favourites"])
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
|
|
||||||
{:ok, _} = CommonAPI.post(other_user, %{status: "bla"})
|
{:ok, _} = CommonAPI.post(other_user, %{status: "bla"})
|
||||||
{:ok, activity} = CommonAPI.post(other_user, %{status: "traps are happy"})
|
{:ok, activity} = CommonAPI.post(other_user, %{status: "trees are happy"})
|
||||||
|
|
||||||
{:ok, _} = CommonAPI.favorite(user, activity.id)
|
{:ok, last_like} = CommonAPI.favorite(user, activity.id)
|
||||||
|
|
||||||
first_conn = get(conn, "/api/v1/favourites")
|
first_conn = get(conn, "/api/v1/favourites")
|
||||||
|
|
||||||
|
@ -1566,9 +1601,7 @@ test "returns the favorites of a user" do
|
||||||
|
|
||||||
{:ok, _} = CommonAPI.favorite(user, second_activity.id)
|
{:ok, _} = CommonAPI.favorite(user, second_activity.id)
|
||||||
|
|
||||||
last_like = status["id"]
|
second_conn = get(conn, "/api/v1/favourites?since_id=#{last_like.id}")
|
||||||
|
|
||||||
second_conn = get(conn, "/api/v1/favourites?since_id=#{last_like}")
|
|
||||||
|
|
||||||
assert [second_status] = json_response_and_validate_schema(second_conn, 200)
|
assert [second_status] = json_response_and_validate_schema(second_conn, 200)
|
||||||
assert second_status["id"] == to_string(second_activity.id)
|
assert second_status["id"] == to_string(second_activity.id)
|
||||||
|
|
|
@ -139,9 +139,7 @@ test "Follow notification" do
|
||||||
test_notifications_rendering([notification], followed, [expected])
|
test_notifications_rendering([notification], followed, [expected])
|
||||||
|
|
||||||
User.perform(:delete, follower)
|
User.perform(:delete, follower)
|
||||||
notification = Notification |> Repo.one() |> Repo.preload(:activity)
|
refute Repo.one(Notification)
|
||||||
|
|
||||||
test_notifications_rendering([notification], followed, [])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag capture_log: true
|
@tag capture_log: true
|
||||||
|
|
|
@ -60,19 +60,19 @@ test "returns error when no metadata present" do
|
||||||
test "doesn't just add a title" do
|
test "doesn't just add a title" do
|
||||||
assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/non-ogp") ==
|
assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/non-ogp") ==
|
||||||
{:error,
|
{:error,
|
||||||
"Found metadata was invalid or incomplete: %{url: \"http://example.com/non-ogp\"}"}
|
"Found metadata was invalid or incomplete: %{\"url\" => \"http://example.com/non-ogp\"}"}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "parses ogp" do
|
test "parses ogp" do
|
||||||
assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/ogp") ==
|
assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/ogp") ==
|
||||||
{:ok,
|
{:ok,
|
||||||
%{
|
%{
|
||||||
image: "http://ia.media-imdb.com/images/rock.jpg",
|
"image" => "http://ia.media-imdb.com/images/rock.jpg",
|
||||||
title: "The Rock",
|
"title" => "The Rock",
|
||||||
description:
|
"description" =>
|
||||||
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
|
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
|
||||||
type: "video.movie",
|
"type" => "video.movie",
|
||||||
url: "http://example.com/ogp"
|
"url" => "http://example.com/ogp"
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -80,12 +80,12 @@ test "falls back to <title> when ogp:title is missing" do
|
||||||
assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/ogp-missing-title") ==
|
assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/ogp-missing-title") ==
|
||||||
{:ok,
|
{:ok,
|
||||||
%{
|
%{
|
||||||
image: "http://ia.media-imdb.com/images/rock.jpg",
|
"image" => "http://ia.media-imdb.com/images/rock.jpg",
|
||||||
title: "The Rock (1996)",
|
"title" => "The Rock (1996)",
|
||||||
description:
|
"description" =>
|
||||||
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
|
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
|
||||||
type: "video.movie",
|
"type" => "video.movie",
|
||||||
url: "http://example.com/ogp-missing-title"
|
"url" => "http://example.com/ogp-missing-title"
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -93,12 +93,12 @@ test "parses twitter card" do
|
||||||
assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/twitter-card") ==
|
assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/twitter-card") ==
|
||||||
{:ok,
|
{:ok,
|
||||||
%{
|
%{
|
||||||
card: "summary",
|
"card" => "summary",
|
||||||
site: "@flickr",
|
"site" => "@flickr",
|
||||||
image: "https://farm6.staticflickr.com/5510/14338202952_93595258ff_z.jpg",
|
"image" => "https://farm6.staticflickr.com/5510/14338202952_93595258ff_z.jpg",
|
||||||
title: "Small Island Developing States Photo Submission",
|
"title" => "Small Island Developing States Photo Submission",
|
||||||
description: "View the album on Flickr.",
|
"description" => "View the album on Flickr.",
|
||||||
url: "http://example.com/twitter-card"
|
"url" => "http://example.com/twitter-card"
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -106,27 +106,28 @@ test "parses OEmbed" do
|
||||||
assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/oembed") ==
|
assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/oembed") ==
|
||||||
{:ok,
|
{:ok,
|
||||||
%{
|
%{
|
||||||
author_name: "bees",
|
"author_name" => "bees",
|
||||||
author_url: "https://www.flickr.com/photos/bees/",
|
"author_url" => "https://www.flickr.com/photos/bees/",
|
||||||
cache_age: 3600,
|
"cache_age" => 3600,
|
||||||
flickr_type: "photo",
|
"flickr_type" => "photo",
|
||||||
height: "768",
|
"height" => "768",
|
||||||
html:
|
"html" =>
|
||||||
"<a data-flickr-embed=\"true\" href=\"https://www.flickr.com/photos/bees/2362225867/\" title=\"Bacon Lollys by bees, on Flickr\"><img src=\"https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_b.jpg\" width=\"1024\" height=\"768\" alt=\"Bacon Lollys\"></a><script async src=\"https://embedr.flickr.com/assets/client-code.js\" charset=\"utf-8\"></script>",
|
"<a data-flickr-embed=\"true\" href=\"https://www.flickr.com/photos/bees/2362225867/\" title=\"Bacon Lollys by bees, on Flickr\"><img src=\"https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_b.jpg\" width=\"1024\" height=\"768\" alt=\"Bacon Lollys\"></a><script async src=\"https://embedr.flickr.com/assets/client-code.js\" charset=\"utf-8\"></script>",
|
||||||
license: "All Rights Reserved",
|
"license" => "All Rights Reserved",
|
||||||
license_id: 0,
|
"license_id" => 0,
|
||||||
provider_name: "Flickr",
|
"provider_name" => "Flickr",
|
||||||
provider_url: "https://www.flickr.com/",
|
"provider_url" => "https://www.flickr.com/",
|
||||||
thumbnail_height: 150,
|
"thumbnail_height" => 150,
|
||||||
thumbnail_url: "https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_q.jpg",
|
"thumbnail_url" =>
|
||||||
thumbnail_width: 150,
|
"https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_q.jpg",
|
||||||
title: "Bacon Lollys",
|
"thumbnail_width" => 150,
|
||||||
type: "photo",
|
"title" => "Bacon Lollys",
|
||||||
url: "http://example.com/oembed",
|
"type" => "photo",
|
||||||
version: "1.0",
|
"url" => "http://example.com/oembed",
|
||||||
web_page: "https://www.flickr.com/photos/bees/2362225867/",
|
"version" => "1.0",
|
||||||
web_page_short_url: "https://flic.kr/p/4AK2sc",
|
"web_page" => "https://www.flickr.com/photos/bees/2362225867/",
|
||||||
width: "1024"
|
"web_page_short_url" => "https://flic.kr/p/4AK2sc",
|
||||||
|
"width" => "1024"
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue