|
2 | 2 | <html lang="en"> |
3 | 3 | <head> |
4 | 4 | <meta charset="UTF-8"> |
5 | | - <title>Perchance + allOrigins (CORS fixed)</title> |
| 5 | + <title>Perchance Generator - Working CORS</title> |
6 | 6 | <style> |
7 | 7 | body { font-family: system-ui, Arial, sans-serif; max-width: 800px; margin: 2rem auto; padding: 20px; } |
8 | | - #output { padding: 1rem; border: 1px solid #ccc; border-radius: 12px; min-height: 80px; margin: 1rem 0; background: #f9f9fc; } |
9 | | - button, input { padding: 8px 14px; font-size: 1rem; border-radius: 40px; border: 1px solid #aaa; } |
| 8 | + #output { padding: 1rem; border: 1px solid #ccc; border-radius: 12px; min-height: 80px; margin: 1rem 0; background: #f9f9fc; white-space: pre-wrap; } |
| 9 | + button, input, select { padding: 8px 14px; font-size: 1rem; border-radius: 40px; border: 1px solid #aaa; margin: 5px; } |
10 | 10 | button { background: #1e2f3e; color: white; cursor: pointer; } |
11 | 11 | button:hover { background: #0f1e2a; } |
| 12 | + button:disabled { opacity: 0.5; cursor: not-allowed; } |
12 | 13 | .error { color: #c33; background: #ffe8e6; padding: 0.5rem; border-radius: 12px; } |
13 | 14 | .success { background: #e0f2e9; padding: 0.5rem; border-radius: 12px; } |
| 15 | + .info { background: #e3f2fd; padding: 0.5rem; border-radius: 12px; font-size: 0.9rem; margin-top: 1rem; } |
14 | 16 | </style> |
15 | 17 | </head> |
16 | 18 | <body> |
17 | | -<h1>🎲 Perchance Generator (via allOrigins)</h1> |
18 | | -<p>Using <code>api.allorigins.win/raw</code> - reliable CORS proxy</p> |
| 19 | +<h1>🎲 Perchance Generator (CORS Fixed)</h1> |
| 20 | +<p>Using reliable CORS proxies</p> |
19 | 21 |
|
20 | | -<label><strong>Generator name:</strong></label><br> |
21 | | -<input type="text" id="generatorInput" value="fantasy-character" style="width: 70%; margin-top: 6px;"> |
| 22 | +<input type="text" id="generatorInput" value="fantasy-character" style="width: 60%;"> |
| 23 | +<select id="proxySelect"> |
| 24 | + <option value="cloudflare">Cloudflare CORS (fastest)</option> |
| 25 | + <option value="x2u">cors.x2u.in (clean)</option> |
| 26 | + <option value="codetabs">codetabs (stable)</option> |
| 27 | + <option value="thebugging">thebugging (10/hour limit)</option> |
| 28 | +</select> |
22 | 29 |
|
23 | 30 | <div id="output">✨ Click Generate</div> |
24 | 31 | <button id="generateBtn">Generate</button> |
25 | 32 |
|
| 33 | +<div class="info"> |
| 34 | + 💡 <strong>Recommended:</strong> Cloudflare CORS proxy - fastest & no limits |
| 35 | +</div> |
| 36 | + |
26 | 37 | <script> |
27 | 38 | const generateBtn = document.getElementById('generateBtn'); |
28 | 39 | const generatorInput = document.getElementById('generatorInput'); |
| 40 | + const proxySelect = document.getElementById('proxySelect'); |
29 | 41 | const outputDiv = document.getElementById('output'); |
30 | 42 |
|
| 43 | + function getProxyUrl(directUrl) { |
| 44 | + const proxyType = proxySelect.value; |
| 45 | + switch(proxyType) { |
| 46 | + case 'cloudflare': |
| 47 | + return `https://test.cors.workers.dev/?${encodeURIComponent(directUrl)}`; |
| 48 | + case 'x2u': |
| 49 | + return `https://cors.x2u.in/${directUrl}`; |
| 50 | + case 'codetabs': |
| 51 | + return `https://codetabs.com/cors-proxy/cors-proxy.html?url=${encodeURIComponent(directUrl)}`; |
| 52 | + case 'thebugging': |
| 53 | + return `https://www.thebugging.com/apis/cors-proxy?url=${encodeURIComponent(directUrl)}`; |
| 54 | + default: |
| 55 | + return `https://test.cors.workers.dev/?${encodeURIComponent(directUrl)}`; |
| 56 | + } |
| 57 | + } |
| 58 | + |
31 | 59 | async function fetchFromPerchance() { |
32 | 60 | const generatorName = generatorInput.value.trim(); |
33 | 61 | if (!generatorName) { |
34 | | - outputDiv.innerHTML = '<span class="error">❌ Enter a generator name</span>'; |
| 62 | + outputDiv.innerHTML = '<span class="error">❌ Please enter a generator name</span>'; |
35 | 63 | return; |
36 | 64 | } |
37 | 65 |
|
38 | | - // Direct Perchance API URL |
39 | 66 | const directUrl = `https://perchance.org/api/generate?generator=${encodeURIComponent(generatorName)}&count=1`; |
40 | | - |
41 | | - // allOrigins proxy (RAW endpoint returns pure JSON) |
42 | | - const proxyUrl = `https://api.allorigins.win/raw?url=${encodeURIComponent(directUrl)}`; |
| 67 | + const proxyUrl = getProxyUrl(directUrl); |
43 | 68 |
|
44 | 69 | generateBtn.disabled = true; |
45 | 70 | generateBtn.textContent = '⏳ Loading...'; |
46 | | - outputDiv.innerHTML = '🌀 Fetching via allOrigins...'; |
| 71 | + outputDiv.innerHTML = '🌀 Fetching via proxy...'; |
47 | 72 |
|
48 | 73 | try { |
49 | | - const response = await fetch(proxyUrl); |
50 | | - |
| 74 | + const controller = new AbortController(); |
| 75 | + const timeoutId = setTimeout(() => controller.abort(), 15000); |
| 76 | + const response = await fetch(proxyUrl, { signal: controller.signal }); |
| 77 | + clearTimeout(timeoutId); |
| 78 | + |
51 | 79 | if (!response.ok) { |
52 | | - throw new Error(`HTTP ${response.status}`); |
| 80 | + throw new Error(`HTTP ${response.status}: ${response.statusText}`); |
53 | 81 | } |
54 | 82 |
|
55 | 83 | const data = await response.json(); |
56 | 84 |
|
57 | | - // Perchance returns array: ["result text"] |
| 85 | + // Handle different proxy response formats |
| 86 | + let result = null; |
58 | 87 | if (Array.isArray(data) && data.length > 0) { |
59 | | - outputDiv.innerHTML = `<div class="success">✨ ${escapeHtml(data[0])}</div>`; |
| 88 | + result = data[0]; |
| 89 | + } else if (data && data.contents) { |
| 90 | + // codetabs wraps in contents |
| 91 | + const parsed = JSON.parse(data.contents); |
| 92 | + result = parsed[0]; |
| 93 | + } else if (data && typeof data === 'object' && data.data) { |
| 94 | + result = data.data; |
| 95 | + } else { |
| 96 | + result = JSON.stringify(data); |
| 97 | + } |
| 98 | + |
| 99 | + if (result) { |
| 100 | + outputDiv.innerHTML = `<div class="success">✨ ${escapeHtml(result)}</div>`; |
60 | 101 | } else { |
61 | 102 | outputDiv.innerHTML = '<span class="error">⚠️ Unexpected response format</span>'; |
62 | 103 | } |
63 | 104 | } catch (err) { |
64 | 105 | console.error(err); |
65 | | - outputDiv.innerHTML = `<span class="error">❌ ${escapeHtml(err.message)}</span>`; |
| 106 | + let errorMsg = err.message; |
| 107 | + if (err.name === 'AbortError') { |
| 108 | + errorMsg = 'Timeout - proxy took too long'; |
| 109 | + } else if (errorMsg.includes('Failed to fetch')) { |
| 110 | + errorMsg = 'Network error. Try another proxy from the dropdown.'; |
| 111 | + } else if (errorMsg.includes('429') || errorMsg.includes('rate')) { |
| 112 | + errorMsg = 'Rate limit exceeded (thebugging proxy). Switch to Cloudflare or x2u.'; |
| 113 | + } |
| 114 | + outputDiv.innerHTML = `<span class="error">❌ ${escapeHtml(errorMsg)}</span>`; |
66 | 115 | } finally { |
67 | 116 | generateBtn.disabled = false; |
68 | 117 | generateBtn.textContent = 'Generate'; |
69 | 118 | } |
70 | 119 | } |
71 | 120 |
|
72 | 121 | function escapeHtml(str) { |
73 | | - return str.replace(/[&<>]/g, function(m) { |
74 | | - if (m === '&') return '&'; |
75 | | - if (m === '<') return '<'; |
76 | | - if (m === '>') return '>'; |
77 | | - return m; |
78 | | - }); |
| 122 | + const div = document.createElement('div'); |
| 123 | + div.textContent = str; |
| 124 | + return div.innerHTML; |
79 | 125 | } |
80 | 126 |
|
81 | 127 | generateBtn.addEventListener('click', fetchFromPerchance); |
|
0 commit comments