forked from AkkomaGang/akkoma
Merge branch 'feature/user-configurable-mascot' into 'develop'
Feature: user configurable mascot See merge request pleroma/pleroma!1177
This commit is contained in:
commit
943d1b2f26
10 changed files with 197 additions and 1 deletions
|
@ -23,6 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Configuration: `report_uri` option
|
- Configuration: `report_uri` option
|
||||||
- Pleroma API: User subscriptions
|
- Pleroma API: User subscriptions
|
||||||
- Pleroma API: Healthcheck endpoint
|
- Pleroma API: Healthcheck endpoint
|
||||||
|
- Pleroma API: `/api/v1/pleroma/mascot` per-user frontend mascot configuration endpoints
|
||||||
- Admin API: Endpoints for listing/revoking invite tokens
|
- Admin API: Endpoints for listing/revoking invite tokens
|
||||||
- Admin API: Endpoints for making users follow/unfollow each other
|
- Admin API: Endpoints for making users follow/unfollow each other
|
||||||
- Admin API: added filters (role, tags, email, name) for users endpoint
|
- Admin API: added filters (role, tags, email, name) for users endpoint
|
||||||
|
|
|
@ -276,6 +276,19 @@
|
||||||
showInstanceSpecificPanel: true
|
showInstanceSpecificPanel: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config :pleroma, :assets,
|
||||||
|
mascots: [
|
||||||
|
pleroma_fox_tan: %{
|
||||||
|
url: "/images/pleroma-fox-tan-smol.png",
|
||||||
|
mime_type: "image/png"
|
||||||
|
},
|
||||||
|
pleroma_fox_tan_shy: %{
|
||||||
|
url: "/images/pleroma-fox-tan-shy.png",
|
||||||
|
mime_type: "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
default_mascot: :pleroma_fox_tan
|
||||||
|
|
||||||
config :pleroma, :activitypub,
|
config :pleroma, :activitypub,
|
||||||
accept_blocks: true,
|
accept_blocks: true,
|
||||||
unfollow_blocked: true,
|
unfollow_blocked: true,
|
||||||
|
|
|
@ -252,6 +252,45 @@ See [Admin-API](Admin-API.md)
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `/api/v1/pleroma/mascot`
|
||||||
|
### Gets user mascot image
|
||||||
|
* Method `GET`
|
||||||
|
* Authentication: required
|
||||||
|
|
||||||
|
* Response: JSON. Returns a mastodon media attachment entity.
|
||||||
|
* Example response:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "abcdefg",
|
||||||
|
"url": "https://pleroma.example.org/media/abcdefg.png",
|
||||||
|
"type": "image",
|
||||||
|
"pleroma": {
|
||||||
|
"mime_type": "image/png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Updates user mascot image
|
||||||
|
* Method `PUT`
|
||||||
|
* Authentication: required
|
||||||
|
* Params:
|
||||||
|
* `image`: Multipart image
|
||||||
|
* Response: JSON. Returns a mastodon media attachment entity
|
||||||
|
when successful, otherwise returns HTTP 415 `{"error": "error_msg"}`
|
||||||
|
* Example response:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "abcdefg",
|
||||||
|
"url": "https://pleroma.example.org/media/abcdefg.png",
|
||||||
|
"type": "image",
|
||||||
|
"pleroma": {
|
||||||
|
"mime_type": "image/png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
* Note: Behaves exactly the same as `POST /api/v1/upload`.
|
||||||
|
Can only accept images - any attempt to upload non-image files will be met with `HTTP 415 Unsupported Media Type`.
|
||||||
|
|
||||||
## `/api/pleroma/notification_settings`
|
## `/api/pleroma/notification_settings`
|
||||||
### Updates user notification settings
|
### Updates user notification settings
|
||||||
* Method `PUT`
|
* Method `PUT`
|
||||||
|
|
|
@ -203,6 +203,16 @@ This section is used to configure Pleroma-FE, unless ``:managed_config`` in ``:i
|
||||||
* `hide_post_stats`: Hide notices statistics(repeats, favorites, …)
|
* `hide_post_stats`: Hide notices statistics(repeats, favorites, …)
|
||||||
* `hide_user_stats`: Hide profile statistics(posts, posts per day, followers, followings, …)
|
* `hide_user_stats`: Hide profile statistics(posts, posts per day, followers, followings, …)
|
||||||
|
|
||||||
|
## :assets
|
||||||
|
|
||||||
|
This section configures assets to be used with various frontends. Currently the only option
|
||||||
|
relates to mascots on the mastodon frontend
|
||||||
|
|
||||||
|
* `mascots`: KeywordList of mascots, each element __MUST__ contain both a `url` and a
|
||||||
|
`mime_type` key.
|
||||||
|
* `default_mascot`: An element from `mascots` - This will be used as the default mascot
|
||||||
|
on MastoFE (default: `:pleroma_fox_tan`)
|
||||||
|
|
||||||
## :mrf_simple
|
## :mrf_simple
|
||||||
* `media_removal`: List of instances to remove medias from
|
* `media_removal`: List of instances to remove medias from
|
||||||
* `media_nsfw`: List of instances to put medias as NSFW(sensitive) from
|
* `media_nsfw`: List of instances to put medias as NSFW(sensitive) from
|
||||||
|
|
|
@ -1402,4 +1402,24 @@ def toggle_confirmation(%User{} = user) do
|
||||||
|> put_embed(:info, info_changeset)
|
|> put_embed(:info, info_changeset)
|
||||||
|> update_and_set_cache()
|
|> update_and_set_cache()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_mascot(%{info: %{mascot: %{} = mascot}}) when not is_nil(mascot) do
|
||||||
|
mascot
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_mascot(%{info: %{mascot: mascot}}) when is_nil(mascot) do
|
||||||
|
# use instance-default
|
||||||
|
config = Pleroma.Config.get([:assets, :mascots])
|
||||||
|
default_mascot = Pleroma.Config.get([:assets, :default_mascot])
|
||||||
|
mascot = Keyword.get(config, default_mascot)
|
||||||
|
|
||||||
|
%{
|
||||||
|
"id" => "default-mascot",
|
||||||
|
"url" => mascot[:url],
|
||||||
|
"preview_url" => mascot[:url],
|
||||||
|
"pleroma" => %{
|
||||||
|
"mime_type" => mascot[:mime_type]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -43,6 +43,7 @@ defmodule Pleroma.User.Info do
|
||||||
field(:hide_favorites, :boolean, default: true)
|
field(:hide_favorites, :boolean, default: true)
|
||||||
field(:pinned_activities, {:array, :string}, default: [])
|
field(:pinned_activities, {:array, :string}, default: [])
|
||||||
field(:flavour, :string, default: nil)
|
field(:flavour, :string, default: nil)
|
||||||
|
field(:mascot, :map, default: nil)
|
||||||
field(:emoji, {:array, :map}, default: [])
|
field(:emoji, {:array, :map}, default: [])
|
||||||
|
|
||||||
field(:notification_settings, :map,
|
field(:notification_settings, :map,
|
||||||
|
@ -248,6 +249,14 @@ def mastodon_flavour_update(info, flavour) do
|
||||||
|> validate_required([:flavour])
|
|> validate_required([:flavour])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def mascot_update(info, url) do
|
||||||
|
params = %{mascot: url}
|
||||||
|
|
||||||
|
info
|
||||||
|
|> cast(params, [:mascot])
|
||||||
|
|> validate_required([:mascot])
|
||||||
|
end
|
||||||
|
|
||||||
def set_source_data(info, source_data) do
|
def set_source_data(info, source_data) do
|
||||||
params = %{source_data: source_data}
|
params = %{source_data: source_data}
|
||||||
|
|
||||||
|
|
|
@ -707,6 +707,41 @@ def upload(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_mascot(%{assigns: %{user: user}} = conn, %{"file" => file}) do
|
||||||
|
with {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)),
|
||||||
|
%{} = attachment_data <- Map.put(object.data, "id", object.id),
|
||||||
|
%{type: type} = rendered <-
|
||||||
|
StatusView.render("attachment.json", %{attachment: attachment_data}) do
|
||||||
|
# Reject if not an image
|
||||||
|
if type == "image" do
|
||||||
|
# Sure!
|
||||||
|
# Save to the user's info
|
||||||
|
info_changeset = User.Info.mascot_update(user.info, rendered)
|
||||||
|
|
||||||
|
user_changeset =
|
||||||
|
user
|
||||||
|
|> Ecto.Changeset.change()
|
||||||
|
|> Ecto.Changeset.put_embed(:info, info_changeset)
|
||||||
|
|
||||||
|
{:ok, _user} = User.update_and_set_cache(user_changeset)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> json(rendered)
|
||||||
|
else
|
||||||
|
conn
|
||||||
|
|> put_resp_content_type("application/json")
|
||||||
|
|> send_resp(415, Jason.encode!(%{"error" => "mascots can only be images"}))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_mascot(%{assigns: %{user: user}} = conn, _params) do
|
||||||
|
mascot = User.get_mascot(user)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> json(mascot)
|
||||||
|
end
|
||||||
|
|
||||||
def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
with %Activity{data: %{"object" => object}} <- Repo.get(Activity, id),
|
with %Activity{data: %{"object" => object}} <- Repo.get(Activity, id),
|
||||||
%Object{data: %{"likes" => likes}} <- Object.normalize(object) do
|
%Object{data: %{"likes" => likes}} <- Object.normalize(object) do
|
||||||
|
@ -1329,7 +1364,7 @@ def index(%{assigns: %{user: user}} = conn, _params) do
|
||||||
display_sensitive_media: false,
|
display_sensitive_media: false,
|
||||||
reduce_motion: false,
|
reduce_motion: false,
|
||||||
max_toot_chars: limit,
|
max_toot_chars: limit,
|
||||||
mascot: "/images/pleroma-fox-tan-smol.png"
|
mascot: User.get_mascot(user)["url"]
|
||||||
},
|
},
|
||||||
rights: %{
|
rights: %{
|
||||||
delete_others_notice: present?(user.info.is_moderator),
|
delete_others_notice: present?(user.info.is_moderator),
|
||||||
|
|
|
@ -352,6 +352,9 @@ defmodule Pleroma.Web.Router do
|
||||||
|
|
||||||
post("/pleroma/flavour/:flavour", MastodonAPIController, :set_flavour)
|
post("/pleroma/flavour/:flavour", MastodonAPIController, :set_flavour)
|
||||||
|
|
||||||
|
get("/pleroma/mascot", MastodonAPIController, :get_mascot)
|
||||||
|
put("/pleroma/mascot", MastodonAPIController, :set_mascot)
|
||||||
|
|
||||||
post("/reports", MastodonAPIController, :reports)
|
post("/reports", MastodonAPIController, :reports)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
BIN
test/fixtures/sound.mp3
vendored
Normal file
BIN
test/fixtures/sound.mp3
vendored
Normal file
Binary file not shown.
|
@ -1455,6 +1455,72 @@ test "media upload", %{conn: conn} do
|
||||||
assert object.data["actor"] == User.ap_id(user)
|
assert object.data["actor"] == User.ap_id(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "mascot upload", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
non_image_file = %Plug.Upload{
|
||||||
|
content_type: "audio/mpeg",
|
||||||
|
path: Path.absname("test/fixtures/sound.mp3"),
|
||||||
|
filename: "sound.mp3"
|
||||||
|
}
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> put("/api/v1/pleroma/mascot", %{"file" => non_image_file})
|
||||||
|
|
||||||
|
assert json_response(conn, 415)
|
||||||
|
|
||||||
|
file = %Plug.Upload{
|
||||||
|
content_type: "image/jpg",
|
||||||
|
path: Path.absname("test/fixtures/image.jpg"),
|
||||||
|
filename: "an_image.jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> put("/api/v1/pleroma/mascot", %{"file" => file})
|
||||||
|
|
||||||
|
assert %{"id" => _, "type" => image} = json_response(conn, 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "mascot retrieving", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
# When user hasn't set a mascot, we should just get pleroma tan back
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/pleroma/mascot")
|
||||||
|
|
||||||
|
assert %{"url" => url} = json_response(conn, 200)
|
||||||
|
assert url =~ "pleroma-fox-tan-smol"
|
||||||
|
|
||||||
|
# When a user sets their mascot, we should get that back
|
||||||
|
file = %Plug.Upload{
|
||||||
|
content_type: "image/jpg",
|
||||||
|
path: Path.absname("test/fixtures/image.jpg"),
|
||||||
|
filename: "an_image.jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> put("/api/v1/pleroma/mascot", %{"file" => file})
|
||||||
|
|
||||||
|
assert json_response(conn, 200)
|
||||||
|
|
||||||
|
user = User.get_cached_by_id(user.id)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/pleroma/mascot")
|
||||||
|
|
||||||
|
assert %{"url" => url, "type" => "image"} = json_response(conn, 200)
|
||||||
|
assert url =~ "an_image"
|
||||||
|
end
|
||||||
|
|
||||||
test "hashtag timeline", %{conn: conn} do
|
test "hashtag timeline", %{conn: conn} do
|
||||||
following = insert(:user)
|
following = insert(:user)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue