From 77cac2c504123fb0b98f0f08a6427c1704d9a306 Mon Sep 17 00:00:00 2001 From: roffy3051 Date: Sun, 20 Oct 2024 08:29:58 +0800 Subject: [PATCH] feat: Add params validate --- Readme.md => README.md | 2 +- assets/img/back-to-top.png | Bin 0 -> 38264 bytes assets/style.css | 63 ++++++++++++++++ index.js | 104 ++++++++++++++------------ package.json | 6 +- pnpm-lock.yaml | 7 ++ utils/index.js | 5 ++ utils/themify.js | 25 +++++-- utils/zod.js | 50 +++++++++++++ views/index.pug | 147 +++++++++++++++++++++++++++---------- 10 files changed, 316 insertions(+), 93 deletions(-) rename Readme.md => README.md (97%) create mode 100644 assets/img/back-to-top.png create mode 100644 utils/index.js create mode 100644 utils/zod.js diff --git a/Readme.md b/README.md similarity index 97% rename from Readme.md rename to README.md index 8fd1d61..9c6bdf8 100644 --- a/Readme.md +++ b/README.md @@ -84,4 +84,4 @@ Glitch can use `.env` file, [documentation](https://help.glitch.com/hc/en-us/art ## License -[MIT License](./LICENSE) +[MIT License](./LICENSE), excluding all themes diff --git a/assets/img/back-to-top.png b/assets/img/back-to-top.png new file mode 100644 index 0000000000000000000000000000000000000000..2aec0d6d0b58bf1789aa95f498f044582240f07c GIT binary patch literal 38264 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≷)sgt{C?yhPRwRwqF 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 M07*qoM6N<$f@310P5=M^ literal 0 HcmV?d00001 diff --git a/assets/style.css b/assets/style.css index da69e7a..0fdae66 100644 --- a/assets/style.css +++ b/assets/style.css @@ -34,6 +34,69 @@ h2, h3, h4, h5 { margin-bottom: .6em; } +.back-to-top { + position: fixed; + z-index: 2; + right: -108px; + bottom: 0; + width: 108px; + height: 150px; + background: url('./img/back-to-top.png?v=1') no-repeat 0 0; /* artwork from https://www.pixiv.net/artworks/83996495 */ + background-size: 108px 450px; + opacity: 0.6; + transition: opacity 0.3s, right 0.8s; + cursor: pointer; +} +.back-to-top:hover { + background-position: 0 -150px; + opacity: 1; +} +.back-to-top.load { + right: 0; +} +.back-to-top.ani-leave { + background-position: 0 -150px; + animation: ani-leave 390ms ease-in-out; + animation-fill-mode: forwards; +} +.back-to-top.leaved { + pointer-events: none; + background: none; + transition: none; +} +.back-to-top.ending { + pointer-events: none; +} +.back-to-top.ending::after { + opacity: 1; + transition-delay: 0.35s; +} +.back-to-top::after { + content: ''; + position: fixed; + z-index: 2; + right: 0; + bottom: 0; + width: 108px; + height: 150px; + background: url('./img/back-to-top.png?v=1') no-repeat 0 0; + background-size: 108px 450px; + background-position: 0 -300px; + transition: opacity 0.3s; + opacity: 0; + pointer-events: none; +} + +@keyframes ani-leave { + 0% { + transform: translateX(0); + } + 100% { + transform: translateX(108px); + } +} + + @media screen and (max-width: 900px) { iframe { display: none; diff --git a/index.js b/index.js index 8a08654..5931d81 100644 --- a/index.js +++ b/index.js @@ -3,9 +3,12 @@ const config = require("config-yml"); const express = require("express"); const compression = require("compression"); +const { z } = require("zod"); const db = require("./db"); -const themify = require("./utils/themify"); +const { themeList, getCountImage } = require("./utils/themify"); +const { ZodValid } = require("./utils/zod"); +const { randomArray } = require("./utils"); const app = express(); @@ -15,56 +18,65 @@ app.set("view engine", "pug"); app.get('/', (req, res) => { const site = config.app.site || `${req.protocol}://${req.get('host')}` - res.render('index', { site }) + res.render('index', { + site, + themeList, + }) }); // get the image -app.get(["/@:name", "/get/@:name"], async (req, res) => { - const { name } = req.params; - const { theme = "moebooru", padding = 7, pixelated = '1', darkmode = 'auto' } = req.query; - const isPixelated = pixelated === '1'; +app.get(["/@:name", "/get/@:name"], + ZodValid({ + params: z.object({ + name: z.string().max(32), + }), + query: z.object({ + theme: z.string().default("moebooru"), + padding: z.coerce.number().min(0).max(32).default(7), + offset: z.coerce.number().min(-500).max(500).default(0), + scale: z.coerce.number().min(0.1).max(2).default(1), + pixelated: z.enum(["0", "1"]).default("1"), + darkmode: z.enum(["0", "1", "auto"]).default("auto") + }) + }), + async (req, res) => { + const { name } = req.params; + let { theme = "moebooru", ...rest } = req.query; - if (name.length > 32) { - res.status(400).send("name too long"); - return; + // This helps with GitHub's image cache + res.set({ + "content-type": "image/svg+xml", + "cache-control": "max-age=0, no-cache, no-store, must-revalidate", + }); + + const data = await getCountByName(name); + + if (name === "demo") { + res.set("cache-control", "max-age=31536000"); + } + + if (theme === "random") { + theme = randomArray(Object.keys(themeList)); + } + + // Send the generated SVG as the result + const renderSvg = getCountImage({ + count: data.num, + theme, + ...rest + }); + + res.send(renderSvg); + + console.log( + data, + `theme: ${theme}`, + `ip: ${req.headers['x-forwarded-for'] || req.connection.remoteAddress}`, + `ref: ${req.get("Referrer") || null}`, + `ua: ${req.get("User-Agent") || null}` + ); } - - if (padding > 32) { - res.status(400).send("padding too long"); - return; - } - - // This helps with GitHub's image cache - res.set({ - "content-type": "image/svg+xml", - "cache-control": "max-age=0, no-cache, no-store, must-revalidate", - }); - - const data = await getCountByName(name); - - if (name === "demo") { - res.set("cache-control", "max-age=31536000"); - } - - // Send the generated SVG as the result - const renderSvg = themify.getCountImage({ - count: data.num, - theme, - padding, - darkmode, - pixelated: isPixelated - }); - - res.send(renderSvg); - - console.log( - data, - `theme: ${theme}`, - `ip: ${req.headers['x-forwarded-for'] || req.connection.remoteAddress}`, - `ref: ${req.get("Referrer") || null}`, - `ua: ${req.get("User-Agent") || null}` - ); -}); +); // JSON record app.get("/record/@:name", async (req, res) => { diff --git a/package.json b/package.json index dc32288..2b6ec71 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,10 @@ "image-size": "^0.8.3", "mime-types": "^2.1.27", "mongoose": "^5.9.28", - "pug": "^3.0.0" + "pug": "^3.0.0", + "zod": "^3.23.8" + }, + "engines": { + "node": "16.x" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6d0a5e2..809153c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,9 @@ dependencies: pug: specifier: ^3.0.0 version: 3.0.3 + zod: + specifier: ^3.23.8 + version: 3.23.8 packages: @@ -1665,3 +1668,7 @@ packages: y18n: 3.2.2 yargs-parser: 2.4.1 dev: false + + /zod@3.23.8: + resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + dev: false diff --git a/utils/index.js b/utils/index.js new file mode 100644 index 0000000..a06a32b --- /dev/null +++ b/utils/index.js @@ -0,0 +1,5 @@ +module.exports = { + randomArray: (arr) => { + return arr[Math.floor(Math.random() * arr.length)] + }, +} diff --git a/utils/themify.js b/utils/themify.js index 589205b..94047db 100644 --- a/utils/themify.js +++ b/utils/themify.js @@ -14,10 +14,10 @@ fs.readdirSync(themePath).forEach(theme => { const imgList = fs.readdirSync(path.resolve(themePath, theme)) imgList.forEach(img => { const imgPath = path.resolve(themePath, theme, img) - const name = path.parse(img).name + const num = path.parse(img).name const { width, height } = sizeOf(imgPath) - themeList[theme][name] = { + themeList[theme][num] = { width, height, data: convertToDatauri(imgPath) @@ -32,8 +32,12 @@ function convertToDatauri(path) { return `data:${mime};base64,${base64}` } -function getCountImage({ count, theme = 'moebooru', padding = 7, pixelated = true, darkmode = 'auto' }) { +function getCountImage(params) { + let { count, theme = 'moebooru', padding = 7, offset = 0, scale = 1, pixelated = '1', darkmode = 'auto' } = params + if (!(theme in themeList)) theme = 'moebooru' + padding = parseInt(padding, 10) + offset = parseInt(offset, 10) // This is not the greatest way for generating an SVG but it'll do for now const countArray = count.toString().padStart(padding, '0').split('') @@ -42,7 +46,9 @@ function getCountImage({ count, theme = 'moebooru', padding = 7, pixelated = tru let x = 0, y = 0 const defs = uniqueChar.reduce((ret, cur) => { - const { width, height, data } = themeList[theme][cur] + let { width, height, data } = themeList[theme][cur] + width *= scale + height *= scale if (height > y) y = height @@ -53,19 +59,23 @@ function getCountImage({ count, theme = 'moebooru', padding = 7, pixelated = tru }, '') const parts = countArray.reduce((ret, cur) => { - const { width } = themeList[theme][cur] + let { width } = themeList[theme][cur] + width *= scale const image = `${ret} ` - x += width + x += width + offset return image }, '') + // Fix the last image offset + x -= offset + const style = ` svg { - ${pixelated ? 'image-rendering: pixelated;' : ''} + ${pixelated === '1' ? 'image-rendering: pixelated;' : ''} ${darkmode === '1' ? 'filter: brightness(.6);' : ''} } ${darkmode === 'auto' ? `@media (prefers-color-scheme: dark) { svg { filter: brightness(.6); } }` : ''} @@ -85,5 +95,6 @@ function getCountImage({ count, theme = 'moebooru', padding = 7, pixelated = tru } module.exports = { + themeList, getCountImage } diff --git a/utils/zod.js b/utils/zod.js new file mode 100644 index 0000000..f5bcc89 --- /dev/null +++ b/utils/zod.js @@ -0,0 +1,50 @@ +function parseError(error) { + const err = JSON.parse(error)[0]; + + return { + code: 400, + message: `The field \`${err.path[0]}\` is invalid. ${err.message}`, + } +} + +module.exports = { + ZodValid: ({ headers, params, query, body }) => { + const handler = (req, res, next) => { + if (headers) { + const result = headers.safeParse(req.headers); + if (!result.success) { + res.status(400).send(parseError(result.error)); + return; + } + } + + if (params) { + const result = params.safeParse(req.params); + if (!result.success) { + res.status(400).send(parseError(result.error)); + return; + } + } + + if (query) { + const result = query.safeParse(req.query); + if (!result.success) { + res.status(400).send(parseError(result.error)); + return; + } + } + + if (body) { + const result = body.safeParse(req.body); + if (!result.success) { + res.status(400).send(parseError(result.error)); + return; + } + } + + next(); + } + + return handler + } +} \ No newline at end of file diff --git a/views/index.pug b/views/index.pug index 8f60810..9b85f3f 100644 --- a/views/index.pug +++ b/views/index.pug @@ -35,7 +35,7 @@ html h3 How to use p Set a unique id for your counter, replace code :name - | in the url, that's all. + | in the url, That's it! h5 SVG address code #{site}/@:name @@ -47,24 +47,15 @@ html code ![:name](#{site}/@:name) h5 e.g. - Moe Count! + Moe Counter! - details#themes + details#themes(style='margin-top: 2em;') summary#more_theme(onclick='_evt_push("click", "normal", "more_theme")') h3(style='display: inline-block; margin: 0; cursor: pointer;') More theme✨ p(style='margin: 0;') Just use the query parameters theme, like this: #{site}/@:name?theme=moebooru - h5 asoul - A-SOUL - h5 moebooru - Moebooru - h5 moebooru-h - Moebooru-Hentai - h5 rule34 - Rule34 - h5 gelbooru - Gelbooru - h5 gelbooru-h - Gelbooru-Hentai + each theme in Object.keys(themeList) + h5 #{theme} + #{theme} h3 Credits ul @@ -74,14 +65,13 @@ html a(href='https://space.bilibili.com/703007996', target='_blank', title='A-SOUL_Official') A-SOUL li a(href='https://github.com/moebooru/moebooru', target='_blank', rel='nofollow') moebooru - li - a(href='javascript:alert("!!! NSFW LINK !!!\\nPlease enter the url manually")') rule34.xxx - | NSFW li a(href='javascript:alert("!!! NSFW LINK !!!\\nPlease enter the url manually")') gelbooru.com | NSFW li a(href='https://icons8.com/icon/80355/star', target='_blank', rel='nofollow') Icons8 + span + i And all booru site... h3 Tool .tool @@ -89,37 +79,58 @@ html thead tr th Param + th Description th Value tbody tr td code name + td Unique counter name td input#name(type='text', placeholder=':name') tr td code theme + td Select a counter image theme, default is + code moebooru td select#theme - option(value='asoul') asoul - option(value='moebooru') moebooru - option(value='moebooru-h') moebooru-h - option(value='rule34') rule34 - option(value='gelbooru') gelbooru - option(value='gelbooru-h') gelbooru-h - tr - td - code pixelated - td - input#pixelated(type='checkbox', checked, style='margin: .5rem .75rem;') + option(value="random", selected) * random + each theme in Object.keys(themeList) + tr td code padding + td Set the minimum length, between 1-32, default is + code 7 td input#padding(type='number', value='7', min='1', max='32', step='1', oninput='this.value = this.value.replace(/[^0-9]/g, "")') + tr + td + code offset + td Set the offset pixel value, between -500-500, default is + code 0 + td + input#offset(type='number', value='0', min='-500', max='500', step='1', oninput='this.value = this.value.replace(/[^0-9|\-]/g, "")') + tr + td + code scale + td Set the image scale, between 0.1-2, default is + code 1 + td + input#scale(type='number', value='1', min='0.1', max='2', step='0.1', oninput='this.value = this.value.replace(/[^0-9|\.]/g, "")') + tr + td + code pixelated + td Enable pixelated mode, Enum 0/1, default is + code 1 + td + input#pixelated(type='checkbox', checked, style='margin: .5rem .75rem;') tr td code darkmode + td Enable dark mode, Enum 0/1/auto, default is + code auto td select#darkmode(name="darkmode") option(value="auto", selected) auto @@ -132,7 +143,13 @@ html code#code(style='visibility: hidden; display: inline-block; margin-bottom: 1em;') img#result(style='display: block;') - script. + p(style='margin-top: 2em;') + a(href='https://github.com/journey-ad/Moe-Counter', target='_blank', onclick='_evt_push("click", "normal", "go_github")') source code + + div.back-to-top + + script. + (function () { var btn = document.getElementById('get'), img = document.getElementById('result'), code = document.getElementById('code') @@ -140,15 +157,13 @@ html btn.addEventListener('click', throttle(function() { var $name = document.getElementById('name'), $theme = document.getElementById('theme'), - $pixelated = document.getElementById('pixelated'), $padding = document.getElementById('padding'), + $offset = document.getElementById('offset'), + $scale = document.getElementById('scale'), + $pixelated = document.getElementById('pixelated'), $darkmode = document.getElementById('darkmode') var name = $name.value ? $name.value.trim() : '' - var theme = $theme.value || 'moebooru' - var pixelated = $pixelated.checked ? '1' : '0' - var padding = $padding.value || '7' - var darkmode = $darkmode.value || 'auto' if(!name) { alert('Please input counter name.') @@ -157,7 +172,19 @@ html party.confetti(this, { count: party.variation.range(20, 40) }); - img.src = `#{site}/@${name}?theme=${theme}&pixelated=${pixelated}&padding=${padding}&darkmode=${darkmode}` + var params = { + name: $name.value ? $name.value.trim() : '', + theme: $theme.value || 'moebooru', + padding: $padding.value || '7', + offset: $offset.value || '0', + scale: $scale.value || '1', + pixelated: $pixelated.checked ? '1' : '0', + darkmode: $darkmode.value || 'auto', + } + + var query = new URLSearchParams(params).toString() + + img.src = `#{site}/@${name}?${query}` code.textContent = img.src code.style.visibility = 'visible' @@ -220,6 +247,50 @@ html } } } + })(); - p(style='margin-top: 2em;') - a(href='https://github.com/journey-ad/Moe-Counter', target='_blank', onclick='_evt_push("click", "normal", "go_github")') source code + script. + (function () { + var isShow = false, lock = false; + var btn = document.querySelector('.back-to-top'); + + window.addEventListener('scroll', function () { + if (lock) return; + + if (document.body.scrollTop >= 1000) { + if (!isShow) btn.classList.add('load'); + isShow = true; + } else { + if (isShow) { + btn.classList.remove('load'); + isShow = false; + } + } + }); + + btn.addEventListener('click', function () { + lock = true; + btn.classList.add('ani-leave'); + + window.scrollTo({ top: 0, behavior: 'smooth' }); + + setTimeout(function () { + btn.classList.remove('ani-leave'); + btn.classList.add('leaved'); + }, 390); + + setTimeout(function () { + btn.classList.add('ending'); + }, 120); + + setTimeout(function () { + btn.classList.remove('load'); + }, 1500); + + setTimeout(function () { + lock = false; + isShow = false; + btn.classList.remove('leaved', 'ending'); + }, 2000); + }); + })();