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 @@
Account
- Commenting + Commenting + Profile Posting Liking
Misc
- Level Analysis + Level Analysis Icons
@@ -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(`
-

${x[0]}

-
+

${x}

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