Merge remote-tracking branch 'origin/develop' into HEAD
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
commit
8b92459345
21 changed files with 207 additions and 41 deletions
|
@ -169,25 +169,6 @@ unit-testing-1.12-erratic:
|
|||
- mix ecto.migrate
|
||||
- mix test --only=erratic
|
||||
|
||||
unit-testing-1.12-rum:
|
||||
extends:
|
||||
- .build_changes_policy
|
||||
- .using-ci-base
|
||||
stage: test
|
||||
cache: *testing_cache_policy
|
||||
services:
|
||||
- name: git.pleroma.social:5050/pleroma/pleroma/postgres-with-rum-13
|
||||
alias: postgres
|
||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||
variables:
|
||||
<<: *global_variables
|
||||
RUM_ENABLED: "true"
|
||||
script:
|
||||
- mix ecto.create
|
||||
- mix ecto.migrate
|
||||
- "mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/"
|
||||
- mix test --preload-modules
|
||||
|
||||
formatting-1.13:
|
||||
extends: .build_changes_policy
|
||||
image: &formatting_elixir elixir:1.13-alpine
|
||||
|
|
1
changelog.d/force-mention-mrf.add
Normal file
1
changelog.d/force-mention-mrf.add
Normal file
|
@ -0,0 +1 @@
|
|||
Add ForceMention MRF
|
1
changelog.d/issue-3241.fix
Normal file
1
changelog.d/issue-3241.fix
Normal file
|
@ -0,0 +1 @@
|
|||
Handle cases when users.inbox is nil.
|
1
changelog.d/notifications.fix
Normal file
1
changelog.d/notifications.fix
Normal file
|
@ -0,0 +1 @@
|
|||
Notifications: improve performance by filtering on users table instead of activities table
|
1
changelog.d/public-polls.add
Normal file
1
changelog.d/public-polls.add
Normal file
|
@ -0,0 +1 @@
|
|||
Expose nonAnonymous field from Smithereen polls
|
|
@ -436,6 +436,10 @@
|
|||
ttl: 60_000,
|
||||
min_length: 50
|
||||
|
||||
config :pleroma, :mrf_force_mention,
|
||||
mention_parent: true,
|
||||
mention_quoted: true
|
||||
|
||||
config :pleroma, :rich_media,
|
||||
enabled: true,
|
||||
ignore_hosts: [],
|
||||
|
|
|
@ -157,7 +157,8 @@ To add configuration to your config file, you can copy it from the base config.
|
|||
* `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)).
|
||||
* `Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent`: Forces every mentioned user to be reflected in the post content.
|
||||
* `Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy`: Forces quote post URLs to be reflected in the message content inline.
|
||||
* `Pleroma.Web.ActivityPub.MRF.QuoteToLinkTagPolicy`: Force a Link tag for posts quoting another post. (may break outgoing federation of quote posts with older Pleroma versions)
|
||||
* `Pleroma.Web.ActivityPub.MRF.QuoteToLinkTagPolicy`: Force a Link tag for posts quoting another post. (may break outgoing federation of quote posts with older Pleroma versions).
|
||||
* `Pleroma.Web.ActivityPub.MRF.ForceMention`: Forces posts to include a mention of the author of parent post or the author of quoted post.
|
||||
* `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
|
||||
* `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.
|
||||
|
||||
|
@ -271,6 +272,10 @@ Notes:
|
|||
#### :mrf_inline_quote
|
||||
* `template`: The template to append to the post. `{url}` will be replaced with the actual link to the quoted post. Default: `<bdi>RT:</bdi> {url}`
|
||||
|
||||
#### :mrf_force_mention
|
||||
* `mention_parent`: Whether to append mention of parent post author
|
||||
* `mention_quoted`: Whether to append mention of parent quoted author
|
||||
|
||||
### :activitypub
|
||||
* `unfollow_blocked`: Whether blocks result in people getting unfollowed
|
||||
* `outgoing_blocks`: Whether to federate blocks to other instances
|
||||
|
|
|
@ -41,6 +41,7 @@ Has these additional fields under the `pleroma` object:
|
|||
- `pinned_at`: a datetime (iso8601) when status was pinned, `null` otherwise.
|
||||
- `quotes_count`: the count of status quotes.
|
||||
- `event`: event information if the post is an event, `null` otherwise.
|
||||
- `non_anonymous`: true if the source post specifies the poll results are not anonymous. Currently only implemented by Smithereen.
|
||||
- `bookmark_folder`: the ID of the folder bookmark is stored within (if any).
|
||||
|
||||
The `GET /api/v1/statuses/:id/source` endpoint additionally has the following attributes:
|
||||
|
|
|
@ -241,13 +241,13 @@ def find(following_relationships, follower, following) do
|
|||
end
|
||||
|
||||
@doc """
|
||||
For a query with joined activity,
|
||||
keeps rows where activity's actor is followed by user -or- is NOT domain-blocked by user.
|
||||
For a query with joined activity's actor,
|
||||
keeps rows where actor is followed by user -or- is NOT domain-blocked by user.
|
||||
"""
|
||||
def keep_following_or_not_domain_blocked(query, user) do
|
||||
where(
|
||||
query,
|
||||
[_, activity],
|
||||
[_, user_actor: user_actor],
|
||||
fragment(
|
||||
# "(actor's domain NOT in domain_blocks) OR (actor IS in followed AP IDs)"
|
||||
"""
|
||||
|
@ -255,9 +255,9 @@ def keep_following_or_not_domain_blocked(query, user) do
|
|||
? = ANY(SELECT ap_id FROM users AS u INNER JOIN following_relationships AS fr
|
||||
ON u.id = fr.following_id WHERE fr.follower_id = ? AND fr.state = ?)
|
||||
""",
|
||||
activity.actor,
|
||||
user_actor.ap_id,
|
||||
^user.domain_blocks,
|
||||
activity.actor,
|
||||
user_actor.ap_id,
|
||||
^User.binary_id(user.id),
|
||||
^accept_state_code()
|
||||
)
|
||||
|
|
|
@ -142,7 +142,7 @@ defp exclude_blocked(query, user, opts) do
|
|||
blocked_ap_ids = opts[:blocked_users_ap_ids] || User.blocked_users_ap_ids(user)
|
||||
|
||||
query
|
||||
|> where([n, a], a.actor not in ^blocked_ap_ids)
|
||||
|> where([..., user_actor: user_actor], user_actor.ap_id not in ^blocked_ap_ids)
|
||||
|> FollowingRelationship.keep_following_or_not_domain_blocked(user)
|
||||
end
|
||||
|
||||
|
@ -153,7 +153,7 @@ defp exclude_blockers(query, user) do
|
|||
blocker_ap_ids = User.incoming_relationships_ungrouped_ap_ids(user, [:block])
|
||||
|
||||
query
|
||||
|> where([n, a], a.actor not in ^blocker_ap_ids)
|
||||
|> where([..., user_actor: user_actor], user_actor.ap_id not in ^blocker_ap_ids)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -166,7 +166,7 @@ defp exclude_notification_muted(query, user, opts) do
|
|||
opts[:notification_muted_users_ap_ids] || User.notification_muted_users_ap_ids(user)
|
||||
|
||||
query
|
||||
|> where([n, a], a.actor not in ^notification_muted_ap_ids)
|
||||
|> where([..., user_actor: user_actor], user_actor.ap_id not in ^notification_muted_ap_ids)
|
||||
|> join(:left, [n, a], tm in ThreadMute,
|
||||
on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data),
|
||||
as: :thread_mute
|
||||
|
|
59
lib/pleroma/web/activity_pub/mrf/force_mention.ex
Normal file
59
lib/pleroma/web/activity_pub/mrf/force_mention.ex
Normal file
|
@ -0,0 +1,59 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.ForceMention do
|
||||
require Pleroma.Constants
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.User
|
||||
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
|
||||
|
||||
defp get_author(url) do
|
||||
with %Object{data: %{"actor" => actor}} <- Object.normalize(url, fetch: false),
|
||||
%User{ap_id: ap_id, nickname: nickname} <- User.get_cached_by_ap_id(actor) do
|
||||
%{"type" => "Mention", "href" => ap_id, "name" => "@#{nickname}"}
|
||||
else
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp prepend_author(tags, _, false), do: tags
|
||||
|
||||
defp prepend_author(tags, nil, _), do: tags
|
||||
|
||||
defp prepend_author(tags, url, _) do
|
||||
actor = get_author(url)
|
||||
|
||||
if not is_nil(actor) do
|
||||
[actor | tags]
|
||||
else
|
||||
tags
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def filter(%{"type" => "Create", "object" => %{"tag" => tag} = object} = activity) do
|
||||
tag =
|
||||
tag
|
||||
|> prepend_author(
|
||||
object["inReplyTo"],
|
||||
Config.get([:mrf_force_mention, :mention_parent, true])
|
||||
)
|
||||
|> prepend_author(
|
||||
object["quoteUrl"],
|
||||
Config.get([:mrf_force_mention, :mention_quoted, true])
|
||||
)
|
||||
|> Enum.uniq()
|
||||
|
||||
{:ok, put_in(activity["object"]["tag"], tag)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def filter(object), do: {:ok, object}
|
||||
|
||||
@impl true
|
||||
def describe, do: {:ok, %{}}
|
||||
end
|
|
@ -158,19 +158,18 @@ defp signature_host(%URI{port: port, scheme: scheme, host: host}) do
|
|||
end
|
||||
end
|
||||
|
||||
defp should_federate?(inbox, public) do
|
||||
if public do
|
||||
true
|
||||
else
|
||||
%{host: host} = URI.parse(inbox)
|
||||
def should_federate?(nil, _), do: false
|
||||
def should_federate?(_, true), do: true
|
||||
|
||||
quarantined_instances =
|
||||
Config.get([:instance, :quarantined_instances], [])
|
||||
|> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples()
|
||||
|> Pleroma.Web.ActivityPub.MRF.subdomains_regex()
|
||||
def should_federate?(inbox, _) do
|
||||
%{host: host} = URI.parse(inbox)
|
||||
|
||||
!Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host)
|
||||
end
|
||||
quarantined_instances =
|
||||
Config.get([:instance, :quarantined_instances], [])
|
||||
|> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples()
|
||||
|> Pleroma.Web.ActivityPub.MRF.subdomains_regex()
|
||||
|
||||
!Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host)
|
||||
end
|
||||
|
||||
@spec recipients(User.t(), Activity.t()) :: [[User.t()]]
|
||||
|
|
|
@ -60,7 +60,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do
|
|||
pleroma: %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
non_anonymous: %Schema{type: :boolean, description: "Is the voters collection public?"}
|
||||
non_anonymous: %Schema{
|
||||
type: :boolean,
|
||||
description: "Can voters be publicly identified?"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -174,6 +174,7 @@ def features do
|
|||
end,
|
||||
"pleroma:get:main/ostatus",
|
||||
"pleroma:group_actors",
|
||||
"pleroma:bookmark_folders",
|
||||
if Pleroma.Language.Translation.configured?() do
|
||||
"translation"
|
||||
end,
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
"vcard": "http://www.w3.org/2006/vcard/ns#",
|
||||
"formerRepresentations": "litepub:formerRepresentations",
|
||||
"sm": "http://smithereen.software/ns#",
|
||||
<<<<<<< HEAD
|
||||
"nonAnonymous": "sm:nonAnonymous",
|
||||
"votersCount": "toot:votersCount",
|
||||
"mz": "https://joinmobilizon.org/ns#",
|
||||
|
@ -66,6 +67,9 @@
|
|||
"@id": "schema:location",
|
||||
"@type": "schema:Place"
|
||||
}
|
||||
=======
|
||||
"nonAnonymous": "sm:nonAnonymous"
|
||||
>>>>>>> origin/develop
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
1
test/fixtures/minds-invalid-mention-post.json
vendored
Normal file
1
test/fixtures/minds-invalid-mention-post.json
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"@context":"https://www.w3.org/ns/activitystreams","type":"Note","id":"https://www.minds.com/api/activitypub/users/1198929502760083472/entities/urn:comment:1600926863310458883:0:0:0:1600932467852709903","attributedTo":"https://www.minds.com/api/activitypub/users/1198929502760083472","content":"\u003Ca class=\u0022u-url mention\u0022 href=\u0022https://www.minds.com/lain\u0022 target=\u0022_blank\u0022\u003E@lain\u003C/a\u003E corn syrup.","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://www.minds.com/api/activitypub/users/1198929502760083472/followers","https://lain.com/users/lain"],"tag":[{"type":"Mention","href":"https://www.minds.com/api/activitypub/users/464237775479123984","name":"@lain"}],"url":"https://www.minds.com/newsfeed/1600926863310458883?focusedCommentUrn=urn:comment:1600926863310458883:0:0:0:1600932467852709903","published":"2024-02-04T17:34:03+00:00","inReplyTo":"https://lain.com/objects/36254095-c839-4167-bcc2-b361d5de9198","source":{"content":"@lain corn syrup.","mediaType":"text/plain"}}
|
1
test/fixtures/minds-pleroma-mentioned-post.json
vendored
Normal file
1
test/fixtures/minds-pleroma-mentioned-post.json
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"@context":["https://www.w3.org/ns/activitystreams","https://lain.com/schemas/litepub-0.1.jsonld",{"@language":"und"}],"actor":"https://lain.com/users/lain","attachment":[],"attributedTo":"https://lain.com/users/lain","cc":["https://lain.com/users/lain/followers"],"content":"which diet is the best for cognitive dissonance","context":"https://lain.com/contexts/98c8a130-e813-4797-8973-600e80114317","conversation":"https://lain.com/contexts/98c8a130-e813-4797-8973-600e80114317","id":"https://lain.com/objects/36254095-c839-4167-bcc2-b361d5de9198","published":"2024-02-04T17:11:23.931890Z","repliesCount":11,"sensitive":null,"source":{"content":"which diet is the best for cognitive dissonance","mediaType":"text/plain"},"summary":"","tag":[],"to":["https://www.w3.org/ns/activitystreams#Public"],"type":"Note"}
|
73
test/pleroma/web/activity_pub/mrf/force_mention_test.exs
Normal file
73
test/pleroma/web/activity_pub/mrf/force_mention_test.exs
Normal file
|
@ -0,0 +1,73 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionTest do
|
||||
use Pleroma.DataCase
|
||||
require Pleroma.Constants
|
||||
|
||||
alias Pleroma.Web.ActivityPub.MRF.ForceMention
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
test "adds mention to a reply" do
|
||||
lain =
|
||||
insert(:user, ap_id: "https://lain.com/users/lain", nickname: "lain@lain.com", local: false)
|
||||
|
||||
niobleoum =
|
||||
insert(:user,
|
||||
ap_id: "https://www.minds.com/api/activitypub/users/1198929502760083472",
|
||||
nickname: "niobleoum@minds.com",
|
||||
local: false
|
||||
)
|
||||
|
||||
status = File.read!("test/fixtures/minds-pleroma-mentioned-post.json") |> Jason.decode!()
|
||||
|
||||
status_activity = %{
|
||||
"type" => "Create",
|
||||
"actor" => lain.ap_id,
|
||||
"object" => status
|
||||
}
|
||||
|
||||
Pleroma.Web.ActivityPub.Transmogrifier.handle_incoming(status_activity)
|
||||
|
||||
reply = File.read!("test/fixtures/minds-invalid-mention-post.json") |> Jason.decode!()
|
||||
|
||||
reply_activity = %{
|
||||
"type" => "Create",
|
||||
"actor" => niobleoum.ap_id,
|
||||
"object" => reply
|
||||
}
|
||||
|
||||
{:ok, %{"object" => %{"tag" => tag}}} = ForceMention.filter(reply_activity)
|
||||
|
||||
assert Enum.find(tag, fn %{"href" => href} -> href == lain.ap_id end)
|
||||
end
|
||||
|
||||
test "adds mention to a quote" do
|
||||
user1 = insert(:user, ap_id: "https://misskey.io/users/83ssedkv53")
|
||||
user2 = insert(:user, ap_id: "https://misskey.io/users/7rkrarq81i")
|
||||
|
||||
status = File.read!("test/fixtures/tesla_mock/misskey.io_8vs6wxufd0.json") |> Jason.decode!()
|
||||
|
||||
status_activity = %{
|
||||
"type" => "Create",
|
||||
"actor" => user1.ap_id,
|
||||
"object" => status
|
||||
}
|
||||
|
||||
Pleroma.Web.ActivityPub.Transmogrifier.handle_incoming(status_activity)
|
||||
|
||||
quote_post = File.read!("test/fixtures/quote_post/misskey_quote_post.json") |> Jason.decode!()
|
||||
|
||||
quote_activity = %{
|
||||
"type" => "Create",
|
||||
"actor" => user2.ap_id,
|
||||
"object" => quote_post
|
||||
}
|
||||
|
||||
{:ok, %{"object" => %{"tag" => tag}}} = ForceMention.filter(quote_activity)
|
||||
|
||||
assert Enum.find(tag, fn %{"href" => href} -> href == user1.ap_id end)
|
||||
end
|
||||
end
|
|
@ -25,6 +25,17 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
|
|||
|
||||
setup_all do: clear_config([:instance, :federating], true)
|
||||
|
||||
describe "should_federate?/1" do
|
||||
test "it returns false when the inbox is nil" do
|
||||
refute Publisher.should_federate?(nil, false)
|
||||
refute Publisher.should_federate?(nil, true)
|
||||
end
|
||||
|
||||
test "it returns true when public is true" do
|
||||
assert Publisher.should_federate?(false, true)
|
||||
end
|
||||
end
|
||||
|
||||
describe "gather_webfinger_links/1" do
|
||||
test "it returns links" do
|
||||
user = insert(:user)
|
||||
|
@ -205,6 +216,7 @@ test "publish to url with with different ports" do
|
|||
refute called(Instances.set_reachable(inbox))
|
||||
end
|
||||
|
||||
@tag capture_log: true
|
||||
test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code",
|
||||
Instances,
|
||||
[:passthrough],
|
||||
|
|
|
@ -181,7 +181,7 @@ test "displays correct voters count basing on voters array" do
|
|||
assert result[:voters_count] == 4
|
||||
end
|
||||
|
||||
test "detects that poll is non anonymous" do
|
||||
test "that poll is non anonymous" do
|
||||
object = Object.normalize("https://friends.grishka.me/posts/54642", fetch: true)
|
||||
result = PollView.render("show.json", %{object: object})
|
||||
|
||||
|
|
|
@ -1675,6 +1675,24 @@ def get("https://example.com/empty", _, _, _) do
|
|||
{:ok, %Tesla.Env{status: 200, body: "hello"}}
|
||||
end
|
||||
|
||||
def get("https://friends.grishka.me/posts/54642", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/tesla_mock/smithereen_non_anonymous_poll.json"),
|
||||
headers: activitypub_object_headers()
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://friends.grishka.me/users/1", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/tesla_mock/smithereen_user.json"),
|
||||
headers: activitypub_object_headers()
|
||||
}}
|
||||
end
|
||||
|
||||
def get(url, query, body, headers) do
|
||||
{:error,
|
||||
"Mock response not implemented for GET #{inspect(url)}, #{query}, #{inspect(body)}, #{inspect(headers)}"}
|
||||
|
|
Loading…
Reference in a new issue