diff --git a/docs/Doxyfile b/docs/Doxyfile
index b667429d1c3..5695a2d6240 100644
--- a/docs/Doxyfile
+++ b/docs/Doxyfile
@@ -52,6 +52,7 @@ INPUT = ../README.md \
app_examples.md \
guides.md \
performance_tuning.md \
+ api.md \
troubleshooting.md \
building.md \
contributing.md \
diff --git a/docs/api.md b/docs/api.md
new file mode 100644
index 00000000000..2c6e640989d
--- /dev/null
+++ b/docs/api.md
@@ -0,0 +1,64 @@
+# API
+
+Sunshine has a RESTful API which can be used to interact with the service.
+
+Unless otherwise specified, authentication is required for all API calls. You can authenticate using
+basic authentication with the admin username and password.
+
+## GET /api/apps
+@copydoc confighttp::getApps()
+
+## GET /api/logs
+@copydoc confighttp::getLogs()
+
+## POST /api/apps
+@copydoc confighttp::saveApp()
+
+## DELETE /api/apps{index}
+@copydoc confighttp::deleteApp()
+
+## POST /api/covers/upload
+@copydoc confighttp::uploadCover()
+
+## GET /api/config
+@copydoc confighttp::getConfig()
+
+## GET /api/configLocale
+@copydoc confighttp::getLocale()
+
+## POST /api/config
+@copydoc confighttp::saveConfig()
+
+## POST /api/restart
+@copydoc confighttp::restart()
+
+## POST /api/password
+@copydoc confighttp::savePassword()
+
+## POST /api/pin
+@copydoc confighttp::savePin()
+
+## POST /api/clients/unpair-all
+@copydoc confighttp::unpairAll()
+
+## POST /api/clients/unpair
+@copydoc confighttp::unpair()
+
+## GET /api/clients/list
+@copydoc confighttp::listClients()
+
+## GET /api/apps/close
+@copydoc confighttp::closeApp()
+
+
+
+| Previous | Next |
+|:--------------------------------------------|--------------------------------------:|
+| [Performance Tuning](performance_tuning.md) | [Troubleshooting](troubleshooting.md) |
+
+
+
+
+
+ [TOC]
+
diff --git a/docs/performance_tuning.md b/docs/performance_tuning.md
index 796121425e7..b4418f6d7ef 100644
--- a/docs/performance_tuning.md
+++ b/docs/performance_tuning.md
@@ -13,9 +13,9 @@ Enabling *Fast Sync* in Nvidia settings may help reduce latency.
-| Previous | Next |
-|:--------------------|--------------------------------------:|
-| [Guides](guides.md) | [Troubleshooting](troubleshooting.md) |
+| Previous | Next |
+|:--------------------|--------------:|
+| [Guides](guides.md) | [API](api.md) |
diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md
index 133a063bbf6..c5fcf13b066 100644
--- a/docs/troubleshooting.md
+++ b/docs/troubleshooting.md
@@ -200,9 +200,9 @@ permissions on the disk.
-| Previous | Next |
-|:--------------------------------------------|------------------------:|
-| [Performance Tuning](performance_tuning.md) | [Building](building.md) |
+| Previous | Next |
+|:--------------|------------------------:|
+| [API](api.md) | [Building](building.md) |
diff --git a/src/confighttp.cpp b/src/confighttp.cpp
index 886008dd23b..756a4688259 100644
--- a/src/confighttp.cpp
+++ b/src/confighttp.cpp
@@ -324,6 +324,11 @@ namespace confighttp {
}
}
+ /**
+ * @brief Get the list of available applications.
+ * @param response The HTTP response object.
+ * @param request The HTTP request object.
+ */
void
getApps(resp_https_t response, req_https_t request) {
if (!authenticate(response, request)) return;
@@ -336,6 +341,11 @@ namespace confighttp {
response->write(content, headers);
}
+ /**
+ * @brief Get the logs from the log file.
+ * @param response The HTTP response object.
+ * @param request The HTTP request object.
+ */
void
getLogs(resp_https_t response, req_https_t request) {
if (!authenticate(response, request)) return;
@@ -348,6 +358,36 @@ namespace confighttp {
response->write(SimpleWeb::StatusCode::success_ok, content, headers);
}
+ /**
+ * @brief Save an application. If the application already exists, it will be updated, otherwise it will be added.
+ * @param response The HTTP response object.
+ * @param request The HTTP request object.
+ * The body for the post request should be JSON serialized in the following format:
+ * @code{.json}
+ * {
+ * "name": "Application Name",
+ * "output": "Log Output Path",
+ * "cmd": "Command to run the application",
+ * "index": -1,
+ * "exclude-global-prep-cmd": false,
+ * "elevated": false,
+ * "auto-detach": true,
+ * "wait-all": true,
+ * "exit-timeout": 5,
+ * "prep-cmd": [
+ * {
+ * "do": "Command to prepare",
+ * "undo": "Command to undo preparation",
+ * "elevated": false
+ * }
+ * ],
+ * "detached": [
+ * "Detached command"
+ * ],
+ * "image-path": "Full path to the application image. Must be a png file.",
+ * }
+ * @endcode
+ */
void
saveApp(resp_https_t response, req_https_t request) {
if (!authenticate(response, request)) return;
@@ -436,6 +476,11 @@ namespace confighttp {
proc::refresh(config::stream.file_apps);
}
+ /**
+ * @brief Delete an application.
+ * @param response The HTTP response object.
+ * @param request The HTTP request object.
+ */
void
deleteApp(resp_https_t response, req_https_t request) {
if (!authenticate(response, request)) return;
@@ -485,6 +530,18 @@ namespace confighttp {
proc::refresh(config::stream.file_apps);
}
+ /**
+ * @brief Upload a cover image.
+ * @param response The HTTP response object.
+ * @param request The HTTP request object.
+ * The body for the post request should be JSON serialized in the following format:
+ * @code{.json}
+ * {
+ * "key": "igdb_",
+ * "url": "https://images.igdb.com/igdb/image/upload/t_cover_big_2x/.png",
+ * }
+ * @endcode
+ */
void
uploadCover(resp_https_t response, req_https_t request) {
if (!authenticate(response, request)) return;
@@ -545,6 +602,11 @@ namespace confighttp {
outputTree.put("path", path);
}
+ /**
+ * @brief Get the configuration settings.
+ * @param response The HTTP response object.
+ * @param request The HTTP request object.
+ */
void
getConfig(resp_https_t response, req_https_t request) {
if (!authenticate(response, request)) return;
@@ -570,6 +632,11 @@ namespace confighttp {
}
}
+ /**
+ * @brief Get the locale setting. This endpoint does not require authentication.
+ * @param response The HTTP response object.
+ * @param request The HTTP request object.
+ */
void
getLocale(resp_https_t response, req_https_t request) {
// we need to return the locale whether authenticated or not
@@ -588,6 +655,19 @@ namespace confighttp {
outputTree.put("locale", config::sunshine.locale);
}
+ /**
+ * @brief Save the configuration settings.
+ * @param response The HTTP response object.
+ * @param request The HTTP request object.
+ * The body for the post request should be JSON serialized in the following format:
+ * @code{.json}
+ * {
+ * "key": "value"
+ * }
+ * @endcode
+ *
+ * @attention{It is recommended to ONLY save the config settings that differ from the default behavior.}
+ */
void
saveConfig(resp_https_t response, req_https_t request) {
if (!authenticate(response, request)) return;
@@ -624,6 +704,11 @@ namespace confighttp {
}
}
+ /**
+ * @brief Restart Sunshine.
+ * @param response The HTTP response object.
+ * @param request The HTTP request object.
+ */
void
restart(resp_https_t response, req_https_t request) {
if (!authenticate(response, request)) return;
@@ -634,6 +719,21 @@ namespace confighttp {
platf::restart();
}
+ /**
+ * @brief Update existing credentials.
+ * @param response The HTTP response object.
+ * @param request The HTTP request object.
+ * The body for the post request should be JSON serialized in the following format:
+ * @code{.json}
+ * {
+ * "currentUsername": "Current Username",
+ * "currentPassword": "Current Password",
+ * "newUsername": "New Username",
+ * "newPassword": "New Password",
+ * "confirmNewPassword": "Confirm New Password"
+ * }
+ * @endcode
+ */
void
savePassword(resp_https_t response, req_https_t request) {
if (!config::sunshine.username.empty() && !authenticate(response, request)) return;
@@ -692,6 +792,18 @@ namespace confighttp {
}
}
+ /**
+ * @brief Send a pin code to the host. The pin is generated from the Moonlight client during the pairing process.
+ * @param response The HTTP response object.
+ * @param request The HTTP request object.
+ * The body for the post request should be JSON serialized in the following format:
+ * @code{.json}
+ * {
+ * "pin": "",
+ * "name": "Friendly Client Name"
+ * }
+ * @endcode
+ */
void
savePin(resp_https_t response, req_https_t request) {
if (!authenticate(response, request)) return;
@@ -724,6 +836,11 @@ namespace confighttp {
}
}
+ /**
+ * @brief Unpair all clients.
+ * @param response The HTTP response object.
+ * @param request The HTTP request object.
+ */
void
unpairAll(resp_https_t response, req_https_t request) {
if (!authenticate(response, request)) return;
@@ -742,6 +859,17 @@ namespace confighttp {
outputTree.put("status", true);
}
+ /**
+ * @brief Unpair a client.
+ * @param response The HTTP response object.
+ * @param request The HTTP request object.
+ * The body for the post request should be JSON serialized in the following format:
+ * @code{.json}
+ * {
+ * "uuid": ""
+ * }
+ * @endcode
+ */
void
unpair(resp_https_t response, req_https_t request) {
if (!authenticate(response, request)) return;
@@ -773,6 +901,11 @@ namespace confighttp {
}
}
+ /**
+ * @brief Get the list of paired clients.
+ * @param response The HTTP response object.
+ * @param request The HTTP request object.
+ */
void
listClients(resp_https_t response, req_https_t request) {
if (!authenticate(response, request)) return;
@@ -795,6 +928,11 @@ namespace confighttp {
outputTree.put("status", true);
}
+ /**
+ * @brief Close the currently running application.
+ * @param response The HTTP response object.
+ * @param request The HTTP request object.
+ */
void
closeApp(resp_https_t response, req_https_t request) {
if (!authenticate(response, request)) return;
diff --git a/src_assets/common/assets/web/apps.html b/src_assets/common/assets/web/apps.html
index f9191eae088..e10ce0262fb 100644
--- a/src_assets/common/assets/web/apps.html
+++ b/src_assets/common/assets/web/apps.html
@@ -506,12 +506,12 @@ {{ $t('apps.env_vars_about') }}
if (dotIndex < 0 || slashIndex < 0) {
return null;
}
- const hash = thumb.substring(slashIndex + 1, dotIndex);
+ const slug = thumb.substring(slashIndex + 1, dotIndex);
return {
name: game.name,
- key: "igdb_" + game.id,
- url: "https://images.igdb.com/igdb/image/upload/t_cover_big/" + hash + ".jpg",
- saveUrl: "https://images.igdb.com/igdb/image/upload/t_cover_big_2x/" + hash + ".png",
+ key: `igdb_${game.id}`,
+ url: `https://images.igdb.com/igdb/image/upload/t_cover_big/${slug}.jpg`,
+ saveUrl: `https://images.igdb.com/igdb/image/upload/t_cover_big_2x/${slug}.png`,
}
}).filter(item => item));
}