aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorRoger Braun <roger@rogerbraun.net>2017-11-12 14:23:39 +0100
committerRoger Braun <roger@rogerbraun.net>2017-11-12 14:25:15 +0100
commit5fc6e9d467f69af155627cccaa27616fe7ffc61f (patch)
treef9f5cf0d8c93d22eeb232fb3579d84d1c732ef71 /lib
parentd293ceb1b535ab749fa841e18c1fa2ee63972afb (diff)
parent08bc31674218cd7ce634b008b7766b48e49c52e3 (diff)
downloadpleroma-5fc6e9d467f69af155627cccaa27616fe7ffc61f.tar.gz
Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into develop
Diffstat (limited to 'lib')
-rw-r--r--lib/pleroma/notification.ex31
-rw-r--r--lib/pleroma/upload.ex35
-rw-r--r--lib/pleroma/web/mastodon_api/mastodon_api_controller.ex116
-rw-r--r--lib/pleroma/web/router.ex4
-rw-r--r--lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex3
5 files changed, 166 insertions, 23 deletions
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index 00a382f31..039cc7312 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -36,6 +36,37 @@ defmodule Pleroma.Notification do
Repo.all(query)
end
+ def get(%{id: user_id} = _user, id) do
+ query = from n in Notification,
+ where: n.id == ^id,
+ preload: [:activity]
+
+ notification = Repo.one(query)
+ case notification do
+ %{user_id: ^user_id} ->
+ {:ok, notification}
+ _ ->
+ {:error, "Cannot get notification"}
+ end
+ end
+
+ def clear(user) do
+ query = from n in Notification,
+ where: n.user_id == ^user.id
+
+ Repo.delete_all(query)
+ end
+
+ def dismiss(%{id: user_id} = _user, id) do
+ notification = Repo.get(Notification, id)
+ case notification do
+ %{user_id: ^user_id} ->
+ Repo.delete(notification)
+ _ ->
+ {:error, "Cannot dismiss notification"}
+ end
+ end
+
def create_notifications(%Activity{id: id, data: %{"to" => to, "type" => type}} = activity) when type in ["Create", "Like", "Announce", "Follow"] do
users = User.get_notified_from_activity(activity)
diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex
index d5723f5de..3567c6c88 100644
--- a/lib/pleroma/upload.ex
+++ b/lib/pleroma/upload.ex
@@ -9,9 +9,8 @@ defmodule Pleroma.Upload do
File.cp!(file.path, result_file)
# fix content type on some image uploads
- matches = Regex.named_captures(~r/\.(?<ext>(jpg|jpeg|png|gif))$/i, file.filename)
- content_type = if file.content_type == "application/octet-stream" and matches do
- if matches["ext"] == "jpg", do: "image/jpeg", else: "image/#{matches["ext"]}"
+ content_type = if file.content_type == "application/octet-stream" do
+ get_content_type(file.path)
else
file.content_type
end
@@ -61,4 +60,34 @@ defmodule Pleroma.Upload do
defp url_for(file) do
"#{Web.base_url()}/media/#{file}"
end
+
+ def get_content_type(file) do
+ match = File.open(file, [:read], fn(f) ->
+ case IO.binread(f, 8) do
+ <<0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a>> ->
+ "image/png"
+ <<0x47, 0x49, 0x46, 0x38, _, 0x61, _, _>> ->
+ "image/gif"
+ <<0xff, 0xd8, 0xff, _, _, _, _, _>> ->
+ "image/jpeg"
+ <<0x1a, 0x45, 0xdf, 0xa3, _, _, _, _>> ->
+ "video/webm"
+ <<0x00, 0x00, 0x00, _, 0x66, 0x74, 0x79, 0x70>> ->
+ "video/mp4"
+ <<0x49, 0x44, 0x33, _, _, _, _, _>> ->
+ "audio/mpeg"
+ <<0x4f, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00>> ->
+ "audio/ogg"
+ <<0x52, 0x49, 0x46, 0x46, _, _, _, _>> ->
+ "audio/wav"
+ _ ->
+ "application/octet-stream"
+ end
+ end)
+
+ case match do
+ {:ok, type} -> type
+ _e -> "application/octet-stream"
+ end
+ end
end
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index 83003b917..41fbe55e7 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -24,6 +24,57 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
end
+ def update_credentials(%{assigns: %{user: user}} = conn, params) do
+ params = if bio = params["note"] do
+ Map.put(params, "bio", bio)
+ else
+ params
+ end
+
+ params = if name = params["display_name"] do
+ Map.put(params, "name", name)
+ else
+ params
+ end
+
+ user = if avatar = params["avatar"] do
+ with %Plug.Upload{} <- avatar,
+ {:ok, object} <- ActivityPub.upload(avatar),
+ change = Ecto.Changeset.change(user, %{avatar: object.data}),
+ {:ok, user} = Repo.update(change) do
+ user
+ else
+ _e -> user
+ end
+ else
+ user
+ end
+
+ user = if banner = params["header"] do
+ with %Plug.Upload{} <- banner,
+ {:ok, object} <- ActivityPub.upload(banner),
+ new_info <- Map.put(user.info, "banner", object.data),
+ change <- User.info_changeset(user, %{info: new_info}),
+ {:ok, user} <- Repo.update(change) do
+ user
+ else
+ _e -> user
+ end
+ else
+ user
+ end
+
+ with changeset <- User.update_changeset(user, params),
+ {:ok, user} <- Repo.update(changeset) do
+ json conn, AccountView.render("account.json", %{user: user})
+ else
+ _e ->
+ conn
+ |> put_status(403)
+ |> json(%{error: "Invalid request"})
+ end
+ end
+
def verify_credentials(%{assigns: %{user: user}} = conn, params) do
account = AccountView.render("account.json", %{user: user})
json(conn, account)
@@ -194,23 +245,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
def notifications(%{assigns: %{user: user}} = conn, params) do
notifications = Notification.for_user(user, params)
- result = Enum.map(notifications, fn (%{id: id, activity: activity, inserted_at: created_at}) ->
- actor = User.get_cached_by_ap_id(activity.data["actor"])
- created_at = NaiveDateTime.to_iso8601(created_at)
- |> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
- case activity.data["type"] do
- "Create" ->
- %{id: id, type: "mention", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: activity, for: user})}
- "Like" ->
- liked_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"])
- %{id: id, type: "favourite", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: liked_activity, for: user})}
- "Announce" ->
- announced_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"])
- %{id: id, type: "reblog", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: announced_activity, for: user})}
- "Follow" ->
- %{id: id, type: "follow", created_at: created_at, account: AccountView.render("account.json", %{user: actor})}
- _ -> nil
- end
+ result = Enum.map(notifications, fn x ->
+ render_notification(user, x)
end)
|> Enum.filter(&(&1))
@@ -219,6 +255,33 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|> json(result)
end
+ def get_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
+ with {:ok, notification} <- Notification.get(user, id) do
+ json(conn, render_notification(user, notification))
+ else
+ {:error, reason} ->
+ conn
+ |> put_resp_content_type("application/json")
+ |> send_resp(403, Poison.encode!(%{"error" => reason}))
+ end
+ end
+
+ def clear_notifications(%{assigns: %{user: user}} = conn, _params) do
+ Notification.clear(user)
+ json(conn, %{})
+ end
+
+ def dismiss_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
+ with {:ok, _notif} <- Notification.dismiss(user, id) do
+ json(conn, %{})
+ else
+ {:error, reason} ->
+ conn
+ |> put_resp_content_type("application/json")
+ |> send_resp(403, Poison.encode!(%{"error" => reason}))
+ end
+ end
+
def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do
id = List.wrap(id)
q = from u in User,
@@ -527,4 +590,23 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
Logger.debug("Unimplemented, returning an empty array")
json(conn, [])
end
+
+ defp render_notification(user, %{id: id, activity: activity, inserted_at: created_at} = _params) do
+ actor = User.get_cached_by_ap_id(activity.data["actor"])
+ created_at = NaiveDateTime.to_iso8601(created_at)
+ |> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
+ case activity.data["type"] do
+ "Create" ->
+ %{id: id, type: "mention", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: activity, for: user})}
+ "Like" ->
+ liked_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"])
+ %{id: id, type: "favourite", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: liked_activity, for: user})}
+ "Announce" ->
+ announced_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"])
+ %{id: id, type: "reblog", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: announced_activity, for: user})}
+ "Follow" ->
+ %{id: id, type: "follow", created_at: created_at, account: AccountView.render("account.json", %{user: actor})}
+ _ -> nil
+ end
+ end
end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 5c94ba392..8fe1d8ec6 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -60,6 +60,7 @@ defmodule Pleroma.Web.Router do
scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through :authenticated_api
+ patch "/accounts/update_credentials", MastodonAPIController, :update_credentials
get "/accounts/verify_credentials", MastodonAPIController, :verify_credentials
get "/accounts/relationships", MastodonAPIController, :relationships
get "/accounts/search", MastodonAPIController, :account_search
@@ -89,7 +90,10 @@ defmodule Pleroma.Web.Router do
post "/statuses/:id/favourite", MastodonAPIController, :fav_status
post "/statuses/:id/unfavourite", MastodonAPIController, :unfav_status
+ post "/notifications/clear", MastodonAPIController, :clear_notifications
+ post "/notifications/dismiss", MastodonAPIController, :dismiss_notification
get "/notifications", MastodonAPIController, :notifications
+ get "/notifications/:id", MastodonAPIController, :get_notification
post "/media", MastodonAPIController, :upload
end
diff --git a/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex b/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex
index a05680205..334dc4f98 100644
--- a/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex
+++ b/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex
@@ -9,9 +9,6 @@
<script src="/packs/common.js"></script>
<script src="/packs/locale_en.js"></script>
<script id='initial-state' type='application/json'><%= raw @initial_state %></script>
-<!--<script id='initial-state' type='application/json'>{"meta":{"streaming_api_base_url":"wss://pleroma.soykaf.com","access_token":"n6CFGTLKHjyxf98AFwQNXWiVkeCcntWUd2YUT3yUNAg=","locale":"en","domain":"pleroma.soykaf.com","admin":"1","me":"6","unfollow_modal":false,"boost_modal":false,"delete_modal":true,"auto_play_gif":false,"reduce_motion":false},"compose":{"me":"6","default_privacy":"public","default_sensitive":false},"accounts":{"6": {"username":"lain","url":"https://pleroma.soykaf.com/users/lain","statuses_count":26717,"source":{"sensitive":"false","privacy":"public","note":""},"note":"pleroma dev. let's be friends\r\n\r\nxmpp: lain@xmpp.soykaf.com \r\ntox: D816CA3999814F0CC67F31EC3FED566734377D1C50925E8F9F0CF384C088AF6DA8BC1F8BA9D7 \r\nmatrix: @lambadalambda:matrix.heldscal.la \r\n10grans: 0xF90F5f5cFfc3E5469c428424c19Ebc1310d59e82","locked":false,"id":6,"header_static":"https://pleroma.soykaf.com/media/1354ed08-bf5f-4d25-9af5-e60944c0bcec/319EE96456BF3FB505DAE8158A47D5EC0B19FA03FC41E48785FFE1CAF06114F8.gif","header":"https://pleroma.soykaf.com/media/1354ed08-bf5f-4d25-9af5-e60944c0bcec/319EE96456BF3FB505DAE8158A47D5EC0B19FA03FC41E48785FFE1CAF06114F8.gif","following_count":510,"followers_count":708,"display_name":"⑨ lain ⑨","created_at":"2017-04-16T09:06:38.000Z","avatar_static":"https://pleroma.soykaf.com/media/6519c8f9-b2ad-49b0-96e8-e2885e163f54/4DA3506EA10AE74A6CDE61277818A4E88FEC665A984E86789C440C8BBDDC4B59.gif","avatar":"https://pleroma.soykaf.com/media/6519c8f9-b2ad-49b0-96e8-e2885e163f54/4DA3506EA10AE74A6CDE61277818A4E88FEC665A984E86789C440C8BBDDC4B59.gif","acct":"lain"},
- "264":{"id":"264","username":"lambadalambda","acct":"lambadalambda","display_name":"Critical Value","locked":false,"created_at":"2016-09-04T16:55:41.021Z","note":"\u003cp\u003e\u003c/p\u003e","url":"https://mastodon.social/@lambadalambda","avatar":"https://files.mastodon.social/accounts/avatars/000/000/264/original/1429214160519.gif","avatar_static":"https://files.mastodon.social/accounts/avatars/000/000/264/static/1429214160519.png","header":"https://files.mastodon.social/accounts/headers/000/000/264/original/28b26104f83747d2.gif","header_static":"https://files.mastodon.social/accounts/headers/000/000/264/static/28b26104f83747d2.png","followers_count":61,"following_count":10,"statuses_count":243},"1":{"id":"1","username":"Gargron","acct":"Gargron","display_name":"Eugen","locked":false,"created_at":"2016-03-16T14:34:26.392Z","note":"\u003cp\u003eDeveloper of Mastodon\u003c/p\u003e\u003cp\u003e\u003ca href=\"https://github.com/tootsuite/mastodon\" rel=\"nofollow noopener\" target=\"_blank\"\u003e\u003cspan class=\"invisible\"\u003ehttps://\u003c/span\u003e\u003cspan class=\"\"\u003egithub.com/tootsuite/mastodon\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e \u003ca href=\"https://www.patreon.com/user?u=619786\" rel=\"nofollow noopener\" target=\"_blank\"\u003e\u003cspan class=\"invisible\"\u003ehttps://www.\u003c/span\u003e\u003cspan class=\"\"\u003epatreon.com/user?u=619786\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e\u003c/p\u003e\u003cp\u003eAvatar by \u003cspan class=\"h-card\"\u003e\u003ca href=\"https://mastodon.art/@DearMsDear\" class=\"u-url mention\"\u003e@\u003cspan\u003eDearMsDear\u003c/span\u003e\u003c/a\u003e\u003c/span\u003e\u003c/p\u003e","url":"https://mastodon.social/@Gargron","avatar":"https://files.mastodon.social/accounts/avatars/000/000/001/original/8e3dd118a025f184.png","avatar_static":"https://files.mastodon.social/accounts/avatars/000/000/001/original/8e3dd118a025f184.png","header":"https://files.mastodon.social/accounts/headers/000/000/001/original/media.jpeg","header_static":"https://files.mastodon.social/accounts/headers/000/000/001/original/media.jpeg","followers_count":24950,"following_count":667,"statuses_count":24810}},"media_attachments":{"accept_content_types":[".jpg",".jpeg",".png",".gif",".webm",".mp4",".m4v","image/jpeg","image/png","image/gif","video/webm","video/mp4"]},"settings":{"onboarded":true,"home":{"shows":{"reblog":true,"reply":true}},"notifications":{"alerts":{"follow":true,"favourite":true,"reblog":true,"mention":true},"shows":{"follow":true,"favourite":true,"reblog":true,"mention":true},"sounds":{"follow":true,"favourite":true,"reblog":true,"mention":true}}},"push_subscription":null,"custom_emojis":[{"shortcode":"sickmeme","url":"https://files.mastodon.social/custom_emojis/images/000/000/778/original/sickmeme.png","static_url":"https://files.mastodon.social/custom_emojis/images/000/000/778/static/sickmeme.png"},{"shortcode":"breathe","url":"https://files.mastodon.social/custom_emojis/images/000/000/782/original/breathe.png","static_url":"https://files.mastodon.social/custom_emojis/images/000/000/782/static/breathe.png"},{"shortcode":"hotboi","url":"https://files.mastodon.social/custom_emojis/images/000/000/783/original/hotboi.png","static_url":"https://files.mastodon.social/custom_emojis/images/000/000/783/static/hotboi.png"},{"shortcode":"nigmaGrin","url":"https://files.mastodon.social/custom_emojis/images/000/000/121/original/294208830299176960.png","static_url":"https://files.mastodon.social/custom_emojis/images/000/000/121/static/294208830299176960.png"},{"shortcode":"unarist","url":"https://files.mastodon.social/custom_emojis/images/000/001/091/original/b6816ca3542e9fc0.png","static_url":"https://files.mastodon.social/custom_emojis/images/000/001/091/static/b6816ca3542e9fc0.png"},{"shortcode":"coolcat","url":"https://files.mastodon.social/custom_emojis/images/000/000/005/original/354315741937270794.png","static_url":"https://files.mastodon.social/custom_emojis/images/000/000/005/static/354315741937270794.png"},{"shortcode":"angery","url":"https://files.mastodon.social/custom_emojis/images/000/000/006/original/angery.png","static_url":"https://files.mastodon.social/custom_emojis/images/000/000/006/static/angery.png"},{"shortcode":"thinkhappy","url":"https://files.mastodon.social/custom_emojis/images/000/000/011/original/328081997266288640.png","static_url":"https://files.mastodon.social/custom_emojis/images/000/000/011/static/328081997266288640.png"},{"shortcode":"thaenkin","url":"https://files.mastodon.social/custom_emojis/images/000/000/012/original/334845559435296768.png","static_url":"https://files.mastodon.social/custom_emojis/images/000/000/012/static/334845559435296768.png"},{"shortcode":"maple","url":"https://files.mastodon.social/custom_emojis/images/000/000/953/original/1b22dacadcf0e224.png","static_url":"https://files.mastodon.social/custom_emojis/images/000/000/953/static/1b22dacadcf0e224.png"},{"shortcode":"computerfairies","url":"https://files.mastodon.social/custom_emojis/images/000/000/954/original/abd0669604e01d4d.png","static_url":"https://files.mastodon.social/custom_emojis/images/000/000/954/static/abd0669604e01d4d.png"},{"shortcode":"noelle","url":"https://files.mastodon.social/custom_emojis/images/000/000/956/original/peridot-santa-transparent.png","static_url":"https://files.mastodon.social/custom_emojis/images/000/000/956/static/peridot-santa-transparent.png"},{"shortcode":"wyd","url":"https://files.mastodon.social/custom_emojis/images/000/001/069/original/wyd.png","static_url":"https://files.mastodon.social/custom_emojis/images/000/001/069/static/wyd.png"},{"shortcode":"weirdfish","url":"https://files.mastodon.social/custom_emojis/images/000/001/489/original/8a2fdcf42b344cd9.png","static_url":"https://files.mastodon.social/custom_emojis/images/000/001/489/static/8a2fdcf42b344cd9.png"},{"shortcode":"screwattack","url":"https://files.mastodon.social/custom_emojis/images/000/001/889/original/6f11873e8ac5dd20.png","static_url":"https://files.mastodon.social/custom_emojis/images/000/001/889/static/6f11873e8ac5dd20.png"},{"shortcode":"dnd","url":"https://files.mastodon.social/custom_emojis/images/000/004/590/original/b12ce74fe2b86ab8.png","static_url":"https://files.mastodon.social/custom_emojis/images/000/004/590/static/b12ce74fe2b86ab8.png"},{"shortcode":"stardewvalley","url":"https://files.mastodon.social/custom_emojis/images/000/004/591/original/f9a94b8af8dd1c72.png","static_url":"https://files.mastodon.social/custom_emojis/images/000/004/591/static/f9a94b8af8dd1c72.png"},{"shortcode":"splatoon","url":"https://files.mastodon.social/custom_emojis/images/000/004/592/original/632e04f8f0f4ca62.png","static_url":"https://files.mastodon.social/custom_emojis/images/000/004/592/static/632e04f8f0f4ca62.png"},{"shortcode":"warcraft","url":"https://files.mastodon.social/custom_emojis/images/000/004/593/original/7ca494c09fae0384.png","static_url":"https://files.mastodon.social/custom_emojis/images/000/004/593/static/7ca494c09fae0384.png"},{"shortcode":"overwatch","url":"https://files.mastodon.social/custom_emojis/images/000/004/594/original/6d181fa79a38e644.png","static_url":"https://files.mastodon.social/custom_emojis/images/000/004/594/static/6d181fa79a38e644.png"},{"shortcode":"kerbal","url":"https://files.mastodon.social/custom_emojis/images/000/003/317/original/9614b39f11d19bcf.png","static_url":"https://files.mastodon.social/custom_emojis/images/000/003/317/static/9614b39f11d19bcf.png"},{"shortcode":"gargamel","url":"https://files.mastodon.social/custom_emojis/images/000/003/677/original/d3af6f81b96e082d.png","static_url":"https://files.mastodon.social/custom_emojis/images/000/003/677/static/d3af6f81b96e082d.png"},{"shortcode":"mastodon","url":"https://files.mastodon.social/custom_emojis/images/000/003/675/original/089aaae26a2abcc1.png","static_url":"https://files.mastodon.social/custom_emojis/images/000/003/675/static/089aaae26a2abcc1.png"},{"shortcode":"blobpats","url":"https://files.mastodon.social/custom_emojis/images/000/003/679/original/80d1ba80bf06950e.png","static_url":"https://files.mastodon.social/custom_emojis/images/000/003/679/static/80d1ba80bf06950e.png"},{"shortcode":"sabakan","url":"https://files.mastodon.social/custom_emojis/images/000/003/676/original/77c4094eacccac9e.png","static_url":"https://files.mastodon.social/custom_emojis/images/000/003/676/static/77c4094eacccac9e.png"},{"shortcode":"wily_ufo","url":"https://files.mastodon.social/custom_emojis/images/000/002/321/original/dc7da5987f1e07b0.png","static_url":"https://files.mastodon.social/custom_emojis/images/000/002/321/static/dc7da5987f1e07b0.png"},{"shortcode":"batman","url":"https://files.mastodon.social/custom_emojis/images/000/005/163/original/8iGbkB7aT.png","static_url":"https://files.mastodon.social/custom_emojis/images/000/005/163/static/8iGbkB7aT.png"}]}</script> -->
-
<script src="/packs/application.js"></script>
</head>
<body class='app-body'>