remote-rdr / utils /bubble /helpers.js
shiveshnavin's picture
Bubble text
f8a99c0
import os from 'os';
import path from 'path';
import fs from 'fs';
import crypto from 'crypto';
export function tempPath(prefix, ext) {
const id = Date.now().toString(36) + '-' + crypto.randomBytes(4).toString('hex');
return path.join(os.tmpdir(), `${prefix}-${id}.${ext}`);
}
export function escapeXml(s) {
return ('' + s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;');
}
export function escapeText(s) {
return ('' + s).replace(/:/g, '\\:').replace(/'/g, "\\'").replace(/\\/g, '\\\\');
}
export function resolveAudioPath(audioKey) {
if (!audioKey) return null;
const key = audioKey;
const assetsDir = path.join(process.cwd(), 'public', 'assets', 'audio-effects');
if (fs.existsSync(assetsDir)) {
const files = fs.readdirSync(assetsDir);
const match = files.find(f => path.parse(f).name === key);
if (match) return path.join(assetsDir, match);
}
// fallback: if key is already a path
if (path.isAbsolute(key) && fs.existsSync(key)) return key;
const candidate = path.join(process.cwd(), key);
if (fs.existsSync(candidate)) return candidate;
return null;
}
// Ensure font file for given fontName exists in public/assets/fonts (or public root). If missing, try downloading from Google Fonts.
export async function ensureFontFile(fontName, fontWeight) {
if (!fontName) return null;
const assetsFontsDir = path.join(process.cwd(), 'public', 'assets', 'fonts');
if (!fs.existsSync(assetsFontsDir)) fs.mkdirSync(assetsFontsDir, { recursive: true });
const normalized = s => ('' + s).replace(/\s+/g, '').toLowerCase();
const target = ('' + fontName).toLowerCase();
// check assets/fonts
const files = fs.readdirSync(assetsFontsDir);
let match = files.find(f => {
const name = path.parse(f).name;
return name.toLowerCase() === target || normalized(name) === normalized(fontName) || name.toLowerCase().includes(target);
});
if (!match && fontWeight) {
const weightNormalized = ('' + fontWeight).toLowerCase();
match = files.find(f => {
const name = path.parse(f).name.toLowerCase();
return name.includes(weightNormalized) || name.includes(target.replace(/\s+/g, '').toLowerCase() + weightNormalized) || name.includes(target + '-' + weightNormalized);
});
}
if (match) return path.join(assetsFontsDir, match);
// check public root
const publicRootFiles = fs.readdirSync(process.cwd()).filter(f => /\.(ttf|otf|woff2?|woff)$/i.test(f));
match = publicRootFiles.find(f => {
const name = path.parse(f).name;
return name.toLowerCase() === target || normalized(name) === normalized(fontName) || name.toLowerCase().includes(target);
});
if (!match && fontWeight) {
const weightNormalized = ('' + fontWeight).toLowerCase();
match = publicRootFiles.find(f => {
const name = path.parse(f).name.toLowerCase();
return name.includes(weightNormalized) || name.includes(target.replace(/\s+/g, '').toLowerCase() + weightNormalized) || name.includes(target + '-' + weightNormalized);
});
}
if (match) return path.join(process.cwd(), match);
// Try downloading from Google Fonts - best-effort with redirect support
try {
// Build Google Fonts family parameter. Include weight when provided so CSS contains specific weight ranges.
const familyBase = fontName.trim().replace(/\s+/g, '+');
const familyParam = fontWeight ? `${familyBase}:wght@${fontWeight}` : familyBase;
const cssUrl = `https://fonts.googleapis.com/css2?family=${familyParam}&display=swap`;
console.log('Attempting to download font from Google Fonts:', cssUrl);
const css = await fetchUrl(cssUrl, 5);
console.log('Fetched CSS for font:', cssUrl);
if (css) {
// find first url(...) occurrence and extract URL
const urlMatches = css.match(/url\((['"]?)[^'"\)]+\1\)/g) || [];
let fontUrl = null;
for (const m of urlMatches) {
let u = m.replace(/^url\((['"]?)/, '').replace(/(['"]?)\)$/, '');
if (u.startsWith('//')) u = 'https:' + u;
if (/^https?:\/\//i.test(u)) { fontUrl = u; break; }
}
if (fontUrl) {
const parsed = new URL(fontUrl);
const ext = path.parse(parsed.pathname).ext || '.woff2';
const weightSuffix = fontWeight ? `_${String(fontWeight).replace(/\s+/g, '')}` : '';
const outFile = path.join(assetsFontsDir, `${fontName.replace(/\s+/g, '_')}${weightSuffix}${ext}`);
await downloadUrl(fontUrl, outFile, 5);
return outFile;
}
}
} catch (e) {
// ignore download errors
}
return null;
}
function fetchUrl(url, redirects = 5) {
return new Promise((resolve, reject) => {
try {
const https = require('https');
const http = require('http');
const doGet = (u, remaining) => {
const client = u.startsWith('https:') ? https : http;
const opts = { headers: { 'User-Agent': 'curl/7.64.1' } };
client.get(u, opts, (res) => {
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location && remaining > 0) {
const loc = res.headers.location.startsWith('http') ? res.headers.location : (u.startsWith('https:') ? 'https:' + res.headers.location : res.headers.location);
return doGet(loc, remaining - 1);
}
if (res.statusCode && res.statusCode >= 400) return reject(new Error('Failed to fetch ' + u + ' status ' + res.statusCode));
let data = '';
res.setEncoding('utf8');
res.on('data', chunk => data += chunk);
res.on('end', () => resolve(data));
}).on('error', reject);
};
doGet(url, redirects);
} catch (e) { reject(e); }
});
}
function downloadUrl(url, outPath, redirects = 5) {
return new Promise((resolve, reject) => {
try {
const https = require('https');
const http = require('http');
const doGet = (u, remaining) => {
const client = u.startsWith('https:') ? https : http;
client.get(u, (res) => {
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location && remaining > 0) {
const loc = res.headers.location.startsWith('http') ? res.headers.location : (u.startsWith('https:') ? 'https:' + res.headers.location : res.headers.location);
return doGet(loc, remaining - 1);
}
if (res.statusCode && res.statusCode >= 400) return reject(new Error('Failed to download ' + u + ' status ' + res.statusCode));
const file = fs.createWriteStream(outPath);
res.pipe(file);
file.on('finish', () => file.close(() => resolve(outPath)));
file.on('error', (err) => { try { fs.unlinkSync(outPath); } catch (e) { }; reject(err); });
}).on('error', (err) => reject(err));
};
doGet(url, redirects);
} catch (e) { reject(e); }
});
}