Back to Home
Gistify Snippet
Public
wikiprompt.js
Imported from GitHub Gist: wikiprompt.js
#github-gist#javascript#scraper#gist:4eb16a1164d5a80ef404e7fb21e8ffb0
7
Rawwikiprompt.jsjavascript
135 lines| 1 | /** |
| 2 | * WikPrompt (wikiprompt.org) scraper — via Supabase REST API publik. |
| 3 | * |
| 4 | * Database: 76.7k+ AI prompts. API anon (public, tanpa login) ditemukan dari |
| 5 | * JS bundle situs: `eywvuycenrpctxonouem.supabase.co` + anon key. |
| 6 | * |
| 7 | * Endpoint: |
| 8 | * - Cari : POST-ish GET /rest/v1/rpc/search_prompts?q=&lim=&off=&sort_by=&cat=&min_quality= |
| 9 | * - Detail: GET /rest/v1/prompts?id=eq.<uuid> |
| 10 | * - Acak : GET /rest/v1/prompts?offset=<random>&order=created_at.desc |
| 11 | * |
| 12 | * TEST LANGSUNG (tanpa dependency apa pun): |
| 13 | * node wikiprompt.js anime |
| 14 | * node wikiprompt.js top |
| 15 | * node wikiprompt.js random |
| 16 | * node wikiprompt.js detail <uuid> |
| 17 | */ |
| 18 | |
| 19 | const SUPABASE_URL = process.env.WIKIPROMPT_URL || 'https://eywvuycenrpctxonouem.supabase.co' |
| 20 | const ANON_KEY = process.env.WIKIPROMPT_KEY || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImV5d3Z1eWNlbnJwY3R4b25vdWVtIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjkwMzc3NTQsImV4cCI6MjA4NDYxMzc1NH0.wcsALXCOLJTXMGsYKLGsCvy4f2jekyLNiweUqD7zxcw' |
| 21 | |
| 22 | const BASE_HEADERS = { |
| 23 | apikey: ANON_KEY, |
| 24 | Authorization: `Bearer ${ANON_KEY}`, |
| 25 | 'Content-Type': 'application/json', |
| 26 | } |
| 27 | |
| 28 | const LIST_SELECT = 'id,title,description,category,tags,views_count,likes_count,quality_score,is_paid,slug' |
| 29 | const FULL_SELECT = 'id,title,description,content,category,tags,views_count,likes_count,quality_score,is_paid,slug,source_link,created_at' |
| 30 | |
| 31 | async function req(url, options = {}) { |
| 32 | const res = await fetch(url, { |
| 33 | headers: { ...BASE_HEADERS, ...(options.headers || {}) }, |
| 34 | signal: AbortSignal.timeout(options.timeout || 25000), |
| 35 | }) |
| 36 | if (!res.ok) throw new Error(`Wikiprompt API ${res.status}: ${(await res.text()).slice(0, 120)}`) |
| 37 | return res |
| 38 | } |
| 39 | |
| 40 | /** |
| 41 | * Cari prompt. `sortBy`: relevance | recent | alphabetical | category | views | rating. |
| 42 | */ |
| 43 | export async function searchPrompts({ q = '', limit = 7, offset = 0, sortBy = 'relevance', category, minQuality, select = LIST_SELECT } = {}) { |
| 44 | const params = new URLSearchParams({ |
| 45 | q: String(q).trim().slice(0, 200), |
| 46 | lim: String(limit), |
| 47 | off: String(offset), |
| 48 | sort_by: sortBy, |
| 49 | select, |
| 50 | }) |
| 51 | if (category) params.set('cat', category) |
| 52 | if (minQuality != null) params.set('min_quality', String(minQuality)) |
| 53 | |
| 54 | const res = await req(`${SUPABASE_URL}/rest/v1/rpc/search_prompts?${params.toString()}`) |
| 55 | const data = await res.json() |
| 56 | return Array.isArray(data) ? data : [] |
| 57 | } |
| 58 | |
| 59 | /** |
| 60 | * Detail prompt by UUID. |
| 61 | */ |
| 62 | export async function getPromptById(id) { |
| 63 | if (!id) throw new Error('ID prompt kosong.') |
| 64 | const res = await req(`${SUPABASE_URL}/rest/v1/prompts?id=eq.${encodeURIComponent(id)}&select=${FULL_SELECT}&limit=1`) |
| 65 | const data = await res.json() |
| 66 | return Array.isArray(data) && data.length ? data[0] : null |
| 67 | } |
| 68 | |
| 69 | /** |
| 70 | * Ambil prompt acak (offset random di tabel). |
| 71 | */ |
| 72 | export async function randomPrompt() { |
| 73 | const countRes = await req(`${SUPABASE_URL}/rest/v1/prompts?select=id&limit=1`, { headers: { Prefer: 'count=exact' } }) |
| 74 | const range = countRes.headers.get('content-range') || '' |
| 75 | const total = Number(range.split('/')[1]) || 10000 |
| 76 | const off = Math.floor(Math.random() * Math.max(total - 1, 0)) |
| 77 | |
| 78 | const res = await req(`${SUPABASE_URL}/rest/v1/prompts?select=${FULL_SELECT}&limit=1&order=created_at.desc&offset=${off}`) |
| 79 | const data = await res.json() |
| 80 | return Array.isArray(data) && data.length ? data[0] : null |
| 81 | } |
| 82 | |
| 83 | /** |
| 84 | * Total jumlah prompt di database. |
| 85 | */ |
| 86 | export async function countPrompts() { |
| 87 | const countRes = await req(`${SUPABASE_URL}/rest/v1/prompts?select=id&limit=1`, { headers: { Prefer: 'count=exact' } }) |
| 88 | const range = countRes.headers.get('content-range') || '' |
| 89 | return Number(range.split('/')[1]) || 0 |
| 90 | } |
| 91 | |
| 92 | // ===== SELF-TEST: node wikiprompt.js [query|top|random|detail <uuid>] ===== |
| 93 | const isDirectRun = process.argv[1] && import.meta.url === new URL(`file://${process.argv[1]}`).href |
| 94 | |
| 95 | if (isDirectRun) { |
| 96 | ;(async () => { |
| 97 | const args = process.argv.slice(2) |
| 98 | try { |
| 99 | const total = await countPrompts() |
| 100 | console.log(`== WikPrompt scraper ==`) |
| 101 | console.log(`db : ${total.toLocaleString('id-ID')} prompts\n`) |
| 102 | |
| 103 | if (args[0] === 'random') { |
| 104 | const p = await randomPrompt() |
| 105 | console.log(`mode : random`) |
| 106 | console.log(`title : ${p.title}`) |
| 107 | console.log(`cat : ${p.category} | quality: ${p.quality_score} | views: ${p.views_count}`) |
| 108 | console.log(`content: ${String(p.content || '').length} chars\n`) |
| 109 | console.log(String(p.content || '').slice(0, 500)) |
| 110 | } else if (args[0] === 'detail' && args[1]) { |
| 111 | const p = await getPromptById(args[1]) |
| 112 | if (!p) return console.log('ERROR: prompt tidak ditemukan.') |
| 113 | console.log(`mode : detail`) |
| 114 | console.log(`title : ${p.title}`) |
| 115 | console.log(`desc : ${p.description}`) |
| 116 | console.log(`content: ${String(p.content || '').length} chars`) |
| 117 | } else if (args[0] === 'top') { |
| 118 | const list = await searchPrompts({ sortBy: 'views', limit: 5 }) |
| 119 | console.log('mode : top views') |
| 120 | list.forEach((p, i) => console.log(`${i + 1}. ${p.title} | ${p.category} | views: ${p.views_count} | quality: ${p.quality_score}`)) |
| 121 | } else { |
| 122 | const q = args.join(' ').trim() || 'anime' |
| 123 | const list = await searchPrompts({ q, limit: 5 }) |
| 124 | console.log(`mode : search "${q}"`) |
| 125 | console.log(`found : ${list.length}\n`) |
| 126 | list.forEach((p, i) => console.log(`${i + 1}. ${p.title} | ${p.category} | quality: ${p.quality_score} | views: ${p.views_count}`)) |
| 127 | } |
| 128 | console.log('\ndone') |
| 129 | } catch (e) { |
| 130 | console.error(`ERROR: ${e.message}`) |
| 131 | process.exit(1) |
| 132 | } |
| 133 | })() |
| 134 | } |
| 135 | |
Discussion & Comments0
No comments yet — be the first to leave feedback or start a code discussion.
Sign in with your GitHub account to write comments and join the discussion.