Back to Home
Gistify Snippet
kyiov
Sep 20·1 file(s)
Public

wikiprompt.js

Imported from GitHub Gist: wikiprompt.js

#github-gist#javascript#scraper#gist:4eb16a1164d5a80ef404e7fb21e8ffb0
7
Raw
wikiprompt.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
19const SUPABASE_URL = process.env.WIKIPROMPT_URL || 'https://eywvuycenrpctxonouem.supabase.co'
20const ANON_KEY = process.env.WIKIPROMPT_KEY || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImV5d3Z1eWNlbnJwY3R4b25vdWVtIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjkwMzc3NTQsImV4cCI6MjA4NDYxMzc1NH0.wcsALXCOLJTXMGsYKLGsCvy4f2jekyLNiweUqD7zxcw'
21
22const BASE_HEADERS = {
23 apikey: ANON_KEY,
24 Authorization: `Bearer ${ANON_KEY}`,
25 'Content-Type': 'application/json',
26}
27
28const LIST_SELECT = 'id,title,description,category,tags,views_count,likes_count,quality_score,is_paid,slug'
29const FULL_SELECT = 'id,title,description,content,category,tags,views_count,likes_count,quality_score,is_paid,slug,source_link,created_at'
30
31async 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 */
43export 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 */
62export 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 */
72export 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 */
86export 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>] =====
93const isDirectRun = process.argv[1] && import.meta.url === new URL(`file://${process.argv[1]}`).href
94
95if (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.