1 defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
2 use Pleroma.Web, :controller
4 alias Pleroma.Emoji.Pack
7 Pleroma.Plugs.OAuthScopesPlug,
8 %{scopes: ["write"], admin: true}
21 [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug]
22 when action in [:download_shared, :list_packs, :list_from]
26 Lists packs from the remote instance.
28 Since JS cannot ask remote instances for their packs due to CPS, it has to
31 def list_from(conn, %{"instance_address" => address}) do
32 with {:ok, packs} <- Pack.list_remote_packs(address) do
37 |> put_status(:internal_server_error)
38 |> json(%{error: "The requested instance does not support sharing emoji packs"})
43 Lists the packs available on the instance as JSON.
45 The information is public and does not require authentication. The format is
46 a map of "pack directory name" to pack.json contents.
48 def list_packs(conn, _params) do
51 Pleroma.Config.get!([:instance, :static_dir]),
55 with {:ok, packs} <- Pack.list_local_packs() do
58 {:create_dir, {:error, e}} ->
60 |> put_status(:internal_server_error)
61 |> json(%{error: "Failed to create the emoji pack directory at #{emoji_path}: #{e}"})
65 |> put_status(:internal_server_error)
67 error: "Failed to get the contents of the emoji pack directory at #{emoji_path}: #{e}"
72 def show(conn, %{"name" => name}) do
73 name = String.trim(name)
75 with {:ok, pack} <- Pack.show(name) do
80 |> put_status(:not_found)
81 |> json(%{error: "Pack #{name} does not exist"})
83 {:error, :empty_values} ->
85 |> put_status(:bad_request)
86 |> json(%{error: "pack name cannot be empty"})
91 An endpoint for other instances (via admin UI) or users (via browser)
92 to download packs that the instance shares.
94 def download_shared(conn, %{"name" => name}) do
95 with {:ok, archive} <- Pack.download(name) do
96 send_download(conn, {:binary, archive}, filename: "#{name}.zip")
98 {:can_download?, _} ->
100 |> put_status(:forbidden)
103 "Pack #{name} cannot be downloaded from this instance, either pack sharing was disabled for this pack or some files are missing"
108 |> put_status(:not_found)
109 |> json(%{error: "Pack #{name} does not exist"})
114 An admin endpoint to request downloading and storing a pack named `pack_name` from the instance
117 If the requested instance's admin chose to share the pack, it will be downloaded
118 from that instance, otherwise it will be downloaded from the fallback source, if there is one.
120 def download_from(conn, %{"instance_address" => address, "pack_name" => name} = params) do
121 with :ok <- Pack.download_from_source(name, address, params["as"]) do
126 |> put_status(:internal_server_error)
127 |> json(%{error: "The requested instance does not support sharing emoji packs"})
131 |> put_status(:internal_server_error)
132 |> json(%{error: "SHA256 for the pack doesn't match the one sent by the server"})
136 |> put_status(:internal_server_error)
142 Creates an empty pack named `name` which then can be updated via the admin UI.
144 def create(conn, %{"name" => name}) do
145 name = String.trim(name)
147 with :ok <- Pack.create(name) do
152 |> put_status(:conflict)
153 |> json(%{error: "A pack named \"#{name}\" already exists"})
155 {:error, :empty_values} ->
157 |> put_status(:bad_request)
158 |> json(%{error: "pack name cannot be empty"})
163 :internal_server_error,
164 "Unexpected error occurred while creating pack."
170 Deletes the pack `name` and all it's files.
172 def delete(conn, %{"name" => name}) do
173 name = String.trim(name)
175 with {:ok, deleted} when deleted != [] <- Pack.delete(name) do
180 |> put_status(:not_found)
181 |> json(%{error: "Pack #{name} does not exist"})
183 {:error, :empty_values} ->
185 |> put_status(:bad_request)
186 |> json(%{error: "pack name cannot be empty"})
190 |> put_status(:internal_server_error)
191 |> json(%{error: "Couldn't delete the pack #{name}"})
196 An endpoint to update `pack_names`'s metadata.
198 `new_data` is the new metadata for the pack, that will replace the old metadata.
200 def update_metadata(conn, %{"pack_name" => name, "new_data" => new_data}) do
201 with {:ok, pack} <- Pack.update_metadata(name, new_data) do
202 json(conn, pack.pack)
204 {:has_all_files?, _} ->
206 |> put_status(:bad_request)
207 |> json(%{error: "The fallback archive does not have all files specified in pack.json"})
212 :internal_server_error,
213 "Unexpected error occurred while updating pack metadata."
219 Updates a file in a pack.
221 Updating can mean three things:
223 - `add` adds an emoji named `shortcode` to the pack `pack_name`,
224 that means that the emoji file needs to be uploaded with the request
225 (thus requiring it to be a multipart request) and be named `file`.
226 There can also be an optional `filename` that will be the new emoji file name
227 (if it's not there, the name will be taken from the uploaded file).
228 - `update` changes emoji shortcode (from `shortcode` to `new_shortcode` or moves the file
229 (from the current filename to `new_filename`)
230 - `remove` removes the emoji named `shortcode` and it's associated file
236 %{"pack_name" => pack_name, "action" => "add"} = params
238 filename = params["filename"] || get_filename(params["file"])
239 shortcode = params["shortcode"] || Path.basename(filename, Path.extname(filename))
241 with {:ok, pack} <- Pack.add_file(pack_name, shortcode, filename, params["file"]) do
242 json(conn, pack.files)
246 |> put_status(:conflict)
247 |> json(%{error: "An emoji with the \"#{shortcode}\" shortcode already exists"})
251 |> put_status(:bad_request)
252 |> json(%{error: "pack \"#{pack_name}\" is not found"})
254 {:error, :empty_values} ->
256 |> put_status(:bad_request)
257 |> json(%{error: "pack name, shortcode or filename cannot be empty"})
262 :internal_server_error,
263 "Unexpected error occurred while adding file to pack."
269 def update_file(conn, %{
270 "pack_name" => pack_name,
271 "action" => "remove",
272 "shortcode" => shortcode
274 with {:ok, pack} <- Pack.remove_file(pack_name, shortcode) do
275 json(conn, pack.files)
279 |> put_status(:bad_request)
280 |> json(%{error: "Emoji \"#{shortcode}\" does not exist"})
284 |> put_status(:bad_request)
285 |> json(%{error: "pack \"#{pack_name}\" is not found"})
287 {:error, :empty_values} ->
289 |> put_status(:bad_request)
290 |> json(%{error: "pack name or shortcode cannot be empty"})
295 :internal_server_error,
296 "Unexpected error occurred while removing file from pack."
304 %{"pack_name" => name, "action" => "update", "shortcode" => shortcode} = params
306 new_shortcode = params["new_shortcode"]
307 new_filename = params["new_filename"]
308 force = params["force"] == true
310 with {:ok, pack} <- Pack.update_file(name, shortcode, new_shortcode, new_filename, force) do
311 json(conn, pack.files)
315 |> put_status(:bad_request)
316 |> json(%{error: "Emoji \"#{shortcode}\" does not exist"})
320 |> put_status(:conflict)
323 "New shortcode \"#{new_shortcode}\" is already used. If you want to override emoji use 'force' option"
328 |> put_status(:bad_request)
329 |> json(%{error: "pack \"#{name}\" is not found"})
331 {:error, :empty_values} ->
333 |> put_status(:bad_request)
334 |> json(%{error: "new_shortcode or new_filename cannot be empty"})
339 :internal_server_error,
340 "Unexpected error occurred while updating file in pack."
345 def update_file(conn, %{"action" => action}) do
347 |> put_status(:bad_request)
348 |> json(%{error: "Unknown action: #{action}"})
352 Imports emoji from the filesystem.
354 Importing means checking all the directories in the
355 `$instance_static/emoji/` for directories which do not have
356 `pack.json`. If one has an emoji.txt file, that file will be used
357 to create a `pack.json` file with it's contents. If the directory has
358 neither, all the files with specific configured extenstions will be
359 assumed to be emojis and stored in the new `pack.json` file.
362 def import_from_fs(conn, _params) do
363 with {:ok, names} <- Pack.import_from_filesystem() do
366 {:error, :not_writable} ->
368 |> put_status(:internal_server_error)
369 |> json(%{error: "Error: emoji pack directory must be writable"})
373 |> put_status(:internal_server_error)
374 |> json(%{error: "Error accessing emoji pack directory"})
378 defp get_filename(%Plug.Upload{filename: filename}), do: filename
379 defp get_filename(url) when is_binary(url), do: Path.basename(url)