UNPKG

@fajarkim/github-readme-profile

Version:

🙀 Generate your Stats GitHub Profile in SVG

129 lines (117 loc) • 5.57 kB
import escapeHTML from "escape-html"; import { Resvg } from "@resvg/resvg-js"; import getData from "../src/getData"; import card from "../src/card"; import { themes, Themes } from "../themes/index"; import { isValidHexColor, isValidGradient, parseBoolean } from "../src/common/utils"; /** * Type representing the configuration for the card options. * * @typedef {Object} UiConfig * @property {string} titleColor - Color for the title text. * @property {string} textColor - Color for the main text. * @property {string} iconColor - Color for icons. * @property {string} borderColor - Color for borders. * @property {string} strokeColor - Color for strokes. * @property {string} usernameColor - Color for the username. * @property {any} bgColor - Background color or gradient. * @property {string} Locale - Locale setting. * @property {number|string} borderWidth - Width of borders. * @property {number|string} borderRadius - Radius of borders. * @property {boolean|string} disabledAnimations - Toggle for disabling animations. * @property {string} Format - Output format (e.g., "svg", "png", or "json"). * @property {string|undefined} hiddenItems - Items to hide. * @property {string|undefined} showItems - Items to show. * @property {boolean|string} hideStroke - Toggle for hiding strokes. * @property {boolean|string} hideBorder - Toggle for hiding borders. */ type UiConfig = { titleColor: string; textColor: string; iconColor: string; borderColor: string; strokeColor: string; usernameColor: string; bgColor: any; Locale: string; borderWidth: number | string; borderRadius: number | string; disabledAnimations: boolean | string; Format: string; hiddenItems: string | undefined; showItems: string | undefined; hideStroke: boolean | string; hideBorder: boolean | string; }; /** * Handles the generation card of a GitHub stats based on user data and specified options. * * @param {any} req - The request object from the client. * @param {any} res - The response object to send data back to the client. * @returns {Promise<any>} - A promise that resolves when the photo profile is generated and sent. */ async function readmeStats(req: any, res: any): Promise<any> { try { const username = escapeHTML(req.query.username); const fallbackTheme = "default"; const defaultTheme: Themes[keyof Themes] = themes[fallbackTheme]; const selectTheme: Themes[keyof Themes] = themes[req.query.theme] || defaultTheme; const uiConfig: UiConfig = { titleColor: escapeHTML(req.query.title_color || selectTheme.title_color || defaultTheme.title_color), textColor: escapeHTML(req.query.text_color || selectTheme.text_color || defaultTheme.text_color), iconColor: escapeHTML(req.query.icon_color || selectTheme.icon_color || defaultTheme.icon_color), borderColor: escapeHTML(req.query.border_color || selectTheme.border_color || defaultTheme.border_color), strokeColor: escapeHTML(req.query.stroke_color || req.query.border_color || selectTheme.stroke_color || selectTheme.border_color || defaultTheme.border_color), usernameColor: escapeHTML(req.query.username_color || req.query.text_color || selectTheme.username_color || selectTheme.text_color || defaultTheme.text_color), bgColor: escapeHTML(req.query.bg_color || selectTheme.bg_color || defaultTheme.bg_color), Locale: escapeHTML(req.query.locale || "en"), borderWidth: escapeHTML(req.query.border_width || 1), borderRadius: escapeHTML(req.query.border_radius || 4.5), disabledAnimations: parseBoolean(escapeHTML(req.query.disabled_animations)) || false, Format: escapeHTML(req.query.format || "svg"), hiddenItems: escapeHTML(req.query.hide), showItems: escapeHTML(req.query.show), hideStroke: parseBoolean(escapeHTML(req.query.hide_stroke)) || false, hideBorder: parseBoolean(escapeHTML(req.query.hide_border)) || false, }; if (!username) { throw new Error("Username is required"); } if ( !isValidHexColor(uiConfig.titleColor) || !isValidHexColor(uiConfig.textColor) || !isValidHexColor(uiConfig.iconColor) || !isValidHexColor(uiConfig.borderColor) || !isValidHexColor(uiConfig.usernameColor) || !isValidHexColor(uiConfig.strokeColor) ) { throw new Error("Enter a valid hex color code"); } if (!isValidGradient(uiConfig.bgColor)) { if (!isValidHexColor(uiConfig.bgColor)) { throw new Error("Enter a valid hex color code"); } } const fetchStats = await getData(username); res.setHeader("Cache-Control", "s-maxage=3600, stale-while-revalidate"); if (uiConfig.Format === "json") { res.json(fetchStats); } else if (uiConfig.Format === "png") { const svgString = card(fetchStats, uiConfig); const resvg = new Resvg(svgString, { font: { defaultFontFamily: "Segoe UI" }}); const pngBuffer = await resvg.render().asPng(); res.setHeader("Content-Type", "image/png"); res.send(pngBuffer); } else { res.setHeader("Content-Type", "image/svg+xml"); const svg = card(fetchStats, uiConfig); res.send(svg); } } catch (error: any) { const message = error.message; res.setHeader("Cache-Control", "s-maxage=7200, stale-while-revalidate"); res.status(500).send(escapeHTML(message)); } } export { UiConfig, readmeStats }; export default readmeStats;