Split mim-api into modular routes, refresh nav brand assets, and pin reusable validate workflow to Order-of-Hospitallers/miracles_in_motion@main. Co-authored-by: Cursor <[email protected]>
142 lines
4.1 KiB
JavaScript
142 lines
4.1 KiB
JavaScript
/**
|
|
* 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
|
|
}
|