Cache character pictures and songs on disk
Build / test (push) Successful in 53s
Build / build (push) Successful in 1m13s

Character pictures are kept in characters/ next to the settings, with an
index.json of when the server last changed each one and when it was
downloaded. Only new or changed pictures are downloaded, using the server's new
/character/details. Older servers fall back to the name list, and pictures are
downloaded again after 7 days.

Downloaded songs are written to songs/ and only their paths are kept in memory.
The folder is emptied when the game starts and when the playlist resets.
This commit is contained in:
Sansan committed 2026-10-07 21:57:27 +02:00
1 parent 62fa63fd8b
commit 240c331ab2
17 files changed
+912 -48

No files matched your search

+26
View File
@@ -0,0 +1,26 @@
# Generated by tools/generate_api_types.gd from server/openapi.json.
# Do not edit; change the server and generate again.
class_name CharacterDetails
extends RefCounted
## The [code]CharacterDetails[/code] schema of the MusicServer API.
## When the image was last changed, in Unix seconds
var modified: int
## File name of the character image
var name: String
## Reads the fields from parsed JSON. Missing fields keep their defaults.
static func from_dict(data: Dictionary) -> CharacterDetails:
var result: CharacterDetails = CharacterDetails.new()
result.modified = ApiJson.get_int(data, "modified", 0)
result.name = ApiJson.get_string(data, "name", "")
return result
## Returns the fields under their JSON names, ready for [method JSON.stringify].
func to_dict() -> Dictionary:
return {
"modified": modified,
"name": name,
}
@@ -0,0 +1 @@
uid://cu6wgjxv120en
+6
View File
@@ -216,6 +216,12 @@ func fetch_character_list(on_done: Callable, on_failed: Callable = Callable()) -
get_json("/character/list", on_done, on_failed)
## Fetches every character image with when it was last changed, as an
## [code]Array[CharacterDetails][/code]. Servers older than this endpoint answer 404.
func fetch_character_details(on_done: Callable, on_failed: Callable = Callable()) -> void:
get_json_list("/character/details", CharacterDetails, on_done, on_failed)
## Downloads a character image as PNG bytes.
func fetch_character_image(
character_name: String, on_done: Callable, on_failed: Callable = Callable()) -> void:
+378 -20
View File
@@ -17,6 +17,25 @@
],
"type": "object"
},
"CharacterDetails": {
"additionalProperties": false,
"properties": {
"modified": {
"description": "When the image was last changed, in Unix seconds",
"format": "int64",
"type": "integer"
},
"name": {
"description": "File name of the character image",
"type": "string"
}
},
"required": [
"name",
"modified"
],
"type": "object"
},
"ProgressResponse": {
"additionalProperties": false,
"properties": {
@@ -437,7 +456,7 @@
}
},
"info": {
"description": "API for the MusicServer application.\n\nGet a token with `POST /api/v1/token` and send it as `Authorization: Bearer <token>`.\n\nThe same endpoints without the `/api/v1` prefix (legacy routes) are deprecated, need no token and are not documented here. They will be removed.",
"description": "API for the MusicServer application.\n\nGet a token with `POST /api/v1/token` and send it as `Authorization: Bearer <token>`.\n\nEvery client has its own play queue. The music endpoints return its ID in the `X-Queue-Id` header; send it back in the same header to keep using that queue. Without it, or when the queue has ended (after a reset or a sync), a new queue is started.",
"title": "MusicServer API",
"version": "5.0.0"
},
@@ -561,6 +580,56 @@
]
}
},
"/api/v1/character/details": {
"get": {
"description": "Returns every character image with the time it was last changed, so clients can cache images and download only the changed ones.",
"operationId": "list-character-details",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/CharacterDetails"
},
"type": "array"
}
}
},
"description": "OK"
},
"401": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/APIError"
}
}
},
"description": "Unauthorized"
},
"500": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/APIError"
}
}
},
"description": "Internal Server Error"
}
},
"security": [
{
"bearer": []
}
],
"summary": "List characters with change times",
"tags": [
"characters"
]
}
},
"/api/v1/character/list": {
"get": {
"description": "Returns the file names of all character images.",
@@ -904,6 +973,15 @@
"description": "Jumps to the song at the given queue position (clamped to the queue) and returns it.",
"operationId": "get-queue-song",
"parameters": [
{
"description": "ID of the client's play queue, from an earlier response. Omit it to start a new queue.",
"in": "header",
"name": "X-Queue-Id",
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"description": "Position of the song in the queue (SongNo)",
"explode": false,
@@ -928,7 +1006,16 @@
}
}
},
"description": "The song (MP3)"
"description": "The song (MP3)",
"headers": {
"X-Queue-Id": {
"description": "ID of the client's play queue. Send it with the following requests.",
"schema": {
"format": "uuid",
"type": "string"
}
}
}
},
"206": {
"content": {
@@ -940,7 +1027,16 @@
}
}
},
"description": "Part of the file, for a Range request"
"description": "Part of the file, for a Range request",
"headers": {
"X-Queue-Id": {
"description": "ID of the client's play queue. Send it with the following requests.",
"schema": {
"format": "uuid",
"type": "string"
}
}
}
},
"401": {
"content": {
@@ -1008,9 +1104,29 @@
"get": {
"description": "Increments the play counters of the current song and its soundtrack.",
"operationId": "mark-current-played",
"parameters": [
{
"description": "ID of the client's play queue, from an earlier response. Omit it to start a new queue.",
"in": "header",
"name": "X-Queue-Id",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
"description": "OK",
"headers": {
"X-Queue-Id": {
"description": "ID of the client's play queue. Send it with the following requests.",
"schema": {
"format": "uuid",
"type": "string"
}
}
}
},
"401": {
"content": {
@@ -1058,9 +1174,29 @@
"get": {
"description": "Adds the song returned by the last random song request to the end of the queue.",
"operationId": "add-latest-to-queue",
"parameters": [
{
"description": "ID of the client's play queue, from an earlier response. Omit it to start a new queue.",
"in": "header",
"name": "X-Queue-Id",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
"description": "OK",
"headers": {
"X-Queue-Id": {
"description": "ID of the client's play queue. Send it with the following requests.",
"schema": {
"format": "uuid",
"type": "string"
}
}
}
},
"401": {
"content": {
@@ -1288,6 +1424,17 @@
"get": {
"description": "Returns the current song in the queue.",
"operationId": "get-song-info",
"parameters": [
{
"description": "ID of the client's play queue, from an earlier response. Omit it to start a new queue.",
"in": "header",
"name": "X-Queue-Id",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
@@ -1297,7 +1444,16 @@
}
}
},
"description": "OK"
"description": "OK",
"headers": {
"X-Queue-Id": {
"description": "ID of the client's play queue. Send it with the following requests.",
"schema": {
"format": "uuid",
"type": "string"
}
}
}
},
"401": {
"content": {
@@ -1335,6 +1491,17 @@
"get": {
"description": "Returns all songs in the queue.",
"operationId": "get-played-songs",
"parameters": [
{
"description": "ID of the client's play queue, from an earlier response. Omit it to start a new queue.",
"in": "header",
"name": "X-Queue-Id",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
@@ -1347,7 +1514,16 @@
}
}
},
"description": "OK"
"description": "OK",
"headers": {
"X-Queue-Id": {
"description": "ID of the client's play queue. Send it with the following requests.",
"schema": {
"format": "uuid",
"type": "string"
}
}
}
},
"401": {
"content": {
@@ -1385,6 +1561,17 @@
"get": {
"description": "Moves to the next song in the queue and returns it. Stays on the last song at the end.",
"operationId": "get-next-song",
"parameters": [
{
"description": "ID of the client's play queue, from an earlier response. Omit it to start a new queue.",
"in": "header",
"name": "X-Queue-Id",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
@@ -1396,7 +1583,16 @@
}
}
},
"description": "The song (MP3)"
"description": "The song (MP3)",
"headers": {
"X-Queue-Id": {
"description": "ID of the client's play queue. Send it with the following requests.",
"schema": {
"format": "uuid",
"type": "string"
}
}
}
},
"206": {
"content": {
@@ -1408,7 +1604,16 @@
}
}
},
"description": "Part of the file, for a Range request"
"description": "Part of the file, for a Range request",
"headers": {
"X-Queue-Id": {
"description": "ID of the client's play queue. Send it with the following requests.",
"schema": {
"format": "uuid",
"type": "string"
}
}
}
},
"401": {
"content": {
@@ -1467,6 +1672,15 @@
"description": "Increments the play counters of the song at the given queue position. Positions outside the queue are ignored.",
"operationId": "mark-song-played",
"parameters": [
{
"description": "ID of the client's play queue, from an earlier response. Omit it to start a new queue.",
"in": "header",
"name": "X-Queue-Id",
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"description": "Position of the song in the queue (SongNo)",
"explode": false,
@@ -1482,7 +1696,16 @@
],
"responses": {
"200": {
"description": "OK"
"description": "OK",
"headers": {
"X-Queue-Id": {
"description": "ID of the client's play queue. Send it with the following requests.",
"schema": {
"format": "uuid",
"type": "string"
}
}
}
},
"401": {
"content": {
@@ -1540,6 +1763,17 @@
"get": {
"description": "Moves to the previous song in the queue and returns it. Stays on the first song at the start.",
"operationId": "get-previous-song",
"parameters": [
{
"description": "ID of the client's play queue, from an earlier response. Omit it to start a new queue.",
"in": "header",
"name": "X-Queue-Id",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
@@ -1551,7 +1785,16 @@
}
}
},
"description": "The song (MP3)"
"description": "The song (MP3)",
"headers": {
"X-Queue-Id": {
"description": "ID of the client's play queue. Send it with the following requests.",
"schema": {
"format": "uuid",
"type": "string"
}
}
}
},
"206": {
"content": {
@@ -1563,7 +1806,16 @@
}
}
},
"description": "Part of the file, for a Range request"
"description": "Part of the file, for a Range request",
"headers": {
"X-Queue-Id": {
"description": "ID of the client's play queue. Send it with the following requests.",
"schema": {
"format": "uuid",
"type": "string"
}
}
}
},
"401": {
"content": {
@@ -1621,6 +1873,17 @@
"get": {
"description": "Picks a random song from a random soundtrack. Add it to the queue with /music/addQue.",
"operationId": "get-random-song",
"parameters": [
{
"description": "ID of the client's play queue, from an earlier response. Omit it to start a new queue.",
"in": "header",
"name": "X-Queue-Id",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
@@ -1632,7 +1895,16 @@
}
}
},
"description": "The song (MP3)"
"description": "The song (MP3)",
"headers": {
"X-Queue-Id": {
"description": "ID of the client's play queue. Send it with the following requests.",
"schema": {
"format": "uuid",
"type": "string"
}
}
}
},
"206": {
"content": {
@@ -1644,7 +1916,16 @@
}
}
},
"description": "Part of the file, for a Range request"
"description": "Part of the file, for a Range request",
"headers": {
"X-Queue-Id": {
"description": "ID of the client's play queue. Send it with the following requests.",
"schema": {
"format": "uuid",
"type": "string"
}
}
}
},
"401": {
"content": {
@@ -1702,6 +1983,17 @@
"get": {
"description": "Picks a random song where every song has the same chance, regardless of soundtrack.",
"operationId": "get-random-song-classic",
"parameters": [
{
"description": "ID of the client's play queue, from an earlier response. Omit it to start a new queue.",
"in": "header",
"name": "X-Queue-Id",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
@@ -1713,7 +2005,16 @@
}
}
},
"description": "The song (MP3)"
"description": "The song (MP3)",
"headers": {
"X-Queue-Id": {
"description": "ID of the client's play queue. Send it with the following requests.",
"schema": {
"format": "uuid",
"type": "string"
}
}
}
},
"206": {
"content": {
@@ -1725,7 +2026,16 @@
}
}
},
"description": "Part of the file, for a Range request"
"description": "Part of the file, for a Range request",
"headers": {
"X-Queue-Id": {
"description": "ID of the client's play queue. Send it with the following requests.",
"schema": {
"format": "uuid",
"type": "string"
}
}
}
},
"401": {
"content": {
@@ -1783,6 +2093,17 @@
"get": {
"description": "Picks a random song, favouring soundtracks that have been played less than average.",
"operationId": "get-random-song-low-chance",
"parameters": [
{
"description": "ID of the client's play queue, from an earlier response. Omit it to start a new queue.",
"in": "header",
"name": "X-Queue-Id",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
@@ -1794,7 +2115,16 @@
}
}
},
"description": "The song (MP3)"
"description": "The song (MP3)",
"headers": {
"X-Queue-Id": {
"description": "ID of the client's play queue. Send it with the following requests.",
"schema": {
"format": "uuid",
"type": "string"
}
}
}
},
"206": {
"content": {
@@ -1806,7 +2136,16 @@
}
}
},
"description": "Part of the file, for a Range request"
"description": "Part of the file, for a Range request",
"headers": {
"X-Queue-Id": {
"description": "ID of the client's play queue. Send it with the following requests.",
"schema": {
"format": "uuid",
"type": "string"
}
}
}
},
"401": {
"content": {
@@ -1862,11 +2201,30 @@
},
"/api/v1/music/reset": {
"get": {
"description": "Clears the queue and reloads the soundtrack list.",
"description": "Ends the queue, starts a new one and reloads the soundtrack list. The new queue's ID is returned in the X-Queue-Id header; send it with the following requests.",
"operationId": "reset-queue",
"parameters": [
{
"description": "ID of the client's play queue, from an earlier response. Omit it to start a new queue.",
"in": "header",
"name": "X-Queue-Id",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
"description": "OK",
"headers": {
"X-Queue-Id": {
"schema": {
"description": "ID of the new queue",
"type": "string"
}
}
}
},
"401": {
"content": {