Add basic URL builder

This commit is contained in:
Tamás Bálint Misius
2025-08-29 13:03:34 +02:00
parent 3dab120c17
commit ed83d5d3aa
23 changed files with 96 additions and 54 deletions

View File

@@ -285,6 +285,19 @@ std::unique_ptr<std::vector<char>> format::PixelsToPNG(PlaneAdapter<std::vector<
const static char hex[] = "0123456789ABCDEF";
ByteString format::Url::ToByteString() const
{
ByteStringBuilder sb;
sb << base;
bool first = true;
for (auto &[ key, value ] : params)
{
sb << (first ? '?' : '&') << key << "=" << format::URLEncode(value);
first = false;
}
return sb.Build();
}
ByteString format::URLEncode(ByteString source)
{
ByteString result;

View File

@@ -2,6 +2,7 @@
#include <memory>
#include <span>
#include <vector>
#include <map>
#include "common/String.h"
#include "common/Plane.h"
#include "graphics/Pixel.h"
@@ -11,6 +12,14 @@ class VideoBuffer;
namespace format
{
struct Url
{
ByteString base;
std::map<ByteString, ByteString> params;
ByteString ToByteString() const;
};
ByteString URLEncode(ByteString value);
ByteString URLDecode(ByteString value);
ByteString UnixtimeToDate(time_t unixtime, ByteString dateFomat = ByteString("%d %b %Y"), bool local = true);

View File

@@ -5,23 +5,17 @@ namespace http
{
namespace
{
ByteString MaybeAppendSession(APIRequest::AuthMode authMode, ByteString url)
format::Url &MaybeAppendSession(APIRequest::AuthMode authMode, format::Url &url)
{
if (auto user = Client::Ref().GetAuthUser(); user && authMode == APIRequest::authRequireAppendSession)
{
// TODO: proper url builder >_>
auto appendChar = '?';
if (url.find('?') != url.npos)
{
appendChar = '&';
}
url = ByteString::Build(url, appendChar, "Key=", user->SessionKey);;
url.params["Key"] = user->SessionKey;
}
return url;
}
}
APIRequest::APIRequest(ByteString url, AuthMode authMode, bool newCheckStatus) : Request(MaybeAppendSession(authMode, url)), checkStatus(newCheckStatus)
APIRequest::APIRequest(format::Url url, AuthMode authMode, bool newCheckStatus) : Request(MaybeAppendSession(authMode, url).ToByteString()), checkStatus(newCheckStatus)
{
auto user = Client::Ref().GetAuthUser();
if ((authMode == authRequire ||

View File

@@ -1,6 +1,7 @@
#pragma once
#include "Request.h"
#include "common/String.h"
#include "Format.h"
#include <json/json.h>
namespace http
@@ -17,7 +18,7 @@ namespace http
authUse,
authOmit,
};
APIRequest(ByteString url, AuthMode authMode, bool newCheckStatus);
APIRequest(format::Url url, AuthMode authMode, bool newCheckStatus);
Json::Value Finish();
};

View File

@@ -5,7 +5,9 @@
namespace http
{
AddCommentRequest::AddCommentRequest(int saveID, String comment) :
APIRequest(ByteString::Build(SERVER, "/Browse/Comments.json?ID=", saveID), authRequire, true)
APIRequest({ ByteString::Build(SERVER, "/Browse/Comments.json"), {
{ "ID", ByteString::Build(saveID) },
} }, authRequire, true)
{
auto user = Client::Ref().GetAuthUser();
AddPostData(FormData{

View File

@@ -4,7 +4,11 @@
namespace http
{
AddTagRequest::AddTagRequest(int saveID, ByteString tag) :
APIRequest(ByteString::Build(SERVER, "/Browse/EditTag.json?Op=add&ID=", saveID, "&Tag=", tag), authRequireAppendSession, true)
APIRequest({ ByteString::Build(SERVER, "/Browse/EditTag.json"), {
{ "Op", "add" },
{ "ID", ByteString::Build(saveID) },
{ "Tag", tag }
} }, authRequireAppendSession, true)
{
}

View File

@@ -4,7 +4,10 @@
namespace http
{
DeleteSaveRequest::DeleteSaveRequest(int saveID) :
APIRequest(ByteString::Build(SERVER, "/Browse/Delete.json?ID=", saveID, "&Mode=Delete"), authRequireAppendSession, true)
APIRequest({ ByteString::Build(SERVER, "/Browse/Delete.json"), {
{ "ID", ByteString::Build(saveID) },
{ "Mode", "Delete" },
} }, authRequireAppendSession, true)
{
}

View File

@@ -5,7 +5,7 @@
namespace http
{
ExecVoteRequest::ExecVoteRequest(int saveID, int newDirection) :
APIRequest(ByteString::Build(SERVER, "/Vote.api"), authRequire, false),
APIRequest({ ByteString::Build(SERVER, "/Vote.api") }, authRequire, false),
direction(newDirection)
{
auto user = Client::Ref().GetAuthUser();

View File

@@ -3,15 +3,15 @@
namespace http
{
static ByteString Url(int saveID, bool favourite)
static format::Url Url(int saveID, bool favourite)
{
ByteStringBuilder builder;
builder << SERVER << "/Browse/Favourite.json?ID=" << saveID;
format::Url url{ ByteString::Build(SERVER, "/Browse/Favourite.json") };
url.params["ID"] = ByteString::Build(saveID);
if (!favourite)
{
builder << "&Mode=Remove";
url.params["Mode"] = "Remove";
}
return builder.Build();
return url;
}
FavouriteSaveRequest::FavouriteSaveRequest(int saveID, bool newFavourite) :

View File

@@ -5,7 +5,11 @@
namespace http
{
GetCommentsRequest::GetCommentsRequest(int saveID, int start, int count) :
APIRequest(ByteString::Build(SERVER, "/Browse/Comments.json?ID=", saveID, "&Start=", start, "&Count=", count), authOmit, false)
APIRequest({ ByteString::Build(SERVER, "/Browse/Comments.json"), {
{ "ID", ByteString::Build(saveID) },
{ "Start", ByteString::Build(start) },
{ "Count", ByteString::Build(count) },
} }, authOmit, false)
{
}

View File

@@ -2,22 +2,23 @@
#include "client/Client.h"
#include "client/SaveInfo.h"
#include "client/GameSave.h"
#include "Format.h"
#include "Config.h"
namespace http
{
static ByteString Url(int saveID, int saveDate)
static format::Url Url(int saveID, int saveDate)
{
ByteStringBuilder builder;
builder << SERVER << "/Browse/View.json?ID=" << saveID;
format::Url url{ ByteString::Build(SERVER, "/Browse/View.json") };
url.params["ID"] = ByteString::Build(saveID);
if (saveDate)
{
builder << "&Date=" << saveDate;
url.params["Date"] = ByteString::Build(saveDate);
}
return builder.Build();
return url;
}
GetSaveRequest::GetSaveRequest(int saveID, int saveDate) : Request(Url(saveID, saveDate))
GetSaveRequest::GetSaveRequest(int saveID, int saveDate) : Request(Url(saveID, saveDate).ToByteString())
{
auto user = Client::Ref().GetAuthUser();
if (user)

View File

@@ -5,7 +5,9 @@
namespace http
{
GetUserInfoRequest::GetUserInfoRequest(ByteString username) :
APIRequest(ByteString::Build(SERVER, "/User.json?Name=", username), authOmit, false)
APIRequest({ ByteString::Build(SERVER, "/User.json"), {
{ "Name", username },
} }, authOmit, false)
{
}

View File

@@ -4,7 +4,7 @@
namespace http
{
LogoutRequest::LogoutRequest() :
APIRequest(ByteString::Build(SERVER, "/Logout.json"), authRequireAppendSession, true)
APIRequest({ ByteString::Build(SERVER, "/Logout.json") }, authRequireAppendSession, true)
{
}

View File

@@ -4,7 +4,9 @@
namespace http
{
PublishSaveRequest::PublishSaveRequest(int saveID) :
APIRequest(ByteString::Build(SERVER, "/Browse/View.json?ID=", saveID), authRequireAppendSession, true)
APIRequest({ ByteString::Build(SERVER, "/Browse/View.json"), {
{ "ID", ByteString::Build(saveID) },
} }, authRequireAppendSession, true)
{
AddPostData(FormData{
{ "ActionPublish", "bagels" },

View File

@@ -4,7 +4,11 @@
namespace http
{
RemoveTagRequest::RemoveTagRequest(int saveID, ByteString tag) :
APIRequest(ByteString::Build(SERVER, "/Browse/EditTag.json?Op=delete&ID=", saveID, "&Tag=", tag), authRequireAppendSession, true)
APIRequest({ ByteString::Build(SERVER, "/Browse/EditTag.json"), {
{ "Op", "delete" },
{ "ID", ByteString::Build(saveID) },
{ "Tag", tag },
} }, authRequireAppendSession, true)
{
}

View File

@@ -4,7 +4,9 @@
namespace http
{
ReportSaveRequest::ReportSaveRequest(int saveID, String message) :
APIRequest(ByteString::Build(SERVER, "/Browse/Report.json?ID=", saveID), authRequireAppendSession, true)
APIRequest({ ByteString::Build(SERVER, "/Browse/Report.json"), {
{ "ID", ByteString::Build(saveID) },
} }, authRequireAppendSession, true)
{
AddPostData(FormData{
{ "Reason", message.ToUtf8() },

View File

@@ -4,7 +4,7 @@
namespace http
{
SaveUserInfoRequest::SaveUserInfoRequest(UserInfo info) :
APIRequest(ByteString::Build(SERVER, "/Profile.json"), authRequire, true)
APIRequest({ ByteString::Build(SERVER, "/Profile.json") }, authRequire, true)
{
AddPostData(FormData{
{ "Location", info.location.ToUtf8() },

View File

@@ -7,10 +7,11 @@
namespace http
{
static ByteString Url(int start, int count, ByteString query, Period period, Sort sort, Category category)
static format::Url Url(int start, int count, ByteString query, Period period, Sort sort, Category category)
{
ByteStringBuilder builder;
builder << SERVER << "/Browse.json?Start=" << start << "&Count=" << count;
format::Url url{ ByteString::Build(SERVER, "/Browse.json") };
url.params["Start"] = ByteString::Build(start);
url.params["Count"] = ByteString::Build(count);
auto appendToQuery = [&query](ByteString str) {
if (query.size())
{
@@ -63,7 +64,7 @@ namespace http
switch (category)
{
case categoryFavourites:
builder << "&Category=Favourites";
url.params["Category"] = "Favourites";
break;
case categoryMyOwn:
@@ -76,9 +77,9 @@ namespace http
}
if (query.size())
{
builder << "&Search_Query=" << format::URLEncode(query);
url.params["Search_Query"] = query;
}
return builder.Build();
return url;
}
SearchSavesRequest::SearchSavesRequest(int start, int count, ByteString query, Period period, Sort sort, Category category) : APIRequest(Url(start, count, query, period, sort, category), authUse, false)

View File

@@ -4,15 +4,16 @@
namespace http
{
static ByteString Url(int start, int count, ByteString query)
static format::Url Url(int start, int count, ByteString query)
{
ByteStringBuilder builder;
builder << SERVER << "/Browse/Tags.json?Start=" << start << "&Count=" << count;
format::Url url{ ByteString::Build(SERVER, "/Browse/Tags.json") };
url.params["Start"] = ByteString::Build(start);
url.params["Count"] = ByteString::Build(count);
if (query.size())
{
builder << "&Search_Query=" << format::URLEncode(query);
url.params["Search_Query"] = query;
}
return builder.Build();
return url;
}
SearchTagsRequest::SearchTagsRequest(int start, int count, ByteString query) : APIRequest(Url(start, count, query), authOmit, false)

View File

@@ -4,7 +4,10 @@
namespace http
{
UnpublishSaveRequest::UnpublishSaveRequest(int saveID) :
APIRequest(ByteString::Build(SERVER, "/Browse/Delete.json?ID=", saveID, "&Mode=Unpublish"), authRequireAppendSession, true)
APIRequest({ ByteString::Build(SERVER, "/Browse/Delete.json"), {
{ "ID", ByteString::Build(saveID) },
{ "Mode", "Unpublish" },
} }, authRequireAppendSession, true)
{
}

View File

@@ -59,14 +59,6 @@ std::vector<std::unique_ptr<SaveInfo>> SearchModel::EndSearchSaves()
void SearchModel::BeginGetTags(int start, int count, String query)
{
lastError = "";
ByteStringBuilder urlStream;
urlStream << SERVER << "/Browse/Tags.json?Start=" << start << "&Count=" << count;
if(query.length())
{
urlStream << "&Search_Query=";
if(query.length())
urlStream << format::URLEncode(query.ToUtf8());
}
getTags = std::make_unique<http::SearchTagsRequest>(start, count, query.ToUtf8());
getTags->Start();
}

View File

@@ -407,7 +407,10 @@ static int request(lua_State *L, bool isPost)
static int getAuthToken(lua_State *L)
{
return LuaHttp::RequestHandle::Make(L, ByteString::Build(SERVER, "/ExternalAuth.api?Action=Get&Audience=", format::URLEncode(tpt_lua_checkByteString(L, 1))), false, {}, LuaHttp::RequestHandle::getAuthToken, {}, {});
return LuaHttp::RequestHandle::Make(L, format::Url{ ByteString::Build(SERVER, "/ExternalAuth.api"), {
{ "Action", "Get" },
{ "Audience", tpt_lua_checkByteString(L, 1) },
} }.ToByteString(), false, {}, LuaHttp::RequestHandle::getAuthToken, {}, {});
}
static int get(lua_State *L)

View File

@@ -2,6 +2,7 @@
#include "client/http/Request.h"
#include "common/platform/Platform.h"
#include "compat_lua.h"
#include "Format.h"
#include "Config.h"
#include "gui/dialogues/ErrorMessage.h"
#include "gui/dialogues/InformationMessage.h"
@@ -39,7 +40,7 @@ static int installScriptManager(lua_State *L)
new ErrorMessage("Script download", "You must run this function from the console");
return 0;
}
lsi->scriptManagerDownload = std::make_unique<http::Request>(ByteString::Build("https://starcatcher.us/scripts/main.lua?get=1"));
lsi->scriptManagerDownload = std::make_unique<http::Request>(format::Url{ "https://starcatcher.us/scripts/main.lua", {{ "get", "1" }} }.ToByteString());
lsi->scriptManagerDownload->Start();
return 0;
}