A whole lotta hoopla

CHANGELOG:
- Profile posts! This one is long overdue but... yeah! You can now leave profile posts, and drop likes on them as well!
- Classes folder! It's a folder... for classes! Don't ask me to explain what a class is, because I'm still not sure myself
- Le Level Class has arrived! Instead of parsing level data in three different files, the level class has stepped up to do the dirty work. (thanks to memimoe for helping)
- Renamed generate.css to iconkit.css, and deleted soon.css because it's ugly
- Removed 404 redirect when trying to view an asset that doesn't exist
- Updated readme to get with the times
- Removed Herobrine
This commit is contained in:
GDColon 2019-12-15 19:11:35 -05:00
parent a2151fcd4c
commit 60da852534
22 changed files with 493 additions and 391 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

40
api/postProfileComment.js Normal file
View file

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

View file

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

View file

@ -14,7 +14,7 @@ main {
background: #556c7d;
}
main.alt {
main:nth-child(even) {
background: #3979b8;
}

View file

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

56
classes/Level.js Normal file
View file

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

View file

@ -41,14 +41,15 @@
<div class="header-category">
<div class="category-name">Account</div>
<div class="category-content">
<a class="header-link" href="#commenting">Commenting</a>
<a class="header-link" href="#commenting">Commenting</a>
<a class="header-link" href="#profileposting">Profile Posting</a>
<a class="header-link" href="#liking">Liking</a>
</div>
</div>
<div class="header-category">
<div class="category-name">Misc</div>
<div class="category-content">
<a class="header-link" href="#analyze">Level Analysis</a>
<a class="header-link" href="#analyze">Level Analysis</a>
<a class="header-link" href="#icons">Icons</a>
</div>
</div>
@ -56,7 +57,7 @@
</div>
</div>
<div id="intro" class="anchor"></div>
<main class="grey">
<main>
<div class="main-block">
<h1>Hi there!</h1>
<p>This is the documentation for the
@ -79,6 +80,7 @@
<p><a href="#comments"><u>Comments & Posts</u></a> <i>/api/comments/level-or-user-ID</i></p>
<p><a href="#analyze"><u>Level Analysis</u></a> <i>/api/analyze/levelID</i></p>
<p><a href="#commenting"><u>Commenting</u></a> <i>/api/postComment (POST)</i></p>
<p><a href="#profileposting"><u>Profile Posting</u></a> <i>/api/postProfileComment (POST)</i></p>
<p><a href="#liking"><u>Liking</u></a> <i>/api/like (POST)</i></p>
<p><a href="#icons"><u>Icons</u></a> <i>/icon/username</i></p>
</quote>
@ -90,7 +92,7 @@
<div class="seperator"></div>
</main>
<main class="alt">
<main>
<div id="level" class="anchor"></div>
<div class="main-block">
<h1>Levels</h1>
@ -168,7 +170,7 @@
</main>
<main class="grey">
<main>
<div id="profile" class="anchor"></div>
@ -225,7 +227,7 @@
</main>
<main class="alt">
<main>
<div id="search" class="anchor"></div>
@ -309,7 +311,7 @@
</main>
<main class="grey">
<main>
<div id="leaderboard" class="anchor"></div>
<div class="main-block">
<h1>Leaderboards</h1>
@ -360,7 +362,7 @@
<div class="seperator"></div>
</main>
<main class="alt">
<main>
<div id="levelleaderboard" class="anchor"></div>
<div class="main-block">
<h1>Level Leaderboards</h1>
@ -405,7 +407,7 @@
<div class="seperator"></div>
</main>
<main class="grey">
<main>
<div id="comments" class="anchor"></div>
<div class="main-block">
<h1>Comments and Profile Posts</h1>
@ -432,12 +434,12 @@
<p>likes: The number of likes the comment has</p>
<p>date: Time since the comment was posted (sent as "x days/weeks/months" ago, since it's all the API sends)</p>
<p>levelID: The ID of the level</p>
<p>browserColor: If the comment was posted through GDBrowser</p>
<p class="red">username: The commenter's username</p>
<p class="red">playerID: The commenter's ID</p>
<p class="red">accountID: The commenter's account ID</p>
<p class="red">form: The form of the commenter's icon</p>
<p class="red">percent: The commenter's percent on the level, if provided</p>
<p class="red">browserColor: If the comment was posted through GDBrowser</p>
<p class="red">modColor: If the commenter is an elder mod and gets fancy green text</p>
</div>
@ -459,7 +461,7 @@
<div class="seperator"></div>
</main>
<main class="alt">
<main>
<div id="analyze" class="anchor"></div>
<div class="main-block">
<h1>Level Analysis</h1>
@ -500,7 +502,7 @@
</main>
<main class="grey">
<main>
<div id="commenting" class="anchor"></div>
<div class="main-block">
<h1>Commenting</h1>
@ -518,7 +520,7 @@
<p>password: Your password (as plain text)</p>
<p>levelID: The ID of the level to comment on</p>
<p>percent: The percent shown on the comment (optional)</p>
<p>color: If the comment should have a special color on GDBrowser (optional)</p>
<p>color: If the comment should have a special pink color on GDBrowser (optional)</p>
</div>
<br>
@ -541,8 +543,44 @@
<div class="seperator"></div>
</main>
<main>
<div id="profileposting" class="anchor"></div>
<div class="main-block">
<h1>Profile Posting</h1>
<p>POST: /api/postProfileComment</p>
<p>Leaves a profile post. This one is a POST request!</p>
<br>
<p class="reveal" onclick="$('#params-profileposting').slideToggle(100)"><b>Parameters (5)</b></p>
<div class="subdiv" id="params-profileposting">
<p>comment: The content of the profile post</p>
<p>username: Your username</p>
<p>accountID: Your account ID</p>
<p>password: Your password (as plain text)</p>
<p>color: If the comment should have a special pink color on GDBrowser (optional)</p>
</div>
<br>
<main class="alt">
<p class="reveal" onclick="$('#request-profileposting').slideToggle(100)"><b>Example</b></p>
<div class="subdiv" id="request-profileposting">
<p><b>Example Requests</b></p>
<p>POST /api/postProfileComment<br>
?comment=Update 2.0 is revolution!<br>
&username=viprin<br>
&accountID=2795<br>
&password=CopyAndPasteTurnsMeOn<br>
<br>
<p>If a status of 200 is returned, then the profile post was successfully posted. Otherwise, a 400 will return with an error message.</p>
</div>
</div>
<div class="seperator"></div>
</main>
<main>
<div id="liking" class="anchor"></div>
<div class="main-block">
<h1>Liking</h1>

View file

@ -1,7 +1,7 @@
<head>
<title>Coming Soon...</title>
<meta charset="utf-8">
<link href="../css/soon.css" type="text/css" rel="stylesheet">
<link href="../css/browser.css" type="text/css" rel="stylesheet">
<link rel="icon" href="../assets/coin.png">
</head>

View file

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

View file

@ -108,8 +108,8 @@ fetch(`./api/credits`).then(res => res.json()).then(res => {
res.specialThanks.forEach((x, y) => {
$('#specialthanks').append(`<div class="specialThanks">
<h2 class="gdButton smaller"><a href="./profile/${x[1]}">${x[0]}</h2></a>
<img src="./icon/${x[1]}" height=85%><br>
<h2 class="gdButton smaller"><a href="./profile/${x}">${x}</h2></a>
<img src="./icon/${x}" height=85%><br>
</div>`)
})

View file

@ -1,7 +1,7 @@
<head>
<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>
<title>Online Icon Kit</title>
<link href="../css/generate.css" type="text/css" rel="stylesheet">
<link href="../css/iconkit.css" type="text/css" rel="stylesheet">
<meta name="viewport" content="width=1024">
<meta property="og:description" content="Build and save your very own Geometry Dash icons, right from the internet!">
<meta property="og:title" content="Geometry Dash Online Icon Kit">

View file

@ -12,6 +12,42 @@
<body class="levelBG" onbeforeunload="saveUrl()">
<div id="everything">
<div class="popup" id="leavePost">
<div class="brownbox bounce center supercenter" style="height: 68%; width: 110vh">
<p style="position:absolute; right: 1vh; margin-top: 0; text-align: left" id="charcount">180</p>
<h1 class="smaller center" style="font-size: 5.5vh">Post Update</h1>
<textarea placeholder="Insert comment" id="content" maxlength="180" style="margin: 2% 0%"></textarea><br>
<form action="nothing lol">
<h3 class="center" style="margin-top: 2%">GD Password</h3>
<input type="password" id="password" maxlength="50" style="height: 8vh; width: 90%; text-align: center; margin-top: 0.5%">
</form>
<div style="min-height: 20%; max-height: 20%">
<p id="message" style="padding: 0% 10%; margin-top: 2%"></p>
</div>
<img src="../assets/btn-cancel.png" height=11%; class="postButton gdButton center" style="margin-right: 1%" onclick="$('#leavePost').hide(); $('textarea').val('')">
<img src="../assets/btn-submit.png" type="submit" height=11%; class="postButton gdButton center" style="margin-left: 1%" id="submitComment">
</div>
</div>
<div class="popup" id="likeComment">
<div class="brownbox bounce center supercenter" style="height: 75%; width: 100vh">
<h1 class="smaller center" style="font-size: 5.5vh">Vote</h1>
<img src="../assets/smashLike.png" id="likebtn" class="inline gdButton likeButton"><!--
--><img src="../assets/smashDislike.png" id="dislikebtn" class="inline gdButton likeButton youAreNotTheOne">
<form action="nothing lol">
<h3 class="center">GD Username</h3>
<input type="text" name="gdbrowser" id="like-username" maxlength="50" style="height: 8vh; width: 90%; text-align: center; margin-top: 0.5%">
<h3 class="center" style="margin-top: 2%">GD Password</h3>
<input type="password" id="like-password" maxlength="50" style="height: 8vh; width: 90%; text-align: center; margin-top: 0.5%">
</form>
<div style="min-height: 18%; max-height: 18%">
<p id="likeMessage" style="padding: 0% 10%; margin-top: 2.5%"></p>
</div>
<img src="../assets/btn-cancel.png" height=10%; class="postButton gdButton center" style="margin-right: 1%" onclick="$('#likeComment').hide(); $('#likebtn').trigger('click');">
<img src="../assets/btn-submit.png" type="submit" height=10%; class="postButton gdButton center" style="margin-left: 1%" id="submitVote">
</div>
</div>
<div class="popup" id="settingsDiv">
<div class="fancybox bounce center supercenter">
@ -68,13 +104,14 @@
<div class="lightBox center dragscroll" id="statusDiv" style="margin: 2% auto; width: 105vh; height: 35vh; background-color: #BE6F3F">
</div>
<div class="center" style="margin: 2.5% auto;">
<div class="center" style="margin: 1.5% auto 2.5% auto;">
<img src="../assets/messages.png" height="10%" id="msgButton" class="sideSpace gdButton" onclick="$('#settingsDiv').show()">
<img src="../assets/friends.png" height="10%" id="friendButton" class="sideSpace gdButton" onclick="$('#settingsDiv').show()">
<a href="../search/[[USERNAME]]?user"><img height="10%" src="../assets/levels.png" class="sideSpace gdButton"></a>
</div>
<!-- <img src="../assets/follow-off.png" class="gdButton" style="position: absolute; left: 0.5%; bottom: 1%; width: 6%"> -->
<a href="../search/[[USERNAME]]?user"><img src="../assets/levels.png" class="gdButton" style="position: absolute; right: 0.5%; bottom: 1%; width: 6%"></a>
<a id="commentA"><img src="../assets/comments.png" class="gdButton" id="commentButton" style="position: absolute; right: 0.5%; bottom: 50%; width: 6%" onclick="$('#settingsDiv').show()"></a>
<div style="position: absolute; right: 0.5%; top: 0%; width: 6%">
@ -95,6 +132,10 @@
<img class="gdButton" src="../assets/arrow-right.png" height="95%" onclick="page += 1; appendComments()">
</div>
<div id="postButton" style="position:absolute;bottom: 0%;right: 0%;width: 14%;text-align: right;transform: translate(30%, 40%);">
<img class="gdButton" src="../assets/comment.png" width="60%" onclick="$('#content').trigger('input'); $('#leavePost').show();">
</div>
</div>
</div>
@ -106,6 +147,10 @@
<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 profile posts <a class="menuLink" target="_blank" href="https://github.com/GDColon/GDBrowser/blob/master/api/postProfileComment.js">here</a>.'
$('#message').html(messageText)
$('#likeMessage').html(messageText.replace("profile posts", "liking posts").replace("postProfileComment", "like"))
$('#collectibles').html($('#collectibles').html().split(' 0 <img class="valign" src="../assets/cp')[0])
$('#modBadge[[MODERATOR]]').show()
$('#globalrank[[RANK]]').hide()
@ -161,12 +206,12 @@ fetch(`../api/comments/[[ACCOUNTID]]?type=profile&page=${page}`).then(res => res
<div class="comment">
<h2>[[USERNAME]]</h2>
<div class="commentAlign">
<p class="commentText" style="color: ${"[[USERNAME]]" == "RobTop" ? "rgb(50, 255, 255)" : "[[MODERATOR]]" == "2" ? "rgb(75, 255, 75)" : "white"}">${x.content}</p>
<p class="commentText" style="color: rgb(${"[[USERNAME]]" == "RobTop" ? "50, 255, 255" : "[[MODERATOR]]" == "2" ? "75, 255, 75" : x.browserColor ? "255, 180, 255" : "255, 255, 255"})">${x.content}</p>
</div>
</div>
<p class="commentDate">${x.date}</p>
<div class="commentLikes">
<img id="likeImg" class="inline" ${x.likes < 0 ? "style='transform: translateY(25%)'" : ""} src="../assets/${x.likes < 0 ? "dis" : ""}like.png" height=20% style="margin-right: 0.4%">
<img id="likeImg" class="likeComment gdButton inline" commentID="${x.ID}" ${x.likes < 0 ? "style='transform: translateY(25%)'" : ""} src="../assets/${x.likes < 0 ? "dis" : ""}like.png" height=20% style="margin-right: 0.4%">
<h3 class="inline">${x.likes}</h3><br>
</div>
</div>`)
@ -187,5 +232,100 @@ let page = 0
let loadingComments = false
appendComments()
$('#content').on('input', function() {
let remaining = 180-$('#content').val().length
$('#charcount').text(remaining)
})
$('#submitComment').click(function () {
let comment = $('#content').val()
let username = '[[USERNAME]]'
let password = $('#password').val()
let accountID = '[[ACCOUNTID]]'
if (!comment || !password || loadingComments) return $('#leavePost').hide()
$('#message').text("Posting...")
$('.postbutton').hide()
allowEsc = false
$.post("../postProfileComment", { comment, username, password, accountID, color: true })
.done(x => {
$('#content').val("")
$('#leavePost').hide()
$('.postbutton').show()
$('#message').html(messageText)
allowEsc = true
page = 0
appendComments()
})
.fail(e => { allowEsc = true; $('.postbutton').show(); $('#message').text(e.responseText.includes("DOCTYPE") ? "Something went wrong..." : e.responseText) })
})
let commentID = 0;
let likeCount, likeImg;
let likedComments;
let like = true;
$('#likebtn').click(function() {
$('#likebtn').removeClass('youAreNotTheOne')
$('#dislikebtn').addClass('youAreNotTheOne')
like = true
})
$('#dislikebtn').click(function() {
$('#likebtn').addClass('youAreNotTheOne')
$('#dislikebtn').removeClass('youAreNotTheOne')
like = false
})
$(document).on('click', '.likeComment', function(cmnt) {
commentID = $(this).attr('commentID')
likedComments = localStorage.likedComments ? JSON.parse(localStorage.likedComments) : []
if (likedComments.includes(commentID)) return;
lvID = $(this).attr('levelID') || 0
likeImg = $(this).find('img')
likeCount = $(this).parent().find('h3:not(.gold)')
$('#likeComment').show()
})
$('#submitVote').click(function() {
if (likedComments.includes(commentID)) return $('#likeMessage').text("You've already liked/disliked this comment!");
let ID = commentID
let username = $('#like-username').val()
let password = $('#like-password').val()
let extraID = lvID || window.location.pathname.split('/')[2]
let accountID = 0
let likeType = like ? "1" : "0"
if (!ID || !username || !password || loadingComments) return $('#postComment').hide()
$('#likeMessage').text(like ? "Liking..." : "Disliking... :(")
$('.postbutton').hide()
allowEsc = false
fetch(`../api/profile/${username}`).then(res => res.json()).then(res => {
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: 3, extraID: "[[ACCOUNTID]]" })
.done(x => {
let newCount = parseInt(likeCount.text()) + (like ? 1 : -1)
likeCount.text(newCount)
if (newCount < 0) likeImg.attr('src', '../assets/dislike.png').css('transform', compact ? 'translateY(15%)' : 'translateY(25%)')
else likeImg.attr('src', '../assets/like.png').removeAttr('style')
$('#likeComment').hide()
$('#likebtn').trigger('click')
$('.postbutton').show()
$('#likeMessage').html(messageText.replace("profile posts", "liking posts").replace("postProfileComment", "like"))
allowEsc = true
likedComments.push(commentID)
localStorage.setItem('likedComments', JSON.stringify(likedComments))
})
.fail(e => {allowEsc = true; $('.postbutton').show();$('#likeMessage').text(e.responseText.includes("DOCTYPE") ? "Something went wrong..." : e.responseText)})
})
})
</script>

View file

@ -129,7 +129,7 @@ let results = 0
let legalPages = true
let superSearch = ['*', '*?type=mostliked', '*?type=mostdownloaded', '*?type=recent'].includes(window.location.href.split('/')[4].toLowerCase())
let searchFilters = `../api/search/${type == 'saved' ? JSON.parse(localStorage.getItem('saved') || '[]').reverse().toString() : accID || path}?page=${page}${window.location.search.replace("?", "&").replace("page", "nope")}`
let searchFilters = `../api/search/${type == 'saved' ? JSON.parse(localStorage.getItem('saved') || '[]').reverse().toString() : accID || path}?page=[PAGE]${window.location.search.replace("?", "&").replace("page", "nope")}`
function Append(firstLoad) {
@ -142,7 +142,7 @@ function Append(firstLoad) {
if (page == 0) $('#pageDown').hide()
else $('#pageDown').show()
fetch(searchFilters).then(res => res.json()).then(res => {
fetch(searchFilters.replace("[PAGE]", page)).then(res => res.json()).then(res => {
if (res == '-1' || (res.length < 9 && type != "recent")) $('#pageUp').hide()
else $('#pageUp').show()
@ -285,7 +285,7 @@ $('#shuffle').click(function() {
if (randomIndex == 0) randomIndex = 10
$('#searchBox').html('<div style="height: 4.5%"></div>')
$('#loading').show()
fetch(searchFilters.replace(/(\?page=)\d+/, `$1${randomPage-1}`)).then(res => res.json()).then(res => {
fetch(searchFilters.replace('PAGE', randomPage-1)).then(res => res.json()).then(res => {
window.location.href = "../" + res[randomIndex-1].id
})
}

View file

@ -56,6 +56,10 @@ app.post("/postComment", function(req, res) {
app.modules.postComment(app, req, res)
})
app.post("/postProfileComment", function(req, res) {
app.modules.postProfileComment(app, req, res)
})
app.post("/like", function(req, res) {
app.modules.like(app, req, res)
})
@ -174,7 +178,8 @@ app.get("/", function(req, res) {
})
app.get('*', function(req, res) {
res.redirect('/search/404%20')
if (req.path.startsWith('/assets')) res.send("Looks like this file doesn't exist ¯\\_(ツ)_/¯<br>You can check out all of the assets <a href='https://github.com/GDColon/GDBrowser/tree/master/assets'>here</a>")
else res.redirect('/search/404%20')
});
app.listen(2000);

View file

@ -68,11 +68,11 @@
],
"specialThanks": [
["Qufy", "Qufy"],
["AlFas", "AlFas"],
["ViPriN", "ViPriN"],
["NeKitDS", "NeKitDS"],
["cos8o", "cos8o"],
["zmxmx", "zmxmx"]
"Qufy",
"AlFas",
"ViPriN",
"NeKitDS",
"cos8o",
"zmxmx"
]
}