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 # GDBrowser
Uh... so I've never actually used GitHub before this. But I'll try to explain everything going on here. 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. 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? ## 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 # 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. Most folders contain exactly what you'd expect, but here's some in-depth info in case you're in the dark.
## API ## API
This is where all the backend stuff happens! Yipee! 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! Assets everywhere! 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) 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. I'd explain what's in all the subfolders but it's pretty obvious. I tried my best to organize everything nicely.
## Classes ## Classes
What's a class you ask? Good question. What's a class you ask? Good question.
I guess the best way to put it is uh... super fancy functions??? 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 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 XOR.js encrypts/decrypts stuff like GD passwords
## HTML ## 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 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 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 ## Misc
Inevitable misc folder Inevitable misc folder
**Level Analysis Stuff (in a separate folder)** **Level Analysis Stuff (in a separate folder)**
blocks.json - The object IDs in the different 'families' of blocks blocks.json - The object IDs in the different 'families' of blocks
colorProperties.json - Color channel cheatsheet colorProperties.json - Color channel cheatsheet
initialProperties.json - Level settings 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) 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 objects.json - IDs for portals, orbs, triggers, and misc stuff
**Everything Else** **Everything Else**
achievements.json - List of all GD/meltdown/subzero/etc achievements. `parseAchievementPlist.js` automatically creates this file 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 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 colors.json - List of icon colors in RGB format
credits.json - Credits! (shown on the homepage) credits.json - Credits! (shown on the homepage)
dragscroll.js - Used on several pages for drag scrolling 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 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 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], 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 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 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 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 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. happy gdbrowsing and god bless.

View file

@ -1,8 +1,6 @@
const request = require('request')
module.exports = async (app, req, res) => { 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 let count = +req.query.count || 10
if (count > 1000) count = 1000 if (count > 1000) count = 1000
@ -20,7 +18,7 @@ module.exports = async (app, req, res) => {
if (req.query.type == "commentHistory") path = "getGJCommentHistory" if (req.query.type == "commentHistory") path = "getGJCommentHistory"
else if (req.query.type == "profile") path = "getGJAccountComments20" 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") 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.content = Buffer.from(x[2], 'base64').toString();
comment.ID = x[6] comment.ID = x[6]
comment.likes = +x[4] comment.likes = +x[4]
comment.date = (x[9] || "?") + app.config.timestampSuffix comment.date = (x[9] || "?") + (req.timestampSuffix || "")
if (comment.content.endsWith("⍟") || comment.content.endsWith("☆")) { if (comment.content.endsWith("⍟") || comment.content.endsWith("☆")) {
comment.content = comment.content.slice(0, -1) comment.content = comment.content.slice(0, -1)
comment.browserColor = true comment.browserColor = true
@ -63,10 +61,10 @@ module.exports = async (app, req, res) => {
comment.moderator = +x[11] || 0 comment.moderator = +x[11] || 0
comment.icon = { comment.icon = {
form: ['icon', 'ship', 'ball', 'ufo', 'wave', 'robot', 'spider'][+y[14]], form: ['icon', 'ship', 'ball', 'ufo', 'wave', 'robot', 'spider'][+y[14]],
icon: +y[9], icon: +y[9] || 1,
col1: +y[10], col1: +y[10],
col2: +y[11], 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') const Level = require('../classes/Level.js')
module.exports = async (app, req, res, api, ID, analyze) => { module.exports = async (app, req, res, api, ID, analyze) => {
if (app.offline) { if (req.offline) {
if (!api && levelID < 0) return res.redirect('/') if (!api && levelID < 0) return res.redirect('/')
if (!api) return res.redirect('search/' + req.params.id) if (!api) return res.redirect('search/' + req.params.id)
else return res.send("-1") 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 if (levelID == "weekly") levelID = -2
else levelID = levelID.replace(/[^0-9]/g, "") 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 && levelID < 0) return res.redirect(`/?daily=${levelID * -1}`)
if (!api) return res.redirect('search/' + req.params.id) if (!api) return res.redirect('search/' + req.params.id)
else return res.send("-1") 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 authorData = body.split("#")[3] // daily/weekly only, most likely
let levelInfo = app.parseResponse(body) 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) 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) { if (err2 && (foundID || authorData)) {
let authorInfo = authorData.split(":") let authorInfo = foundID || authorData.split(":")
level.author = authorInfo[1] level.author = authorInfo[1] || "-"
level.accountID = authorInfo[0] level.accountID = authorInfo[0].includes(",") ? "0" : authorInfo[0]
} }
else if (!err && b2 != '-1') { else if (!err && b2 != '-1') {
let account = app.parseResponse(b2) let account = app.parseResponse(b2)
level.author = account[1] level.author = account[1] || "-"
level.accountID = gdSearchResult[16] level.accountID = gdSearchResult[16]
} }
@ -48,7 +51,7 @@ module.exports = async (app, req, res, api, ID, analyze) => {
level.accountID = "0" 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') { if (!err && songRes != '-1') {
let songData = app.parseResponse(songRes, '~|~') let songData = app.parseResponse(songRes, '~|~')
@ -71,7 +74,12 @@ module.exports = async (app, req, res, api, ID, analyze) => {
level.data = levelInfo[4] level.data = levelInfo[4]
if (analyze) return app.run.analyze(app, req, res, level) 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() { function sendLevel() {
if (api) return res.send(level) if (api) return res.send(level)
@ -88,7 +96,7 @@ module.exports = async (app, req, res, api, ID, analyze) => {
} }
if (levelID < 0) { 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() if (err || dailyInfo == -1) return sendLevel()
let dailyTime = dailyInfo.split("|")[1] let dailyTime = dailyInfo.split("|")[1]
level.nextDaily = +dailyTime 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) { request.get('https://www.pointercrate.com/api/v2/demons/?name=' + level.name.trim(), function (err, resp, demonList) {
if (err) return sendLevel() if (err) return sendLevel()
let demon = JSON.parse(demonList) let demon = JSON.parse(demonList)

View file

@ -1,21 +1,21 @@
const request = require('request') let cache = {}
let cache = {data: null, indexed: 0}
module.exports = async (app, req, res) => { module.exports = async (app, req, res) => {
if (app.offline) return res.send("-1") if (req.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
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 gauntlets = body.split('#')[0].split('|').map(x => app.parseResponse(x))
let gauntletList = gauntlets.map(x => ({ id: +x[1], levels: x[3].split(",") })) 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) res.send(gauntletList)
}) })
} }

View file

@ -2,7 +2,6 @@
// i advise you to turn back now // i advise you to turn back now
// seriously, it's not too late // seriously, it's not too late
const request = require('request')
const Jimp = require('jimp'); const Jimp = require('jimp');
const fs = require('fs'); const fs = require('fs');
const icons = require('../icons/gameSheet.json'); const icons = require('../icons/gameSheet.json');
@ -360,29 +359,28 @@ module.exports = async (app, req, res) => {
let userCode; let userCode;
res.contentType('image/png'); 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) { 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) if (cache[userCode]) return res.end(cache[userCode].value)
} }
let accountMode = !req.query.hasOwnProperty("player") && Number(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 skipRequest = accountMode || foundID
let forceGD = req.query.hasOwnProperty("forceGD") // forces request to be made on boomlings.com let forceGD = req.query.hasOwnProperty("forceGD")
let endpoint = forceGD ? "http://boomlings.com/database/" : app.endpoint
// skip request by causing fake error lmao // 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) 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); 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) => { 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([]) if (!app.sheetsKey) return res.send([])
let gdMode = post || req.query.hasOwnProperty("gd") let gdMode = post || req.query.hasOwnProperty("gd")
let modMode = !gdMode && req.query.hasOwnProperty("mod") let modMode = !gdMode && req.query.hasOwnProperty("mod")

View file

@ -3,7 +3,7 @@ const request = require('request')
module.exports = async (app, req, res) => { module.exports = async (app, req, res) => {
request.post('http://robtopgames.com/Boomlings/get_scores.php', { 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") 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) => { module.exports = async (app, req, res) => {
if (app.offline) return res.send("-1") if (req.offline) return res.send("-1")
let amount = 100; let amount = 100;
let count = req.query.count ? parseInt(req.query.count) : null let count = req.query.count ? parseInt(req.query.count) : null
@ -11,19 +9,19 @@ module.exports = async (app, req, res) => {
else amount = count; else amount = count;
} }
let params = req.gdParams({ let params = {
levelID: req.params.id, levelID: req.params.id,
accountID: app.id, accountID: app.id,
gjp: app.gjp, gjp: app.gjp,
type: req.query.hasOwnProperty("week") ? "2" : "1", 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") if (err || body == -1 || !body) return res.send("-1")
scores = body.split('|').map(x => app.parseResponse(x)).filter(x => x[1]) scores = body.split('|').map(x => app.parseResponse(x)).filter(x => x[1])
if (!scores.length) return res.send("-1") if (!scores.length) return res.send("-1")
else app.trackSuccess() else app.trackSuccess(req.id)
scores.forEach(x => { scores.forEach(x => {
let keys = Object.keys(x) let keys = Object.keys(x)
@ -32,13 +30,13 @@ module.exports = async (app, req, res) => {
x.percent = +x[3] x.percent = +x[3]
x.coins = +x[13] x.coins = +x[13]
x.playerID = x[2] x.playerID = x[2]
x.date = x[42] + app.config.timestampSuffix x.date = x[42] + (req.timestampSuffix || "")
x.icon = { x.icon = {
form: ['icon', 'ship', 'ball', 'ufo', 'wave', 'robot', 'spider'][+x[14]], form: ['icon', 'ship', 'ball', 'ufo', 'wave', 'robot', 'spider'][+x[14]],
icon: +x[9], icon: +x[9],
col1: +x[10], col1: +x[10],
col2: +x[11], col2: +x[11],
glow: +x[15] > 0 glow: +x[15] > 1
} }
keys.forEach(k => delete x[k]) keys.forEach(k => delete x[k])
}) })

View file

@ -1,8 +1,6 @@
const request = require('request')
module.exports = async (app, req, res) => { module.exports = async (app, req, res) => {
if (app.offline) return res.send("-1") if (req.offline) return res.send("-1")
let amount = 100; let amount = 100;
let count = req.query.count ? parseInt(req.query.count) : null 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"} let params = {count: amount, type: "top"}
if (["creators", "creator", "cp"].some(x => req.query.hasOwnProperty(x) || req.query.type == x)) params.type = "creators" 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") if (err || body == '-1' || !body) return res.send("-1")
scores = body.split('|').map(x => app.parseResponse(x)).filter(x => x[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], icon: +x[9],
col1: +x[10], col1: +x[10],
col2: +x[11], col2: +x[11],
glow: +x[15] > 0 glow: +x[15] > 1
} }
keys.forEach(k => delete x[k]) keys.forEach(k => delete x[k])
}) })

View file

@ -2,11 +2,9 @@ const request = require('request')
const fs = require('fs') const fs = require('fs')
const Level = require('../classes/Level.js') 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) => { module.exports = async (app, req, res, api, analyze) => {
if (app.offline) { if (req.offline) {
if (!api) return res.redirect('search/' + req.params.id) if (!api) return res.redirect('search/' + req.params.id)
else return res.send("-1") 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) 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) if (!api) return res.redirect('search/' + req.params.id)
else return res.send("-1") else return res.send("-1")
} }
@ -35,7 +33,7 @@ module.exports = async (app, req, res, api, analyze) => {
song = app.parseResponse(song, '~|~') song = app.parseResponse(song, '~|~')
let levelInfo = app.parseResponse(preRes[0]) 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) { if (level.customSong) {
level.songName = song[2] || "Unknown" level.songName = song[2] || "Unknown"
@ -52,7 +50,13 @@ module.exports = async (app, req, res, api, analyze) => {
level.songID = "Level " + [parseInt(levelInfo[12]) + 1] 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() { function sendLevel() {
@ -67,11 +71,13 @@ module.exports = async (app, req, res, api, analyze) => {
let regex = new RegExp(`\\[\\[${x.toUpperCase()}\\]\\]`, "g") let regex = new RegExp(`\\[\\[${x.toUpperCase()}\\]\\]`, "g")
html = html.replace(regex, app.clean(level[x])) 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) 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) { request.get('http://www.pointercrate.com/api/v2/demons/?name=' + level.name.trim(), function (err, resp, demonList) {
if (err) return sendLevel() if (err) return sendLevel()
let demon = JSON.parse(demonList) let demon = JSON.parse(demonList)

View file

@ -1,33 +1,43 @@
const request = require('request') let difficulties = ["auto", "easy", "normal", "hard", "harder", "insane", "demon", "demon-easy", "demon-medium", "demon-insane", "demon-extreme"]
const difficulties = ["unrated", "easy", "normal", "hard", "harder", "insane", "demon"] let cache = {}
let cache = {data: null, indexed: 0}
module.exports = async (app, req, res) => { module.exports = async (app, req, res) => {
if (app.offline) return res.send("-1") if (req.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
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 // not all GDPS'es support the count param, which means recursion time!!!
id: +x[1], if (newPacks.length == 10) {
levels: x[3].split(","), params.page++
name: x[2], return mapPackLoop()
stars: +x[4], }
coins: +x[5],
difficulty: difficulties[+x[6]], let mappacks = packs.map(x => ({ // "packs.map()" laugh now please
barColor: x[7], id: +x[1],
textColor: x[8] 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()} if (app.config.cacheMapPacks) cache[req.id] = {data: mappacks, indexed: Date.now()}
return res.send(mappacks) 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) => { module.exports = async (app, req, res) => {
if (!req.body.accountID) return res.status(400).send("No account ID provided!") 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!") if (!req.body.password) return res.status(400).send("No password provided!")
let params = req.gdParams({ let params = {
accountID: req.body.accountID, accountID: req.body.accountID,
targetAccountID: 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.`) 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() else app.trackSuccess(req.id)
let count = app.parseResponse(body)[38] let count = app.parseResponse(body)[38]
if (!count) return res.status(400).send("Error fetching unread messages!") if (!count) return res.status(400).send("Error fetching unread messages!")
else res.status(200).send(count) 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) => { module.exports = async (app, req, res, api) => {
if (!req.body.accountID) return res.status(400).send("No account ID provided!") 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 = { let params = {
accountID: req.body.accountID, 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, messages: Array.isArray(req.body.id) ? req.body.id.map(x => x.trim()).join(",") : req.body.id,
} }
let deleted = params.messages.split(",").length 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!`) 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) => { module.exports = async (app, req, res, api) => {
if (!req.body.accountID) return res.status(400).send("No account ID provided!") 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({ let params = req.gdParams({
accountID: req.body.accountID, accountID: req.body.accountID,
gjp: xor.encrypt(req.body.password, 37526), gjp: app.xor.encrypt(req.body.password, 37526),
messageID: req.params.id, 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.`) 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() else app.trackSuccess(req.id)
let x = app.parseResponse(body) let x = app.parseResponse(body)
let msg = {} let msg = {}
@ -25,8 +21,8 @@ module.exports = async (app, req, res, api) => {
msg.accountID = x[2] msg.accountID = x[2]
msg.author = x[6] msg.author = x[6]
msg.subject = Buffer.from(x[4], "base64").toString().replace(/^Re: ☆/, "Re: ") msg.subject = Buffer.from(x[4], "base64").toString().replace(/^Re: ☆/, "Re: ")
msg.content = xor.decrypt(x[5], 14251) msg.content = app.xor.decrypt(x[5], 14251)
msg.date = x[7] + app.config.timestampSuffix msg.date = x[7] + (req.timestampSuffix || "")
if (msg.subject.endsWith("☆") || msg.subject.startsWith("☆")) { if (msg.subject.endsWith("☆") || msg.subject.startsWith("☆")) {
if (msg.subject.endsWith("☆")) msg.subject = msg.subject.slice(0, -1) if (msg.subject.endsWith("☆")) msg.subject = msg.subject.slice(0, -1)
else msg.subject = msg.subject.slice(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) => { module.exports = async (app, req, res, api) => {
if (req.body.count) return app.run.countMessages(app, req, res) 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({ let params = req.gdParams({
accountID: req.body.accountID, accountID: req.body.accountID,
gjp: xor.encrypt(req.body.password, 37526), gjp: app.xor.encrypt(req.body.password, 37526),
page: req.body.page || 0, page: req.body.page || 0,
getSent: req.query.sent ? 1 : 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.`) 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() else app.trackSuccess(req.id)
let messages = body.split("|").map(msg => app.parseResponse(msg)) let messages = body.split("|").map(msg => app.parseResponse(msg))
let messageArray = [] let messageArray = []
@ -30,7 +26,7 @@ module.exports = async (app, req, res, api) => {
msg.accountID = x[2] msg.accountID = x[2]
msg.author = x[6] msg.author = x[6]
msg.subject = Buffer.from(x[4], "base64").toString().replace(/^Re: ☆/, "Re: ") 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" msg.unread = x[8] != "1"
if (msg.subject.endsWith("☆") || msg.subject.startsWith("☆")) { if (msg.subject.endsWith("☆") || msg.subject.startsWith("☆")) {
if (msg.subject.endsWith("☆")) msg.subject = msg.subject.slice(0, -1) 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) => { module.exports = async (app, req, res, api) => {
if (!req.body.targetID) return res.status(400).send("No target ID provided!") 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!") 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 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({ let params = req.gdParams({
accountID: req.body.accountID, accountID: req.body.accountID,
gjp: xor.encrypt(req.body.password, 37526), gjp: app.xor.encrypt(req.body.password, 37526),
toAccountID: req.body.targetID, toAccountID: req.body.targetID,
subject, body, subject, body,
}) })
request.post(app.endpoint + 'uploadGJMessage20.php', params, async function (err, resp, body) { 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()} ago.`) 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!') 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') const crypto = require('crypto')
function sha1(data) { return crypto.createHash("sha1").update(data, "binary").digest("hex"); } 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.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.accountID = req.body.accountID.toString()
params.like = req.body.like.toString() params.like = req.body.like.toString()
params.special = req.body.extraID.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" let chk = params.special + params.itemID + params.like + params.type + params.rs + params.accountID + params.udid + params.uuid + "ysg6pUrtjn0J"
chk = sha1(chk) chk = sha1(chk)
chk = xor.encrypt(chk, 58281) chk = app.xor.encrypt(chk, 58281)
params.chk = chk 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 (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.`) 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() 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)") 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') const crypto = require('crypto')
function sha1(data) { return crypto.createHash("sha1").update(data, "binary").digest("hex"); } 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 } let params = { percent: 0 }
params.comment = Buffer.from(req.body.comment + (req.body.color ? "☆" : "")).toString('base64').replace(/\//g, '_').replace(/\+/g, "-") 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.levelID = req.body.levelID.toString()
params.accountID = req.body.accountID.toString() params.accountID = req.body.accountID.toString()
params.userName = req.body.username 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" let chk = params.userName + params.comment + params.levelID + params.percent + "0xPT6iUrtws0J"
chk = sha1(chk) chk = sha1(chk)
chk = xor.encrypt(chk, 29481) chk = app.xor.encrypt(chk, 29481)
params.chk = chk 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 (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")) { if (body.startsWith("temp")) {
let banStuff = body.split("_") 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"}`) 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}`) 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(); rateLimit[req.body.username] = Date.now();
setTimeout(() => {delete rateLimit[req.body.username]; }, cooldown); 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') const crypto = require('crypto')
function sha1(data) { return crypto.createHash("sha1").update(data, "binary").digest("hex"); } 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' } let params = { cType: '1' }
params.comment = Buffer.from(req.body.comment.slice(0, 190) + (req.body.color ? "☆" : "")).toString('base64').replace(/\//g, '_').replace(/\+/g, "-") 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.accountID = req.body.accountID.toString()
params.userName = req.body.username params.userName = req.body.username
let chk = params.userName + params.comment + "1xPT6iUrtws0J" let chk = params.userName + params.comment + "1xPT6iUrtws0J"
chk = sha1(chk) chk = sha1(chk)
chk = xor.encrypt(chk, 29481) chk = app.xor.encrypt(chk, 29481)
params.chk = chk 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") 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")) { if (body.startsWith("temp")) {
let banStuff = body.split("_") 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"}`) 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}`) 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') const fs = require('fs')
module.exports = async (app, req, res, api, getLevels) => { 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 username = getLevels || req.params.id
let accountMode = !req.query.hasOwnProperty("player") && Number(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 skipRequest = accountMode || foundID
let searchResult; 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) // 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] if (foundID) searchResult = foundID[0]
else if (accountMode || err1 || b1 == '-1' || b1.startsWith("<!") || !b1) searchResult = req.params.id 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 (!req.isGDPS) searchResult = app.parseResponse(b1.split("|")[0])[16]
else { // GDPS's return multiple users, GD no longer does this else { // GDPS's return multiple users, GD no longer does this
let userResults = b1.split("|").map(x => app.parseResponse(x)) let userResults = b1.split("|").map(x => app.parseResponse(x))
searchResult = userResults.find(x => x[1].toLowerCase() == username.toLowerCase() || x[2] == username) || "" 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) 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 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 (err2 || body == '-1' || !body || dumbGDPSError) {
if (!api) return res.redirect('/search/' + req.params.id) if (!api) return res.redirect('/search/' + req.params.id)
else return res.send("-1") 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 = { let userData = {
username: account[1] || "[MISSINGNO.]", username: account[1] || "[MISSINGNO.]",

View file

@ -5,11 +5,11 @@ let demonList = {list: [], lastUpdated: 0}
module.exports = async (app, req, res) => { 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" let demonMode = req.query.hasOwnProperty("demonlist") || req.query.hasOwnProperty("demonList") || req.query.type == "demonlist" || req.query.type == "demonList"
if (demonMode) { 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 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) { return request.get('http://www.pointercrate.com/api/v2/demons/listed/?limit=100', function (err1, resp1, list1) {
if (err1) return res.send("-1") if (err1) return res.send("-1")
@ -23,7 +23,7 @@ module.exports = async (app, req, res) => {
} }
let amount = 10; let amount = 10;
let count = +req.query.count let count = req.isGDPS ? 10 : +req.query.count
if (count && count > 0) { if (count && count > 0) {
if (count > 500) amount = 500 if (count > 500) amount = 500
else amount = count; else amount = count;
@ -74,7 +74,7 @@ module.exports = async (app, req, res) => {
} }
if (req.query.hasOwnProperty("user")) { 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 filters.type = 5
if (accountCheck) filters.str = accountCheck[1] if (accountCheck) filters.str = accountCheck[1]
else if (!filters.str.match(/^[0-9]*$/)) return app.run.profile(app, req, res, null, req.params.text) 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 if (req.query.hasOwnProperty("creators")) filters.type = 12
let listSize = 10 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.type = 10
filters.str = demonMode ? demonList.list : filters.str.split(",") filters.str = demonMode ? demonList.list : filters.str.split(",")
listSize = filters.str.length listSize = filters.str.length
@ -93,9 +93,9 @@ module.exports = async (app, req, res) => {
if (filters.str == "*") delete filters.str 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 splitBody = body.split('#')
let preRes = splitBody[0].split('|') let preRes = splitBody[0].split('|')
let authorList = {} 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 levelArray = preRes.map(x => app.parseResponse(x)).filter(x => x[1])
let parsedLevels = [] 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]) || [] let songSearch = songs.find(y => y['~1'] == x[35]) || []
level.author = authorList[x[6]] ? authorList[x[6]][0] : "-"; 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] 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 //this is broken if you're not on page 0, blame robtop
if (filters.page == 0 && y == 0) { if (filters.page == 0 && y == 0) {
let pages = splitBody[3].split(":"); 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 } } 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 let songID = req.params.song
request.post('http://boomlings.com/database/testSong.php?songID=' + songID, req.gdParams(), async function(err, resp, body) { 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) 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) { req.gdRequest('getGJSongInfo', {songID: songID}, function(err2, resp, songAllowed) {
if (err2 || !songAllowed || songAllowed < 0 || body.startsWith("<!")) return res.send(info) if (err2 || !songAllowed || songAllowed < 0 || body.startsWith("<")) return res.send(info)
let artistInfo = body.split(/<\/?br>/) let artistInfo = body.split(/<\/?br>/)
info.artist.name = artistInfo[0].split(": ")[1] 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; background-image: linear-gradient(#4B0062, #22002D) !important;
} }
.purpleBG {
background-image: linear-gradient(#6E00FD, #330074) !important;
}
img, .noSelect { img, .noSelect {
user-select: none; user-select: none;
} }
@ -197,7 +201,7 @@ input[type=text], input[type=password], input[type=number] {
font-size: 5vh; font-size: 5vh;
} }
input[type=checkbox] { input[type=checkbox], .changeDaWorld {
display: none; display: none;
} }
@ -539,7 +543,7 @@ input::-webkit-inner-spin-button {
} }
.mappack h3, .gauntlet h3 { .mappack h3, .gauntlet h3 {
font-size: 40px font-size: 35px
} }
.mappack { .mappack {
@ -805,10 +809,6 @@ input::-webkit-inner-spin-button {
border: 0.6vh solid rgba(0, 0, 0, 0.5); 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 { .messageInput {
font-size: 3.5vh; font-size: 3.5vh;
text-align: left; text-align: left;
@ -1148,6 +1148,20 @@ input::-webkit-inner-spin-button {
transform: scale(1.04); 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 { .hidey {
opacity: 0%; opacity: 0%;
pointer-events: none; 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 XOR = require(__dirname + "/../classes/XOR");
const config = require(__dirname + "/../settings");
let orbs = [0, 0, 50, 75, 125, 175, 225, 275, 350, 425, 500] let orbs = [0, 0, 50, 75, 125, 175, 225, 275, 350, 425, 500]
let length = ['Tiny', 'Short', 'Medium', 'Long', 'XL'] let length = ['Tiny', 'Short', 'Medium', 'Long', 'XL']
let difficulty = { 0: 'Unrated', 10: 'Easy', 20: 'Normal', 30: 'Hard', 40: 'Harder', 50: 'Insane' } let difficulty = { 0: 'Unrated', 10: 'Easy', 20: 'Normal', 30: 'Hard', 40: 'Harder', 50: 'Insane' }
class Level { class Level {
constructor(levelInfo, download, author = []) { constructor(levelInfo, server, download, author = []) {
if (!levelInfo[2]) return; if (!levelInfo[2]) return;
this.name = levelInfo[2]; this.name = levelInfo[2];
this.id = levelInfo[1]; 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.author = author[1] || "-"
this.authorID = levelInfo[6] this.authorID = levelInfo[6]
this.accountID = author[2] || 0 this.accountID = author[2] || 0
@ -20,13 +19,13 @@ class Level {
this.disliked = levelInfo[14] < 0 this.disliked = levelInfo[14] < 0
this.length = length[levelInfo[15]] || "XL" this.length = length[levelInfo[15]] || "XL"
this.stars = +levelInfo[18] 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.diamonds = levelInfo[18] < 2 ? 0 : parseInt(levelInfo[18]) + 2
this.featured = levelInfo[19] > 0 this.featured = levelInfo[19] > 0
this.epic = levelInfo[42] > 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" 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[28]) this.uploaded = levelInfo[28] + (server.timestampSuffix || "")
if (levelInfo[29]) this.updated = levelInfo[29] + config.timestampSuffix if (levelInfo[29]) this.updated = levelInfo[29] + (server.timestampSuffix || "")
if (download) { this.editorTime = +levelInfo[46] || 0; this.totalEditorTime = +levelInfo[47] || 0 } if (download) { this.editorTime = +levelInfo[46] || 0; this.totalEditorTime = +levelInfo[47] || 0 }
if (levelInfo[27]) this.password = levelInfo[27]; if (levelInfo[27]) this.password = levelInfo[27];
this.version = +levelInfo[5]; this.version = +levelInfo[5];
@ -53,7 +52,7 @@ class Level {
if (this.password && this.password != 0) { if (this.password && this.password != 0) {
let xor = new XOR(); 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); if (pass.length > 1) this.password = pass.slice(1);
else this.password = pass; else this.password = pass;
} }

View file

@ -78,7 +78,7 @@
</body> </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="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 type="text/javascript" src="../dragscroll.js"></script>
<script> <script>

View file

@ -86,7 +86,7 @@
</div> </div>
</body> </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="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> <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;")} 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> </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="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.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 type="text/javascript" src="../dragscroll.js"></script>
<script> <script>

View file

@ -128,7 +128,7 @@
</body> </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="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 type="text/javascript" src="../dragscroll.js"></script>
<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`) $('#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) { if (history) {
@ -445,6 +450,7 @@ let likeCount, likeImg;
let likedComments; let likedComments;
$(document).on('click', '.likeComment', function(cmnt) { $(document).on('click', '.likeComment', function(cmnt) {
if (gdps) return
commentID = $(this).attr('commentID') commentID = $(this).attr('commentID')
likedComments = localStorage.likedComments ? JSON.parse(localStorage.likedComments) : [] likedComments = localStorage.likedComments ? JSON.parse(localStorage.likedComments) : []

View file

@ -70,7 +70,7 @@
</body> </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="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 type="text/javascript" src="../dragscroll.js"></script>
<script> <script>

View file

@ -80,7 +80,7 @@
<br> <br>
<img src="../assets/btn-awarded.png" height="27%" class="valign gdButton levelSearch" search="awarded"> <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-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>
<div class="center"> <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=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" 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> <!-- <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> <!-- <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 class="diffDiv gdButton" diff=-3><img src="../assets/difficulties/auto.png"><h3 class="mini">Auto</h3></div>
</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=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 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> <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>
<div class="transparentBox center" style="width: 115vh; height: 6%; margin: 0.5% auto 1% auto; padding-top: 1%; padding-bottom: 0.5%;"> <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> </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="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> <script>
let filters = [] 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> </script>

View file

@ -21,6 +21,8 @@
<img src="../assets/gauntlets.png" width="50%"> <img src="../assets/gauntlets.png" width="50%">
</div> </div>
<img id="loading" style="margin-top: 1%" class="spin noSelect" src="../assets/loading.png" height="12%">
<div id="gauntletList"> <div id="gauntletList">
<br> <br>
</div> </div>
@ -30,12 +32,13 @@
</div> </div>
</body> </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="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> <script>
let gauntletNames = ["Fire", "Ice", "Poison", "Shadow", "Lava", "Bonus", "Chaos", "Demon", "Time", "Crystal", "Magic", "Spike", "Monster", "Doom", "Death"] 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 => { fetch('../api/gauntlets').then(res => res.json()).then(gauntlets => {
$('#loading').hide()
gauntlets.forEach((x, y) => { gauntlets.forEach((x, y) => {
let name = gauntletNames[x.id - 1] let name = gauntletNames[x.id - 1]
$('#gauntletList').append(` $('#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()"> <img id="creditsButton" class="gdButton" src="../assets/credits.png" width="60%" onclick="loadCredits()">
</div> </div>
<div style="position:absolute; bottom: 2.5%; right: 1.5%; text-align: right; width: 14%;"> <div style="position:absolute; bottom: 2.5%; right: 1.5%; text-align: right; width: 18%;">
<a href="../achievements"><img class="gdButton" src="../assets/achievements.png" width="40%"></a> <a href="../gdps"><img class="gdButton" src="../assets/basement.png" width="40%"></a>
</div> </div>
<div style="position:absolute; top: -1.5%; right: 10%; text-align: right; width: 10%;"> <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> <a href="../iconkit"><img class="iconRope" src="../assets/iconrope.png" width="40%"></a>
</div> </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> <a href="../messages"><img class="iconRope" src="../assets/messagerope.png" width="40%"></a>
</div> </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%;"> <div class="supercenter center" id="menuButtons" style="bottom: 5%;">
<table> <table>
<tr> <tr>
<td><a href="./search/*?type=saved"><img class="menubutton" src="../assets/category-saved.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" src="../assets/category-daily.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" src="../assets/category-weekly.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" src="../assets/category-gauntlets.png"></a></td> <td><a href="./gauntlets"><img class="menubutton menu-gauntlets" src="../assets/category-gauntlets.png"></a></td>
</tr> </tr>
<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;"> --> <!-- <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="./search/*?type=hof"><img class="menubutton menu-hof" src="../assets/category-hof.png"></a></td>
<td><a href="./mappacks"><img class="menubutton" src="../assets/category-packs.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" src="../assets/category-search.png"></a></td> <td><a href="./search"><img class="menubutton menu-search" src="../assets/category-search.png"></a></td>
</tr> </tr>
</table> </table>
@ -82,7 +92,7 @@
</body> </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="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> <script>
let page = 1 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 lastPage = res.credits.length + 1
res.credits.forEach((x, y) => { res.credits.forEach((x, y) => {
$('#credits').append(`<div id="credits${y+1}" class="subCredits" style="display: none;"> $('#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%;"> <div class="brownBox center supercenter" style="width: 80vh; height: 43%; padding-top: 1.5%; padding-bottom: 3.5%;">
<h1>${x.header}</h1><br> <h1>${x.header}</h1><br>
<h2 style="margin-bottom: 1.5%" class="gdButton"><a href="./u/${x.ign || x.name}">${x.name}</h2></a> <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}" height=30%; style="margin-bottom: 7%"><br> <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.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.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> <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) => { res.specialThanks.forEach((x, y) => {
n = x.split("/") n = x.split("/")
$('#specialthanks').append(`<div class="specialThanks"> $('#specialthanks').append(`<div class="specialThanks">
<h2 class="gdButton smaller"><a href="./u/${n[1] || n[0]}">${n[0]}</h2></a> <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]}" height=77%><br> <img class="creditsicon" icon="./icon/${n[1] || n[0]}?forceGD=1" height=77%><br>
</div>`) </div>`)
}) })
$('#credits').append(`<div id="closeCredits" class="center supercenter" style="width: 80vh; height: ${xButtonPos}%; pointer-events: none;"> $('#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>`) <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="center hidden"><br>
<div class="popup" id="steal"> <div class="popup" id="steal">
<div class="brownbox bounce center supercenter" style="height: 350px; width: 700px"> <div id="stealBox" class="brownbox bounce center supercenter" style="height: 340px; width: 700px">
<h1 class="center gold" style="margin-top: 10px">Copy Icon</h1> <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;"> <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> <div id="copyForms"></div>
<img src="../assets/ok.png" height=55px; class="postButton gdButton center" style="margin-top: 30px" id="fetchUser"> <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 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 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) { function appendIcon(form, formName) {
@ -470,6 +477,7 @@ fetch('./api/icons').then(res => {
fetch('../api/profile/' + user).then(res => res.json()) fetch('../api/profile/' + user).then(res => res.json())
.then(info => { .then(info => {
if (info == "-1") return
$(`#${formCopy}-${info[formCopy == "cube" ? "icon" : formCopy] || 1}`).trigger('click') $(`#${formCopy}-${info[formCopy == "cube" ? "icon" : formCopy] || 1}`).trigger('click')
$(`#col1-${info.col1}`).trigger('click') $(`#col1-${info.col1}`).trigger('click')
$(`#col2-${info.col2}`).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> <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"> <link rel="icon" href="../assets/trophy.png">
<meta id="meta-title" property="og:title" content="Leaderboards"> <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"> <meta id="meta-image" name="og:image" itemprop="image" content="https://gdbrowser.com/assets/trophy.png">
</head> </head>
@ -17,6 +17,10 @@
<img src="../assets/tab-top-on.png" class="leaderboardTab" id="topTabOn" style="display: none"> <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"> <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-on.png" class="sideSpaceC leaderboardTab" id="accurateTabOn">
<img src="../assets/tab-accurate-off.png" class="sideSpaceC leaderboardTab leaderboardClick" id="accurateTabOff" style="display: none"> <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="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.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 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 type="text/javascript" src="../dragscroll.js"></script>
<script> <script>
let sort = "stars" let sort = "stars"
let useTrophies = false let useTrophies = false
let modMode = false let modMode = false
let weekly = false
let trophies = [1, 3, 10, 25, 50, 75, 100] 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 = 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 = 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>.` `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>`) $('#searchBox').html(`<div style="height: 4.5%"></div>`)
$('#loading').show() $('#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() $('#accurateTabOn').remove()
$('#accurateTabOff').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') return $('#topTabOff').trigger('click')
} }
@ -123,21 +140,22 @@ function leaderboard(val) {
$('.ranking').remove() $('.ranking').remove()
if (modMode && sort == "cp") res = res.sort(function(a, b){return b.cp - a.cp}); 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"> $('#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)">` : ""} ${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> <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">${x.stars} <img class="valign" src="../assets/star.png" <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> style="cursor: help; height: 19%; transform: translate(-25%, -10%);" title="Stars"></h3>
<h3 class="lessSpaced leaderboardStats"> <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"> ${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">`}
<span${x.coins >= 149 ? " class='yellow'" : ""}>${x.coins}</span> <img class="valign" src="../assets/coin.png" style="cursor: help" title="Secret Coins"> ${wk ? "&nbsp;" : `<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"> ${wk || onePointNine ? "" : `<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"> ${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">` : ""} ${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> </h3>
<div class="center ranking" style="position:absolute; transform:scale(0.82) translate(-20.7vh, -20vh); height: 10%; width: 12.5%;"> <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) 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 () { $('#weeklyTabOff').click(function() {
if ($('#loading').is(":visible")) return if (type == "weekly" || !gdps) return;
sort = $(this).attr('sort') type = "weekly"
$('.sortButton').each(function() { leaderboard(type)
$(this).attr('src', $(this).attr('src').replace('-on', '').replace('.png', '') + ($(this).attr('sort') == sort ? "-on" : "") + ".png") $('.leaderboardTab').hide();
}) $('#topTabOff').show()
return leaderboard("accurate") $('#weeklyTabOn').show()
}) $('#creatorTabOff').show()
infoText(weeklyText)
$('.sortDiv').hide()
})
$('#topTabOff').click(function() { $('#creatorTabOff').click(function() {
if (type == "top") return; if (type == "creator") return;
type = "top" type = "creator"
leaderboard(type) leaderboard(type)
$('.leaderboardTab').hide(); $('.leaderboardTab').hide();
$('#topTabOn').show() $('#topTabOff').show()
$('#accurateTabOff').show() $(weekly ? '#weeklyTabOff' : '#accurateTabOff').show()
$('#creatorTabOff').show() $('#creatorTabOn').show()
infoText(top250Text) infoText(creatorText)
$('.sortDiv').hide() $('.sortDiv').hide()
}) });
$('#accurateTabOff').click(function() { $('#modSort').click(function() {
if (type == "accurate") return; modMode = !modMode
type = "accurate" $(this).attr('src', `../assets/sort-mod${modMode ? "-on" : ""}.png`)
leaderboard(type) if (modMode) { $('#cpSort').show() }
$('.leaderboardTab').hide(); else { $('#cpSort').hide(); if (sort == "cp") $('#starSort').trigger('click') }
$('#topTabOff').show() leaderboard(type)
$('#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)
})
</script> </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="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="infoButton" src="../assets/info.png" onclick="$('#infoDiv').show()"><br>
<!-- <img class="gdButton sideButton" id="likeButton" src="../assets/vote.png" onclick="$('#likeDiv').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="./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> <a href="./leaderboard/[[ID]]"><img id="leaderboardbtn" class="gdButton sideButton" src="../assets/leaderboard.png"></a><br>
</div> </div>
@ -158,7 +158,7 @@
</body> </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="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> <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>.' 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('[[TIME2]]', "")
.replace('[[UPLOAD]]', "") .replace('[[UPLOAD]]', "")
.replace('[[UPDATE]]', "") + .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() if (![[LARGE]]) $('#largeBadge').hide()
@ -259,11 +259,14 @@ if ("[[SONGID]]".startsWith("Level")) {
$('#songInfo').text('[[SONGID]]') $('#songInfo').text('[[SONGID]]')
$('.songLink').hide() $('.songLink').hide()
} }
else if ("[[GDPS]]" == "true") { else $('#checkSong').show()
if (!"[[GDPS]]".startsWith("[")) {
$('#playSong').hide() $('#playSong').hide()
$('#moreSongs').hide() $('#moreSongs').hide()
$('#leaderboardbtn').hide()
$('#checkSong').remove()
} }
else $('#checkSong').show()
if ("[[SONGAUTHOR]]" == "Unknown" || "[[INVALIDSONG]]" == "true") $('.songLink').hide() if ("[[SONGAUTHOR]]" == "Unknown" || "[[INVALIDSONG]]" == "true") $('.songLink').hide()
if ("[[DISLIKED]]" == "true") $('#likeImg').attr('src', '../assets/dislike.png').css('transform', 'translateY(20%)') 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]] > 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 ([[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") { if ("[[ACCOUNTID]]" == "0") {
$("#authorName").addClass("green").addClass("unregistered") $("#authorName").addClass("green").addClass("unregistered")
$("#authorLink").attr('href', '/search/[[AUTHORID]]?user') $("#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="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.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 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 type="text/javascript" src="../dragscroll.js"></script>
<script> <script>

View file

@ -17,6 +17,8 @@
<h1 style="transform:scale(1.2)">Map Packs</h1> <h1 style="transform:scale(1.2)">Map Packs</h1>
</div> </div>
<img id="loading" style="margin-top: 1%" class="spin noSelect" src="../assets/loading.png" height="12%">
<div id="packList"> <div id="packList">
<br> <br>
</div> </div>
@ -28,10 +30,11 @@
</div> </div>
</body> </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="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> <script>
fetch('../api/mappacks').then(res => res.json()).then(packs => { fetch('../api/mappacks').then(res => res.json()).then(packs => {
$('#loading').hide()
packs.forEach(x => { packs.forEach(x => {
$('#packList').append(` $('#packList').append(`
<div class="mappack"> <div class="mappack">

View file

@ -215,7 +215,7 @@
</body> </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="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> <script>
let accountID; let accountID;

View file

@ -34,7 +34,7 @@
</body> </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="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> <script>
let line = 0 let line = 0

View file

@ -148,7 +148,7 @@
</body> </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="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 type="text/javascript" src="../dragscroll.js"></script>
<script> <script>

View file

@ -106,7 +106,7 @@
</body> </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="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 type="text/javascript" src="https://asvd.github.io/dragscroll/dragscroll.js"></script>
<script> <script>
@ -150,7 +150,7 @@ function Append(firstLoad) {
if (page == 0) $('#pageDown').hide() if (page == 0) $('#pageDown').hide()
else $('#pageDown').show() 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 } if (res == '-1' || res.length == 0) { $('#loading').hide(); $('#pageUp').hide(); return loading = false }
@ -171,8 +171,9 @@ function Append(firstLoad) {
res.forEach((x, y) => { res.forEach((x, y) => {
let hasAuthor = (x.accountID != "0") let hasAuthor = (x.accountID != "0")
let userSearch = (type == 5 || typeof userMode == 'string')
if (demonList) x.demonID = (res.length * page) + y + 1 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") $('#header').text(((x.author == "-" ? "Someone" : x.author)) + (x.author.toLowerCase().endsWith('s') ? "'" : "'s") + " levels")
document.title = $('#header').text() document.title = $('#header').text()
accID = x.authorID accID = x.authorID
@ -182,7 +183,7 @@ function Append(firstLoad) {
if (!filteredSong) filteredSong = x.songName if (!filteredSong) filteredSong = x.songName
$('#searchBox').append(`<div class="searchresult"> $('#searchBox').append(`<div class="searchresult">
<h1 class="lessspaced pre">${x.name}</h1> <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 pre ${x.customSong == 0 ? "blue" : "whatIfItWasPurple"}" style="overflow: hidden; max-height: 19%">${filteredSong}</h3>
<h3 class="lessSpaced"> <h3 class="lessSpaced">
<img class="valign" src="../assets/time.png" height="14%"> ${x.length} <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 express = require('express');
const fs = require("fs") const request = require('request');
const compression = require('compression'); const compression = require('compression');
const timeout = require('connect-timeout') const timeout = require('connect-timeout');
const rateLimit = require("express-rate-limit"); const rateLimit = require("express-rate-limit");
const fs = require("fs");
const app = express(); 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.js')
app.config = require('./settings') // tweak settings in this file if you're using a GDPS app.servers = require('./servers.json')
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
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." + 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>" + " 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'] } keyGenerator: function(req) { return req.headers['x-real-ip'] || req.headers['x-forwarded-for'] }
}) })
let api = true; let forms = { "player": "cube", "bird": "ufo", "dart": "wave" }
let gdIcons = fs.readdirSync('./assets/previewicons') 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 sampleIcons = require('./misc/sampleIcons.json')
let achievements = require('./misc/achievements.json') let achievements = require('./misc/achievements.json')
let achievementTypes = require('./misc/achievementTypes.json') let achievementTypes = require('./misc/achievementTypes.json')
let shopIcons = require('./misc/shops.json') let shopIcons = require('./misc/shops.json')
let colorList = require('./icons/colors.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 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 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(compression());
app.use(express.json()); app.use(express.json());
app.use(express.urlencoded({extended: true})); app.use(express.urlencoded({extended: true}));
app.use(timeout('20s')); 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) { req.gdParams = function(obj={}, substitute=true) {
Object.keys(app.config.params).forEach(x => { if (!obj[x]) obj[x] = app.config.params[x] }) 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 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} : {}} let params = {form: obj, headers: app.config.ipForwarding && ip ? {'x-forwarded-for': ip, 'x-real-ip': ip} : {}}
if (substitute) { // GDPS substitutions in settings.js if (substitute) { // GDPS substitutions in settings.js
for (let sub in app.config.substitutions) { for (let ss in req.server.substitutions) {
if (params.form[sub]) { params.form[app.config.substitutions[sub]] = params.form[sub]; delete params.form[sub] } if (params.form[ss]) { params.form[req.server.substitutions[ss]] = params.form[ss]; delete params.form[ss] }
} }
} }
return params 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() next()
}) })
let directories = [""] let directories = [""]
fs.readdirSync('./api').filter(x => !x.includes(".")).forEach(x => directories.push(x)) fs.readdirSync('./api').filter(x => !x.includes(".")).forEach(x => directories.push(x))
app.trackSuccess = function() { app.trackSuccess = function(id) {
// made this a function in case i wanna do more stuff in the future app.lastSuccess[id] = Date.now()
app.lastSuccess = Date.now() if (!app.actuallyWorked[id]) app.actuallyWorked[id] = true
} }
app.timeSince = function(time=app.lastSuccess) { app.timeSince = function(id, time) {
if (!time) return "[unknown]" if (!time) time = app.lastSuccess[id]
let secsPassed = Math.floor((Date.now() - time) / 1000) let secsPassed = Math.floor((Date.now() - time) / 1000)
let minsPassed = Math.floor(secsPassed / 60) let minsPassed = Math.floor(secsPassed / 60)
secsPassed -= 60 * minsPassed; 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 = {} app.run = {}
directories.forEach(d => { directories.forEach(d => {
fs.readdirSync('./api/' + d).forEach(x => {if (x.includes('.')) app.run[x.split('.')[0]] = require('./api/' + d + "/" + x) }) 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 return res
} }
app.xor = new XOR()
//xss bad //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;")} 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) } if (fs.existsSync(path)) { files = fs.readdirSync(path) }
assetPage = fs.readFileSync('./html/assets.html', 'utf8') 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('.'))}) 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)) 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("/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("/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", RL, 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/: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("/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("/sendMessage", RL, function(req, res) { app.run.sendMessage(app, req, res) })
app.post("/accurateLeaderboard", function(req, res) { app.run.accurate(app, req, res, true) }) app.post("/accurateLeaderboard", function(req, res) { app.run.accurate(app, req, res, true) })
// HTML // HTML
let onePointNineDisabled = ['daily', 'weekly', 'gauntlets', 'messages']
let downloadDisabled = ['daily', 'weekly']
let gdpsHide = ['achievements', 'messages']
app.get("/", function(req, res) { app.get("/", function(req, res) {
if (app.offline && !req.query.hasOwnProperty("home")) res.sendFile(__dirname + "/html/offline.html") if (req.offline && !req.query.hasOwnProperty("home")) res.sendFile(__dirname + "/html/offline.html")
else res.sendFile(__dirname + "/html/home.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("/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("/api", function(req, res) { res.sendFile(__dirname + "/html/api.html") })
app.get("/boomlings", function(req, res) { res.sendFile(__dirname + "/html/boomlings.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("/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("/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("/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("/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", function(req, res) { res.sendFile(__dirname + "/html/leaderboard.html") })
app.get("/leaderboard/:text", function(req, res) { res.sendFile(__dirname + "/html/levelboard.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 // 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/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/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/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/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/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/level/:id", RL, function(req, res) { app.run.level(app, req, res, true) })
app.get("/api/mappacks", async function(req, res) { app.run.mappacks(app, req, res) }) 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, api) }) 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/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) }) 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 // MISC
app.get("/icon/:text", function(req, res) { app.run.icon(app, req, res) }) 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/achievements", function(req, res) { res.send({achievements, types: achievementTypes, shopIcons, colors: colorList }) })
app.get('/api/icons', function(req, res) { app.get('/api/icons', function(req, res) {
let sample = [JSON.stringify(sampleIcons[Math.floor(Math.random() * sampleIcons.length)].slice(1))] 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) { app.get('*', function(req, res) {

View file

@ -34,7 +34,22 @@ function backButton() {
else window.location.href = "../../../../../" 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; let allowEsc = true;
$(document).keydown(function(k) { $(document).keydown(function(k) {
if (k.keyCode == 27) { //esc if (k.keyCode == 27) { //esc
if (!allowEsc) return 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 used to be a place for GDPS settings but that has all been moved over to servers.json
// This isn't a JSON because you can't leave comments on them, ew // Feel free to enable/disable stuff here for smoother local use, free of rate limits
module.exports = { module.exports = {
port: 2000, // Port to host website on 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 params: { // Always send this stuff to the servers
secret: 'Wmfd2893gb7', secret: 'Wmfd2893gb7',
gameVersion: '21', gameVersion: '21',
binaryVersion: '35', 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. 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. 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. 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"
}
} }