b84eccd78697b24d2ac910b97f29e8b4a0f88dea
[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 @files ['actor.json', 'outbox.json', 'likes.json', 'bookmarks.json']
16
17 def run(user) do
18 with {:ok, path} <- create_dir(user),
19 :ok <- actor(path, user),
20 :ok <- statuses(path, user),
21 :ok <- likes(path, user),
22 :ok <- bookmarks(path, user),
23 {:ok, zip_path} <- :zip.create('#{path}.zip', @files, cwd: path),
24 {:ok, _} <- File.rm_rf(path) do
25 {:ok, :binary.list_to_bin(zip_path)}
26 end
27 end
28
29 def upload(zip_path) do
30 uploader = Pleroma.Config.get([Pleroma.Upload, :uploader])
31 file_name = zip_path |> String.split("/") |> List.last()
32 id = Ecto.UUID.generate()
33
34 upload = %Pleroma.Upload{
35 id: id,
36 name: file_name,
37 tempfile: zip_path,
38 content_type: "application/zip",
39 path: id <> "/" <> file_name
40 }
41
42 with :ok <- uploader.put_file(upload), :ok <- File.rm(zip_path) do
43 {:ok, upload}
44 end
45 end
46
47 defp actor(dir, user) do
48 with {:ok, json} <-
49 UserView.render("user.json", %{user: user})
50 |> Map.merge(%{"likes" => "likes.json", "bookmarks" => "bookmarks.json"})
51 |> Jason.encode() do
52 File.write(dir <> "/actor.json", json)
53 end
54 end
55
56 defp create_dir(user) do
57 datetime = Calendar.NaiveDateTime.Format.iso8601_basic(NaiveDateTime.utc_now())
58 dir = Path.join(System.tmp_dir!(), "archive-#{user.id}-#{datetime}")
59
60 with :ok <- File.mkdir(dir), do: {:ok, dir}
61 end
62
63 defp write_header(file, name) do
64 IO.write(
65 file,
66 """
67 {
68 "@context": "https://www.w3.org/ns/activitystreams",
69 "id": "#{name}.json",
70 "type": "OrderedCollection",
71 "orderedItems": [
72 """
73 )
74 end
75
76 defp write(query, dir, name, fun) do
77 path = dir <> "/#{name}.json"
78
79 with {:ok, file} <- File.open(path, [:write, :utf8]),
80 :ok <- write_header(file, name) do
81 counter = :counters.new(1, [])
82
83 query
84 |> Pleroma.RepoStreamer.chunk_stream(100)
85 |> Stream.each(fn items ->
86 Enum.each(items, fn i ->
87 with {:ok, str} <- fun.(i),
88 :ok <- IO.write(file, str <> ",\n") do
89 :counters.add(counter, 1, 1)
90 end
91 end)
92 end)
93 |> Stream.run()
94
95 total = :counters.get(counter, 1)
96
97 with :ok <- :file.pwrite(file, {:eof, -2}, "\n],\n \"totalItems\": #{total}}") do
98 File.close(file)
99 end
100 end
101 end
102
103 defp bookmarks(dir, %{id: user_id} = _user) do
104 Bookmark
105 |> where(user_id: ^user_id)
106 |> join(:inner, [b], activity in assoc(b, :activity))
107 |> select([b, a], %{id: b.id, object: fragment("(?)->>'object'", a.data)})
108 |> write(dir, "bookmarks", fn a -> {:ok, "\"#{a.object}\""} end)
109 end
110
111 defp likes(dir, user) do
112 user.ap_id
113 |> Activity.Queries.by_actor()
114 |> Activity.Queries.by_type("Like")
115 |> select([like], %{id: like.id, object: fragment("(?)->>'object'", like.data)})
116 |> write(dir, "likes", fn a -> {:ok, "\"#{a.object}\""} end)
117 end
118
119 defp statuses(dir, user) do
120 opts =
121 %{}
122 |> Map.put(:type, ["Create", "Announce"])
123 |> Map.put(:blocking_user, user)
124 |> Map.put(:muting_user, user)
125 |> Map.put(:reply_filtering_user, user)
126 |> Map.put(:announce_filtering_user, user)
127 |> Map.put(:user, user)
128
129 [[user.ap_id], User.following(user), Pleroma.List.memberships(user)]
130 |> Enum.concat()
131 |> ActivityPub.fetch_activities_query(opts)
132 |> write(dir, "outbox", fn a ->
133 with {:ok, activity} <- Transmogrifier.prepare_outgoing(a.data) do
134 activity |> Map.delete("@context") |> Jason.encode()
135 end
136 end)
137 end
138 end