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
This commit is contained in:
GDColon 2019-12-21 22:16:18 -05:00
parent 7098a1db33
commit 252e3f3b05
18 changed files with 104 additions and 37 deletions

View file

@ -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

View file

@ -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([])

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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")

View file

@ -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])
})

View file

@ -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,

View file

@ -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")

View file

@ -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")

View file

@ -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")

View file

@ -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

View file

@ -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) => {

View file

@ -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;
}

View file

@ -99,7 +99,7 @@
<img src="../icon/[[USERNAME]]?form=wave" title="Wave [[WAVE]]" style="height: 7%">
<img src="../icon/[[USERNAME]]?form=robot" title="Robot [[ROBOT]]" >
<img src="../icon/[[USERNAME]]?form=spider" title="Spider [[SPIDER]]">
<img src="../deatheffects/[[DEATHEFFECT]].png" title="Death Effect [[DEATHEFFECT]]">
<img src="../deatheffects/[[DEATHEFFECT]].png" title="Death Effect [[DEATHEFFECT]]" id="deatheffect">
</div>
<div class="lightBox center dragscroll" id="statusDiv" style="margin: 2% auto; width: 105vh; height: 35vh; background-color: #BE6F3F">
@ -159,6 +159,7 @@ $('#globalrank[[RANK]]').hide()
if (`[[YOUTUBE]]` != "null") $('#youtube').show()
if (`[[TWITTER]]` != "null") $('#twitter').show()
if (`[[TWITCH]]` != "null") $('#twitch').show()
if (`[[DEATHEFFECT]]` == "undefined") $('#deatheffect').hide()
let messages = "[[MESSAGES]]"
let commenthistory = "[[COMMENTHISTORY]]"

View file

@ -19,11 +19,21 @@ fs.readdirSync('./api').forEach(x => {
})
app.secret = 'Wmfd2893gb7'
app.endpoint = 'http://boomlings.com/database/'
app.config = require('./misc/gdpsConfig') // tweak settings in this file if you're using a GDPS
const secrets = require("./misc/secretStuff.json")
app.id = secrets.id
app.gjp = secrets.gjp
//these are the only two things in secretStuff.json, both are only used for level leaderboards
try {
const secrets = require("./misc/secretStuff.json")
app.id = secrets.id
app.gjp = secrets.gjp
}
catch {
app.id = 0
app.gjp = 0
console.log("No GJP/Account ID has been set, which means that level leaderboards won't load. Put those two values in /misc/secretStuff.json")
}
function haltOnTimedout (req, res, next) {
if (!req.timedout) next()

27
misc/gdpsConfig.js Normal file
View file

@ -0,0 +1,27 @@
// In case you wanna use a fork of GDBrowser for a GDPS or something, here are some settings you can tweak to save you some precious time
// Main endpoint (e.g. boomlings.com) should be edited in index.js
// This isn't a JSON because you can't leave comments on them, ew
module.exports = {
base64descriptions: true, // Are level descriptions encoded in Base64?
xorPasswords: true, // Are level passwords XOR encrypted?
timestampSuffix: " ago", // Suffix to add after timestamps, if any.
// more settings soon
// feel free to drop a PR if you're able to make gdbrowser work better with gdps'es <3
}
/*
STUFF THAT'S BROKEN
- Comments, because of how profiles are handled
- Leaderboards
- Level descriptions, if a mix of Base64 and plain text is used
- Map packs and gauntlets
STUFF THAT I HAVEN'T TESTED
- Level leaderboards
- All POST requests (commenting, liking, etc)
*/