155 lines
6.5 KiB
JavaScript
155 lines
6.5 KiB
JavaScript
const express = require('express');
|
|
const axios = require('axios');
|
|
const helmet = require('helmet');
|
|
const app = express();
|
|
const PORT = process.env.PORT || 3000;
|
|
|
|
app.use(helmet({
|
|
crossOriginResourcePolicy: false,
|
|
}));
|
|
|
|
app.get('/badge', async (req, res) => {
|
|
const {
|
|
icon = 'simpleicons',
|
|
label = 'Simple Icons',
|
|
bgicon = '#cccccc', // ikon háttérszín, alap: világosszürke
|
|
bglabel = '#444444', // label háttérszín, alap: sötétszürke
|
|
color = '#000000',
|
|
size = 24,
|
|
labelpos = 'right',
|
|
fontweight = 'normal',
|
|
} = req.query;
|
|
|
|
try {
|
|
// download icon SVG from SimpleIcons CDN
|
|
const iconUrl = `https://cdn.simpleicons.org/${icon}?viewbox=auto&size=${size}`;
|
|
const iconResponse = await axios.get(iconUrl, { responseType: 'text' });
|
|
if (iconResponse.status !== 200) {
|
|
return res.status(404).send('Icon not found');
|
|
}
|
|
const iconSVG = iconResponse.data;
|
|
|
|
// if there is no label query, use the title from the SVG
|
|
let effectiveLabel = label;
|
|
if (!req.query.label || req.query.label === 'Simple Icons') {
|
|
const titleMatch = iconSVG.match(/<title>([^<]+)<\/title>/i);
|
|
if (titleMatch && titleMatch[1]) {
|
|
effectiveLabel = titleMatch[1];
|
|
}
|
|
}
|
|
|
|
// Badge size
|
|
const padding = 10;
|
|
const iconSize = parseInt(size);
|
|
const fontSize = Math.round(iconSize * 0.6);
|
|
const textPadding = Math.round(iconSize * 0.4);
|
|
const radius = 8;
|
|
const approxCharWidth = fontSize * 0.6;
|
|
const textWidth = effectiveLabel.length * approxCharWidth;
|
|
|
|
// Kinyerjük az ikon SVG-ből a width és height attribútumokat (ha vannak)
|
|
let iconViewBoxWidth = iconSize;
|
|
let iconViewBoxHeight = iconSize;
|
|
const widthMatch = iconSVG.match(/width="(\d+\.?\d*)"/);
|
|
const heightMatch = iconSVG.match(/height="(\d+\.?\d*)"/);
|
|
if (widthMatch) iconViewBoxWidth = parseFloat(widthMatch[1]);
|
|
if (heightMatch) iconViewBoxHeight = parseFloat(heightMatch[1]);
|
|
|
|
// Ikon doboz mérete
|
|
const iconBoxWidth = iconViewBoxWidth + 5;
|
|
const iconBoxHeight = iconViewBoxHeight + 5;
|
|
|
|
// Ha a color paraméter nincs megadva, próbáljuk kinyerni az ikon színét
|
|
let effectiveColor = color;
|
|
if (!req.query.color || req.query.color === '#000000') {
|
|
const match = iconSVG.match(/fill="([^"]+)"/);
|
|
if (match && match[1]) {
|
|
effectiveColor = match[1];
|
|
} else {
|
|
effectiveColor = "#222"; // fallback
|
|
}
|
|
}
|
|
|
|
// doboz magasság 5 pixellel nagyobb legyen az ikon méreténél
|
|
const iconSizeAndPadding = iconSize + 5;
|
|
const labelRectHeight = fontSize + padding;
|
|
|
|
let width, height, iconGroup, textElem;
|
|
|
|
const fontFamily = "Verdana,Geneva,DejaVu Sans,sans-serif";
|
|
const fontWeight = fontweight; // paraméterből
|
|
|
|
if (labelpos === 'left') {
|
|
// Külön dobozok: padding csak a széleken kell, a dobozokon belül nem!
|
|
textElem = `<rect x="0" y="0" width="${textWidth}" height="${iconBoxHeight}" rx="${radius}" fill="${bglabel}"/>
|
|
<text x="${textWidth / 2}" y="${iconBoxHeight / 2}" font-size="${fontSize}" font-family="${fontFamily}" font-weight="${fontWeight}" fill="${effectiveColor}" text-anchor="middle" dominant-baseline="middle">${effectiveLabel}</text>`;
|
|
iconGroup = `
|
|
<rect x="${textWidth}" y="0" width="${iconBoxWidth + 5}" height="${iconBoxHeight}" rx="${radius}" fill="${bgicon}"/>
|
|
<g transform="translate(${textWidth + (5 / 2)}, ${(iconBoxHeight - iconSize) / 2})">${iconSVG}</g>
|
|
`;
|
|
} else if (labelpos === 'above' || labelpos === 'below') {
|
|
// Felső vagy alsó label: ikon doboz magassága = iconSize + 5, szélessége = szöveg doboz szélessége
|
|
width = Math.max(iiconSizeAndPadding, textWidth);
|
|
|
|
const iconRectY = labelpos === 'above'
|
|
? labelRectHeight
|
|
: 0;
|
|
const labelRectY = labelpos === 'above'
|
|
? 0
|
|
: iiconSizeAndPadding;
|
|
height = iconSizeAndPadding + textPadding + labelRectHeight;
|
|
|
|
// Label háttér
|
|
const labelRect = `<rect x="0" y="${labelRectY}" width="${width}" height="${labelRectHeight}" rx="${radius}" fill="${bglabel}"/>`;
|
|
// Ikon háttér
|
|
const iconRect = `<rect x="0" y="${iconRectY}" width="${width}" height="${iconSize + 5}" rx="${radius}" fill="${bgicon}"/>`;
|
|
|
|
// Szöveg
|
|
const textY = labelpos === 'above'
|
|
? labelRectY + 5 + labelRectHeight / 2
|
|
: labelRectY + 5 + labelRectHeight / 2;
|
|
textElem = `
|
|
${labelRect}
|
|
<text x="${width / 2}" y="${textY}" font-size="${fontSize}" font-family="${fontFamily}" font-weight="${fontWeight}" fill="${effectiveColor}" text-anchor="middle" dominant-baseline="middle">${effectiveLabel}</text>
|
|
`;
|
|
|
|
// Ikon
|
|
iconGroup = `
|
|
${iconRect}
|
|
<g transform="translate(${(width - iconSize) / 2 + 2.5}, ${iconRectY + (iconSize + 5 - iconSize) / 2})">${iconSVG}</g>
|
|
`;
|
|
} else {
|
|
// Alapértelmezett: ikon balra, szöveg jobbra (függőleges közép)
|
|
iconGroup = `
|
|
<rect x="0" y="0" width="${iconBoxWidth}" height="${iconBoxHeight}" rx="${radius}" fill="${bgicon}"/>
|
|
<g transform="translate(${(iconBoxWidth - iconViewBoxWidth) / 2}, ${(iconBoxHeight - iconViewBoxHeight) / 2})">${iconSVG}</g>
|
|
`;
|
|
textElem = `<rect x="${iconBoxWidth}" y="0" width="${textWidth}" height="${iconBoxHeight}" rx="${radius}" fill="${bglabel}"/>
|
|
<text x="${iconBoxWidth + textWidth / 2}" y="${iconBoxHeight / 2}" font-size="${fontSize}" font-family="${fontFamily}" font-weight="${fontWeight}" fill="${effectiveColor}" text-anchor="middle" dominant-baseline="middle">${effectiveLabel}</text>`;
|
|
}
|
|
|
|
// SVG badge string összefűzéssel, szöveg árnyékkal
|
|
const svg = `
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="${(labelpos === 'left' || labelpos === 'right') ? iconBoxWidth + textWidth : width}" height="${(labelpos === 'left' || labelpos === 'right') ? iconBoxHeight : labelRectHeight+iconSize + 5}" style="background:none">
|
|
<defs>
|
|
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
|
<feDropShadow dx="0" dy="1" stdDeviation="1" flood-color="#000" flood-opacity="0.25"/>
|
|
</filter>
|
|
</defs>
|
|
${iconGroup}
|
|
${textElem.replace('<text ', '<text filter="url(#shadow)" ')}
|
|
</svg>
|
|
`.trim();
|
|
|
|
res.setHeader('Content-Type', 'image/svg+xml');
|
|
res.send(svg);
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.status(500).send('Internal Server Error');
|
|
}
|
|
});
|
|
|
|
app.listen(PORT, () => {
|
|
console.log(`🚀 Badge API running at http://localhost:${PORT}/badge`);
|
|
});
|