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:
parent
7098a1db33
commit
252e3f3b05
18 changed files with 104 additions and 37 deletions
23
README.md
23
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
|
||||
|
|
|
@ -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([])
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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])
|
||||
})
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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]]"
|
||||
|
|
18
index.js
18
index.js
|
@ -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
27
misc/gdpsConfig.js
Normal 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)
|
||||
*/
|
Loading…
Reference in a new issue