HE WILL NEVER ADD PRIVATE SERVERS TO GDBROWSER

holy shit this is probably my biggest update yet
This commit is contained in:
GDColon 2021-01-18 21:54:18 -05:00
parent 46e03b1932
commit c662f26698
63 changed files with 814 additions and 374 deletions

171
README.md
View file

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

View file

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

View file

@ -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("<!")) {
if (err || !body || body == '-1' || body.startsWith("<")) {
if (!api && levelID < 0) return res.redirect(`/?daily=${levelID * -1}`)
if (!api) return res.redirect('search/' + req.params.id)
else return res.send("-1")
@ -25,21 +25,24 @@ module.exports = async (app, req, res, api, ID, analyze) => {
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)

View file

@ -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("<!")) return res.send("-1")
req.gdRequest('getGJGauntlets21', {}, function (err, resp, body) {
if (err || !body || body == '-1' || body.startsWith("<")) return res.send("-1")
let gauntlets = body.split('#')[0].split('|').map(x => 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)
})
}
}

View file

@ -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("<!")) ? username : app.parseResponse(body1)[16];
let result = foundID ? foundID[0] : (accountMode || err1 || !body1 || body1 == "-1" || body1.startsWith("<")) ? username : app.parseResponse(body1)[16];
request.post(endpoint + 'getGJUserInfo20.php', req.gdParams({ targetAccountID: result }, !forceGD), function (err2, res2, body2) {
req.gdRequest('getGJUserInfo20', req.gdParams({ targetAccountID: result, forceGD }, !forceGD), function (err2, res2, body2) {
if (err2 || !body2 || body2 == '-1' || body2.startsWith("<!")) return buildIcon();
if (err2 || !body2 || body2 == '-1' || body2.startsWith("<")) return buildIcon();
let iconData = app.parseResponse(body2)
if (!foundID && !forceGD && app.config.cacheAccountIDs) app.accountCache[app.GDPSName + username.toLowerCase()] = [iconData[16], iconData[2]]
if (!foundID && !forceGD && app.config.cacheAccountIDs) app.accountCache[req.id][username.toLowerCase()] = [iconData[16], iconData[2], iconData[1]]
return buildIcon(iconData, userCode);
})

View file

@ -7,7 +7,7 @@ let caches = [{"stars": null, "coins": null, "demons": null}, {"stars": null, "c
module.exports = async (app, req, res, post) => {
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")

View file

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

View file

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

View file

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

View file

@ -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("<!") || body.startsWith("##")) {
if (err || !body || body == '-1' || body.startsWith("<") || body.startsWith("##")) {
if (!api) return res.redirect('search/' + req.params.id)
else return res.send("-1")
}
@ -35,7 +33,7 @@ module.exports = async (app, req, res, api, analyze) => {
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)

View file

@ -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("<!")) return res.send("-1")
if (err || !body || body == '-1' || body.startsWith("<")) return res.send("-1")
let packs = body.split('#')[0].split('|').map(x => 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()
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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("<!") || !b1) searchResult = req.params.id
else if (!app.isGDPS) searchResult = app.parseResponse(b1.split("|")[0])[16]
else if (accountMode || err1 || b1 == '-1' || b1.startsWith("<") || !b1) searchResult = req.params.id
else if (!req.isGDPS) searchResult = app.parseResponse(b1.split("|")[0])[16]
else { // GDPS's return multiple users, GD no longer does this
let userResults = b1.split("|").map(x => 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.]",

View file

@ -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("<!")) return res.send("-1")
if (err || !body || body == '-1' || body.startsWith("<")) return res.send("-1")
let splitBody = body.split('#')
let preRes = splitBody[0].split('|')
let authorList = {}
@ -112,9 +112,9 @@ module.exports = async (app, req, res) => {
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(":");

View file

@ -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("<!")) return res.send(info)
request.post('http://boomlings.com/database/testSong.php?songID=' + songID, req.gdParams(), function(err, resp, body) {
if (err || !body || body == '-1' || body.startsWith("<")) return res.send(info)
request.post(app.endpoint + 'getGJSongInfo.php', req.gdParams({songID: songID}), async function(err2, resp, songAllowed) {
if (err2 || !songAllowed || songAllowed < 0 || body.startsWith("<!")) return res.send(info)
req.gdRequest('getGJSongInfo', {songID: songID}, function(err2, resp, songAllowed) {
if (err2 || !songAllowed || songAllowed < 0 || body.startsWith("<")) return res.send(info)
let artistInfo = body.split(/<\/?br>/)
info.artist.name = artistInfo[0].split(": ")[1]

BIN
assets/basement.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View file

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

BIN
assets/gdps/19gdps_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
assets/gdps/19gdps_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
assets/gdps/gd_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
assets/gdps/gd_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
assets/gdps/wgdps_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

BIN
assets/gdps/wgdps_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
assets/gdps/xgdps_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

BIN
assets/gdps/xgdps_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
assets/lock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 19 KiB

BIN
assets/tab-weekly-off.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
assets/tab-weekly-on.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
assets/unlock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View file

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

View file

@ -78,7 +78,7 @@
</body>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script async type="text/javascript" src="../sizecheck.js"></script>
<script type="text/javascript" src="../sizecheck.js?"></script>
<script type="text/javascript" src="../dragscroll.js"></script>
<script>

View file

@ -86,7 +86,7 @@
</div>
</body>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script async type="text/javascript" src="../sizecheck.js"></script>
<script type="text/javascript" src="../sizecheck.js?"></script>
<script>
function clean(text) {return text.toString().replace(/&/g, "&#38;").replace(/</g, "&#60;").replace(/>/g, "&#62;").replace(/=/g, "&#61;").replace(/"/g, "&#34;").replace(/'/g, "&#39;")}

View file

@ -41,7 +41,7 @@
</body>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery.lazy/1.7.9/jquery.lazy.min.js"></script>
<script type="text/javascript" src="../sizecheck.js"></script>
<script type="text/javascript" src="../sizecheck.js?"></script>
<script type="text/javascript" src="../dragscroll.js"></script>
<script>

View file

@ -128,7 +128,7 @@
</body>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script async type="text/javascript" src="../sizecheck.js"></script>
<script type="text/javascript" src="../sizecheck.js?"></script>
<script type="text/javascript" src="../dragscroll.js"></script>
<script>
@ -158,7 +158,12 @@ function clean(text) {return text.replace(/&/g, "&#38;").replace(/</g, "&#60;").
$('#compactMode').attr('src', `../assets/compact-${compact ? "on" : "off"}.png`)
fetch(target).then(res => res.json()).then(lvl => {
Fetch(target).then(lvl => {
if (gdps) {
$('#leaveComment').hide()
$('#postComment').remove()
}
if (history) {
@ -445,6 +450,7 @@ let likeCount, likeImg;
let likedComments;
$(document).on('click', '.likeComment', function(cmnt) {
if (gdps) return
commentID = $(this).attr('commentID')
likedComments = localStorage.likedComments ? JSON.parse(localStorage.likedComments) : []

View file

@ -70,7 +70,7 @@
</body>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script async type="text/javascript" src="../sizecheck.js"></script>
<script type="text/javascript" src="../sizecheck.js?"></script>
<script type="text/javascript" src="../dragscroll.js"></script>
<script>

View file

@ -80,7 +80,7 @@
<br>
<img src="../assets/btn-awarded.png" height="27%" class="valign gdButton levelSearch" search="awarded">
<img src="../assets/btn-featured.png" height="27%" class="valign gdButton levelSearch" search="featured" style="margin: 0% 2%">
<img src="../assets/btn-followed.png" height="27%" class="valign gdButton levelSearch" search="followed">
<img src="../assets/btn-followed.png" height="27%" id="followedSearch" class="valign gdButton levelSearch" search="followed">
</div>
<div class="center">
@ -95,10 +95,10 @@
<div class="diffDiv gdButton" diff=4><img src="../assets/difficulties/harder.png"><h3 class="mini">Harder</h3></div>
<div class="diffDiv gdButton" diff=5><img src="../assets/difficulties/insane.png"><h3 class="mini">Insane</h3></div>
<!-- <div class="diffDiv gdButton" id="demonBtn" diff=-2><img src="../assets/difficulties/demon.png" style="width: 85%"><h3 class="mini">Demon</h3></div> -->
<div class="diffDiv gdButton" id="demonBtn" diff=-2><img src="../assets/difficulties/demon.png" style="width: 85%"><h3 class="mini">Demon</h3></div>
<div class="diffDiv gdButton" style="filter: brightness(100%)" id="demonBtn" diff=-2><img class="darkDiff" src="../assets/difficulties/demon.png" style="width: 85%"><h3 class="darkDiff mini">Demon</h3>
<img src="../assets/exclamation.png" style="position: absolute; width: 19%; left: 86%; bottom: 68%"></div>
<!-- <div class="diffDiv gdButton" style="filter: brightness(100%)" id="demonBtn" diff=-2><img class="darkDiff" src="../assets/difficulties/demon.png" style="width: 85%"><h3 class="darkDiff mini">Demon</h3> -->
<!-- <img src="../assets/exclamation.png" style="position: absolute; width: 19%; left: 86%; bottom: 68%"></div> -->
<div class="diffDiv gdButton" diff=-3><img src="../assets/difficulties/auto.png"><h3 class="mini">Auto</h3></div>
</div>
@ -110,7 +110,7 @@
<div class="diffDiv gdButton demonDiff" diff=4><img src="../assets/difficulties/demon-insane.png" style="width: 95%"><h3 class="mini center smallTextWoo">Insane</h3></div>
<div class="diffDiv gdButton demonDiff" diff=5><img src="../assets/difficulties/demon-extreme.png" style="width: 100%"><h3 class="mini center smallTextWoo">Extreme</h3></div>
<div class="diffDiv gdButton goBack" diff=-2 style="margin-left: 2.3%; filter: none"><img src="../assets/difficulties/demon.png" style="width: 90%"><h3 class="mini">Demon</h3></div>
<a href="./search/*?type=demonlist"><div class="gdButton diffDiv" style="filter: none"><img src="../assets/trophy2.png" style="width: 95%"><h3 class="yellow mini center">List</h3></div></a>
<a id="demonList" href="./search/*?type=demonlist"><div class="gdButton diffDiv" style="filter: none"><img src="../assets/trophy2.png" style="width: 95%"><h3 class="yellow mini center">List</h3></div></a>
</div>
<div class="transparentBox center" style="width: 115vh; height: 6%; margin: 0.5% auto 1% auto; padding-top: 1%; padding-bottom: 0.5%;">
@ -135,7 +135,7 @@
</body>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script async type="text/javascript" src="../sizecheck.js"></script>
<script type="text/javascript" src="../sizecheck.js?"></script>
<script>
let filters = []
@ -263,4 +263,14 @@ $('#listLevels, #listName').on('input blur', function (event) {
})
// some gdps magic
Fetch(`../api/gdps`).then(res => {
if (onePointNine) {
$('#userSearch').hide()
$('#followedSearch').addClass('menuDisabled')
$('#levelName').css('width', '76%')
}
if (gdps) $('#demonList').hide()
})
</script>

View file

@ -21,6 +21,8 @@
<img src="../assets/gauntlets.png" width="50%">
</div>
<img id="loading" style="margin-top: 1%" class="spin noSelect" src="../assets/loading.png" height="12%">
<div id="gauntletList">
<br>
</div>
@ -30,12 +32,13 @@
</div>
</body>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script async type="text/javascript" src="../sizecheck.js"></script>
<script type="text/javascript" src="../sizecheck.js?"></script>
<script>
let gauntletNames = ["Fire", "Ice", "Poison", "Shadow", "Lava", "Bonus", "Chaos", "Demon", "Time", "Crystal", "Magic", "Spike", "Monster", "Doom", "Death"]
fetch('../api/gauntlets').then(res => res.json()).then(gauntlets => {
$('#loading').hide()
gauntlets.forEach((x, y) => {
let name = gauntletNames[x.id - 1]
$('#gauntletList').append(`

86
html/gdps.html Normal file
View file

@ -0,0 +1,86 @@
<head>
<title>GD Multiverse Navigation Terminal</title>
<meta charset="utf-8">
<link href="../assets/css/browser.css" type="text/css" rel="stylesheet">
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-135255146-3"></script><script>window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);}gtag('js', new Date());gtag('config', 'UA-135255146-3');</script>
<link rel="icon" href="../assets/unlock.png">
<meta id="meta-title" property="og:title" content="GD Multiverse Navigation Terminal">
<meta id="meta-desc" property="og:description" content="That's uhh... that's just fancy talk for GDPS Browser. Select a popular GD private server and view its levels, creators, packs, leaderboards, and more!">
<meta id="meta-image" name="og:image" itemprop="image" content="https://gdbrowser.com/assets/unlock.png">
</head>
<body class="levelBG vaultBG" onbeforeunload="saveUrl()">
<div id="everything" style="overflow: auto;">
<div class="popup" id="infoDiv">
<div class="fancybox bounce center supercenter" style="width: 90vh">
<h2 class="smaller center" style="font-size: 5.5vh">Add server</h2>
<p class="bigger center" style="line-height: 5vh; margin-top: 1.5vh; margin-bottom: 2.5vh;">
Please contact <span style="color: #FF8000">Colon</span></a> on
<a target="_blank" href="https://twitter.com/TheRealGDColon"><span style="color:aqua; text-decoration: underline">Twitter</span></a> or
<span style="color: #7289DA">Discord</span> if you are interested in adding your <span style="color: yellow">private server</span> to this list.
</p>
<p class="bigger center" style="margin-top: 1vh">
Please note that I am only adding <span style="color: lime">relatively popular</span> servers at this time.
Servers which are <span style="color: lightcoral">inactive</span> or have <span style="color: lightcoral">few levels/members</span> will not be accepted.
</p>
<img src="../assets/ok.png" width=15%; class="gdButton center" onclick="$('.popup').hide()">
</div>
</div>
<div id="searchBox" class="supercenter dragscroll"; style="width: 127vh">
<div style="height: 4.5%"></div>
</div>
<div class="epicbox supercenter gs" style="width: 126vh; height: 80%; pointer-events: none"></div>
<div class="center" style="position:absolute; top: 8%; left: 0%; right: 0%">
<h1 class="pre" id="header">GD Private Servers</h1>
</div>
<div style="position:absolute; top: 2%; left: 1.5%; width: 10%; height: 25%; pointer-events: none">
<img class="gdButton yesClick" id="backButton" src="../assets/back.png" height="30%" onclick="backButton()">
</div>
<div class="supercenter" id="loading" style="height: 10%; top: 47%">
<img class="spin noSelect" src="../assets/loading.png" height="105%">
</div>
<div style="position:absolute; top: 3%; right: 2%; text-align: right; width: 20%;">
<img id="plusButton" class="inline gdButton" src="../assets/plus.png" width="25%" onclick="$('#infoDiv').show()">
</div>
</div>
</body>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script type="text/javascript" src="../sizecheck.js?"></script>
<script type="text/javascript" src="../dragscroll.js"></script>
<script>
let host = window.location.host.split(".").slice(-2).join(".")
Fetch('../api/gdps').then(servers => {
servers.forEach(x => {
if (!x.id) x.id = null
$('#searchBox').append(`<div class="searchresult" style="height: 19%; padding-top: 1.2%">
<h1 class="lessspaced blue" style="color: ${gdps == x.id ? "#00DDFF" : "white"}">${x.name}</h1>
<h2 class="lessSpaced smaller inline gdButton"><a href="${x.authorLink}" target="_blank">By ${x.author}</a></h2>
<div class="center" style="position:absolute; height: 10%; width: 12.5%; left: 3%; transform:translateY(-160%)">
<a href="${x.link}" target="_blank"><img class="gdButton spaced gdpslogo" src="../assets/gdps/${x.id || "gd"}_icon.png" height="130%"></a>
</div>
<div class="center" style="position:absolute; right: 7%; transform:translateY(-150%); height: 10%">
<a href="http://${x.id || ""}${x.id ? "." : ""}${host}"><img style="margin-bottom: 4.5%" class="valign gdButton" src="../assets/view.png" height="105%"></a>
</div>
</div>`)
})
$('#searchBox').append('<div style="height: 4%"></div>')
$('#loading').hide();
})
</script>

View file

@ -28,32 +28,42 @@
<img id="creditsButton" class="gdButton" src="../assets/credits.png" width="60%" onclick="loadCredits()">
</div>
<div style="position:absolute; bottom: 2.5%; right: 1.5%; text-align: right; width: 14%;">
<a href="../achievements"><img class="gdButton" src="../assets/achievements.png" width="40%"></a>
<div style="position:absolute; bottom: 2.5%; right: 1.5%; text-align: right; width: 18%;">
<a href="../gdps"><img class="gdButton" src="../assets/basement.png" width="40%"></a>
</div>
<div style="position:absolute; top: -1.5%; right: 10%; text-align: right; width: 10%;">
<a href="../iconkit"><img class="iconRope" src="../assets/iconrope.png" width="40%"></a>
</div>
<div style="position:absolute; top: -1.7%; left: 5%; text-align: right; width: 10%;">
<div class="menu-achievements" style="position:absolute; top: 5.5%; left: 3%; width: 12%;">
<a href="../achievements"><img class="gdButton" src="../assets/achievements.png" width="40%"></a>
</div>
<div class="menu-messages" style="position:absolute; top: -1.7%; left: 11%; text-align: left; width: 10%;">
<a href="../messages"><img class="iconRope" src="../assets/messagerope.png" width="40%"></a>
</div>
<div id="dl" style="display: none; position:absolute; top: 15%; right: 0.5%; text-align: center; width: 17%">
<h1 class="smaller" style="margin-bottom: 1%">Note</h1>
<p style="font-size: 2.2vh; margin-top: 1.2%"><span style="color: cyan">Level downloading</span> has been <span style="color: red">blocked</span> by RobTop.
<span style="color: yellow">Level analysis, daily levels, and downloading extra info</span> will <span style="color: lime">not work</span> until a resolution is made.</p>
</div>
<div class="supercenter center" id="menuButtons" style="bottom: 5%;">
<table>
<tr>
<td><a href="./search/*?type=saved"><img class="menubutton" src="../assets/category-saved.png"></a></td>
<td><a href="./daily"><img class="menubutton" src="../assets/category-daily.png"></a></td>
<td><a href="./weekly"><img class="menubutton" src="../assets/category-weekly.png"></a></td>
<td><a href="./gauntlets"><img class="menubutton" src="../assets/category-gauntlets.png"></a></td>
<td><a href="./search/*?type=saved"><img class="menubutton menu-saved" src="../assets/category-saved.png"></a></td>
<td><a href="./daily"><img class="menubutton menu-daily" src="../assets/category-daily.png"></a></td>
<td><a href="./weekly"><img class="menubutton menu-weekly" src="../assets/category-weekly.png"></a></td>
<td><a href="./gauntlets"><img class="menubutton menu-gauntlets" src="../assets/category-gauntlets.png"></a></td>
</tr>
<tr>
<td><a href="./leaderboard"><img class="menubutton" src="../assets/category-scores.png"></a></td>
<td><a href="./leaderboard"><img class="menubutton menu-leaderboard" src="../assets/category-scores.png"></a></td>
<!-- <img src="./assets/exclamation.png" style="position: absolute; height: 18%; left: 3.5%; bottom: 23%; pointer-events: none; z-index: 50;"> -->
<td><a href="./search/*?type=hof"><img class="menubutton" src="../assets/category-hof.png"></a></td>
<td><a href="./mappacks"><img class="menubutton" src="../assets/category-packs.png"></a></td>
<td><a href="./search"><img class="menubutton" src="../assets/category-search.png"></a></td>
<td><a href="./search/*?type=hof"><img class="menubutton menu-hof" src="../assets/category-hof.png"></a></td>
<td><a href="./mappacks"><img class="menubutton menu-mappacks" src="../assets/category-packs.png"></a></td>
<td><a href="./search"><img class="menubutton menu-search" src="../assets/category-search.png"></a></td>
</tr>
</table>
@ -82,7 +92,7 @@
</body>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script async type="text/javascript" src="../sizecheck.js"></script>
<script type="text/javascript" src="../sizecheck.js?"></script>
<script>
let page = 1
@ -111,15 +121,15 @@ function loadCredits() {
})
}
fetch(`./api/credits`).then(res => res.json()).then(res => {
Fetch(`./api/credits`).then(res => {
lastPage = res.credits.length + 1
res.credits.forEach((x, y) => {
$('#credits').append(`<div id="credits${y+1}" class="subCredits" style="display: none;">
<div class="brownBox center supercenter" style="width: 80vh; height: 43%; padding-top: 1.5%; padding-bottom: 3.5%;">
<h1>${x.header}</h1><br>
<h2 style="margin-bottom: 1.5%" class="gdButton"><a href="./u/${x.ign || x.name}">${x.name}</h2></a>
<img class="creditsicon" icon="./icon/${x.ign || x.name}" height=30%; style="margin-bottom: 7%"><br>
<h2 style="margin-bottom: 1.5%" class="gdButton"><a href="https://gdbrowser.com/u/${x.ign || x.name}">${x.name}</h2></a>
<img class="creditsicon" icon="./icon/${x.ign || x.name}?forceGD=1" height=30%; style="margin-bottom: 7%"><br>
<a target=_blank href="${x.youtube[0]}"><img src="../assets/${x.youtube[1]}.png" width="11%" class="gdButton"></a>
<a target=_blank href="${x.twitter[0]}"><img src="../assets/${x.twitter[1]}.png" width="11%" class="sideSpace gdButton"></a>
<a target=_blank href="${x.github[0]}"><img src="../assets/${x.github[1]}.png" width="11%" class="sideSpace gdButton"></a>
@ -140,10 +150,10 @@ fetch(`./api/credits`).then(res => res.json()).then(res => {
res.specialThanks.forEach((x, y) => {
n = x.split("/")
$('#specialthanks').append(`<div class="specialThanks">
<h2 class="gdButton smaller"><a href="./u/${n[1] || n[0]}">${n[0]}</h2></a>
<img class="creditsicon" icon="./icon/${n[1] || n[0]}" height=77%><br>
<h2 class="gdButton smaller"><a href="https://gdbrowser.com/u/${n[1] || n[0]}">${n[0]}</h2></a>
<img class="creditsicon" icon="./icon/${n[1] || n[0]}?forceGD=1" height=77%><br>
</div>`)
})
})
$('#credits').append(`<div id="closeCredits" class="center supercenter" style="width: 80vh; height: ${xButtonPos}%; pointer-events: none;">
<img class="closeWindow gdButton" src="../assets/close.png" width="14%" style="position: absolute; top: -24%; left: -7vh; pointer-events: all;" onclick="$('#credits').hide(); page = 1;"></div>`)

View file

@ -15,8 +15,9 @@
<div class="center hidden"><br>
<div class="popup" id="steal">
<div class="brownbox bounce center supercenter" style="height: 350px; width: 700px">
<h1 class="center gold" style="margin-top: 10px">Copy Icon</h1>
<div id="stealBox" class="brownbox bounce center supercenter" style="height: 340px; width: 700px">
<h1 class="center gold" style="margin-top: 10px">Copy Icon</h1>
<p id="copyFrom" class="white" style="font-size: 24px; margin: 10px auto 5px auto"></p>
<input type="text" name="gdbrowser" id="playerName" autocomplete="off" placeholder="Username" maxlength="32" style="height: 58px; width: 90%; text-align: center; margin-top: 25px; margin-bottom: 5px;">
<div id="copyForms"></div>
<img src="../assets/ok.png" height=55px; class="postButton gdButton center" style="margin-top: 30px" id="fetchUser">
@ -173,6 +174,12 @@ fetch('./api/icons').then(res => {
let miniIcon = iconStuff.icons.filter(x => x.startsWith("cube")).length
if (iconStuff.noCopy) $('#getUserIcon').remove()
else if (iconStuff.server) {
$('#copyFrom').html(`Copying from the <span style="color: yellow">${iconStuff.server}</span> servers`)
$('#stealBox').css('height', '385px')
}
function filterIcon(name) { return iconStuff.icons.filter(x => x.startsWith(name)).sort(function (a,b) {return a.replace(/[^0-9]/g, "") - b.replace(/[^0-9]/g, "");})}
function appendIcon(form, formName) {
@ -470,6 +477,7 @@ fetch('./api/icons').then(res => {
fetch('../api/profile/' + user).then(res => res.json())
.then(info => {
if (info == "-1") return
$(`#${formCopy}-${info[formCopy == "cube" ? "icon" : formCopy] || 1}`).trigger('click')
$(`#col1-${info.col1}`).trigger('click')
$(`#col2-${info.col2}`).trigger('click')

View file

@ -5,7 +5,7 @@
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-135255146-3"></script><script>window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);}gtag('js', new Date());gtag('config', 'UA-135255146-3');</script>
<link rel="icon" href="../assets/trophy.png">
<meta id="meta-title" property="og:title" content="Leaderboards">
<meta id="meta-desc" property="og:description" content="View Geometry Dash's leaderboards, plus an accurate and updated list of the top 100 players.">
<meta id="meta-desc" property="og:description" content="View Geometry Dash's leaderboards, plus an accurate and updated list of the top players.">
<meta id="meta-image" name="og:image" itemprop="image" content="https://gdbrowser.com/assets/trophy.png">
</head>
@ -17,6 +17,10 @@
<img src="../assets/tab-top-on.png" class="leaderboardTab" id="topTabOn" style="display: none">
<img src="../assets/tab-top-off.png" class="leaderboardTab leaderboardClick" id="topTabOff">
<!-- for some GDPS'es -->
<img src="../assets/tab-weekly-on.png" class="sideSpaceC leaderboardTab" id="weeklyTabOn" style="display: none">
<img src="../assets/tab-weekly-off.png" class="sideSpaceC leaderboardTab leaderboardClick" id="weeklyTabOff" style="display: none">
<img src="../assets/tab-accurate-on.png" class="sideSpaceC leaderboardTab" id="accurateTabOn">
<img src="../assets/tab-accurate-off.png" class="sideSpaceC leaderboardTab leaderboardClick" id="accurateTabOff" style="display: none">
@ -74,18 +78,25 @@
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery.lazy/1.7.9/jquery.lazy.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery.lazy/1.7.9/jquery.lazy.plugins.min.js"></script>
<script async type="text/javascript" src="../sizecheck.js"></script>
<script type="text/javascript" src="../sizecheck.js?"></script>
<script type="text/javascript" src="../dragscroll.js"></script>
<script>
let sort = "stars"
let useTrophies = false
let modMode = false
let weekly = false
let trophies = [1, 3, 10, 25, 50, 75, 100]
let colors = ["red", "orange", "yellow", "green", "teal", "blue", "pink"]
let boomColors = ["red", "orange", "yellow", "green", "teal", "blue", "pink"]
let top250Text =
`The <g>Top 250<> leaderboard contains the <g>top 250 players<>, sorted by <y>star<> value. However, due to <o>hackers<> flooding the leaderboard, this leaderboard has been <b>frozen<> for well over 2 years and displays <o>very outdated information<>.`
`The <g>Stars<> leaderboard contains the <g>top 250 players<>, sorted by <y>star<> value. However, due to <o>hackers<> flooding the leaderboard, this leaderboard has been <b>frozen<> for well over 2 years and displays <o>very outdated information<>.`
let topGDPSText =
`The <g>Stars<> leaderboard contains the <g>top players<>, sorted by <y>star<> value. It is <o>inaccurate<> in vanilla GD but should be <b>reliable<> on most <y>private servers<>.`
let weeklyText =
`The <g>Weekly<> leaderboard displays the players who have gained the most <y>stars<> in the <b>past week<>. It was officially <o>removed<> in update 2.0, but lives on in some GDPS'es.`
let accurateText =
`The <g>Accurate Leaderboard<> is a highly accurate, hacker-proof leaderboard with <y>proper stats and positioning<> (unlike the regular one). It is managed by <b>XShadowWizardX, Pepper360, Octeract<>, and many many other helpers. Be sure to check out their <a target="_blank" href="https://docs.google.com/spreadsheets/d/10lbPnDYJXhbtlA0ls0cGjjX_osFSG559IDrTbhgPHvc"><span style="color:aqua; text-decoration: underline">interactive leaderboard spreadsheet<></a> or join their <a target="_blank" href="https://discord.gg/Uz7pd4d"><span style="color:aqua; text-decoration: underline">Discord server<></a>.`
@ -110,12 +121,18 @@ function leaderboard(val) {
$('#searchBox').html(`<div style="height: 4.5%"></div>`)
$('#loading').show()
fetch(`../api/leaderboard?count=250&${val}&type=${sort}${modMode ? "&mod=1" : ""}`).then(res => res.json()).then(res => {
Fetch(`../api/leaderboard?count=250&${val}&type=${sort}${modMode ? "&mod=1" : ""}`).then(res => {
if (type == "accurate" && res == "-2") { // for GDPS'es
if (gdps) {
top250Text = topGDPSText
$('#boomling').remove()
}
if (gdps && type == "accurate" && (res == "-2" || res == "-3")) { // for GDPS'es
$('#accurateTabOn').remove()
$('#accurateTabOff').remove()
$('#scoreTabs').css('margin-left', '-29vh')
if (res == "-3") { $('#weeklyTabOff').show(); weekly = true }
else $('#scoreTabs').css('margin-left', '-29vh')
return $('#topTabOff').trigger('click')
}
@ -123,21 +140,22 @@ function leaderboard(val) {
$('.ranking').remove()
if (modMode && sort == "cp") res = res.sort(function(a, b){return b.cp - a.cp});
let wk = type == "weekly"
if (val == type && res != -1 && res != -2 && res.length) res.forEach((x, y) => {
if (val == type && res != -1 && res.length) res.forEach((x, y) => {
$('#searchBox').append(`<div class="searchresult leaderboardSlot">
${x.moderator ? `<img title="${x.moderator == 2 ? "Elder " : ""}Moderator" src="../assets/mod${x.moderator == 2 ? "-elder" : ""}.png" style="height: 30%; cursor: help; padding-right: 1.6%; transform: translateY(0.7vh)">` : ""}
<h2 class="small inline gdButton" style="margin-top: 1.5%${x.moderator == 2 ? "; color: #FF9977;" : ""}"><a href="../u/${x.username}">${x.username}</a></h2>
<h3 class="inline sideSpace${x.stars >= 100000 ? " yellow" : ""}" style="font-size: 4.5vh">${x.stars} <img class="valign" src="../assets/star.png"
<h2 class="small inline gdButton" style="margin-top: 1.5%${x.moderator == 2 ? "; color: #FF9977;" : ""}"><a href="${onePointNine ? `../search/${x.playerID}?user` : `../u/${x.username}`}">${x.username}</a></h2>
<h3 class="inline sideSpace${x.stars >= 100000 ? " yellow" : ""}" style="font-size: 4.5vh">${type == "weekly" ? "+" : ""}${x.stars} <img class="valign" src="../assets/star.png"
style="cursor: help; height: 19%; transform: translate(-25%, -10%);" title="Stars"></h3>
<h3 class="lessSpaced leaderboardStats">
<span${x.diamonds >= 65535 ? ` class='blue'>${type == "accurate" ? "~" : ""}` : ">"}${x.diamonds}</span> <img class="valign" src="../assets/diamond.png" style="cursor: help" title="Diamonds">
<span${x.coins >= 149 ? " class='yellow'" : ""}>${x.coins}</span> <img class="valign" src="../assets/coin.png" style="cursor: help" title="Secret Coins">
<span${x.usercoins >= 10000 ? " class='brightblue'" : ""}>${x.usercoins}</span> <img class="valign" src="../assets/silvercoin.png" style="cursor: help" title="User Coins">
<span${x.demons >= 1000 ? " class='brightred'" : ""}>${x.demons}</span> <img class="valign" src="../assets/demon.png" style="cursor: help" title="Demons">
${x.cp != 0 ? `<span${x.cp >= 100 ? " class='yellow'" : ""}>${x.cp}</span> <img class="valign" src="../assets/cp.png" style="cursor: help" title="Creator Points">` : ""}
${wk || onePointNine ? "" : `<span${x.diamonds >= 65535 ? ` class='blue'>${type == "accurate" ? "~" : ""}` : ">"}${x.diamonds}</span> <img class="valign" src="../assets/diamond.png" style="cursor: help" title="Diamonds">`}
${wk ? "&nbsp;" : `<span${x.coins >= 149 ? " class='yellow'" : ""}>${x.coins}</span> <img class="valign" src="../assets/coin.png" style="cursor: help" title="Secret Coins">`}
${wk || onePointNine ? "" : `<span${x.usercoins >= 10000 ? " class='brightblue'" : ""}>${x.usercoins}</span> <img class="valign" src="../assets/silvercoin.png" style="cursor: help" title="User Coins">`}
${wk ? "" : `<span${x.demons >= 1000 ? " class='brightred'" : ""}>${x.demons}</span> <img class="valign" src="../assets/demon.png" style="cursor: help" title="Demons">`}
${x.cp <= 0 ? "" : `<span${x.cp >= 100 ? " class='yellow'" : ""}>${x.cp}</span> <img class="valign" src="../assets/cp.png" style="cursor: help" title="Creator Points">`}
</h3>
<div class="center ranking" style="position:absolute; transform:scale(0.82) translate(-20.7vh, -20vh); height: 10%; width: 12.5%;">
@ -163,63 +181,75 @@ function leaderboard(val) {
})
}
let type = "accurate"
let type = "accurate"
leaderboard(type)
$('#boomling').attr('src', `../assets/boomlings/${boomColors[Math.floor(Math.random() * boomColors.length)]}.png`)
$(document).on('click', '.sortButton', function () {
if ($('#loading').is(":visible")) return
sort = $(this).attr('sort')
$('.sortButton').each(function() {
$(this).attr('src', $(this).attr('src').replace('-on', '').replace('.png', '') + ($(this).attr('sort') == sort ? "-on" : "") + ".png")
})
return leaderboard("accurate")
})
$('#topTabOff').click(function() {
if (type == "top") return;
type = "top"
leaderboard(type)
$('.leaderboardTab').hide();
$('#topTabOn').show()
$(weekly ? '#weeklyTabOff' : '#accurateTabOff').show()
$('#creatorTabOff').show()
infoText(top250Text)
$('.sortDiv').hide()
})
$('#boomling').attr('src', `../assets/boomlings/${colors[Math.floor(Math.random() * colors.length)]}.png`)
$('#accurateTabOff').click(function() {
if (type == "accurate") return;
type = "accurate"
leaderboard(type)
$('.leaderboardTab').hide();
$('#topTabOff').show()
$('#accurateTabOn').show()
$('#creatorTabOff').show()
infoText(accurateText)
$('.sortDiv').show()
})
$(document).on('click', '.sortButton', function () {
if ($('#loading').is(":visible")) return
sort = $(this).attr('sort')
$('.sortButton').each(function() {
$(this).attr('src', $(this).attr('src').replace('-on', '').replace('.png', '') + ($(this).attr('sort') == sort ? "-on" : "") + ".png")
})
return leaderboard("accurate")
})
$('#weeklyTabOff').click(function() {
if (type == "weekly" || !gdps) return;
type = "weekly"
leaderboard(type)
$('.leaderboardTab').hide();
$('#topTabOff').show()
$('#weeklyTabOn').show()
$('#creatorTabOff').show()
infoText(weeklyText)
$('.sortDiv').hide()
})
$('#topTabOff').click(function() {
if (type == "top") return;
type = "top"
leaderboard(type)
$('.leaderboardTab').hide();
$('#topTabOn').show()
$('#accurateTabOff').show()
$('#creatorTabOff').show()
infoText(top250Text)
$('.sortDiv').hide()
})
$('#creatorTabOff').click(function() {
if (type == "creator") return;
type = "creator"
leaderboard(type)
$('.leaderboardTab').hide();
$('#topTabOff').show()
$(weekly ? '#weeklyTabOff' : '#accurateTabOff').show()
$('#creatorTabOn').show()
infoText(creatorText)
$('.sortDiv').hide()
});
$('#accurateTabOff').click(function() {
if (type == "accurate") return;
type = "accurate"
leaderboard(type)
$('.leaderboardTab').hide();
$('#topTabOff').show()
$('#accurateTabOn').show()
$('#creatorTabOff').show()
infoText(accurateText)
$('.sortDiv').show()
})
$('#creatorTabOff').click(function() {
if (type == "creator") return;
type = "creator"
leaderboard(type)
$('.leaderboardTab').hide();
$('#topTabOff').show()
$('#accurateTabOff').show()
$('#creatorTabOn').show()
infoText(creatorText)
$('.sortDiv').hide()
});
$('#modSort').click(function() {
modMode = !modMode
$(this).attr('src', `../assets/sort-mod${modMode ? "-on" : ""}.png`)
if (modMode) { $('#cpSort').show() }
else { $('#cpSort').hide(); if (sort == "cp") $('#starSort').trigger('click') }
leaderboard(type)
})
$('#modSort').click(function() {
modMode = !modMode
$(this).attr('src', `../assets/sort-mod${modMode ? "-on" : ""}.png`)
if (modMode) { $('#cpSort').show() }
else { $('#cpSort').hide(); if (sort == "cp") $('#starSort').trigger('click') }
leaderboard(type)
})
</script>

View file

@ -140,7 +140,7 @@
<img class="gdButton sideButton" id="saveButton" src="../assets/plus.png" onclick="$('#saveDiv').show(); saveLevel()"><br>
<img class="gdButton sideButton" id="infoButton" src="../assets/info.png" onclick="$('#infoDiv').show()"><br>
<!-- <img class="gdButton sideButton" id="likeButton" src="../assets/vote.png" onclick="$('#likeDiv').show()"><br> -->
<a href="./analyze/[[ID]]"><img class="gdButton sideButton" src="../assets/edit.png"></a><br>
<a href="./analyze/[[ID]]"><img id="analyzeBtn" class="gdButton sideButton" src="../assets/edit.png"></a><br>
<a href="./comments/[[ID]]"><img class="gdButton sideButton" src="../assets/comment.png"></a><br>
<a href="./leaderboard/[[ID]]"><img id="leaderboardbtn" class="gdButton sideButton" src="../assets/leaderboard.png"></a><br>
</div>
@ -158,7 +158,7 @@
</body>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script async type="text/javascript" src="../sizecheck.js"></script>
<script type="text/javascript" src="../sizecheck.js?"></script>
<script>
let messageText = 'Your <span style="color: yellow">Geometry Dash password</span> will <span style="color: lime">not be stored</span> anywhere on the site, both <span style="color:rgb(113, 234, 255)">locally and server-side.</span> You can view the code used for liking a level <a class="menuLink" target="_blank" href="https://github.com/GDColon/GDBrowser/blob/master/api/post/like.js">here</a>.'
@ -226,7 +226,7 @@ else {
.replace('[[TIME2]]', "")
.replace('[[UPLOAD]]', "")
.replace('[[UPDATE]]', "") +
`<br><a class="youCanClickThis" href="/[[ID]]?download"><span style="color:aqua">Download additional info</span></a>`
`<br><a id="additional" class="youCanClickThis" href="/[[ID]]?download"><span style="color:aqua">Download additional info</span></a>`
)}
if (![[LARGE]]) $('#largeBadge').hide()
@ -259,11 +259,14 @@ if ("[[SONGID]]".startsWith("Level")) {
$('#songInfo').text('[[SONGID]]')
$('.songLink').hide()
}
else if ("[[GDPS]]" == "true") {
else $('#checkSong').show()
if (!"[[GDPS]]".startsWith("[")) {
$('#playSong').hide()
$('#moreSongs').hide()
$('#leaderboardbtn').hide()
$('#checkSong').remove()
}
else $('#checkSong').show()
if ("[[SONGAUTHOR]]" == "Unknown" || "[[INVALIDSONG]]" == "true") $('.songLink').hide()
if ("[[DISLIKED]]" == "true") $('#likeImg').attr('src', '../assets/dislike.png').css('transform', 'translateY(20%)')
@ -273,6 +276,8 @@ if ([[COINS]] > 0) $("#coins").append(`<img src="../assets/${coinColor}.png" hei
if ([[COINS]] > 1) $("#coins").append(`<img class="squeeze" src="../assets/${coinColor}.png" height="5%">`)
if ([[COINS]] > 2) $("#coins").append(`<img class="squeeze" src="../assets/${coinColor}.png" height="5%">`)
if ("[[GDPS]]".startsWith("1.9/")) $("#authorLink").attr('href', '/search/[[AUTHORID]]?user')
if ("[[ACCOUNTID]]" == "0") {
$("#authorName").addClass("green").addClass("unregistered")
$("#authorLink").attr('href', '/search/[[AUTHORID]]?user')

View file

@ -53,7 +53,7 @@
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery.lazy/1.7.9/jquery.lazy.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery.lazy/1.7.9/jquery.lazy.plugins.min.js"></script>
<script async type="text/javascript" src="../sizecheck.js"></script>
<script type="text/javascript" src="../sizecheck.js?"></script>
<script type="text/javascript" src="../dragscroll.js"></script>
<script>

View file

@ -17,6 +17,8 @@
<h1 style="transform:scale(1.2)">Map Packs</h1>
</div>
<img id="loading" style="margin-top: 1%" class="spin noSelect" src="../assets/loading.png" height="12%">
<div id="packList">
<br>
</div>
@ -28,10 +30,11 @@
</div>
</body>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script async type="text/javascript" src="../sizecheck.js"></script>
<script type="text/javascript" src="../sizecheck.js?"></script>
<script>
fetch('../api/mappacks').then(res => res.json()).then(packs => {
$('#loading').hide()
packs.forEach(x => {
$('#packList').append(`
<div class="mappack">

View file

@ -215,7 +215,7 @@
</body>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script async type="text/javascript" src="../sizecheck.js"></script>
<script type="text/javascript" src="../sizecheck.js?"></script>
<script>
let accountID;

View file

@ -34,7 +34,7 @@
</body>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script async type="text/javascript" src="../sizecheck.js"></script>
<script type="text/javascript" src="../sizecheck.js?"></script>
<script>
let line = 0

View file

@ -148,7 +148,7 @@
</body>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script async type="text/javascript" src="../sizecheck.js"></script>
<script type="text/javascript" src="../sizecheck.js?"></script>
<script type="text/javascript" src="../dragscroll.js"></script>
<script>

View file

@ -106,7 +106,7 @@
</body>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script async type="text/javascript" src="../sizecheck.js"></script>
<script type="text/javascript" src="../sizecheck.js?"></script>
<script type="text/javascript" src="https://asvd.github.io/dragscroll/dragscroll.js"></script>
<script>
@ -150,7 +150,7 @@ function Append(firstLoad) {
if (page == 0) $('#pageDown').hide()
else $('#pageDown').show()
fetch(searchFilters.replace("[PAGE]", page)).then(res => res.json()).then(res => {
Fetch(searchFilters.replace("[PAGE]", page)).then(res => {
if (res == '-1' || res.length == 0) { $('#loading').hide(); $('#pageUp').hide(); return loading = false }
@ -171,8 +171,9 @@ function Append(firstLoad) {
res.forEach((x, y) => {
let hasAuthor = (x.accountID != "0")
let userSearch = (type == 5 || typeof userMode == 'string')
if (demonList) x.demonID = (res.length * page) + y + 1
if (y == 0 && (type == 5 || typeof userMode == 'string')) {
if (y == 0 && userSearch) {
$('#header').text(((x.author == "-" ? "Someone" : x.author)) + (x.author.toLowerCase().endsWith('s') ? "'" : "'s") + " levels")
document.title = $('#header').text()
accID = x.authorID
@ -182,7 +183,7 @@ function Append(firstLoad) {
if (!filteredSong) filteredSong = x.songName
$('#searchBox').append(`<div class="searchresult">
<h1 class="lessspaced pre">${x.name}</h1>
<h2 class="lessSpaced pre smaller inline gdButton ${hasAuthor ? "" : "green unregistered"}">${hasAuthor ? `<a href="../u/${x.author}">By ${x.author}</a>` : `<a href="../search/${x.authorID}?user">By ${x.author}</a>`}</h2><h2 class="inline" style="margin-left: 1.5%; transform:translateY(30%)"> ${x.copiedID == '0' ? "" : '<img class="valign sideSpace" src="../assets/copied.png" height="12%">'}${x.large ? '<img class="valign sideSpaceD" src="../assets/large.png" height="12%">' : ''}</h2>
<h2 class="lessSpaced pre smaller inline gdButton ${hasAuthor ? "" : "green unregistered"}">${hasAuthor && !onePointNine ? `<a href="../u/${x.author}">By ${x.author}</a>` : `<a ${userSearch ? "" : `href="../search/${x.authorID}?user"`}>By ${x.author}</a>`}</h2><h2 class="inline" style="margin-left: 1.5%; transform:translateY(30%)"> ${x.copiedID == '0' ? "" : '<img class="valign sideSpace" src="../assets/copied.png" height="12%">'}${x.large ? '<img class="valign sideSpaceD" src="../assets/large.png" height="12%">' : ''}</h2>
<h3 class="lessSpaced pre ${x.customSong == 0 ? "blue" : "whatIfItWasPurple"}" style="overflow: hidden; max-height: 19%">${filteredSong}</h3>
<h3 class="lessSpaced">
<img class="valign" src="../assets/time.png" height="14%"> ${x.length}

132
index.js
View file

@ -1,15 +1,13 @@
const express = require('express');
const fs = require("fs")
const request = require('request');
const compression = require('compression');
const timeout = require('connect-timeout')
const timeout = require('connect-timeout');
const rateLimit = require("express-rate-limit");
const fs = require("fs");
const app = express();
app.offline = false // set to true to go into "offline" mode (in case of ip ban from rob)
app.config = require('./settings') // tweak settings in this file if you're using a GDPS
app.endpoint = app.config.endpoint // default is boomlings.com/database/
app.accountCache = {} // account IDs are cached here to shave off requests to getgjusers
app.lastSuccess = null // timestamp of the last time a gjp request was accepted my the servers
app.config = require('./settings.js')
app.servers = require('./servers.json')
let rlMessage = "Rate limited ¯\\_(ツ)_/¯<br><br>Please do not spam my servers with a crazy amount of requests. It slows things down on my end and stresses RobTop's servers just as much." +
" If you really want to send a zillion requests for whatever reason, please download the GDBrowser repository locally - or even just send the request directly to the GD servers.<br><br>" +
@ -31,59 +29,95 @@ const RL2 = rateLimit({
keyGenerator: function(req) { return req.headers['x-real-ip'] || req.headers['x-forwarded-for'] }
})
let api = true;
let gdIcons = fs.readdirSync('./assets/previewicons')
let forms = { "player": "cube", "bird": "ufo", "dart": "wave" }
let colorOrder = [0, 1, 2, 3, 16, 4, 5, 6, 13, 7, 8, 9, 29, 10, 14, 11, 12, 17, 18, 15, 27, 32, 28, 38, 20, 33, 21, 34, 22, 39, 23, 35, 24, 36, 25, 37, 30, 26, 31, 19, 40, 41]
let XOR = require('./classes/XOR.js');
let sampleIcons = require('./misc/sampleIcons.json')
let achievements = require('./misc/achievements.json')
let achievementTypes = require('./misc/achievementTypes.json')
let shopIcons = require('./misc/shops.json')
let colorList = require('./icons/colors.json')
let forms = { "player": "cube", "bird": "ufo", "dart": "wave" }
let gdIcons = fs.readdirSync('./assets/previewicons')
let assetPage = fs.readFileSync('./html/assets.html', 'utf8')
let whiteIcons = fs.readdirSync('./icons').filter(x => x.endsWith("extra_001.png")).map(function (x) { let xh = x.split("_"); return [xh[1] == "ball" ? "ball" : forms[xh[0]] || xh[0], +xh[xh[1] == "ball" ? 2 : 1]]})
let colorOrder = [0, 1, 2, 3, 16, 4, 5, 6, 13, 7, 8, 9, 29, 10, 14, 11, 12, 17, 18, 15, 27, 32, 28, 38, 20, 33, 21, 34, 22, 39, 23, 35, 24, 36, 25, 37, 30, 26, 31, 19, 40, 41]
app.accountCache = {}
app.lastSuccess = {}
app.actuallyWorked = {}
app.servers.forEach(x => {
app.accountCache[x.id || "gd"] = {}
app.lastSuccess[x.id || "gd"] = Date.now()
})
app.set('json spaces', 2)
app.use(compression());
app.use(express.json());
app.use(express.urlencoded({extended: true}));
app.use(timeout('20s'));
app.set('json spaces', 2)
app.use(function(req, res, next) {
app.use(async function(req, res, next) {
let subdomains = req.subdomains.map(x => x.toLowerCase())
if (!subdomains.length) subdomains = [""]
req.server = app.servers.find(x => subdomains.includes(x.id.toLowerCase()))
if (subdomains.length > 1 || !req.server) return res.redirect("http://" + req.get('host').split(".").slice(subdomains.length).join(".") + req.originalUrl)
// literally just for convenience
req.offline = req.server.offline
req.endpoint = req.server.endpoint
req.onePointNine = req.server.onePointNine
req.id = req.server.id || "gd"
req.isGDPS = req.server.endpoint != "http://boomlings.com/database/"
if (req.isGDPS) res.set("gdps", (req.onePointNine ? "1.9/" : "") + req.id)
req.gdParams = function(obj={}, substitute=true) {
Object.keys(app.config.params).forEach(x => { if (!obj[x]) obj[x] = app.config.params[x] })
Object.keys(req.server.extraParams || {}).forEach(x => { if (!obj[x]) obj[x] = req.server.extraParams[x] })
let ip = req.headers['x-real-ip'] || req.headers['x-forwarded-for']
let params = {form: obj, headers: app.config.ipForwarding && ip ? {'x-forwarded-for': ip, 'x-real-ip': ip} : {}}
if (substitute) { // GDPS substitutions in settings.js
for (let sub in app.config.substitutions) {
if (params.form[sub]) { params.form[app.config.substitutions[sub]] = params.form[sub]; delete params.form[sub] }
for (let ss in req.server.substitutions) {
if (params.form[ss]) { params.form[req.server.substitutions[ss]] = params.form[ss]; delete params.form[ss] }
}
}
return params
}
req.gdRequest = function(target, params={}, cb=function(){}) {
if (!target) return cb(true)
target = req.server.overrides ? (req.server.overrides[target] || target) : target
let parameters = params.headers ? params : req.gdParams(params)
let endpoint = req.endpoint
if (params.forceGD || (params.form && params.form.forceGD)) endpoint = "http://boomlings.com/database/"
request.post(endpoint + target + '.php', parameters, function(err, res, body) {
return cb(err, res, body)
})
}
next()
})
let directories = [""]
fs.readdirSync('./api').filter(x => !x.includes(".")).forEach(x => directories.push(x))
app.trackSuccess = function() {
// made this a function in case i wanna do more stuff in the future
app.lastSuccess = Date.now()
app.trackSuccess = function(id) {
app.lastSuccess[id] = Date.now()
if (!app.actuallyWorked[id]) app.actuallyWorked[id] = true
}
app.timeSince = function(time=app.lastSuccess) {
if (!time) return "[unknown]"
app.timeSince = function(id, time) {
if (!time) time = app.lastSuccess[id]
let secsPassed = Math.floor((Date.now() - time) / 1000)
let minsPassed = Math.floor(secsPassed / 60)
secsPassed -= 60 * minsPassed;
return `${minsPassed}m ${secsPassed}s`
return `${app.actuallyWorked[id] ? "" : "~"}${minsPassed}m ${secsPassed}s`
}
app.isGDPS = app.endpoint != "http://boomlings.com/database/"
app.GDPSName = (app.isGDPS ? app.endpoint.split("/")[2] : "")
app.run = {}
directories.forEach(d => {
fs.readdirSync('./api/' + d).forEach(x => {if (x.includes('.')) app.run[x.split('.')[0]] = require('./api/' + d + "/" + x) })
@ -115,6 +149,8 @@ app.parseResponse = function (responseBody, splitter) {
return res
}
app.xor = new XOR()
//xss bad
app.clean = function(text) {if (!text || typeof text != "string") return text; else return text.replace(/&/g, "&#38;").replace(/</g, "&#60;").replace(/>/g, "&#62;").replace(/=/g, "&#61;").replace(/"/g, "&#34;").replace(/'/g, "&#39;")}
@ -141,8 +177,6 @@ app.get("/assets/:dir*?", function(req, res) {
if (fs.existsSync(path)) { files = fs.readdirSync(path) }
assetPage = fs.readFileSync('./html/assets.html', 'utf8')
// remember to remove this
let assetData = JSON.stringify({files: files.filter(x => x.includes('.')), directories: files.filter(x => !x.includes('.'))})
res.send(assetPage.replace('{NAME}', dir || "assets").replace('{DATA}', assetData))
})
@ -154,28 +188,48 @@ app.post("/like", RL, function(req, res) { app.run.like(app, req, res) })
app.post("/postComment", RL, function(req, res) { app.run.postComment(app, req, res) })
app.post("/postProfileComment", RL, function(req, res) { app.run.postProfileComment(app, req, res) })
app.post("/messages", RL, async function(req, res) { app.run.getMessages(app, req, res) })
app.post("/messages/:id", RL, async function(req, res) { app.run.fetchMessage(app, req, res) })
app.post("/messages", RL, function(req, res) { app.run.getMessages(app, req, res) })
app.post("/messages/:id", RL, function(req, res) { app.run.fetchMessage(app, req, res) })
app.post("/deleteMessage", RL, function(req, res) { app.run.deleteMessage(app, req, res) })
app.post("/sendMessage", RL, function(req, res) { app.run.sendMessage(app, req, res) })
app.post("/accurateLeaderboard", function(req, res) { app.run.accurate(app, req, res, true) })
// HTML
let onePointNineDisabled = ['daily', 'weekly', 'gauntlets', 'messages']
let downloadDisabled = ['daily', 'weekly']
let gdpsHide = ['achievements', 'messages']
app.get("/", function(req, res) {
if (app.offline && !req.query.hasOwnProperty("home")) res.sendFile(__dirname + "/html/offline.html")
else res.sendFile(__dirname + "/html/home.html")
if (req.offline && !req.query.hasOwnProperty("home")) res.sendFile(__dirname + "/html/offline.html")
else {
fs.readFile('./html/home.html', 'utf8', function (err, data) {
let html = data;
if (req.isGDPS) {
html = html.replace('"levelBG"', '"levelBG purpleBG"')
.replace(/Geometry Dash Browser!/g, req.server.name + " Browser!")
.replace("/assets/gdlogo", `/assets/gdps/${req.id}_logo`)
gdpsHide.forEach(x => { html = html.replace(`menu-${x}`, 'changeDaWorld') })
}
if (req.onePointNine) onePointNineDisabled.forEach(x => { html = html.replace(`menu-${x}`, 'menuDisabled') })
if (req.server.downloadsDisabled) {
downloadDisabled.forEach(x => { html = html.replace(`menu-${x}`, 'menuDisabled') })
html = html.replace('id="dl" style="display: none', 'style="display: block')
}
return res.send(html)
})
}
})
app.get("/achievements", function(req, res) { res.sendFile(__dirname + "/html/achievements.html") })
app.get("/analyze/:id", async function(req, res) { res.sendFile(__dirname + "/html/analyze.html") })
app.get("/analyze/:id", function(req, res) { res.sendFile(__dirname + "/html/analyze.html") })
app.get("/api", function(req, res) { res.sendFile(__dirname + "/html/api.html") })
app.get("/boomlings", function(req, res) { res.sendFile(__dirname + "/html/boomlings.html") })
app.get("/comments/:id", function(req, res) { res.sendFile(__dirname + "/html/comments.html") })
app.get("/demon/:id", function(req, res) { res.sendFile(__dirname + "/html/demon.html") })
app.get("/gauntlets", function(req, res) { res.sendFile(__dirname + "/html/gauntlets.html") })
app.get("/gdps", function(req, res) { res.sendFile(__dirname + "/html/gdps.html") })
app.get("/iconkit", function(req, res) { res.sendFile(__dirname + "/html/iconkit.html") })
app.get("/leaderboard", function(req, res) { res.sendFile(__dirname + "/html/leaderboard.html") })
app.get("/leaderboard/:text", function(req, res) { res.sendFile(__dirname + "/html/levelboard.html") })
@ -187,16 +241,16 @@ app.get("/search/:text", function(req, res) { res.sendFile(__dirname + "/html/se
// API
app.get("/api/analyze/:id", RL, async function(req, res) { app.run.level(app, req, res, api, true) })
app.get("/api/analyze/:id", RL, function(req, res) { app.run.level(app, req, res, true, true) })
app.get("/api/boomlings", function(req, res) { app.run.boomlings(app, req, res) })
app.get("/api/comments/:id", RL2, function(req, res) { app.run.comments(app, req, res) })
app.get("/api/credits", function(req, res) { res.send(require('./misc/credits.json')) })
app.get("/api/gauntlets", async function(req, res) { app.run.gauntlets(app, req, res) })
app.get("/api/gauntlets", function(req, res) { app.run.gauntlets(app, req, res) })
app.get("/api/leaderboard", function(req, res) { app.run[req.query.hasOwnProperty("accurate") ? "accurate" : "scores"](app, req, res) })
app.get("/api/leaderboardLevel/:id", RL2, function(req, res) { app.run.leaderboardLevel(app, req, res) })
app.get("/api/level/:id", RL, async function(req, res) { app.run.level(app, req, res, api) })
app.get("/api/mappacks", async function(req, res) { app.run.mappacks(app, req, res) })
app.get("/api/profile/:id", RL2, function(req, res) { app.run.profile(app, req, res, api) })
app.get("/api/level/:id", RL, function(req, res) { app.run.level(app, req, res, true) })
app.get("/api/mappacks", function(req, res) { app.run.mappacks(app, req, res) })
app.get("/api/profile/:id", RL2, function(req, res) { app.run.profile(app, req, res, true) })
app.get("/api/search/:text", RL2, function(req, res) { app.run.search(app, req, res) })
app.get("/api/song/:song", function(req, res){ app.run.song(app, req, res) })
@ -224,10 +278,12 @@ app.get("/:id", function(req, res) { app.run.level(app, req, res) })
// MISC
app.get("/icon/:text", function(req, res) { app.run.icon(app, req, res) })
app.get("/api/gdps", function(req, res) { res.send(app.servers) })
app.get("/api/achievements", function(req, res) { res.send({achievements, types: achievementTypes, shopIcons, colors: colorList }) })
app.get('/api/icons', function(req, res) {
let sample = [JSON.stringify(sampleIcons[Math.floor(Math.random() * sampleIcons.length)].slice(1))]
res.send({icons: gdIcons, colors: colorList, colorOrder, whiteIcons, sample});
let iconserver = req.isGDPS ? req.server.name : undefined
res.send({icons: gdIcons, colors: colorList, colorOrder, whiteIcons, server: iconserver, noCopy: req.onePointNine, sample});
});
app.get('*', function(req, res) {

View file

@ -34,7 +34,22 @@ function backButton() {
else window.location.href = "../../../../../"
}
let gdps = null
let onePointNine = false
function Fetch(link) {
return new Promise(function (res, rej) {
fetch(link).then(resp => {
if (!resp.ok) return rej(resp)
gdps = resp.headers.get('gdps')
if (gdps && gdps.startsWith('1.9/')) { onePointNine = true; gdps = gdps.slice(4) }
resp.json().then(res)
}).catch(rej)
})
}
let allowEsc = true;
$(document).keydown(function(k) {
if (k.keyCode == 27) { //esc
if (!allowEsc) return

60
servers.json Normal file
View file

@ -0,0 +1,60 @@
[
{
"name": "Geometry Dash",
"link": "https://store.steampowered.com/app/322170/Geometry_Dash/",
"author": "RobTop",
"authorLink": "https://www.youtube.com/channel/UCz_yk8mDSAnxJq0ar66L4sw",
"id": "",
"endpoint": "http://boomlings.com/database/",
"timestampSuffix": " ago",
"downloadsDisabled": true
},
{
"name": "2.2 Unlocked",
"link": "https://smjs.eu/gd/unlock/database/dashboard/",
"author": "SMJS",
"authorLink": "https://www.youtube.com/channel/UClXb1w9vSL3Z0V-mUbudOnw",
"id": "22unlocked",
"endpoint": "http://smjs.eu/gd/unlock/database/",
"substitutions": {
"levelID": "oereoIE",
"accountID": "BddpvouKE",
"targetAccountID": "targetBddpvouKE"
}
},
{
"name": "1.9 GDPS",
"link": "https://absolllute.com/gdps/",
"author": "Absolute",
"authorLink": "https://www.youtube.com/channel/UCpdDW0ZdzoRzioT4eTfn-yw",
"id": "19gdps",
"endpoint": "http://absolllute.com/gdps/gdapi/",
"onePointNine": true,
"weeklyLeaderboard": true,
"overrides": {
"getGJMapPacks21": "getGJMapPacks",
"getGJScores20": "getGJScores",
"getGJComments21": "getGJComments"
}
},
{
"name": "XGDPS",
"link": "http://xcggdpsserver.xyz/",
"author": "XcreatorGoal",
"authorLink": "https://www.youtube.com/channel/UC33L-Y8asG7gju6f-4-Cl2g",
"id": "xgdps",
"endpoint": "http://xcggdpsserver.xyz/database/"
},
{
"name": "WGDPS",
"link": "http://wyliegdps02.7m.pl/",
"author": "Wylie",
"authorLink": "https://www.youtube.com/channel/UCG5I4-KAW3Kwzam4svLJWBA",
"id": "wgdps",
"endpoint": "http://wyliegdps02.7m.pl/database/"
}
]

View file

@ -1,29 +1,21 @@
// In case you wanna use a fork of GDBrowser locally or for a GDPS or something, here are some settings you can tweak to save you some precious time
// This isn't a JSON because you can't leave comments on them, ew
// This used to be a place for GDPS settings but that has all been moved over to servers.json
// Feel free to enable/disable stuff here for smoother local use, free of rate limits
module.exports = {
port: 2000, // Port to host website on
endpoint: "http://boomlings.com/database/", // Server endpoint to send requests to, must end with a slash
params: { // Always send this stuff to the servers
secret: 'Wmfd2893gb7',
gameVersion: '21',
binaryVersion: '35',
gdbrowser: '1'
},
rateLimiting: true, // Enables rate limiting to avoid api spam, feel free to disable for private use.
ipForwarding: true, // Forwards 'x-real-ip' to the servers. (requested by robtop)
cacheMapPacks: true, // Caches map packs to speed up loading. Useful if they're rarely updated.
cacheAccountIDs: true, // Caches account IDs in order to shave off an extra request to the servers.
cachePlayerIcons: true, // Caches player icons to speed up loading. Changing your icon in-game may take time to update on the site.
rateLimiting: true, // Enables rate limiting to avoid api spam, feel free to disable for private use.
ipForwarding: true, // Forwards 'x-real-ip' to the servers. (requested by robtop)
// GDPS Related (feel free to drop a PR if you're able to make gdbrowser work better with gdps'es <3)
timestampSuffix: " ago", // Suffix to add after timestamps, if any.
base64descriptions: true, // Are level descriptions encoded in Base64?
xorPasswords: true, // Are level passwords XOR encrypted?
substitutions: { // Any parameters that are renamed on the GDPS should be listed here, e.g. { levelID: "abcde" }
// levelID: "oiuyhxp4w9I"
}
}