Add account export
[akkoma] / lib / pleroma / export.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Export do
6 alias Pleroma.Activity
7 alias Pleroma.Bookmark
8 alias Pleroma.User
9 alias Pleroma.Web.ActivityPub.ActivityPub
10 alias Pleroma.Web.ActivityPub.Transmogrifier
11 alias Pleroma.Web.ActivityPub.UserView
12
13 import Ecto.Query
14
15 def run(user) do
16 with {:ok, dir} <- create_dir(),
17 :ok <- actor(dir, user),
18 :ok <- statuses(dir, user),
19 :ok <- likes(dir, user),
20 :ok <- bookmarks(dir, user) do
21 IO.inspect({"DONE", dir})
22 else
23 err -> IO.inspect({"export error", err})
24 end
25 end
26
27 def actor(dir, user) do
28 with {:ok, json} <-
29 UserView.render("user.json", %{user: user})
30 |> Map.merge(%{"likes" => "likes.json", "bookmarks" => "bookmarks.json"})
31 |> Jason.encode() do
32 File.write(dir <> "/actor.json", json)
33 end
34 end
35
36 defp create_dir do
37 datetime = Calendar.NaiveDateTime.Format.iso8601_basic(NaiveDateTime.utc_now())
38 dir = Path.join(System.tmp_dir!(), "archive-" <> datetime)
39
40 with :ok <- File.mkdir(dir), do: {:ok, dir}
41 end
42
43 defp write_header(file, name) do
44 IO.write(
45 file,
46 """
47 {
48 "@context": "https://www.w3.org/ns/activitystreams",
49 "id": "#{name}.json",
50 "type": "OrderedCollection",
51 "orderedItems": [
52 """
53 )
54 end
55
56 defp write(query, dir, name, fun) do
57 path = dir <> "/#{name}.json"
58
59 with {:ok, file} <- File.open(path, [:write, :utf8]),
60 :ok <- write_header(file, name) do
61 counter = :counters.new(1, [])
62
63 query
64 |> Pleroma.RepoStreamer.chunk_stream(100)
65 |> Stream.each(fn items ->
66 Enum.each(items, fn i ->
67 with {:ok, str} <- fun.(i),
68 :ok <- IO.write(file, str <> ",\n") do
69 :counters.add(counter, 1, 1)
70 end
71 end)
72 end)
73 |> Stream.run()
74
75 total = :counters.get(counter, 1)
76
77 with :ok <- :file.pwrite(file, {:eof, -2}, "\n],\n \"totalItems\": #{total}}") do
78 File.close(file)
79 end
80 end
81 end
82
83 def bookmarks(dir, %{id: user_id} = _user) do
84 Bookmark
85 |> where(user_id: ^user_id)
86 |> join(:inner, [b], activity in assoc(b, :activity))
87 |> select([b, a], %{id: b.id, object: fragment("(?)->>'object'", a.data)})
88 |> write(dir, "bookmarks", fn a -> {:ok, "\"#{a.object}\""} end)
89 end
90
91 def likes(dir, user) do
92 user.ap_id
93 |> Activity.Queries.by_actor()
94 |> Activity.Queries.by_type("Like")
95 |> select([like], %{id: like.id, object: fragment("(?)->>'object'", like.data)})
96 |> write(dir, "likes", fn a -> {:ok, "\"#{a.object}\""} end)
97 end
98
99 def statuses(dir, user) do
100 opts =
101 %{}
102 |> Map.put(:type, ["Create", "Announce"])
103 |> Map.put(:blocking_user, user)
104 |> Map.put(:muting_user, user)
105 |> Map.put(:reply_filtering_user, user)
106 |> Map.put(:announce_filtering_user, user)
107 |> Map.put(:user, user)
108
109 [[user.ap_id], User.following(user), Pleroma.List.memberships(user)]
110 |> Enum.concat()
111 |> ActivityPub.fetch_activities_query(opts)
112 |> write(dir, "outbox", fn a ->
113 with {:ok, activity} <- Transmogrifier.prepare_outgoing(a.data) do
114 activity |> Map.delete("@context") |> Jason.encode()
115 end
116 end)
117 end
118 end