/** * WikPrompt (wikiprompt.org) scraper — via Supabase REST API publik. * * Database: 76.7k+ AI prompts. API anon (public, tanpa login) ditemukan dari * JS bundle situs: `eywvuycenrpctxonouem.supabase.co` + anon key. * * Endpoint: * - Cari : POST-ish GET /rest/v1/rpc/search_prompts?q=&lim=&off=&sort_by=&cat=&min_quality= * - Detail: GET /rest/v1/prompts?id=eq. * - Acak : GET /rest/v1/prompts?offset=&order=created_at.desc * * TEST LANGSUNG (tanpa dependency apa pun): * node wikiprompt.js anime * node wikiprompt.js top * node wikiprompt.js random * node wikiprompt.js detail */ const SUPABASE_URL = process.env.WIKIPROMPT_URL || 'https://eywvuycenrpctxonouem.supabase.co' const ANON_KEY = process.env.WIKIPROMPT_KEY || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImV5d3Z1eWNlbnJwY3R4b25vdWVtIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjkwMzc3NTQsImV4cCI6MjA4NDYxMzc1NH0.wcsALXCOLJTXMGsYKLGsCvy4f2jekyLNiweUqD7zxcw' const BASE_HEADERS = { apikey: ANON_KEY, Authorization: `Bearer ${ANON_KEY}`, 'Content-Type': 'application/json', } const LIST_SELECT = 'id,title,description,category,tags,views_count,likes_count,quality_score,is_paid,slug' const FULL_SELECT = 'id,title,description,content,category,tags,views_count,likes_count,quality_score,is_paid,slug,source_link,created_at' async function req(url, options = {}) { const res = await fetch(url, { headers: { ...BASE_HEADERS, ...(options.headers || {}) }, signal: AbortSignal.timeout(options.timeout || 25000), }) if (!res.ok) throw new Error(`Wikiprompt API ${res.status}: ${(await res.text()).slice(0, 120)}`) return res } /** * Cari prompt. `sortBy`: relevance | recent | alphabetical | category | views | rating. */ export async function searchPrompts({ q = '', limit = 7, offset = 0, sortBy = 'relevance', category, minQuality, select = LIST_SELECT } = {}) { const params = new URLSearchParams({ q: String(q).trim().slice(0, 200), lim: String(limit), off: String(offset), sort_by: sortBy, select, }) if (category) params.set('cat', category) if (minQuality != null) params.set('min_quality', String(minQuality)) const res = await req(`${SUPABASE_URL}/rest/v1/rpc/search_prompts?${params.toString()}`) const data = await res.json() return Array.isArray(data) ? data : [] } /** * Detail prompt by UUID. */ export async function getPromptById(id) { if (!id) throw new Error('ID prompt kosong.') const res = await req(`${SUPABASE_URL}/rest/v1/prompts?id=eq.${encodeURIComponent(id)}&select=${FULL_SELECT}&limit=1`) const data = await res.json() return Array.isArray(data) && data.length ? data[0] : null } /** * Ambil prompt acak (offset random di tabel). */ export async function randomPrompt() { const countRes = await req(`${SUPABASE_URL}/rest/v1/prompts?select=id&limit=1`, { headers: { Prefer: 'count=exact' } }) const range = countRes.headers.get('content-range') || '' const total = Number(range.split('/')[1]) || 10000 const off = Math.floor(Math.random() * Math.max(total - 1, 0)) const res = await req(`${SUPABASE_URL}/rest/v1/prompts?select=${FULL_SELECT}&limit=1&order=created_at.desc&offset=${off}`) const data = await res.json() return Array.isArray(data) && data.length ? data[0] : null } /** * Total jumlah prompt di database. */ export async function countPrompts() { const countRes = await req(`${SUPABASE_URL}/rest/v1/prompts?select=id&limit=1`, { headers: { Prefer: 'count=exact' } }) const range = countRes.headers.get('content-range') || '' return Number(range.split('/')[1]) || 0 } // ===== SELF-TEST: node wikiprompt.js [query|top|random|detail ] ===== const isDirectRun = process.argv[1] && import.meta.url === new URL(`file://${process.argv[1]}`).href if (isDirectRun) { ;(async () => { const args = process.argv.slice(2) try { const total = await countPrompts() console.log(`== WikPrompt scraper ==`) console.log(`db : ${total.toLocaleString('id-ID')} prompts\n`) if (args[0] === 'random') { const p = await randomPrompt() console.log(`mode : random`) console.log(`title : ${p.title}`) console.log(`cat : ${p.category} | quality: ${p.quality_score} | views: ${p.views_count}`) console.log(`content: ${String(p.content || '').length} chars\n`) console.log(String(p.content || '').slice(0, 500)) } else if (args[0] === 'detail' && args[1]) { const p = await getPromptById(args[1]) if (!p) return console.log('ERROR: prompt tidak ditemukan.') console.log(`mode : detail`) console.log(`title : ${p.title}`) console.log(`desc : ${p.description}`) console.log(`content: ${String(p.content || '').length} chars`) } else if (args[0] === 'top') { const list = await searchPrompts({ sortBy: 'views', limit: 5 }) console.log('mode : top views') list.forEach((p, i) => console.log(`${i + 1}. ${p.title} | ${p.category} | views: ${p.views_count} | quality: ${p.quality_score}`)) } else { const q = args.join(' ').trim() || 'anime' const list = await searchPrompts({ q, limit: 5 }) console.log(`mode : search "${q}"`) console.log(`found : ${list.length}\n`) list.forEach((p, i) => console.log(`${i + 1}. ${p.title} | ${p.category} | quality: ${p.quality_score} | views: ${p.views_count}`)) } console.log('\ndone') } catch (e) { console.error(`ERROR: ${e.message}`) process.exit(1) } })() }