diff --git a/README.md b/README.md
index 091df1c..7d4d1da 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# GDBrowser
-Uh... so I've never actually used GitHub. 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.
@@ -16,21 +16,36 @@ 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)
-/blocks and /objects are used for the analysis page. I just put them in seperate folders for extra neatness.
+/blocks, /objects and /initial are used for the analysis page. I just put them in seperate folders for extra neatness.
/gdfaces holds all the difficulty faces
+/css has the CSS stuff. They're in a special folder so browsers won't cache them (in case of updates)
+
Figure out what /gauntlets and /iconkitbuttons have.
+## Classes
+What's a class you ask? I still have no idea.
+
+Seriously, these things are *confusing*
+
+I guess the best way to put it is uh... super fancy functions???
+
+Level.js parses the server's disgusting response and sends back a nice object with all the level info
+
+XOR.js encrypts/decrypts stuff like GD passwords. I stole the code from somewhere so uh if you wrote it, please don't hunt me down
+
## 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.
+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
-Also contains the very important .plist file
+parsePlist.js reads GJ_GameSheet02-uhd.plist and magically transforms it into gameSheet.json. Props to 101arrowz for making this
-/cache is a folder for cached generated icons
+forms.json is a list of the different icon forms, their ingame filenames, and their index in responses from the GD servers
/iconkit is a folder for the little grey preview icons on the icon kit
@@ -53,14 +68,16 @@ objects.json - IDs for portals, orbs, triggers, and misc stuff
colors.json - The colors for generating icons
+credits.json - Credits! (shown on the homepage)
+
level.json - An array of the official GD tracks, and also difficulty face stuff for level searching
-mapPacks.json - The IDs for the levels in map packs. I can't believe I have to hardcode this.
+mapPacks.json - The IDs for the levels in map packs. I can't believe I have to hardcode this
+
+secretStuff.json - GJP goes here, needed for level leaderboards. Not included in the repo for obvious reasons
sizecheck.js - Excecuted on most pages, used for the 'page isn't wide enough' message, back button, and a few other things
-XOR.js - Decrypts ciphered GD passwords. I stole the code from somewhere so uh if you wrote it, please don't hunt me down
-
---
happy painting and god bless.
\ No newline at end of file
diff --git a/api/comments.js b/api/comments.js
index 28d75be..9742749 100644
--- a/api/comments.js
+++ b/api/comments.js
@@ -41,6 +41,11 @@ module.exports = async (app, req, res) => {
comment.ID = x[6]
comment.likes = x[4]
comment.date = (x[9] || "?") + " ago"
+ if (comment.content.endsWith("⍟")) {
+ comment.content = comment.content.slice(0, -1)
+ comment.browserColor = true
+ }
+
if (req.query.type != "profile") {
comment.username = y[1] || "Unknown"
comment.levelID = x[1] || req.params.id
@@ -48,9 +53,6 @@ module.exports = async (app, req, res) => {
comment.accountID = y[16]
comment.form = ['icon', 'ship', 'ball', 'ufo', 'wave', 'robot', 'spider'][Number(y[14])]
if (x[10] > 0) comment.percent = x[10]
- if (comment.content.endsWith("⍟")) {
- comment.content = comment.content.slice(0, -1)
- comment.browserColor = true }
if (x[12] && x[12].includes(',')) comment.modColor = true
}
diff --git a/api/download.js b/api/download.js
index d6339a0..e690294 100644
--- a/api/download.js
+++ b/api/download.js
@@ -1,152 +1,101 @@
const request = require('request')
const fs = require('fs')
-const XOR = require('../misc/XOR.js');
-const xor = new XOR();
+const Level = require('../classes/Level.js')
module.exports = async (app, req, res, api, ID, analyze) => {
- let orbs = [0, 0, 50, 75, 125, 175, 225, 275, 350, 425, 500]
- let length = ['Tiny', 'Short', 'Medium', 'Long', 'XL']
- let difficulty = {0: 'Unrated', 10: 'Easy', 20: 'Normal', 30: 'Hard', 40: 'Harder', 50: 'Insane'}
+ let levelID = ID || req.params.id
+ if (levelID == "daily") levelID = -1
+ else if (levelID == "weekly") levelID = -2
+ else levelID = levelID.replace(/[^0-9]/g, "")
- let levelID = ID || req.params.id
- if (levelID == "daily") levelID = -1
- else if (levelID == "weekly") levelID = -2
- else levelID = levelID.replace(/[^0-9]/g, "")
+ request.post('http://boomlings.com/database/downloadGJLevel22.php', {
+ form: {
+ levelID,
+ secret: app.secret
+ }
+ }, async function (err, resp, body) {
- request.post('http://boomlings.com/database/downloadGJLevel22.php', {
- form : {
- levelID,
- secret : app.secret
- }}, async function(err, resp, body) {
+ if (err || !body || body == '-1') {
+ if (!api && levelID < 0) return res.redirect('/')
+ if (!api) return res.redirect('search/' + req.params.id)
+ else return res.send("-1")
+ }
- if (err || !body || body == '-1') {
- if (!api && levelID < 0) return res.redirect('/')
- if (!api) return res.redirect('search/' + req.params.id)
- else return res.send("-1")
- }
+ let levelInfo = app.parseResponse(body)
+ let level = new Level(levelInfo)
- let levelInfo = app.parseResponse(body)
- let level = {
- name: levelInfo[2],
- id: levelInfo[1],
- description: Buffer.from(levelInfo[3], 'base64').toString() || "(No description provided)",
- author: "-",
- authorID: levelInfo[6],
- accountID: 0,
- difficulty: difficulty[levelInfo[9]],
- downloads: levelInfo[10],
- likes: levelInfo[14],
- disliked : levelInfo[14] < 0,
- length: length[levelInfo[15]],
- stars: levelInfo[18],
- orbs: orbs[levelInfo[18]],
- diamonds: levelInfo[18] < 2 ? 0 : parseInt(levelInfo[18]) + 2,
- featured: levelInfo[19] > 0,
- epic: levelInfo[42] == 1,
- uploaded: levelInfo[28] + ' ago', //not given in search
- updated: levelInfo[29] + ' ago', //not given in search
- version: levelInfo[5],
- password: levelInfo[27],
- copiedID: levelInfo[30],
- officialSong: levelInfo[12] != 0 ? parseInt(levelInfo[12]) + 1 : 0,
- customSong: levelInfo[35],
- coins: levelInfo[37],
- verifiedCoins: levelInfo[38] == 1,
- starsRequested: levelInfo[39],
- ldm: levelInfo[40] == 1, //not given in search
- objects: levelInfo[45] == "65535" ? "65000+" : levelInfo[45],
- large: levelInfo[45] > 40000,
- }
- if (level.password != "0") {
+ request.post('http://boomlings.com/database/getGJUsers20.php', {
+ form: { str: level.authorID, secret: app.secret }
+ }, function (err1, res1, b1) {
+ let gdSearchResult = app.parseResponse(b1)
+ request.post('http://boomlings.com/database/getGJUserInfo20.php', {
+ form: { targetAccountID: gdSearchResult[16], secret: app.secret }
+ }, function (err2, res2, b2) {
+ if (b2 != '-1') {
+ let account = app.parseResponse(b2)
+ level.author = account[1]
+ level.accountID = gdSearchResult[16]
+ }
- let pass = level.password
- pass = xor.decrypt(pass, 26364);
- if (pass.length > 1) level.password = pass.slice(1);
- else level.password = pass
- }
-
- level.cp = (level.stars > 0) + level.featured + level.epic
-
- if (levelInfo[17] == 1) level.difficulty += ' Demon'
- if (level.difficulty == "Insane Demon") level.difficulty = "Extreme Demon"
- else if (level.difficulty == "Harder Demon") level.difficulty = "Insane Demon"
- else if (level.difficulty == "Normal Demon") level.difficulty = "Medium Demon"
- else if (levelInfo[25] == 1) level.difficulty = 'Auto'
- level.difficultyFace = `${levelInfo[17] != 1 ? level.difficulty.toLowerCase() : `demon-${level.difficulty.toLowerCase().split(' ')[0]}`}${level.epic ? '-epic' : `${level.featured ? '-featured' : ''}`}`
-
-
- request.post('http://boomlings.com/database/getGJUsers20.php', {
- form: {str: level.authorID, secret: app.secret}
- }, function (err1, res1, b1) {
- let gdSearchResult = app.parseResponse(b1)
- request.post('http://boomlings.com/database/getGJUserInfo20.php', {
- form: {targetAccountID: gdSearchResult[16], secret: app.secret}
- }, function (err2, res2, b2) {
- if (b2 != '-1') {
- let account = app.parseResponse(b2)
- level.author = account[1]
- level.accountID = gdSearchResult[16]
- }
-
- else {
- level.author = "-"
- level.accountID = "0"
- }
+ else {
+ level.author = "-"
+ level.accountID = "0"
+ }
request.post('http://boomlings.com/database/getGJSongInfo.php', {
- form : {
- songID : level.customSong,
- secret : app.secret
- }}, async function(err, resp, songRes) {
+ form: {
+ songID: level.customSong,
+ secret: app.secret
+ }
+ }, async function (err, resp, songRes) {
- if (songRes != '-1') {
- let songData = app.parseResponse(songRes, '~|~')
- level.songName = songData[2] || "Unknown"
- level.songAuthor = songData[4] || "Unknown"
- level.songSize = (songData[5] || "0") + "MB"
- level.songID = songData[1] || level.customSong
- if (!songData[2]) level.invalidSong = true
- }
+ if (songRes != '-1') {
+ let songData = app.parseResponse(songRes, '~|~')
+ level.songName = songData[2] || "Unknown"
+ level.songAuthor = songData[4] || "Unknown"
+ level.songSize = (songData[5] || "0") + "MB"
+ level.songID = songData[1] || level.customSong
+ if (!songData[2]) level.invalidSong = true
+ }
- else {
- let foundSong = require('../misc/level.json').music[parseInt(levelInfo[12]) + 1] || {"null": true}
- level.songName = foundSong[0] || "Unknown"
- level.songAuthor = foundSong[1] || "Unknown"
- level.songSize = "0MB"
- level.songID = "Level " + [parseInt(levelInfo[12]) + 1]
- }
+ else {
+ let foundSong = require('../misc/level.json').music[parseInt(levelInfo[12]) + 1] || { "null": true }
+ level.songName = foundSong[0] || "Unknown"
+ level.songAuthor = foundSong[1] || "Unknown"
+ level.songSize = "0MB"
+ level.songID = "Level " + [parseInt(levelInfo[12]) + 1]
+ }
- level.data = levelInfo[4]
+ level.data = levelInfo[4]
- if (analyze) return app.modules.analyze(app, req, res, level)
+ if (analyze) return app.modules.analyze(app, req, res, level)
- function sendLevel() {
- if (api) return res.send(level)
+ function sendLevel() {
+ if (api) return res.send(level)
+
+ else return fs.readFile('./html/level.html', 'utf8', function (err, data) {
+ let html = data;
+ let variables = Object.keys(level)
+ variables.forEach(x => {
+ let regex = new RegExp(`\\[\\[${x.toUpperCase()}\\]\\]`, "g")
+ html = html.replace(regex, app.clean(level[x]))
+ })
+ return res.send(html)
+ })
+ }
+
+ if (level.difficulty == "Extreme Demon") {
+ request.get('https://www.pointercrate.com/api/v1/demons/?name=' + level.name.trim(), function (err, resp, demonList) {
+ let demon = JSON.parse(demonList)
+ if (demon[0] && demon[0].position <= 150) level.demonList = demon[0].position
+ return sendLevel()
+ })
+ }
+
+ return sendLevel()
- else return fs.readFile('./html/level.html', 'utf8', function(err, data) {
- let html = data;
- let variables = Object.keys(level)
- variables.forEach(x => {
- let regex = new RegExp(`\\[\\[${x.toUpperCase()}\\]\\]`, "g")
- html = html.replace(regex, app.clean(level[x]))
})
- return res.send(html)
- })
- }
-
- //demon list stuff
- if (level.difficulty == "Extreme Demon") {
- request.get('https://www.pointercrate.com/api/v1/demons/?name=' + level.name.trim(), async function(err, resp, demonList) {
- let demon = JSON.parse(demonList)
- if (demon[0] && demon[0].position <= 150) level.demonList = demon[0].position
- return sendLevel()
- })
- }
-
- else return sendLevel()
-
})
})
})
-})
}
\ No newline at end of file
diff --git a/api/level.js b/api/level.js
index e8db6ad..8993c54 100644
--- a/api/level.js
+++ b/api/level.js
@@ -1,29 +1,27 @@
const request = require('request')
const fs = require('fs')
+const Level = require('../classes/Level.js')
module.exports = async (app, req, res, api, analyze) => {
- let orbs = [0, 0, 50, 75, 125, 175, 225, 275, 350, 425, 500]
- let length = ['Tiny', 'Short', 'Medium', 'Long', 'XL']
- let difficulty = {0: 'Unrated', 10: 'Easy', 20: 'Normal', 30: 'Hard', 40: 'Harder', 50: 'Insane'}
-
let levelID = req.params.id
if (levelID == "daily") return app.modules.download(app, req, res, api, 'daily', analyze)
else if (levelID == "weekly") return app.modules.download(app, req, res, api, 'weekly', analyze)
else if (levelID.match(/[^0-9]/)) {
- if (!api) return res.redirect('search/' + req.params.id)
- else return res.send("-1")
+ if (!api) return res.redirect('search/' + req.params.id)
+ else return res.send("-1")
}
else levelID = levelID.replace(/[^0-9]/g, "")
if (analyze || req.query.hasOwnProperty("download")) return app.modules.download(app, req, res, api, levelID, analyze)
request.post('http://boomlings.com/database/getGJLevels21.php', {
- form : {
- str : levelID,
- secret : app.secret,
+ form: {
+ str: levelID,
+ secret: app.secret,
type: 0
- }}, async function(err, resp, body) {
+ }
+ }, async function (err, resp, body) {
if (err || !body || body == '-1') {
if (!api) return res.redirect('search/' + req.params.id)
@@ -32,90 +30,52 @@ module.exports = async (app, req, res, api, analyze) => {
let preRes = body.split('#')[0].split('|', 10)
let author = body.split('#')[1].split('|')[0].split(':')
- let song = '~' + body.split('#')[2];
- song = app.parseResponse(song, '~|~')
+ let song = '~' + body.split('#')[2];
+ song = app.parseResponse(song, '~|~')
let levelInfo = app.parseResponse(preRes[0])
- let level = {
- name: levelInfo[2],
- id: levelInfo[1],
- description: Buffer.from(levelInfo[3], 'base64').toString() || "(No description provided)",
- author: author[1] || "-",
- authorID: levelInfo[6],
- accountID: author[2] || 0,
- difficulty: difficulty[levelInfo[9]],
- downloads: levelInfo[10],
- likes: levelInfo[14],
- disliked : levelInfo[14] < 0,
- length: length[levelInfo[15]] || "?",
- stars: levelInfo[18],
- orbs: orbs[levelInfo[18]],
- diamonds: levelInfo[18] < 2 ? 0 : parseInt(levelInfo[18]) + 2,
- featured: levelInfo[19] > 0,
- epic: levelInfo[42] == 1,
- //uploaded: levelInfo[28] + ' ago',
- //updated: levelInfo[29] + ' ago',
- version: levelInfo[5],
- copiedID: levelInfo[30],
- officialSong: levelInfo[12] != 0 ? parseInt(levelInfo[12]) + 1 : 0,
- customSong: levelInfo[35],
- coins: levelInfo[37],
- verifiedCoins: levelInfo[38] == 1,
- starsRequested: levelInfo[39],
- //ldm: levelInfo[40] == 1, //not given in search
- objects: levelInfo[45] == "65535" ? "65000+" : levelInfo[45],
- large: levelInfo[45] > 40000
- }
+ let level = new Level(levelInfo, author)
- level.cp = (level.stars > 0) + level.featured + level.epic
- if (levelInfo[17] == 1) level.difficulty += ' Demon'
- if (level.difficulty == "Insane Demon") level.difficulty = "Extreme Demon"
- else if (level.difficulty == "Harder Demon") level.difficulty = "Insane Demon"
- else if (level.difficulty == "Normal Demon") level.difficulty = "Medium Demon"
- else if (levelInfo[25] == 1) level.difficulty = 'Auto'
- level.difficultyFace = `${levelInfo[17] != 1 ? level.difficulty.toLowerCase() : `demon-${level.difficulty.toLowerCase().split(' ')[0]}`}${level.epic ? '-epic' : `${level.featured ? '-featured' : ''}`}`
+ if (song[2]) {
+ level.songName = song[2] || "Unknown"
+ level.songAuthor = song[4] || "Unknown"
+ level.songSize = (song[5] || "0") + "MB"
+ level.songID = song[1] || level.customSong
+ }
- if (song[2]) {
- level.songName = song[2] || "Unknown"
- level.songAuthor = song[4] || "Unknown"
- level.songSize = (song[5] || "0") + "MB"
- level.songID = song[1] || level.customSong
- }
-
- else {
- let foundSong = require('../misc/level.json').music[parseInt(levelInfo[12]) + 1] || {"null": true}
- level.songName = foundSong[0] || "Unknown"
- level.songAuthor = foundSong[1] || "Unknown"
- level.songSize = "0MB"
- level.songID = "Level " + [parseInt(levelInfo[12]) + 1]
- }
+ else {
+ let foundSong = require('../misc/level.json').music[parseInt(levelInfo[12]) + 1] || { "null": true }
+ level.songName = foundSong[0] || "Unknown"
+ level.songAuthor = foundSong[1] || "Unknown"
+ level.songSize = "0MB"
+ level.songID = "Level " + [parseInt(levelInfo[12]) + 1]
+ }
function sendLevel() {
- if (api) return res.send(level)
+ if (api) return res.send(level)
- else return fs.readFile('./html/level.html', 'utf8', function(err, data) {
- let html = data;
- let variables = Object.keys(level)
- variables.forEach(x => {
- let regex = new RegExp(`\\[\\[${x.toUpperCase()}\\]\\]`, "g")
- html = html.replace(regex, app.clean(level[x]))
+ else return fs.readFile('./html/level.html', 'utf8', function (err, data) {
+ let html = data;
+ let variables = Object.keys(level)
+ variables.forEach(x => {
+ let regex = new RegExp(`\\[\\[${x.toUpperCase()}\\]\\]`, "g")
+ html = html.replace(regex, app.clean(level[x]))
+ })
+ return res.send(html)
})
- return res.send(html)
- })
- }
+ }
- //demon list stuff
- if (level.difficulty == "Extreme Demon") {
- request.get('https://www.pointercrate.com/api/v1/demons/?name=' + level.name.trim(), async function(err, resp, demonList) {
+ if (level.difficulty == "Extreme Demon") {
+ request.get('https://www.pointercrate.com/api/v1/demons/?name=' + level.name.trim(), function (err, resp, demonList) {
let demon = JSON.parse(demonList)
if (demon[0] && demon[0].position <= 150) level.demonList = demon[0].position
return sendLevel()
})
- }
+ }
else return sendLevel()
- })
+ })
}
\ No newline at end of file
diff --git a/api/like.js b/api/like.js
index 544e1ca..e1517ab 100644
--- a/api/like.js
+++ b/api/like.js
@@ -1,5 +1,5 @@
const request = require('request')
-const XOR = require('../misc/XOR.js');
+const XOR = require('../classes/XOR.js');
const xor = new XOR();
const crypto = require('crypto')
function sha1(data) { return crypto.createHash("sha1").update(data, "binary").digest("hex"); }
diff --git a/api/postComment.js b/api/postComment.js
index 74ce2f9..f26386f 100644
--- a/api/postComment.js
+++ b/api/postComment.js
@@ -1,5 +1,5 @@
const request = require('request')
-const XOR = require('../misc/XOR.js');
+const XOR = require('../classes/XOR.js');
const xor = new XOR();
const crypto = require('crypto')
function sha1(data) { return crypto.createHash("sha1").update(data, "binary").digest("hex"); }
diff --git a/api/postProfileComment.js b/api/postProfileComment.js
new file mode 100644
index 0000000..43f764f
--- /dev/null
+++ b/api/postProfileComment.js
@@ -0,0 +1,40 @@
+const request = require('request')
+const XOR = require('../classes/XOR.js');
+const xor = new XOR();
+const crypto = require('crypto')
+function sha1(data) { return crypto.createHash("sha1").update(data, "binary").digest("hex"); }
+
+module.exports = async (app, req, res) => {
+
+ if (!req.body.comment) return res.status(400).send("No comment provided!")
+ if (!req.body.username) return res.status(400).send("No username 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.comment.includes('\n')) return res.status(400).send("Profile posts cannot contain line breaks!")
+
+ let params = {
+ gameVersion: '21',
+ binaryVersion: '35',
+ secret: app.secret,
+ cType: '1'
+ }
+
+ params.comment = new Buffer(req.body.comment.slice(0, 190) + (req.body.color ? "⍟" : "")).toString('base64').replace(/\//g, '_').replace(/\+/g, "-")
+ params.gjp = xor.encrypt(req.body.password, 37526)
+ params.accountID = req.body.accountID.toString()
+ params.userName = req.body.username
+
+ let chk = params.userName + params.comment + "1xPT6iUrtws0J"
+ chk = sha1(chk)
+ chk = xor.encrypt(chk, 29481)
+ params.chk = chk
+
+ request.post('http://boomlings.com/database/uploadGJAccComment20.php', {
+ form: params
+ }, function (err, resp, body) {
+ if (err) return res.status(400).send("The Geometry Dash servers returned an error! Perhaps they're down for maintenance")
+ 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.")
+ res.status(200).send(`Comment posted to ${params.userName} with ID ${body}`)
+ })
+}
\ No newline at end of file
diff --git a/api/search.js b/api/search.js
index 6cd9f81..594d9db 100644
--- a/api/search.js
+++ b/api/search.js
@@ -4,6 +4,7 @@ const difficulty = {0: 'Unrated', 10: 'Easy', 20: 'Normal', 30: 'Hard', 40: 'Har
const length = ['Tiny', 'Short', 'Medium', 'Long', 'XL']
const mapPacks = require('../misc/mapPacks.json')
const levels = require('../misc/level.json').music
+const Level = require('../classes/Level.js')
module.exports = async (app, req, res) => {
@@ -85,71 +86,47 @@ module.exports = async (app, req, res) => {
authorList[arr[0]] = [arr[1], arr[2]]})
let levelArray = preRes.map(x => app.parseResponse(x))
+ let parsedLevels = []
await levelArray.forEach(async (x, y) => {
- let keys = Object.keys(x)
-
- if (filters.page == 0) { //this is broken if you're not on page 0
- let pages = splitBody[3].split(":");
- x.results = +pages[0];
- x.pages = +Math.ceil(pages[0] / 10);
- }
-
- x.name = x[2];
- x.id = x[1];
- x.description = Buffer.from(x[3], 'base64').toString() || "(No description provided)",
- x.author = authorList[x[6]] ? authorList[x[6]][0] : "-";
- x.authorID = x[6];
- x.accountID = authorList[x[6]] ? authorList[x[6]][1] : "0";
- x.difficulty = difficulty[x[9]];
- x.downloads = x[10];
- x.likes = x[14];
- x.disliked = x[14] < 0;
- x.length = length[x[15]] || "?";
- x.stars = x[18];
- x.orbs = orbs[x[18]];
- x.diamonds = x[18] < 2 ? 0 : parseInt(x[18]) + 2;
- x.featured = x[19] > 0;
- x.epic = x[42] == 1;
- x.version = x[5];
- x.copiedID = x[30];
- x.officialSong = x[12] != 0 ? parseInt(x[12]) + 1 : 0;
- x.customSong = x[35];
- x.coins = x[37];
- x.verifiedCoins = x[38] == 1;
- x.starsRequested = x[39];
- x.objects = x[45];
- x.large = x[45] > 40000;
- x.cp = (x.stars > 0) + x.featured + x.epic;
-
- if (x[17] == 1) x.difficulty += ' Demon'
- if (x.difficulty == "Insane Demon") x.difficulty = "Extreme Demon"
- else if (x.difficulty == "Harder Demon") x.difficulty = "Insane Demon"
- else if (x.difficulty == "Normal Demon") x.difficulty = "Medium Demon"
- else if (x[25] == 1) x.difficulty = 'Auto'
- x.difficultyFace = `${x[17] != 1 ? x.difficulty.toLowerCase() : `demon-${x.difficulty.toLowerCase().split(' ')[0]}`}${x.epic ? '-epic' : `${x.featured ? '-featured' : ''}`}`
+ let level = new Level(x)
let songSearch = songs.find(y => y['~1'] == x[35])
+ level.author = authorList[x[6]] ? authorList[x[6]][0] : "-";
+ level.accountID = authorList[x[6]] ? authorList[x[6]][1] : "0";
+
if (songSearch) {
- x.songName = app.clean(songSearch[2] || "Unknown")
- x.songAuthor = songSearch[4] || "Unknown"
- x.songSize = (songSearch[5] || "0") + "MB"
- x.songID = songSearch[1] || x.customSong
+ level.songName = app.clean(songSearch[2] || "Unknown")
+ level.songAuthor = songSearch[4] || "Unknown"
+ level.songSize = (songSearch[5] || "0") + "MB"
+ level.songID = songSearch[1] || level.customSong
}
else {
let foundSong = require('../misc/level.json').music[parseInt(x[12]) + 1] || {"null": true}
- x.songName = foundSong[0] || "Unknown"
- x.songAuthor = foundSong[1] || "Unknown"
- x.songSize = "0MB"
- x.songID = "Level " + [parseInt(x[12]) + 1]
- }
+ level.songName = foundSong[0] || "Unknown"
+ level.songAuthor = foundSong[1] || "Unknown"
+ level.songSize = "0MB"
+ level.songID = "Level " + [parseInt(x[12]) + 1]
+ }
- keys.forEach(k => delete x[k])
+ //this is broken if you're not on page 0, blame robtop
+ if (filters.page == 0 && y == 0) {
+ let pages = splitBody[3].split(":");
+ level.results = +pages[0];
+ level.pages = +Math.ceil(pages[0] / 10);
+
+ if (filters.gauntlet || foundPack) {
+ level.results = levelArray.length
+ level.pages = 1
+ }
+ }
+
+ parsedLevels[y] = level
})
- return res.send(levelArray.slice(0, amount))
+ return res.send(parsedLevels.slice(0, amount))
})
}
\ No newline at end of file
diff --git a/assets/css/api.css b/assets/css/api.css
index aa3738e..f34e4bc 100644
--- a/assets/css/api.css
+++ b/assets/css/api.css
@@ -14,7 +14,7 @@ main {
background: #556c7d;
}
-main.alt {
+main:nth-child(even) {
background: #3979b8;
}
diff --git a/assets/css/generate.css b/assets/css/iconkit.css
similarity index 100%
rename from assets/css/generate.css
rename to assets/css/iconkit.css
diff --git a/assets/css/soon.css b/assets/css/soon.css
deleted file mode 100644
index 47d49f7..0000000
--- a/assets/css/soon.css
+++ /dev/null
@@ -1,82 +0,0 @@
-@font-face {font-family: Pusab; src: url('./../assets/Pusab.ttf')}
-
-body {
- margin: 0;
- height: 100vh;
- background: #000;
-}
-
-.supercenter {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%,-50%);
- text-align: center;
-}
-
-.levelBG {
- background-image: linear-gradient(#0065FD, #002E73);
- height: 100vh;
- background-position: center center;
- background-repeat: no-repeat;
- background-size: cover;
- background-attachment: fixed;
-}
-
-p {
- font-family: aller, helvetica;
- color: white;
- font-size: 2.9vh;
-}
-
-h1 {
- font-weight: normal;
- margin: 0% 0%;
- font-size: 90px;
- font-family: Pusab;
- color: white;
- letter-spacing: 0.02em;
- overflow: hidden;
- white-space: nowrap;
- text-shadow: -2.5px -2.5px 0px #000, 2.5px -2.5px 0px #000, -2.5px 2.5px 0px #000, 2.5px 2.5px 0px #000, 0.5px 0.6px 0px rgba(0,0,0,0.4);
- -webkit-text-size-adjust: 100%;
- line-height: 100%;
-}
-
-h2 {
- font-weight: normal;
- margin: 0% 0%;
- font-size: 40px;
- font-family: Pusab;
- color: white;
- letter-spacing: 0.02em;
- overflow: hidden;
- white-space: nowrap;
- text-shadow: -1.5px -1.5px 0px #000, 1.5px -1.5px 0px #000, -1.5px 1.5px 0px #000, 1.5px 1.5px 0px #000, 0.5px 0.6px 0px rgba(0,0,0,0.4);
- -webkit-text-size-adjust: 100%;
- line-height: 100%;
-}
-
-.gdButton {
- cursor: pointer;
- z-index: 1;
- user-select: none;
- transition-duration: 0.07s;
- transition-timing-function: ease-in-out;
-}
-
-.gdButton:hover {
- transform: scale(1.03);
-}
-
-.gdButton:active {
- transform: scale(1.08);
-}
-
-.inline {
- display: inline-block;
-}
-
-a {
- text-decoration: none;
-}
\ No newline at end of file
diff --git a/classes/Level.js b/classes/Level.js
new file mode 100644
index 0000000..242514f
--- /dev/null
+++ b/classes/Level.js
@@ -0,0 +1,56 @@
+const XOR = require(__dirname + "/../classes/XOR");
+
+let orbs = [0, 0, 50, 75, 125, 175, 225, 275, 350, 425, 500]
+let length = ['Tiny', 'Short', 'Medium', 'Long', 'XL']
+let difficulty = { 0: 'Unrated', 10: 'Easy', 20: 'Normal', 30: 'Hard', 40: 'Harder', 50: 'Insane' }
+
+class Level {
+ constructor(levelInfo, author = []) {
+ this.name = levelInfo[2];
+ this.id = levelInfo[1];
+ this.description = Buffer.from(levelInfo[3], "base64").toString() || "(No description provided)";
+ this.author = author[1] || "-"
+ this.authorID = levelInfo[6]
+ this.accountID = author[2] || 0
+ this.difficulty = difficulty[levelInfo[9]]
+ this.downloads = levelInfo[10]
+ this.likes = levelInfo[14]
+ this.disliked = levelInfo[14] < 0
+ this.length = length[levelInfo[15]] || "?"
+ this.stars = levelInfo[18]
+ this.orbs = orbs[levelInfo[18]]
+ this.diamonds = levelInfo[18] < 2 ? 0 : parseInt(levelInfo[18]) + 2
+ this.featured = levelInfo[19] > 0
+ this.epic = levelInfo[42] == 1
+ if (levelInfo[28]) this.uploaded = levelInfo[28] + ' ago'
+ if (levelInfo[29]) this.updated = levelInfo[29] + ' ago'
+ this.version = levelInfo[5];
+ if (levelInfo[27]) this.password = levelInfo[27];
+ this.copiedID = levelInfo[30]
+ this.officialSong = levelInfo[12] != 0 ? parseInt(levelInfo[12]) + 1 : 0
+ this.customSong = levelInfo[35]
+ this.coins = levelInfo[37]
+ this.verifiedCoins = levelInfo[38] == 1
+ this.starsRequested = levelInfo[39]
+ this.ldm = levelInfo[40] == 1
+ this.objects = levelInfo[45] == "65535" ? "65535+" : levelInfo[45]
+ this.large = levelInfo[45] > 40000;
+ this.cp = (this.stars > 0) + this.featured + this.epic
+
+ if (levelInfo[17] == 1) this.difficulty += ' Demon'
+ if (this.difficulty == "Insane Demon") this.difficulty = "Extreme Demon"
+ else if (this.difficulty == "Harder Demon") this.difficulty = "Insane Demon"
+ else if (this.difficulty == "Normal Demon") this.difficulty = "Medium Demon"
+ else if (levelInfo[25] == 1) this.difficulty = 'Auto';
+ this.difficultyFace = `${levelInfo[17] != 1 ? this.difficulty.toLowerCase() : `demon-${this.difficulty.toLowerCase().split(' ')[0]}`}${this.epic ? '-epic' : `${this.featured ? '-featured' : ''}`}`
+
+ if (this.password && this.password != 0) {
+ let xor = new XOR();
+ let pass = xor.decrypt(this.password, 26364);
+ if (pass.length > 1) this.password = pass.slice(1);
+ else this.password = pass;
+ }
+ }
+}
+
+module.exports = Level;
\ No newline at end of file
diff --git a/misc/XOR.js b/classes/XOR.js
similarity index 100%
rename from misc/XOR.js
rename to classes/XOR.js
diff --git a/html/api.html b/html/api.html
index a850668..4e14e09 100644
--- a/html/api.html
+++ b/html/api.html
@@ -41,14 +41,15 @@
@@ -56,7 +57,7 @@
-
+
Hi there!
This is the documentation for the
@@ -79,6 +80,7 @@
Comments & Posts /api/comments/level-or-user-ID
Level Analysis /api/analyze/levelID
Commenting /api/postComment (POST)
+
Profile Posting /api/postProfileComment (POST)
Liking /api/like (POST)
Icons /icon/username
@@ -90,7 +92,7 @@
-
+
Levels
@@ -168,7 +170,7 @@
-
+
@@ -225,7 +227,7 @@
-
+
@@ -309,7 +311,7 @@
-
+
Leaderboards
@@ -360,7 +362,7 @@
-
+
Level Leaderboards
@@ -405,7 +407,7 @@
-
+
Comments and Profile Posts
@@ -432,12 +434,12 @@
likes: The number of likes the comment has
date: Time since the comment was posted (sent as "x days/weeks/months" ago, since it's all the API sends)
levelID: The ID of the level
+
browserColor: If the comment was posted through GDBrowser
username: The commenter's username
playerID: The commenter's ID
accountID: The commenter's account ID
form: The form of the commenter's icon
percent: The commenter's percent on the level, if provided
-
browserColor: If the comment was posted through GDBrowser
modColor: If the commenter is an elder mod and gets fancy green text
@@ -459,7 +461,7 @@
-
+
Level Analysis
@@ -500,7 +502,7 @@
-
+
Commenting
@@ -518,7 +520,7 @@
password: Your password (as plain text)
levelID: The ID of the level to comment on
percent: The percent shown on the comment (optional)
-
color: If the comment should have a special color on GDBrowser (optional)
+
color: If the comment should have a special pink color on GDBrowser (optional)
@@ -541,8 +543,44 @@
+
+
+
+
Profile Posting
+
POST: /api/postProfileComment
+
+
Leaves a profile post. This one is a POST request!
+
+
+
Parameters (5)
+
+
comment: The content of the profile post
+
username: Your username
+
accountID: Your account ID
+
password: Your password (as plain text)
+
color: If the comment should have a special pink color on GDBrowser (optional)
+
+
+
-
+ Example
+
+
Example Requests
+
POST /api/postProfileComment
+ ?comment=Update 2.0 is revolution!
+ &username=viprin
+ &accountID=2795
+ &password=CopyAndPasteTurnsMeOn
+
+
If a status of 200 is returned, then the profile post was successfully posted. Otherwise, a 400 will return with an error message.
+
+
+
+
+
+
+
+
Liking
diff --git a/html/comingsoon.html b/html/comingsoon.html
index af84c7d..a576135 100644
--- a/html/comingsoon.html
+++ b/html/comingsoon.html
@@ -1,7 +1,7 @@
Coming Soon...
-
+
diff --git a/html/comments.html b/html/comments.html
index 28cb509..c3840f9 100644
--- a/html/comments.html
+++ b/html/comments.html
@@ -330,7 +330,7 @@ $('#submitComment').click(function() {
allowEsc = false
fetch(`../api/profile/${username}`).then(res => res.json()).then(res => {
- if (!res || res == "-1") {$('.postbutton').show(); return $('#message').text("The username you provided doesn't exist!")}
+ if (!res || res == "-1") {allowEsc = true; $('.postbutton').show(); return $('#message').text("The username you provided doesn't exist!")}
else accountID = res.accountID
$.post("../postComment", {comment, username, password, levelID, accountID, color: true})
@@ -346,7 +346,7 @@ $('#submitComment').click(function() {
page = 0
appendComments()
})
- .fail(e => {$('.postbutton').show();$('#message').text(e.responseText.includes("DOCTYPE") ? "Something went wrong..." : e.responseText)})
+ .fail(e => {allowEsc = true; $('.postbutton').show();$('#message').text(e.responseText.includes("DOCTYPE") ? "Something went wrong..." : e.responseText)})
})
})
@@ -397,7 +397,7 @@ $('#submitVote').click(function() {
allowEsc = false
fetch(`../api/profile/${username}`).then(res => res.json()).then(res => {
- if (!res || res == "-1") {$('.postbutton').show(); return $('#likeMessage').text("The username you provided doesn't exist!")}
+ if (!res || res == "-1") {allowEsc = true; $('.postbutton').show(); return $('#likeMessage').text("The username you provided doesn't exist!")}
else accountID = res.accountID
$.post("../like", { ID, accountID, password, like: likeType, type: 2, extraID })
@@ -414,7 +414,7 @@ $('#submitVote').click(function() {
likedComments.push(commentID)
localStorage.setItem('likedComments', JSON.stringify(likedComments))
})
- .fail(e => {$('.postbutton').show();$('#likeMessage').text(e.responseText.includes("DOCTYPE") ? "Something went wrong..." : e.responseText)})
+ .fail(e => {allowEsc = true; $('.postbutton').show();$('#likeMessage').text(e.responseText.includes("DOCTYPE") ? "Something went wrong..." : e.responseText)})
})
})
diff --git a/html/home.html b/html/home.html
index cad2e8d..7086fcf 100644
--- a/html/home.html
+++ b/html/home.html
@@ -108,8 +108,8 @@ fetch(`./api/credits`).then(res => res.json()).then(res => {
res.specialThanks.forEach((x, y) => {
$('#specialthanks').append(`
-
-
+
+
`)
})
diff --git a/html/iconkit.html b/html/iconkit.html
index d2fb09d..aa37c26 100644
--- a/html/iconkit.html
+++ b/html/iconkit.html
@@ -1,7 +1,7 @@
Online Icon Kit
-
+
diff --git a/html/profile.html b/html/profile.html
index 04b8aa3..18c7972 100644
--- a/html/profile.html
+++ b/html/profile.html
@@ -12,6 +12,42 @@
+
+
+
+