add /y/accounts and /y/data/quests in API
This commit is contained in:
@@ -677,19 +677,21 @@ To enable the HTTP server, add a port number in the HTTPListen list in config.js
|
||||
All returned data is JSON-encoded, and all request data (for POST requests) must also be JSON-encoded with the `Content-Type: application/json` header.
|
||||
|
||||
The HTTP server has the following endpoints:
|
||||
* `GET /`: Returns the server's build date and revision.
|
||||
* `GET /y/data/ep3-cards`: Returns the Episode 3 card definitions.
|
||||
* `GET /y/data/ep3-cards-trial`: Returns the Episode 3 Trial Edition card definitions.
|
||||
* `GET /y/data/common-tables`: Returns the parameters for generating common items (ItemPT files). This endpoint returns a lot of data and can be slow!
|
||||
* `GET /y/data/rare-tables`: Returns a list of rare table names.
|
||||
* `GET /y/data/rare-tables/<TABLE-NAME>` (for example, `/y/data/rare-tables/rare-table-v4`): Returns the contents of a rare item table.
|
||||
* `GET /y/data/quests`: Returns metadata about all available quests and quest categories.
|
||||
* `GET /y/data/config`: Returns the server's configuration file.
|
||||
* `GET /y/accounts`: Returns information about all registered accounts.
|
||||
* `GET /y/clients`: Returns information about all connected clients on the game server.
|
||||
* `GET /y/proxy-clients`: Returns information about all connected clients on the proxy server.
|
||||
* `GET /y/lobbies`: Returns information about all lobbies and games.
|
||||
* `GET /y/server`: Returns information about the server.
|
||||
* `GET /y/all`: Returns the same information as the above four endpoints, but in a single call. This endpoint can be slow!
|
||||
* `GET /y/summary`: Returns a summary of the server's state, connected clients, active games, and proxy sessions.
|
||||
* `GET /y/rare-drops/stream`: WebSocket endpoint that sends messages whenever an announceable rare item is dropped in any game. See below.
|
||||
* `WS /y/rare-drops/stream`: WebSocket endpoint that sends messages whenever an announceable rare item is dropped in any game. See below.
|
||||
* `POST /y/shell-exec`: Runs a server shell command. Input should be a JSON dict of e.g. `{"command": "announce hello"}`; response will be a JSON dict of `{"result": "<result text>"}` or an HTTP error.
|
||||
|
||||
### Rare drop stream endpoint
|
||||
|
||||
+33
-39
@@ -13,6 +13,7 @@
|
||||
#include "EventUtils.hh"
|
||||
#include "Loggers.hh"
|
||||
#include "ProxyServer.hh"
|
||||
#include "Revision.hh"
|
||||
#include "Server.hh"
|
||||
#include "ShellCommands.hh"
|
||||
|
||||
@@ -441,22 +442,12 @@ void HTTPServer::dispatch_handle_request(struct evhttp_request* req, void* ctx)
|
||||
reinterpret_cast<HTTPServer*>(ctx)->handle_request(req);
|
||||
}
|
||||
|
||||
phosg::JSON HTTPServer::generate_quest_json_st(shared_ptr<const Quest> q) {
|
||||
if (!q) {
|
||||
return nullptr;
|
||||
}
|
||||
auto battle_rules_json = q->battle_rules ? q->battle_rules->json() : nullptr;
|
||||
auto challenge_template_index_json = (q->challenge_template_index >= 0)
|
||||
? q->challenge_template_index
|
||||
: phosg::JSON(nullptr);
|
||||
phosg::JSON HTTPServer::generate_server_version_st() {
|
||||
return phosg::JSON::dict({
|
||||
{"Number", q->quest_number},
|
||||
{"Episode", name_for_episode(q->episode)},
|
||||
{"Joinable", q->joinable},
|
||||
{"LockStatusRegister", (q->lock_status_register >= 0) ? q->lock_status_register : phosg::JSON(nullptr)},
|
||||
{"Name", q->name},
|
||||
{"BattleRules", std::move(battle_rules_json)},
|
||||
{"ChallengeTemplateIndex", std::move(challenge_template_index_json)},
|
||||
{"ServerType", "newserv"},
|
||||
{"BuildTime", BUILD_TIMESTAMP},
|
||||
{"BuildTimeStr", phosg::format_time(BUILD_TIMESTAMP)},
|
||||
{"Revision", GIT_REVISION_HASH},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -575,7 +566,7 @@ phosg::JSON HTTPServer::generate_game_client_json_st(shared_ptr<const Client> c,
|
||||
if (p) {
|
||||
if (!is_ep3(c->version())) {
|
||||
if (c->version() != Version::DC_NTE) {
|
||||
ret.emplace("InventoryLanguage", p->inventory.language);
|
||||
ret.emplace("InventoryLanguage", name_for_language_code(p->inventory.language));
|
||||
ret.emplace("NumHPMaterialsUsed", p->get_material_usage(PSOBBCharacterFile::MaterialType::HP));
|
||||
ret.emplace("NumTPMaterialsUsed", p->get_material_usage(PSOBBCharacterFile::MaterialType::TP));
|
||||
if (!is_v1_or_v2(c->version())) {
|
||||
@@ -861,7 +852,7 @@ phosg::JSON HTTPServer::generate_lobby_json_st(shared_ptr<const Lobby> l, shared
|
||||
}
|
||||
}
|
||||
ret.emplace("FloorItems", std::move(floor_items_json));
|
||||
ret.emplace("Quest", HTTPServer::generate_quest_json_st(l->quest));
|
||||
ret.emplace("Quest", l->quest ? l->quest->json() : phosg::JSON(nullptr));
|
||||
|
||||
} else {
|
||||
ret.emplace("BattleInProgress", l->check_flag(Lobby::Flag::BATTLE_IN_PROGRESS));
|
||||
@@ -966,6 +957,16 @@ phosg::JSON HTTPServer::generate_lobby_json_st(shared_ptr<const Lobby> l, shared
|
||||
return ret;
|
||||
}
|
||||
|
||||
phosg::JSON HTTPServer::generate_accounts_json() const {
|
||||
return call_on_event_thread<phosg::JSON>(this->state->base, [&]() {
|
||||
auto res = phosg::JSON::list();
|
||||
for (const auto& it : this->state->account_index->all()) {
|
||||
res.emplace_back(it->json());
|
||||
}
|
||||
return res;
|
||||
});
|
||||
}
|
||||
|
||||
phosg::JSON HTTPServer::generate_game_server_clients_json() const {
|
||||
return call_on_event_thread<phosg::JSON>(this->state->base, [&]() {
|
||||
auto res = phosg::JSON::list();
|
||||
@@ -1083,7 +1084,7 @@ phosg::JSON HTTPServer::generate_summary_json() const {
|
||||
game_json.emplace("SectionID", name_for_section_id(l->effective_section_id()));
|
||||
game_json.emplace("Mode", name_for_mode(l->mode));
|
||||
game_json.emplace("Difficulty", name_for_difficulty(l->difficulty));
|
||||
game_json.emplace("Quest", this->generate_quest_json_st(l->quest));
|
||||
game_json.emplace("Quest", l->quest ? l->quest->json() : phosg::JSON(nullptr));
|
||||
}
|
||||
games_json.emplace_back(std::move(game_json));
|
||||
}
|
||||
@@ -1155,6 +1156,12 @@ phosg::JSON HTTPServer::generate_rare_table_json(const std::string& table_name)
|
||||
}
|
||||
}
|
||||
|
||||
phosg::JSON HTTPServer::generate_quest_list_json(std::shared_ptr<const QuestIndex> quest_index) {
|
||||
return call_on_event_thread<phosg::JSON>(this->state->base, [&]() {
|
||||
return quest_index->json();
|
||||
});
|
||||
}
|
||||
|
||||
void HTTPServer::require_GET(struct evhttp_request* req) {
|
||||
if (evhttp_request_get_command(req) != EVHTTP_REQ_GET) {
|
||||
throw HTTPServer::http_error(405, "GET method required for this endpoint");
|
||||
@@ -1198,23 +1205,7 @@ void HTTPServer::handle_request(struct evhttp_request* req) {
|
||||
|
||||
if (uri == "/") {
|
||||
this->require_GET(req);
|
||||
auto endpoints_json = phosg::JSON::list({
|
||||
"/y/data/ep3-cards",
|
||||
"/y/data/ep3-cards-trial",
|
||||
"/y/data/common-tables",
|
||||
"/y/data/rare-tables",
|
||||
"/y/data/rare-tables/<TABLE-NAME>",
|
||||
"/y/data/config",
|
||||
"/y/clients",
|
||||
"/y/proxy-clients",
|
||||
"/y/lobbies",
|
||||
"/y/server",
|
||||
"/y/rare-drops/stream",
|
||||
"/y/summary",
|
||||
"/y/all",
|
||||
"/y/shell-exec",
|
||||
});
|
||||
ret = make_shared<phosg::JSON>(phosg::JSON::dict({{"endpoints", std::move(endpoints_json)}}));
|
||||
ret = make_shared<phosg::JSON>(this->generate_server_version_st());
|
||||
|
||||
} else if (uri == "/y/shell-exec") {
|
||||
auto json = this->require_POST(req);
|
||||
@@ -1233,7 +1224,7 @@ void HTTPServer::handle_request(struct evhttp_request* req) {
|
||||
throw http_error(400, "this path requires a websocket connection");
|
||||
} else {
|
||||
this->rare_drop_subscribers.emplace(c);
|
||||
auto version_message = phosg::JSON::dict({{"ServerType", "newserv"}});
|
||||
auto version_message = this->generate_server_version_st();
|
||||
this->send_websocket_message(c, version_message.serialize());
|
||||
return;
|
||||
}
|
||||
@@ -1253,9 +1244,15 @@ void HTTPServer::handle_request(struct evhttp_request* req) {
|
||||
} else if (!strncmp(uri.c_str(), "/y/data/rare-tables/", 20)) {
|
||||
this->require_GET(req);
|
||||
ret = make_shared<phosg::JSON>(this->generate_rare_table_json(uri.substr(20)));
|
||||
} else if (uri == "/y/data/quests") {
|
||||
this->require_GET(req);
|
||||
ret = make_shared<phosg::JSON>(this->generate_quest_list_json(this->state->quest_index(Version::GC_V3)));
|
||||
} else if (uri == "/y/data/config") {
|
||||
this->require_GET(req);
|
||||
ret = call_on_event_thread<shared_ptr<const phosg::JSON>>(this->state->base, [this]() { return this->state->config_json; });
|
||||
} else if (uri == "/y/accounts") {
|
||||
this->require_GET(req);
|
||||
ret = make_shared<phosg::JSON>(this->generate_accounts_json());
|
||||
} else if (uri == "/y/clients") {
|
||||
this->require_GET(req);
|
||||
ret = make_shared<phosg::JSON>(this->generate_game_server_clients_json());
|
||||
@@ -1271,9 +1268,6 @@ void HTTPServer::handle_request(struct evhttp_request* req) {
|
||||
} else if (uri == "/y/summary") {
|
||||
this->require_GET(req);
|
||||
ret = make_shared<phosg::JSON>(this->generate_summary_json());
|
||||
} else if (uri == "/y/all") {
|
||||
this->require_GET(req);
|
||||
ret = make_shared<phosg::JSON>(this->generate_all_json());
|
||||
|
||||
} else {
|
||||
throw http_error(404, "unknown action");
|
||||
|
||||
+3
-1
@@ -100,12 +100,13 @@ protected:
|
||||
const std::string& key,
|
||||
const std::string* _default = nullptr);
|
||||
|
||||
static phosg::JSON generate_quest_json_st(std::shared_ptr<const Quest> q);
|
||||
static phosg::JSON generate_server_version_st();
|
||||
static phosg::JSON generate_client_config_json_st(const Client::Config& config);
|
||||
static phosg::JSON generate_account_json_st(std::shared_ptr<const Account> a);
|
||||
static phosg::JSON generate_game_client_json_st(std::shared_ptr<const Client> c, std::shared_ptr<const ItemNameIndex> item_name_index);
|
||||
static phosg::JSON generate_proxy_client_json_st(std::shared_ptr<const ProxyServer::LinkedSession> ses);
|
||||
static phosg::JSON generate_lobby_json_st(std::shared_ptr<const Lobby> l, std::shared_ptr<const ItemNameIndex> item_name_index);
|
||||
phosg::JSON generate_accounts_json() const;
|
||||
phosg::JSON generate_game_server_clients_json() const;
|
||||
phosg::JSON generate_proxy_server_clients_json() const;
|
||||
phosg::JSON generate_server_info_json() const;
|
||||
@@ -117,4 +118,5 @@ protected:
|
||||
phosg::JSON generate_common_tables_json() const;
|
||||
phosg::JSON generate_rare_tables_json() const;
|
||||
phosg::JSON generate_rare_table_json(const std::string& table_name) const;
|
||||
phosg::JSON generate_quest_list_json(std::shared_ptr<const QuestIndex> q);
|
||||
};
|
||||
|
||||
@@ -397,6 +397,41 @@ Quest::Quest(shared_ptr<const VersionedQuest> initial_version)
|
||||
this->add_version(initial_version);
|
||||
}
|
||||
|
||||
phosg::JSON Quest::json() const {
|
||||
auto versions_json = phosg::JSON::list();
|
||||
for (const auto& [_, vq] : this->versions) {
|
||||
versions_json.emplace_back(phosg::JSON::dict({
|
||||
{"Version", phosg::name_for_enum(vq->version)},
|
||||
{"Language", name_for_language_code(vq->language)},
|
||||
{"ShortDescription", vq->short_description},
|
||||
{"LongDescription", vq->long_description},
|
||||
{"BINFileSize", vq->bin_contents ? vq->bin_contents->size() : phosg::JSON(nullptr)},
|
||||
{"DATFileSize", vq->dat_contents ? vq->dat_contents->size() : phosg::JSON(nullptr)},
|
||||
{"PVRFileSize", vq->pvr_contents ? vq->pvr_contents->size() : phosg::JSON(nullptr)},
|
||||
}));
|
||||
}
|
||||
|
||||
auto battle_rules_json = this->battle_rules ? this->battle_rules->json() : nullptr;
|
||||
auto challenge_template_index_json = (this->challenge_template_index >= 0)
|
||||
? this->challenge_template_index
|
||||
: phosg::JSON(nullptr);
|
||||
return phosg::JSON::dict({
|
||||
{"Number", this->quest_number},
|
||||
{"CategoryID", this->category_id},
|
||||
{"Episode", name_for_episode(this->episode)},
|
||||
{"AllowStartFromChatCommand", this->allow_start_from_chat_command},
|
||||
{"Joinable", this->joinable},
|
||||
{"LockStatusRegister", (this->lock_status_register >= 0) ? this->lock_status_register : phosg::JSON(nullptr)},
|
||||
{"Name", this->name},
|
||||
{"BattleRules", std::move(battle_rules_json)},
|
||||
{"ChallengeTemplateIndex", std::move(challenge_template_index_json)},
|
||||
{"DescriptionFlag", this->description_flag},
|
||||
{"AvailableExpression", this->available_expression ? this->available_expression->str() : phosg::JSON(nullptr)},
|
||||
{"EnabledExpression", this->available_expression ? this->available_expression->str() : phosg::JSON(nullptr)},
|
||||
{"Versions", std::move(versions_json)},
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t Quest::versions_key(Version v, uint8_t language) {
|
||||
return (static_cast<uint32_t>(v) << 8) | language;
|
||||
}
|
||||
@@ -863,6 +898,34 @@ QuestIndex::QuestIndex(
|
||||
}
|
||||
}
|
||||
|
||||
phosg::JSON QuestIndex::json() const {
|
||||
auto categories_json = phosg::JSON::dict();
|
||||
for (const auto& cat : this->category_index->categories) {
|
||||
auto dict = phosg::JSON::dict({
|
||||
{"CategoryID", cat->category_id},
|
||||
{"Flags", cat->enabled_flags},
|
||||
{"DirectoryName", cat->directory_name},
|
||||
{"Name", cat->name},
|
||||
{"Description", cat->description},
|
||||
});
|
||||
categories_json.emplace(cat->name, std::move(dict));
|
||||
}
|
||||
|
||||
auto quests_json = phosg::JSON::list();
|
||||
for (const auto& [_, q] : this->quests_by_number) {
|
||||
quests_json.emplace_back(q->json());
|
||||
}
|
||||
|
||||
return phosg::JSON::dict({
|
||||
{"Directory", this->directory},
|
||||
{"Categories", std::move(categories_json)},
|
||||
{"Quests", std::move(quests_json)},
|
||||
});
|
||||
// std::map<uint32_t, std::shared_ptr<Quest>> quests_by_number;
|
||||
// std::map<std::string, std::shared_ptr<Quest>> quests_by_name;
|
||||
// std::map<uint32_t, std::map<uint32_t, std::shared_ptr<Quest>>> quests_by_category_id_and_number;
|
||||
}
|
||||
|
||||
shared_ptr<const Quest> QuestIndex::get(uint32_t quest_number) const {
|
||||
try {
|
||||
return this->quests_by_number.at(quest_number);
|
||||
|
||||
+20
-18
@@ -113,24 +113,7 @@ struct VersionedQuest {
|
||||
std::string encode_qst() const;
|
||||
};
|
||||
|
||||
class Quest {
|
||||
public:
|
||||
Quest() = delete;
|
||||
explicit Quest(std::shared_ptr<const VersionedQuest> initial_version);
|
||||
Quest(const Quest&) = default;
|
||||
Quest(Quest&&) = default;
|
||||
Quest& operator=(const Quest&) = default;
|
||||
Quest& operator=(Quest&&) = default;
|
||||
|
||||
std::shared_ptr<const SuperMap> get_supermap(int64_t random_seed) const;
|
||||
|
||||
void add_version(std::shared_ptr<const VersionedQuest> vq);
|
||||
bool has_version(Version v, uint8_t language) const;
|
||||
bool has_version_any_language(Version v) const;
|
||||
std::shared_ptr<const VersionedQuest> version(Version v, uint8_t language) const;
|
||||
|
||||
static uint32_t versions_key(Version v, uint8_t language);
|
||||
|
||||
struct Quest {
|
||||
uint32_t quest_number;
|
||||
uint32_t category_id;
|
||||
Episode episode;
|
||||
@@ -145,6 +128,24 @@ public:
|
||||
std::shared_ptr<const IntegralExpression> available_expression;
|
||||
std::shared_ptr<const IntegralExpression> enabled_expression;
|
||||
std::map<uint32_t, std::shared_ptr<const VersionedQuest>> versions;
|
||||
|
||||
Quest() = delete;
|
||||
explicit Quest(std::shared_ptr<const VersionedQuest> initial_version);
|
||||
Quest(const Quest&) = default;
|
||||
Quest(Quest&&) = default;
|
||||
Quest& operator=(const Quest&) = default;
|
||||
Quest& operator=(Quest&&) = default;
|
||||
|
||||
phosg::JSON json() const;
|
||||
|
||||
std::shared_ptr<const SuperMap> get_supermap(int64_t random_seed) const;
|
||||
|
||||
void add_version(std::shared_ptr<const VersionedQuest> vq);
|
||||
bool has_version(Version v, uint8_t language) const;
|
||||
bool has_version_any_language(Version v) const;
|
||||
std::shared_ptr<const VersionedQuest> version(Version v, uint8_t language) const;
|
||||
|
||||
static uint32_t versions_key(Version v, uint8_t language);
|
||||
};
|
||||
|
||||
struct QuestIndex {
|
||||
@@ -163,6 +164,7 @@ struct QuestIndex {
|
||||
std::map<uint32_t, std::map<uint32_t, std::shared_ptr<Quest>>> quests_by_category_id_and_number;
|
||||
|
||||
QuestIndex(const std::string& directory, std::shared_ptr<const QuestCategoryIndex> category_index, bool is_ep3);
|
||||
phosg::JSON json() const;
|
||||
|
||||
std::shared_ptr<const Quest> get(uint32_t quest_number) const;
|
||||
std::shared_ptr<const Quest> get(const std::string& name) const;
|
||||
|
||||
Reference in New Issue
Block a user