Fork of Pleroma with site-specific changes and feature branches https://git.pleroma.social/pleroma/pleroma
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

407 lines
12KB

  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.Activity do
  5. use Ecto.Schema
  6. alias Pleroma.Activity
  7. alias Pleroma.Activity.Queries
  8. alias Pleroma.Bookmark
  9. alias Pleroma.Notification
  10. alias Pleroma.Object
  11. alias Pleroma.Repo
  12. alias Pleroma.ReportNote
  13. alias Pleroma.ThreadMute
  14. alias Pleroma.User
  15. alias Pleroma.Web.ActivityPub.ActivityPub
  16. import Ecto.Changeset
  17. import Ecto.Query
  18. @type t :: %__MODULE__{}
  19. @type actor :: String.t()
  20. @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
  21. @cachex Pleroma.Config.get([:cachex, :provider], Cachex)
  22. schema "activities" do
  23. field(:data, :map)
  24. field(:local, :boolean, default: true)
  25. field(:actor, :string)
  26. field(:recipients, {:array, :string}, default: [])
  27. field(:thread_muted?, :boolean, virtual: true)
  28. # A field that can be used if you need to join some kind of other
  29. # id to order / paginate this field by
  30. field(:pagination_id, :string, virtual: true)
  31. # This is a fake relation,
  32. # do not use outside of with_preloaded_user_actor/with_joined_user_actor
  33. has_one(:user_actor, User, on_delete: :nothing, foreign_key: :id)
  34. # This is a fake relation, do not use outside of with_preloaded_bookmark/get_bookmark
  35. has_one(:bookmark, Bookmark)
  36. # This is a fake relation, do not use outside of with_preloaded_report_notes
  37. has_many(:report_notes, ReportNote)
  38. has_many(:notifications, Notification, on_delete: :delete_all)
  39. # Attention: this is a fake relation, don't try to preload it blindly and expect it to work!
  40. # The foreign key is embedded in a jsonb field.
  41. #
  42. # To use it, you probably want to do an inner join and a preload:
  43. #
  44. # ```
  45. # |> join(:inner, [activity], o in Object,
  46. # on: fragment("(?->>'id') = COALESCE((?)->'object'->> 'id', (?)->>'object')",
  47. # o.data, activity.data, activity.data))
  48. # |> preload([activity, object], [object: object])
  49. # ```
  50. #
  51. # As a convenience, Activity.with_preloaded_object() sets up an inner join and preload for the
  52. # typical case.
  53. has_one(:object, Object, on_delete: :nothing, foreign_key: :id)
  54. timestamps()
  55. end
  56. def with_joined_object(query, join_type \\ :inner) do
  57. join(query, join_type, [activity], o in Object,
  58. on:
  59. fragment(
  60. "(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')",
  61. o.data,
  62. activity.data,
  63. activity.data
  64. ),
  65. as: :object
  66. )
  67. end
  68. def with_preloaded_object(query, join_type \\ :inner) do
  69. query
  70. |> has_named_binding?(:object)
  71. |> if(do: query, else: with_joined_object(query, join_type))
  72. |> preload([activity, object: object], object: object)
  73. end
  74. # Note: applies to fake activities (ActivityPub.Utils.get_notified_from_object/1 etc.)
  75. def user_actor(%Activity{actor: nil}), do: nil
  76. def user_actor(%Activity{} = activity) do
  77. with %User{} <- activity.user_actor do
  78. activity.user_actor
  79. else
  80. _ -> User.get_cached_by_ap_id(activity.actor)
  81. end
  82. end
  83. def with_joined_user_actor(query, join_type \\ :inner) do
  84. join(query, join_type, [activity], u in User,
  85. on: u.ap_id == activity.actor,
  86. as: :user_actor
  87. )
  88. end
  89. def with_preloaded_user_actor(query, join_type \\ :inner) do
  90. query
  91. |> with_joined_user_actor(join_type)
  92. |> preload([activity, user_actor: user_actor], user_actor: user_actor)
  93. end
  94. def with_preloaded_bookmark(query, %User{} = user) do
  95. from([a] in query,
  96. left_join: b in Bookmark,
  97. on: b.user_id == ^user.id and b.activity_id == a.id,
  98. as: :bookmark,
  99. preload: [bookmark: b]
  100. )
  101. end
  102. def with_preloaded_bookmark(query, _), do: query
  103. def with_preloaded_report_notes(query) do
  104. from([a] in query,
  105. left_join: r in ReportNote,
  106. on: a.id == r.activity_id,
  107. as: :report_note,
  108. preload: [report_notes: r]
  109. )
  110. end
  111. def with_preloaded_report_notes(query, _), do: query
  112. def with_set_thread_muted_field(query, %User{} = user) do
  113. from([a] in query,
  114. left_join: tm in ThreadMute,
  115. on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data),
  116. as: :thread_mute,
  117. select: %Activity{a | thread_muted?: not is_nil(tm.id)}
  118. )
  119. end
  120. def with_set_thread_muted_field(query, _), do: query
  121. def get_by_ap_id(ap_id) do
  122. ap_id
  123. |> Queries.by_ap_id()
  124. |> Repo.one()
  125. end
  126. def get_bookmark(%Activity{} = activity, %User{} = user) do
  127. if Ecto.assoc_loaded?(activity.bookmark) do
  128. activity.bookmark
  129. else
  130. Bookmark.get(user.id, activity.id)
  131. end
  132. end
  133. def get_bookmark(_, _), do: nil
  134. def get_report(activity_id) do
  135. opts = %{
  136. type: "Flag",
  137. skip_preload: true,
  138. preload_report_notes: true
  139. }
  140. ActivityPub.fetch_activities_query([], opts)
  141. |> where(id: ^activity_id)
  142. |> Repo.one()
  143. end
  144. def change(struct, params \\ %{}) do
  145. struct
  146. |> cast(params, [:data, :recipients])
  147. |> validate_required([:data])
  148. |> unique_constraint(:ap_id, name: :activities_unique_apid_index)
  149. end
  150. def get_by_ap_id_with_object(ap_id) do
  151. ap_id
  152. |> Queries.by_ap_id()
  153. |> with_preloaded_object(:left)
  154. |> Repo.one()
  155. end
  156. @doc """
  157. Gets activity by ID, doesn't load activities from deactivated actors by default.
  158. """
  159. @spec get_by_id(String.t(), keyword()) :: t() | nil
  160. def get_by_id(id, opts \\ [filter: [:restrict_deactivated]]), do: get_by_id_with_opts(id, opts)
  161. @spec get_by_id_with_user_actor(String.t()) :: t() | nil
  162. def get_by_id_with_user_actor(id), do: get_by_id_with_opts(id, preload: [:user_actor])
  163. @spec get_by_id_with_object(String.t()) :: t() | nil
  164. def get_by_id_with_object(id), do: get_by_id_with_opts(id, preload: [:object])
  165. defp get_by_id_with_opts(id, opts) do
  166. if FlakeId.flake_id?(id) do
  167. query = Queries.by_id(id)
  168. with_filters_query =
  169. if is_list(opts[:filter]) do
  170. Enum.reduce(opts[:filter], query, fn
  171. {:type, type}, acc -> Queries.by_type(acc, type)
  172. :restrict_deactivated, acc -> restrict_deactivated_users(acc)
  173. _, acc -> acc
  174. end)
  175. else
  176. query
  177. end
  178. with_preloads_query =
  179. if is_list(opts[:preload]) do
  180. Enum.reduce(opts[:preload], with_filters_query, fn
  181. :user_actor, acc -> with_preloaded_user_actor(acc)
  182. :object, acc -> with_preloaded_object(acc)
  183. _, acc -> acc
  184. end)
  185. else
  186. with_filters_query
  187. end
  188. Repo.one(with_preloads_query)
  189. end
  190. end
  191. def all_by_ids_with_object(ids) do
  192. Activity
  193. |> where([a], a.id in ^ids)
  194. |> with_preloaded_object()
  195. |> Repo.all()
  196. end
  197. @doc """
  198. Accepts `ap_id` or list of `ap_id`.
  199. Returns a query.
  200. """
  201. @spec create_by_object_ap_id(String.t() | [String.t()]) :: Ecto.Queryable.t()
  202. def create_by_object_ap_id(ap_id) do
  203. ap_id
  204. |> Queries.by_object_id()
  205. |> Queries.by_type("Create")
  206. end
  207. def get_all_create_by_object_ap_id(ap_id) do
  208. ap_id
  209. |> create_by_object_ap_id()
  210. |> Repo.all()
  211. end
  212. def get_create_by_object_ap_id(ap_id) when is_binary(ap_id) do
  213. create_by_object_ap_id(ap_id)
  214. |> restrict_deactivated_users()
  215. |> Repo.one()
  216. end
  217. def get_create_by_object_ap_id(_), do: nil
  218. @doc """
  219. Accepts `ap_id` or list of `ap_id`.
  220. Returns a query.
  221. """
  222. @spec create_by_object_ap_id_with_object(String.t() | [String.t()]) :: Ecto.Queryable.t()
  223. def create_by_object_ap_id_with_object(ap_id) do
  224. ap_id
  225. |> create_by_object_ap_id()
  226. |> with_preloaded_object()
  227. end
  228. def get_create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do
  229. ap_id
  230. |> create_by_object_ap_id_with_object()
  231. |> Repo.one()
  232. end
  233. def get_create_by_object_ap_id_with_object(_), do: nil
  234. @spec create_by_id_with_object(String.t()) :: t() | nil
  235. def create_by_id_with_object(id) do
  236. get_by_id_with_opts(id, preload: [:object], filter: [type: "Create"])
  237. end
  238. defp get_in_reply_to_activity_from_object(%Object{data: %{"inReplyTo" => ap_id}}) do
  239. get_create_by_object_ap_id_with_object(ap_id)
  240. end
  241. defp get_in_reply_to_activity_from_object(_), do: nil
  242. def get_in_reply_to_activity(%Activity{} = activity) do
  243. get_in_reply_to_activity_from_object(Object.normalize(activity, fetch: false))
  244. end
  245. def normalize(%Activity{data: %{"id" => ap_id}}), do: get_by_ap_id_with_object(ap_id)
  246. def normalize(%{"id" => ap_id}), do: get_by_ap_id_with_object(ap_id)
  247. def normalize(ap_id) when is_binary(ap_id), do: get_by_ap_id_with_object(ap_id)
  248. def normalize(_), do: nil
  249. def delete_all_by_object_ap_id(id) when is_binary(id) do
  250. id
  251. |> Queries.by_object_id()
  252. |> Queries.exclude_type("Delete")
  253. |> select([u], u)
  254. |> Repo.delete_all()
  255. |> elem(1)
  256. |> Enum.find(fn
  257. %{data: %{"type" => "Create", "object" => ap_id}} when is_binary(ap_id) -> ap_id == id
  258. %{data: %{"type" => "Create", "object" => %{"id" => ap_id}}} -> ap_id == id
  259. _ -> nil
  260. end)
  261. |> purge_web_resp_cache()
  262. end
  263. def delete_all_by_object_ap_id(_), do: nil
  264. defp purge_web_resp_cache(%Activity{data: %{"id" => id}} = activity) when is_binary(id) do
  265. with %{path: path} <- URI.parse(id) do
  266. @cachex.del(:web_resp_cache, path)
  267. end
  268. activity
  269. end
  270. defp purge_web_resp_cache(activity), do: activity
  271. def follow_accepted?(
  272. %Activity{data: %{"type" => "Follow", "object" => followed_ap_id}} = activity
  273. ) do
  274. with %User{} = follower <- Activity.user_actor(activity),
  275. %User{} = followed <- User.get_cached_by_ap_id(followed_ap_id) do
  276. Pleroma.FollowingRelationship.following?(follower, followed)
  277. else
  278. _ -> false
  279. end
  280. end
  281. def follow_accepted?(_), do: false
  282. def all_by_actor_and_id(actor, status_ids \\ [])
  283. def all_by_actor_and_id(_actor, []), do: []
  284. def all_by_actor_and_id(actor, status_ids) do
  285. Activity
  286. |> where([s], s.id in ^status_ids)
  287. |> where([s], s.actor == ^actor)
  288. |> Repo.all()
  289. end
  290. def follow_requests_for_actor(%User{ap_id: ap_id}) do
  291. ap_id
  292. |> Queries.by_object_id()
  293. |> Queries.by_type("Follow")
  294. |> where([a], fragment("? ->> 'state' = 'pending'", a.data))
  295. end
  296. def following_requests_for_actor(%User{ap_id: ap_id}) do
  297. Queries.by_type("Follow")
  298. |> where([a], fragment("?->>'state' = 'pending'", a.data))
  299. |> where([a], a.actor == ^ap_id)
  300. |> Repo.all()
  301. end
  302. def restrict_deactivated_users(query) do
  303. deactivated_users =
  304. from(u in User.Query.build(%{deactivated: true}), select: u.ap_id)
  305. |> Repo.all()
  306. Activity.Queries.exclude_authors(query, deactivated_users)
  307. end
  308. defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search
  309. def direct_conversation_id(activity, for_user) do
  310. alias Pleroma.Conversation.Participation
  311. with %{data: %{"context" => context}} when is_binary(context) <- activity,
  312. %Pleroma.Conversation{} = conversation <- Pleroma.Conversation.get_for_ap_id(context),
  313. %Participation{id: participation_id} <-
  314. Participation.for_user_and_conversation(for_user, conversation) do
  315. participation_id
  316. else
  317. _ -> nil
  318. end
  319. end
  320. @spec get_by_object_ap_id_with_object(String.t()) :: t() | nil
  321. def get_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do
  322. ap_id
  323. |> Queries.by_object_id()
  324. |> with_preloaded_object()
  325. |> first()
  326. |> Repo.one()
  327. end
  328. def get_by_object_ap_id_with_object(_), do: nil
  329. @spec add_by_params_query(String.t(), String.t(), String.t()) :: Ecto.Query.t()
  330. def add_by_params_query(object_id, actor, target) do
  331. object_id
  332. |> Queries.by_object_id()
  333. |> Queries.by_type("Add")
  334. |> Queries.by_actor(actor)
  335. |> where([a], fragment("?->>'target' = ?", a.data, ^target))
  336. end
  337. end