diff --git a/README.md b/README.md index 5723efc..7c59263 100644 --- a/README.md +++ b/README.md @@ -1,100 +1,249 @@ + # GDBrowser + + Uh... so I've never actually used GitHub before this. But I'll try to explain everything going on here. + + 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. + + ## How do I run this? +If you're just here to use GDBrowser locally because the site is down or blocked or restricted or god knows what, this is the only part you really need to read + + +To run GDBrowser locally: +1) Install [node.js](https://nodejs.org/en/download/) if you don't already have it +2) Clone/download this repository +3) Open cmd/powershell/terminal in the main folder (with index.js) +4) Type `npm i` to flood your hard drive with code that's 99% useless +5) Type `node index` to run the web server +6) GDBrowser is now running locally at http://localhost:2000 + + +If you want to disable rate limits, ip forwarding, etc you can do so by modifying `settings.js`. Doing this is probably a good idea if you feel like obliterating Rob's servers for some reason. (please don't) + + ## 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. +~~I mean, sure. Why not.~~ +Hold up, wait a minute... private servers are an official feature now! -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. (seriously what's with that?) -You can also check out `settings.js` to tweak some additional settings (mainly GDPS related) such as whether to cache things or if timestamps should end with "ago" +If you would like to add your GDPS to GDBrowser, simply reach out to me on [Twitter](https://twitter.com/TheRealGDColon) and I'll be happy to add it (provided the server is relatively large and active) + + +If you 100% insist on adding a private server to your own magical little fork, you can do so by adding it to **servers.json**. Simply add a new object to the array with the following information: + + + + +**name:** The display name of the server + +**link:** The server's website URL (unrelated to the actual endpoint) + +**author:** The creator(s) of the server + +**authorLink:** The URL to open when clicking on the creator's name + +**id:** An ID for the server, also used as the subdomain (e.g. `something` would become `something.gdbrowser.com`) + +**endpoint:** The actual endpoint to ~~spam~~ send requests to (e.g. `http://boomlings.com/database/` - make sure it ends with a slash!) + + +---- +There's also a few optional values for fine-tuning. I'll add more over time + +[string] **timestampSuffix:** A string to append at the end of timestamps. Vanilla GD uses " ago" + +[bool] **downloadsDisabled:** (Greys out all forms of downloading on the frontend (daily, weekly, analysis, etc). I love you too RobTop <3 + +[bool] **onePointNine:** Makes a bunch of fancy changes to better fit 1.9 servers. (removes orbs/diamonds, hides some pointless buttons, etc) + +[bool] **weeklyLeaderboard:** Enables the lost but not forgotten Weekly Leaderboard, for servers that still milk it + +[object] **substitutions:** A list of parameter substitutions, because some servers +rename/obfuscate them. (e.g. `{ "levelID": "oiuyhxp4w9I" }`) + +[object] **overrides:** A list of endpoint substitutions, because some servers +use renamed or older versions. (e.g. `{ "getGJLevels21": "dorabaeChooseLevel42" }`) + + # Folders -GDBrowser has a lot of folders. I like to keep things neat. + + +GDBrowser has a lot of folders. [citation needed] +I pride myself in keeping my files neat, without doing the whole `src/main/data/stuff/code/homework/newfolder/util/actualcode` garbage + + 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. + -The odd one out is icon.js, which is for generating GD icons. The code here is horrendous, so apologies in advance. Improvements to it (especially UFO and robot generation) would be greatly appreciated! (and i will love you forever) +They're all fairly similar. Fetch something, parse the response, and serve it in a crisp and non-intimidating JSON. This is probably what you came for. + + + +The odd one out is icon.js, which is for generating GD icons. The code here is horrendous, so apologies in advance. Improvements to it would be greatly appreciated! (and i will love you forever) + + ## Assets + Assets! Assets everywhere! + + All the GD stuff was ripped straight from the GD spritesheets via [Absolute's texture splitter hack](https://youtu.be/pYQgIyNhow8). If you want a nice categorized version, [I've done all the dirty work for you.](https://www.mediafire.com/file/4d99bw1zhwcl507/textures.zip/file) + + I'd explain what's in all the subfolders but it's pretty obvious. I tried my best to organize everything nicely. + + ## Classes + What's a class you ask? Good question. + + I guess the best way to put it is uh... super fancy functions??? + + Level.js parses the server's disgusting response and sends back a nice object with all the level info + + XOR.js encrypts/decrypts stuff like GD passwords + + ## HTML -The HTML files! Nothing too fancy, since it can all be seen directly from gdbrowser. Note that profile.html and level.html have [[VARIABLES]] (name, id, etc) replaced by the server when they're sent. + +The HTML files! Nothing too fancy, since it can all be seen directly from gdbrowser. Note that profile.html and level.html (and some parts of home.html) have [[VARIABLES]] (name, id, etc) replaced by the server when they're sent. + + comingsoon.html was used while the site was still in development, I just left it in there as a nice little throwback -## Icons -It's GJ_Gamesheet02 but split into a much more intimidating cluster of a million files. These icons are put together and colored in the monstrosity that is icon.js + + +## Icons + +It's GJ_Gamesheet02 but split into a much more intimidating cluster of a million files. These icons are put together and colored in the monstrosity that is icon.js + + + +parseIconPlist.js reads GJ_GameSheet02-uhd.plist and magically transforms it into gameSheet.json. Props to 101arrowz for helping make this + + +colors.json is a list of the player colors in GD. Fairly straight forward -parsePlist.js reads GJ_GameSheet02-uhd.plist and magically transforms it into gameSheet.json. Props to 101arrowz for making this forms.json is a list of the different icon forms, their ingame filenames, and their index in responses from the GD servers + +offsets.json is a bunch of hardcoded offsets. Desperate times call for desperate measures + + + ## Misc + Inevitable misc folder + + **Level Analysis Stuff (in a separate folder)** + + blocks.json - The object IDs in the different 'families' of blocks + + colorProperties.json - Color channel cheatsheet + + initialProperties.json - Level settings cheatsheet + + objectProperties.json - Object property cheatsheet. Low budget version of [AlFas' one](https://github.com/AlFasGD/GDAPI/blob/master/GDAPI/GDAPI/Enumerations/GeometryDash/ObjectProperty.cs) + + objects.json - IDs for portals, orbs, triggers, and misc stuff + + **Everything Else** + + achievements.json - List of all GD/meltdown/subzero/etc achievements. `parseAchievementPlist.js` automatically creates this file + + achievementTypes.json - An object containing different categories of achievements (stars, shards, vault, etc) and how to identify them + + colors.json - List of icon colors in RGB format + + credits.json - Credits! (shown on the homepage) + + dragscroll.js - Used on several pages for drag scrolling + + level.json - An array of the official GD tracks, and also difficulty face stuff for level searching + + parseAchievementPlist.js - A script that reads GD's achievement .plist files and converts it into achievements.json + + sampleIcons.json - A pool of icons, one of which will randomly appear when visiting the icon kit. Syntax is [Name, ID, Col1, Col2, Glow], + + secretStuff.json - GJP goes here, needed for level leaderboards. Not included in the repo for obvious reasons + + settings.js - Tweak small settings here, mainly for local use or GDPS'es + + shops.js - A hardcoded list of all the shop icons in GD + + sizecheck.js - Excecuted on most pages. Used for the 'page isn't wide enough' message, back button, and a few other things + + --- + + happy gdbrowsing and god bless. \ No newline at end of file diff --git a/api/comments.js b/api/comments.js index 670b75b..8570926 100644 --- a/api/comments.js +++ b/api/comments.js @@ -1,8 +1,6 @@ -const request = require('request') - module.exports = async (app, req, res) => { - if (app.offline) return res.send("-1") + if (req.offline) return res.send("-1") let count = +req.query.count || 10 if (count > 1000) count = 1000 @@ -20,7 +18,7 @@ module.exports = async (app, req, res) => { if (req.query.type == "commentHistory") path = "getGJCommentHistory" else if (req.query.type == "profile") path = "getGJAccountComments20" - request.post(`${app.endpoint}${path}.php`, params, async function(err, resp, body) { + req.gdRequest(path, params, function(err, resp, body) { if (err || body == '-1' || !body) return res.send("-1") @@ -47,7 +45,7 @@ module.exports = async (app, req, res) => { comment.content = Buffer.from(x[2], 'base64').toString(); comment.ID = x[6] comment.likes = +x[4] - comment.date = (x[9] || "?") + app.config.timestampSuffix + comment.date = (x[9] || "?") + (req.timestampSuffix || "") if (comment.content.endsWith("⍟") || comment.content.endsWith("☆")) { comment.content = comment.content.slice(0, -1) comment.browserColor = true @@ -63,10 +61,10 @@ module.exports = async (app, req, res) => { comment.moderator = +x[11] || 0 comment.icon = { form: ['icon', 'ship', 'ball', 'ufo', 'wave', 'robot', 'spider'][+y[14]], - icon: +y[9], + icon: +y[9] || 1, col1: +y[10], col2: +y[11], - glow: +y[15] > 0 + glow: +y[15] > 1 } } diff --git a/api/download.js b/api/download.js index 540a3da..2e3d371 100644 --- a/api/download.js +++ b/api/download.js @@ -3,7 +3,7 @@ const fs = require('fs') const Level = require('../classes/Level.js') module.exports = async (app, req, res, api, ID, analyze) => { - if (app.offline) { + if (req.offline) { if (!api && levelID < 0) return res.redirect('/') if (!api) return res.redirect('search/' + req.params.id) else return res.send("-1") @@ -14,9 +14,9 @@ module.exports = async (app, req, res, api, ID, analyze) => { else if (levelID == "weekly") levelID = -2 else levelID = levelID.replace(/[^0-9]/g, "") - request.post(app.endpoint + 'downloadGJLevel22.php', req.gdParams({ levelID }), async function (err, resp, body) { + req.gdRequest('downloadGJLevel22', { levelID }, function (err, resp, body) { - if (err || !body || body == '-1' || body.startsWith(" { let authorData = body.split("#")[3] // daily/weekly only, most likely let levelInfo = app.parseResponse(body) - let level = new Level(levelInfo, true) + let level = new Level(levelInfo, req.server, true) - request.post(authorData ? "" : app.endpoint + 'getGJUsers20.php', authorData ? {} : req.gdParams({ str: level.authorID }), function (err1, res1, b1) { + let foundID = app.accountCache[Object.keys(app.accountCache).find(x => app.accountCache[x][1] == level.authorID)] + if (foundID) foundID = foundID.filter(x => x != level.authorID) + + req.gdRequest(authorData ? "" : 'getGJUsers20', { str: level.authorID }, function (err1, res1, b1) { let gdSearchResult = authorData ? "" : app.parseResponse(b1) - request.post(authorData ? "" : app.endpoint + 'getGJUserInfo20.php', authorData ? {} : req.gdParams({ targetAccountID: gdSearchResult[16] }), function (err2, res2, b2) { + req.gdRequest(authorData ? "" : 'getGJUserInfo20', { targetAccountID: gdSearchResult[16] }, function (err2, res2, b2) { - if (err2 && authorData) { - let authorInfo = authorData.split(":") - level.author = authorInfo[1] - level.accountID = authorInfo[0] + if (err2 && (foundID || authorData)) { + let authorInfo = foundID || authorData.split(":") + level.author = authorInfo[1] || "-" + level.accountID = authorInfo[0].includes(",") ? "0" : authorInfo[0] } else if (!err && b2 != '-1') { let account = app.parseResponse(b2) - level.author = account[1] + level.author = account[1] || "-" level.accountID = gdSearchResult[16] } @@ -48,7 +51,7 @@ module.exports = async (app, req, res, api, ID, analyze) => { level.accountID = "0" } - request.post(app.endpoint + 'getGJSongInfo.php', req.gdParams({ songID: level.customSong }), async function (err, resp, songRes) { + req.gdRequest('getGJSongInfo', { songID: level.customSong }, function (err, resp, songRes) { if (!err && songRes != '-1') { let songData = app.parseResponse(songRes, '~|~') @@ -71,7 +74,12 @@ module.exports = async (app, req, res, api, ID, analyze) => { level.data = levelInfo[4] if (analyze) return app.run.analyze(app, req, res, level) - if (app.isGDPS) level.gdps = true + + if (req.isGDPS) level.gdps = (req.onePointNine ? "1.9/" : "") + req.endpoint + if (req.onePointNine) { + level.orbs = 0 + level.diamonds = 0 + } function sendLevel() { if (api) return res.send(level) @@ -88,7 +96,7 @@ module.exports = async (app, req, res, api, ID, analyze) => { } if (levelID < 0) { - request.post(app.endpoint + 'getGJDailyLevel.php', req.gdParams({ weekly: levelID == -2 ? "1" : "0" }), async function (err, resp, dailyInfo) { + req.gdRequest('getGJDailyLevel', { weekly: levelID == -2 ? "1" : "0" }, function (err, resp, dailyInfo) { if (err || dailyInfo == -1) return sendLevel() let dailyTime = dailyInfo.split("|")[1] level.nextDaily = +dailyTime @@ -97,7 +105,7 @@ module.exports = async (app, req, res, api, ID, analyze) => { }) } - else if (level.difficulty == "Extreme Demon") { + else if (!level.gdps && level.difficulty == "Extreme Demon") { request.get('https://www.pointercrate.com/api/v2/demons/?name=' + level.name.trim(), function (err, resp, demonList) { if (err) return sendLevel() let demon = JSON.parse(demonList) diff --git a/api/gauntlets.js b/api/gauntlets.js index 9842960..4ae605b 100644 --- a/api/gauntlets.js +++ b/api/gauntlets.js @@ -1,21 +1,21 @@ -const request = require('request') - -let cache = {data: null, indexed: 0} +let cache = {} module.exports = async (app, req, res) => { - if (app.offline) return res.send("-1") - else if (app.config.cacheGauntlets && cache.data != null && cache.indexed + 2000000 > Date.now()) return res.send(cache.data) // half hour cache + if (req.offline) return res.send("-1") - request.post(app.endpoint + 'getGJGauntlets21.php', req.gdParams({}), function (err, resp, body) { + let cached = cache[req.id] + if (app.config.cacheGauntlets && cached && cached.data && cached.indexed + 2000000 > Date.now()) return res.send(cached.data) // half hour cache - if (err || !body || body == '-1' || body.startsWith(" app.parseResponse(x)) let gauntletList = gauntlets.map(x => ({ id: +x[1], levels: x[3].split(",") })) - if (app.config.cacheGauntlets) cache = {data: gauntletList, indexed: Date.now()} + if (app.config.cacheGauntlets) cache[req.id] = {data: gauntletList, indexed: Date.now()} res.send(gauntletList) }) -} \ No newline at end of file +} \ No newline at end of file diff --git a/api/icon.js b/api/icon.js index 680aad4..6cc8254 100644 --- a/api/icon.js +++ b/api/icon.js @@ -2,7 +2,6 @@ // i advise you to turn back now // seriously, it's not too late -const request = require('request') const Jimp = require('jimp'); const fs = require('fs'); const icons = require('../icons/gameSheet.json'); @@ -360,29 +359,28 @@ module.exports = async (app, req, res) => { let userCode; res.contentType('image/png'); - if (app.offline || req.query.hasOwnProperty("noUser") || req.query.hasOwnProperty("nouser") || username == "icon") return buildIcon() + if (req.offline || req.query.hasOwnProperty("noUser") || req.query.hasOwnProperty("nouser") || username == "icon") return buildIcon() else if (app.config.cachePlayerIcons && !Object.keys(req.query).filter(x => !["form", "forceGD"].includes(x)).length) { - userCode = `${app.GDPSName}u-${username.toLowerCase()}-${forms[req.query.form] ? req.query.form : 'cube'}` + userCode = `${req.id}u-${username.toLowerCase()}-${forms[req.query.form] ? req.query.form : 'cube'}` if (cache[userCode]) return res.end(cache[userCode].value) } let accountMode = !req.query.hasOwnProperty("player") && Number(req.params.id) - let foundID = app.accountCache[app.GDPSName + username.toLowerCase()] + let foundID = app.accountCache[req.id][username.toLowerCase()] let skipRequest = accountMode || foundID - let forceGD = req.query.hasOwnProperty("forceGD") // forces request to be made on boomlings.com - let endpoint = forceGD ? "http://boomlings.com/database/" : app.endpoint + let forceGD = req.query.hasOwnProperty("forceGD") // skip request by causing fake error lmao - request.post(skipRequest ? "" : endpoint + 'getGJUsers20.php', skipRequest ? {} : req.gdParams({ str: username }, !forceGD), function (err1, res1, body1) { + req.gdRequest(skipRequest ? "" : 'getGJUsers20', skipRequest ? {} : req.gdParams({ str: username, forceGD }, !forceGD), function (err1, res1, body1) { - let result = foundID ? foundID[0] : (accountMode || err1 || !body1 || body1 == "-1" || body1.startsWith(" { - if (app.isGDPS) return res.send("-2") + if (req.isGDPS) return res.send(req.server.weeklyLeaderboard ? "-3" : "-2") if (!app.sheetsKey) return res.send([]) let gdMode = post || req.query.hasOwnProperty("gd") let modMode = !gdMode && req.query.hasOwnProperty("mod") diff --git a/api/leaderboards/boomlings.js b/api/leaderboards/boomlings.js index d896b21..fae32b2 100644 --- a/api/leaderboards/boomlings.js +++ b/api/leaderboards/boomlings.js @@ -3,7 +3,7 @@ const request = require('request') module.exports = async (app, req, res) => { request.post('http://robtopgames.com/Boomlings/get_scores.php', { - form : { secret: app.config.params.secret || "Wmfd2893gb7", name: "Player" } }, async function(err, resp, body) { + form : { secret: app.config.params.secret || "Wmfd2893gb7", name: "Player" } }, function(err, resp, body) { if (err || !body || body == 0) return res.send("0") diff --git a/api/leaderboards/leaderboardLevel.js b/api/leaderboards/leaderboardLevel.js index c55eaa3..5ba2800 100644 --- a/api/leaderboards/leaderboardLevel.js +++ b/api/leaderboards/leaderboardLevel.js @@ -1,8 +1,6 @@ -const request = require('request') - module.exports = async (app, req, res) => { - if (app.offline) return res.send("-1") + if (req.offline) return res.send("-1") let amount = 100; let count = req.query.count ? parseInt(req.query.count) : null @@ -11,19 +9,19 @@ module.exports = async (app, req, res) => { else amount = count; } - let params = req.gdParams({ + let params = { levelID: req.params.id, accountID: app.id, gjp: app.gjp, type: req.query.hasOwnProperty("week") ? "2" : "1", - }) + } - request.post(app.endpoint + 'getGJLevelScores211.php', params, async function(err, resp, body) { + req.gdRequest('getGJLevelScores211', params, function(err, resp, body) { if (err || body == -1 || !body) return res.send("-1") scores = body.split('|').map(x => app.parseResponse(x)).filter(x => x[1]) if (!scores.length) return res.send("-1") - else app.trackSuccess() + else app.trackSuccess(req.id) scores.forEach(x => { let keys = Object.keys(x) @@ -32,13 +30,13 @@ module.exports = async (app, req, res) => { x.percent = +x[3] x.coins = +x[13] x.playerID = x[2] - x.date = x[42] + app.config.timestampSuffix + x.date = x[42] + (req.timestampSuffix || "") x.icon = { form: ['icon', 'ship', 'ball', 'ufo', 'wave', 'robot', 'spider'][+x[14]], icon: +x[9], col1: +x[10], col2: +x[11], - glow: +x[15] > 0 + glow: +x[15] > 1 } keys.forEach(k => delete x[k]) }) diff --git a/api/leaderboards/scores.js b/api/leaderboards/scores.js index 8923eac..4780f07 100644 --- a/api/leaderboards/scores.js +++ b/api/leaderboards/scores.js @@ -1,8 +1,6 @@ -const request = require('request') - module.exports = async (app, req, res) => { - if (app.offline) return res.send("-1") + if (req.offline) return res.send("-1") let amount = 100; let count = req.query.count ? parseInt(req.query.count) : null @@ -14,9 +12,9 @@ module.exports = async (app, req, res) => { let params = {count: amount, type: "top"} if (["creators", "creator", "cp"].some(x => req.query.hasOwnProperty(x) || req.query.type == x)) params.type = "creators" - else if (["week", "weekly"].some(x => req.query.hasOwnProperty(x) || req.query.type == x)) params.type = "weekly" // i think GDPS'es use this + else if (["week", "weekly"].some(x => req.query.hasOwnProperty(x) || req.query.type == x)) params.type = "week" // i think GDPS'es use this - request.post(app.endpoint + 'getGJScores20.php', req.gdParams(params), async function(err, resp, body) { + req.gdRequest('getGJScores20', params, function(err, resp, body) { if (err || body == '-1' || !body) return res.send("-1") scores = body.split('|').map(x => app.parseResponse(x)).filter(x => x[1]) @@ -39,7 +37,7 @@ module.exports = async (app, req, res) => { icon: +x[9], col1: +x[10], col2: +x[11], - glow: +x[15] > 0 + glow: +x[15] > 1 } keys.forEach(k => delete x[k]) }) diff --git a/api/level.js b/api/level.js index 9af5bad..4eeac96 100644 --- a/api/level.js +++ b/api/level.js @@ -2,11 +2,9 @@ const request = require('request') const fs = require('fs') const Level = require('../classes/Level.js') -function xor(str, key) { return Buffer.from(String.fromCodePoint(...str.split('').map((char, i) => char.charCodeAt(0) ^ key.charCodeAt(i % key.length)))).toString('base64') } - module.exports = async (app, req, res, api, analyze) => { - if (app.offline) { + if (req.offline) { if (!api) return res.redirect('search/' + req.params.id) else return res.send("-1") } @@ -22,9 +20,9 @@ module.exports = async (app, req, res, api, analyze) => { if (analyze || req.query.hasOwnProperty("download")) return app.run.download(app, req, res, api, levelID, analyze) - request.post(app.endpoint + 'getGJLevels21.php', req.gdParams({ str: levelID, type: 0 }), async function (err, resp, body) { + req.gdRequest('getGJLevels21', { str: levelID, type: 0 }, function (err, resp, body) { - if (err || !body || body == '-1' || body.startsWith(" { song = app.parseResponse(song, '~|~') let levelInfo = app.parseResponse(preRes[0]) - let level = new Level(levelInfo, false, author) + let level = new Level(levelInfo, req.server, false, author) if (level.customSong) { level.songName = song[2] || "Unknown" @@ -52,7 +50,13 @@ module.exports = async (app, req, res, api, analyze) => { level.songID = "Level " + [parseInt(levelInfo[12]) + 1] } - if (app.isGDPS) level.gdps = true + if (level.author != "-" && app.config.cacheAccountIDs) app.accountCache[req.id][level.author.toLowerCase()] = [level.accountID, level.authorID, level.author] + + if (req.isGDPS) level.gdps = (req.onePointNine ? "1.9/" : "") + req.endpoint + if (req.onePointNine) { + level.orbs = 0 + level.diamonds = 0 + } function sendLevel() { @@ -67,11 +71,13 @@ module.exports = async (app, req, res, api, analyze) => { let regex = new RegExp(`\\[\\[${x.toUpperCase()}\\]\\]`, "g") html = html.replace(regex, app.clean(level[x])) }) + if (req.server.downloadsDisabled) html = html.replace('id="additional" class="', 'class="downloadDisabled ') + .replace('analyzeBtn"', 'analyzeBtn" style="filter: opacity(30%)"') return res.send(html) }) } - if (level.difficulty == "Extreme Demon") { + if (!level.gdps && level.difficulty == "Extreme Demon") { request.get('http://www.pointercrate.com/api/v2/demons/?name=' + level.name.trim(), function (err, resp, demonList) { if (err) return sendLevel() let demon = JSON.parse(demonList) diff --git a/api/mappacks.js b/api/mappacks.js index 2c3ecce..73428a2 100644 --- a/api/mappacks.js +++ b/api/mappacks.js @@ -1,33 +1,43 @@ -const request = require('request') -const difficulties = ["unrated", "easy", "normal", "hard", "harder", "insane", "demon"] - -let cache = {data: null, indexed: 0} +let difficulties = ["auto", "easy", "normal", "hard", "harder", "insane", "demon", "demon-easy", "demon-medium", "demon-insane", "demon-extreme"] +let cache = {} module.exports = async (app, req, res) => { - if (app.offline) return res.send("-1") - else if (app.config.cacheMapPacks && cache.data != null && cache.indexed + 20000000 > Date.now()) return res.send(cache.data) // 6 hour cache + if (req.offline) return res.send("-1") + + let cached = cache[req.id] + if (app.config.cacheMapPacks && cached && cached.data && cached.indexed + 5000000 > Date.now()) return res.send(cached.data) // 1.5 hour cache + let params = { count: 250, page: 0 } + let packs = [] - request.post(app.endpoint + 'getGJMapPacks21.php', req.gdParams({ count: 200 }), function (err, resp, body) { + function mapPackLoop() { + req.gdRequest('getGJMapPacks21', params, function (err, resp, body) { - if (err || !body || body == '-1' || body.startsWith(" app.parseResponse(x)).filter(x => x[2]) + let newPacks = body.split('#')[0].split('|').map(x => app.parseResponse(x)).filter(x => x[2]) + packs = packs.concat(newPacks) - let mappacks = packs.map(x => ({ // "packs.map()" laugh now please - id: +x[1], - levels: x[3].split(","), - name: x[2], - stars: +x[4], - coins: +x[5], - difficulty: difficulties[+x[6]], - barColor: x[7], - textColor: x[8] - })) + // not all GDPS'es support the count param, which means recursion time!!! + if (newPacks.length == 10) { + params.page++ + return mapPackLoop() + } + + let mappacks = packs.map(x => ({ // "packs.map()" laugh now please + id: +x[1], + levels: x[3].split(","), + name: x[2], + stars: +x[4], + coins: +x[5], + difficulty: difficulties[+x[6]] || "unrated", + barColor: x[7], + textColor: x[8] + })) - if (app.config.cacheMapPacks) cache = {data: mappacks, indexed: Date.now()} - return res.send(mappacks) - - }) - + if (app.config.cacheMapPacks) cache[req.id] = {data: mappacks, indexed: Date.now()} + return res.send(mappacks) + }) + } + mapPackLoop() } \ No newline at end of file diff --git a/api/messages/countMessages.js b/api/messages/countMessages.js index d930b47..602a77b 100644 --- a/api/messages/countMessages.js +++ b/api/messages/countMessages.js @@ -1,22 +1,18 @@ -const request = require('request') -const XOR = require('../../classes/XOR.js'); -const xor = new XOR(); - module.exports = async (app, req, res) => { if (!req.body.accountID) return res.status(400).send("No account ID provided!") if (!req.body.password) return res.status(400).send("No password provided!") - let params = req.gdParams({ + let params = { accountID: req.body.accountID, targetAccountID: req.body.accountID, - gjp: xor.encrypt(req.body.password, 37526), - }) + gjp: app.xor.encrypt(req.body.password, 37526), + } - request.post(app.endpoint + 'getGJUserInfo20.php', params, async function (err, resp, body) { + req.gdRequest('getGJUserInfo20', params, function (err, resp, body) { - if (err || body == '-1' || body == '-2' || !body) return res.status(400).send(`Error counting messages! Messages get blocked a lot so try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince()} ago.`) - else app.trackSuccess() + if (err || body == -1 || body == -2 || !body) return res.status(400).send(`Error counting messages! Messages get blocked a lot so try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince(req.id)} ago.`) + else app.trackSuccess(req.id) let count = app.parseResponse(body)[38] if (!count) return res.status(400).send("Error fetching unread messages!") else res.status(200).send(count) diff --git a/api/messages/deleteMessage.js b/api/messages/deleteMessage.js index 190cd28..693a5bb 100644 --- a/api/messages/deleteMessage.js +++ b/api/messages/deleteMessage.js @@ -1,7 +1,3 @@ -const request = require('request') -const XOR = require('../../classes/XOR.js'); -const xor = new XOR(); - module.exports = async (app, req, res, api) => { if (!req.body.accountID) return res.status(400).send("No account ID provided!") @@ -10,17 +6,17 @@ module.exports = async (app, req, res, api) => { let params = { accountID: req.body.accountID, - gjp: xor.encrypt(req.body.password, 37526), + gjp: app.xor.encrypt(req.body.password, 37526), messages: Array.isArray(req.body.id) ? req.body.id.map(x => x.trim()).join(",") : req.body.id, } let deleted = params.messages.split(",").length - request.post(app.endpoint + 'deleteGJMessages20.php', req.gdParams(params), async function (err, resp, body) { + req.gdRequest('deleteGJMessages20', params, function (err, resp, body) { - if (body != 1) return res.status(400).send(`The Geometry Dash servers refused to delete the message! Try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince()} ago.`) + if (body != 1) return res.status(400).send(`The Geometry Dash servers refused to delete the message! Try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince(req.id)} ago.`) else res.status(200).send(`${deleted == 1 ? "1 message" : `${deleted} messages`} deleted!`) - app.trackSuccess() + app.trackSuccess(req.id) }) } \ No newline at end of file diff --git a/api/messages/fetchMessage.js b/api/messages/fetchMessage.js index 9dbfee4..513747e 100644 --- a/api/messages/fetchMessage.js +++ b/api/messages/fetchMessage.js @@ -1,7 +1,3 @@ -const request = require('request') -const XOR = require("../../classes/XOR"); -const xor = new XOR(); - module.exports = async (app, req, res, api) => { if (!req.body.accountID) return res.status(400).send("No account ID provided!") @@ -9,14 +5,14 @@ module.exports = async (app, req, res, api) => { let params = req.gdParams({ accountID: req.body.accountID, - gjp: xor.encrypt(req.body.password, 37526), + gjp: app.xor.encrypt(req.body.password, 37526), messageID: req.params.id, }) - request.post(app.endpoint + 'downloadGJMessage20.php', params, async function (err, resp, body) { + req.gdRequest('downloadGJMessage20', params, function (err, resp, body) { - if (err || body == '-1' || !body) return res.status(400).send(`Error fetching message! Try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince()} ago.`) - else app.trackSuccess() + if (err || body == -1 || !body) return res.status(400).send(`Error fetching message! Try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince(req.id)} ago.`) + else app.trackSuccess(req.id) let x = app.parseResponse(body) let msg = {} @@ -25,8 +21,8 @@ module.exports = async (app, req, res, api) => { msg.accountID = x[2] msg.author = x[6] msg.subject = Buffer.from(x[4], "base64").toString().replace(/^Re: ☆/, "Re: ") - msg.content = xor.decrypt(x[5], 14251) - msg.date = x[7] + app.config.timestampSuffix + msg.content = app.xor.decrypt(x[5], 14251) + msg.date = x[7] + (req.timestampSuffix || "") if (msg.subject.endsWith("☆") || msg.subject.startsWith("☆")) { if (msg.subject.endsWith("☆")) msg.subject = msg.subject.slice(0, -1) else msg.subject = msg.subject.slice(1) diff --git a/api/messages/getMessages.js b/api/messages/getMessages.js index fffe9bd..42dd357 100644 --- a/api/messages/getMessages.js +++ b/api/messages/getMessages.js @@ -1,7 +1,3 @@ -const request = require('request') -const XOR = require('../../classes/XOR.js'); -const xor = new XOR(); - module.exports = async (app, req, res, api) => { if (req.body.count) return app.run.countMessages(app, req, res) @@ -10,15 +6,15 @@ module.exports = async (app, req, res, api) => { let params = req.gdParams({ accountID: req.body.accountID, - gjp: xor.encrypt(req.body.password, 37526), + gjp: app.xor.encrypt(req.body.password, 37526), page: req.body.page || 0, getSent: req.query.sent ? 1 : 0 }) - request.post(app.endpoint + 'getGJMessages20.php', params, async function (err, resp, body) { + req.gdRequest('getGJMessages20', params, function (err, resp, body) { - if (err || body == '-1' || body == '-2' || !body) return res.status(400).send(`Error fetching messages! Messages get blocked a lot so try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince()} ago.`) - else app.trackSuccess() + if (err || body == -1 || body == -2 || !body) return res.status(400).send(`Error fetching messages! Messages get blocked a lot so try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince(req.id)} ago.`) + else app.trackSuccess(req.id) let messages = body.split("|").map(msg => app.parseResponse(msg)) let messageArray = [] @@ -30,7 +26,7 @@ module.exports = async (app, req, res, api) => { msg.accountID = x[2] msg.author = x[6] msg.subject = Buffer.from(x[4], "base64").toString().replace(/^Re: ☆/, "Re: ") - msg.date = x[7] + app.config.timestampSuffix + msg.date = x[7] + (req.timestampSuffix || "") msg.unread = x[8] != "1" if (msg.subject.endsWith("☆") || msg.subject.startsWith("☆")) { if (msg.subject.endsWith("☆")) msg.subject = msg.subject.slice(0, -1) diff --git a/api/messages/sendMessage.js b/api/messages/sendMessage.js index 27c6ec1..49eb4ec 100644 --- a/api/messages/sendMessage.js +++ b/api/messages/sendMessage.js @@ -1,7 +1,3 @@ -const request = require('request') -const XOR = require('../../classes/XOR.js'); -const xor = new XOR(); - module.exports = async (app, req, res, api) => { if (!req.body.targetID) return res.status(400).send("No target ID provided!") @@ -10,19 +6,19 @@ module.exports = async (app, req, res, api) => { if (!req.body.password) return res.status(400).send("No password provided!") let subject = Buffer.from(req.body.subject ? (req.body.color ? "☆" : "") + (req.body.subject.slice(0, 50)) : (req.body.color ? "☆" : "") + "No subject").toString('base64').replace(/\//g, '_').replace(/\+/g, "-") - let body = xor.encrypt(req.body.message.slice(0, 300), 14251) + let body = app.xor.encrypt(req.body.message.slice(0, 300), 14251) let params = req.gdParams({ accountID: req.body.accountID, - gjp: xor.encrypt(req.body.password, 37526), + gjp: app.xor.encrypt(req.body.password, 37526), toAccountID: req.body.targetID, subject, body, }) - request.post(app.endpoint + 'uploadGJMessage20.php', params, async function (err, resp, body) { - if (body != 1) return res.status(400).send(`The Geometry Dash servers refused to send the message! Try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince()} ago.`) + req.gdRequest('uploadGJMessage20', params, function (err, resp, body) { + if (body != 1) return res.status(400).send(`The Geometry Dash servers refused to send the message! Try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince(req.id)} ago.`) else res.status(200).send('Message sent!') - app.trackSuccess() + app.trackSuccess(req.id) }) } \ No newline at end of file diff --git a/api/post/like.js b/api/post/like.js index fe37105..75e3131 100644 --- a/api/post/like.js +++ b/api/post/like.js @@ -1,6 +1,3 @@ -const request = require('request') -const XOR = require('../../classes/XOR.js'); -const xor = new XOR(); const crypto = require('crypto') function sha1(data) { return crypto.createHash("sha1").update(data, "binary").digest("hex"); } @@ -20,7 +17,7 @@ module.exports = async (app, req, res) => { } params.itemID = req.body.ID.toString() - params.gjp = xor.encrypt(req.body.password, 37526) + params.gjp = app.xor.encrypt(req.body.password, 37526) params.accountID = req.body.accountID.toString() params.like = req.body.like.toString() params.special = req.body.extraID.toString() @@ -28,14 +25,14 @@ module.exports = async (app, req, res) => { let chk = params.special + params.itemID + params.like + params.type + params.rs + params.accountID + params.udid + params.uuid + "ysg6pUrtjn0J" chk = sha1(chk) - chk = xor.encrypt(chk, 58281) + chk = app.xor.encrypt(chk, 58281) params.chk = chk - request.post(app.endpoint + 'likeGJItem211.php', req.gdParams(params), function (err, resp, body) { + req.gdRequest('likeGJItem211', 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") - if (!body || body == "-1") return res.status(400).send(`The Geometry Dash servers rejected your vote! Try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince()} ago.`) - else app.trackSuccess() + if (!body || body == -1) return res.status(400).send(`The Geometry Dash servers rejected your vote! Try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince(req.id)} ago.`) + else app.trackSuccess(req.id) res.status(200).send((params.like == 1 ? 'Successfully liked!' : 'Successfully disliked!') + " (this will only take effect if this is your first time doing so)") }) } \ No newline at end of file diff --git a/api/post/postComment.js b/api/post/postComment.js index cdda43e..863177e 100644 --- a/api/post/postComment.js +++ b/api/post/postComment.js @@ -1,6 +1,3 @@ -const request = require('request') -const XOR = require('../../classes/XOR.js'); -const xor = new XOR(); const crypto = require('crypto') function sha1(data) { return crypto.createHash("sha1").update(data, "binary").digest("hex"); } @@ -27,7 +24,7 @@ module.exports = async (app, req, res) => { let params = { percent: 0 } params.comment = Buffer.from(req.body.comment + (req.body.color ? "☆" : "")).toString('base64').replace(/\//g, '_').replace(/\+/g, "-") - params.gjp = xor.encrypt(req.body.password, 37526) + params.gjp = app.xor.encrypt(req.body.password, 37526) params.levelID = req.body.levelID.toString() params.accountID = req.body.accountID.toString() params.userName = req.body.username @@ -37,19 +34,19 @@ module.exports = async (app, req, res) => { let chk = params.userName + params.comment + params.levelID + params.percent + "0xPT6iUrtws0J" chk = sha1(chk) - chk = xor.encrypt(chk, 29481) + chk = app.xor.encrypt(chk, 29481) params.chk = chk - request.post(app.endpoint + 'uploadGJComment21.php', req.gdParams(params), function (err, resp, body) { + req.gdRequest('uploadGJComment21', 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") - if (!body || body == "-1") return res.status(400).send(`The Geometry Dash servers rejected your comment! Try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince()} ago.`) + if (!body || body == -1) return res.status(400).send(`The Geometry Dash servers rejected your comment! Try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince(req.id)} ago.`) if (body.startsWith("temp")) { let banStuff = body.split("_") return res.status(400).send(`You have been banned from commenting for ${(parseInt(banStuff[1]) / 86400).toFixed(0)} days. Reason: ${banStuff[2] || "None"}`) } res.status(200).send(`Comment posted to level ${params.levelID} with ID ${body}`) - app.trackSuccess() + app.trackSuccess(req.id) rateLimit[req.body.username] = Date.now(); setTimeout(() => {delete rateLimit[req.body.username]; }, cooldown); }) diff --git a/api/post/postProfileComment.js b/api/post/postProfileComment.js index b478626..6acdbf1 100644 --- a/api/post/postProfileComment.js +++ b/api/post/postProfileComment.js @@ -1,6 +1,3 @@ -const request = require('request') -const XOR = require('../../classes/XOR.js'); -const xor = new XOR(); const crypto = require('crypto') function sha1(data) { return crypto.createHash("sha1").update(data, "binary").digest("hex"); } @@ -16,23 +13,23 @@ module.exports = async (app, req, res) => { let params = { cType: '1' } params.comment = Buffer.from(req.body.comment.slice(0, 190) + (req.body.color ? "☆" : "")).toString('base64').replace(/\//g, '_').replace(/\+/g, "-") - params.gjp = xor.encrypt(req.body.password, 37526) + params.gjp = app.xor.encrypt(req.body.password, 37526) params.accountID = req.body.accountID.toString() params.userName = req.body.username let chk = params.userName + params.comment + "1xPT6iUrtws0J" chk = sha1(chk) - chk = xor.encrypt(chk, 29481) + chk = app.xor.encrypt(chk, 29481) params.chk = chk - request.post(app.endpoint + 'uploadGJAccComment20.php', req.gdParams(params), function (err, resp, body) { + req.gdRequest('uploadGJAccComment20', 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") - else if (!body || body == "-1") return res.status(400).send(`The Geometry Dash servers rejected your profile post! Try again later, or make sure your username and password are entered correctly. Try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince()} ago.`) + else if (!body || body == -1) return res.status(400).send(`The Geometry Dash servers rejected your profile post! Try again later, or make sure your username and password are entered correctly. Try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince(req.id)} ago.`) if (body.startsWith("temp")) { let banStuff = body.split("_") return res.status(400).send(`You have been banned from commenting for ${(parseInt(banStuff[1]) / 86400).toFixed(0)} days. Reason: ${banStuff[2] || "None"}`) } - else app.trackSuccess() + else app.trackSuccess(req.id) res.status(200).send(`Comment posted to ${params.userName} with ID ${body}`) }) } \ No newline at end of file diff --git a/api/profile.js b/api/profile.js index 6ebae13..cf8ad52 100644 --- a/api/profile.js +++ b/api/profile.js @@ -1,21 +1,20 @@ -const request = require('request') const fs = require('fs') module.exports = async (app, req, res, api, getLevels) => { - if (app.offline) return res.send("-1") + if (req.offline) return res.send("-1") let username = getLevels || req.params.id let accountMode = !req.query.hasOwnProperty("player") && Number(req.params.id) - let foundID = app.accountCache[app.GDPSName + username.toLowerCase()] + let foundID = app.accountCache[req.id][username.toLowerCase()] let skipRequest = accountMode || foundID let searchResult; // if you're searching by account id, an intentional error is caused to skip the first request to the gd servers. see i pulled a sneaky on ya. (fuck callbacks man) - request.post(skipRequest ? "" : app.endpoint + 'getGJUsers20.php', skipRequest ? {} : req.gdParams({ str: username, page: 0 }), function (err1, res1, b1) { + req.gdRequest(skipRequest ? "" : 'getGJUsers20', skipRequest ? {} : { str: username, page: 0 }, function (err1, res1, b1) { if (foundID) searchResult = foundID[0] - else if (accountMode || err1 || b1 == '-1' || b1.startsWith(" app.parseResponse(x)) searchResult = userResults.find(x => x[1].toLowerCase() == username.toLowerCase() || x[2] == username) || "" @@ -27,17 +26,17 @@ module.exports = async (app, req, res, api, getLevels) => { return app.run.search(app, req, res) } - request.post(app.endpoint + 'getGJUserInfo20.php', req.gdParams({ targetAccountID: searchResult }), function (err2, res2, body) { + req.gdRequest('getGJUserInfo20', { targetAccountID: searchResult }, function (err2, res2, body) { let account = app.parseResponse(body || "") - let dumbGDPSError = app.isGDPS && !account[16] || account[1].toLowerCase() == "undefined" + let dumbGDPSError = req.isGDPS && (!account[16] || account[1].toLowerCase() == "undefined") if (err2 || body == '-1' || !body || dumbGDPSError) { if (!api) return res.redirect('/search/' + req.params.id) else return res.send("-1") } - if (!foundID && app.config.cacheAccountIDs) app.accountCache[app.GDPSName + username.toLowerCase()] = [account[16], account[2]] + if (!foundID && app.config.cacheAccountIDs) app.accountCache[req.id][username.toLowerCase()] = [account[16], account[2], account[1]] let userData = { username: account[1] || "[MISSINGNO.]", diff --git a/api/search.js b/api/search.js index 0178da3..89fb305 100644 --- a/api/search.js +++ b/api/search.js @@ -5,11 +5,11 @@ let demonList = {list: [], lastUpdated: 0} module.exports = async (app, req, res) => { - if (app.offline) return res.send("-1") + if (req.offline) return res.send("-1") let demonMode = req.query.hasOwnProperty("demonlist") || req.query.hasOwnProperty("demonList") || req.query.type == "demonlist" || req.query.type == "demonList" if (demonMode) { - if (app.isGDPS) return res.send('-1') + if (req.isGDPS) return res.send('-1') if (!demonList.list.length || demonList.lastUpdated + 600000 < Date.now()) { // 10 minute cache return request.get('http://www.pointercrate.com/api/v2/demons/listed/?limit=100', function (err1, resp1, list1) { if (err1) return res.send("-1") @@ -23,7 +23,7 @@ module.exports = async (app, req, res) => { } let amount = 10; - let count = +req.query.count + let count = req.isGDPS ? 10 : +req.query.count if (count && count > 0) { if (count > 500) amount = 500 else amount = count; @@ -74,7 +74,7 @@ module.exports = async (app, req, res) => { } if (req.query.hasOwnProperty("user")) { - let accountCheck = app.accountCache[app.GDPSName + filters.str.toLowerCase()] + let accountCheck = app.accountCache[req.id][filters.str.toLowerCase()] filters.type = 5 if (accountCheck) filters.str = accountCheck[1] else if (!filters.str.match(/^[0-9]*$/)) return app.run.profile(app, req, res, null, req.params.text) @@ -83,7 +83,7 @@ module.exports = async (app, req, res) => { if (req.query.hasOwnProperty("creators")) filters.type = 12 let listSize = 10 - if (demonMode || req.query.gauntlet || ["mappack", "list", "saved"].some(x => req.query.hasOwnProperty(x))) { + if (demonMode || req.query.gauntlet || req.query.type == "saved" || ["mappack", "list", "saved"].some(x => req.query.hasOwnProperty(x))) { filters.type = 10 filters.str = demonMode ? demonList.list : filters.str.split(",") listSize = filters.str.length @@ -93,9 +93,9 @@ module.exports = async (app, req, res) => { if (filters.str == "*") delete filters.str - request.post(app.endpoint + 'getGJLevels21.php', req.gdParams(filters), async function(err, resp, body) { + req.gdRequest('getGJLevels21', req.gdParams(filters), function(err, resp, body) { - if (err || !body || body == '-1' || body.startsWith(" { let levelArray = preRes.map(x => app.parseResponse(x)).filter(x => x[1]) let parsedLevels = [] - levelArray.forEach(async (x, y) => { + levelArray.forEach((x, y) => { - let level = new Level(x) + let level = new Level(x, req.server) let songSearch = songs.find(y => y['~1'] == x[35]) || [] level.author = authorList[x[6]] ? authorList[x[6]][0] : "-"; @@ -134,6 +134,13 @@ module.exports = async (app, req, res) => { level.songID = "Level " + [parseInt(x[12]) + 1] } + if (req.onePointNine) { + level.orbs = 0 + level.diamonds = 0 + } + + if (level.author != "-" && app.config.cacheAccountIDs) app.accountCache[req.id][level.author.toLowerCase()] = [level.accountID, level.authorID, level.author] + //this is broken if you're not on page 0, blame robtop if (filters.page == 0 && y == 0) { let pages = splitBody[3].split(":"); diff --git a/api/song.js b/api/song.js index d103af2..49429f4 100644 --- a/api/song.js +++ b/api/song.js @@ -4,15 +4,15 @@ module.exports = async (app, req, res) => { let info = {error: true, exists: false, artist: { name: "", scouted: false, whitelisted: false }, song: { name: "", externalUse: false, allowed: false } } - if (app.offline) return res.send(info) + if (req.offline) return res.send(info) let songID = req.params.song - request.post('http://boomlings.com/database/testSong.php?songID=' + songID, req.gdParams(), async function(err, resp, body) { - if (err || !body || body == '-1' || body.startsWith("/) info.artist.name = artistInfo[0].split(": ")[1] diff --git a/assets/basement.png b/assets/basement.png new file mode 100644 index 0000000..9d8b7b4 Binary files /dev/null and b/assets/basement.png differ diff --git a/assets/css/browser.css b/assets/css/browser.css index a8c6442..4808283 100644 --- a/assets/css/browser.css +++ b/assets/css/browser.css @@ -24,6 +24,10 @@ body { background-image: linear-gradient(#4B0062, #22002D) !important; } +.purpleBG { + background-image: linear-gradient(#6E00FD, #330074) !important; +} + img, .noSelect { user-select: none; } @@ -197,7 +201,7 @@ input[type=text], input[type=password], input[type=number] { font-size: 5vh; } -input[type=checkbox] { +input[type=checkbox], .changeDaWorld { display: none; } @@ -539,7 +543,7 @@ input::-webkit-inner-spin-button { } .mappack h3, .gauntlet h3 { - font-size: 40px + font-size: 35px } .mappack { @@ -805,10 +809,6 @@ input::-webkit-inner-spin-button { border: 0.6vh solid rgba(0, 0, 0, 0.5); } -/* .gdMessage:active { - box-shadow: inset 0px 0px 400px 400px rgba(0, 0, 0, .1); -} */ - .messageInput { font-size: 3.5vh; text-align: left; @@ -1148,6 +1148,20 @@ input::-webkit-inner-spin-button { transform: scale(1.04); } +.gdpslogo { + margin-bottom: 0%; + border-radius: 10%; + filter: drop-shadow(0.5vh 0.5vh 0.15vh rgba(0, 0, 0, 0.6)) +} + +.menuDisabled, .downloadDisabled { + filter: opacity(50%); +} + +.downloadDisabled { + text-decoration: line-through; +} + .hidey { opacity: 0%; pointer-events: none; diff --git a/assets/gdps/19gdps_icon.png b/assets/gdps/19gdps_icon.png new file mode 100644 index 0000000..40ee553 Binary files /dev/null and b/assets/gdps/19gdps_icon.png differ diff --git a/assets/gdps/19gdps_logo.png b/assets/gdps/19gdps_logo.png new file mode 100644 index 0000000..8ea8d49 Binary files /dev/null and b/assets/gdps/19gdps_logo.png differ diff --git a/assets/gdps/22unlocked_icon.png b/assets/gdps/22unlocked_icon.png new file mode 100644 index 0000000..b3e11f2 Binary files /dev/null and b/assets/gdps/22unlocked_icon.png differ diff --git a/assets/gdps/22unlocked_logo.png b/assets/gdps/22unlocked_logo.png new file mode 100644 index 0000000..1361346 Binary files /dev/null and b/assets/gdps/22unlocked_logo.png differ diff --git a/assets/gdps/gd_icon.png b/assets/gdps/gd_icon.png new file mode 100644 index 0000000..7299373 Binary files /dev/null and b/assets/gdps/gd_icon.png differ diff --git a/assets/gdps/gd_logo.png b/assets/gdps/gd_logo.png new file mode 100644 index 0000000..c3f9b12 Binary files /dev/null and b/assets/gdps/gd_logo.png differ diff --git a/assets/gdps/wgdps_icon.png b/assets/gdps/wgdps_icon.png new file mode 100644 index 0000000..9ac6702 Binary files /dev/null and b/assets/gdps/wgdps_icon.png differ diff --git a/assets/gdps/wgdps_logo.png b/assets/gdps/wgdps_logo.png new file mode 100644 index 0000000..49c71fd Binary files /dev/null and b/assets/gdps/wgdps_logo.png differ diff --git a/assets/gdps/xgdps_icon.png b/assets/gdps/xgdps_icon.png new file mode 100644 index 0000000..ac66cb4 Binary files /dev/null and b/assets/gdps/xgdps_icon.png differ diff --git a/assets/gdps/xgdps_logo.png b/assets/gdps/xgdps_logo.png new file mode 100644 index 0000000..507739f Binary files /dev/null and b/assets/gdps/xgdps_logo.png differ diff --git a/assets/lock.png b/assets/lock.png new file mode 100644 index 0000000..645e550 Binary files /dev/null and b/assets/lock.png differ diff --git a/assets/tab-top-off.png b/assets/tab-top-off.png index d677896..ecc2690 100644 Binary files a/assets/tab-top-off.png and b/assets/tab-top-off.png differ diff --git a/assets/tab-top-on.png b/assets/tab-top-on.png index 731c528..eaa1a91 100644 Binary files a/assets/tab-top-on.png and b/assets/tab-top-on.png differ diff --git a/assets/tab-weekly-off.png b/assets/tab-weekly-off.png new file mode 100644 index 0000000..074f324 Binary files /dev/null and b/assets/tab-weekly-off.png differ diff --git a/assets/tab-weekly-on.png b/assets/tab-weekly-on.png new file mode 100644 index 0000000..780d63b Binary files /dev/null and b/assets/tab-weekly-on.png differ diff --git a/assets/unlock.png b/assets/unlock.png new file mode 100644 index 0000000..dadee08 Binary files /dev/null and b/assets/unlock.png differ diff --git a/classes/Level.js b/classes/Level.js index cf8a2c7..7412e96 100644 --- a/classes/Level.js +++ b/classes/Level.js @@ -1,16 +1,15 @@ const XOR = require(__dirname + "/../classes/XOR"); -const config = require(__dirname + "/../settings"); let orbs = [0, 0, 50, 75, 125, 175, 225, 275, 350, 425, 500] let length = ['Tiny', 'Short', 'Medium', 'Long', 'XL'] let difficulty = { 0: 'Unrated', 10: 'Easy', 20: 'Normal', 30: 'Hard', 40: 'Harder', 50: 'Insane' } class Level { - constructor(levelInfo, download, author = []) { + constructor(levelInfo, server, download, author = []) { if (!levelInfo[2]) return; this.name = levelInfo[2]; this.id = levelInfo[1]; - this.description = (config.base64descriptions ? Buffer.from(levelInfo[3], "base64").toString() : levelInfo[3]) || "(No description provided)"; + this.description = Buffer.from(levelInfo[3], "base64").toString() || "(No description provided)"; this.author = author[1] || "-" this.authorID = levelInfo[6] this.accountID = author[2] || 0 @@ -20,13 +19,13 @@ class Level { this.disliked = levelInfo[14] < 0 this.length = length[levelInfo[15]] || "XL" this.stars = +levelInfo[18] - this.orbs = orbs[levelInfo[18]] + this.orbs = orbs[levelInfo[18]] || 0 this.diamonds = levelInfo[18] < 2 ? 0 : parseInt(levelInfo[18]) + 2 this.featured = levelInfo[19] > 0 this.epic = levelInfo[42] > 0 this.gameVersion = levelInfo[13] > 17 ? (levelInfo[13] / 10).toFixed(1) : levelInfo[13] == 11 ? "1.8" : levelInfo[13] == 10 ? "1.7" : "Pre-1.7" - if (levelInfo[28]) this.uploaded = levelInfo[28] + config.timestampSuffix - if (levelInfo[29]) this.updated = levelInfo[29] + config.timestampSuffix + if (levelInfo[28]) this.uploaded = levelInfo[28] + (server.timestampSuffix || "") + if (levelInfo[29]) this.updated = levelInfo[29] + (server.timestampSuffix || "") if (download) { this.editorTime = +levelInfo[46] || 0; this.totalEditorTime = +levelInfo[47] || 0 } if (levelInfo[27]) this.password = levelInfo[27]; this.version = +levelInfo[5]; @@ -53,7 +52,7 @@ class Level { if (this.password && this.password != 0) { let xor = new XOR(); - let pass = config.xorPasswords ? xor.decrypt(this.password, 26364) : this.password; + let pass = xor.decrypt(this.password, 26364); if (pass.length > 1) this.password = pass.slice(1); else this.password = pass; } diff --git a/html/achievements.html b/html/achievements.html index 796d078..6f975c1 100644 --- a/html/achievements.html +++ b/html/achievements.html @@ -78,7 +78,7 @@ - + - + - + - + - + - + \ No newline at end of file diff --git a/html/gauntlets.html b/html/gauntlets.html index def23c4..0ea8849 100644 --- a/html/gauntlets.html +++ b/html/gauntlets.html @@ -21,6 +21,8 @@ + +

@@ -30,12 +32,13 @@ - + + + + + + + + + +
+ + + + + +
+ +
+

GD Private Servers

+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/html/home.html b/html/home.html index 35f4dd0..3420fdb 100644 --- a/html/home.html +++ b/html/home.html @@ -28,32 +28,42 @@ -
- +
+
-
+ + + + + @@ -158,7 +158,7 @@ - + - + - + - + - + - + - +