From 1884d58e4688e091e55f46e860923cfcada04829 Mon Sep 17 00:00:00 2001 From: GDColon Date: Tue, 29 Sep 2020 21:42:18 -0400 Subject: [PATCH] GDPS improvements, comment jumping, + more - Improved param building for GDPS'es - Changed /profile/ to /u/ - Removed "most disliked" comment sort - Added ability to jump to last page of comments - Added page number and other small improvements to comment page --- api/accurateLeaderboard.js | 2 +- api/analyze.js | 4 +- api/comments.js | 18 +++---- api/download.js | 16 ++---- api/icon.js | 10 +--- api/leaderboard.js | 7 +-- api/leaderboardLevel.js | 5 +- api/level.js | 5 +- api/messages/countMessages.js | 7 +-- api/messages/deleteMessage.js | 5 +- api/messages/fetchMessage.js | 5 +- api/messages/getMessages.js | 7 +-- api/messages/sendMessage.js | 5 +- api/post/like.js | 7 +-- api/post/postComment.js | 7 +-- api/post/postProfileComment.js | 7 +-- api/profile.js | 10 +--- api/search.js | 9 ++-- assets/double-arrow.png | Bin 0 -> 11562 bytes assets/sort-dislikes-on.png | Bin 11646 -> 0 bytes assets/sort-dislikes.png | Bin 11383 -> 0 bytes html/api.html | 4 +- html/comments.html | 92 ++++++++++++++++++--------------- html/filters.html | 2 +- html/home.html | 4 +- html/leaderboard.html | 2 +- html/level.html | 2 +- html/levelboard.html | 2 +- html/messages.html | 4 +- html/search.html | 2 +- index.js | 20 +++---- misc/gdpsConfig.js | 8 +++ 32 files changed, 120 insertions(+), 158 deletions(-) create mode 100644 assets/double-arrow.png delete mode 100644 assets/sort-dislikes-on.png delete mode 100644 assets/sort-dislikes.png diff --git a/api/accurateLeaderboard.js b/api/accurateLeaderboard.js index 47e1e63..a121f2f 100644 --- a/api/accurateLeaderboard.js +++ b/api/accurateLeaderboard.js @@ -30,7 +30,7 @@ module.exports = async (app, req, res) => { idArray.forEach((x, y) => { request.post(app.endpoint + 'getGJUserInfo20.php', { - form: {targetAccountID: x, secret: app.secret} + form: app.gdParams({targetAccountID: x}) }, function (err, resp, body) { if (err || !body || body == '-1') return res.send([]) diff --git a/api/analyze.js b/api/analyze.js index d1b8418..933f475 100644 --- a/api/analyze.js +++ b/api/analyze.js @@ -1,4 +1,4 @@ -const pako = require('pako') +const pako = require('zlib') const properties = require('../misc/objectProperties.json') const init = require('../misc/initialProperties.json') const colorStuff = require('../misc/colorProperties.json') @@ -16,7 +16,7 @@ if (unencrypted) rawData = level.data else { let buffer; - try { buffer = pako.inflate(levelString, {to:"string"}) } + try { buffer = zlib.gzipSync(levelString).toString() } catch(e) { return res.send("-1") } rawData = buffer.toString('utf8') } diff --git a/api/comments.js b/api/comments.js index 8421def..66b8b0a 100644 --- a/api/comments.js +++ b/api/comments.js @@ -1,24 +1,20 @@ const request = require('request') -module.exports = async (app, req, res, worstPage) => { +module.exports = async (app, req, res) => { if (app.offline) return res.send("-1") let count = +req.query.count || 10 if (count > 1000) count = 1000 - if (+req.query.worstPage) worstPage = +req.query.worstPage - let params = { + let params = app.gdParams({ userID : req.params.id, accountID : req.params.id, levelID: req.params.id, - page: worstPage ? worstPage - (+req.query.page || 0) - 1 : +req.query.page || 0, - secret: app.secret, + page: +req.query.page || 0, count, - gameVersion: app.gameVersion, - binaryVersion: app.binaryVersion, - mode: worstPage || req.query.hasOwnProperty("top") ? "1" : "0", - } + mode: req.query.hasOwnProperty("top") ? "1" : "0", + }) let path = "getGJComments21" if (req.query.type == "commentHistory") path = "getGJCommentHistory" @@ -38,11 +34,9 @@ module.exports = async (app, req, res, worstPage) => { let pages = body.split('#')[1].split(":") let lastPage = +Math.ceil(+pages[0] / +pages[2]); - if (path == "getGJComments21" && !worstPage && req.query.hasOwnProperty("worst")) return app.run.comments(app, req, res, lastPage) let commentArray = [] - if (worstPage) comments.reverse() comments.forEach((c, i) => { var x = c[0] //comment info @@ -74,7 +68,7 @@ module.exports = async (app, req, res, worstPage) => { if (i == 0 && req.query.type != "commentHistory") { comment.results = +pages[0]; comment.pages = lastPage; - comment.range = `${+pages[1] + 1} of ${Math.min(+pages[0], +pages[1] + +pages[2])}` + comment.range = `${+pages[1] + 1} to ${Math.min(+pages[0], +pages[1] + +pages[2])}` } commentArray.push(comment) diff --git a/api/download.js b/api/download.js index 08485b1..e377b46 100644 --- a/api/download.js +++ b/api/download.js @@ -15,12 +15,7 @@ module.exports = async (app, req, res, api, ID, analyze) => { else levelID = levelID.replace(/[^0-9]/g, "") request.post(app.endpoint + 'downloadGJLevel22.php', { - form: { - levelID, - gameVersion: app.gameVersion, - binaryVersion: app.binaryVersion, - secret: app.secret - } + form: app.gdParams({ levelID }) }, async function (err, resp, body) { if (err || !body || body == '-1' || body.startsWith(" { let level = new Level(levelInfo) request.post(app.endpoint + 'getGJUsers20.php', { - form: { str: level.authorID, secret: app.secret } + form: app.gdParams({ str: level.authorID }) }, function (err1, res1, b1) { let gdSearchResult = app.parseResponse(b1) request.post(app.endpoint + 'getGJUserInfo20.php', { - form: { targetAccountID: gdSearchResult[16], secret: app.secret } + form: app.gdParams({ targetAccountID: gdSearchResult[16] }) }, function (err2, res2, b2) { if (b2 != '-1') { let account = app.parseResponse(b2) @@ -51,10 +46,7 @@ module.exports = async (app, req, res, api, ID, analyze) => { } request.post(app.endpoint + 'getGJSongInfo.php', { - form: { - songID: level.customSong, - secret: app.secret - } + form: app.gdParams({ songID: level.customSong }) }, async function (err, resp, songRes) { if (songRes != '-1') { diff --git a/api/icon.js b/api/icon.js index 36017d9..719e741 100644 --- a/api/icon.js +++ b/api/icon.js @@ -361,19 +361,13 @@ module.exports = async (app, req, res) => { res.contentType('image/png'); request.post(app.endpoint + 'getGJUsers20.php', { - form: { - str: username, - secret: app.secret - } + form: app.gdParams({ str: username }) }, function (err1, res1, body1) { if (err1 || !body1 || body1 == "-1") return buildIcon() else result = app.parseResponse(body1); request.post(app.endpoint + 'getGJUserInfo20.php', { - form: { - targetAccountID: result[16], - secret: app.secret - } + form: app.gdParams({ targetAccountID: result[16] }) }, function (err2, res2, body2) { if (!err2 && body2 && body2 != '-1') return buildIcon(app.parseResponse(body2)); diff --git a/api/leaderboard.js b/api/leaderboard.js index b3fcc57..5a4ca40 100644 --- a/api/leaderboard.js +++ b/api/leaderboard.js @@ -11,13 +11,10 @@ module.exports = async (app, req, res) => { else amount = count; } - let params = { + let params = app.gdParams({ count: amount, - gameVersion: app.gameVersion, - binaryVersion: app.binaryVersion, - secret: app.secret, type: (req.query.hasOwnProperty("creator") || req.query.hasOwnProperty("creators")) ? "creators" : "top", - } + }) request.post(app.endpoint + 'getGJScores20.php', { form : params}, async function(err, resp, body) { diff --git a/api/leaderboardLevel.js b/api/leaderboardLevel.js index ff83d5a..a278366 100644 --- a/api/leaderboardLevel.js +++ b/api/leaderboardLevel.js @@ -11,13 +11,12 @@ module.exports = async (app, req, res) => { else amount = count; } - let params = { + let params = app.gdParams({ levelID: req.params.id, - secret: app.secret, accountID: app.id, gjp: app.gjp, type: req.query.hasOwnProperty("week") ? "2" : "1", - } + }) request.post(app.endpoint + 'getGJLevelScores211.php', { form : params, headers: {'x-forwarded-for': req.headers['x-real-ip']}}, async function(err, resp, body) { diff --git a/api/level.js b/api/level.js index b05e868..49a00e3 100644 --- a/api/level.js +++ b/api/level.js @@ -23,11 +23,10 @@ module.exports = async (app, req, res, api, analyze) => { if (analyze || req.query.hasOwnProperty("download")) return app.run.download(app, req, res, api, levelID, analyze) request.post(app.endpoint + 'getGJLevels21.php', { - form: { + form: app.gdParams({ str: levelID, - secret: app.secret, type: 0 - } + }) }, async function (err, resp, body) { if (err || !body || body == '-1' || body.startsWith(" { if (!req.body.accountID) return res.status(400).send("No account ID provided!") if (!req.body.password) return res.status(400).send("No password provided!") - let params = { + let params = app.gdParams({ accountID: req.body.accountID, targetAccountID: req.body.accountID, gjp: xor.encrypt(req.body.password, 37526), - secret: app.secret, - gameVersion: app.gameVersion, - binaryVersion: app.binaryVersion, - } + }) request.post(app.endpoint + 'getGJUserInfo20.php', { form: params, diff --git a/api/messages/deleteMessage.js b/api/messages/deleteMessage.js index 0c950cb..6316fe4 100644 --- a/api/messages/deleteMessage.js +++ b/api/messages/deleteMessage.js @@ -8,12 +8,11 @@ module.exports = async (app, req, res, api) => { if (!req.body.password) return res.status(400).send("No password provided!") if (!req.body.id) return res.status(400).send("No message ID(s) provided!") - let params = { + let params = app.gdParams({ accountID: req.body.accountID, gjp: xor.encrypt(req.body.password, 37526), messages: Array.isArray(req.body.id) ? req.body.id.map(x => x.trim()).join(",") : req.body.id, - secret: app.secret, - } + }) let deleted = params.messages.split(",").length diff --git a/api/messages/fetchMessage.js b/api/messages/fetchMessage.js index 4526150..25c3cd3 100644 --- a/api/messages/fetchMessage.js +++ b/api/messages/fetchMessage.js @@ -7,12 +7,11 @@ module.exports = async (app, req, res, api) => { if (!req.body.accountID) return res.status(400).send("No account ID provided!") if (!req.body.password) return res.status(400).send("No password provided!") - let params = { + let params = app.gdParams({ accountID: req.body.accountID, gjp: xor.encrypt(req.body.password, 37526), messageID: req.params.id, - secret: app.secret, - } + }) request.post(app.endpoint + 'downloadGJMessage20.php', { form: params, diff --git a/api/messages/getMessages.js b/api/messages/getMessages.js index f335498..e07e4d0 100644 --- a/api/messages/getMessages.js +++ b/api/messages/getMessages.js @@ -8,15 +8,12 @@ module.exports = async (app, req, res, api) => { if (!req.body.accountID) return res.status(400).send("No account ID provided!") if (!req.body.password) return res.status(400).send("No password provided!") - let params = { + let params = app.gdParams({ accountID: req.body.accountID, gjp: xor.encrypt(req.body.password, 37526), page: req.body.page || 0, - secret: app.secret, - gameVersion: app.gameVersion, - binaryVersion: app.binaryVersion, getSent: req.query.sent ? 1 : 0 - } + }) request.post(app.endpoint + 'getGJMessages20.php', { form: params, diff --git a/api/messages/sendMessage.js b/api/messages/sendMessage.js index e24c901..1bf3c98 100644 --- a/api/messages/sendMessage.js +++ b/api/messages/sendMessage.js @@ -12,13 +12,12 @@ module.exports = async (app, req, res, api) => { let subject = Buffer.from(req.body.subject ? (req.body.color ? "☆" : "") + (req.body.subject.slice(0, 50)) : (req.body.color ? "☆" : "") + "No subject").toString('base64').replace(/\//g, '_').replace(/\+/g, "-") let body = xor.encrypt(req.body.message.slice(0, 300), 14251) - let params = { + let params = app.gdParams({ accountID: req.body.accountID, gjp: xor.encrypt(req.body.password, 37526), toAccountID: req.body.targetID, subject, body, - secret: app.secret, - } + }) request.post(app.endpoint + 'uploadGJMessage20.php', { form: params, diff --git a/api/post/like.js b/api/post/like.js index 764ee74..85576fc 100644 --- a/api/post/like.js +++ b/api/post/like.js @@ -13,14 +13,11 @@ module.exports = async (app, req, res) => { if (!req.body.type) return res.status(400).send("No type provided! (1=level, 2=comment, 3=profile") if (!req.body.extraID) return res.status(400).send("No extra ID provided! (this should be a level ID, account ID, or '0' for levels") - let params = { - gameVersion: app.gameVersion, - binaryVersion: app.binaryVersion, - secret: app.secret, + let params = app.gdParams({ udid: '0', uuid: '0', rs: '8f0l0ClAN1' - } + }) params.itemID = req.body.ID.toString() params.gjp = xor.encrypt(req.body.password, 37526) diff --git a/api/post/postComment.js b/api/post/postComment.js index e854c21..979462d 100644 --- a/api/post/postComment.js +++ b/api/post/postComment.js @@ -24,12 +24,9 @@ module.exports = async (app, req, res) => { if (rateLimit[req.body.username]) return res.status(400).send(`Please wait ${getTime(rateLimit[req.body.username] + cooldown - Date.now())} seconds before posting another comment!`) - let params = { - gameVersion: app.gameVersion, - binaryVersion: app.binaryVersion, - secret: app.secret, + let params = app.gdParams({ percent: 0 - } + }) params.comment = Buffer.from(req.body.comment + (req.body.color ? "☆" : "")).toString('base64').replace(/\//g, '_').replace(/\+/g, "-") params.gjp = xor.encrypt(req.body.password, 37526) diff --git a/api/post/postProfileComment.js b/api/post/postProfileComment.js index 66e1d62..63e51c4 100644 --- a/api/post/postProfileComment.js +++ b/api/post/postProfileComment.js @@ -13,12 +13,9 @@ module.exports = async (app, req, res) => { if (req.body.comment.includes('\n')) return res.status(400).send("Profile posts cannot contain line breaks!") - let params = { - gameVersion: app.gameVersion, - binaryVersion: app.binaryVersion, - secret: app.secret, + let params = app.gdParams({ cType: '1' - } + }) params.comment = Buffer.from(req.body.comment.slice(0, 190) + (req.body.color ? "☆" : "")).toString('base64').replace(/\//g, '_').replace(/\+/g, "-") params.gjp = xor.encrypt(req.body.password, 37526) diff --git a/api/profile.js b/api/profile.js index 53ac198..502e14b 100644 --- a/api/profile.js +++ b/api/profile.js @@ -6,19 +6,13 @@ module.exports = async (app, req, res, api, getLevels) => { if (app.offline) return res.send("-1") request.post(app.endpoint + 'getGJUsers20.php', { - form: { - str: getLevels || req.params.id, - secret: app.secret, - } + form: app.gdParams({ str: getLevels || req.params.id }) }, function (err1, res1, b1) { let searchResult = (req.query.hasOwnProperty("account") || err1 || b1 == '-1' || b1.startsWith(" { else amount = count; } - let filters = { + let filters = app.gdParams({ str: req.params.text, diff: req.query.diff, @@ -35,10 +35,7 @@ module.exports = async (app, req, res) => { customSong: req.query.hasOwnProperty("customSong") ? 1 : 0, type: req.query.type || 0, - gameVersion: app.gameVersion, - binaryVersion: app.binaryVersion, - secret: app.secret - } + }) let foundPack = mapPacks[req.params.text.toLowerCase()] if (foundPack) filters.str = `${foundPack[0]},${foundPack[1]},${foundPack[2]}`; @@ -73,7 +70,7 @@ module.exports = async (app, req, res) => { request.post(app.endpoint + 'getGJLevels21.php', { form : filters}, async function(err, resp, body) { - + if (err || !body || body == '-1' || body.startsWith("1^@s6yr`J+00001b5ch_0Itp) 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 literal 0 HcmV?d00001 diff --git a/assets/sort-dislikes-on.png b/assets/sort-dislikes-on.png deleted file mode 100644 index 9b5826f045ad1707c5bc2487d281686a2b44023f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11646 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 diff --git a/html/api.html b/html/api.html index 3b1f54a..97406ba 100644 --- a/html/api.html +++ b/html/api.html @@ -423,12 +423,10 @@

Returns up to 10 comments or profile posts


-

Parameters (5)

+

Parameters (4)

page: The page of the search

top: Whether or not to sort by most liked (comments only)

-

worst: Whether or not to sort by most disliked (comments only)

-

worstPage: The total number of pages, if you already know what it is. Optional, but speeds up disliked sorting

count: The number of comments/posts to list (default is 10, max is 1000)

type: The type of comments to fetch. Instead of a level ID, they require a player and account ID, respectively.

• commentHistory - All the comments from a player, if public on their profile

diff --git a/html/comments.html b/html/comments.html index fddb6ae..07d7254 100644 --- a/html/comments.html +++ b/html/comments.html @@ -59,12 +59,24 @@
-
+
+

+
+ +
-
- +
+ +
+ + + +
+
@@ -72,19 +84,15 @@
- -
- -
-
+
-
- +
+
-
+

-
+

@@ -134,7 +142,7 @@ let history = false let page = 0 let loadingComments = true let like = true -let worstPage = 0 +let lastPage = 0 let auto = false let interval = null @@ -143,8 +151,7 @@ if (lvlID > 999999999 || lvlID < -999999999) window.location.href = window.locat if (!Number.isInteger(+lvlID)) {history = true; target = `../api/profile/${lvlID}`} else lvlID = Math.round(+lvlID) -if (mode == "top" || (history && mode == "worst")) { mode = "top"; $('#topSort').attr('src', "../assets/sort-likes-on.png") } -else if (mode == "worst") $('#worstSort').attr('src', "../assets/sort-dislikes-on.png") +if (mode == "top") { mode = "top"; $('#topSort').attr('src', "../assets/sort-likes-on.png") } else $('#timeSort').attr('src', "../assets/sort-time-on.png") function clean(text) {return text.replace(/&/g, "&").replace(//g, ">").replace(/=/g, "=").replace(/"/g, """).replace(/'/g, "'")} @@ -156,7 +163,6 @@ fetch(target).then(res => res.json()).then(lvl => { if (history) { $('#autoMode').remove() - $('#worstSort').addClass('disabled') if (!lvl.username) return window.location.href = window.location.href.replace("comments", "search") @@ -174,7 +180,7 @@ fetch(target).then(res => res.json()).then(lvl => { $('#levelAuthor').addClass("green").addClass("unregistered") $('#authorLink').attr('href', '../search/' + lvl.authorID + "?user") } - else $('#authorLink').attr('href', '../profile/' + lvl.author) + else $('#authorLink').attr('href', '../u/' + lvl.author) $('#levelName').text(lvl.name || ("Nonexistent level " + lvlID)) if (!lvl.name) $('#leaveComment').hide() $('#levelAuthor').text("By " + (lvl.author || "-")) @@ -198,10 +204,18 @@ else loadingComments = true; if (!auto) $('#commentBox').html(`
`) -if (page == 0) $('#pageDown').hide() -else $('#pageDown').show() +if (page == 0) { + $('#pageDown').hide() + $('#firstPage').hide() + $('#refreshButton').show() +} +else { + $('#pageDown').show() + $('#firstPage').show() + $('#refreshButton').hide() +} -fetch(`../api${!history ? window.location.pathname : "/comments/" + lvl.playerID}?count=${compact && !auto ? 20 : 10}&page=${page}${history ? "&type=commentHistory" : ""}&${mode}${mode == "worst" && worstPage ? "&worstPage=" + worstPage : ""}`).then(res => res.json()).then(res => { +fetch(`../api${!history ? window.location.pathname : "/comments/" + lvl.playerID}?count=${compact && !auto ? 20 : 10}&page=${page}${history ? "&type=commentHistory" : ""}&${mode}`).then(res => res.json()).then(res => { if (history && lvl.commentHistory != "all") $('#pageUp').hide() @@ -223,8 +237,9 @@ fetch(`../api${!history ? window.location.pathname : "/comments/" + lvl.playerID let modNumber = x.moderator || lvl.moderator if (x.pages) { - worstPage = x.pages - if (page >= x.pages) $('#pageUp').hide() + lastPage = x.pages + $('#pagenum').html(`Page ${page+1} of ${x.pages}`) + if (page+1 >= x.pages) $('#pageUp').hide() else $('#pageUp').show() } @@ -234,7 +249,7 @@ fetch(`../api${!history ? window.location.pathname : "/comments/" + lvl.playerID
- +

${userName}

${modNumber > 0 ? `` : ""}

${x.percent ? x.percent + "%" : ""}

@@ -259,7 +274,7 @@ fetch(`../api${!history ? window.location.pathname : "/comments/" + lvl.playerID
- +

${userName}

${modNumber > 0 ? `` : ""}

${x.percent ? x.percent + "%" : ""}

@@ -284,9 +299,9 @@ fetch(`../api${!history ? window.location.pathname : "/comments/" + lvl.playerID }) $('.commentText').each(function() { - if ($(this).text().length > 100) { - let overflow = ($(this).text().length - 100) * 0.01 - $(this).css('font-size', (3.5 - (overflow)) + 'vh') + if ($(this).text().length > 100) { + let overflow = ($(this).text().length - 100) * 0.01 + $(this).css('font-size', (3.5 - (overflow)) + 'vh') } }); @@ -301,6 +316,7 @@ appendComments() $('#pageUp').click(function() {if (loadingComments) return; page += 1; appendComments()}) $('#pageDown').click(function() {if (loadingComments) return; page -= 1; appendComments()}) +$('#lastPage').click(function() {if (loadingComments || auto) return; page = lastPage - 1; appendComments()}) function resetSort() { page = 0 @@ -316,7 +332,6 @@ $('#topSort').click(function() { mode = "top"; $('#timeSort').attr('src', "../assets/sort-time.png") $('#topSort').attr('src', "../assets/sort-likes-on.png") - $('#worstSort').attr('src', "../assets/sort-dislikes.png") appendComments() }) @@ -326,24 +341,13 @@ $('#timeSort').click(function() { mode = "time"; $('#timeSort').attr('src', "../assets/sort-time-on.png") $('#topSort').attr('src', "../assets/sort-likes.png") - $('#worstSort').attr('src', "../assets/sort-dislikes.png") - appendComments() -}) - -$('#worstSort').click(function() { - if (mode == "worst" || loadingComments) return; - resetSort() - mode = "worst"; - $('#timeSort').attr('src', "../assets/sort-time.png") - $('#topSort').attr('src', "../assets/sort-likes.png") - $('#worstSort').attr('src', "../assets/sort-dislikes-on.png") appendComments() }) $('#compactMode').click(function() { if (loadingComments) return; compact = !compact - worstPage = 0; + lastPage = 0; page = 0; $('#compactMode').attr('src', `../assets/compact-${compact ? "on" : "off"}.png`) appendComments() @@ -356,13 +360,15 @@ $('#autoMode').click(function() { page = 0; $('#timeSort').attr('src', "../assets/sort-time-on.png") $('#topSort').attr('src', "../assets/sort-likes.png") - $('#worstSort').attr('src', "../assets/sort-dislikes.png") + if (auto) { + document.title = "[LIVE] " + document.title $('#liveText').show() $('#autoMode').attr('src', `../assets/stopbutton.png`) interval = setInterval(function() { appendComments(true) }, 2000) } else { + document.title = document.title.slice(6) $('#liveText').hide() $('#autoMode').attr('src', `../assets/playbutton.png`) clearInterval(interval) @@ -370,9 +376,9 @@ $('#autoMode').click(function() { appendComments(true) }) -$('#refresh').click(function() { +$(document).on('click', '.refreshBtn', function () { if (loadingComments) return - worstPage = 0; + lastPage = 0; page = 0; appendComments() }) diff --git a/html/filters.html b/html/filters.html index 1bfa81e..6bef5ed 100644 --- a/html/filters.html +++ b/html/filters.html @@ -117,7 +117,7 @@ let demonMode = false; $('#userSearch').click(function() { let query = encodeURIComponent($('#levelName').val()) - if (query) window.location.href = "./profile/" + query + if (query) window.location.href = "./u/" + query }) $('.levelSearch').click(function() { diff --git a/html/home.html b/html/home.html index 11f02bb..518d6fa 100644 --- a/html/home.html +++ b/html/home.html @@ -91,7 +91,7 @@ fetch(`./api/credits`).then(res => res.json()).then(res => { $('#credits').append(`