From c6683bd6585daec4ba4a0b2aa561611b3c9413ef Mon Sep 17 00:00:00 2001 From: Colon <56617416+GDColon@users.noreply.github.com> Date: Tue, 7 Dec 2021 14:14:56 -0500 Subject: [PATCH] Minor error rework + last page button on search --- api/comments.js | 6 +- api/download.js | 2 +- api/gauntlets.js | 4 +- api/icon.js | 606 ++++++++++++--------------- api/icon_old.js | 2 +- api/leaderboards/accurate.js | 2 +- api/leaderboards/leaderboardLevel.js | 2 +- api/leaderboards/scores.js | 6 +- api/level.js | 2 +- api/mappacks.js | 4 +- api/profile.js | 4 +- api/search.js | 10 +- api/song.js | 4 +- html/home.html | 3 +- html/search.html | 9 + index.js | 5 + 16 files changed, 304 insertions(+), 367 deletions(-) diff --git a/api/comments.js b/api/comments.js index 8889991..e38498d 100644 --- a/api/comments.js +++ b/api/comments.js @@ -1,6 +1,6 @@ module.exports = async (app, req, res) => { - if (req.offline) return res.status(500).send("-1") + if (req.offline) return res.sendError() let count = +req.query.count || 10 if (count > 1000) count = 1000 @@ -20,14 +20,14 @@ module.exports = async (app, req, res) => { req.gdRequest(path, req.gdParams(params), function(err, resp, body) { - if (err) return res.status(500).send("-1") + if (err) return res.sendError() comments = body.split('|') comments = comments.map(x => x.split(':')) comments = comments.map(x => x.map(x => app.parseResponse(x, "~"))) if (req.query.type == "profile") comments.filter(x => x[0][2]) else comments = comments.filter(x => x[0] && x[0][2]) - if (!comments.length) return res.status(204).send("-1") + if (!comments.length) return res.status(204).send([]) let pages = body.split('#')[1].split(":") let lastPage = +Math.ceil(+pages[0] / +pages[2]); diff --git a/api/download.js b/api/download.js index 4a3c283..0b4fa11 100644 --- a/api/download.js +++ b/api/download.js @@ -6,7 +6,7 @@ module.exports = async (app, req, res, api, ID, analyze) => { function rejectLevel() { if (!api) return res.redirect('search/' + req.params.id) - else return res.status(500).send("-1") + else return res.sendError() } if (req.offline) { diff --git a/api/gauntlets.js b/api/gauntlets.js index 5a911cc..bd48ba4 100644 --- a/api/gauntlets.js +++ b/api/gauntlets.js @@ -3,14 +3,14 @@ let gauntletNames = ["Fire", "Ice", "Poison", "Shadow", "Lava", "Bonus", "Chaos" module.exports = async (app, req, res) => { - if (req.offline) return res.status(500).send("-1") + if (req.offline) return res.sendError() let cached = cache[req.id] if (app.config.cacheGauntlets && cached && cached.data && cached.indexed + 2000000 > Date.now()) return res.status(200).send(cached.data) // half hour cache req.gdRequest('getGJGauntlets21', {}, function (err, resp, body) { - if (err) return res.status(500).send("-1") + if (err) return res.sendError() let gauntlets = body.split('#')[0].split('|').map(x => app.parseResponse(x)).filter(x => x[3]) let gauntletList = gauntlets.map(x => ({ id: +x[1], name: gauntletNames[+x[1] - 1] || "Unknown", levels: x[3].split(",") })) diff --git a/api/icon.js b/api/icon.js index 9915987..0ce5cde 100644 --- a/api/icon.js +++ b/api/icon.js @@ -1,389 +1,311 @@ -// this file is a potential candidate for worst code on github -// i advise you to turn back now -// seriously, it's not too late - -const Jimp = require('jimp'); +const sharp = require('sharp'); +const Canvas = require('canvas') +const psd = require('ag-psd') const fs = require('fs'); -const icons = require('../icons/gameSheet.json'); -const colors = require('../icons/colors.json'); -const forms = require('../icons/forms.json') -const offsets = require('../icons/offsets.json'); +const mainPath = `../icons/` +const icons = require('../misc/icons/gameSheet.json'); +const colors = require('../misc/icons/colors.json'); +const forms = require('../misc/icons/forms.json') +const offsets = require('../misc/icons/offsets.json') +const legOffsets = require('../misc/icons/legOffsets.json') -let hexRegex = /^[A-Fa-f0-9]{6}$/ -function hexConvert(hex) { hex = hex.replace('#', ''); return {r: '0x' + hex[0] + hex[1] | 0, g: '0x' + hex[2] + hex[3] | 0, b: '0x' + hex[4] + hex[5] | 0}; } -function recolor(img, col) { - return img.scan(0, 0, img.bitmap.width, img.bitmap.height, function (x, y, idx) { - if (img.bitmap.data.slice(idx, idx+3).every(function(val) {return val >= 20 && val <= 255})) { // If it's not "black, i.e. we want to recolor it" - this.bitmap.data[idx] = colors[col].r / (255 / this.bitmap.data[idx]); - this.bitmap.data[idx + 1] = colors[col].g / (255 / this.bitmap.data[idx + 1]); - this.bitmap.data[idx + 2] = colors[col].b / (255 / this.bitmap.data[idx + 2]); - } - }) +let canvasSize = 300 +let halfCanvas = canvasSize/2 +let TRANSPARENT = {r: 0, g: 0, b: 0, alpha: 0} +let cache = {} + +let partNames = { + "1": "Primary", + "2": "Secondary", + "3": "UFO Dome", + "glow": "Glow", + "extra": "White", } -/* Caveat of genFileName is that if there are any falsey values in the arguments they are ignored. -This is usually a good thing though - avoid issues by not putting something like 0 instead of '0' */ -function genFileName(...args) { return args.filter(function(val) {return val}).join('_') +'_001.png' } -function fromIcons(filename) { return `./icons/${filename}` } -let cache = {}; +// convert hex to RGB +let hexRegex = /^[A-Fa-f0-9]{6}$/ +function hexConvert(hex) { hex = hex.replace('#', ''); return {val: hex, r: '0x' + hex[0] + hex[1] | 0, g: '0x' + hex[2] + hex[3] | 0, b: '0x' + hex[4] + hex[5] | 0}; } + +// get path name from icon form and ID +function getIconPath(icon, formName) { + return `${mainPath}${formName}_${icon < 10 ? "0" : ""}${icon}` +} + +// get color from param input +function getColor(colInput, defaultCol) { + colInput = String(colInput) + let foundColor = colors[colInput] + if (foundColor) { + foundColor.val = colInput + return foundColor + } + else if (colInput.match(hexRegex)) { // custom hex code + let hexCol = hexConvert(colInput) + colors[colInput.toLowerCase()] = hexCol + return hexCol + } + else if (!foundColor && defaultCol) { + let def = colors[defaultCol] + def.val = defaultCol + return def + } +} module.exports = async (app, req, res) => { - function buildIcon(account=[], usercode) { +async function buildIcon(account=[], userCode) { - let { form, ind } = forms[req.query.form] || {}; - form = form || 'player'; - ind = ind || 21; + let form = forms[req.query.form] || forms["icon"] - let iconID = req.query.icon || account[ind] || 1; - let col1 = req.query.col1 || account[10] || 0; - let col2 = req.query.col2 || account[11] || 3; - let colG = req.query.colG || req.query.colg - let colW = req.query.colW || req.query.colw || req.query.col3 - let outline = req.query.glow || account[28] || "0"; + let iconID = req.query.icon || account[form.index] || 1; + let col1 = getColor(req.query.col1 || account[10], "0") + let col2 = getColor(req.query.col2 || account[11], "3") + let colG = getColor(req.query.colG || req.query.colg) + let colW = getColor(req.query.colW || req.query.colw || req.query.col3) - let topless = form == "bird" && req.query.topless - let drawLegs = !(req.query.noLegs > 0) - let autoSize = req.query.size == "auto" - let sizeParam = autoSize || (req.query.size && !isNaN(req.query.size)) - if (outline == "0" || outline == "false") outline = false; + let useGlow = req.query.glow || account[28] || false; + if (useGlow && ["false", "0"].includes(useGlow)) useGlow = false + if (col1.r == 0 && col1.g == 0 && col1.b == 0 ) useGlow = true - if (iconID && iconID.toString().length == 1) iconID = "0" + iconID; + // bit of a hacky solution for glow color but whatev + let glowColor = colG || col2 + if (glowColor.r == 0 && glowColor.g == 0 && glowColor.b == 0) glowColor = col1 + if (glowColor.r == 0 && glowColor.g == 0 && glowColor.b == 0) glowColor = {r: 255, g: 255, b: 255} - function genImageName(...args) { return genFileName(form, iconID, ...args) } + let psdExport = req.query.psd || false + let topless = form.name == "UFO" ? req.query.topless || false : false - let icon, glow, extra; - function setBaseIcons() { - icon = genImageName(isSpecial && '01'); - glow = genImageName(isSpecial && '01', '2'); - extra = genImageName(isSpecial && '01', 'extra'); - } - let isSpecial = ['robot', 'spider'].includes(form); - setBaseIcons(); + let customSize = req.query.size == "auto" ? "auto" : +req.query.size || null - if (!fs.existsSync(fromIcons(icon)) || (isSpecial && !fs.existsSync(fromIcons(genImageName('02'))))) { - iconID = '01'; - setBaseIcons(); - } + let iconPath = getIconPath(iconID, form.form) - let ex = fromIcons(extra) - let hasExtra = fs.existsSync(ex) + let iconCode = `${form.name}-${iconID}-${col1.val}-${col2.val}-${colG ? colG.val : "x"}-${colW ? colW.val : "x"}-${useGlow ? 1 : 0}` + let cachable = !topless && !customSize && !psdExport + if (cachable && cache[iconCode]) return res.end(cache[iconCode].buffer) - let cols = [col1, col2, colG, colW] - cols.forEach(col => { - if (!col) return - col = col.toString() - if (col.match(hexRegex)) colors[col.toLowerCase()] = hexConvert(col) - }) + // default to 1 if icon ID does not exist + if (!fs.existsSync(getPartName(1).slice(1))) { // slice 1 from filename since fs reads paths differently + iconID = 1 + iconPath = getIconPath(1, form.form) + } - if (!colors[col1] || isNaN(colors[col1].r)) col1 = colors[+col1] ? +col1 : 0 - if (!colors[col2] || isNaN(colors[col2].r)) col2 = colors[+col2] ? +col2 : 3 - if (!colors[colG] || isNaN(colors[colG].r)) colG = colors[+colG] ? +colG : null - if (!colors[colW] || isNaN(colors[colW].r)) colW = colors[+colW] ? +colW : null - if (colW && (!hasExtra || colW == 12)) colW = null + // get path of icon 'part' (1: primary, 2: secondary, 3: ufo top, extra: white, glow: glow, ) + function getPartName(part, robotPart) { + let path = iconPath + if (form.legs) path += `_0${robotPart || 1}` + if (!part || part == "1") return `${path}_001.png` + else return `${path}_${part}_001.png` + } - if (col1 == 15 || col1 === "000000") outline = true; - - let iconCode = `${req.query.form == "cursed" ? "cursed" : form}${topless ? "top" : ""}-${iconID}-${col1}-${col2}-${colG || "x"}-${colW || "x"}-${outline ? 1 : 0}` - - if (!sizeParam && (!isSpecial || drawLegs) && cache[iconCode]) return res.end(cache[iconCode].value) - - let useExtra = false - let originalOffset = icons[icon].spriteOffset; - let minusOrigOffset = function(x, y) { return x - originalOffset[y] } - let offset = icons[glow].spriteOffset.map(minusOrigOffset); - let robotLeg1, robotLeg2, robotLeg3, robotLeg3b, robotLeg2b, robotLeg1b, robotLeg1c; - let robotOffset1, robotOffset2, robotOffset3, robotOffset1b, robotOffset2b, robotOffset3b; - let robotGlow1, robotGlow2, robotGlow3, glowOffset - let ufoTop, ufoOffset, ufoCoords, ufoSprite - let extrabit, offset2, size2; - - if (isSpecial) { - const legs = [1,2,3].map(function(val) {return genImageName(`0${val+1}`)}); - const glows = [1,2,3].map(function(val) {return genImageName(`0${val+1}`, '2')}); - robotOffset1 = icons[legs[0]].spriteOffset.map(minusOrigOffset).concat(icons[legs[0]].spriteSize); - robotOffset2 = icons[legs[1]].spriteOffset.map(minusOrigOffset).concat(icons[legs[1]].spriteSize); - robotOffset3 = icons[legs[2]].spriteOffset.map(minusOrigOffset).concat(icons[legs[2]].spriteSize); - - robotOffset1b = icons[glows[0]].spriteOffset.map(minusOrigOffset).concat(icons[glows[0]].spriteSize); - robotOffset2b = icons[glows[1]].spriteOffset.map(minusOrigOffset).concat(icons[glows[1]].spriteSize); - robotOffset3b = icons[glows[2]].spriteOffset.map(minusOrigOffset).concat(icons[glows[2]].spriteSize); - - robotLeg1 = new Jimp(fromIcons(legs[0])); robotGlow1 = new Jimp(fromIcons(glows[0])) - robotLeg2 = new Jimp(fromIcons(legs[1])); robotGlow2 = new Jimp(fromIcons(glows[1])) - robotLeg3 = new Jimp(fromIcons(legs[2])); robotGlow3 = new Jimp(fromIcons(glows[2])) - - glowOffset = offsets[form][+iconID] || [] - } - - Jimp.read(fromIcons(glow)).then(async function (image) { - - let size = [image.bitmap.width, image.bitmap.height] - let glow = recolor(image, col2) - let imgOff = isSpecial ? 100 : 0 - - let eb = fromIcons(extra) - if (fs.existsSync(eb)) { - extrabit = icons[extra] - offset2 = extrabit.spriteOffset.map(minusOrigOffset); - size2 = extrabit.spriteSize; - extra = new Jimp(eb); - if (colW) await Jimp.read(eb).then(e => { extra = recolor(e, colW) }) - useExtra = true + // recolor white parts of icon to specified color + async function recolor(img, col) { + let rawData = await img.raw().toBuffer({resolveWithObject: true}) + for (let i=0; i 0) { + rawData.data[i] = col.r / (255 / rawData.data[i]); + rawData.data[i + 1] = col.g / (255 / rawData.data[i + 1]); + rawData.data[i + 2] = col.b / (255 / rawData.data[i + 2]); } + } + return sharp(rawData.data, {raw: {width: rawData.info.width, height: rawData.info.height, channels: 4, background: TRANSPARENT}}).png() + } - Jimp.read(fromIcons(icon)).then(async function (ic) { + // color icon part and add to layer list + async function addLayer(part, color, legSection) { - let iconSize = [ic.bitmap.width, ic.bitmap.height] - recolor(ic, col1) - ic.composite(glow, (iconSize[0] / 2) - (size[0] / 2) + offset[0], (iconSize[1] / 2) - (size[1] / 2) - offset[1], { mode: Jimp.BLEND_DESTINATION_OVER }) + let leg = legSection ? legSection.leg : null - if (form == "bird" && !topless) { - ufoTop = genImageName('3') - ufoOffset = icons[ufoTop].spriteOffset.map(minusOrigOffset).concat(icons[ufoTop].spriteSize); - ufoCoords = [imgOff + (iconSize[0] / 2) - (ufoOffset[2] / 2) + ufoOffset[0], (iconSize[1] / 2) - (ufoOffset[3] / 2) - ufoOffset[1] + 300 - iconSize[1]] - ufoSprite = fromIcons(ufoTop) - ic.contain(iconSize[0], 300, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_BOTTOM) - // Only add dome if there's no glow, otherwise the dome will be outlined as well - if (!outline) ic.composite(await Jimp.read(ufoSprite), ufoCoords[0], ufoCoords[1], {mode: Jimp.BLEND_DESTINATION_OVER}) - } + let partName = getPartName(part, leg) + let offsetData = icons[partName.slice(mainPath.length)] + let { spriteSize, spriteOffset } = offsetData - if (drawLegs && (form == "robot" || req.query.form == "cursed")) { + let builtPart = sharp(partName.slice(1)) // slice 1 from filename since sharp also reads paths differently + if (color) builtPart = await recolor(builtPart, color) - ic.contain(iconSize[0], 300, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_TOP) - ic.contain(iconSize[0] + 200, 300, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_TOP) + let left = halfCanvas - Math.floor(spriteSize[0] / 2) + spriteOffset[0] + let top = halfCanvas - Math.floor(spriteSize[1] / 2) - spriteOffset[1] - await Jimp.read(new Jimp(robotGlow1)).then(rob => { - rob.rotate(-45) - robotGlow1 = recolor(rob, col2) - }) + if (legSection) { + left += Math.floor(legSection.xPos) + top -= Math.floor(legSection.yPos) + // if (legSection.darken) builtPart.tint({r: 100, g: 100, b: 100}) + if (legSection.rotation) { + builtPart.rotate(legSection.rotation, {background: TRANSPARENT}) + if (part == "glow") { left--; top--; } + } + if (legSection.yScale) builtPart.resize({width: spriteSize[0], height: Math.floor(spriteSize[1] * legSection.yScale), fit: "fill"}) + if (legSection.xFlip) builtPart.flop() + } - await Jimp.read(new Jimp(robotGlow2)).then(rob => { - rob.rotate(45) - robotGlow2 = recolor(rob, col2) - }) + let layerData = { + partName, spriteOffset, spriteSize, leg, + layerName: partNames[part], + behind: legSection && legSection.darken, + isGlow: part == "glow", + input: await builtPart.toBuffer(), + left, top + } - await Jimp.read(new Jimp(robotGlow3)).then(rob => { - robotGlow3 = recolor(rob, col2) - }) + if (legSection) { + if (!legLayers[legSection.leg]) legLayers[legSection.leg] = [layerData] + else legLayers[legSection.leg].push(layerData) + } - await Jimp.read(new Jimp(robotLeg1)).then(rob => { - rob.rotate(-45) - recolor(rob, col1) - rob.composite(robotGlow1, (robotOffset1[2] - robotOffset1b[2]) + (glowOffset[0] || 1), ((robotOffset1[3] - robotOffset1b[3]) / 2) + (glowOffset[1] || 0), { mode: Jimp.BLEND_DESTINATION_OVER }) - robotLeg1 = rob - }) + else layers.push(layerData) + } - await Jimp.read(new Jimp(robotLeg2)).then(rob => { - rob.rotate(45) - recolor(rob, col1) - rob.composite(robotGlow2, ((robotOffset2[2] - robotOffset2b[2]) / 4) + (glowOffset[4] || 0), ((robotOffset2[3] - robotOffset2b[3]) / 2) + (glowOffset[5] || 0), { mode: Jimp.BLEND_DESTINATION_OVER }) - robotLeg2 = rob - }) + // build all layers of icon segment (col1, col2, glow, extra) + async function buildFullLayer(legSection) { + let hasExtra = fs.existsSync(getPartName("extra", legSection ? legSection.leg : null).slice(1)) - await Jimp.read(new Jimp(robotLeg2)).then(rob => { - robotLeg2b = rob.color([{ apply: 'darken', params: [20] }]).rotate(-5) - }) + if (form.form == "bird" && !topless) await addLayer(3, null, legSection) // ufo top + await addLayer(2, col2, legSection) // secondary color + if (useGlow) await addLayer("glow", glowColor, legSection) // glow + await addLayer(1, col1, legSection) // primary color + if (hasExtra) await addLayer("extra", colW, legSection) // extra - await Jimp.read(new Jimp(robotLeg3)).then(rob => { - recolor(rob, col1) - rob.composite(robotGlow3, ((robotOffset3[2] - robotOffset3b[2]) / 2) + (glowOffset[2] || 0), ((robotOffset3[3] - robotOffset3b[3]) / 2) + (glowOffset[3] || 0), { mode: Jimp.BLEND_DESTINATION_OVER }) - robotLeg3 = rob - }) + // if (legSection) { + // let foundLeg = legLayers[legSection.leg] + // foundLeg.forEach(x => layers.push(x)) + // } + } - await Jimp.read(new Jimp(robotLeg3)).then(rob => { - robotLeg3b = rob.color([{ apply: 'darken', params: [10] }]) - }) + let layers = [] + let legLayers = [] + let legData = form.legs ? legOffsets[form.form] || [] : [] + let parentSize = icons[getPartName(1).slice(mainPath.length)].spriteSize + let canvas = sharp({create: {width: canvasSize, height: canvasSize, channels: 4, background: TRANSPARENT}}) - ic.composite(robotLeg2b, 100 + (iconSize[0] / 2) - (robotOffset2[2]) + robotOffset2[0] - 31, (iconSize[1] / 2) - (robotOffset2[3]) - robotOffset2[1] + 73) - ic.composite(robotLeg3b, 100 + (iconSize[0] / 2) - (robotOffset3[2]) + robotOffset3[0] + 20, (iconSize[1] / 2) - (robotOffset3[3]) - robotOffset3[1] + 78) - ic.composite(robotLeg2, 100 + (iconSize[0] / 2) - (robotOffset2[2]) + robotOffset2[0] - 20, (iconSize[1] / 2) - (robotOffset2[3]) - robotOffset2[1] + 73) - ic.composite(robotLeg3, 100 + (iconSize[0] / 2) - (robotOffset3[2]) + robotOffset3[0] + 40, (iconSize[1] / 2) - (robotOffset3[3]) - robotOffset3[1] + 78) - ic.composite(robotLeg1, 100 + (iconSize[0] / 2) - (robotOffset1[2]) + robotOffset1[0] - 20, (iconSize[1] / 2) - (robotOffset1[3]) - robotOffset1[1] + 50) + // if (legData.length) { + // for (let i=0; i x).sort((a, b) => !!b.behind - !!a.behind).sort((a, b) => !!b.isGlow - !!a.isGlow) - let spiderBody; - ic.contain(iconSize[0], 300, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_TOP) - ic.contain(iconSize[0] + 200, 300, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_TOP) + canvas.composite(layers) - if (iconID == "07") { - robotOffset2[2] -= 10 - robotOffset2[1] += 12 - robotOffset1b[3] -= 105 - robotOffset2b[3] -= 150 - robotOffset2b[2] -= 60 - } + let rawData = await canvas.toBuffer({resolveWithObject: true}) + let minX = canvasSize; let maxX = 0; + let minY = canvasSize; let maxY = 0; + for (let i=0; i 0) { + if (x < minX) minX = x + if (x > maxX) maxX = x + if (y < minY) minY = y + if (y > maxY) maxY = y + } + } - if (iconID == "16") { - robotOffset1b[3] -= 100 - robotOffset2b[3] -= 200 - robotOffset2b[2] -= 30 - } + // need to make a new sharp instance so everything is merged. bit hacky but it works + let dimensions = [maxX - minX, maxY - minY] - await Jimp.read(new Jimp(robotGlow1)).then(rob => { - if (robotGlow1.bitmap.width < 10) robotGlow1.opacity(0) - else robotGlow1 = recolor(rob, col2) - }) + if (!psdExport) { + let finalIcon = sharp(rawData.data, {raw: {width: canvasSize, height: canvasSize, channels: 4}}) + .extract({left: minX, top: minY, width: dimensions[0], height: dimensions[1]}) - await Jimp.read(new Jimp(robotGlow2)).then(rob => { - robotGlow2 = recolor(rob, col2) - }) + if (customSize) { + let isThicc = dimensions[0] > dimensions[1] + let squareSize = req.query.size == "auto" ? (isThicc ? dimensions[0] : dimensions[1]) : Math.floor(req.query.size) + if (squareSize < 32) squareSize = 32 + if (squareSize > 256) squareSize = 256 - await Jimp.read(new Jimp(robotGlow3)).then(rob => { - robotGlow3 = recolor(rob, col2) - }) - - await Jimp.read(new Jimp(robotLeg1)).then(rob => { - recolor(rob, col1) - rob.composite(robotGlow1, ((robotOffset1[2] - robotOffset1b[2]) / 2) + (glowOffset[2] || 0), ((robotOffset1[3] - robotOffset1b[3]) / 4) + (glowOffset[3] || 0), { mode: Jimp.BLEND_DESTINATION_OVER }) - robotLeg1 = rob - }) - - await Jimp.read(new Jimp(robotLeg2)).then(rob => { - recolor(rob, col1) - rob.composite(robotGlow2, ((robotOffset2[2] - robotOffset2b[2]) / 6) + (glowOffset[0] || 0), ((robotOffset2[3] - robotOffset2b[3]) / 6) + (glowOffset[1] || 0), { mode: Jimp.BLEND_DESTINATION_OVER }) - rob.rotate(-40) - robotLeg2 = rob - }) - - await Jimp.read(new Jimp(robotLeg1)).then(rob => { - robotLeg1b = rob.color([{ apply: 'darken', params: [20] }]) - }) - - await Jimp.read(new Jimp(robotLeg1b)).then(rob => { - robotLeg1c = rob.mirror(true, false) - }) - - await Jimp.read(new Jimp(robotLeg3)).then(rob => { - recolor(rob, col1) - rob.composite(robotGlow3, ((robotOffset3[2] - robotOffset3b[2]) / 2) + (glowOffset[4] || 0), ((robotOffset3[3] - robotOffset3b[3]) / 2) + (glowOffset[5] || 0), { mode: Jimp.BLEND_DESTINATION_OVER }) - robotLeg3 = rob - }) - - await Jimp.read(new Jimp(ic)).then(rob => { - spiderBody = rob - }) - - ic.composite(robotLeg3, 100 + (iconSize[0] / 2) - (robotOffset3[2]) + (robotOffset3[0]), (iconSize[1] / 2) - (robotOffset2[3]) - robotOffset2[1] + 77) - ic.composite(robotLeg1b, 100 + (iconSize[0] / 2) - (robotOffset1[2]) + robotOffset1[0] + 35, (iconSize[1] / 2) - (robotOffset1[3]) - robotOffset1[1] + 70) - ic.composite(robotLeg1c, 100 + (iconSize[0] / 2) - (robotOffset1[2]) + robotOffset1[0] + 75, (iconSize[1] / 2) - (robotOffset1[3]) - robotOffset1[1] + 70) - // ^ BELOW - ic.composite(spiderBody, 0, 0) - // v ABOVE - ic.composite(robotLeg2, 100 + (iconSize[0] / 2) - (robotOffset2[2]) + robotOffset2[0] - 60, (iconSize[1] / 2) - (robotOffset2[3]) - robotOffset2[1] + 75) - ic.composite(robotLeg1, 100 + (iconSize[0] / 2) - (robotOffset1[2]) + robotOffset1[0] + 7, (iconSize[1] / 2) - (robotOffset1[3]) - robotOffset1[1] + 70) - } - - // every now and then jimp does a fucky wucky uwu and this line errors. seems to be an issue with the lib itself :v - try { if (useExtra) ic.composite(extra, imgOff + (iconSize[0] / 2) - (size2[0] / 2) + offset2[0], (iconSize[1] / 2) - (size2[1] / 2) - offset2[1] + (form == "bird" && !req.query.topless ? 300 - iconSize[1] : 0)) } - catch(e) {} - - let finalSize = [ic.bitmap.width, ic.bitmap.height] - - function finish(img) { - img.autocrop(0.01, false) - if (form == "swing") img.resize(120, 111) - if (img.bitmap.height == 300) ic.autocrop(1, false) - if (sizeParam) { - let thicc = img.bitmap.width > img.bitmap.height - let imgSize = req.query.size == "auto" ? (thicc ? img.bitmap.width : img.bitmap.height) : Math.round(req.query.size) - if (imgSize < 32) imgSize = 32 - if (imgSize > 512) imgSize = 512 - if (thicc) img.contain(img.bitmap.width, img.bitmap.width, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_MIDDLE) - else img.contain(img.bitmap.height, img.bitmap.height, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_MIDDLE) - img.resize(imgSize, Jimp.AUTO) - } - img.getBuffer(Jimp.AUTO, (err, buffer) => { - if (!sizeParam && drawLegs) { - cache[iconCode] = { value: buffer, timeoutID: setTimeout(function() {delete cache[iconCode]}, 10000000) } // 3 hour cache - if (usercode) cache[usercode] = { value: buffer, timeoutID: setTimeout(function() {delete cache[usercode]}, 300000) } // 5 min cache for player icons - } - return res.end(buffer, 'base64') - }) - } - - if (!outline) return finish(ic) - - else { - - ic.getBuffer(Jimp.AUTO, function (err, buff) { - - const Canvas = require('canvas') - , Image = Canvas.Image - , canvas = Canvas.createCanvas(finalSize[0] + 10, finalSize[1] + 10) - , ctx = canvas.getContext('2d'); - - if (!colG) colG = (col2 == 15 || col2 == "000000" ? col1 : col2) - if (colG == 15 || colG == "000000") colG = 12 - - const img = new Image() - img.onload = () => { - var dArr = [-1, -1, 0, -1, 1, -1, -1, 0, 1, 0, -1, 1, 0, 1, 1, 1], // offset array - s = 2, i = 0, x = canvas.width / 2 - finalSize[0] / 2, y = canvas.height / 2 - finalSize[1] / 2; - - for (; i < dArr.length; i += 2) ctx.drawImage(img, x + dArr[i] * s, y + dArr[i + 1] * s); - - ctx.globalCompositeOperation = "source-in"; - ctx.fillStyle = `rgba(${colors[colG].r}, ${colors[colG].g}, ${colors[colG].b}, 1})`; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.globalCompositeOperation = "source-over"; - ctx.imageSmoothingEnabled = false; - - // Add UFO top last so it doesn't get glow'd - if (form == "bird" && !topless) { - const dome = new Image() - dome.src = ufoSprite - ctx.drawImage(dome, ufoCoords[0]+5, ufoCoords[1]+5) - } - - ctx.drawImage(img, x, y) - - } - - img.onerror = err => { throw err } - img.src = buff - - Jimp.read(canvas.toBuffer()).then(b => { - return finish(b) - }) - }) - } + // use longest side to make square + if (isThicc) finalIcon.resize({ + width: dimensions[isThicc ? 0 : 1], + height: dimensions[isThicc ? 0 : 1], + fit: "contain", + background: TRANSPARENT }) - }) + finalIcon.resize({width: squareSize, height: squareSize, fit: "contain", background: TRANSPARENT}) + } + finalIcon.png().toBuffer().then(x => { + res.end(x) // send file + if (cachable) { // cache for a bit + cache[iconCode] = { buffer: x, timeoutID: setTimeout(function() {delete cache[iconCode]}, 10000000) } // cache file for 3 hours + if (userCode) cache[userCode] = { buffer: x, timeoutID: setTimeout(function() {delete cache[userCode]}, 300000) } // 5 min cache for player icons + } + }) } - let username = req.params.text - let userCode; - res.contentType('image/png'); - - if (req.offline || req.query.hasOwnProperty("noUser") || req.query.hasOwnProperty("nouser") || username == "icon") return buildIcon() - - else if (app.config.cachePlayerIcons && !Object.keys(req.query).filter(x => !["form", "forceGD"].includes(x)).length) { - userCode = `${req.id}u-${username.toLowerCase()}-${forms[req.query.form] ? req.query.form : 'cube'}` - if (cache[userCode]) return res.end(cache[userCode].value) - } - - let accountMode = !req.query.hasOwnProperty("player") && Number(req.params.id) - let foundID = app.userCache(req.id, username) - let skipRequest = accountMode || foundID - let forceGD = req.query.hasOwnProperty("forceGD") - - // skip request by causing fake error lmao - req.gdRequest(skipRequest ? "" : 'getGJUsers20', skipRequest ? {} : req.gdParams({ str: username, forceGD }, !forceGD), function (err1, res1, body1) { - - let result = foundID ? foundID[0] : (accountMode || err1) ? username : app.parseResponse(body1)[16]; - - req.gdRequest('getGJUserInfo20', req.gdParams({ targetAccountID: result, forceGD }, !forceGD), function (err2, res2, body2) { - - if (err2) return buildIcon(); - let iconData = app.parseResponse(body2) - if (!foundID && !forceGD) app.userCache(req.id, iconData[16], iconData[2], iconData[1]) - return buildIcon(iconData, userCode); - + else { + let psdLayers = layers.map(x => { + let Image = Canvas.Image + let canvas = Canvas.createCanvas(...dimensions) + let ctx = canvas.getContext('2d'); + const img = new Image() + img.onload = () => { + ctx.drawImage(img, 0 + x.left - minX, 0 + x.top - minY) + } + img.onerror = err => { throw err } + img.src = x.input + return {name: x.layerName, canvas, leg: x.leg} }) - }); + + if (form.legs) { + let legLayers = [] + for (let i=1; i<=form.legs + 1; i++) legLayers.push({name: i == 1 ? "Base" : `Leg ${i}`, opened: true, children: []}) + psdLayers.forEach(x => { + legLayers[x.leg-1].children.push(x) + }) + psdLayers = legLayers.reverse() + } + + const photoshop = { + width: dimensions[0], + height: dimensions[1], + children: psdLayers + }; + + const buffer = psd.writePsdBuffer(photoshop); + return res.end(buffer) + } + +} + + +// ==================================== // + +// OLD CODE IS BEING USED FOR ROBOTS AND SPIDERS +let formCheck = forms[req.query.form] +if (formCheck && formCheck.legs) return app.run.icon_old(app, req, res) + +let username = req.params.text +let userCode; +res.contentType('image/png'); + +if (req.offline || req.query.hasOwnProperty("noUser") || req.query.hasOwnProperty("nouser") || username == "icon") return buildIcon() + +else if (app.config.cachePlayerIcons && !Object.keys(req.query).filter(x => !["form", "forceGD"].includes(x)).length) { + userCode = `${req.id}u-${username.toLowerCase()}-${forms[req.query.form] ? req.query.form : 'cube'}` + if (cache[userCode]) return res.end(cache[userCode].value) +} + +let accountMode = !req.query.hasOwnProperty("player") && Number(req.params.id) +let foundID = app.userCache(req.id, username) +let skipRequest = accountMode || foundID +let forceGD = req.query.hasOwnProperty("forceGD") + +// skip request by causing fake error lmao +req.gdRequest(skipRequest ? "" : 'getGJUsers20', skipRequest ? {} : req.gdParams({ str: username, forceGD }, !forceGD), function (err1, res1, body1) { + + let result = foundID ? foundID[0] : (accountMode || err1) ? username : app.parseResponse(body1)[16]; + + req.gdRequest('getGJUserInfo20', req.gdParams({ targetAccountID: result, forceGD }, !forceGD), function (err2, res2, body2) { + + if (err2) return buildIcon(); + let iconData = app.parseResponse(body2) + if (!foundID && !forceGD) app.userCache(req.id, iconData[16], iconData[2], iconData[1]) + return buildIcon(iconData, userCode); + + }) +}); } \ No newline at end of file diff --git a/api/icon_old.js b/api/icon_old.js index b7706dc..35cd142 100644 --- a/api/icon_old.js +++ b/api/icon_old.js @@ -389,4 +389,4 @@ module.exports = async (app, req, res) => { }) }); -} +} \ No newline at end of file diff --git a/api/leaderboards/accurate.js b/api/leaderboards/accurate.js index 2624c4b..5cce0e6 100644 --- a/api/leaderboards/accurate.js +++ b/api/leaderboards/accurate.js @@ -30,7 +30,7 @@ module.exports = async (app, req, res, post) => { if (modMode) cellIndex += indexes.length let cell = tab.getCell(1, cellIndex).value - if (!cell || typeof cell != "string" || cell.startsWith("GoogleSpreadsheetFormulaError")) { console.log("Spreadsheet Error:"); console.log(cell); return res.status(500).send("-1") } + if (!cell || typeof cell != "string" || cell.startsWith("GoogleSpreadsheetFormulaError")) { console.log("Spreadsheet Error:"); console.log(cell); return res.sendError() } let leaderboard = JSON.parse(cell.replace(/~( |$)/g, "")) let gdFormatting = "" diff --git a/api/leaderboards/leaderboardLevel.js b/api/leaderboards/leaderboardLevel.js index 9b880a1..71e816f 100644 --- a/api/leaderboards/leaderboardLevel.js +++ b/api/leaderboards/leaderboardLevel.js @@ -1,6 +1,6 @@ module.exports = async (app, req, res) => { - if (req.offline) return res.status(500).send("-1") + if (req.offline) return res.sendError() let amount = 100; let count = req.query.count ? parseInt(req.query.count) : null diff --git a/api/leaderboards/scores.js b/api/leaderboards/scores.js index 7d6a6a9..730d461 100644 --- a/api/leaderboards/scores.js +++ b/api/leaderboards/scores.js @@ -1,6 +1,6 @@ module.exports = async (app, req, res) => { - if (req.offline) return res.status(500).send("-1") + if (req.offline) return res.sendError() let amount = 100; let count = req.query.count ? parseInt(req.query.count) : null @@ -16,9 +16,9 @@ module.exports = async (app, req, res) => { req.gdRequest('getGJScores20', params, function(err, resp, body) { - if (err) return res.status(500).send("-1") + if (err) return res.sendError() scores = body.split('|').map(x => app.parseResponse(x)).filter(x => x[1]) - if (!scores.length) return res.status(500).send("-1") + if (!scores.length) return res.sendError() scores.forEach(x => { let keys = Object.keys(x) diff --git a/api/level.js b/api/level.js index c97a45c..f0303ac 100644 --- a/api/level.js +++ b/api/level.js @@ -6,7 +6,7 @@ module.exports = async (app, req, res, api, analyze) => { function rejectLevel() { if (!api) return res.redirect('search/' + req.params.id) - else return res.status(500).send("-1") + else return res.sendError() } if (req.offline) return rejectLevel() diff --git a/api/mappacks.js b/api/mappacks.js index eca93f4..e746730 100644 --- a/api/mappacks.js +++ b/api/mappacks.js @@ -3,7 +3,7 @@ let cache = {} module.exports = async (app, req, res) => { - if (req.offline) return res.status(500).send("-1") + if (req.offline) return res.sendError() let cached = cache[req.id] if (app.config.cacheMapPacks && cached && cached.data && cached.indexed + 5000000 > Date.now()) return res.status(200).send(cached.data) // 1.5 hour cache @@ -13,7 +13,7 @@ module.exports = async (app, req, res) => { function mapPackLoop() { req.gdRequest('getGJMapPacks21', params, function (err, resp, body) { - if (err) return res.status(500).send("-1") + if (err) return res.sendError() let newPacks = body.split('#')[0].split('|').map(x => app.parseResponse(x)).filter(x => x[2]) packs = packs.concat(newPacks) diff --git a/api/profile.js b/api/profile.js index 30ce2f1..142011d 100644 --- a/api/profile.js +++ b/api/profile.js @@ -4,7 +4,7 @@ module.exports = async (app, req, res, api, getLevels) => { if (req.offline) { if (!api) return res.redirect('/search/' + req.params.id) - else return res.status(500).send("-1") + else return res.sendError() } let username = getLevels || req.params.id @@ -42,7 +42,7 @@ module.exports = async (app, req, res, api, getLevels) => { if (err2 || dumbGDPSError) { if (!api) return res.redirect('/search/' + req.params.id) - else return res.status(500).send("-1") + else return res.sendError() } if (!foundID) app.userCache(req.id, account[16], account[2], account[1]) diff --git a/api/search.js b/api/search.js index 6bcd36d..7455fe8 100644 --- a/api/search.js +++ b/api/search.js @@ -9,13 +9,13 @@ module.exports = async (app, req, res) => { let demonMode = req.query.hasOwnProperty("demonlist") || req.query.hasOwnProperty("demonList") || req.query.type == "demonlist" || req.query.type == "demonList" if (demonMode) { - if (!req.server.demonList) return res.status(400).send('-1') + if (!req.server.demonList) return res.sendError(400) let dList = demonList[req.id] if (!dList || !dList.list.length || dList.lastUpdated + 600000 < Date.now()) { // 10 minute cache return request.get(req.server.demonList + 'api/v2/demons/listed/?limit=100', function (err1, resp1, list1) { - if (err1) return res.status(500).send("-1") + if (err1) return res.sendError() else return request.get(req.server.demonList + 'api/v2/demons/listed/?limit=100&after=100', function (err2, resp2, list2) { - if (err2) return res.status(500).send("-1") + if (err2) return res.sendError() demonList[req.id] = {list: JSON.parse(list1).concat(JSON.parse(list2)).map(x => String(x.level_id)), lastUpdated: Date.now()} return app.run.search(app, req, res) }) @@ -85,7 +85,7 @@ module.exports = async (app, req, res) => { filters.str = demonMode ? demonList[req.id].list : filters.str.split(",") listSize = filters.str.length filters.str = filters.str.slice(filters.page*amount, filters.page*amount + amount) - if (!filters.str.length) return res.status(400).send("-1") + if (!filters.str.length) return res.sendError(400) filters.str = filters.str.map(x => String(Number(x) + (+req.query.l || 0))).join() filters.page = 0 } @@ -95,7 +95,7 @@ module.exports = async (app, req, res) => { req.gdRequest('getGJLevels21', req.gdParams(filters), function(err, resp, body) { - if (err) return res.status(500).send("-1") + if (err) return res.sendError() let splitBody = body.split('#') let preRes = splitBody[0].split('|') let authorList = {} diff --git a/api/song.js b/api/song.js index 3668ae3..97c2144 100644 --- a/api/song.js +++ b/api/song.js @@ -4,11 +4,11 @@ module.exports = async (app, req, res) => { // temporary solution until song api is re-enabled - if (req.offline) return res.status(500).send('-1') + if (req.offline) return res.sendError() let songID = req.params.song req.gdRequest('getGJSongInfo', {songID: songID}, function(err, resp, body) { - if (err) return res.status(400).send('-1') + if (err) return res.sendError(400) else if (body < 0) return res.send(false) request.get('https://www.newgrounds.com/audio/listen/' + songID, function(err2, resp2, song) { console.log(resp2.statusCode) diff --git a/html/home.html b/html/home.html index 1219e5a..7211d5c 100644 --- a/html/home.html +++ b/html/home.html @@ -53,7 +53,8 @@ - + + diff --git a/html/search.html b/html/search.html index 4794321..5f697b0 100644 --- a/html/search.html +++ b/html/search.html @@ -83,6 +83,10 @@ + +
@@ -159,6 +163,9 @@ function Append(firstLoad, noCache) { if (page == 0) $('#pageDown').hide() else $('#pageDown').show() + if (page == (pages - 1)) $('#lastPage').addClass('grayscale').find('img').removeClass('gdButton') + else $('#lastPage').removeClass('grayscale').find('img').addClass('gdButton') + if (!noCache && pageCache[page]) appendLevels(pageCache[page]) else Fetch(searchFilters.replace("[PAGE]", page)).then(appendLevels) @@ -176,6 +183,7 @@ function Append(firstLoad, noCache) { else $('#shuffle').css('filter', 'hue-rotate(100deg)') } $('#shuffle').show() + $('#lastPage').show() $('#pagenum').text(`Page ${page + 1}${pages ? ` of ${pages}` : ""}`) } @@ -250,6 +258,7 @@ Append(true) $('#pageUp').click(function() {page += 1; if (!loading) Append()}) $('#pageDown').click(function() {page -= 1; if (!loading) Append()}) +$('#lastPage').click(function() {page = (pages - 1); if (!loading) Append()}) $('#pageJump').click(function() {if (loading) return; page = parseInt(($('#pageSelect').val() || 1) - 1); Append()}) $('#refreshPage').click(function() { Append(false, true) } ) diff --git a/index.js b/index.js index dad4dc7..d74f881 100644 --- a/index.js +++ b/index.js @@ -73,6 +73,11 @@ app.use(async function(req, res, next) { req.server = app.servers.find(x => subdomains.includes(x.id.toLowerCase())) if (subdomains.length > 1 || !req.server) return res.redirect("http://" + req.get('host').split(".").slice(subdomains.length).join(".") + req.originalUrl) + // will expand this in the future :wink: + res.sendError = function(errorCode=500) { + res.status(errorCode).send("-1") + } + // literally just for convenience req.offline = req.server.offline req.endpoint = req.server.endpoint