move to 2.5.5
[anni] / lib / pleroma / conversation.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Conversation do
6   alias Pleroma.Conversation.Participation
7   alias Pleroma.Conversation.Participation.RecipientShip
8   alias Pleroma.Object
9   alias Pleroma.Repo
10   alias Pleroma.User
11   use Ecto.Schema
12   import Ecto.Changeset
13
14   schema "conversations" do
15     # This is the context ap id.
16     field(:ap_id, :string)
17     has_many(:participations, Participation)
18     has_many(:users, through: [:participations, :user])
19
20     timestamps()
21   end
22
23   def creation_cng(struct, params) do
24     struct
25     |> cast(params, [:ap_id])
26     |> validate_required([:ap_id])
27     |> unique_constraint(:ap_id)
28   end
29
30   def create_for_ap_id(ap_id) do
31     %__MODULE__{}
32     |> creation_cng(%{ap_id: ap_id})
33     |> Repo.insert(
34       on_conflict: [set: [updated_at: NaiveDateTime.utc_now()]],
35       returning: true,
36       conflict_target: :ap_id
37     )
38   end
39
40   def get_for_ap_id(ap_id) do
41     Repo.get_by(__MODULE__, ap_id: ap_id)
42   end
43
44   def maybe_create_recipientships(participation, activity) do
45     participation = Repo.preload(participation, :recipients)
46
47     if Enum.empty?(participation.recipients) do
48       recipients = User.get_all_by_ap_id(activity.recipients)
49       RecipientShip.create(recipients, participation)
50     end
51   end
52
53   @doc """
54   This will
55   1. Create a conversation if there isn't one already
56   2. Create a participation for all the people involved who don't have one already
57   3. Bump all relevant participations to 'unread'
58   """
59   def create_or_bump_for(activity, opts \\ []) do
60     with true <- Pleroma.Web.ActivityPub.Visibility.is_direct?(activity),
61          "Create" <- activity.data["type"],
62          %Object{} = object <- Object.normalize(activity, fetch: false),
63          true <- object.data["type"] in ["Note", "Question"],
64          ap_id when is_binary(ap_id) and byte_size(ap_id) > 0 <- object.data["context"],
65          {:ok, conversation} <- create_for_ap_id(ap_id) do
66       users = User.get_users_from_set(activity.recipients, local_only: false)
67
68       participations =
69         Enum.map(users, fn user ->
70           invisible_conversation = Enum.any?(users, &User.blocks?(user, &1))
71
72           opts = Keyword.put(opts, :invisible_conversation, invisible_conversation)
73
74           {:ok, participation} =
75             Participation.create_for_user_and_conversation(user, conversation, opts)
76
77           maybe_create_recipientships(participation, activity)
78           participation
79         end)
80
81       {:ok,
82        %{
83          conversation
84          | participations: participations
85        }}
86     else
87       e -> {:error, e}
88     end
89   end
90
91   @doc """
92   This is only meant to be run by a mix task. It creates conversations/participations for all direct messages in the database.
93   """
94   def bump_for_all_activities do
95     stream =
96       Pleroma.Web.ActivityPub.ActivityPub.fetch_direct_messages_query()
97       |> Repo.stream()
98
99     Repo.transaction(
100       fn ->
101         stream
102         |> Enum.each(fn a -> create_or_bump_for(a, read: true) end)
103       end,
104       timeout: :infinity
105     )
106   end
107 end