/** * QR Code Monkey API — custom QR with logo and design * @see https://www.qrcode-monkey.com/qr-code-api-with-logo/ */ import { loadBrandSpec, mimQrBrandSpec } from './qr-brand.js' const DIRECT_BASE = 'https://api.qrcode-monkey.com' const RAPIDAPI_BASE = 'https://qrcode-monkey.p.rapidapi.com' const RAPIDAPI_HOST = 'qrcode-monkey.p.rapidapi.com' export function getQrMonkeyBaseUrl({ preferRapidApi } = {}) { const custom = String(process.env.QR_MONKEY_API_BASE || '').trim() if (custom) return custom.replace(/\/$/, '') const useRapid = preferRapidApi || ['1', 'true', 'yes'].includes(String(process.env.QR_MONKEY_USE_RAPIDAPI || '').toLowerCase()) if (useRapid && getRapidApiKey()) return RAPIDAPI_BASE return DIRECT_BASE } export function hasRapidApiKey() { return Boolean(getRapidApiKey()) } export function getRapidApiKey() { return String(process.env.RAPIDAPI_KEY || process.env.QR_MONKEY_RAPIDAPI_KEY || '').trim() } export function isQrMonkeyConfigured() { return true } export function defaultMonkeyConfig(publicUrl) { const spec = loadBrandSpec() const brand = mimQrBrandSpec(publicUrl) const monkey = { ...spec.qrcodeMonkey } if (brand.logoUrl) monkey.logo = brand.logoUrl const uploaded = String(process.env.QR_MONKEY_LOGO_FILE || '').trim() if (uploaded) monkey.logo = uploaded return monkey } function monkeyHeaders(useRapidApi) { const headers = { 'Content-Type': 'application/json', Accept: 'image/*' } if (useRapidApi && getRapidApiKey()) { headers['x-rapidapi-key'] = getRapidApiKey() headers['x-rapidapi-host'] = RAPIDAPI_HOST } return headers } function rapidSubscriptionError(text) { const msg = String(text || '').toLowerCase() return msg.includes('not subscribed') || msg.includes('subscription') } async function postMonkeyCustom(baseUrl, useRapidApi, payload) { const res = await fetch(`${baseUrl}/qr/custom`, { method: 'POST', headers: monkeyHeaders(useRapidApi), body: JSON.stringify(payload), }) const contentType = res.headers.get('content-type') || '' if (!res.ok) { const text = await res.text().catch(() => '') return { res, text, buffer: null, contentType } } const buffer = Buffer.from(await res.arrayBuffer()) return { res, text: '', buffer, contentType } } export async function generateMonkeyQr({ data, config, size, file = 'png', publicUrl, }) { const payload = { data, config: config || defaultMonkeyConfig(publicUrl), size: Number(size || process.env.QR_MONKEY_SIZE || 600), download: false, file, } const useRapid = ['1', 'true', 'yes'].includes(String(process.env.QR_MONKEY_USE_RAPIDAPI || '').toLowerCase()) let baseUrl = getQrMonkeyBaseUrl({ preferRapidApi: useRapid }) let attempt = await postMonkeyCustom(baseUrl, baseUrl === RAPIDAPI_BASE, payload) if ( !attempt.res.ok && baseUrl === RAPIDAPI_BASE && (attempt.res.status === 403 || attempt.res.status === 401) && rapidSubscriptionError(attempt.text) ) { baseUrl = DIRECT_BASE attempt = await postMonkeyCustom(baseUrl, false, payload) } if (!attempt.res.ok) { const err = new Error( `QR Code Monkey error (${attempt.res.status})${attempt.text ? `: ${attempt.text.slice(0, 200)}` : ''}`, ) err.status = attempt.res.status throw err } return { buffer: attempt.buffer, contentType: attempt.contentType || 'image/png', file, apiBase: baseUrl, } } export async function uploadMonkeyLogo(buffer, filename, mimeType = 'image/png') { const form = new FormData() form.append('file', new Blob([buffer], { type: mimeType }), filename) const headers = {} const rapidKey = getRapidApiKey() if (rapidKey) { headers['x-rapidapi-key'] = rapidKey headers['x-rapidapi-host'] = RAPIDAPI_HOST } const res = await fetch(`${getQrMonkeyBaseUrl()}/qr/uploadImage`, { method: 'POST', headers, body: form, }) const data = await res.json().catch(() => ({})) if (!res.ok) { const err = new Error(data?.message || `QR Code Monkey upload failed (${res.status})`) err.status = res.status throw err } return data.file }