prepick working

This commit is contained in:
mrbesen 2022-07-02 12:36:38 +02:00
parent 69cc172f12
commit 9e0b15cff2
Signed by untrusted user: MrBesen
GPG Key ID: 596B2350DCD67504
10 changed files with 471 additions and 93 deletions

View File

@ -11,7 +11,9 @@ public:
INPROGRESS,
Accepted,
DECLINED
};
static ReadyCheckState toReadyCheckState(const QJsonObject& json);
enum class GameflowPhase : uint32_t {
NONE = 0,
@ -29,6 +31,7 @@ public:
ENDOFGAME,
TERMINATEDINERROR,
};
static GameflowPhase toGameflowPhase(const std::string&);
enum class ChampSelectPhase : uint32_t {
INVALID = 0,
@ -36,6 +39,7 @@ public:
BAN_PICK,
FINALIZATION
};
static ChampSelectPhase toChampSelectPhase(const std::string& str);
enum class Position : uint32_t {
INVALID = 0,
@ -45,6 +49,15 @@ public:
JUNGLE,
UTILITY
};
static Position toPosition(const std::string& str);
enum class ChampSelectActionType : uint32_t {
INVALID = 0,
BAN,
PICK,
TEN_BANS_REVEAL,
};
static ChampSelectActionType toChampSelectActionType(const std::string& str);
struct TimerInfo {
int64_t adjustedTimeLeftInPhase;
@ -54,6 +67,9 @@ public:
int64_t totalTimeInPhase;
bool valid = false;
TimerInfo();
explicit TimerInfo(const QJsonObject& json);
};
struct PlayerInfo {
@ -67,6 +83,60 @@ public:
uint32_t level = 0;
};
struct ChampSelectAction {
int32_t actorCellID = -1;
int32_t championID = 0;
int32_t id = 0;
bool completed = false;
bool isAllyAction = true;
bool isInProgress = false;
ChampSelectActionType type = ChampSelectActionType::INVALID;
ChampSelectAction();
explicit ChampSelectAction(const QJsonObject& json);
};
struct ChampSelectCell {
Position position;
int32_t cellID = 0;
int32_t championID = 0;
int32_t championPickIntentID = 0;
int64_t summonerID = 0;
ChampSelectCell();
explicit ChampSelectCell(const QJsonObject& json);
};
static std::vector<ChampSelectCell> loadAllInfos(const QJsonArray& arr);
struct ChampSelectSession {
std::vector<ChampSelectAction> actions;
int32_t counter = 0; // ??
int64_t gameid = 0;
bool allowDuplicatePick = false;
bool allowRerolling = false;
bool allowSkinSelection = true;
bool hasSimultaneousBans = true;
bool hasSimultaneousPicks = false;
bool isCustomGame = false;
bool isSpectating = false;
bool skipChampionSelect = false;
int32_t rerollsRemaining = 0;
int32_t localPlayerCellId = 0;
std::vector<ChampSelectCell> myTeam;
std::vector<ChampSelectCell> theirTeam;
TimerInfo timer;
// std::vector<> trades; // TODO
ChampSelectSession();
explicit ChampSelectSession(const QJsonObject& json);
operator bool();
};
ClientAPI(const ClientAccess& access);
~ClientAPI();
@ -74,8 +144,10 @@ public:
GameflowPhase getGameflowPhase();
void acceptMatch();
void declineMatch();
void getChampSelectSession();
ChampSelectSession getChampSelectSession();
bool setChampSelectAction(int32_t actionid, int32_t actorcellid, int32_t champid, bool completed, bool pick);
PlayerInfo getSelf();
std::vector<std::string> getLog();
std::vector<int32_t> getBannableChampIDs();
std::vector<int32_t> getPickableChampIDs();
@ -94,3 +166,4 @@ std::ostream& operator<<(std::ostream&, const ClientAPI::ReadyCheckState&);
std::ostream& operator<<(std::ostream&, const ClientAPI::GameflowPhase&);
std::ostream& operator<<(std::ostream&, const ClientAPI::ChampSelectPhase&);
std::ostream& operator<<(std::ostream&, const ClientAPI::Position&);
std::ostream& operator<<(std::ostream&, const ClientAPI::ChampSelectActionType&);

View File

@ -11,7 +11,7 @@ protected:
Stage();
virtual ~Stage();
std::string champ; // not every stage has a champ
uint32_t champid = 0; // not every stage has a champ
bool enabled = false;
virtual void action(LolAutoAccept& lolaa);
};
@ -37,7 +37,7 @@ public:
LolAutoAccept();
~LolAutoAccept();
void setChamp(const std::string& champ, State s);
void setChamp(uint32_t champ, State s);
void setEnabled(bool b, State s);
bool init(); // returns true on success

View File

@ -13,12 +13,14 @@ public:
GET,
POST,
PUT,
PATCH,
DELETE
};
protected:
QByteArray requestRaw(const std::string& url, Method m = Method::GET);
QJsonDocument request(const std::string& url, Method m = Method::GET);
QByteArray requestRaw(const std::string& url, Method m = Method::GET, const std::string& data = {});
QJsonDocument request(const std::string& url, Method m = Method::GET, const std::string& data = {});
void enableDebugging(bool enabled = true);
std::string baseurl;

View File

@ -25,6 +25,7 @@ defineReplace(prependAll) {
SOURCES += \
src/arg.cpp \
src/clientaccess.cpp \
src/clientapi_json.cpp \
src/clientapi.cpp \
src/config.cpp \
src/datadragon.cpp \

View File

@ -60,7 +60,7 @@ std::shared_ptr<ClientAccess> ClientAccess::find(bool uselockfile) {
std::string cmdfile = "/proc/" + std::to_string(pid) + "/cmdline";
std::vector<std::string> args = readProcFile(cmdfile);
Log::debug << "process: " << pid << " has " << args.size() << " args";
// Log::debug << "process: " << pid << " has " << args.size() << " args";
if(args.empty()) continue;

View File

@ -9,34 +9,10 @@
#include "json.h"
// calculate the size of a constant sized array
#define ARRSIZE(ARRNAME) (sizeof(ARRNAME) / sizeof(ARRNAME[0]))
#define ARR(NAME, ...) static const std::string NAME ## Names[] = {__VA_ARGS__}; \
static const uint32_t NAME ## NamesCount = ARRSIZE(NAME ## Names);
ARR(ReadyCheckState, "Invalid", "None", "InProgress", "Accepted", "Declined");
ARR(GameflowPhase, "None", "Lobby", "Matchmaking", "CheckedIntoTournament", "ReadyCheck", "ChampSelect", "GameStart", "FailedToLaunch", "InProgress", "Reconnect", "WaitingForStats", "PreEndOfGame", "EndOfGame", "TerminatedInError");
ARR(ChampSelectPhase, "Invalid", "PLANNING", "BAN_PICK", "FINALIZATION");
ARR(Position, "Invalid", "TOP", "MIDDLE", "BOTTOM", "JUNGLE", "UTILITY");
template<typename T>
static T mapEnum(const std::string& input, const std::string* names, uint32_t count, T defaul) {
for (uint32_t i = 0; i < count; ++i) {
if (names[i] == input) {
return (T) i;
}
}
Log::warn << "no mapping of enum-string: " << std::quoted(input);
return defaul;
}
#define MAPENUM(VAR, ENUM, DEFAULT) \
mapEnum(VAR, ENUM ## Names, ENUM ## NamesCount, ClientAPI:: ENUM :: DEFAULT)
ClientAPI::ClientAPI(const ClientAccess& ca) : RestClient(ca.getURL()), access(ca) {
basicauth = ca.getBasicAuth();
disableCertCheck = true;
// enableDebugging();
}
ClientAPI::~ClientAPI() {}
@ -44,22 +20,7 @@ ClientAPI::ReadyCheckState ClientAPI::getReadyCheckState() {
QJsonDocument doc = request("lol-matchmaking/v1/ready-check");
if (doc.isObject()) {
QJsonObject obj = doc.object();
Log::info << "ready check obj: " << doc.toJson().toStdString();
std::string searchState = getValue<std::string>(obj, "state", "Invalid");
std::string playerresponse = getValue<std::string>(obj, "playerResponse", "None");
ClientAPI::ReadyCheckState response = MAPENUM(playerresponse, ReadyCheckState, NONE);
if(response == ClientAPI::ReadyCheckState::NONE) {
auto sstate = MAPENUM(searchState, ReadyCheckState, INVALID);
if(sstate == ClientAPI::ReadyCheckState::INPROGRESS) {
return sstate;
}
}
return response;
return toReadyCheckState(doc.object());
}
return ClientAPI::ReadyCheckState::NONE;
@ -73,7 +34,7 @@ ClientAPI::GameflowPhase ClientAPI::getGameflowPhase() {
if (data.size() > 2) {
datastr = datastr.substr(1, datastr.size() -2);
return MAPENUM(datastr, GameflowPhase, NONE);
return toGameflowPhase(datastr);
}
return ClientAPI::GameflowPhase::NONE;
@ -87,10 +48,30 @@ void ClientAPI::declineMatch() {
request("lol-matchmaking/v1/ready-check/decline", Method::POST);
}
void ClientAPI::getChampSelectSession() {
ClientAPI::ChampSelectSession ClientAPI::getChampSelectSession() {
QJsonDocument doc = request("lol-champ-select/v1/session");
Log::info << "lol-champ-select session: " << doc.toJson().toStdString();
if(doc.isObject()) {
return (ChampSelectSession) doc.object();
}
return {};
}
bool ClientAPI::setChampSelectAction(int32_t actionid, int32_t actorcellid, int32_t champid, bool completed, bool pick) {
QJsonObject requestj;
requestj["championId"] = champid;
requestj["actorCellId"] = actorcellid;
requestj["completed"] = completed;
requestj["id"] = actionid;
requestj["isAllyAction"] = true;
requestj["type"] = pick ? "pick" : "ban";
QJsonDocument requestdoc(requestj);
const std::string requeststr = requestdoc.toJson(QJsonDocument::JsonFormat::Compact).toStdString();
Log::debug << "requeststr: " << std::quoted(requeststr);
QJsonDocument doc = request("lol-champ-select/v1/session/actions/" + std::to_string(actionid), Method::PATCH, requeststr);
Log::info << "patching action: " << actionid << " result: " << doc.toJson().toStdString();
return true;
}
ClientAPI::PlayerInfo ClientAPI::getSelf() {
@ -116,6 +97,25 @@ ClientAPI::PlayerInfo ClientAPI::getSelf() {
return {};
}
std::vector<std::string> ClientAPI::getLog() {
std::vector<std::string> out;
QJsonDocument doc = request("LoggingGetEntries");
if(doc.isArray()) {
QJsonArray arr = doc.array();
for(auto it : arr) {
std::string message;
if(it.isObject()) {
QJsonObject logobj = it.toObject();
message = getValue<std::string>(logobj, "severity");
message += getValue<std::string>(logobj, "message");
}
out.push_back(message);
}
}
return out;
}
static std::vector<int32_t> fromArrayToVector(const QJsonArray& arr) {
std::vector<int32_t> out;
out.reserve(arr.size());
@ -151,28 +151,7 @@ ClientAPI::TimerInfo ClientAPI::getTimerInfo() {
if (!doc.isObject()) return {};
TimerInfo ti;
QJsonObject obj = doc.object();
ti.adjustedTimeLeftInPhase = getValue<int64_t>(obj, "adjustedTimeLeftInPhase", 0);
ti.internalNowInEpochMs = getValue<int64_t>(obj, "internalNowInEpochMs", 0);
ti.isefinite = getValue<bool>(obj, "isefinite", 0);
ti.phase = MAPENUM(getValue<std::string>(obj, "phase", "Invalid"), ChampSelectPhase, INVALID);
ti.totalTimeInPhase = getValue<int64_t>(obj, "totalTimeInPhase", 0);
return ti;
return (TimerInfo) obj;
}
#define PRINTENUM(ENUMNAME) \
std::ostream& operator<<(std::ostream& str, const ClientAPI:: ENUMNAME & state) { \
assert(((int) state) < ENUMNAME ## NamesCount); \
return str << ENUMNAME ## Names[(int) state] << " (" << (int) state << ')'; \
}
PRINTENUM(ReadyCheckState)
PRINTENUM(GameflowPhase)
PRINTENUM(ChampSelectPhase)
PRINTENUM(Position)

181
src/clientapi_json.cpp Normal file
View File

@ -0,0 +1,181 @@
#include "clientapi.h"
#include <iomanip>
#include <QJsonArray>
#include <QJsonObject>
#include <Log.h>
#include "json.h"
// calculate the size of a constant sized array
#define ARRSIZE(ARRNAME) (sizeof(ARRNAME) / sizeof(ARRNAME[0]))
#define ARR(NAME, ...) static const std::string NAME ## Names[] = {__VA_ARGS__}; \
static const uint32_t NAME ## NamesCount = ARRSIZE(NAME ## Names);
ARR(ReadyCheckState, "Invalid", "None", "InProgress", "Accepted", "Declined");
ARR(GameflowPhase, "None", "Lobby", "Matchmaking", "CheckedIntoTournament", "ReadyCheck", "ChampSelect", "GameStart", "FailedToLaunch", "InProgress", "Reconnect", "WaitingForStats", "PreEndOfGame", "EndOfGame", "TerminatedInError");
ARR(ChampSelectPhase, "Invalid", "PLANNING", "BAN_PICK", "FINALIZATION");
ARR(Position, "Invalid", "top", "middle", "bottom", "jungle", "utility");
ARR(ChampSelectActionType, "Invalid", "ban", "pick", "ten_bans_reveal");
template<typename T>
static T mapEnum(const std::string& input, const std::string* names, uint32_t count, T defaul) {
if(input.empty()) return defaul;
for (uint32_t i = 0; i < count; ++i) {
if (names[i] == input) {
return (T) i;
}
}
Log::warn << "no mapping of enum-string: " << std::quoted(input);
return defaul;
}
#define MAPENUM(VAR, ENUM, DEFAULT) \
mapEnum(VAR, ENUM ## Names, ENUM ## NamesCount, ClientAPI:: ENUM :: DEFAULT)
ClientAPI::ReadyCheckState ClientAPI::toReadyCheckState(const QJsonObject& obj) {
std::string searchState = getValue<std::string>(obj, "state", "Invalid");
std::string playerresponse = getValue<std::string>(obj, "playerResponse", "None");
ClientAPI::ReadyCheckState response = MAPENUM(playerresponse, ReadyCheckState, NONE);
if(response == ClientAPI::ReadyCheckState::NONE) {
auto sstate = MAPENUM(searchState, ReadyCheckState, INVALID);
if(sstate == ClientAPI::ReadyCheckState::INPROGRESS) {
return sstate;
}
}
return response;
}
ClientAPI::GameflowPhase ClientAPI::toGameflowPhase(const std::string& str) {
return MAPENUM(str, GameflowPhase, NONE);
}
ClientAPI::ChampSelectPhase ClientAPI::toChampSelectPhase(const std::string& str) {
return MAPENUM(str, ChampSelectPhase, INVALID);
}
ClientAPI::Position ClientAPI::toPosition(const std::string& str) {
return MAPENUM(str, Position, INVALID);
}
ClientAPI::ChampSelectActionType ClientAPI::toChampSelectActionType(const std::string& str) {
return MAPENUM(str, ChampSelectActionType, INVALID);
}
ClientAPI::TimerInfo::TimerInfo() {}
ClientAPI::TimerInfo::TimerInfo(const QJsonObject& obj) {
adjustedTimeLeftInPhase = getValue<int64_t>(obj, "adjustedTimeLeftInPhase", 0);
internalNowInEpochMs = getValue<int64_t>(obj, "internalNowInEpochMs", 0);
isefinite = getValue<bool>(obj, "isefinite", 0);
phase = MAPENUM(getValue<std::string>(obj, "phase", "Invalid"), ChampSelectPhase, INVALID);
totalTimeInPhase = getValue<int64_t>(obj, "totalTimeInPhase", 0);
}
ClientAPI::ChampSelectAction::ChampSelectAction() {}
ClientAPI::ChampSelectAction::ChampSelectAction(const QJsonObject& json) {
actorCellID = getValue<int32_t>(json, "actorCellId");
championID = getValue<int32_t>(json, "championId");
completed = getValue<bool>(json, "completed");
id = getValue<int32_t>(json, "id");
isAllyAction = getValue<bool>(json, "isAllyAction");
isInProgress = getValue<bool>(json, "isInProgress");
const std::string typestr = getValue<std::string>(json, "type", "Invalid");
type = toChampSelectActionType(typestr);
}
ClientAPI::ChampSelectCell::ChampSelectCell() {}
ClientAPI::ChampSelectCell::ChampSelectCell(const QJsonObject& json) {
position = toPosition(getValue<std::string>(json, "assignedPosition"));
cellID = getValue<int32_t>(json, "cellId");
championID = getValue<int32_t>(json, "championId");
championPickIntentID = getValue<int32_t>(json, "championPickIntent");
summonerID = getValue<int32_t>(json, "summonerId");
}
std::vector<ClientAPI::ChampSelectCell> ClientAPI::loadAllInfos(const QJsonArray& arr) {
std::vector<ClientAPI::ChampSelectCell> out;
out.reserve(arr.size()); // should usualy be 5
for(auto it = arr.constBegin(); it != arr.constEnd(); ++it) {
if(it->isObject()) {
out.push_back((ChampSelectCell) it->toObject());
}
}
return out;
}
ClientAPI::ChampSelectSession::ChampSelectSession() {}
ClientAPI::ChampSelectSession::ChampSelectSession(const QJsonObject& json) {
// loading actions
auto actionsref = json["actions"];
// its an array of array id ChampSelectAction
if(actionsref.isArray()) {
QJsonArray jactions = actionsref.toArray();
for(auto jact = jactions.constBegin(); jact != jactions.constEnd(); ++jact) {
if(!jact->isArray()) continue;
QJsonArray jactarr = jact->toArray();
for(auto it = jactarr.constBegin(); it != jactarr.constEnd(); ++it) {
if(it->isObject()) {
actions.push_back((ChampSelectAction) it->toObject());
}
}
}
}
counter = getValue<int32_t>(json, "counter");
gameid = getValue<int64_t>(json, "gameId");
allowDuplicatePick = getValue(json, "allowDuplicatePicks", false);
allowRerolling = getValue(json, "allowRerolling", false);
allowSkinSelection = getValue(json, "allowSkinSelection", true);
hasSimultaneousBans = getValue(json, "hasSimultaneousBans", false);
hasSimultaneousPicks = getValue(json, "hasSimultaneousPicks", true);
isCustomGame = getValue(json, "isCustomGame", false);
isSpectating = getValue(json, "isSpectating", false);
skipChampionSelect = getValue(json, "skipChampionSelect", false);
rerollsRemaining = getValue(json, "rerollsRemaining", 0);
localPlayerCellId = getValue(json, "localPlayerCellId", -1);
auto timerref = json["timer"];
if(timerref.isObject()) {
timer = (TimerInfo) timerref.toObject();
}
// load cellinfo
auto myteamref = json["myTeam"];
if(myteamref.isArray()) {
myTeam = loadAllInfos(myteamref.toArray());
}
auto theirteamref = json["theirTeam"];
if(theirteamref.isArray()) {
theirTeam = loadAllInfos(theirteamref.toArray());
}
}
ClientAPI::ChampSelectSession::operator bool() {
return gameid != 0;
}
#define PRINTENUM(ENUMNAME) \
std::ostream& operator<<(std::ostream& str, const ClientAPI:: ENUMNAME & state) { \
assert(((int) state) < ENUMNAME ## NamesCount); \
return str << ENUMNAME ## Names[(int) state] << " (" << (int) state << ')'; \
}
PRINTENUM(ReadyCheckState)
PRINTENUM(GameflowPhase)
PRINTENUM(ChampSelectPhase)
PRINTENUM(Position)
PRINTENUM(ChampSelectActionType)

View File

@ -25,13 +25,14 @@ LolAutoAccept::~LolAutoAccept() {
}
}
void LolAutoAccept::setChamp(const std::string& champ, State s) {
void LolAutoAccept::setChamp(uint32_t champ, State s) {
if(s == State::LOBBY || s >= State::GAME) {
Log::warn << "setChamp() called on invalid State";
return;
}
stages.at((int) s)->champ = champ;
stages.at((int) s)->champid = champ;
Log::info << "set state: " << (int) s << " to " << champ;
}
void LolAutoAccept::setEnabled(bool b, State s) {
@ -99,7 +100,55 @@ void LolAutoAccept::innerRun() {
}
}
} else if(phase == ClientAPI::GameflowPhase::CHAMPSELECT) {
clientapi->getChampSelectSession();
auto session = clientapi->getChampSelectSession();
int32_t cellid = session.localPlayerCellId;
// find own cellid info
const ClientAPI::ChampSelectCell* me = nullptr;
for(const auto& it : session.myTeam) {
if(it.cellID == cellid) {
me = &it;
break;
}
}
ClientAPI::Position pos = me ? me->position : ClientAPI::Position::INVALID;
Log::info << "cellid: " << cellid << " position: " << pos << " counter: " << session.counter << " timer: timeleftip: " << session.timer.adjustedTimeLeftInPhase << " totaltimephase: " << session.timer.totalTimeInPhase;
// find actions for own cell
std::vector<const ClientAPI::ChampSelectAction*> ownactions;
for(const auto& it : session.actions) {
if(it.actorCellID == cellid) {
Log::debug << "ownaction: champid: " << it.championID << " completed: " << it.completed << " type: " << it.type;
ownactions.push_back(&it);
}
}
Log::note << "ownactions: " << ownactions.size();
// try to prepick champ
Log::info << "champselectphase: " << session.timer.phase;
if(session.timer.phase == ClientAPI::ChampSelectPhase::PLANNING || session.timer.phase == ClientAPI::ChampSelectPhase::BAN_PICK) {
Log::info << "prepick phase";
for(auto it : ownactions) {
if(it->type == ClientAPI::ChampSelectActionType::PICK) {
Log::info << "pick action anvailable: " << it->id << "champid: " << it->championID;
if(it->championID == 0) {
// try to prepick a champion
uint32_t champid = stages.at((int) State::PREPICK)->champid;
Log::info << "try to prepick champ: " << champid;
clientapi->setChampSelectAction(it->id, cellid, champid, false, true);
break;
}
}
}
} else if(session.timer.phase == ClientAPI::ChampSelectPhase::BAN_PICK) {
// ban and pick phase
Log::info << "ban-pick phase";
} else if(session.timer.phase == ClientAPI::ChampSelectPhase::FINALIZATION) {
// trade?
Log::info << "trade phase";
}
}
auto end = std::chrono::high_resolution_clock::now();
@ -107,6 +156,13 @@ void LolAutoAccept::innerRun() {
//if(dur.count() > 1e-5)
Log::info << "iteration took: " << (dur.count() * 1000) << " ms";
/*
auto logs = clientapi->getLog();
for(const auto& l : logs) {
Log::note << "APILog: " << l;
}
*/
std::this_thread::sleep_for(std::chrono::milliseconds(1200));
}
}

View File

@ -69,7 +69,7 @@ void MainWindow::pptoggled(bool state) {
void MainWindow::ppedited(const QString& b) {
Log::info << "prepick edited: " << b.toStdString();
lolaa.setChamp(b.toStdString(), LolAutoAccept::State::PREPICK);
lolaa.setChamp(dd.getBestMatchingChamp(b.toStdString()).key, LolAutoAccept::State::PREPICK);
conf.getConfig().prepick.champ = b.toStdString();
}
@ -81,7 +81,7 @@ void MainWindow::bantoggled(bool state) {
void MainWindow::banedited(const QString& b) {
Log::info << "ban edited: " << b.toStdString();
lolaa.setChamp(b.toStdString(), LolAutoAccept::State::BAN);
lolaa.setChamp(dd.getBestMatchingChamp(b.toStdString()).key, LolAutoAccept::State::BAN);
conf.getConfig().ban.champ = b.toStdString();
}
@ -93,6 +93,6 @@ void MainWindow::picktoggled(bool state) {
void MainWindow::pickedited(const QString& b) {
Log::info << "pick edited: " << b.toStdString();
lolaa.setChamp(b.toStdString(), LolAutoAccept::State::PICK);
lolaa.setChamp(dd.getBestMatchingChamp(b.toStdString()).key, LolAutoAccept::State::PICK);
conf.getConfig().pick.champ = b.toStdString();
}

View File

@ -1,9 +1,10 @@
#include "restclient.h"
#include <iomanip>
#include <Log.h>
static size_t arrayWriteCallback(char* contents, size_t size, size_t nmemb, void* userdata) {
if(userdata) {
if (userdata) {
QByteArray* arr = (QByteArray*) userdata;
arr->append(contents, size * nmemb);
return size * nmemb;
@ -11,6 +12,56 @@ static size_t arrayWriteCallback(char* contents, size_t size, size_t nmemb, void
return 0;
}
static void dump(const char* text, FILE* stream, unsigned char* ptr, size_t size) {
const unsigned int width = 0x40;
fprintf(stream, "%s, %10.10lu bytes (0x%8.8lx)\n", text, (unsigned long) size, (unsigned long) size);
for (size_t i = 0; i < size; i += width) {
fprintf(stream, "%4.4lx: ", (unsigned long) i);
for (size_t c = 0; (c < width) && (i + c < size); c++) {
/* check for 0D0A; if found, skip past and start a new line of output */
fprintf(stream, "%c", (ptr[i + c] >= 0x20) && (ptr[i + c] < 0x80) ? ptr[i + c] : '.');
}
fputc('\n', stream); /* newline */
}
fflush(stream);
}
static int my_trace(CURL* handle, curl_infotype type, char* data, size_t size, void* userp) {
const char* text;
(void) handle; /* prevent compiler warning */
switch (type) {
case CURLINFO_TEXT:
fprintf(stderr, "== Info: %s", data);
/* FALLTHROUGH */
default: /* in case a new one is introduced to shock us */
return 0;
case CURLINFO_HEADER_OUT:
text = "=> Send header";
break;
case CURLINFO_DATA_OUT:
text = "=> Send data";
break;
case CURLINFO_HEADER_IN:
text = "<= Recv header";
break;
case CURLINFO_DATA_IN:
text = "<= Recv data";
break;
case CURLINFO_SSL_DATA_OUT:
case CURLINFO_SSL_DATA_IN:
return 0;
}
dump(text, stderr, (unsigned char*) data, size);
return 0;
}
RestClient::RestClient(const std::string& base) : baseurl(base) {
curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
@ -22,8 +73,8 @@ RestClient::~RestClient() {
curl = nullptr;
}
QByteArray RestClient::requestRaw(const std::string& url, Method m) {
if(!curl) return {};
QByteArray RestClient::requestRaw(const std::string& url, Method m, const std::string& data) {
if (!curl) return {};
std::string requrl = baseurl + url;
QByteArray ba; //buffer
@ -36,32 +87,56 @@ QByteArray RestClient::requestRaw(const std::string& url, Method m) {
curl_easy_setopt(curl, CURLOPT_USERPWD, basicauth.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
if(disableCertCheck) {
if (disableCertCheck) {
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
}
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, NULL);
// curl header
struct curl_slist* headerlist = NULL;
headerlist = curl_slist_append(headerlist, "Accept: application/json");
switch (m) {
default:
case Method::GET:
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); break;
case Method::POST:
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "");
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());
headerlist = curl_slist_append(headerlist, "Content-Type: application/json");
break;
case Method::PUT:
curl_easy_setopt(curl, CURLOPT_PUT, 1L); break;
curl_easy_setopt(curl, CURLOPT_PUT, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());
headerlist = curl_slist_append(headerlist, "Content-Type: application/json");
break;
case Method::PATCH:
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH");
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());
headerlist = curl_slist_append(headerlist, "Content-Type: application/json");
break;
case Method::DELETE:
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); break;
}
// Check for errors
if (headerlist) {
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
}
CURLcode res = curl_easy_perform(curl);
if (headerlist) {
curl_slist_free_all(headerlist);
}
// Check for errors
if (res != CURLE_OK) {
if(res == CURLE_HTTP_RETURNED_ERROR) {
if (res == CURLE_HTTP_RETURNED_ERROR) {
long responsecode = -1;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responsecode);
Log::warn << "API request failed: " << baseurl << " " << url << " -> " << responsecode ;
} else {
Log::warn << "API request failed: " << baseurl << " " << url << " -> " << responsecode;
}
else {
Log::warn << "API request failed: " << baseurl << " " << url << " " << curl_easy_strerror(res);
}
return {};
@ -69,16 +144,27 @@ QByteArray RestClient::requestRaw(const std::string& url, Method m) {
return ba;
}
QJsonDocument RestClient::request(const std::string& url, Method m) {
QByteArray arr = requestRaw(url, m);
if(arr.isEmpty()) return {};
QJsonDocument RestClient::request(const std::string& url, Method m, const std::string& data) {
QByteArray arr = requestRaw(url, m, data);
if (arr.isEmpty()) return {};
QJsonParseError err;
QJsonDocument parsed = QJsonDocument::fromJson(arr, &err);
if(parsed.isNull() || err.error != QJsonParseError::NoError) {
if (parsed.isNull() || err.error != QJsonParseError::NoError) {
Log::error << "API Json parse error " << err.errorString().toStdString() << " offset: " << err.offset;
return {};
}
return parsed;
}
}
void RestClient::enableDebugging(bool enabled) {
// enable debugging
if(enabled) {
curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, my_trace);
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
} else {
curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, NULL);
curl_easy_setopt(curl, CURLOPT_VERBOSE, 0L);
}
}