logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://hacktivis.me/git/pleroma.git

activity.ex (11911B)


  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. 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') = associated_object_id((?))",
  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') = associated_object_id(?)",
  61. o.data,
  62. activity.data
  63. ),
  64. as: :object
  65. )
  66. end
  67. def with_preloaded_object(query, join_type \\ :inner) do
  68. query
  69. |> has_named_binding?(:object)
  70. |> if(do: query, else: with_joined_object(query, join_type))
  71. |> preload([activity, object: object], object: object)
  72. end
  73. # Note: applies to fake activities (ActivityPub.Utils.get_notified_from_object/1 etc.)
  74. def user_actor(%Activity{actor: nil}), do: nil
  75. def user_actor(%Activity{} = activity) do
  76. with %User{} <- activity.user_actor do
  77. activity.user_actor
  78. else
  79. _ -> User.get_cached_by_ap_id(activity.actor)
  80. end
  81. end
  82. def with_joined_user_actor(query, join_type \\ :inner) do
  83. join(query, join_type, [activity], u in User,
  84. on: u.ap_id == activity.actor,
  85. as: :user_actor
  86. )
  87. end
  88. def with_preloaded_user_actor(query, join_type \\ :inner) do
  89. query
  90. |> with_joined_user_actor(join_type)
  91. |> preload([activity, user_actor: user_actor], user_actor: user_actor)
  92. end
  93. def with_preloaded_bookmark(query, %User{} = user) do
  94. from([a] in query,
  95. left_join: b in Bookmark,
  96. on: b.user_id == ^user.id and b.activity_id == a.id,
  97. as: :bookmark,
  98. preload: [bookmark: b]
  99. )
  100. end
  101. def with_preloaded_bookmark(query, _), do: query
  102. def with_preloaded_report_notes(query) do
  103. from([a] in query,
  104. left_join: r in ReportNote,
  105. on: a.id == r.activity_id,
  106. as: :report_note,
  107. preload: [report_notes: r]
  108. )
  109. end
  110. def with_preloaded_report_notes(query, _), do: query
  111. def with_set_thread_muted_field(query, %User{} = user) do
  112. from([a] in query,
  113. left_join: tm in ThreadMute,
  114. on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data),
  115. as: :thread_mute,
  116. select: %Activity{a | thread_muted?: not is_nil(tm.id)}
  117. )
  118. end
  119. def with_set_thread_muted_field(query, _), do: query
  120. def get_by_ap_id(ap_id) do
  121. ap_id
  122. |> Queries.by_ap_id()
  123. |> Repo.one()
  124. end
  125. def get_bookmark(%Activity{} = activity, %User{} = user) do
  126. if Ecto.assoc_loaded?(activity.bookmark) do
  127. activity.bookmark
  128. else
  129. Bookmark.get(user.id, activity.id)
  130. end
  131. end
  132. def get_bookmark(_, _), do: nil
  133. def get_report(activity_id) do
  134. opts = %{
  135. type: "Flag",
  136. skip_preload: true,
  137. preload_report_notes: true
  138. }
  139. ActivityPub.fetch_activities_query([], opts)
  140. |> where(id: ^activity_id)
  141. |> Repo.one()
  142. end
  143. def change(struct, params \\ %{}) do
  144. struct
  145. |> cast(params, [:data, :recipients])
  146. |> validate_required([:data])
  147. |> unique_constraint(:ap_id, name: :activities_unique_apid_index)
  148. end
  149. def get_by_ap_id_with_object(ap_id) do
  150. ap_id
  151. |> Queries.by_ap_id()
  152. |> with_preloaded_object(:left)
  153. |> Repo.one()
  154. end
  155. @doc """
  156. Gets activity by ID, doesn't load activities from deactivated actors by default.
  157. """
  158. @spec get_by_id(String.t(), keyword()) :: t() | nil
  159. def get_by_id(id, opts \\ [filter: [:restrict_deactivated]]), do: get_by_id_with_opts(id, opts)
  160. @spec get_by_id_with_user_actor(String.t()) :: t() | nil
  161. def get_by_id_with_user_actor(id), do: get_by_id_with_opts(id, preload: [:user_actor])
  162. @spec get_by_id_with_object(String.t()) :: t() | nil
  163. def get_by_id_with_object(id), do: get_by_id_with_opts(id, preload: [:object])
  164. defp get_by_id_with_opts(id, opts) do
  165. if FlakeId.flake_id?(id) do
  166. query = Queries.by_id(id)
  167. with_filters_query =
  168. if is_list(opts[:filter]) do
  169. Enum.reduce(opts[:filter], query, fn
  170. {:type, type}, acc -> Queries.by_type(acc, type)
  171. :restrict_deactivated, acc -> restrict_deactivated_users(acc)
  172. _, acc -> acc
  173. end)
  174. else
  175. query
  176. end
  177. with_preloads_query =
  178. if is_list(opts[:preload]) do
  179. Enum.reduce(opts[:preload], with_filters_query, fn
  180. :user_actor, acc -> with_preloaded_user_actor(acc)
  181. :object, acc -> with_preloaded_object(acc)
  182. _, acc -> acc
  183. end)
  184. else
  185. with_filters_query
  186. end
  187. Repo.one(with_preloads_query)
  188. end
  189. end
  190. def all_by_ids_with_object(ids) do
  191. Activity
  192. |> where([a], a.id in ^ids)
  193. |> with_preloaded_object()
  194. |> Repo.all()
  195. end
  196. @doc """
  197. Accepts `ap_id` or list of `ap_id`.
  198. Returns a query.
  199. """
  200. @spec create_by_object_ap_id(String.t() | [String.t()]) :: Ecto.Queryable.t()
  201. def create_by_object_ap_id(ap_id) do
  202. ap_id
  203. |> Queries.by_object_id()
  204. |> Queries.by_type("Create")
  205. end
  206. def get_all_create_by_object_ap_id(ap_id) do
  207. ap_id
  208. |> create_by_object_ap_id()
  209. |> Repo.all()
  210. end
  211. def get_create_by_object_ap_id(ap_id) when is_binary(ap_id) do
  212. create_by_object_ap_id(ap_id)
  213. |> restrict_deactivated_users()
  214. |> Repo.one()
  215. end
  216. def get_create_by_object_ap_id(_), do: nil
  217. @doc """
  218. Accepts `ap_id` or list of `ap_id`.
  219. Returns a query.
  220. """
  221. @spec create_by_object_ap_id_with_object(String.t() | [String.t()]) :: Ecto.Queryable.t()
  222. def create_by_object_ap_id_with_object(ap_id) do
  223. ap_id
  224. |> create_by_object_ap_id()
  225. |> with_preloaded_object()
  226. end
  227. def get_create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do
  228. ap_id
  229. |> create_by_object_ap_id_with_object()
  230. |> Repo.one()
  231. end
  232. def get_create_by_object_ap_id_with_object(_), do: nil
  233. @spec create_by_id_with_object(String.t()) :: t() | nil
  234. def create_by_id_with_object(id) do
  235. get_by_id_with_opts(id, preload: [:object], filter: [type: "Create"])
  236. end
  237. defp get_in_reply_to_activity_from_object(%Object{data: %{"inReplyTo" => ap_id}}) do
  238. get_create_by_object_ap_id_with_object(ap_id)
  239. end
  240. defp get_in_reply_to_activity_from_object(_), do: nil
  241. def get_in_reply_to_activity(%Activity{} = activity) do
  242. get_in_reply_to_activity_from_object(Object.normalize(activity, fetch: false))
  243. end
  244. def normalize(%Activity{data: %{"id" => ap_id}}), do: get_by_ap_id_with_object(ap_id)
  245. def normalize(%{"id" => ap_id}), do: get_by_ap_id_with_object(ap_id)
  246. def normalize(ap_id) when is_binary(ap_id), do: get_by_ap_id_with_object(ap_id)
  247. def normalize(_), do: nil
  248. def delete_all_by_object_ap_id(id) when is_binary(id) do
  249. id
  250. |> Queries.by_object_id()
  251. |> Queries.exclude_type("Delete")
  252. |> select([u], u)
  253. |> Repo.delete_all(timeout: :infinity)
  254. |> elem(1)
  255. |> Enum.find(fn
  256. %{data: %{"type" => "Create", "object" => ap_id}} when is_binary(ap_id) -> ap_id == id
  257. %{data: %{"type" => "Create", "object" => %{"id" => ap_id}}} -> ap_id == id
  258. _ -> nil
  259. end)
  260. |> purge_web_resp_cache()
  261. end
  262. def delete_all_by_object_ap_id(_), do: nil
  263. defp purge_web_resp_cache(%Activity{data: %{"id" => id}} = activity) when is_binary(id) do
  264. with %{path: path} <- URI.parse(id) do
  265. @cachex.del(:web_resp_cache, path)
  266. end
  267. activity
  268. end
  269. defp purge_web_resp_cache(activity), do: activity
  270. def follow_accepted?(
  271. %Activity{data: %{"type" => "Follow", "object" => followed_ap_id}} = activity
  272. ) do
  273. with %User{} = follower <- Activity.user_actor(activity),
  274. %User{} = followed <- User.get_cached_by_ap_id(followed_ap_id) do
  275. Pleroma.FollowingRelationship.following?(follower, followed)
  276. else
  277. _ -> false
  278. end
  279. end
  280. def follow_accepted?(_), do: false
  281. def all_by_actor_and_id(actor, status_ids \\ [])
  282. def all_by_actor_and_id(_actor, []), do: []
  283. def all_by_actor_and_id(actor, status_ids) do
  284. Activity
  285. |> where([s], s.id in ^status_ids)
  286. |> where([s], s.actor == ^actor)
  287. |> Repo.all()
  288. end
  289. def follow_requests_for_actor(%User{ap_id: ap_id}) do
  290. ap_id
  291. |> Queries.by_object_id()
  292. |> Queries.by_type("Follow")
  293. |> where([a], fragment("? ->> 'state' = 'pending'", a.data))
  294. end
  295. def following_requests_for_actor(%User{ap_id: ap_id}) do
  296. Queries.by_type("Follow")
  297. |> where([a], fragment("?->>'state' = 'pending'", a.data))
  298. |> where([a], a.actor == ^ap_id)
  299. |> Repo.all()
  300. end
  301. def restrict_deactivated_users(query) do
  302. query
  303. |> join(:inner, [activity], user in User,
  304. as: :user,
  305. on: activity.actor == user.ap_id and user.is_active == true
  306. )
  307. end
  308. defdelegate search(user, query, options \\ []), to: Pleroma.Search.DatabaseSearch
  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