diff --git a/build/package.json b/package.json old mode 100755 new mode 100644 similarity index 64% rename from build/package.json rename to package.json index 6ddc0425..b09618ef --- a/build/package.json +++ b/package.json @@ -1,13 +1,12 @@ -{ - "name": "test-repo", - "version": "1.0.0", - "main": "server.js", - "scripts": { - "start": "node server.js" - }, - "type": "module", - "dependencies": { - "express": "^4.18.2", - "node-fetch": "^3.3.0" - } +{ + "name": "selfh-st_icons", + "version": "1.0.0", + "main": "server.js", + "scripts": { + "start": "node server.js" + }, + "type": "module", + "dependencies": { + "express": "^4.18.2" + } } \ No newline at end of file diff --git a/build/server.js b/server.js old mode 100755 new mode 100644 similarity index 58% rename from build/server.js rename to server.js index 5c812048..e7cf154e --- a/build/server.js +++ b/server.js @@ -1,89 +1,88 @@ -import express from 'express' -import fetch from 'node-fetch' -import path from 'path' - -const app = express() -const PORT = 4050 -const CDN_ROOT = 'https://cdn.jsdelivr.net/gh/selfhst/icons' -const CDN_PATH = 'svg' - -async function fileExists(url) { - try { - const resp = await fetch(url, { method: 'HEAD' }); - return resp.ok; - } catch { - return false; - } -} - -async function fetchAndPipe(url, res) { - const response = await fetch(url); - if (!response.ok) return res.status(404).send('File not found'); - res.type(path.extname(url).slice(1)); - response.body.pipe(res); -} - -app.get('/*', async (req, res) => { - const urlPath = req.path; - const extMatch = urlPath.match(/\.(\w+)$/); - if (!extMatch) - return res.status(404).send('File extension missing'); - - const ext = extMatch[1].toLowerCase(); - if (!['png', 'webp', 'svg'].includes(ext)) - return res.status(404).send('Format not supported'); - - const filename = urlPath.slice(1); - const lowerFilename = filename.toLowerCase(); - - const isSuffix = lowerFilename.endsWith('-light.svg') || lowerFilename.endsWith('-dark.svg'); - - if (isSuffix) { - return fetchAndPipe(`${CDN_ROOT}/${CDN_PATH}/${filename}`, res); - } - - let mainUrl; - if (ext === 'png') { - mainUrl = `${CDN_ROOT}/png/${filename}`; - } else if (ext === 'webp') { - mainUrl = `${CDN_ROOT}/webp/${filename}`; - } else if (ext === 'svg') { - mainUrl = `${CDN_ROOT}/svg/${filename}`; - } else { - mainUrl = null; - } - - const hasColor = !!req.query['color'] && req.query['color'].trim() !== ''; - - if (ext === 'svg') { - if (hasColor) { - const baseName = filename.replace(/\.(png|webp|svg)$/, ''); - const suffixUrl = `${CDN_ROOT}/${CDN_PATH}/${baseName}-light.svg`; - if (await fileExists(suffixUrl)) { - let svgContent = await fetch(suffixUrl).then(r => r.text()); - const color = req.query['color'].startsWith('#') ? req.query['color'] : `#${req.query['color']}`; - svgContent = svgContent - .replace(/style="[^"]*fill:\s*#fff[^"]*"/gi, (match) => { - console.log('Replacing style fill:', match); - return match.replace(/fill:\s*#fff/gi, `fill:${color}`); - }) - .replace(/fill="#fff"/gi, `fill="${color}"`); - return res.type('image/svg+xml').send(svgContent); - } else { - return fetchAndPipe(mainUrl, res); - } - } else { - return fetchAndPipe(mainUrl, res); - } - } else { - // PNG/WebP: serve directly - return fetchAndPipe(mainUrl, res); - } -}); - -app.get('/', (req, res) => { - res.send('Self-hosted icon server'); -}); -app.listen(PORT, () => { - console.log(`Listening on port ${PORT}`); +import fs from 'fs' +import express from 'express' +import path from 'path' + +const app = express() +const PORT = 4050 + +const APP_DIR = process.cwd() + +async function readFile(filePath, res) { + const readStream = fs.createReadStream(filePath); + + readStream.on("open", () => { + res.type(path.extname(filePath).slice(1)); + readStream.pipe(res); + }); + readStream.on("error", () => { + res.set("Content-Type", "text/plain"); + res.status(404).end("Not found"); + }); +} + +app.get('/', (_req, res) => { + res.send('Self-hosted icon server'); +}); + +app.get('/*', async (req, res) => { + const urlPath = req.path; + const extMatch = urlPath.match(/\.(\w+)$/); + if (!extMatch) + return res.status(404).send('File extension missing'); + + const ext = extMatch[1].toLowerCase(); + if (!['png', 'webp', 'svg'].includes(ext)) + return res.status(404).send('Format not supported'); + + const filename = urlPath.slice(1); + const lowerFilename = filename.toLowerCase(); + + const isSuffix = lowerFilename.endsWith('-light.svg') || lowerFilename.endsWith('-dark.svg'); + + if (isSuffix) { + return readFile(`${APP_DIR}/svg/${filename}`, res); + } + + let filePath; + if (ext === 'png') { + filePath = `${APP_DIR}/png/${filename}`; + } else if (ext === 'webp') { + filePath = `${APP_DIR}/webp/${filename}`; + } else if (ext === 'svg') { + filePath = `${APP_DIR}/svg/${filename}`; + } else { + filePath = null; + } + + const hasColor = !!req.query['color'] && req.query['color'].trim() !== ''; + + if (ext === 'svg') { + if (hasColor) { + const baseName = filename.replace(/\.(png|webp|svg)$/, ''); + const baseFile = `${APP_DIR}/svg/${baseName}-light.svg`; + + if (fs.existsSync(baseFile)) { + let svgContent = fs.readFileSync(baseFile, { encoding: 'utf-8'}); + const color = req.query['color'].startsWith('#') ? req.query['color'] : `#${req.query['color']}`; + svgContent = svgContent + .replace(/style="[^"]*fill:\s*#fff[^"]*"/gi, (match) => { + console.log('Replacing style fill:', match); + return match.replace(/fill:\s*#fff/gi, `fill:${color}`); + }) + .replace(/fill="#fff"/gi, `fill="${color}"`); + return res.type('image/svg+xml').send(svgContent); + } else { + return res.status(404).end("Not found"); + } + } else { + return readFile(filePath, res); + } + } else { + // PNG/WebP: serve directly + return readFile(filePath, res); + } +}); + +app.listen(PORT, () => { + console.log(`Listening on port ${PORT}`); }); \ No newline at end of file