const express = require('express'); const axios = require('axios'); const app = express(); const PORT = process.env.PORT || 3000; 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 { // Ikon letöltése 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; // Ha nincs label paraméter, akkor az ikon SVG mezőjét használjuk 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 méretek const padding = 10; const iconSize = parseInt(size); const fontSize = Math.round(iconSize * 0.6); // Font size az ikon méret 60%-a const textPadding = Math.round(iconSize * 0.4); // Dinamikus: size 40%-a 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') { // Próbáljuk kinyerni az ikon fő színét a SimpleIcons API-ból try { const iconMetaUrl = `https://cdn.simpleicons.org/${icon}`; const iconMetaResp = await axios.get(iconMetaUrl, { responseType: 'text' }); // Kikeressük az első "fill=" attribútumot const match = iconMetaResp.data.match(/fill="([^"]+)"/); if (match && match[1]) { effectiveColor = match[1]; } else { effectiveColor = "#222"; // fallback } } catch { effectiveColor = "#222"; } } // ÚJ: doboz magasság 5 pixellel nagyobb legyen az ikon méreténél const boxHeight = iconSize + 5; const labelBoxHeight = boxHeight; const totalHeight = Math.max(boxHeight, fontSize + padding * 2); 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(iconSize + 5, textWidth); const labelRectHeight = fontSize + padding; const iconRectY = labelpos === 'above' ? labelRectHeight : 0; const labelRectY = labelpos === 'above' ? 0 : iconSize + 5; height = iconSize + 5 + 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="${width}" height="${height}" 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`); });