Merge remote-tracking branch 'origin/develop' into bugfix/elixir-1.15
This commit is contained in:
commit
e43e09a04c
1
changelog.d/3280-fix-emoji-ids.fix
Normal file
1
changelog.d/3280-fix-emoji-ids.fix
Normal file
@ -0,0 +1 @@
|
|||||||
|
Fix Emoji object IDs not always being valid
|
0
changelog.d/user-refresh-rework.skip
Normal file
0
changelog.d/user-refresh-rework.skip
Normal file
1
changelog.d/user-refresh.change
Normal file
1
changelog.d/user-refresh.change
Normal file
@ -0,0 +1 @@
|
|||||||
|
User profile refreshes are now asynchronous
|
0
changelog.d/web_push_actor_regression.skip
Normal file
0
changelog.d/web_push_actor_regression.skip
Normal file
@ -38,6 +38,7 @@ defmodule Pleroma.User do
|
|||||||
alias Pleroma.Web.OAuth
|
alias Pleroma.Web.OAuth
|
||||||
alias Pleroma.Web.RelMe
|
alias Pleroma.Web.RelMe
|
||||||
alias Pleroma.Workers.BackgroundWorker
|
alias Pleroma.Workers.BackgroundWorker
|
||||||
|
alias Pleroma.Workers.UserRefreshWorker
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
@ -2154,20 +2155,20 @@ defmodule Pleroma.User do
|
|||||||
|
|
||||||
def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
|
def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
|
||||||
|
|
||||||
|
@spec get_or_fetch_by_ap_id(String.t()) :: {:ok, User.t()} | {:error, any()}
|
||||||
def get_or_fetch_by_ap_id(ap_id) do
|
def get_or_fetch_by_ap_id(ap_id) do
|
||||||
cached_user = get_cached_by_ap_id(ap_id)
|
with cached_user = %User{} <- get_cached_by_ap_id(ap_id),
|
||||||
|
_ <- maybe_refresh(cached_user) do
|
||||||
|
{:ok, cached_user}
|
||||||
|
else
|
||||||
|
_ -> fetch_by_ap_id(ap_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id)
|
defp maybe_refresh(user) do
|
||||||
|
if needs_update?(user) do
|
||||||
case {cached_user, maybe_fetched_user} do
|
UserRefreshWorker.new(%{"ap_id" => user.ap_id})
|
||||||
{_, {:ok, %User{} = user}} ->
|
|> Oban.insert()
|
||||||
{:ok, user}
|
|
||||||
|
|
||||||
{%User{} = user, _} ->
|
|
||||||
{:ok, user}
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
{:error, :not_found}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -913,9 +913,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||||||
|
|
||||||
def add_emoji_tags(object), do: object
|
def add_emoji_tags(object), do: object
|
||||||
|
|
||||||
defp build_emoji_tag({name, url}) do
|
def build_emoji_tag({name, url}) do
|
||||||
|
url = URI.encode(url)
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"icon" => %{"url" => "#{URI.encode(url)}", "type" => "Image"},
|
"icon" => %{"url" => "#{url}", "type" => "Image"},
|
||||||
"name" => ":" <> name <> ":",
|
"name" => ":" <> name <> ":",
|
||||||
"type" => "Emoji",
|
"type" => "Emoji",
|
||||||
"updated" => "1970-01-01T00:00:00Z",
|
"updated" => "1970-01-01T00:00:00Z",
|
||||||
|
@ -19,69 +19,71 @@ defmodule Pleroma.Web.Push.Impl do
|
|||||||
@body_chars 140
|
@body_chars 140
|
||||||
@types ["Create", "Follow", "Announce", "Like", "Move", "EmojiReact", "Update"]
|
@types ["Create", "Follow", "Announce", "Like", "Move", "EmojiReact", "Update"]
|
||||||
|
|
||||||
@doc "Performs sending notifications for user subscriptions"
|
@doc "Builds webpush notification payloads for the subscriptions enabled by the receiving user"
|
||||||
@spec perform(Notification.t()) :: list(any) | :error | {:error, :unknown_type}
|
@spec build(Notification.t()) ::
|
||||||
def perform(
|
list(%{content: map(), subscription: Subscription.t()}) | []
|
||||||
|
def build(
|
||||||
%{
|
%{
|
||||||
activity: %{data: %{"type" => activity_type}} = activity,
|
activity: %{data: %{"type" => activity_type}} = activity,
|
||||||
user: %User{id: user_id}
|
user_id: user_id
|
||||||
} = notification
|
} = notification
|
||||||
)
|
)
|
||||||
when activity_type in @types do
|
when activity_type in @types do
|
||||||
user = User.get_cached_by_ap_id(notification.activity.data["actor"])
|
notification_actor = User.get_cached_by_ap_id(notification.activity.data["actor"])
|
||||||
|
avatar_url = User.avatar_url(notification_actor)
|
||||||
|
|
||||||
gcm_api_key = Application.get_env(:web_push_encryption, :gcm_api_key)
|
|
||||||
avatar_url = User.avatar_url(user)
|
|
||||||
object = Object.normalize(activity, fetch: false)
|
object = Object.normalize(activity, fetch: false)
|
||||||
user = User.get_cached_by_id(user_id)
|
user = User.get_cached_by_id(user_id)
|
||||||
direct_conversation_id = Activity.direct_conversation_id(activity, user)
|
direct_conversation_id = Activity.direct_conversation_id(activity, user)
|
||||||
|
|
||||||
for subscription <- fetch_subscriptions(user_id),
|
subscriptions = fetch_subscriptions(user_id)
|
||||||
Subscription.enabled?(subscription, notification.type) do
|
|
||||||
%{
|
subscriptions
|
||||||
access_token: subscription.token.token,
|
|> Enum.filter(&Subscription.enabled?(&1, notification.type))
|
||||||
notification_id: notification.id,
|
|> Enum.map(fn subscription ->
|
||||||
notification_type: notification.type,
|
payload =
|
||||||
icon: avatar_url,
|
%{
|
||||||
preferred_locale: "en",
|
access_token: subscription.token.token,
|
||||||
pleroma: %{
|
notification_id: notification.id,
|
||||||
activity_id: notification.activity.id,
|
notification_type: notification.type,
|
||||||
direct_conversation_id: direct_conversation_id
|
icon: avatar_url,
|
||||||
|
preferred_locale: "en",
|
||||||
|
pleroma: %{
|
||||||
|
activity_id: notification.activity.id,
|
||||||
|
direct_conversation_id: direct_conversation_id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|> Map.merge(build_content(notification, notification_actor, object))
|
||||||
|> Map.merge(build_content(notification, user, object))
|
|> Jason.encode!()
|
||||||
|> Jason.encode!()
|
|
||||||
|> push_message(build_sub(subscription), gcm_api_key, subscription)
|
%{payload: payload, subscription: subscription}
|
||||||
end
|
end)
|
||||||
|> (&{:ok, &1}).()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def perform(_) do
|
def build(notif) do
|
||||||
Logger.warning("Unknown notification type")
|
Logger.warning("WebPush: unknown activity type: #{inspect(notif)}")
|
||||||
{:error, :unknown_type}
|
[]
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "Push message to web"
|
@doc "Deliver push notification to the provided webpush subscription"
|
||||||
def push_message(body, sub, api_key, subscription) do
|
@spec deliver(%{payload: String.t(), subscription: Subscription.t()}) :: :ok | :error
|
||||||
try do
|
def deliver(%{payload: payload, subscription: subscription}) do
|
||||||
case WebPushEncryption.send_web_push(body, sub, api_key) do
|
gcm_api_key = Application.get_env(:web_push_encryption, :gcm_api_key)
|
||||||
{:ok, %{status: code}} when code in 400..499 ->
|
formatted_subscription = build_sub(subscription)
|
||||||
Logger.debug("Removing subscription record")
|
|
||||||
Repo.delete!(subscription)
|
|
||||||
:ok
|
|
||||||
|
|
||||||
{:ok, %{status: code}} when code in 200..299 ->
|
case WebPushEncryption.send_web_push(payload, formatted_subscription, gcm_api_key) do
|
||||||
:ok
|
{:ok, %{status: code}} when code in 200..299 ->
|
||||||
|
:ok
|
||||||
|
|
||||||
{:ok, %{status: code}} ->
|
{:ok, %{status: code}} when code in 400..499 ->
|
||||||
Logger.error("Web Push Notification failed with code: #{code}")
|
Logger.debug("Removing subscription record")
|
||||||
:error
|
Repo.delete!(subscription)
|
||||||
|
:ok
|
||||||
|
|
||||||
|
{:ok, %{status: code}} ->
|
||||||
|
Logger.error("Web Push Notification failed with code: #{code}")
|
||||||
|
:error
|
||||||
|
|
||||||
error ->
|
|
||||||
Logger.error("Web Push Notification failed with #{inspect(error)}")
|
|
||||||
:error
|
|
||||||
end
|
|
||||||
rescue
|
|
||||||
error ->
|
error ->
|
||||||
Logger.error("Web Push Notification failed with #{inspect(error)}")
|
Logger.error("Web Push Notification failed with #{inspect(error)}")
|
||||||
:error
|
:error
|
||||||
@ -140,9 +142,7 @@ defmodule Pleroma.Web.Push.Impl do
|
|||||||
|
|
||||||
content_text = content <> "\n"
|
content_text = content <> "\n"
|
||||||
|
|
||||||
options_text =
|
options_text = Enum.map_join(options, "\n", fn x -> "○ #{x["name"]}" end)
|
||||||
Enum.map(options, fn x -> "○ #{x["name"]}" end)
|
|
||||||
|> Enum.join("\n")
|
|
||||||
|
|
||||||
[content_text, options_text]
|
[content_text, options_text]
|
||||||
|> Enum.join("\n")
|
|> Enum.join("\n")
|
||||||
@ -199,19 +199,15 @@ defmodule Pleroma.Web.Push.Impl do
|
|||||||
"New Direct Message"
|
"New Direct Message"
|
||||||
end
|
end
|
||||||
|
|
||||||
def format_title(%{type: type}) do
|
def format_title(%{type: "mention"}), do: "New Mention"
|
||||||
case type do
|
def format_title(%{type: "status"}), do: "New Status"
|
||||||
"mention" -> "New Mention"
|
def format_title(%{type: "follow"}), do: "New Follower"
|
||||||
"status" -> "New Status"
|
def format_title(%{type: "follow_request"}), do: "New Follow Request"
|
||||||
"follow" -> "New Follower"
|
def format_title(%{type: "reblog"}), do: "New Repeat"
|
||||||
"follow_request" -> "New Follow Request"
|
def format_title(%{type: "favourite"}), do: "New Favorite"
|
||||||
"reblog" -> "New Repeat"
|
def format_title(%{type: "update"}), do: "New Update"
|
||||||
"favourite" -> "New Favorite"
|
def format_title(%{type: "pleroma:chat_mention"}), do: "New Chat Message"
|
||||||
"update" -> "New Update"
|
def format_title(%{type: "pleroma:emoji_reaction"}), do: "New Reaction"
|
||||||
"pleroma:chat_mention" -> "New Chat Message"
|
def format_title(%{type: "poll"}), do: "Poll Results"
|
||||||
"pleroma:emoji_reaction" -> "New Reaction"
|
def format_title(%{type: type}), do: "New #{String.capitalize(type || "event")}"
|
||||||
"poll" -> "Poll Results"
|
|
||||||
type -> "New #{String.capitalize(type || "event")}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
14
lib/pleroma/workers/user_refresh_worker.ex
Normal file
14
lib/pleroma/workers/user_refresh_worker.ex
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Workers.UserRefreshWorker do
|
||||||
|
use Pleroma.Workers.WorkerHelper, queue: "background", max_attempts: 1, unique: [period: 300]
|
||||||
|
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
@impl Oban.Worker
|
||||||
|
def perform(%Job{args: %{"ap_id" => ap_id}}) do
|
||||||
|
User.fetch_by_ap_id(ap_id)
|
||||||
|
end
|
||||||
|
end
|
@ -5,6 +5,7 @@
|
|||||||
defmodule Pleroma.Workers.WebPusherWorker do
|
defmodule Pleroma.Workers.WebPusherWorker do
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.Web.Push.Impl
|
||||||
|
|
||||||
use Pleroma.Workers.WorkerHelper, queue: "web_push"
|
use Pleroma.Workers.WorkerHelper, queue: "web_push"
|
||||||
|
|
||||||
@ -15,7 +16,8 @@ defmodule Pleroma.Workers.WebPusherWorker do
|
|||||||
|> Repo.get(notification_id)
|
|> Repo.get(notification_id)
|
||||||
|> Repo.preload([:activity, :user])
|
|> Repo.preload([:activity, :user])
|
||||||
|
|
||||||
Pleroma.Web.Push.Impl.perform(notification)
|
Impl.build(notification)
|
||||||
|
|> Enum.each(&Impl.deliver(&1))
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl Oban.Worker
|
@impl Oban.Worker
|
||||||
|
@ -952,9 +952,16 @@ defmodule Pleroma.UserTest do
|
|||||||
|
|
||||||
{:ok, user} = User.get_or_fetch_by_ap_id("http://mastodon.example.org/users/admin")
|
{:ok, user} = User.get_or_fetch_by_ap_id("http://mastodon.example.org/users/admin")
|
||||||
|
|
||||||
assert user.inbox
|
# Oban job was generated to refresh the stale user
|
||||||
|
assert_enqueued(worker: "Pleroma.Workers.UserRefreshWorker", args: %{"ap_id" => user.ap_id})
|
||||||
|
|
||||||
refute user.last_refreshed_at == orig_user.last_refreshed_at
|
# Run job to refresh the user; just capture its output instead of fetching it again
|
||||||
|
assert {:ok, updated_user} =
|
||||||
|
perform_job(Pleroma.Workers.UserRefreshWorker, %{"ap_id" => user.ap_id})
|
||||||
|
|
||||||
|
assert updated_user.inbox
|
||||||
|
|
||||||
|
refute updated_user.last_refreshed_at == orig_user.last_refreshed_at
|
||||||
end
|
end
|
||||||
|
|
||||||
test "if nicknames clash, the old user gets a prefix with the old id to the nickname" do
|
test "if nicknames clash, the old user gets a prefix with the old id to the nickname" do
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
defmodule Pleroma.Web.ActivityPub.Transmogrifier.EmojiTagBuildingTest do
|
||||||
|
use Pleroma.DataCase, async: true
|
||||||
|
|
||||||
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
|
|
||||||
|
test "it encodes the id to be a valid url" do
|
||||||
|
name = "hanapog"
|
||||||
|
url = "https://misskey.local.live/emojis/hana pog.png"
|
||||||
|
|
||||||
|
tag = Transmogrifier.build_emoji_tag({name, url})
|
||||||
|
|
||||||
|
assert tag["id"] == "https://misskey.local.live/emojis/hana%20pog.png"
|
||||||
|
end
|
||||||
|
end
|
@ -5,6 +5,7 @@
|
|||||||
defmodule Pleroma.Web.Push.ImplTest do
|
defmodule Pleroma.Web.Push.ImplTest do
|
||||||
use Pleroma.DataCase, async: true
|
use Pleroma.DataCase, async: true
|
||||||
|
|
||||||
|
import ExUnit.CaptureLog
|
||||||
import Mox
|
import Mox
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
@ -32,17 +33,6 @@ defmodule Pleroma.Web.Push.ImplTest do
|
|||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
@sub %{
|
|
||||||
endpoint: "https://example.com/example/1234",
|
|
||||||
keys: %{
|
|
||||||
auth: "8eDyX_uCN0XRhSbY5hs7Hg==",
|
|
||||||
p256dh:
|
|
||||||
"BCIWgsnyXDv1VkhqL2P7YRBvdeuDnlwAPT2guNhdIoW3IP7GmHh1SMKPLxRf7x8vJy6ZFK3ol2ohgn_-0yP7QQA="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@api_key "BASgACIHpN1GYgzSRp"
|
|
||||||
@message "@Bob: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sagittis finibus turpis."
|
|
||||||
|
|
||||||
test "performs sending notifications" do
|
test "performs sending notifications" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
user2 = insert(:user)
|
user2 = insert(:user)
|
||||||
@ -68,37 +58,65 @@ defmodule Pleroma.Web.Push.ImplTest do
|
|||||||
type: "mention"
|
type: "mention"
|
||||||
)
|
)
|
||||||
|
|
||||||
assert Impl.perform(notif) == {:ok, [:ok, :ok]}
|
Impl.build(notif)
|
||||||
|
|> Enum.each(fn push -> assert match?(:ok, Impl.deliver(push)) end)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "returns error if notif does not match " do
|
test "returns error if notification activity type does not match" do
|
||||||
assert Impl.perform(%{}) == {:error, :unknown_type}
|
assert capture_log(fn ->
|
||||||
end
|
assert Impl.build(%{}) == []
|
||||||
|
end) =~ "WebPush: unknown activity type"
|
||||||
test "successful message sending" do
|
|
||||||
assert Impl.push_message(@message, @sub, @api_key, %Subscription{}) == :ok
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "fail message sending" do
|
test "fail message sending" do
|
||||||
assert Impl.push_message(
|
user = insert(:user)
|
||||||
@message,
|
|
||||||
Map.merge(@sub, %{endpoint: "https://example.com/example/bad"}),
|
insert(:push_subscription,
|
||||||
@api_key,
|
user: user,
|
||||||
%Subscription{}
|
endpoint: "https://example.com/example/bad",
|
||||||
) == :error
|
data: %{alerts: %{"follow" => true}}
|
||||||
|
)
|
||||||
|
|
||||||
|
other_user = insert(:user)
|
||||||
|
{:ok, _, _, activity} = CommonAPI.follow(user, other_user)
|
||||||
|
|
||||||
|
notif =
|
||||||
|
insert(:notification,
|
||||||
|
user: user,
|
||||||
|
activity: activity,
|
||||||
|
type: "follow"
|
||||||
|
)
|
||||||
|
|
||||||
|
[push] = Impl.build(notif)
|
||||||
|
|
||||||
|
assert Impl.deliver(push) == :error
|
||||||
end
|
end
|
||||||
|
|
||||||
test "delete subscription if result send message between 400..500" do
|
test "delete subscription if result send message between 400..500" do
|
||||||
subscription = insert(:push_subscription)
|
user = insert(:user)
|
||||||
|
|
||||||
assert Impl.push_message(
|
bad_subscription =
|
||||||
@message,
|
insert(:push_subscription,
|
||||||
Map.merge(@sub, %{endpoint: "https://example.com/example/not_found"}),
|
user: user,
|
||||||
@api_key,
|
endpoint: "https://example.com/example/not_found",
|
||||||
subscription
|
data: %{alerts: %{"follow" => true}}
|
||||||
) == :ok
|
)
|
||||||
|
|
||||||
refute Pleroma.Repo.get(Subscription, subscription.id)
|
other_user = insert(:user)
|
||||||
|
{:ok, _, _, activity} = CommonAPI.follow(user, other_user)
|
||||||
|
|
||||||
|
notif =
|
||||||
|
insert(:notification,
|
||||||
|
user: user,
|
||||||
|
activity: activity,
|
||||||
|
type: "follow"
|
||||||
|
)
|
||||||
|
|
||||||
|
[push] = Impl.build(notif)
|
||||||
|
|
||||||
|
assert Impl.deliver(push) == :ok
|
||||||
|
|
||||||
|
refute Pleroma.Repo.get(Subscription, bad_subscription.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "deletes subscription when token has been deleted" do
|
test "deletes subscription when token has been deleted" do
|
||||||
@ -403,4 +421,23 @@ defmodule Pleroma.Web.Push.ImplTest do
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "build/1 notification payload body starts with nickname of actor the notification originated from" do
|
||||||
|
user = insert(:user, nickname: "Bob")
|
||||||
|
user2 = insert(:user, nickname: "Tom")
|
||||||
|
insert(:push_subscription, user: user2, data: %{alerts: %{"mention" => true}})
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(user, %{
|
||||||
|
status: "@Tom Hey are you okay?"
|
||||||
|
})
|
||||||
|
|
||||||
|
{:ok, [notification]} = Notification.create_notifications(activity)
|
||||||
|
|
||||||
|
[push] = Impl.build(notification)
|
||||||
|
|
||||||
|
{:ok, payload} = Jason.decode(push.payload)
|
||||||
|
|
||||||
|
assert String.starts_with?(payload["body"], "@Bob:")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user