diff --git a/README.md b/README.md index f980961..6e81217 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,6 @@ +mod is developing now!11 +# Geode Mod Comments -# How To Install On Windows: -- download pc game by [this link](https://mega.nz/file/d88CgDhb#LWFs7ly3J87fe4coBPzSZtWsUnTGKSehOy5VPaZiQGU) -- unarchive it -Thats all. You can made shortcut and stuff... **Main game executable file is a __GemetryTrash.exe__** -# How To Install On Android: -- download my custom geode launcher version by [this link](https://mega.nz/file/5w0SRKbD#HkixtbyHiq2yosE7JI21iR7nuVRJmJ1eZd3mDMt-hM0) -- install apk package -- run installed launcher -- download main mod by [this link](https://github.com/user95401/GemetryTrash/releases/latest/download/user95401.gemetry_trash.geode) and install it manually. -Thats all. Now you can launch game idk. -



-### https://gt.ps.fhgdps.com -### https://discord.gg/UyQytJsrGZ +Add comments in mod popups. + +Using GitHub Rest API with OAuth. \ No newline at end of file diff --git a/about.md b/about.md index 7b767a8..6e81217 100644 --- a/about.md +++ b/about.md @@ -1,5 +1,6 @@ +mod is developing now!11 # Geode Mod Comments -Add comments in mod popups... +Add comments in mod popups. -GitHub Rest API with OAuth. idka +Using GitHub Rest API with OAuth. \ No newline at end of file diff --git a/changelog.md b/changelog.md deleted file mode 100644 index a45c96e..0000000 --- a/changelog.md +++ /dev/null @@ -1 +0,0 @@ -pohui \ No newline at end of file diff --git a/mod.json b/mod.json index 2bd8c4d..89d3938 100644 --- a/mod.json +++ b/mod.json @@ -8,7 +8,7 @@ }, "id": "user95401.geode-mod-comments", "name": "Mod Comments", - "version": "v1.0.0", + "version": "v1.0.0-alpha", "developer": "user95401", "description": "add comments in mod popups", "early-load": true, diff --git a/resources/sprites/SquareShadow_001.png b/resources/sprites/SquareShadow_001.png new file mode 100644 index 0000000..2a13430 Binary files /dev/null and b/resources/sprites/SquareShadow_001.png differ diff --git a/src/main.cpp b/src/main.cpp index 57ef42e..e0485a0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,13 +4,350 @@ using namespace geode::prelude; +#define SETTING(type, key_name) Mod::get()->getSettingValue(key_name) + +#define public_cast(value, member) \ +[](auto* v) { \ + class FriendClass__; \ + using T = std::remove_pointer::type; \ + class FriendeeClass__: public T { \ + protected: \ + friend FriendClass__; \ + }; \ + class FriendClass__ { \ + public: \ + auto& get(FriendeeClass__* v) { return v->member; } \ + } c; \ + return c.get(reinterpret_cast(v)); \ +}(value) + namespace github { - auto mod_api_repo_url = std::string( - "https://api.github.com/repos/user95401/Ryzen-Mods/issues" + inline auto api_repo_url = std::string( + "https://api.github.com/repos/user95401/Geode-Mod-Comments" ); + inline std::string get_token() { + return Mod::get()->getSavedValue("gh_access_token"); + } + inline void set_token(std::string token) { + Mod::get()->setSavedValue("gh_access_token", token); + } + inline bool has_token() { + return (get_token().size() > 3); + } + inline const char* authorization_header_name() { + return has_token() ? "Authorization" : "AuthDisabled"; + } + inline auto get_basic_web_request() { + auto req = web::WebRequest(); + req.userAgent(Mod::get()->getID()); + req.header("X-GitHub-Api-Version", "2022-11-28"); + req.header(authorization_header_name(), fmt::format("Bearer {}", get_token())); + return req; + } } -auto last_issues = matjson::Value(); +inline auto issues = matjson::Value(); +inline std::map mod_issues; +inline std::map mod_comments; + +class IssueCommentItem : public CCMenuItem { +public: + matjson::Value m_json; + static auto create(CCNode* parent, matjson::Value json) { + auto rtn = new IssueCommentItem(); + rtn->m_json = json; + rtn->init(); + rtn->customSetup(parent); + return rtn; + } + void customSetup(CCNode* parent) { + this->setContentWidth(parent->getContentWidth()); + //row + if (auto row = CCMenu::create()) { + this->addChild(row); + row->setAnchorPoint(CCPointZero); + row->setLayout( + RowLayout::create() + ->setAxisAlignment(AxisAlignment::Start) + ->setCrossAxisLineAlignment(AxisAlignment::End) + ); + row->setContentWidth(parent->getContentWidth()); + //avatar + auto avatar_size = CCSize(30.f, 30.f); + { + //sprite + auto sprite = CCSprite::createWithSpriteFrameName("edit_eDamageSquare_001.png"); + sprite->setScale(avatar_size.width / sprite->getContentSize().width); + //item + auto avatar = CCMenuItemSpriteExtra::create(sprite, this, menu_selector(IssueCommentItem::onAvatar)); + avatar->m_animationEnabled = 0; + avatar->m_colorEnabled = 1; + avatar->setContentSize(avatar_size); + row->addChild(avatar); + // + auto filep = dirs::getTempDir() / ("." + m_json["user"]["login"].as_string()); + auto a = [this, sprite, filep, avatar](std::monostate const& asd) { + if (not sprite) return; + sprite->initWithFile(filep.string().c_str()); + sprite->setScale(avatar->getContentWidth() / sprite->getContentSize().width); + }; + auto req = web::WebRequest(); + auto listener = new EventListener; + listener->bind( + [this, a, filep](web::WebTask::Event* e) { + if (web::WebResponse* res = e->getValue()) { + std::string data = res->string().unwrapOr(""); + //call the some shit + if (res->code() < 399) { + res->into(filep); + a(std::monostate()); + } + } + } + ); + listener->setFilter(req.send("GET", m_json["user"]["avatar_url"].as_string())); + /* web::AsyncWebRequest().fetch(m_json["user"]["avatar_url"].as_string()) + .into(filep).then(a).expect(b);*/ + } + //text + if (auto text = CCNode::create()) { + row->addChild(text); + text->setLayout( + ColumnLayout::create() + ->setCrossAxisLineAlignment(AxisAlignment::Start) + ->setAxisReverse(true) + ->setGap(0.f) + ); + text->setContentWidth(parent->getContentWidth() - avatar_size.width); + //user + auto updated_at = m_json["updated_at"].as_string(); + updated_at = string::replace(updated_at, "T", " "); + updated_at = string::replace(updated_at, "Z", ""); + updated_at = string::replace(updated_at, "-", "."); + auto user = CCLabelTTF::create( + fmt::format( + "{} at {}", + m_json["user"]["login"].as_string(), + updated_at + ).c_str(), + "arial", 12.f + ); + user->setID("user"); + text->addChild(user); + //menu in end of user text + if (auto menu = CCMenu::create()) { + menu->setPosition(user->getContentSize()); + menu->setContentHeight(user->getContentHeight()); + menu->setAnchorPoint({ -0.01f, 1.f }); + menu->setLayout( + RowLayout::create() + ->setAxisAlignment(AxisAlignment::Start) + ->setAutoScale(false) + ->setCrossAxisOverflow(false) + ); + user->addChild(menu); + //delete_btn + auto edit_delBtn_001 = CCSprite::createWithSpriteFrameName("edit_delBtn_001.png"); + edit_delBtn_001->setScale(0.5f); + auto delete_btn = CCMenuItemSpriteExtra::create( + edit_delBtn_001, + this, + menu_selector(IssueCommentItem::deleteComment) + ); + menu->addChild(delete_btn); + //menu update + menu->updateLayout(); + } + //body + auto body = public_cast( + MDTextArea::create(m_json["body"].as_string(), { text->getContentWidth(), 10 }), + m_content + ); + body->setVisible(1); + auto body_container = CCNode::create(); + body_container->addChild(body); + body_container->setLayout(ColumnLayout::create()); + body_container->setContentHeight(body->getContentHeight()); + body_container->updateLayout(); + text->addChild(body_container); + //set col height + text->setContentHeight(user->getContentHeight() + body_container->getContentHeight()); + text->updateLayout(); + } + //upd + row->updateLayout(); + } + this->setLayout(RowLayout::create()); + } + //IssueCommentItem + void deleteComment(CCObject*) { + auto a = [this](std::string const& rtn) + { + if (auto reload = dynamic_cast(CCDirector::get()->m_pRunningScene->getChildByIDRecursive("reload"))) + reload->activate(); + //asd + if (this) { + auto parent = this->getParent(); + this->removeFromParentAndCleanup(false); + if (parent) parent->updateLayout(); + }; + }; + auto b = [this](std::string const& rtn) + { + auto message = rtn; + auto asd = geode::createQuickPopup( + "Request exception", + message, + "Nah", nullptr, 420.f, nullptr, false + ); + asd->show(); + }; + auto req = github::get_basic_web_request(); + auto listener = new EventListener; + listener->bind( + [this, a, b](web::WebTask::Event* e) { + if (web::WebResponse* res = e->getValue()) { + std::string data = res->string().unwrapOr(""); + //call the some shit + if (res->code() < 399) a(data); + else b(data); + } + } + ); + listener->setFilter(req.send("DELETE", m_json["url"].as_string())); + } + void onAvatar(CCObject*) { + if (not m_json.contains("user")) return; + if (not m_json["user"].contains("html_url")) return; + else web::openLinkInBrowser(m_json["user"]["html_url"].as_string()); + } + void onCreateReaction(CCObject*) { + } +}; + +class CommentsLayer : public CCLayer { +public: + static auto create(CCContentLayer* contentLayer, std::string modID) { + + auto scroll = typeinfo_cast(contentLayer->getParent()); + + float scroll_gap = 12.f; + + contentLayer->setAnchorPoint(CCPointZero); + contentLayer->setPositionX(0); + contentLayer->setLayout( + ColumnLayout::create() + ->setGap(scroll_gap) + ->setAxisReverse(true) + ->setAxisAlignment(AxisAlignment::End) + ); + + contentLayer->setContentHeight(0.f); + if (mod_comments[modID].is_array()) for (auto comment : mod_comments[modID].as_array()) { + auto item = IssueCommentItem::create(contentLayer, comment); + contentLayer->setContentHeight(//make content layer longer + contentLayer->getContentHeight() + + item->getContentHeight() + scroll_gap + ); + contentLayer->addChild(item); + } + //fix some shit goes when content smaller than scroll + if (contentLayer->getContentSize().height < scroll->getContentSize().height) { + contentLayer->setContentSize({ + contentLayer->getContentSize().width, + scroll->getContentSize().height + }); + } + contentLayer->updateLayout(); + + } +}; + +class LoadCommentsLayer : public CCLayer { +public: + EventListener m_webTaskListener; + static auto create(CCContentLayer* contentLayer, std::string modID) { + auto me = new LoadCommentsLayer(); + me->init(); + + me->setAnchorPoint({ 0.0f, 0.0f }); + me->setContentSize(contentLayer->getParent()->getContentSize()); + contentLayer->setContentSize(me->getContentSize()); + contentLayer->addChild(me); + + if (issues.is_array()) for (auto issue : issues.as_array()) { + auto title = issue.try_get("title").value_or("NO_TITLE"); + auto number = issue.try_get("number").value_or(1); + if (title == modID) { + mod_issues[modID] = issue; + break; + } + else if (number == 1) mod_issues[modID] = issue; + } + + if (mod_issues.contains(modID)) void(); + else { + auto label = CCLabelBMFont::create(fmt::format( + "Can't find any issue for {}!", + modID + ).data(), "chatFont.fnt" + ); + label->setAlignment(kCCTextAlignmentCenter); + label->setAnchorPoint({ 0.5f, 1.f }); + me->addChildAtPosition(label, Anchor::Top, { 0.f, -72.f }); + } + + auto bg = CCScale9Sprite::create( + "square02_small.png" + ); + bg->setOpacity(75); + bg->setContentSize(me->getContentSize()); + me->addChildAtPosition(bg, Anchor::Center); + + auto label = CCLabelBMFont::create(fmt::format( + "Loading comments for {}...", + modID + ).data(), "chatFont.fnt" + ); + label->setAlignment(kCCTextAlignmentCenter); + label->setAnchorPoint({ 0.5f, 1.f }); + me->addChildAtPosition(label, Anchor::Top, {0.f, -72.f}); + + auto circle = LoadingCircleSprite::create(); + circle->setScale(0.6f); + me->addChildAtPosition(circle, Anchor::Top, {0.f, -44.f}); + + me->m_webTaskListener.bind( + [contentLayer, modID, me, label](web::WebTask::Event* e) { + if (web::WebResponse* res = e->getValue()) { + auto json = res->json(); + auto string = res->string(); + if (json.has_value()) { + mod_comments[modID] = json.value(); + me->removeFromParent(); + CommentsLayer::create(contentLayer, modID); + } + else label->setString(fmt::format( + "{}\n{}", + label->getString(), + json.error_or("no err, str: " + string.value_or("no str")) + ).c_str()); + } + else if (e->isCancelled()) label->setString(fmt::format( + "{}\n\n\nThe request was cancelled...", + label->getString() + ).c_str()); + } + ); + + auto req = github::get_basic_web_request(); + me->m_webTaskListener.setFilter(req.get( + mod_issues[modID].try_get("comments_url").value_or("") + )); + + return me; + } +}; class LoadIssuesLayer : public CCLayer { public: @@ -44,12 +381,14 @@ class LoadIssuesLayer : public CCLayer { me->addChildAtPosition(circle, Anchor::Top, {0.f, -44.f}); me->m_webTaskListener.bind( - [me, label](web::WebTask::Event* e) { + [contentLayer, modID, me, label](web::WebTask::Event* e) { if (web::WebResponse* res = e->getValue()) { auto json = res->json(); auto string = res->string(); if (json.has_value()) { - last_issues = json.value(); + issues = json.value(); + me->removeFromParent(); + LoadCommentsLayer::create(contentLayer, modID); } else label->setString(fmt::format( "{}\n{}", @@ -64,8 +403,10 @@ class LoadIssuesLayer : public CCLayer { } ); - auto req = web::WebRequest(); - me->m_webTaskListener.setFilter(req.get("https://pastebin.com/raw/vNi1WHNF")); + auto req = github::get_basic_web_request(); + me->m_webTaskListener.setFilter(req.get( + github::api_repo_url + "/issues" + )); return me; } @@ -149,7 +490,48 @@ void hi() { tabspr->select(1); if (sender->getParent()->getTag() == myTabID) { - + auto pop = FLAlertLayer::create( + nullptr, + "Create Comment", + "\n \n \n \n \n ", + "Close", "Create", + 380.f + ); + //md_prev + auto md_prev = MDTextArea::create("# input and preview here...\nJust tap on me and type your text!\n\nAlso you can type `\\n` and it will be replaced with newline char automatically. And remember, in **Markdown** you write two newlines to start new paragraph!", CCSize(290.f, 112.f)); + md_prev->setID("md_prev"); + md_prev->setPositionY(86.f); + pop->m_buttonMenu->addChild(md_prev, 1); + //input + auto input = TextInput::create(md_prev->getContentWidth(), "", "chatFont.fnt"); + input->setID("input"); + input->hideBG(); + input->setContentHeight(md_prev->getContentHeight() * 2); + input->getInputNode()->setContentHeight(md_prev->getContentHeight() * 2); + input->getInputNode()->m_placeholderLabel->setOpacity(0); + input->getInputNode()->m_cursor->setOpacity(0); + input->getInputNode()->m_filterSwearWords = (0); + input->getInputNode()->m_allowedChars = ( + " !\"#$ % &'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" + ); + input->setCallback( + [input, md_prev](std::string p0) { + auto endl_filtered = string::replace(p0, "\\n", "\n"); + if (p0.find("\\n") != std::string::npos) { + input->setString(endl_filtered.data()); + } + //p0->setString(); + //log::debug("{}(text:{}, m_fontValue1={})", __FUNCTION__, p0->getString(), p0->m_fontValue2); + md_prev->setString(endl_filtered.data()); + md_prev->getScrollLayer()->scrollLayer(9999.f); + } + ); + input->setPosition(md_prev->getPosition() * 2); + pop->m_buttonMenu->addChild(input); + //last popup setup + handleTouchPriority(pop); + pop->setID("post_comment_popup"); + pop->show(); } else { auto textarea = rcontainer->getChildByIDRecursive("textarea"); diff --git a/support.md b/support.md deleted file mode 100644 index 6d48eff..0000000 --- a/support.md +++ /dev/null @@ -1 +0,0 @@ -Edit this file to change your mod's support info, or delete it if you don't need it.