From 252e3f3b0516d4d1769eca13f4ac5a05ea89057c Mon Sep 17 00:00:00 2001 From: GDColon Date: Sat, 21 Dec 2019 22:16:18 -0500 Subject: [PATCH] GDPS Compatibility (kinda) I've started work on making GDBrowser compatible with GD private servers :D Currently a few things (comments, leaderboards, etc) are broken but other than that I'm kind of getting there. This commit mostly just makes life easier for people who want to fork GDBrowser for their private server - The endpoint (e.g. boomlings.com) is now set in index.js - Added gdpsConfig.js, where you can tweak small settings in case your GDPS does stuff weirdly - Small tweaks to the code so the weird GDPS responses can be correctly interpreted - secretStuff.json is no longer mandatory If you're able to help me out with this project, PRs are appreciated :D - Removed Herobrine --- README.md | 23 ++++++++++++++++++++++- api/accurateLeaderboard.js | 2 +- api/analyze.js | 18 ++++++++++++------ api/comments.js | 6 +++--- api/download.js | 8 ++++---- api/icon.js | 4 ++-- api/leaderboard.js | 2 +- api/leaderboardLevel.js | 4 ++-- api/level.js | 2 +- api/like.js | 2 +- api/postComment.js | 2 +- api/postProfileComment.js | 2 +- api/profile.js | 4 ++-- api/search.js | 4 ++-- classes/Level.js | 10 ++++++---- html/profile.html | 3 ++- index.js | 18 ++++++++++++++---- misc/gdpsConfig.js | 27 +++++++++++++++++++++++++++ 18 files changed, 104 insertions(+), 37 deletions(-) create mode 100644 misc/gdpsConfig.js diff --git a/README.md b/README.md index 7d4d1da..f8f28e3 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,26 @@ Uh... so I've never actually used GitHub before this. But I'll try to explain ev Sorry for my messy code. It's why I was skeptical about making this open source, but you know what, the code runs fine in the end. -## The API folder +## Using this for a GDPS? +I mean, sure. Why not. + +Just make sure to give credit, obviously. Via the bottom of the homepage, the credits button, or maybe even both if you're feeling extra nice. + +Obviously, GDBrowser isn't perfect when it comes to GD private servers, since both requests and responses might be a bit different. Or a LOT, as I learned. + +You can tweak the endpoint (e.g. boomlings.com) in index.js + +You can also check out `/misc/gdpsConfig.js` to tweak some additional GDPS settings such as whether to decrypt level descriptions or if timestamps should end with "ago" + +GDPS compatibility is still a HUGE work in progress, so pull requests would be greatly appreciated if you manage to make any improvements! + +# Folders + +GDBrowser has a lot of folders. I like to keep things neat. + +Most folders contain exactly what you'd expect, but here's some in-depth info in case you're in the dark. + +## API This is where all the backend stuff happens! Yipee! They're all fairly similar. Fetch something from boomlings.com, parse the response, and serve it in a crisp and non-intimidating JSON. This is probably what you came for. @@ -70,6 +89,8 @@ colors.json - The colors for generating icons credits.json - Credits! (shown on the homepage) +gdpsConfig.js - Tweak small settings for GDPS'es here, such as whether to decrypt level descriptions or if timestamps should end with "ago" + level.json - An array of the official GD tracks, and also difficulty face stuff for level searching mapPacks.json - The IDs for the levels in map packs. I can't believe I have to hardcode this diff --git a/api/accurateLeaderboard.js b/api/accurateLeaderboard.js index c4063ce..ef3f0db 100644 --- a/api/accurateLeaderboard.js +++ b/api/accurateLeaderboard.js @@ -11,7 +11,7 @@ module.exports = async (app, req, res) => { idArray.forEach((x, y) => { - request.post('http://boomlings.com/database/getGJUserInfo20.php', { + request.post(app.endpoint + 'getGJUserInfo20.php', { form: {targetAccountID: x, secret: app.secret} }, function (err, resp, body) { if (err || !body || body == '-1') return res.send([]) diff --git a/api/analyze.js b/api/analyze.js index 7ae0356..5868630 100644 --- a/api/analyze.js +++ b/api/analyze.js @@ -7,15 +7,21 @@ const blocks = require('../misc/blocks.json') module.exports = async (app, req, res, level) => { -let levelString = Buffer.from(level.data, 'base64') -let buffer; +let unencrypted = level.data.startsWith('kS') // some gdps'es don't encrypt level data +let levelString = unencrypted ? level.data : Buffer.from(level.data, 'base64') let response = {}; +let rawData; -try { buffer = pako.inflate(levelString, {to:"string"}) } -catch(e) { return res.send("-1") } +if (unencrypted) rawData = level.data -let rawData = buffer.toString('utf8') -let data = rawData +else { + let buffer; + try { buffer = pako.inflate(levelString, {to:"string"}) } + catch(e) { return res.send("-1") } + rawData = buffer.toString('utf8') +} + +let data = rawData; // data is tweaked around a lot, so rawData is preserved let blockNames = Object.keys(blocks) let miscNames = Object.keys(ids.misc) diff --git a/api/comments.js b/api/comments.js index 9742749..d2d14fe 100644 --- a/api/comments.js +++ b/api/comments.js @@ -15,7 +15,7 @@ module.exports = async (app, req, res) => { if (req.query.type == "commentHistory") path = "getGJCommentHistory" else if (req.query.type == "profile") path = "getGJAccountComments20" - request.post(`http://boomlings.com/database/${path}.php`, { + request.post(`${app.endpoint}${path}.php`, { form : params}, async function(err, resp, body) { if (err || body == '-1' || !body) return res.send("-1") @@ -24,7 +24,7 @@ module.exports = async (app, req, res) => { comments = comments.map(x => x.split(':')) comments = comments.map(x => x.map(x => app.parseResponse(x, "~"))) if (req.query.type == "profile") comments.filter(x => x[0][2]) - else comments = comments.filter(x => x[1][1]) + else comments = comments.filter(x => x[1] && x[1][1]) if (!comments.length) return res.send("-1") let commentArray = [] @@ -40,7 +40,7 @@ module.exports = async (app, req, res) => { comment.content = app.clean(Buffer.from(x[2], 'base64').toString()); comment.ID = x[6] comment.likes = x[4] - comment.date = (x[9] || "?") + " ago" + comment.date = (x[9] || "?") + app.config.timestampSuffix if (comment.content.endsWith("⍟")) { comment.content = comment.content.slice(0, -1) comment.browserColor = true diff --git a/api/download.js b/api/download.js index e690294..a42f878 100644 --- a/api/download.js +++ b/api/download.js @@ -8,7 +8,7 @@ module.exports = async (app, req, res, api, ID, analyze) => { else if (levelID == "weekly") levelID = -2 else levelID = levelID.replace(/[^0-9]/g, "") - request.post('http://boomlings.com/database/downloadGJLevel22.php', { + request.post(app.endpoint + 'downloadGJLevel22.php', { form: { levelID, secret: app.secret @@ -24,11 +24,11 @@ module.exports = async (app, req, res, api, ID, analyze) => { let levelInfo = app.parseResponse(body) let level = new Level(levelInfo) - request.post('http://boomlings.com/database/getGJUsers20.php', { + request.post(app.endpoint + 'getGJUsers20.php', { form: { str: level.authorID, secret: app.secret } }, function (err1, res1, b1) { let gdSearchResult = app.parseResponse(b1) - request.post('http://boomlings.com/database/getGJUserInfo20.php', { + request.post(app.endpoint + 'getGJUserInfo20.php', { form: { targetAccountID: gdSearchResult[16], secret: app.secret } }, function (err2, res2, b2) { if (b2 != '-1') { @@ -42,7 +42,7 @@ module.exports = async (app, req, res, api, ID, analyze) => { level.accountID = "0" } - request.post('http://boomlings.com/database/getGJSongInfo.php', { + request.post(app.endpoint + 'getGJSongInfo.php', { form: { songID: level.customSong, secret: app.secret diff --git a/api/icon.js b/api/icon.js index 48b72f0..3914c7d 100644 --- a/api/icon.js +++ b/api/icon.js @@ -328,7 +328,7 @@ module.exports = async (app, req, res) => { if (req.query.hasOwnProperty("noUser") || req.query.hasOwnProperty("nouser") || username == "icon") return buildIcon() - request.post('http://boomlings.com/database/getGJUsers20.php', { + request.post(app.endpoint + 'getGJUsers20.php', { form: { str: username, secret: app.secret @@ -337,7 +337,7 @@ module.exports = async (app, req, res) => { if (err1 || !body1 || body1 == "-1") return buildIcon() else result = app.parseResponse(body1); - request.post('http://boomlings.com/database/getGJUserInfo20.php', { + request.post(app.endpoint + 'getGJUserInfo20.php', { form: { targetAccountID: result[16], secret: app.secret diff --git a/api/leaderboard.js b/api/leaderboard.js index 995ff69..b8d72db 100644 --- a/api/leaderboard.js +++ b/api/leaderboard.js @@ -15,7 +15,7 @@ module.exports = async (app, req, res) => { type: (req.query.hasOwnProperty("creator") || req.query.hasOwnProperty("creators")) ? "creators" : "top", } - request.post('http://boomlings.com/database/getGJScores20.php', { + request.post(app.endpoint + 'getGJScores20.php', { form : params}, async function(err, resp, body) { if (err || body == '-1' || !body) return res.send("-1") diff --git a/api/leaderboardLevel.js b/api/leaderboardLevel.js index c0bed0e..af28fde 100644 --- a/api/leaderboardLevel.js +++ b/api/leaderboardLevel.js @@ -18,7 +18,7 @@ module.exports = async (app, req, res) => { type: req.query.hasOwnProperty("week") ? "2" : "1", } - request.post('http://boomlings.com/database/getGJLevelScores211.php', { + request.post(app.endpoint + 'getGJLevelScores211.php', { form : params}, async function(err, resp, body) { if (err || body == '-1' || !body) return res.send("-1") @@ -32,7 +32,7 @@ module.exports = async (app, req, res) => { x.percent = x[3] x.coins = x[13] x.playerID = x[2] - x.date = x[42] + " ago" + x.date = x[42] + app.config.timestampSuffix keys.forEach(k => delete x[k]) }) diff --git a/api/level.js b/api/level.js index 8993c54..361c2ab 100644 --- a/api/level.js +++ b/api/level.js @@ -15,7 +15,7 @@ module.exports = async (app, req, res, api, analyze) => { if (analyze || req.query.hasOwnProperty("download")) return app.modules.download(app, req, res, api, levelID, analyze) - request.post('http://boomlings.com/database/getGJLevels21.php', { + request.post(app.endpoint + 'getGJLevels21.php', { form: { str: levelID, secret: app.secret, diff --git a/api/like.js b/api/like.js index e1517ab..123acac 100644 --- a/api/like.js +++ b/api/like.js @@ -35,7 +35,7 @@ module.exports = async (app, req, res) => { params.chk = chk - request.post('http://boomlings.com/database/likeGJItem211.php', { + request.post(app.endpoint + 'likeGJItem211.php', { form: params }, function (err, resp, body) { if (err) return res.status(400).send("The Geometry Dash servers returned an error! Perhaps they're down for maintenance") diff --git a/api/postComment.js b/api/postComment.js index f26386f..73d207b 100644 --- a/api/postComment.js +++ b/api/postComment.js @@ -45,7 +45,7 @@ module.exports = async (app, req, res) => { chk = xor.encrypt(chk, 29481) params.chk = chk - request.post('http://boomlings.com/database/uploadGJComment21.php', { + request.post(app.endpoint + 'uploadGJComment21.php', { form: params }, function (err, resp, body) { if (err) return res.status(400).send("The Geometry Dash servers returned an error! Perhaps they're down for maintenance") diff --git a/api/postProfileComment.js b/api/postProfileComment.js index 43f764f..54f141e 100644 --- a/api/postProfileComment.js +++ b/api/postProfileComment.js @@ -30,7 +30,7 @@ module.exports = async (app, req, res) => { chk = xor.encrypt(chk, 29481) params.chk = chk - request.post('http://boomlings.com/database/uploadGJAccComment20.php', { + request.post(app.endpoint + 'uploadGJAccComment20.php', { form: params }, function (err, resp, body) { if (err) return res.status(400).send("The Geometry Dash servers returned an error! Perhaps they're down for maintenance") diff --git a/api/profile.js b/api/profile.js index 634eaa7..d5fe927 100644 --- a/api/profile.js +++ b/api/profile.js @@ -3,7 +3,7 @@ const fs = require('fs') module.exports = async (app, req, res, api, getLevels) => { - request.post('http://boomlings.com/database/getGJUsers20.php', { + request.post(app.endpoint + 'getGJUsers20.php', { form: { str: getLevels || req.params.id, secret: app.secret @@ -17,7 +17,7 @@ module.exports = async (app, req, res, api, getLevels) => { let gdSearchResult = app.parseResponse(b1) - request.post('http://boomlings.com/database/getGJUserInfo20.php', { + request.post(app.endpoint + 'getGJUserInfo20.php', { form: { targetAccountID: gdSearchResult[16], secret: app.secret diff --git a/api/search.js b/api/search.js index 594d9db..c2dc6db 100644 --- a/api/search.js +++ b/api/search.js @@ -67,7 +67,7 @@ module.exports = async (app, req, res) => { if (req.params.text == "*") delete filters.str - request.post('http://boomlings.com/database/getGJLevels21.php', { + request.post(app.endpoint + 'getGJLevels21.php', { form : filters}, async function(err, resp, body) { if (err || !body || body == '-1') return res.send("-1") @@ -85,7 +85,7 @@ module.exports = async (app, req, res) => { let arr = x.split(':') authorList[arr[0]] = [arr[1], arr[2]]}) - let levelArray = preRes.map(x => app.parseResponse(x)) + let levelArray = preRes.map(x => app.parseResponse(x)).filter(x => x[1]) let parsedLevels = [] await levelArray.forEach(async (x, y) => { diff --git a/classes/Level.js b/classes/Level.js index 8486c06..6094bf4 100644 --- a/classes/Level.js +++ b/classes/Level.js @@ -1,4 +1,5 @@ const XOR = require(__dirname + "/../classes/XOR"); +const config = require(__dirname + "/../misc/gdpsConfig"); let orbs = [0, 0, 50, 75, 125, 175, 225, 275, 350, 425, 500] let length = ['Tiny', 'Short', 'Medium', 'Long', 'XL'] @@ -6,9 +7,10 @@ let difficulty = { 0: 'Unrated', 10: 'Easy', 20: 'Normal', 30: 'Hard', 40: 'Hard class Level { constructor(levelInfo, author = []) { + if (!levelInfo[2]) return; this.name = levelInfo[2]; this.id = levelInfo[1]; - this.description = Buffer.from(levelInfo[3], "base64").toString() || "(No description provided)"; + this.description = (config.base64descriptions ? Buffer.from(levelInfo[3], "base64").toString() : levelInfo[3]) || "(No description provided)"; this.author = author[1] || "-" this.authorID = levelInfo[6] this.accountID = author[2] || 0 @@ -22,8 +24,8 @@ class Level { this.diamonds = levelInfo[18] < 2 ? 0 : parseInt(levelInfo[18]) + 2 this.featured = levelInfo[19] > 0 this.epic = levelInfo[42] == 1 - if (levelInfo[28]) this.uploaded = levelInfo[28] + ' ago' - if (levelInfo[29]) this.updated = levelInfo[29] + ' ago' + if (levelInfo[28]) this.uploaded = levelInfo[28] + config.timestampSuffix + if (levelInfo[29]) this.updated = levelInfo[29] + config.timestampSuffix this.version = levelInfo[5]; if (levelInfo[27]) this.password = levelInfo[27]; this.copiedID = levelInfo[30] @@ -46,7 +48,7 @@ class Level { if (this.password && this.password != 0) { let xor = new XOR(); - let pass = xor.decrypt(this.password, 26364); + let pass = config.xorPasswords ? xor.decrypt(this.password, 26364) : this.password; if (pass.length > 1) this.password = pass.slice(1); else this.password = pass; } diff --git a/html/profile.html b/html/profile.html index 73ab1a3..d0d22dd 100644 --- a/html/profile.html +++ b/html/profile.html @@ -99,7 +99,7 @@ - +