Local copy of Pleroma, an ActivityPub server software. Contains modifications running live on fedi.underscore.world
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.

user.ex 44KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564
  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.User do
  5. use Ecto.Schema
  6. import Ecto.Changeset
  7. import Ecto.Query
  8. alias Comeonin.Pbkdf2
  9. alias Ecto.Multi
  10. alias Pleroma.Activity
  11. alias Pleroma.Keys
  12. alias Pleroma.Notification
  13. alias Pleroma.Object
  14. alias Pleroma.Registration
  15. alias Pleroma.Repo
  16. alias Pleroma.RepoStreamer
  17. alias Pleroma.User
  18. alias Pleroma.Web
  19. alias Pleroma.Web.ActivityPub.ActivityPub
  20. alias Pleroma.Web.ActivityPub.Utils
  21. alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
  22. alias Pleroma.Web.OAuth
  23. alias Pleroma.Web.OStatus
  24. alias Pleroma.Web.RelMe
  25. alias Pleroma.Web.Websub
  26. require Logger
  27. @type t :: %__MODULE__{}
  28. @primary_key {:id, Pleroma.FlakeId, autogenerate: true}
  29. # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
  30. @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
  31. @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
  32. @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
  33. schema "users" do
  34. field(:bio, :string)
  35. field(:email, :string)
  36. field(:name, :string)
  37. field(:nickname, :string)
  38. field(:password_hash, :string)
  39. field(:password, :string, virtual: true)
  40. field(:password_confirmation, :string, virtual: true)
  41. field(:following, {:array, :string}, default: [])
  42. field(:ap_id, :string)
  43. field(:avatar, :map)
  44. field(:local, :boolean, default: true)
  45. field(:follower_address, :string)
  46. field(:following_address, :string)
  47. field(:search_rank, :float, virtual: true)
  48. field(:search_type, :integer, virtual: true)
  49. field(:tags, {:array, :string}, default: [])
  50. field(:last_refreshed_at, :naive_datetime_usec)
  51. field(:last_digest_emailed_at, :naive_datetime)
  52. has_many(:notifications, Notification)
  53. has_many(:registrations, Registration)
  54. embeds_one(:info, User.Info)
  55. timestamps()
  56. end
  57. def auth_active?(%User{info: %User.Info{confirmation_pending: true}}),
  58. do: !Pleroma.Config.get([:instance, :account_activation_required])
  59. def auth_active?(%User{}), do: true
  60. def visible_for?(user, for_user \\ nil)
  61. def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
  62. def visible_for?(%User{} = user, for_user) do
  63. auth_active?(user) || superuser?(for_user)
  64. end
  65. def visible_for?(_, _), do: false
  66. def superuser?(%User{local: true, info: %User.Info{is_admin: true}}), do: true
  67. def superuser?(%User{local: true, info: %User.Info{is_moderator: true}}), do: true
  68. def superuser?(_), do: false
  69. def avatar_url(user, options \\ []) do
  70. case user.avatar do
  71. %{"url" => [%{"href" => href} | _]} -> href
  72. _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
  73. end
  74. end
  75. def banner_url(user, options \\ []) do
  76. case user.info.banner do
  77. %{"url" => [%{"href" => href} | _]} -> href
  78. _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
  79. end
  80. end
  81. def profile_url(%User{info: %{source_data: %{"url" => url}}}), do: url
  82. def profile_url(%User{ap_id: ap_id}), do: ap_id
  83. def profile_url(_), do: nil
  84. def ap_id(%User{nickname: nickname}) do
  85. "#{Web.base_url()}/users/#{nickname}"
  86. end
  87. def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
  88. def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
  89. @spec ap_following(User.t()) :: Sring.t()
  90. def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
  91. def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
  92. def user_info(%User{} = user, args \\ %{}) do
  93. following_count =
  94. if args[:following_count],
  95. do: args[:following_count],
  96. else: user.info.following_count || following_count(user)
  97. follower_count =
  98. if args[:follower_count], do: args[:follower_count], else: user.info.follower_count
  99. %{
  100. note_count: user.info.note_count,
  101. locked: user.info.locked,
  102. confirmation_pending: user.info.confirmation_pending,
  103. default_scope: user.info.default_scope
  104. }
  105. |> Map.put(:following_count, following_count)
  106. |> Map.put(:follower_count, follower_count)
  107. end
  108. def set_info_cache(user, args) do
  109. Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
  110. end
  111. @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
  112. def restrict_deactivated(query) do
  113. from(u in query,
  114. where: not fragment("? \\? 'deactivated' AND ?->'deactivated' @> 'true'", u.info, u.info)
  115. )
  116. end
  117. def following_count(%User{following: []}), do: 0
  118. def following_count(%User{} = user) do
  119. user
  120. |> get_friends_query()
  121. |> Repo.aggregate(:count, :id)
  122. end
  123. def remote_user_creation(params) do
  124. params =
  125. params
  126. |> Map.put(:info, params[:info] || %{})
  127. info_cng = User.Info.remote_user_creation(%User.Info{}, params[:info])
  128. changes =
  129. %User{}
  130. |> cast(params, [:bio, :name, :ap_id, :nickname, :avatar])
  131. |> validate_required([:name, :ap_id])
  132. |> unique_constraint(:nickname)
  133. |> validate_format(:nickname, @email_regex)
  134. |> validate_length(:bio, max: 5000)
  135. |> validate_length(:name, max: 100)
  136. |> put_change(:local, false)
  137. |> put_embed(:info, info_cng)
  138. if changes.valid? do
  139. case info_cng.changes[:source_data] do
  140. %{"followers" => followers, "following" => following} ->
  141. changes
  142. |> put_change(:follower_address, followers)
  143. |> put_change(:following_address, following)
  144. _ ->
  145. followers = User.ap_followers(%User{nickname: changes.changes[:nickname]})
  146. changes
  147. |> put_change(:follower_address, followers)
  148. end
  149. else
  150. changes
  151. end
  152. end
  153. def update_changeset(struct, params \\ %{}) do
  154. struct
  155. |> cast(params, [:bio, :name, :avatar, :following])
  156. |> unique_constraint(:nickname)
  157. |> validate_format(:nickname, local_nickname_regex())
  158. |> validate_length(:bio, max: 5000)
  159. |> validate_length(:name, min: 1, max: 100)
  160. end
  161. def upgrade_changeset(struct, params \\ %{}) do
  162. params =
  163. params
  164. |> Map.put(:last_refreshed_at, NaiveDateTime.utc_now())
  165. info_cng =
  166. struct.info
  167. |> User.Info.user_upgrade(params[:info])
  168. struct
  169. |> cast(params, [
  170. :bio,
  171. :name,
  172. :follower_address,
  173. :following_address,
  174. :avatar,
  175. :last_refreshed_at
  176. ])
  177. |> unique_constraint(:nickname)
  178. |> validate_format(:nickname, local_nickname_regex())
  179. |> validate_length(:bio, max: 5000)
  180. |> validate_length(:name, max: 100)
  181. |> put_embed(:info, info_cng)
  182. end
  183. def password_update_changeset(struct, params) do
  184. struct
  185. |> cast(params, [:password, :password_confirmation])
  186. |> validate_required([:password, :password_confirmation])
  187. |> validate_confirmation(:password)
  188. |> put_password_hash
  189. end
  190. @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
  191. def reset_password(%User{id: user_id} = user, data) do
  192. multi =
  193. Multi.new()
  194. |> Multi.update(:user, password_update_changeset(user, data))
  195. |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
  196. |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
  197. case Repo.transaction(multi) do
  198. {:ok, %{user: user} = _} -> set_cache(user)
  199. {:error, _, changeset, _} -> {:error, changeset}
  200. end
  201. end
  202. def register_changeset(struct, params \\ %{}, opts \\ []) do
  203. need_confirmation? =
  204. if is_nil(opts[:need_confirmation]) do
  205. Pleroma.Config.get([:instance, :account_activation_required])
  206. else
  207. opts[:need_confirmation]
  208. end
  209. info_change =
  210. User.Info.confirmation_changeset(%User.Info{}, need_confirmation: need_confirmation?)
  211. changeset =
  212. struct
  213. |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
  214. |> validate_required([:name, :nickname, :password, :password_confirmation])
  215. |> validate_confirmation(:password)
  216. |> unique_constraint(:email)
  217. |> unique_constraint(:nickname)
  218. |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
  219. |> validate_format(:nickname, local_nickname_regex())
  220. |> validate_format(:email, @email_regex)
  221. |> validate_length(:bio, max: 1000)
  222. |> validate_length(:name, min: 1, max: 100)
  223. |> put_change(:info, info_change)
  224. changeset =
  225. if opts[:external] do
  226. changeset
  227. else
  228. validate_required(changeset, [:email])
  229. end
  230. if changeset.valid? do
  231. ap_id = User.ap_id(%User{nickname: changeset.changes[:nickname]})
  232. followers = User.ap_followers(%User{nickname: changeset.changes[:nickname]})
  233. changeset
  234. |> put_password_hash
  235. |> put_change(:ap_id, ap_id)
  236. |> unique_constraint(:ap_id)
  237. |> put_change(:following, [followers])
  238. |> put_change(:follower_address, followers)
  239. else
  240. changeset
  241. end
  242. end
  243. defp autofollow_users(user) do
  244. candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
  245. autofollowed_users =
  246. User.Query.build(%{nickname: candidates, local: true, deactivated: false})
  247. |> Repo.all()
  248. follow_all(user, autofollowed_users)
  249. end
  250. @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
  251. def register(%Ecto.Changeset{} = changeset) do
  252. with {:ok, user} <- Repo.insert(changeset),
  253. {:ok, user} <- autofollow_users(user),
  254. {:ok, user} <- set_cache(user),
  255. {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
  256. {:ok, _} <- try_send_confirmation_email(user) do
  257. {:ok, user}
  258. end
  259. end
  260. def try_send_confirmation_email(%User{} = user) do
  261. if user.info.confirmation_pending &&
  262. Pleroma.Config.get([:instance, :account_activation_required]) do
  263. user
  264. |> Pleroma.Emails.UserEmail.account_confirmation_email()
  265. |> Pleroma.Emails.Mailer.deliver_async()
  266. {:ok, :enqueued}
  267. else
  268. {:ok, :noop}
  269. end
  270. end
  271. def needs_update?(%User{local: true}), do: false
  272. def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
  273. def needs_update?(%User{local: false} = user) do
  274. NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
  275. end
  276. def needs_update?(_), do: true
  277. @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
  278. def maybe_direct_follow(%User{} = follower, %User{local: true, info: %{locked: true}}) do
  279. {:ok, follower}
  280. end
  281. def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
  282. follow(follower, followed)
  283. end
  284. def maybe_direct_follow(%User{} = follower, %User{} = followed) do
  285. if not User.ap_enabled?(followed) do
  286. follow(follower, followed)
  287. else
  288. {:ok, follower}
  289. end
  290. end
  291. @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
  292. @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
  293. def follow_all(follower, followeds) do
  294. followed_addresses =
  295. followeds
  296. |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
  297. |> Enum.map(fn %{follower_address: fa} -> fa end)
  298. q =
  299. from(u in User,
  300. where: u.id == ^follower.id,
  301. update: [
  302. set: [
  303. following:
  304. fragment(
  305. "array(select distinct unnest (array_cat(?, ?)))",
  306. u.following,
  307. ^followed_addresses
  308. )
  309. ]
  310. ],
  311. select: u
  312. )
  313. {1, [follower]} = Repo.update_all(q, [])
  314. Enum.each(followeds, fn followed ->
  315. update_follower_count(followed)
  316. end)
  317. set_cache(follower)
  318. end
  319. def follow(%User{} = follower, %User{info: info} = followed) do
  320. deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
  321. ap_followers = followed.follower_address
  322. cond do
  323. info.deactivated ->
  324. {:error, "Could not follow user: You are deactivated."}
  325. deny_follow_blocked and blocks?(followed, follower) ->
  326. {:error, "Could not follow user: #{followed.nickname} blocked you."}
  327. true ->
  328. if !followed.local && follower.local && !ap_enabled?(followed) do
  329. Websub.subscribe(follower, followed)
  330. end
  331. q =
  332. from(u in User,
  333. where: u.id == ^follower.id,
  334. update: [push: [following: ^ap_followers]],
  335. select: u
  336. )
  337. {1, [follower]} = Repo.update_all(q, [])
  338. follower = maybe_update_following_count(follower)
  339. {:ok, _} = update_follower_count(followed)
  340. set_cache(follower)
  341. end
  342. end
  343. def unfollow(%User{} = follower, %User{} = followed) do
  344. ap_followers = followed.follower_address
  345. if following?(follower, followed) and follower.ap_id != followed.ap_id do
  346. q =
  347. from(u in User,
  348. where: u.id == ^follower.id,
  349. update: [pull: [following: ^ap_followers]],
  350. select: u
  351. )
  352. {1, [follower]} = Repo.update_all(q, [])
  353. follower = maybe_update_following_count(follower)
  354. {:ok, followed} = update_follower_count(followed)
  355. set_cache(follower)
  356. {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
  357. else
  358. {:error, "Not subscribed!"}
  359. end
  360. end
  361. @spec following?(User.t(), User.t()) :: boolean
  362. def following?(%User{} = follower, %User{} = followed) do
  363. Enum.member?(follower.following, followed.follower_address)
  364. end
  365. def locked?(%User{} = user) do
  366. user.info.locked || false
  367. end
  368. def get_by_id(id) do
  369. Repo.get_by(User, id: id)
  370. end
  371. def get_by_ap_id(ap_id) do
  372. Repo.get_by(User, ap_id: ap_id)
  373. end
  374. # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
  375. # of the ap_id and the domain and tries to get that user
  376. def get_by_guessed_nickname(ap_id) do
  377. domain = URI.parse(ap_id).host
  378. name = List.last(String.split(ap_id, "/"))
  379. nickname = "#{name}@#{domain}"
  380. get_cached_by_nickname(nickname)
  381. end
  382. def set_cache({:ok, user}), do: set_cache(user)
  383. def set_cache({:error, err}), do: {:error, err}
  384. def set_cache(%User{} = user) do
  385. Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
  386. Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
  387. Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
  388. {:ok, user}
  389. end
  390. def update_and_set_cache(changeset) do
  391. with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
  392. set_cache(user)
  393. else
  394. e -> e
  395. end
  396. end
  397. def invalidate_cache(user) do
  398. Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
  399. Cachex.del(:user_cache, "nickname:#{user.nickname}")
  400. Cachex.del(:user_cache, "user_info:#{user.id}")
  401. end
  402. def get_cached_by_ap_id(ap_id) do
  403. key = "ap_id:#{ap_id}"
  404. Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
  405. end
  406. def get_cached_by_id(id) do
  407. key = "id:#{id}"
  408. ap_id =
  409. Cachex.fetch!(:user_cache, key, fn _ ->
  410. user = get_by_id(id)
  411. if user do
  412. Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
  413. {:commit, user.ap_id}
  414. else
  415. {:ignore, ""}
  416. end
  417. end)
  418. get_cached_by_ap_id(ap_id)
  419. end
  420. def get_cached_by_nickname(nickname) do
  421. key = "nickname:#{nickname}"
  422. Cachex.fetch!(:user_cache, key, fn ->
  423. user_result = get_or_fetch_by_nickname(nickname)
  424. case user_result do
  425. {:ok, user} -> {:commit, user}
  426. {:error, _error} -> {:ignore, nil}
  427. end
  428. end)
  429. end
  430. def get_cached_by_nickname_or_id(nickname_or_id) do
  431. get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
  432. end
  433. def get_by_nickname(nickname) do
  434. Repo.get_by(User, nickname: nickname) ||
  435. if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
  436. Repo.get_by(User, nickname: local_nickname(nickname))
  437. end
  438. end
  439. def get_by_email(email), do: Repo.get_by(User, email: email)
  440. def get_by_nickname_or_email(nickname_or_email) do
  441. get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
  442. end
  443. def get_cached_user_info(user) do
  444. key = "user_info:#{user.id}"
  445. Cachex.fetch!(:user_cache, key, fn _ -> user_info(user) end)
  446. end
  447. def fetch_by_nickname(nickname) do
  448. ap_try = ActivityPub.make_user_from_nickname(nickname)
  449. case ap_try do
  450. {:ok, user} -> {:ok, user}
  451. _ -> OStatus.make_user(nickname)
  452. end
  453. end
  454. def get_or_fetch_by_nickname(nickname) do
  455. with %User{} = user <- get_by_nickname(nickname) do
  456. {:ok, user}
  457. else
  458. _e ->
  459. with [_nick, _domain] <- String.split(nickname, "@"),
  460. {:ok, user} <- fetch_by_nickname(nickname) do
  461. if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
  462. fetch_initial_posts(user)
  463. end
  464. {:ok, user}
  465. else
  466. _e -> {:error, "not found " <> nickname}
  467. end
  468. end
  469. end
  470. @doc "Fetch some posts when the user has just been federated with"
  471. def fetch_initial_posts(user),
  472. do: PleromaJobQueue.enqueue(:background, __MODULE__, [:fetch_initial_posts, user])
  473. @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
  474. def get_followers_query(%User{} = user, nil) do
  475. User.Query.build(%{followers: user, deactivated: false})
  476. end
  477. def get_followers_query(user, page) do
  478. from(u in get_followers_query(user, nil))
  479. |> User.Query.paginate(page, 20)
  480. end
  481. @spec get_followers_query(User.t()) :: Ecto.Query.t()
  482. def get_followers_query(user), do: get_followers_query(user, nil)
  483. @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
  484. def get_followers(user, page \\ nil) do
  485. q = get_followers_query(user, page)
  486. {:ok, Repo.all(q)}
  487. end
  488. @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
  489. def get_external_followers(user, page \\ nil) do
  490. q =
  491. user
  492. |> get_followers_query(page)
  493. |> User.Query.build(%{external: true})
  494. {:ok, Repo.all(q)}
  495. end
  496. def get_followers_ids(user, page \\ nil) do
  497. q = get_followers_query(user, page)
  498. Repo.all(from(u in q, select: u.id))
  499. end
  500. @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
  501. def get_friends_query(%User{} = user, nil) do
  502. User.Query.build(%{friends: user, deactivated: false})
  503. end
  504. def get_friends_query(user, page) do
  505. from(u in get_friends_query(user, nil))
  506. |> User.Query.paginate(page, 20)
  507. end
  508. @spec get_friends_query(User.t()) :: Ecto.Query.t()
  509. def get_friends_query(user), do: get_friends_query(user, nil)
  510. def get_friends(user, page \\ nil) do
  511. q = get_friends_query(user, page)
  512. {:ok, Repo.all(q)}
  513. end
  514. def get_friends_ids(user, page \\ nil) do
  515. q = get_friends_query(user, page)
  516. Repo.all(from(u in q, select: u.id))
  517. end
  518. @spec get_follow_requests(User.t()) :: {:ok, [User.t()]}
  519. def get_follow_requests(%User{} = user) do
  520. users =
  521. Activity.follow_requests_for_actor(user)
  522. |> join(:inner, [a], u in User, on: a.actor == u.ap_id)
  523. |> where([a, u], not fragment("? @> ?", u.following, ^[user.follower_address]))
  524. |> group_by([a, u], u.id)
  525. |> select([a, u], u)
  526. |> Repo.all()
  527. {:ok, users}
  528. end
  529. def increase_note_count(%User{} = user) do
  530. User
  531. |> where(id: ^user.id)
  532. |> update([u],
  533. set: [
  534. info:
  535. fragment(
  536. "jsonb_set(?, '{note_count}', ((?->>'note_count')::int + 1)::varchar::jsonb, true)",
  537. u.info,
  538. u.info
  539. )
  540. ]
  541. )
  542. |> select([u], u)
  543. |> Repo.update_all([])
  544. |> case do
  545. {1, [user]} -> set_cache(user)
  546. _ -> {:error, user}
  547. end
  548. end
  549. def decrease_note_count(%User{} = user) do
  550. User
  551. |> where(id: ^user.id)
  552. |> update([u],
  553. set: [
  554. info:
  555. fragment(
  556. "jsonb_set(?, '{note_count}', (greatest(0, (?->>'note_count')::int - 1))::varchar::jsonb, true)",
  557. u.info,
  558. u.info
  559. )
  560. ]
  561. )
  562. |> select([u], u)
  563. |> Repo.update_all([])
  564. |> case do
  565. {1, [user]} -> set_cache(user)
  566. _ -> {:error, user}
  567. end
  568. end
  569. def update_note_count(%User{} = user) do
  570. note_count_query =
  571. from(
  572. a in Object,
  573. where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
  574. select: count(a.id)
  575. )
  576. note_count = Repo.one(note_count_query)
  577. info_cng = User.Info.set_note_count(user.info, note_count)
  578. user
  579. |> change()
  580. |> put_embed(:info, info_cng)
  581. |> update_and_set_cache()
  582. end
  583. def maybe_fetch_follow_information(user) do
  584. with {:ok, user} <- fetch_follow_information(user) do
  585. user
  586. else
  587. e ->
  588. Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
  589. user
  590. end
  591. end
  592. def fetch_follow_information(user) do
  593. with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
  594. info_cng = User.Info.follow_information_update(user.info, info)
  595. changeset =
  596. user
  597. |> change()
  598. |> put_embed(:info, info_cng)
  599. update_and_set_cache(changeset)
  600. else
  601. {:error, _} = e -> e
  602. e -> {:error, e}
  603. end
  604. end
  605. def update_follower_count(%User{} = user) do
  606. if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
  607. follower_count_query =
  608. User.Query.build(%{followers: user, deactivated: false})
  609. |> select([u], %{count: count(u.id)})
  610. User
  611. |> where(id: ^user.id)
  612. |> join(:inner, [u], s in subquery(follower_count_query))
  613. |> update([u, s],
  614. set: [
  615. info:
  616. fragment(
  617. "jsonb_set(?, '{follower_count}', ?::varchar::jsonb, true)",
  618. u.info,
  619. s.count
  620. )
  621. ]
  622. )
  623. |> select([u], u)
  624. |> Repo.update_all([])
  625. |> case do
  626. {1, [user]} -> set_cache(user)
  627. _ -> {:error, user}
  628. end
  629. else
  630. {:ok, maybe_fetch_follow_information(user)}
  631. end
  632. end
  633. def maybe_update_following_count(%User{local: false} = user) do
  634. if Pleroma.Config.get([:instance, :external_user_synchronization]) do
  635. {:ok, maybe_fetch_follow_information(user)}
  636. else
  637. user
  638. end
  639. end
  640. def maybe_update_following_count(user), do: user
  641. def remove_duplicated_following(%User{following: following} = user) do
  642. uniq_following = Enum.uniq(following)
  643. if length(following) == length(uniq_following) do
  644. {:ok, user}
  645. else
  646. user
  647. |> update_changeset(%{following: uniq_following})
  648. |> update_and_set_cache()
  649. end
  650. end
  651. @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
  652. def get_users_from_set(ap_ids, local_only \\ true) do
  653. criteria = %{ap_id: ap_ids, deactivated: false}
  654. criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
  655. User.Query.build(criteria)
  656. |> Repo.all()
  657. end
  658. @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
  659. def get_recipients_from_activity(%Activity{recipients: to}) do
  660. User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
  661. |> Repo.all()
  662. end
  663. @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
  664. def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
  665. info = muter.info
  666. info_cng =
  667. User.Info.add_to_mutes(info, ap_id)
  668. |> User.Info.add_to_muted_notifications(info, ap_id, notifications?)
  669. cng =
  670. change(muter)
  671. |> put_embed(:info, info_cng)
  672. update_and_set_cache(cng)
  673. end
  674. def unmute(muter, %{ap_id: ap_id}) do
  675. info = muter.info
  676. info_cng =
  677. User.Info.remove_from_mutes(info, ap_id)
  678. |> User.Info.remove_from_muted_notifications(info, ap_id)
  679. cng =
  680. change(muter)
  681. |> put_embed(:info, info_cng)
  682. update_and_set_cache(cng)
  683. end
  684. def subscribe(subscriber, %{ap_id: ap_id}) do
  685. deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
  686. with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
  687. blocked = blocks?(subscribed, subscriber) and deny_follow_blocked
  688. if blocked do
  689. {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
  690. else
  691. info_cng =
  692. subscribed.info
  693. |> User.Info.add_to_subscribers(subscriber.ap_id)
  694. change(subscribed)
  695. |> put_embed(:info, info_cng)
  696. |> update_and_set_cache()
  697. end
  698. end
  699. end
  700. def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
  701. with %User{} = user <- get_cached_by_ap_id(ap_id) do
  702. info_cng =
  703. user.info
  704. |> User.Info.remove_from_subscribers(unsubscriber.ap_id)
  705. change(user)
  706. |> put_embed(:info, info_cng)
  707. |> update_and_set_cache()
  708. end
  709. end
  710. def block(blocker, %User{ap_id: ap_id} = blocked) do
  711. # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
  712. blocker =
  713. if following?(blocker, blocked) do
  714. {:ok, blocker, _} = unfollow(blocker, blocked)
  715. blocker
  716. else
  717. blocker
  718. end
  719. blocker =
  720. if subscribed_to?(blocked, blocker) do
  721. {:ok, blocker} = unsubscribe(blocked, blocker)
  722. blocker
  723. else
  724. blocker
  725. end
  726. if following?(blocked, blocker) do
  727. unfollow(blocked, blocker)
  728. end
  729. {:ok, blocker} = update_follower_count(blocker)
  730. info_cng =
  731. blocker.info
  732. |> User.Info.add_to_block(ap_id)
  733. cng =
  734. change(blocker)
  735. |> put_embed(:info, info_cng)
  736. update_and_set_cache(cng)
  737. end
  738. # helper to handle the block given only an actor's AP id
  739. def block(blocker, %{ap_id: ap_id}) do
  740. block(blocker, get_cached_by_ap_id(ap_id))
  741. end
  742. def unblock(blocker, %{ap_id: ap_id}) do
  743. info_cng =
  744. blocker.info
  745. |> User.Info.remove_from_block(ap_id)
  746. cng =
  747. change(blocker)
  748. |> put_embed(:info, info_cng)
  749. update_and_set_cache(cng)
  750. end
  751. def mutes?(nil, _), do: false
  752. def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.info.mutes, ap_id)
  753. @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
  754. def muted_notifications?(nil, _), do: false
  755. def muted_notifications?(user, %{ap_id: ap_id}),
  756. do: Enum.member?(user.info.muted_notifications, ap_id)
  757. def blocks?(%User{} = user, %User{} = target) do
  758. blocks_ap_id?(user, target) || blocks_domain?(user, target)
  759. end
  760. def blocks?(nil, _), do: false
  761. def blocks_ap_id?(%User{} = user, %User{} = target) do
  762. Enum.member?(user.info.blocks, target.ap_id)
  763. end
  764. def blocks_ap_id?(_, _), do: false
  765. def blocks_domain?(%User{} = user, %User{} = target) do
  766. domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.info.domain_blocks)
  767. %{host: host} = URI.parse(target.ap_id)
  768. Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
  769. end
  770. def blocks_domain?(_, _), do: false
  771. def subscribed_to?(user, %{ap_id: ap_id}) do
  772. with %User{} = target <- get_cached_by_ap_id(ap_id) do
  773. Enum.member?(target.info.subscribers, user.ap_id)
  774. end
  775. end
  776. @spec muted_users(User.t()) :: [User.t()]
  777. def muted_users(user) do
  778. User.Query.build(%{ap_id: user.info.mutes, deactivated: false})
  779. |> Repo.all()
  780. end
  781. @spec blocked_users(User.t()) :: [User.t()]
  782. def blocked_users(user) do
  783. User.Query.build(%{ap_id: user.info.blocks, deactivated: false})
  784. |> Repo.all()
  785. end
  786. @spec subscribers(User.t()) :: [User.t()]
  787. def subscribers(user) do
  788. User.Query.build(%{ap_id: user.info.subscribers, deactivated: false})
  789. |> Repo.all()
  790. end
  791. def block_domain(user, domain) do
  792. info_cng =
  793. user.info
  794. |> User.Info.add_to_domain_block(domain)
  795. cng =
  796. change(user)
  797. |> put_embed(:info, info_cng)
  798. update_and_set_cache(cng)
  799. end
  800. def unblock_domain(user, domain) do
  801. info_cng =
  802. user.info
  803. |> User.Info.remove_from_domain_block(domain)
  804. cng =
  805. change(user)
  806. |> put_embed(:info, info_cng)
  807. update_and_set_cache(cng)
  808. end
  809. def deactivate_async(user, status \\ true) do
  810. PleromaJobQueue.enqueue(:background, __MODULE__, [:deactivate_async, user, status])
  811. end
  812. def deactivate(%User{} = user, status \\ true) do
  813. info_cng = User.Info.set_activation_status(user.info, status)
  814. with {:ok, friends} <- User.get_friends(user),
  815. {:ok, followers} <- User.get_followers(user),
  816. {:ok, user} <-
  817. user
  818. |> change()
  819. |> put_embed(:info, info_cng)
  820. |> update_and_set_cache() do
  821. Enum.each(followers, &invalidate_cache(&1))
  822. Enum.each(friends, &update_follower_count(&1))
  823. {:ok, user}
  824. end
  825. end
  826. def update_notification_settings(%User{} = user, settings \\ %{}) do
  827. info_changeset = User.Info.update_notification_settings(user.info, settings)
  828. change(user)
  829. |> put_embed(:info, info_changeset)
  830. |> update_and_set_cache()
  831. end
  832. @spec delete(User.t()) :: :ok
  833. def delete(%User{} = user),
  834. do: PleromaJobQueue.enqueue(:background, __MODULE__, [:delete, user])
  835. @spec perform(atom(), User.t()) :: {:ok, User.t()}
  836. def perform(:delete, %User{} = user) do
  837. {:ok, _user} = ActivityPub.delete(user)
  838. # Remove all relationships
  839. {:ok, followers} = User.get_followers(user)
  840. Enum.each(followers, fn follower ->
  841. ActivityPub.unfollow(follower, user)
  842. User.unfollow(follower, user)
  843. end)
  844. {:ok, friends} = User.get_friends(user)
  845. Enum.each(friends, fn followed ->
  846. ActivityPub.unfollow(user, followed)
  847. User.unfollow(user, followed)
  848. end)
  849. delete_user_activities(user)
  850. invalidate_cache(user)
  851. Repo.delete(user)
  852. end
  853. @spec perform(atom(), User.t()) :: {:ok, User.t()}
  854. def perform(:fetch_initial_posts, %User{} = user) do
  855. pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
  856. Enum.each(
  857. # Insert all the posts in reverse order, so they're in the right order on the timeline
  858. Enum.reverse(Utils.fetch_ordered_collection(user.info.source_data["outbox"], pages)),
  859. &Pleroma.Web.Federator.incoming_ap_doc/1
  860. )
  861. {:ok, user}
  862. end
  863. def perform(:deactivate_async, user, status), do: deactivate(user, status)
  864. @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
  865. def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
  866. when is_list(blocked_identifiers) do
  867. Enum.map(
  868. blocked_identifiers,
  869. fn blocked_identifier ->
  870. with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
  871. {:ok, blocker} <- block(blocker, blocked),
  872. {:ok, _} <- ActivityPub.block(blocker, blocked) do
  873. blocked
  874. else
  875. err ->
  876. Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
  877. err
  878. end
  879. end
  880. )
  881. end
  882. @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
  883. def perform(:follow_import, %User{} = follower, followed_identifiers)
  884. when is_list(followed_identifiers) do
  885. Enum.map(
  886. followed_identifiers,
  887. fn followed_identifier ->
  888. with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
  889. {:ok, follower} <- maybe_direct_follow(follower, followed),
  890. {:ok, _} <- ActivityPub.follow(follower, followed) do
  891. followed
  892. else
  893. err ->
  894. Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
  895. err
  896. end
  897. end
  898. )
  899. end
  900. @spec external_users_query() :: Ecto.Query.t()
  901. def external_users_query do
  902. User.Query.build(%{
  903. external: true,
  904. active: true,
  905. order_by: :id
  906. })
  907. end
  908. @spec external_users(keyword()) :: [User.t()]
  909. def external_users(opts \\ []) do
  910. query =
  911. external_users_query()
  912. |> select([u], struct(u, [:id, :ap_id, :info]))
  913. query =
  914. if opts[:max_id],
  915. do: where(query, [u], u.id > ^opts[:max_id]),
  916. else: query
  917. query =
  918. if opts[:limit],
  919. do: limit(query, ^opts[:limit]),
  920. else: query
  921. Repo.all(query)
  922. end
  923. def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers),
  924. do:
  925. PleromaJobQueue.enqueue(:background, __MODULE__, [
  926. :blocks_import,
  927. blocker,
  928. blocked_identifiers
  929. ])
  930. def follow_import(%User{} = follower, followed_identifiers) when is_list(followed_identifiers),
  931. do:
  932. PleromaJobQueue.enqueue(:background, __MODULE__, [
  933. :follow_import,
  934. follower,
  935. followed_identifiers
  936. ])
  937. def delete_user_activities(%User{ap_id: ap_id} = user) do
  938. ap_id
  939. |> Activity.query_by_actor()
  940. |> RepoStreamer.chunk_stream(50)
  941. |> Stream.each(fn activities ->
  942. Enum.each(activities, &delete_activity(&1))
  943. end)
  944. |> Stream.run()
  945. {:ok, user}
  946. end
  947. defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
  948. activity
  949. |> Object.normalize()
  950. |> ActivityPub.delete()
  951. end
  952. defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
  953. user = get_cached_by_ap_id(activity.actor)
  954. object = Object.normalize(activity)
  955. ActivityPub.unlike(user, object)
  956. end
  957. defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
  958. user = get_cached_by_ap_id(activity.actor)
  959. object = Object.normalize(activity)
  960. ActivityPub.unannounce(user, object)
  961. end
  962. defp delete_activity(_activity), do: "Doing nothing"
  963. def html_filter_policy(%User{info: %{no_rich_text: true}}) do
  964. Pleroma.HTML.Scrubber.TwitterText
  965. end
  966. def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
  967. def fetch_by_ap_id(ap_id) do
  968. ap_try = ActivityPub.make_user_from_ap_id(ap_id)
  969. case ap_try do
  970. {:ok, user} ->
  971. {:ok, user}
  972. _ ->
  973. case OStatus.make_user(ap_id) do
  974. {:ok, user} -> {:ok, user}
  975. _ -> {:error, "Could not fetch by AP id"}
  976. end
  977. end
  978. end
  979. def get_or_fetch_by_ap_id(ap_id) do
  980. user = get_cached_by_ap_id(ap_id)
  981. if !is_nil(user) and !User.needs_update?(user) do
  982. {:ok, user}
  983. else
  984. # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
  985. should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
  986. resp = fetch_by_ap_id(ap_id)
  987. if should_fetch_initial do
  988. with {:ok, %User{} = user} <- resp do
  989. fetch_initial_posts(user)
  990. end
  991. end
  992. resp
  993. end
  994. end
  995. @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
  996. def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
  997. if user = get_cached_by_ap_id(uri) do
  998. user
  999. else
  1000. changes =
  1001. %User{info: %User.Info{}}
  1002. |> cast(%{}, [:ap_id, :nickname, :local])
  1003. |> put_change(:ap_id, uri)
  1004. |> put_change(:nickname, nickname)
  1005. |> put_change(:local, true)
  1006. |> put_change(:follower_address, uri <> "/followers")
  1007. {:ok, user} = Repo.insert(changes)
  1008. user
  1009. end
  1010. end
  1011. # AP style
  1012. def public_key_from_info(%{
  1013. source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}
  1014. }) do
  1015. key =
  1016. public_key_pem
  1017. |> :public_key.pem_decode()
  1018. |> hd()
  1019. |> :public_key.pem_entry_decode()
  1020. {:ok, key}
  1021. end
  1022. # OStatus Magic Key
  1023. def public_key_from_info(%{magic_key: magic_key}) when not is_nil(magic_key) do
  1024. {:ok, Pleroma.Web.Salmon.decode_key(magic_key)}
  1025. end
  1026. def public_key_from_info(_), do: {:error, "not found key"}
  1027. def get_public_key_for_ap_id(ap_id) do
  1028. with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
  1029. {:ok, public_key} <- public_key_from_info(user.info) do
  1030. {:ok, public_key}
  1031. else
  1032. _ -> :error
  1033. end
  1034. end
  1035. defp blank?(""), do: nil
  1036. defp blank?(n), do: n
  1037. def insert_or_update_user(data) do
  1038. data
  1039. |> Map.put(:name, blank?(data[:name]) || data[:nickname])
  1040. |> remote_user_creation()
  1041. |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
  1042. |> set_cache()
  1043. end
  1044. def ap_enabled?(%User{local: true}), do: true
  1045. def ap_enabled?(%User{info: info}), do: info.ap_enabled
  1046. def ap_enabled?(_), do: false
  1047. @doc "Gets or fetch a user by uri or nickname."
  1048. @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
  1049. def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
  1050. def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
  1051. # wait a period of time and return newest version of the User structs
  1052. # this is because we have synchronous follow APIs and need to simulate them
  1053. # with an async handshake
  1054. def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
  1055. with %User{} = a <- User.get_cached_by_id(a.id),
  1056. %User{} = b <- User.get_cached_by_id(b.id) do
  1057. {:ok, a, b}
  1058. else
  1059. _e ->
  1060. :error
  1061. end
  1062. end
  1063. def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
  1064. with :ok <- :timer.sleep(timeout),
  1065. %User{} = a <- User.get_cached_by_id(a.id),
  1066. %User{} = b <- User.get_cached_by_id(b.id) do
  1067. {:ok, a, b}
  1068. else
  1069. _e ->
  1070. :error
  1071. end
  1072. end
  1073. def parse_bio(bio) when is_binary(bio) and bio != "" do
  1074. bio
  1075. |> CommonUtils.format_input("text/plain", mentions_format: :full)
  1076. |> elem(0)
  1077. end
  1078. def parse_bio(_), do: ""
  1079. def parse_bio(bio, user) when is_binary(bio) and bio != "" do
  1080. # TODO: get profile URLs other than user.ap_id
  1081. profile_urls = [user.ap_id]
  1082. bio
  1083. |> CommonUtils.format_input("text/plain",
  1084. mentions_format: :full,
  1085. rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
  1086. )
  1087. |> elem(0)
  1088. end
  1089. def parse_bio(_, _), do: ""
  1090. def tag(user_identifiers, tags) when is_list(user_identifiers) do
  1091. Repo.transaction(fn ->
  1092. for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
  1093. end)
  1094. end
  1095. def tag(nickname, tags) when is_binary(nickname),
  1096. do: tag(get_by_nickname(nickname), tags)
  1097. def tag(%User{} = user, tags),
  1098. do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
  1099. def untag(user_identifiers, tags) when is_list(user_identifiers) do
  1100. Repo.transaction(fn ->
  1101. for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
  1102. end)
  1103. end
  1104. def untag(nickname, tags) when is_binary(nickname),
  1105. do: untag(get_by_nickname(nickname), tags)
  1106. def untag(%User{} = user, tags),
  1107. do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
  1108. defp update_tags(%User{} = user, new_tags) do
  1109. {:ok, updated_user} =
  1110. user
  1111. |> change(%{tags: new_tags})
  1112. |> update_and_set_cache()
  1113. updated_user
  1114. end
  1115. defp normalize_tags(tags) do
  1116. [tags]
  1117. |> List.flatten()
  1118. |> Enum.map(&String.downcase(&1))
  1119. end
  1120. defp local_nickname_regex do
  1121. if Pleroma.Config.get([:instance, :extended_nickname_format]) do
  1122. @extended_local_nickname_regex
  1123. else
  1124. @strict_local_nickname_regex
  1125. end
  1126. end
  1127. def local_nickname(nickname_or_mention) do
  1128. nickname_or_mention
  1129. |> full_nickname()
  1130. |> String.split("@")
  1131. |> hd()
  1132. end
  1133. def full_nickname(nickname_or_mention),
  1134. do: String.trim_leading(nickname_or_mention, "@")
  1135. def error_user(ap_id) do
  1136. %User{
  1137. name: ap_id,
  1138. ap_id: ap_id,
  1139. info: %User.Info{},
  1140. nickname: "erroruser@example.com",
  1141. inserted_at: NaiveDateTime.utc_now()
  1142. }
  1143. end
  1144. @spec all_superusers() :: [User.t()]
  1145. def all_superusers do
  1146. User.Query.build(%{super_users: true, local: true, deactivated: false})
  1147. |> Repo.all()
  1148. end
  1149. def showing_reblogs?(%User{} = user, %User{} = target) do
  1150. target.ap_id not in user.info.muted_reblogs
  1151. end
  1152. @doc """
  1153. The function returns a query to get users with no activity for given interval of days.
  1154. Inactive users are those who didn't read any notification, or had any activity where
  1155. the user is the activity's actor, during `inactivity_threshold` days.
  1156. Deactivated users will not appear in this list.
  1157. ## Examples
  1158. iex> Pleroma.User.list_inactive_users()
  1159. %Ecto.Query{}
  1160. """
  1161. @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
  1162. def list_inactive_users_query(inactivity_threshold \\ 7) do
  1163. negative_inactivity_threshold = -inactivity_threshold
  1164. now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
  1165. # Subqueries are not supported in `where` clauses, join gets too complicated.
  1166. has_read_notifications =
  1167. from(n in Pleroma.Notification,
  1168. where: n.seen == true,
  1169. group_by: n.id,
  1170. having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
  1171. select: n.user_id
  1172. )
  1173. |> Pleroma.Repo.all()
  1174. from(u in Pleroma.User,
  1175. left_join: a in Pleroma.Activity,
  1176. on: u.ap_id == a.actor,
  1177. where: not is_nil(u.nickname),
  1178. where: fragment("not (?->'deactivated' @> 'true')", u.info),
  1179. where: u.id not in ^has_read_notifications,
  1180. group_by: u.id,
  1181. having:
  1182. max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
  1183. is_nil(max(a.inserted_at))
  1184. )
  1185. end
  1186. @doc """
  1187. Enable or disable email notifications for user
  1188. ## Examples
  1189. iex> Pleroma.User.switch_email_notifications(Pleroma.User{info: %{email_notifications: %{"digest" => false}}}, "digest", true)
  1190. Pleroma.User{info: %{email_notifications: %{"digest" => true}}}
  1191. iex> Pleroma.User.switch_email_notifications(Pleroma.User{info: %{email_notifications: %{"digest" => true}}}, "digest", false)
  1192. Pleroma.User{info: %{email_notifications: %{"digest" => false}}}
  1193. """
  1194. @spec switch_email_notifications(t(), String.t(), boolean()) ::
  1195. {:ok, t()} | {:error, Ecto.Changeset.t()}
  1196. def switch_email_notifications(user, type, status) do
  1197. info = Pleroma.User.Info.update_email_notifications(user.info, %{type => status})
  1198. change(user)
  1199. |> put_embed(:info, info)
  1200. |> update_and_set_cache()
  1201. end
  1202. @doc """
  1203. Set `last_digest_emailed_at` value for the user to current time
  1204. """
  1205. @spec touch_last_digest_emailed_at(t()) :: t()
  1206. def touch_last_digest_emailed_at(user) do
  1207. now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
  1208. {:ok, updated_user} =
  1209. user
  1210. |> change(%{last_digest_emailed_at: now})
  1211. |> update_and_set_cache()
  1212. updated_user
  1213. end
  1214. @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
  1215. def toggle_confirmation(%User{} = user) do
  1216. need_confirmation? = !user.info.confirmation_pending
  1217. info_changeset =
  1218. User.Info.confirmation_changeset(user.info, need_confirmation: need_confirmation?)
  1219. user
  1220. |> change()
  1221. |> put_embed(:info, info_changeset)
  1222. |> update_and_set_cache()
  1223. end
  1224. def get_mascot(%{info: %{mascot: %{} = mascot}}) when not is_nil(mascot) do
  1225. mascot
  1226. end
  1227. def get_mascot(%{info: %{mascot: mascot}}) when is_nil(mascot) do
  1228. # use instance-default
  1229. config = Pleroma.Config.get([:assets, :mascots])
  1230. default_mascot = Pleroma.Config.get([:assets, :default_mascot])
  1231. mascot = Keyword.get(config, default_mascot)
  1232. %{
  1233. "id" => "default-mascot",
  1234. "url" => mascot[:url],
  1235. "preview_url" => mascot[:url],
  1236. "pleroma" => %{
  1237. "mime_type" => mascot[:mime_type]
  1238. }
  1239. }
  1240. end
  1241. def ensure_keys_present(%User{info: info} = user) do
  1242. if info.keys do
  1243. {:ok, user}
  1244. else
  1245. {:ok, pem} = Keys.generate_rsa_pem()
  1246. user
  1247. |> Ecto.Changeset.change()
  1248. |> Ecto.Changeset.put_embed(:info, User.Info.set_keys(info, pem))
  1249. |> update_and_set_cache()
  1250. end
  1251. end
  1252. def get_ap_ids_by_nicknames(nicknames) do
  1253. from(u in User,
  1254. where: u.nickname in ^nicknames,
  1255. select: u.ap_id
  1256. )
  1257. |> Repo.all()
  1258. end
  1259. defdelegate search(query, opts \\ []), to: User.Search
  1260. defp put_password_hash(
  1261. %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
  1262. ) do
  1263. change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
  1264. end
  1265. defp put_password_hash(changeset), do: changeset
  1266. def is_internal_user?(%User{nickname: nil}), do: true
  1267. def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
  1268. def is_internal_user?(_), do: false
  1269. end