Merge pull request 'Various fixes' (#1043) from Oneric/akkoma:varfixes into develop
All checks were successful
ci/woodpecker/push/docs Pipeline was successful
ci/woodpecker/push/publish/4 Pipeline was successful
ci/woodpecker/push/publish/1 Pipeline was successful
ci/woodpecker/push/publish/2 Pipeline was successful

Reviewed-on: #1043
This commit is contained in:
Oneric 2026-01-05 14:38:31 +00:00
commit e326285085
14 changed files with 212 additions and 64 deletions

View file

@ -193,6 +193,12 @@ defmodule Pleroma.Filter do
end
end
defp escape_for_regex(plain_phrase) do
# Escape all active characters:
# .^$*+?()[{\|
Regex.replace(~r/\.\^\$\*\+\?\(\)\[\{\\\|/, plain_phrase, fn m -> "\\" <> m end)
end
@spec compose_regex(User.t() | [t()], format()) :: String.t() | Regex.t() | nil
def compose_regex(user_or_filters, format \\ :postgres)
@ -207,7 +213,7 @@ defmodule Pleroma.Filter do
def compose_regex([_ | _] = filters, format) do
phrases =
filters
|> Enum.map(& &1.phrase)
|> Enum.map(&escape_for_regex(&1.phrase))
|> Enum.join("|")
case format do

View file

@ -1447,7 +1447,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> restrict_muted_reblogs(restrict_muted_reblogs_opts)
|> restrict_instance(opts)
|> restrict_announce_object_actor(opts)
|> restrict_filtered(opts)
|> maybe_restrict_deactivated_users(opts)
|> exclude_poll_votes(opts)
|> exclude_invisible_actors(opts)

View file

@ -57,6 +57,17 @@ defmodule Pleroma.Web.ActivityPub.Builder do
{:ok, data, []}
end
@spec emoji_object!({String.t(), String.t()}) :: map()
def emoji_object!({name, url}) do
# TODO: we should probably send mtime instead of unix epoch time for updated
%{
"icon" => %{"url" => "#{URI.encode(url)}", "type" => "Image"},
"name" => Emoji.maybe_quote(name),
"type" => "Emoji",
"updated" => "1970-01-01T00:00:00Z"
}
end
defp unicode_emoji_react(_object, data, emoji) do
data
|> Map.put("content", emoji)
@ -67,18 +78,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
data
|> Map.put("content", Emoji.maybe_quote(emoji))
|> Map.put("type", "EmojiReact")
|> Map.put("tag", [
%{}
|> Map.put("id", url)
|> Map.put("type", "Emoji")
|> Map.put("name", Emoji.maybe_quote(emoji))
|> Map.put(
"icon",
%{}
|> Map.put("type", "Image")
|> Map.put("url", url)
)
])
|> Map.put("tag", [emoji_object!({emoji, url})])
end
defp remote_custom_emoji_react(

View file

@ -1028,29 +1028,19 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def take_emoji_tags(%User{emoji: emoji}) do
emoji
|> Map.to_list()
|> Enum.map(&build_emoji_tag/1)
|> Enum.map(&Builder.emoji_object!/1)
end
# TODO: we should probably send mtime instead of unix epoch time for updated
def add_emoji_tags(%{"emoji" => emoji} = object) do
tags = object["tag"] || []
out = Enum.map(emoji, &build_emoji_tag/1)
out = Enum.map(emoji, &Builder.emoji_object!/1)
Map.put(object, "tag", tags ++ out)
end
def add_emoji_tags(object), do: object
defp build_emoji_tag({name, url}) do
%{
"icon" => %{"url" => "#{URI.encode(url)}", "type" => "Image"},
"name" => ":" <> name <> ":",
"type" => "Emoji",
"updated" => "1970-01-01T00:00:00Z"
}
end
def set_conversation(object) do
Map.put(object, "conversation", object["context"])
end

View file

@ -516,7 +516,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|> where([activity], fragment("?->>'content' = ?
AND EXISTS (
SELECT FROM jsonb_array_elements(?->'tag') elem
WHERE elem->>'id' ILIKE ?
WHERE COALESCE(elem->'icon'->>'url', '') ILIKE ?
)", activity.data, ^emoji_pattern, activity.data, ^domain_pattern))
else
query

View file

@ -59,6 +59,9 @@ defmodule Pleroma.Web.ApiSpec.PleromaConversationOperation do
end
def update_operation do
recipients_description =
"A list of ids of users that should receive posts to this conversation. This will replace the current list of recipients, so submit the full list. The owner of owner of the conversation will always be part of the set of recipients, though."
%Operation{
tags: ["Conversations"],
summary: "Update conversation",
@ -72,10 +75,21 @@ defmodule Pleroma.Web.ApiSpec.PleromaConversationOperation do
:recipients,
:query,
%Schema{type: :array, items: FlakeID},
"A list of ids of users that should receive posts to this conversation. This will replace the current list of recipients, so submit the full list. The owner of owner of the conversation will always be part of the set of recipients, though.",
required: true
recipients_description
)
],
requestBody:
request_body("Parameters", %Schema{
type: :object,
properties: %{
recipients: %Schema{
type: :array,
items: FlakeID,
nullable: false,
description: recipients_description
}
}
}),
security: [%{"oAuth" => ["write:conversations"]}],
operationId: "PleromaAPI.ConversationController.update",
responses: %{

View file

@ -64,13 +64,22 @@ defmodule Pleroma.Web.PleromaAPI.ConversationController do
end
def update(
%{assigns: %{user: %{id: user_id} = user}} = conn,
%{id: participation_id, recipients: recipients}
%{assigns: %{user: %{id: user_id} = user}, body_params: body_params} = conn,
%{id: participation_id} = params
) do
with %Participation{user_id: ^user_id} = participation <- Participation.get(participation_id),
# OpenApiSpex 3.x prevents Plug's usual parameter premerging
params = Map.merge(body_params, params)
with {_, recipients} when recipients != nil <- {:params, params[:recipients]},
%Participation{user_id: ^user_id} = participation <- Participation.get(participation_id),
{:ok, participation} <- Participation.set_recipients(participation, recipients) do
render(conn, "participation.json", participation: participation, for: user)
else
{:params, _} ->
conn
|> put_status(:bad_request)
|> json(%{"error" => "No paramters passed to update!"})
{:error, message} ->
conn
|> put_status(:bad_request)

View file

@ -87,22 +87,32 @@ defmodule Pleroma.Web.StreamerView do
|> Jason.encode!()
end
def render("follow_relationships_update.json", item, topic) do
def render(
"follow_relationships_update.json",
%{follower: follower, following: following, state: state},
topic
) do
# This is streamed out to the _follower_
# Thus the full details of the follower should be sent out unchecked,
# but details of the following user must obey user-indicated preferences
following_followers = if following.hide_followers_count, do: 0, else: following.follower_count
following_following = if following.hide_follows_count, do: 0, else: following.following_count
%{
stream: [topic],
event: "pleroma:follow_relationships_update",
payload:
%{
state: item.state,
state: state,
follower: %{
id: item.follower.id,
follower_count: item.follower.follower_count,
following_count: item.follower.following_count
id: follower.id,
follower_count: follower.follower_count,
following_count: follower.following_count
},
following: %{
id: item.following.id,
follower_count: item.following.follower_count,
following_count: item.following.following_count
id: following.id,
follower_count: following_followers,
following_count: following_following
}
}
|> Jason.encode!()

View file

@ -62,21 +62,29 @@ defmodule Pleroma.Web.ActivityPub.BuilderTest do
user = insert(:user)
note = insert(:note)
assert {:ok,
%{
"content" => ":dinosaur:",
"type" => "EmojiReact",
"tag" => [
%{
"name" => ":dinosaur:",
"id" => "http://localhost:4001/emoji/dino walking.gif",
"icon" => %{
"type" => "Image",
"url" => "http://localhost:4001/emoji/dino walking.gif"
}
}
]
}, []} = Builder.emoji_react(user, note, ":dinosaur:")
{:ok, %{} = data, []} = Builder.emoji_react(user, note, ":dinosaur:")
assert match?(
%{
"content" => ":dinosaur:",
"type" => "EmojiReact",
"tag" => [
%{
"type" => "Emoji",
"name" => ":dinosaur:",
"icon" => %{
"type" => "Image",
"url" => "http://localhost:4001/emoji/dino%20walking.gif"
}
}
]
},
data
)
emoji = hd(data["tag"])
refute emoji["id"]
end
test "remote custom emoji" do
@ -95,7 +103,6 @@ defmodule Pleroma.Web.ActivityPub.BuilderTest do
"tag" => [
%{
"name" => ":wow:",
"id" => "https://remote/emoji/wow",
"icon" => %{
"type" => "Image",
"url" => "https://remote/emoji/wow"

View file

@ -58,7 +58,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
count: 2,
me: false,
name: "dinosaur",
url: "http://localhost:4001/emoji/dino walking.gif",
url: "http://localhost:4001/emoji/dino%20walking.gif",
account_ids: [other_user.id, user.id]
},
%{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]},
@ -75,7 +75,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
count: 2,
me: true,
name: "dinosaur",
url: "http://localhost:4001/emoji/dino walking.gif",
url: "http://localhost:4001/emoji/dino%20walking.gif",
account_ids: [other_user.id, user.id]
},
%{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]}

View file

@ -76,7 +76,7 @@ defmodule Pleroma.Web.PleromaAPI.ConversationControllerTest do
|> json_response_and_validate_schema(:ok)
end
test "PATCH /api/v1/pleroma/conversations/:id" do
defp patch_test(api_req) do
%{user: user, conn: conn} = oauth_access(["write:conversations"])
other_user = insert(:user)
@ -90,11 +90,9 @@ defmodule Pleroma.Web.PleromaAPI.ConversationControllerTest do
assert [user] == participation.recipients
assert other_user not in participation.recipients
query = "recipients[]=#{user.id}&recipients[]=#{other_user.id}"
result =
conn
|> patch("/api/v1/pleroma/conversations/#{participation.id}?#{query}")
|> api_req.(participation.id, [user, other_user])
|> json_response_and_validate_schema(200)
assert result["id"] == participation.id |> to_string
@ -106,6 +104,29 @@ defmodule Pleroma.Web.PleromaAPI.ConversationControllerTest do
assert other_user in participation.recipients
end
test "PATCH /api/v1/pleroma/conversations/:id with query params" do
patch_test(fn conn, cid, new_users ->
query =
new_users
|> Enum.map(&"recipients[]=#{&1.id}")
|> Enum.join("&")
patch(conn, "/api/v1/pleroma/conversations/#{cid}?#{query}")
end)
end
test "PATCH /api/v1/pleroma/conversations/:id with JSON body" do
patch_test(fn conn, cid, new_users ->
payload = %{
recipients: Enum.map(new_users, & &1.id)
}
conn
|> put_req_header("content-type", "application/json")
|> patch("/api/v1/pleroma/conversations/#{cid}", payload)
end)
end
test "POST /api/v1/pleroma/conversations/read" do
user = insert(:user)
%{user: other_user, conn: conn} = oauth_access(["write:conversations"])

View file

@ -95,7 +95,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionControllerTest do
"name" => "dinosaur",
"count" => 1,
"me" => true,
"url" => "http://localhost:4001/emoji/dino walking.gif",
"url" => "http://localhost:4001/emoji/dino%20walking.gif",
"account_ids" => [other_user.id]
}
]

View file

@ -416,7 +416,7 @@ defmodule Pleroma.Web.StreamerTest do
token: oauth_token
} do
user_id = user.id
other_user = insert(:user)
other_user = insert(:user, hide_follows_count: false)
other_user_id = other_user.id
Streamer.get_topic_and_add_socket("user", user, oauth_token)

View file

@ -0,0 +1,92 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.StreamerViewTest do
use Pleroma.Web.ConnCase, async: true
# import ExUnit.CaptureLog
import Pleroma.Factory
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.StreamerView
describe "follow_relationships_update.json" do
test "shows follower/following count normally" do
other_user = insert(:user)
%{id: following_id} = following = insert(:user)
follower = insert(:user)
{:ok, _, _, _} = CommonAPI.follow(other_user, following)
{:ok, following, follower, _activity} = CommonAPI.follow(following, follower)
result =
StreamerView.render(
"follow_relationships_update.json",
%{follower: follower, following: following, state: :test},
"user:test"
)
{:ok, %{"payload" => payload}} = Jason.decode(result)
{:ok, decoded_payload} = Jason.decode(payload)
# check the payload updating the user that was followed
assert match?(
%{"follower_count" => 1, "following_count" => 1, "id" => ^following_id},
decoded_payload["following"]
)
end
test "hides follower count for :hide_followers_count" do
other_user = insert(:user)
%{id: following_id} = following = insert(:user, %{hide_followers_count: true})
follower = insert(:user)
{:ok, _, _, _} = CommonAPI.follow(other_user, following)
{:ok, following, follower, _activity} = CommonAPI.follow(following, follower)
result =
StreamerView.render(
"follow_relationships_update.json",
%{follower: follower, following: following, state: :test},
"user:test"
)
{:ok, %{"payload" => payload}} = Jason.decode(result)
{:ok, decoded_payload} = Jason.decode(payload)
# check the payload updating the user that was followed
assert match?(
%{"follower_count" => 0, "following_count" => 1, "id" => ^following_id},
decoded_payload["following"]
)
end
test "hides follows count for :hide_follows_count" do
other_user = insert(:user)
%{id: following_id} = following = insert(:user, %{hide_follows_count: true})
follower = insert(:user)
{:ok, _, _, _} = CommonAPI.follow(other_user, following)
{:ok, following, follower, _activity} = CommonAPI.follow(following, follower)
result =
StreamerView.render(
"follow_relationships_update.json",
%{follower: follower, following: following, state: :test},
"user:test"
)
{:ok, %{"payload" => payload}} = Jason.decode(result)
{:ok, decoded_payload} = Jason.decode(payload)
# check the payload updating the user that was followed
assert match?(
%{"follower_count" => 1, "following_count" => 0, "id" => ^following_id},
decoded_payload["following"]
)
end
end
end