* Upload a base64 data URI to catbox.moe and return a public URL. * Google image models (nano-banana) return data URIs instead of hosted URLs, * which breaks Telegram and other clients that can't render raw base64.
(dataUri: string)
| 1847 | * which breaks Telegram and other clients that can't render raw base64. |
| 1848 | */ |
| 1849 | async function uploadDataUriToHost(dataUri: string): Promise<string> { |
| 1850 | const match = dataUri.match(/^data:(image\/\w+);base64,(.+)$/); |
| 1851 | if (!match) throw new Error("Invalid data URI format"); |
| 1852 | const [, mimeType, b64Data] = match; |
| 1853 | const ext = mimeType === "image/jpeg" ? "jpg" : (mimeType.split("/")[1] ?? "png"); |
| 1854 | |
| 1855 | const buffer = Buffer.from(b64Data, "base64"); |
| 1856 | const blob = new Blob([buffer], { type: mimeType }); |
| 1857 | |
| 1858 | const form = new FormData(); |
| 1859 | form.append("reqtype", "fileupload"); |
| 1860 | form.append("fileToUpload", blob, `image.${ext}`); |
| 1861 | |
| 1862 | const uploadController = new AbortController(); |
| 1863 | const uploadTimeout = setTimeout(() => uploadController.abort(), 30_000); |
| 1864 | try { |
| 1865 | const resp = await fetch("https://catbox.moe/user/api.php", { |
| 1866 | method: "POST", |
| 1867 | body: form, |
| 1868 | signal: uploadController.signal, |
| 1869 | }); |
| 1870 | |
| 1871 | if (!resp.ok) throw new Error(`catbox.moe upload failed: HTTP ${resp.status}`); |
| 1872 | const result = await resp.text(); |
| 1873 | if (result.startsWith("https://")) { |
| 1874 | return result.trim(); |
| 1875 | } |
| 1876 | throw new Error(`catbox.moe upload failed: ${result}`); |
| 1877 | } finally { |
| 1878 | clearTimeout(uploadTimeout); |
| 1879 | } |
| 1880 | } |
| 1881 | |
| 1882 | /** |
| 1883 | * Start the local x402 proxy server. |