Skip to content

Commit 0b1926c

Browse files
committed
omg discord linking
1 parent 8acd1ae commit 0b1926c

2 files changed

Lines changed: 166 additions & 11 deletions

File tree

src/ui/MenuExtras.hpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,37 +31,55 @@ namespace horrible {
3131
static MenuSuggest* get() noexcept;
3232
};
3333

34+
struct DiscordLink final {
35+
std::string id;
36+
std::string username;
37+
std::string avatar;
38+
};
39+
3440
class AuthState final : public base::Singleton<AuthState> {
3541
private:
3642
bool m_authorized = false;
43+
bool m_discordLinked = false;
3744

3845
int m_accountID;
3946
int m_userID;
4047
std::string m_username;
4148
std::string m_token;
4249

50+
DiscordLink m_discord;
51+
4352
protected:
4453
void setAuthInfo(int accountID, int userID, std::string username, std::string token);
4554

4655
public:
4756
void startAuth(geode::CopyableFunction<void(Result<>)>&& callback);
4857

58+
void setDiscordLinkInfo(DiscordLink discord);
59+
4960
bool isAuthorized() const noexcept;
5061
bool isAuthValid() const;
5162

5263
int getAccountID() const noexcept;
5364
int getUserID() const noexcept;
5465
geode::ZStringView getUsername() const noexcept;
5566
geode::ZStringView getToken() const noexcept;
67+
68+
geode::Result<DiscordLink> getDiscord() const;
5669
};
5770

5871
class MenuDiscord final : public geode::Popup {
5972
private:
6073
static MenuDiscord* s_inst;
6174

75+
std::string m_state;
76+
TaskHolder<web::WebResponse> m_listener;
77+
6278
protected:
6379
void onExit() override;
6480

81+
void checkDiscordStatus(float);
82+
6583
bool init(geode::ZStringView theme);
6684

6785
public:
@@ -84,4 +102,10 @@ namespace horrible {
84102

85103
static MenuKofi* get() noexcept;
86104
};
105+
};
106+
107+
template <>
108+
struct horrible::json::Serialize<horrible::DiscordLink> final {
109+
static geode::Result<horrible::DiscordLink> fromJson(horrible::json::Value const& value);
110+
static horrible::json::Value toJson(horrible::DiscordLink const& value);
87111
};

src/ui/src/MenuExtras.cpp

Lines changed: 142 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ using namespace geode::prelude;
1010
using namespace horrible::prelude;
1111

1212
$on_game(Loaded) {
13-
if (auto as = AuthState::get()) as->startAuth([](Result<> res) {if (res.isErr()) log::error("Argon authorization failed: {}", std::move(res).unwrapErr()); });
13+
if (auto as = AuthState::get()) as->startAuth([](Result<> res) { if (res.isErr()) log::error("Argon authorization failed: {}", std::move(res).unwrapErr()); });
1414
};
1515

1616
static constexpr auto g_suggestWait = 60;
@@ -19,6 +19,26 @@ MenuSuggest* MenuSuggest::s_inst = nullptr;
1919

2020
asp::Instant MenuSuggest::s_lastSuggest = asp::Instant();
2121

22+
Result<DiscordLink> json::Serialize<DiscordLink>::fromJson(json::Value const& value) {
23+
if (!value.isObject()) return Err("Expected an object");
24+
25+
GEODE_UNWRAP_INTO(std::string id, value["id"].asString());
26+
GEODE_UNWRAP_INTO(std::string username, value["username"].asString());
27+
GEODE_UNWRAP_INTO(std::string avatar, value["avatar"].asString());
28+
29+
return Ok(DiscordLink{std::move(id), std::move(username), std::move(avatar)});
30+
};
31+
32+
json::Value json::Serialize<DiscordLink>::toJson(DiscordLink const& value) {
33+
auto obj = json::Value();
34+
35+
obj["id"] = value.id;
36+
obj["username"] = value.username;
37+
obj["avatar"] = value.avatar;
38+
39+
return obj;
40+
};
41+
2242
bool MenuSuggest::init(ZStringView theme) {
2343
auto btns = themes::getCircleBaseColor(theme);
2444

@@ -175,7 +195,7 @@ void MenuSuggest::processSuggestion(Button* sender) {
175195
if (auto s = self.lock()) toggleBack(s);
176196
};
177197

178-
if (!res.ok()) return fallback(fmt::format("Request failed ({}: {})", res.code(), res.errorMessage()));
198+
if (res.error()) return fallback(fmt::format("Request failed ({}: {})", res.code(), res.errorMessage()));
179199

180200
s_lastSuggest = asp::Instant::now();
181201

@@ -221,6 +241,12 @@ void AuthState::setAuthInfo(int accountID, int userID, std::string username, std
221241
m_authorized = !m_token.empty();
222242
};
223243

244+
void AuthState::setDiscordLinkInfo(DiscordLink discord) {
245+
m_discord = std::move(discord);
246+
247+
m_discordLinked = !m_discord.id.empty();
248+
};
249+
224250
bool AuthState::isAuthorized() const noexcept {
225251
return m_authorized;
226252
};
@@ -245,23 +271,56 @@ ZStringView AuthState::getToken() const noexcept {
245271
return m_token;
246272
};
247273

274+
Result<DiscordLink> AuthState::getDiscord() const {
275+
if (!m_discordLinked) return Err("Discord account not linked");
276+
return Ok(m_discord);
277+
};
278+
248279
void AuthState::startAuth(CopyableFunction<void(Result<>)>&& callback) {
249280
if (!argon::signedIn()) return callback(Err("Player is logged out"));
250281

251-
if (auto as = AuthState::get()) {
252-
if (as->isAuthValid()) return callback(Ok());
282+
if (isAuthValid()) return callback(Ok());
283+
284+
async::spawn(
285+
argon::startAuth(),
286+
[this, cb = std::move(callback)](Result<std::string> res) {
287+
if (res.isErr()) return cb(res.asErr());
288+
289+
auto const acc = argon::getGameAccountData();
290+
291+
setAuthInfo(acc.accountId, acc.userId, acc.username, std::move(res).unwrap());
292+
log::info("Authorized {} ({}) with Argon", acc.username, acc.accountId);
293+
294+
return cb(Ok());
295+
});
296+
297+
if (auto gjam = GJAccountManager::sharedState()) {
298+
auto req = web::WebRequest()
299+
.param("id", gjam->m_accountID);
253300

254301
async::spawn(
255-
argon::startAuth(),
256-
[cb = std::move(callback), as](Result<std::string> res) {
257-
if (res.isErr()) return cb(res.asErr());
302+
req.get("https://api.cubicstudios.xyz/breakeode/v1/discord"),
303+
[this](web::WebResponse res) {
304+
auto const fallback = [](std::string_view err = "") {
305+
log::error("Discord link web request failed ({})", err);
306+
};
307+
308+
if (res.error()) return fallback(res.errorMessage());
309+
310+
auto jsonRes = res.json();
311+
if (jsonRes.isErr()) return fallback(std::move(jsonRes).unwrapErr());
312+
313+
auto json = std::move(jsonRes).unwrap();
314+
315+
auto discordRes = json.as<DiscordLink>();
316+
if (discordRes.isErr()) return fallback(std::move(discordRes).unwrapErr());
258317

259-
auto const acc = argon::getGameAccountData();
318+
setDiscordLinkInfo(std::move(discordRes).unwrap());
260319

261-
as->setAuthInfo(acc.accountId, acc.userId, acc.username, std::move(res).unwrap());
262-
log::info("Authorized {} ({}) with Argon", acc.username, acc.accountId);
320+
auto discordLinkRes = getDiscord();
321+
if (discordLinkRes.isErr()) return fallback(std::move(discordLinkRes).unwrapErr());
263322

264-
return cb(Ok());
323+
log::info("Authorized as Discord user {}", std::move(discordLinkRes).unwrap().username);
265324
});
266325
};
267326
};
@@ -279,6 +338,24 @@ bool MenuDiscord::init(ZStringView theme) {
279338

280339
popup::closeBtnID(m_closeBtn);
281340

341+
if (auto as = AuthState::get()) {
342+
auto discordRes = as->getDiscord();
343+
if (discordRes.isOk()) { // testing, will replace with actual ui later..!
344+
auto const discord = std::move(discordRes).unwrap();
345+
346+
auto label = Label::createRich(fmt::format("Authorized as <cg>@{}</c>", discord.username), "chatFont.fnt");
347+
348+
m_mainLayer->addChildAtPosition(label, Anchor::Center);
349+
} else {
350+
log::error("{}", std::move(discordRes).unwrapErr());
351+
352+
m_state = utils::random::generateUUID();
353+
web::openLinkInBrowser(fmt::format("https://api.cubicstudios.xyz/breakeode/v1/discord/link/auth?state={}", m_state));
354+
355+
scheduleOnce(schedule_selector(MenuDiscord::checkDiscordStatus), 1.25f);
356+
};
357+
};
358+
282359
auto infoBtn = Button::createWithSpriteFrameName(
283360
"GJ_infoIcon_001.png",
284361
[](auto) {
@@ -298,8 +375,62 @@ bool MenuDiscord::init(ZStringView theme) {
298375
return true;
299376
};
300377

378+
void MenuDiscord::checkDiscordStatus(float) {
379+
if (auto as = AuthState::get()) {
380+
if (!as->isAuthValid()) return unschedule(schedule_selector(MenuDiscord::checkDiscordStatus));
381+
382+
auto reqJson = json::Value();
383+
reqJson["account_id"] = as->getAccountID();
384+
reqJson["user_id"] = as->getUserID();
385+
reqJson["username"] = as->getUsername();
386+
reqJson["authtoken"] = as->getToken();
387+
reqJson["state"] = m_state;
388+
389+
auto req = web::WebRequest()
390+
.bodyJSON(reqJson);
391+
392+
log::debug("Checking endpoint for Discord link status...");
393+
394+
m_listener.spawn(
395+
req.post("https://api.cubicstudios.xyz/breakeode/v1/discord/link/check"),
396+
[self = WeakRef(this)](web::WebResponse res) {
397+
if (auto s = self.lock()) {
398+
auto const fallback = [&s](std::string_view err = "") {
399+
log::error("Discord link web request failed ({}), trying again in 1.25s", err);
400+
s->scheduleOnce(schedule_selector(MenuDiscord::checkDiscordStatus), 1.25f);
401+
};
402+
403+
if (res.error()) return fallback(res.errorMessage());
404+
405+
auto jsonRes = res.json();
406+
if (jsonRes.isErr()) return fallback(std::move(jsonRes).unwrapErr());
407+
408+
auto json = std::move(jsonRes).unwrap();
409+
410+
auto discordRes = json.as<DiscordLink>();
411+
if (discordRes.isErr()) return fallback(std::move(discordRes).unwrapErr());
412+
413+
auto discord = std::move(discordRes).unwrap();
414+
415+
log::info("Successfully authorized as {}", discord.username);
416+
if (auto as = AuthState::get()) as->setDiscordLinkInfo(std::move(discord));
417+
418+
s->unschedule(schedule_selector(MenuDiscord::checkDiscordStatus));
419+
s->m_listener.cancel();
420+
};
421+
});
422+
} else {
423+
unschedule(schedule_selector(MenuDiscord::checkDiscordStatus));
424+
};
425+
};
426+
301427
void MenuDiscord::onExit() {
302428
s_inst = nullptr;
429+
430+
if (m_listener.isPending()) log::trace("Cancelling Discord link tasks");
431+
unschedule(schedule_selector(MenuDiscord::checkDiscordStatus));
432+
m_listener.cancel();
433+
303434
Popup::onExit();
304435
};
305436

0 commit comments

Comments
 (0)