server/src/confighttp_steam.cpp
Rama main · réplica de lectura del repositorio original.
/** @file src/confighttp_steam.cpp */
#include "config.h"
#include "config_steam.h"
#include "confighttp.h"
#include "file_handler.h"
#include "platform/common.h"
#include "steam_artwork.h"
#include "steam_integration.h"
#include "steam_sync_policy.h"
#include <algorithm>
#include <cctype>
#include <cstdint>
#include <nlohmann/json.hpp>
#include <Simple-Web-Server/server_https.hpp>
#include <sstream>
namespace confighttp {
using resp_https_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Response>;
using req_https_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Request>;
bool authenticate(resp_https_t response, req_https_t request);
bool check_content_type(resp_https_t response, req_https_t request, const std::string_view &contentType);
void send_response(resp_https_t response, const nlohmann::json &output_tree);
void bad_request(resp_https_t response, req_https_t request, const std::string &error_message = "Bad Request");
namespace {
nlohmann::json game_json(const platf::steam::game_t &game) {
nlohmann::json out;
out["appid"] = game.app_id;
out["steam_id"] = std::to_string(game.app_id);
out["stable_id"] = game.stable_id;
out["name"] = game.name;
out["install_dir"] = game.install_dir.generic_string();
out["library_path"] = game.library_path.generic_string();
out["icon_path"] = game.icon_path.generic_string();
out["header_path"] = game.header_path.generic_string();
out["portrait_path"] = game.portrait_path.generic_string();
out["boxart_path"] = game.boxart_path.generic_string();
out["artwork_path"] = game.artwork_path.generic_string();
out["artwork_client_path"] = game.artwork_client_path.generic_string();
out["artwork_format"] = game.artwork_format;
out["artwork_client_compatible"] = !game.artwork_client_path.empty();
out["app_type"] = game.app_type;
out["installed"] = game.installed;
out["last_played"] = game.last_played;
out["playtime_minutes"] = game.playtime_minutes;
out["importable"] = platf::steam::sync::policy::is_importable(game, config::steam.include_tools);
out["launch_uri"] = platf::steam::launch_uri(game.app_id);
return out;
}
} // namespace
void getSteamStatus(resp_https_t response, req_https_t request) {
if (!authenticate(response, request)) {
return;
}
try {
const auto provider_available = platf::steam::available();
auto found = platf::steam::discover();
const auto all_importable = platf::steam::sync::policy::filter_games(found, {}, config::steam.include_tools);
const auto selected = platf::steam::sync::policy::select_games(
found,
config::steam.sync_all_installed,
config::steam.recent_games,
config::steam.recent_max_age_days,
config::steam.exclude_games_meta,
config::steam.include_tools
);
const auto selected_importable = platf::steam::sync::policy::filter_games(selected, {}, config::steam.include_tools);
const auto filtered = platf::steam::sync::policy::filter_games(selected, config::steam.exclude_games_meta, config::steam.include_tools);
const auto all_filtered = platf::steam::sync::policy::filter_games(found, config::steam.exclude_games_meta, config::steam.include_tools);
nlohmann::json exclusions = nlohmann::json::array();
for (const auto &entry : config::steam.exclude_games_meta) {
exclusions.push_back({{"id", entry.id}, {"name", entry.name}});
}
nlohmann::json out {{"status", true}, {"provider", "steam"}, {"enabled", config::steam.enabled}, {"forced", false}, {"available", provider_available}, {"game_count", found.size()}, {"importable_game_count", filtered.size()}, {"tool_game_count", found.size() - all_importable.size()}, {"excluded_game_count", all_importable.size() - all_filtered.size()}, {"selected_game_count", selected_importable.size()}, {"exclude_games", std::move(exclusions)}, {"auto_sync", config::steam.auto_sync}, {"sync_all_installed", config::steam.sync_all_installed}, {"recent_games", config::steam.recent_games}, {"recent_max_age_days", config::steam.recent_max_age_days}, {"autosync_remove_uninstalled", config::steam.autosync_remove_uninstalled}, {"include_tools", config::steam.include_tools}};
#if defined(__linux__)
out["forced"] = true;
out["playnite_available"] = false;
#else
out["playnite_available"] = true;
#endif
send_response(response, out);
} catch (const std::exception &error) {
bad_request(response, request, error.what());
}
}
void getSteamGames(resp_https_t response, req_https_t request) {
if (!authenticate(response, request)) {
return;
}
try {
nlohmann::json out {{"status", true}, {"enabled", config::steam.enabled}, {"games", nlohmann::json::array()}};
auto games = platf::steam::discover_catalog();
// Prepare only the selected game's cover. Browsing a large library
// must not download or convert artwork for every search result.
const auto query = request->parse_query_string();
if (const auto selected = query.find("appid"); selected != query.end()) {
std::erase_if(games, [&](const auto &game) {
return std::to_string(game.app_id) != selected->second;
});
platf::steam::artwork::prepare(games, platf::appdata());
}
for (const auto &game : games) {
auto item = game_json(game);
item["excluded"] = std::find_if(config::steam.exclude_games_meta.begin(), config::steam.exclude_games_meta.end(), [&game](const auto &entry) {
return (!entry.id.empty() && entry.id == std::to_string(game.app_id)) ||
(entry.id.empty() && !entry.name.empty() && entry.name == game.name);
}) != config::steam.exclude_games_meta.end();
item["filtered"] = !platf::steam::sync::policy::is_importable(game, config::steam.include_tools);
out["games"].push_back(std::move(item));
}
send_response(response, out);
} catch (const std::exception &error) {
bad_request(response, request, error.what());
}
}
void postSteamForceSync(resp_https_t response, req_https_t request) {
if (!check_content_type(response, request, "application/json")) {
return;
}
if (!authenticate(response, request)) {
return;
}
if (!config::steam.enabled) {
bad_request(response, request, "Steam integration is disabled");
return;
}
try {
if (!platf::steam::available()) {
bad_request(response, request, "Steam installation was not found");
return;
}
auto catalog = platf::steam::discover_catalog();
auto found = platf::steam::sync::policy::select_games(
catalog,
config::steam.sync_all_installed,
config::steam.recent_games,
config::steam.recent_max_age_days,
config::steam.exclude_games_meta,
config::steam.include_tools
);
platf::steam::artwork::prepare(found, platf::appdata());
std::lock_guard apps_lock {apps_file_mutex()};
auto root = nlohmann::json::parse(file_handler::read_file(config::stream.file_apps.c_str()));
const bool changed = platf::steam::sync::policy::reconcile(root, found, !config::steam.sync_all_installed || config::steam.autosync_remove_uninstalled, config::steam.exclude_games_meta, config::steam.include_tools, config::steam.sync_all_installed ? "installed" : "recent");
if (changed && !refresh_client_apps_cache(root)) {
bad_request(response, request, "Unable to save applications");
return;
}
const auto filtered = platf::steam::sync::policy::filter_games(found, config::steam.exclude_games_meta, config::steam.include_tools);
send_response(response, nlohmann::json {{"status", true}, {"changed", changed}, {"game_count", found.size()}, {"importable_game_count", filtered.size()}});
} catch (const std::exception &error) {
bad_request(response, request, error.what());
}
}
void postSteamLaunch(resp_https_t response, req_https_t request) {
if (!check_content_type(response, request, "application/json")) {
return;
}
if (!authenticate(response, request)) {
return;
}
if (!config::steam.enabled) {
bad_request(response, request, "Steam integration is disabled");
return;
}
try {
std::stringstream stream;
stream << request->content.rdbuf();
const auto input = nlohmann::json::parse(stream.str());
std::uint64_t app_id = 0;
const auto id = input.contains("appid") ? input["appid"] : input.value("steam_id", nlohmann::json {});
if (id.is_number_unsigned() || id.is_number_integer()) {
app_id = id.get<std::uint64_t>();
} else if (id.is_string()) {
const auto text = id.get<std::string>();
if (text.empty() || !std::all_of(text.begin(), text.end(), [](unsigned char c) {
return std::isdigit(c) != 0;
})) {
bad_request(response, request, "Steam app ID must be numeric");
return;
}
app_id = std::stoull(text);
}
if (app_id == 0 || app_id > UINT32_MAX) {
bad_request(response, request, "Invalid Steam app ID or Steam launch failed");
return;
}
const auto requested_id = static_cast<std::uint32_t>(app_id);
const auto launchable = platf::steam::sync::policy::filter_games(
platf::steam::discover(),
config::steam.exclude_games_meta,
config::steam.include_tools
);
const auto installed = std::find_if(launchable.begin(), launchable.end(), [requested_id](const auto &game) {
return game.app_id == requested_id;
});
if (installed == launchable.end() || !platf::steam::launch(requested_id)) {
bad_request(response, request, "Steam app ID is not installed, is excluded, or Steam launch failed");
return;
}
send_response(response, nlohmann::json {{"status", true}, {"launch_uri", platf::steam::launch_uri(static_cast<std::uint32_t>(app_id))}});
} catch (const std::exception &error) {
bad_request(response, request, error.what());
}
}
} // namespace confighttp