{"repo": "0x70hu-sh/webblocker", "files": {"guardai-extension/aiClient.js": {"4def0b1f853c851a6015cc6ac1fcd8f8f58d3538": {"t": 1785746001.5618038, "n": 13360, "h": "214fdd2286fbfe9c", "c": "// aiClient.js\n// AI Abstraction Layer — รองรับหลาย Provider สลับได้ใน Settings\n\nconst SYSTEM_PROMPT = `You are GuardAI — an extremely aggressive web content safety classifier for Thailand.\nBlock hard. Block first. When in doubt → BLOCK. False positive >>> false negative.\n\n═══════════════════════════════════\n🔞 ADULT DETECTION — BLOCK AGGRESSIVELY\n═══════════════════════════════════\nENGLISH:\n- porn, xxx, sex, nude, hentai, NSFW, adult, erotic, hardcore, blowjob, milf\n- onlyfans, cam, webcam + sexual context\n- BLOCK DOMAINS: xnxx, pornhub, xvideos, redtube, brazzers, onlyfans, chaturbate, livejasmin, myfreecams, youporn, xhamster, spankbang, tnaflix\n\nTHAI — อวัยวะเพศ (BLOCK IMMEDIATELY):\n- หี, ควย, เย็ด, แตด, จิ๋ม, จิมิ, หอย, จู๋, กระเจี๊ยว, เจี๊ยว, หำ, กระจู๋\n- จ้ำบ๊ะ, โมะ, แคม, คลิตอริส, ดอก, ปี๊, ไข่, นม, หน้าอก, เต้า, หัวนม\n- ก้น, ตูด, รูตูด, รูก้น\n\nTHAI — การกระทำทางเพศ:\n- เสียว, เงี่ยน, เงี่ยนหี, เงี่ยนควย, อมควย, อมจู๋, อมหี, เลียหี, เลียไข่\n- ชักว่าว, ช่วยตัวเอง, สำเร็จความใคร่, แตกใน, แตกนอก, แตกปาก, เสียบ, กระเด้า, เด้า, ฟัน\n- นวดกระปู๋, นวดเงี่ยน, น้ำแตก, น้ำพุ่ง, สวิงกิ้ง, สามเส้า, สี่เส้า, หมู่, สลับคู่\n- ท่าสุนัข, ท่าหมา, 69\n\nTHAI — สื่อลามก:\n- โป๊, หนังโป๊, หนังx, คลิปx, คลิปโป๊, คลิปหลุด, คลิปเสียว\n- หลุดดารา, หลุดนักศึกษา, หลุดvk, หลุดonlyfans, หลุด, เด็กโป๊\n- หนังav, jav, avซับไทย, uncen, หนังอาร์, โดจิน, doujin, มังงะโป๊, การ์ตูนโป๊\n- โป๊เด็ด, หีเด็ด, ควยสวย, คลิปเย็ด, หนังเย็ด\n\nTHAI — ขายบริการ / นัดพบ:\n- ขายตัว, ขายบริการ, นัดเย็ด, นัดฟัน, คู่ขา, หาคู่\n- ไซด์ไลน์, สาวไซด์ไลน์, เด็กชง, เด็กเสริฟ, พริตตี้\n- นวดน้ำมัน, อาบอบนวด, สปา, รับงาน, งานนอกสถานที่, ขายคลิป, ขายรูป, ขายหี\n\nTHAI — ไลฟ์สด / เปลือย:\n- ไลฟ์สด, liveสด, ไลฟ์สดโป๊, เปลือย, แก้ผ้า, โชว์หี, โชว์นม, โชว์เสียว\n- camfrog, bigo, stripchat, bongacams, โชว์สด\n\nTHAI — อื่นๆ:\n- โรคจิต, หื่น, ลามก, อนาจาร, ลวนลาม, ข่มขืน, น้องแนท, เยาวชน\n\n═══════════════════════════════════\n🎰 GAMBLING DETECTION — BLOCK EVERYTHING\n═══════════════════════════════════\nINTERNATIONAL BRANDS (block instantly):\n- casino, bet365, betway, betfair, draftkings, fanduel, williamhill, paddypower, unibet, bwin, 888casino, leovegas, pokerstars\n\nTHAI CASINO BRANDS (block instantly):\n- ufabet, ufa, gclub, sbobet, sagame, goldenslot, ts911\n- betflix, betflik, pgslot, joker, slotxo, ambbet, live22\n- allbet, maxbet, ibcbet, m88, w88, dafabet, fun88, 12bet, 188bet\n- lmgbet, kingkongxo, slot123, auto123, wallet123, royalslot, slotwallet\n- pussy888, 918kiss, mega888, xe88, ace333, playtech, habanero\n- pragmatik, pragmatic, pgsoft, netent, microgaming\n- baccarat (เกมคาสิโน — แต่ถ้าเป็นแบรนด์คริสตัล/สินค้าหรู Baccarat ในบริบทช้อปปิ้ง → SAFE)\n\n═══════════════════════════════════\n🎲 THAI LOTTERY (หวย) — BLOCK ALL\n═══════════════════════════════════\nKEYWORDS = BLOCK INSTANTLY:\n- lotto, lottery, huay, ruay, หวย, ล็อตโต้, ล็อตเตอรี่, สลากกินแบ่ง\n- หวยรัฐ, หวยลาว, หวยฮานอย, หวยหุ้น, หวยใต้ดิน, หวยออนไลน์, หวยออน\n- เลขเด็ด, ซื้อหวย, ขายหวย, แทงหวย, ใบหวย, ตรวจหวย\n\nTHAI LOTTERY DOMAIN PATTERNS (BLOCK if domain contains):\n- lotto + numbers (lotto432, lotto888, lotto4d, lotto6d, lotto77, lottovip)\n- huay + numbers (huay999, huay789, huay24, huay88)\n- ruay + numbers (ruay99, ruay888, ruay77)\n- หวย + numbers or tld\n- Any domain with both numbers AND หวย/ล็อต/lotto/huay = GAMBLING\n\n═══════════════════════════════════\n🧩 PATTERN MATCHING — BLOCK IF ANY\n═══════════════════════════════════\nGAMBLING PATTERNS:\n- xxxbet, betxxx, xxxslot, slotxxx, xxxcasino, pgxxx, jokerxxx\n- brand + numbers (ufabet999, betflix789, pgslot123, joker456, lotto432)\n- abbreviations (ufa, pg, joker, slot) + numbers = GAMBLING\n- auto/wallet/deposit/withdraw + slot/bet/casino = GAMBLING\n- live22, live33, live44, live + numbers\n- Any domain ending in ...slot, ...bet, ...casino, ...lotto, ...huay, ...ruay\n\nSUSPICIOUS TLDs (auto-lower threshold to 10%):\n.xyz .top .vip .cc .tk .ml .ga .cf .gq .fun .club .shop .store .online .site .icu .buzz .rest .live .bet .casino .win\n\n═══════════════════════════════════\n⚡ RULES — FOLLOW STRICTLY\n═══════════════════════════════════\n- Be EXTREMELY aggressive. When in doubt → ALWAYS block.\n- Analyze: domain name, TLD, subdomains, path, query string.\n- Thai gambling = brand + numbers (ufabet999, lotto432, pgslot123) → BLOCK\n- Abbreviations (ufa, pg, joker, slot, lotto) + numbers → BLOCK\n- slot/bet/casino/lotto/huay anywhere in domain → 90%+ confidence → BLOCK\n- SUSPICIOUS TLD + any gambling keyword → BLOCK instantly (confidence 95)\n- TWO OR MORE suspicious indicators → BLOCK\n- Confidence > 25% for harmful → block (MEGA aggressive)\n- False positive is MUCH better than false negative\n- NEVER block these safe domains: x.com, twitter.com, tesla.com, spacex.com, openai.com, github.com, youtube.com, google.com, microsoft.com, apple.com, amazon.com, wikipedia.org, reddit.com\n- Do NOT block \"sex\" as part of larger word (spacex, sussex, essex, sextant)\n- NEVER explain reasoning in prose → JSON only. NEVER output markdown. NEVER output code fences.\n\n═══════════════════════════════════\n📄 CONTENT ANALYSIS MODE (when PAGE TEXT is provided)\n═══════════════════════════════════\n- You are analyzing ACTUAL webpage text content, not just a domain name.\n- Focus 80% on CONTENT, 20% on domain name.\n- Scan for: sexual descriptions, adult service offers, gambling invitations, lottery sales, explicit anatomy references in sexual context.\n- Innocent words in non-sexual context → SAFE (e.g. medical article about breast cancer = SAFE).\n- News article mentioning gambling arrests → SAFE (context is negative/news).\n- Educational content about sex education → SAFE.\n- Any page directly offering porn, gambling, lottery, prostitution, or explicit sexual content → BLOCK.\n- Thai forums/discussions with casual sexual slang → BLOCK.\n- Any page whose PRIMARY purpose is adult/gambling → BLOCK.\n- When text is empty or too short → rely on domain name only.\n\nOUTPUT: Valid JSON only.\n{\n \"blocked\": boolean,\n \"category\": \"gambling\" | \"adult\" | \"safe\",\n \"confidence\": 0-100,\n \"reason\": \"Short concise English explanation (under 15 words)\"\n}\nCRITICAL: Output all fields including \"reason\" strictly in ENGLISH. Never output Thai characters.`;\n\n// ─── Content-based scan: sends actual PAGE TEXT to AI ──────────\nexport async function checkContentWithAI(url, pageText, apiKey, provider = \"deepseek\") {\n const domain = extractDomain(url);\n const userMsg = `URL: ${domain}\\n\\nPAGE TEXT (first 2500 chars):\\n${pageText.substring(0, 2500)}`;\n\n switch (provider) {\n case \"claude\":\n return await callClaude(userMsg, apiKey);\n case \"deepseek\":\n return await callDeepSeek(userMsg, apiKey);\n case \"openai\":\n return await callOpenAI(userMsg, apiKey);\n default:\n return await callDeepSeek(userMsg, apiKey);\n }\n}\n\n// ─── Domain-only scan: original behavior ────────────────────────\nexport async function checkWithAI(url, apiKey, provider = \"deepseek\") {\n const domain = extractDomain(url);\n const userMsg = `Analyze this URL: ${domain}`;\n\n switch (provider) {\n case \"claude\":\n return await callClaude(userMsg, apiKey);\n case \"deepseek\":\n return await callDeepSeek(userMsg, apiKey);\n case \"openai\":\n return await callOpenAI(userMsg, apiKey);\n default:\n return await callDeepSeek(userMsg, apiKey);\n }\n}\n\nasync function callClaude(msg, apiKey) {\n const res = await fetch(\"https://api.anthropic.com/v1/messages\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": apiKey,\n \"anthropic-version\": \"2023-06-01\"\n },\n body: JSON.stringify({\n model: \"claude-3-5-haiku-20241022\",\n max_tokens: 500,\n system: SYSTEM_PROMPT,\n messages: [{ role: \"user\", content: msg }]\n })\n });\n\n if (!res.ok) {\n throw new Error(`Claude API error: ${res.status} ${res.statusText}`);\n }\n\n const data = await res.json();\n const content = data?.content?.[0]?.text || \"\";\n return parseAIResponse(content, msg);\n}\n\nasync function callDeepSeek(msg, apiKey) {\n // Try deepseek-chat (standard DeepSeek API model)\n let res = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`\n },\n body: JSON.stringify({\n model: \"deepseek-chat\",\n max_tokens: 500,\n messages: [\n { role: \"system\", content: SYSTEM_PROMPT },\n { role: \"user\", content: msg }\n ]\n })\n });\n\n if (!res.ok) {\n // Retry with deepseek-v4-pro if deepseek-chat returned error\n res = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`\n },\n body: JSON.stringify({\n model: \"deepseek-v4-pro\",\n max_tokens: 500,\n messages: [\n { role: \"system\", content: SYSTEM_PROMPT },\n { role: \"user\", content: msg }\n ]\n })\n });\n }\n\n if (!res.ok) {\n throw new Error(`DeepSeek API error: ${res.status} ${res.statusText}`);\n }\n\n const data = await res.json();\n const content = data?.choices?.[0]?.message?.content || \"\";\n return parseAIResponse(content, msg);\n}\n\nasync function callOpenAI(msg, apiKey) {\n const res = await fetch(\"https://api.openai.com/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`\n },\n body: JSON.stringify({\n model: \"gpt-4o-mini\",\n max_tokens: 500,\n messages: [\n { role: \"system\", content: SYSTEM_PROMPT },\n { role: \"user\", content: msg }\n ]\n })\n });\n\n if (!res.ok) {\n throw new Error(`OpenAI API error: ${res.status} ${res.statusText}`);\n }\n\n const data = await res.json();\n const content = data?.choices?.[0]?.message?.content || \"\";\n return parseAIResponse(content, msg);\n}\n\n// confidence ขั้นต่ำที่จะยอมบล็อก (สอดคล้องกับ prompt: harmful > 25% → block)\nconst AI_BLOCK_THRESHOLD = 25;\n\nfunction parseAIResponse(text, rawUserMsg = \"\") {\n // If response is empty or null, perform heuristic keyword inspection on rawUserMsg\n if (!text || typeof text !== \"string\" || !text.trim()) {\n const lowerMsg = (rawUserMsg || \"\").toLowerCase();\n const isGambling = lowerMsg.includes(\"gambling\") || lowerMsg.includes(\"พนัน\") || lowerMsg.includes(\"คาสิโน\")\n || lowerMsg.includes(\"สล็อต\") || lowerMsg.includes(\"slot\") || lowerMsg.includes(\"bet\") || lowerMsg.includes(\"ufa\")\n || lowerMsg.includes(\"tako\") || lowerMsg.includes(\"เครดิตฟรี\") || lowerMsg.includes(\"ฝากถอน\") || lowerMsg.includes(\"บาคาร่า\");\n const isAdult = lowerMsg.includes(\"adult\") || lowerMsg.includes(\"porn\") || lowerMsg.includes(\"โป๊\") || lowerMsg.includes(\"18+\") || lowerMsg.includes(\"xxx\") || lowerMsg.includes(\"เย็ด\") || lowerMsg.includes(\"หี\") || lowerMsg.includes(\"ควย\");\n\n if (isGambling || isAdult) {\n return {\n blocked: true,\n category: isGambling ? \"gambling\" : \"adult\",\n confidence: 90,\n reason: \"Detected forbidden keywords (Heuristic Fallback)\",\n source: \"heuristic\"\n };\n }\n\n return {\n blocked: false,\n category: \"safe\",\n confidence: 0,\n reason: \"Empty response from AI (Passed Heuristic Check)\",\n source: \"ai\"\n };\n }\n\n try {\n // 1. Extract first JSON object { ... } via Regex\n const jsonMatch = text.match(/\\{[\\s\\S]*?\\}/);\n const jsonStr = jsonMatch ? jsonMatch[0] : text;\n const parsed = JSON.parse(jsonStr);\n\n let confidence = Number(parsed.confidence);\n if (!Number.isFinite(confidence)) confidence = parsed.blocked ? 100 : 0;\n confidence = Math.max(0, Math.min(100, confidence));\n\n const category = (parsed.category === \"adult\" || parsed.category === \"gambling\")\n ? parsed.category : \"safe\";\n const blocked = (parsed.blocked === true || String(parsed.blocked).toLowerCase() === \"true\")\n && category !== \"safe\"\n && confidence >= AI_BLOCK_THRESHOLD;\n\n return {\n blocked,\n category: blocked ? category : \"safe\",\n confidence,\n reason: parsed.reason || \"Evaluated by AI\",\n source: \"ai\"\n };\n } catch (err) {\n // 2. Fallback: Scan raw response text & message for block keywords if JSON parse failed\n const lowerText = (text + \" \" + rawUserMsg).toLowerCase();\n const isGambling = lowerText.includes(\"gambling\") || lowerText.includes(\"พนัน\") || lowerText.includes(\"คาสิโน\") || lowerText.includes(\"สล็อต\") || lowerText.includes(\"tako\") || lowerText.includes(\"ufa\") || lowerText.includes(\"bet\");\n const isAdult = lowerText.includes(\"adult\") || lowerText.includes(\"porn\") || lowerText.includes(\"โป๊\") || lowerText.includes(\"18+\");\n const isBlocked = lowerText.includes('\"blocked\": true') || lowerText.includes('\"blocked\":true') || lowerText.includes(\"blocked\");\n\n if ((isGambling || isAdult) && isBlocked) {\n return {\n blocked: true,\n category: isGambling ? \"gambling\" : \"adult\",\n confidence: 90,\n reason: \"AI detected harmful content\",\n source: \"ai\"\n };\n }\n\n return {\n blocked: false,\n category: \"safe\",\n confidence: 0,\n reason: \"No block instruction in AI response\",\n source: \"ai\"\n };\n }\n}\n\nfunction extractDomain(url) {\n try {\n return new URL(url.startsWith(\"http\") ? url : `https://${url}`).hostname;\n } catch {\n return url;\n }\n}\n"}, "3ed9b0b4903a349caa33782ec06e7c9c8f6d0a54": {"t": 1785758692.769646, "n": 13360, "h": "214fdd2286fbfe9c", "c": "// aiClient.js\n// AI Abstraction Layer — รองรับหลาย Provider สลับได้ใน Settings\n\nconst SYSTEM_PROMPT = `You are GuardAI — an extremely aggressive web content safety classifier for Thailand.\nBlock hard. Block first. When in doubt → BLOCK. False positive >>> false negative.\n\n═══════════════════════════════════\n🔞 ADULT DETECTION — BLOCK AGGRESSIVELY\n═══════════════════════════════════\nENGLISH:\n- porn, xxx, sex, nude, hentai, NSFW, adult, erotic, hardcore, blowjob, milf\n- onlyfans, cam, webcam + sexual context\n- BLOCK DOMAINS: xnxx, pornhub, xvideos, redtube, brazzers, onlyfans, chaturbate, livejasmin, myfreecams, youporn, xhamster, spankbang, tnaflix\n\nTHAI — อวัยวะเพศ (BLOCK IMMEDIATELY):\n- หี, ควย, เย็ด, แตด, จิ๋ม, จิมิ, หอย, จู๋, กระเจี๊ยว, เจี๊ยว, หำ, กระจู๋\n- จ้ำบ๊ะ, โมะ, แคม, คลิตอริส, ดอก, ปี๊, ไข่, นม, หน้าอก, เต้า, หัวนม\n- ก้น, ตูด, รูตูด, รูก้น\n\nTHAI — การกระทำทางเพศ:\n- เสียว, เงี่ยน, เงี่ยนหี, เงี่ยนควย, อมควย, อมจู๋, อมหี, เลียหี, เลียไข่\n- ชักว่าว, ช่วยตัวเอง, สำเร็จความใคร่, แตกใน, แตกนอก, แตกปาก, เสียบ, กระเด้า, เด้า, ฟัน\n- นวดกระปู๋, นวดเงี่ยน, น้ำแตก, น้ำพุ่ง, สวิงกิ้ง, สามเส้า, สี่เส้า, หมู่, สลับคู่\n- ท่าสุนัข, ท่าหมา, 69\n\nTHAI — สื่อลามก:\n- โป๊, หนังโป๊, หนังx, คลิปx, คลิปโป๊, คลิปหลุด, คลิปเสียว\n- หลุดดารา, หลุดนักศึกษา, หลุดvk, หลุดonlyfans, หลุด, เด็กโป๊\n- หนังav, jav, avซับไทย, uncen, หนังอาร์, โดจิน, doujin, มังงะโป๊, การ์ตูนโป๊\n- โป๊เด็ด, หีเด็ด, ควยสวย, คลิปเย็ด, หนังเย็ด\n\nTHAI — ขายบริการ / นัดพบ:\n- ขายตัว, ขายบริการ, นัดเย็ด, นัดฟัน, คู่ขา, หาคู่\n- ไซด์ไลน์, สาวไซด์ไลน์, เด็กชง, เด็กเสริฟ, พริตตี้\n- นวดน้ำมัน, อาบอบนวด, สปา, รับงาน, งานนอกสถานที่, ขายคลิป, ขายรูป, ขายหี\n\nTHAI — ไลฟ์สด / เปลือย:\n- ไลฟ์สด, liveสด, ไลฟ์สดโป๊, เปลือย, แก้ผ้า, โชว์หี, โชว์นม, โชว์เสียว\n- camfrog, bigo, stripchat, bongacams, โชว์สด\n\nTHAI — อื่นๆ:\n- โรคจิต, หื่น, ลามก, อนาจาร, ลวนลาม, ข่มขืน, น้องแนท, เยาวชน\n\n═══════════════════════════════════\n🎰 GAMBLING DETECTION — BLOCK EVERYTHING\n═══════════════════════════════════\nINTERNATIONAL BRANDS (block instantly):\n- casino, bet365, betway, betfair, draftkings, fanduel, williamhill, paddypower, unibet, bwin, 888casino, leovegas, pokerstars\n\nTHAI CASINO BRANDS (block instantly):\n- ufabet, ufa, gclub, sbobet, sagame, goldenslot, ts911\n- betflix, betflik, pgslot, joker, slotxo, ambbet, live22\n- allbet, maxbet, ibcbet, m88, w88, dafabet, fun88, 12bet, 188bet\n- lmgbet, kingkongxo, slot123, auto123, wallet123, royalslot, slotwallet\n- pussy888, 918kiss, mega888, xe88, ace333, playtech, habanero\n- pragmatik, pragmatic, pgsoft, netent, microgaming\n- baccarat (เกมคาสิโน — แต่ถ้าเป็นแบรนด์คริสตัล/สินค้าหรู Baccarat ในบริบทช้อปปิ้ง → SAFE)\n\n═══════════════════════════════════\n🎲 THAI LOTTERY (หวย) — BLOCK ALL\n═══════════════════════════════════\nKEYWORDS = BLOCK INSTANTLY:\n- lotto, lottery, huay, ruay, หวย, ล็อตโต้, ล็อตเตอรี่, สลากกินแบ่ง\n- หวยรัฐ, หวยลาว, หวยฮานอย, หวยหุ้น, หวยใต้ดิน, หวยออนไลน์, หวยออน\n- เลขเด็ด, ซื้อหวย, ขายหวย, แทงหวย, ใบหวย, ตรวจหวย\n\nTHAI LOTTERY DOMAIN PATTERNS (BLOCK if domain contains):\n- lotto + numbers (lotto432, lotto888, lotto4d, lotto6d, lotto77, lottovip)\n- huay + numbers (huay999, huay789, huay24, huay88)\n- ruay + numbers (ruay99, ruay888, ruay77)\n- หวย + numbers or tld\n- Any domain with both numbers AND หวย/ล็อต/lotto/huay = GAMBLING\n\n═══════════════════════════════════\n🧩 PATTERN MATCHING — BLOCK IF ANY\n═══════════════════════════════════\nGAMBLING PATTERNS:\n- xxxbet, betxxx, xxxslot, slotxxx, xxxcasino, pgxxx, jokerxxx\n- brand + numbers (ufabet999, betflix789, pgslot123, joker456, lotto432)\n- abbreviations (ufa, pg, joker, slot) + numbers = GAMBLING\n- auto/wallet/deposit/withdraw + slot/bet/casino = GAMBLING\n- live22, live33, live44, live + numbers\n- Any domain ending in ...slot, ...bet, ...casino, ...lotto, ...huay, ...ruay\n\nSUSPICIOUS TLDs (auto-lower threshold to 10%):\n.xyz .top .vip .cc .tk .ml .ga .cf .gq .fun .club .shop .store .online .site .icu .buzz .rest .live .bet .casino .win\n\n═══════════════════════════════════\n⚡ RULES — FOLLOW STRICTLY\n═══════════════════════════════════\n- Be EXTREMELY aggressive. When in doubt → ALWAYS block.\n- Analyze: domain name, TLD, subdomains, path, query string.\n- Thai gambling = brand + numbers (ufabet999, lotto432, pgslot123) → BLOCK\n- Abbreviations (ufa, pg, joker, slot, lotto) + numbers → BLOCK\n- slot/bet/casino/lotto/huay anywhere in domain → 90%+ confidence → BLOCK\n- SUSPICIOUS TLD + any gambling keyword → BLOCK instantly (confidence 95)\n- TWO OR MORE suspicious indicators → BLOCK\n- Confidence > 25% for harmful → block (MEGA aggressive)\n- False positive is MUCH better than false negative\n- NEVER block these safe domains: x.com, twitter.com, tesla.com, spacex.com, openai.com, github.com, youtube.com, google.com, microsoft.com, apple.com, amazon.com, wikipedia.org, reddit.com\n- Do NOT block \"sex\" as part of larger word (spacex, sussex, essex, sextant)\n- NEVER explain reasoning in prose → JSON only. NEVER output markdown. NEVER output code fences.\n\n═══════════════════════════════════\n📄 CONTENT ANALYSIS MODE (when PAGE TEXT is provided)\n═══════════════════════════════════\n- You are analyzing ACTUAL webpage text content, not just a domain name.\n- Focus 80% on CONTENT, 20% on domain name.\n- Scan for: sexual descriptions, adult service offers, gambling invitations, lottery sales, explicit anatomy references in sexual context.\n- Innocent words in non-sexual context → SAFE (e.g. medical article about breast cancer = SAFE).\n- News article mentioning gambling arrests → SAFE (context is negative/news).\n- Educational content about sex education → SAFE.\n- Any page directly offering porn, gambling, lottery, prostitution, or explicit sexual content → BLOCK.\n- Thai forums/discussions with casual sexual slang → BLOCK.\n- Any page whose PRIMARY purpose is adult/gambling → BLOCK.\n- When text is empty or too short → rely on domain name only.\n\nOUTPUT: Valid JSON only.\n{\n \"blocked\": boolean,\n \"category\": \"gambling\" | \"adult\" | \"safe\",\n \"confidence\": 0-100,\n \"reason\": \"Short concise English explanation (under 15 words)\"\n}\nCRITICAL: Output all fields including \"reason\" strictly in ENGLISH. Never output Thai characters.`;\n\n// ─── Content-based scan: sends actual PAGE TEXT to AI ──────────\nexport async function checkContentWithAI(url, pageText, apiKey, provider = \"deepseek\") {\n const domain = extractDomain(url);\n const userMsg = `URL: ${domain}\\n\\nPAGE TEXT (first 2500 chars):\\n${pageText.substring(0, 2500)}`;\n\n switch (provider) {\n case \"claude\":\n return await callClaude(userMsg, apiKey);\n case \"deepseek\":\n return await callDeepSeek(userMsg, apiKey);\n case \"openai\":\n return await callOpenAI(userMsg, apiKey);\n default:\n return await callDeepSeek(userMsg, apiKey);\n }\n}\n\n// ─── Domain-only scan: original behavior ────────────────────────\nexport async function checkWithAI(url, apiKey, provider = \"deepseek\") {\n const domain = extractDomain(url);\n const userMsg = `Analyze this URL: ${domain}`;\n\n switch (provider) {\n case \"claude\":\n return await callClaude(userMsg, apiKey);\n case \"deepseek\":\n return await callDeepSeek(userMsg, apiKey);\n case \"openai\":\n return await callOpenAI(userMsg, apiKey);\n default:\n return await callDeepSeek(userMsg, apiKey);\n }\n}\n\nasync function callClaude(msg, apiKey) {\n const res = await fetch(\"https://api.anthropic.com/v1/messages\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": apiKey,\n \"anthropic-version\": \"2023-06-01\"\n },\n body: JSON.stringify({\n model: \"claude-3-5-haiku-20241022\",\n max_tokens: 500,\n system: SYSTEM_PROMPT,\n messages: [{ role: \"user\", content: msg }]\n })\n });\n\n if (!res.ok) {\n throw new Error(`Claude API error: ${res.status} ${res.statusText}`);\n }\n\n const data = await res.json();\n const content = data?.content?.[0]?.text || \"\";\n return parseAIResponse(content, msg);\n}\n\nasync function callDeepSeek(msg, apiKey) {\n // Try deepseek-chat (standard DeepSeek API model)\n let res = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`\n },\n body: JSON.stringify({\n model: \"deepseek-chat\",\n max_tokens: 500,\n messages: [\n { role: \"system\", content: SYSTEM_PROMPT },\n { role: \"user\", content: msg }\n ]\n })\n });\n\n if (!res.ok) {\n // Retry with deepseek-v4-pro if deepseek-chat returned error\n res = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`\n },\n body: JSON.stringify({\n model: \"deepseek-v4-pro\",\n max_tokens: 500,\n messages: [\n { role: \"system\", content: SYSTEM_PROMPT },\n { role: \"user\", content: msg }\n ]\n })\n });\n }\n\n if (!res.ok) {\n throw new Error(`DeepSeek API error: ${res.status} ${res.statusText}`);\n }\n\n const data = await res.json();\n const content = data?.choices?.[0]?.message?.content || \"\";\n return parseAIResponse(content, msg);\n}\n\nasync function callOpenAI(msg, apiKey) {\n const res = await fetch(\"https://api.openai.com/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`\n },\n body: JSON.stringify({\n model: \"gpt-4o-mini\",\n max_tokens: 500,\n messages: [\n { role: \"system\", content: SYSTEM_PROMPT },\n { role: \"user\", content: msg }\n ]\n })\n });\n\n if (!res.ok) {\n throw new Error(`OpenAI API error: ${res.status} ${res.statusText}`);\n }\n\n const data = await res.json();\n const content = data?.choices?.[0]?.message?.content || \"\";\n return parseAIResponse(content, msg);\n}\n\n// confidence ขั้นต่ำที่จะยอมบล็อก (สอดคล้องกับ prompt: harmful > 25% → block)\nconst AI_BLOCK_THRESHOLD = 25;\n\nfunction parseAIResponse(text, rawUserMsg = \"\") {\n // If response is empty or null, perform heuristic keyword inspection on rawUserMsg\n if (!text || typeof text !== \"string\" || !text.trim()) {\n const lowerMsg = (rawUserMsg || \"\").toLowerCase();\n const isGambling = lowerMsg.includes(\"gambling\") || lowerMsg.includes(\"พนัน\") || lowerMsg.includes(\"คาสิโน\")\n || lowerMsg.includes(\"สล็อต\") || lowerMsg.includes(\"slot\") || lowerMsg.includes(\"bet\") || lowerMsg.includes(\"ufa\")\n || lowerMsg.includes(\"tako\") || lowerMsg.includes(\"เครดิตฟรี\") || lowerMsg.includes(\"ฝากถอน\") || lowerMsg.includes(\"บาคาร่า\");\n const isAdult = lowerMsg.includes(\"adult\") || lowerMsg.includes(\"porn\") || lowerMsg.includes(\"โป๊\") || lowerMsg.includes(\"18+\") || lowerMsg.includes(\"xxx\") || lowerMsg.includes(\"เย็ด\") || lowerMsg.includes(\"หี\") || lowerMsg.includes(\"ควย\");\n\n if (isGambling || isAdult) {\n return {\n blocked: true,\n category: isGambling ? \"gambling\" : \"adult\",\n confidence: 90,\n reason: \"Detected forbidden keywords (Heuristic Fallback)\",\n source: \"heuristic\"\n };\n }\n\n return {\n blocked: false,\n category: \"safe\",\n confidence: 0,\n reason: \"Empty response from AI (Passed Heuristic Check)\",\n source: \"ai\"\n };\n }\n\n try {\n // 1. Extract first JSON object { ... } via Regex\n const jsonMatch = text.match(/\\{[\\s\\S]*?\\}/);\n const jsonStr = jsonMatch ? jsonMatch[0] : text;\n const parsed = JSON.parse(jsonStr);\n\n let confidence = Number(parsed.confidence);\n if (!Number.isFinite(confidence)) confidence = parsed.blocked ? 100 : 0;\n confidence = Math.max(0, Math.min(100, confidence));\n\n const category = (parsed.category === \"adult\" || parsed.category === \"gambling\")\n ? parsed.category : \"safe\";\n const blocked = (parsed.blocked === true || String(parsed.blocked).toLowerCase() === \"true\")\n && category !== \"safe\"\n && confidence >= AI_BLOCK_THRESHOLD;\n\n return {\n blocked,\n category: blocked ? category : \"safe\",\n confidence,\n reason: parsed.reason || \"Evaluated by AI\",\n source: \"ai\"\n };\n } catch (err) {\n // 2. Fallback: Scan raw response text & message for block keywords if JSON parse failed\n const lowerText = (text + \" \" + rawUserMsg).toLowerCase();\n const isGambling = lowerText.includes(\"gambling\") || lowerText.includes(\"พนัน\") || lowerText.includes(\"คาสิโน\") || lowerText.includes(\"สล็อต\") || lowerText.includes(\"tako\") || lowerText.includes(\"ufa\") || lowerText.includes(\"bet\");\n const isAdult = lowerText.includes(\"adult\") || lowerText.includes(\"porn\") || lowerText.includes(\"โป๊\") || lowerText.includes(\"18+\");\n const isBlocked = lowerText.includes('\"blocked\": true') || lowerText.includes('\"blocked\":true') || lowerText.includes(\"blocked\");\n\n if ((isGambling || isAdult) && isBlocked) {\n return {\n blocked: true,\n category: isGambling ? \"gambling\" : \"adult\",\n confidence: 90,\n reason: \"AI detected harmful content\",\n source: \"ai\"\n };\n }\n\n return {\n blocked: false,\n category: \"safe\",\n confidence: 0,\n reason: \"No block instruction in AI response\",\n source: \"ai\"\n };\n }\n}\n\nfunction extractDomain(url) {\n try {\n return new URL(url.startsWith(\"http\") ? url : `https://${url}`).hostname;\n } catch {\n return url;\n }\n}\n"}, "efb3f2231b57aa135af0c340ab68b1b4fc7f1238": {"t": 1785758694.8137581, "n": 13351, "h": "8a81f1d6a42f8ba2", "c": "// aiClient.js\n// AI Abstraction Layer — รองรับหลาย Provider สลับได้ใน Settings\n\nconst SYSTEM_PROMPT = `You are GuardAI — an extremely aggressive web content safety classifier for Thailand.\nBlock hard. Block first. When in doubt → BLOCK. False positive >>> false negative.\n\n═══════════════════════════════════\n🔞 ADULT DETECTION — BLOCK AGGRESSIVELY\n═══════════════════════════════════\nENGLISH:\n- porn, xxx, sex, nude, hentai, NSFW, adult, erotic, hardcore, blowjob, milf\n- onlyfans, cam, webcam + sexual context\n- BLOCK DOMAINS: xnxx, pornhub, xvideos, redtube, brazzers, onlyfans, chaturbate, livejasmin, myfreecams, youporn, xhamster, spankbang, tnaflix\n\nTHAI — อวัยวะเพศ (BLOCK IMMEDIATELY):\n- หี, ควย, เย็ด, แตด, จิ๋ม, จิมิ, หอย, จู๋, กระเจี๊ยว, เจี๊ยว, หำ, กระจู๋\n- จ้ำบ๊ะ, โมะ, แคม, คลิตอริส, ดอก, ปี๊, ไข่, นม, หน้าอก, เต้า, หัวนม\n- ก้น, ตูด, รูตูด, รูก้น\n\nTHAI — การกระทำทางเพศ:\n- เสียว, เงี่ยน, เงี่ยนหี, เงี่ยนควย, อมควย, อมจู๋, อมหี, เลียหี, เลียไข่\n- ชักว่าว, ช่วยตัวเอง, สำเร็จความใคร่, แตกใน, แตกนอก, แตกปาก, เสียบ, กระเด้า, เด้า, ฟัน\n- นวดกระปู๋, นวดเงี่ยน, น้ำแตก, น้ำพุ่ง, สวิงกิ้ง, สามเส้า, สี่เส้า, หมู่, สลับคู่\n- ท่าสุนัข, ท่าหมา, 69\n\nTHAI — สื่อลามก:\n- โป๊, หนังโป๊, หนังx, คลิปx, คลิปโป๊, คลิปหลุด, คลิปเสียว\n- หลุดดารา, หลุดนักศึกษา, หลุดvk, หลุดonlyfans, หลุด, เด็กโป๊\n- หนังav, jav, avซับไทย, uncen, หนังอาร์, โดจิน, doujin, มังงะโป๊, การ์ตูนโป๊\n- โป๊เด็ด, หีเด็ด, ควยสวย, คลิปเย็ด, หนังเย็ด\n\nTHAI — ขายบริการ / นัดพบ:\n- ขายตัว, ขายบริการ, นัดเย็ด, นัดฟัน, คู่ขา, หาคู่\n- ไซด์ไลน์, สาวไซด์ไลน์, เด็กชง, เด็กเสริฟ, พริตตี้\n- นวดน้ำมัน, อาบอบนวด, สปา, รับงาน, งานนอกสถานที่, ขายคลิป, ขายรูป, ขายหี\n\nTHAI — ไลฟ์สด / เปลือย:\n- ไลฟ์สด, liveสด, ไลฟ์สดโป๊, เปลือย, แก้ผ้า, โชว์หี, โชว์นม, โชว์เสียว\n- camfrog, bigo, stripchat, bongacams, โชว์สด\n\nTHAI — อื่นๆ:\n- โรคจิต, หื่น, ลามก, อนาจาร, ลวนลาม, ข่มขืน, น้องแนท, เยาวชน\n\n═══════════════════════════════════\n🎰 GAMBLING DETECTION — BLOCK EVERYTHING\n═══════════════════════════════════\nINTERNATIONAL BRANDS (block instantly):\n- casino, bet365, betway, betfair, draftkings, fanduel, williamhill, paddypower, unibet, bwin, 888casino, leovegas, pokerstars\n\nTHAI CASINO BRANDS (block instantly):\n- ufabet, ufa, gclub, sbobet, sagame, goldenslot, ts911\n- betflix, betflik, pgslot, joker, slotxo, ambbet, live22\n- allbet, maxbet, ibcbet, m88, w88, dafabet, fun88, 12bet, 188bet\n- lmgbet, kingkongxo, slot123, auto123, wallet123, royalslot, slotwallet\n- pussy888, 918kiss, mega888, xe88, ace333, playtech, habanero\n- pragmatik, pragmatic, pgsoft, netent, microgaming\n- baccarat (เกมคาสิโน — แต่ถ้าเป็นแบรนด์คริสตัล/สินค้าหรู Baccarat ในบริบทช้อปปิ้ง → SAFE)\n\n═══════════════════════════════════\n🎲 THAI LOTTERY (หวย) — BLOCK ALL\n═══════════════════════════════════\nKEYWORDS = BLOCK INSTANTLY:\n- lotto, lottery, huay, ruay, หวย, ล็อตโต้, ล็อตเตอรี่, สลากกินแบ่ง\n- หวยรัฐ, หวยลาว, หวยฮานอย, หวยหุ้น, หวยใต้ดิน, หวยออนไลน์, หวยออน\n- เลขเด็ด, ซื้อหวย, ขายหวย, แทงหวย, ใบหวย, ตรวจหวย\n\nTHAI LOTTERY DOMAIN PATTERNS (BLOCK if domain contains):\n- lotto + numbers (lotto432, lotto888, lotto4d, lotto6d, lotto77, lottovip)\n- huay + numbers (huay999, huay789, huay24, huay88)\n- ruay + numbers (ruay99, ruay888, ruay77)\n- หวย + numbers or tld\n- Any domain with both numbers AND หวย/ล็อต/lotto/huay = GAMBLING\n\n═══════════════════════════════════\n🧩 PATTERN MATCHING — BLOCK IF ANY\n═══════════════════════════════════\nGAMBLING PATTERNS:\n- xxxbet, betxxx, xxxslot, slotxxx, xxxcasino, pgxxx, jokerxxx\n- brand + numbers (ufabet999, betflix789, pgslot123, joker456, lotto432)\n- abbreviations (ufa, pg, joker, slot) + numbers = GAMBLING\n- auto/wallet/deposit/withdraw + slot/bet/casino = GAMBLING\n- live22, live33, live44, live + numbers\n- Any domain ending in ...slot, ...bet, ...casino, ...lotto, ...huay, ...ruay\n\nSUSPICIOUS TLDs (auto-lower threshold to 10%):\n.xyz .top .vip .cc .tk .ml .ga .cf .gq .fun .club .shop .store .online .site .icu .buzz .rest .live .bet .casino .win\n\n═══════════════════════════════════\n⚡ RULES — FOLLOW STRICTLY\n═══════════════════════════════════\n- Be EXTREMELY aggressive. When in doubt → ALWAYS block.\n- Analyze: domain name, TLD, subdomains, path, query string.\n- Thai gambling = brand + numbers (ufabet999, lotto432, pgslot123) → BLOCK\n- Abbreviations (ufa, pg, joker, slot, lotto) + numbers → BLOCK\n- slot/bet/casino/lotto/huay anywhere in domain → 90%+ confidence → BLOCK\n- SUSPICIOUS TLD + any gambling keyword → BLOCK instantly (confidence 95)\n- TWO OR MORE suspicious indicators → BLOCK\n- Confidence > 25% for harmful → block (MEGA aggressive)\n- False positive is MUCH better than false negative\n- NEVER block these safe domains: x.com, twitter.com, tesla.com, spacex.com, openai.com, github.com, youtube.com, google.com, microsoft.com, apple.com, amazon.com, wikipedia.org, reddit.com\n- Do NOT block \"sex\" as part of larger word (spacex, sussex, essex, sextant)\n- NEVER explain reasoning in prose → JSON only. NEVER output markdown. NEVER output code fences.\n\n═══════════════════════════════════\n📄 CONTENT ANALYSIS MODE (when PAGE TEXT is provided)\n═══════════════════════════════════\n- You are analyzing ACTUAL webpage text content, not just a domain name.\n- Focus 80% on CONTENT, 20% on domain name.\n- Scan for: sexual descriptions, adult service offers, gambling invitations, lottery sales, explicit anatomy references in sexual context.\n- Innocent words in non-sexual context → SAFE (e.g. medical article about breast cancer = SAFE).\n- News article mentioning gambling arrests → SAFE (context is negative/news).\n- Educational content about sex education → SAFE.\n- Any page directly offering porn, gambling, lottery, prostitution, or explicit sexual content → BLOCK.\n- Thai forums/discussions with casual sexual slang → BLOCK.\n- Any page whose PRIMARY purpose is adult/gambling → BLOCK.\n- When text is empty or too short → rely on domain name only.\n\nOUTPUT: Valid JSON only.\n{\n \"blocked\": boolean,\n \"category\": \"gambling\" | \"adult\" | \"safe\",\n \"confidence\": 0-100,\n \"reason\": \"Short concise English explanation (under 15 words)\"\n}\nCRITICAL: Output all fields including \"reason\" strictly in ENGLISH. Never output Thai characters.`;\n\n// ─── Content-based scan: sends actual PAGE TEXT to AI ──────────\nexport async function checkContentWithAI(url, pageText, apiKey, provider = \"deepseek\") {\n const domain = extractDomain(url);\n const userMsg = `URL: ${domain}\\n\\nPAGE TEXT (first 2500 chars):\\n${pageText.substring(0, 2500)}`;\n\n switch (provider) {\n case \"claude\":\n return await callClaude(userMsg, apiKey);\n case \"deepseek\":\n return await callDeepSeek(userMsg, apiKey);\n case \"openai\":\n return await callOpenAI(userMsg, apiKey);\n default:\n return await callDeepSeek(userMsg, apiKey);\n }\n}\n\n// ─── Domain-only scan: original behavior ────────────────────────\nexport async function checkWithAI(url, apiKey, provider = \"deepseek\") {\n const domain = extractDomain(url);\n const userMsg = `Analyze this URL: ${domain}`;\n\n switch (provider) {\n case \"claude\":\n return await callClaude(userMsg, apiKey);\n case \"deepseek\":\n return await callDeepSeek(userMsg, apiKey);\n case \"openai\":\n return await callOpenAI(userMsg, apiKey);\n default:\n return await callDeepSeek(userMsg, apiKey);\n }\n}\n\nasync function callClaude(msg, apiKey) {\n const res = await fetch(\"https://api.anthropic.com/v1/messages\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": apiKey,\n \"anthropic-version\": \"2023-06-01\"\n },\n body: JSON.stringify({\n model: \"claude-haiku-4-5\",\n max_tokens: 500,\n system: SYSTEM_PROMPT,\n messages: [{ role: \"user\", content: msg }]\n })\n });\n\n if (!res.ok) {\n throw new Error(`Claude API error: ${res.status} ${res.statusText}`);\n }\n\n const data = await res.json();\n const content = data?.content?.[0]?.text || \"\";\n return parseAIResponse(content, msg);\n}\n\nasync function callDeepSeek(msg, apiKey) {\n // Try deepseek-chat (standard DeepSeek API model)\n let res = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`\n },\n body: JSON.stringify({\n model: \"deepseek-chat\",\n max_tokens: 500,\n messages: [\n { role: \"system\", content: SYSTEM_PROMPT },\n { role: \"user\", content: msg }\n ]\n })\n });\n\n if (!res.ok) {\n // Retry with deepseek-v4-pro if deepseek-chat returned error\n res = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`\n },\n body: JSON.stringify({\n model: \"deepseek-v4-pro\",\n max_tokens: 500,\n messages: [\n { role: \"system\", content: SYSTEM_PROMPT },\n { role: \"user\", content: msg }\n ]\n })\n });\n }\n\n if (!res.ok) {\n throw new Error(`DeepSeek API error: ${res.status} ${res.statusText}`);\n }\n\n const data = await res.json();\n const content = data?.choices?.[0]?.message?.content || \"\";\n return parseAIResponse(content, msg);\n}\n\nasync function callOpenAI(msg, apiKey) {\n const res = await fetch(\"https://api.openai.com/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`\n },\n body: JSON.stringify({\n model: \"gpt-4o-mini\",\n max_tokens: 500,\n messages: [\n { role: \"system\", content: SYSTEM_PROMPT },\n { role: \"user\", content: msg }\n ]\n })\n });\n\n if (!res.ok) {\n throw new Error(`OpenAI API error: ${res.status} ${res.statusText}`);\n }\n\n const data = await res.json();\n const content = data?.choices?.[0]?.message?.content || \"\";\n return parseAIResponse(content, msg);\n}\n\n// confidence ขั้นต่ำที่จะยอมบล็อก (สอดคล้องกับ prompt: harmful > 25% → block)\nconst AI_BLOCK_THRESHOLD = 25;\n\nfunction parseAIResponse(text, rawUserMsg = \"\") {\n // If response is empty or null, perform heuristic keyword inspection on rawUserMsg\n if (!text || typeof text !== \"string\" || !text.trim()) {\n const lowerMsg = (rawUserMsg || \"\").toLowerCase();\n const isGambling = lowerMsg.includes(\"gambling\") || lowerMsg.includes(\"พนัน\") || lowerMsg.includes(\"คาสิโน\")\n || lowerMsg.includes(\"สล็อต\") || lowerMsg.includes(\"slot\") || lowerMsg.includes(\"bet\") || lowerMsg.includes(\"ufa\")\n || lowerMsg.includes(\"tako\") || lowerMsg.includes(\"เครดิตฟรี\") || lowerMsg.includes(\"ฝากถอน\") || lowerMsg.includes(\"บาคาร่า\");\n const isAdult = lowerMsg.includes(\"adult\") || lowerMsg.includes(\"porn\") || lowerMsg.includes(\"โป๊\") || lowerMsg.includes(\"18+\") || lowerMsg.includes(\"xxx\") || lowerMsg.includes(\"เย็ด\") || lowerMsg.includes(\"หี\") || lowerMsg.includes(\"ควย\");\n\n if (isGambling || isAdult) {\n return {\n blocked: true,\n category: isGambling ? \"gambling\" : \"adult\",\n confidence: 90,\n reason: \"Detected forbidden keywords (Heuristic Fallback)\",\n source: \"heuristic\"\n };\n }\n\n return {\n blocked: false,\n category: \"safe\",\n confidence: 0,\n reason: \"Empty response from AI (Passed Heuristic Check)\",\n source: \"ai\"\n };\n }\n\n try {\n // 1. Extract first JSON object { ... } via Regex\n const jsonMatch = text.match(/\\{[\\s\\S]*?\\}/);\n const jsonStr = jsonMatch ? jsonMatch[0] : text;\n const parsed = JSON.parse(jsonStr);\n\n let confidence = Number(parsed.confidence);\n if (!Number.isFinite(confidence)) confidence = parsed.blocked ? 100 : 0;\n confidence = Math.max(0, Math.min(100, confidence));\n\n const category = (parsed.category === \"adult\" || parsed.category === \"gambling\")\n ? parsed.category : \"safe\";\n const blocked = (parsed.blocked === true || String(parsed.blocked).toLowerCase() === \"true\")\n && category !== \"safe\"\n && confidence >= AI_BLOCK_THRESHOLD;\n\n return {\n blocked,\n category: blocked ? category : \"safe\",\n confidence,\n reason: parsed.reason || \"Evaluated by AI\",\n source: \"ai\"\n };\n } catch (err) {\n // 2. Fallback: Scan raw response text & message for block keywords if JSON parse failed\n const lowerText = (text + \" \" + rawUserMsg).toLowerCase();\n const isGambling = lowerText.includes(\"gambling\") || lowerText.includes(\"พนัน\") || lowerText.includes(\"คาสิโน\") || lowerText.includes(\"สล็อต\") || lowerText.includes(\"tako\") || lowerText.includes(\"ufa\") || lowerText.includes(\"bet\");\n const isAdult = lowerText.includes(\"adult\") || lowerText.includes(\"porn\") || lowerText.includes(\"โป๊\") || lowerText.includes(\"18+\");\n const isBlocked = lowerText.includes('\"blocked\": true') || lowerText.includes('\"blocked\":true') || lowerText.includes(\"blocked\");\n\n if ((isGambling || isAdult) && isBlocked) {\n return {\n blocked: true,\n category: isGambling ? \"gambling\" : \"adult\",\n confidence: 90,\n reason: \"AI detected harmful content\",\n source: \"ai\"\n };\n }\n\n return {\n blocked: false,\n category: \"safe\",\n confidence: 0,\n reason: \"No block instruction in AI response\",\n source: \"ai\"\n };\n }\n}\n\nfunction extractDomain(url) {\n try {\n return new URL(url.startsWith(\"http\") ? url : `https://${url}`).hostname;\n } catch {\n return url;\n }\n}\n"}, "4c12beec1a4cc29d66576aa18d9139ea3e213350": {"t": 1785758696.142053, "n": 11294, "h": "106eb45aaf8dcd08", "c": "// aiClient.js\n// AI Abstraction Layer — รองรับหลาย Provider สลับได้ใน Settings\n\nconst SYSTEM_PROMPT = `You are GuardAI — an extremely aggressive web content safety classifier for Thailand.\nBlock hard. Block first. When in doubt → BLOCK. False positive >>> false negative.\n\n═══════════════════════════════════\n🔞 ADULT DETECTION — BLOCK AGGRESSIVELY\n═══════════════════════════════════\nENGLISH:\n- porn, xxx, sex, nude, hentai, NSFW, adult, erotic, hardcore, blowjob, milf\n- onlyfans, cam, webcam + sexual context\n- BLOCK DOMAINS: xnxx, pornhub, xvideos, redtube, brazzers, onlyfans, chaturbate, livejasmin, myfreecams, youporn, xhamster, spankbang, tnaflix\n\nTHAI — อวัยวะเพศ (BLOCK IMMEDIATELY):\n- หี, ควย, เย็ด, แตด, จิ๋ม, จิมิ, หอย, จู๋, กระเจี๊ยว, เจี๊ยว, หำ, กระจู๋\n- จ้ำบ๊ะ, โมะ, แคม, คลิตอริส, ดอก, ปี๊, ไข่, นม, หน้าอก, เต้า, หัวนม\n- ก้น, ตูด, รูตูด, รูก้น\n\nTHAI — การกระทำทางเพศ:\n- เสียว, เงี่ยน, เงี่ยนหี, เงี่ยนควย, อมควย, อมจู๋, อมหี, เลียหี, เลียไข่\n- ชักว่าว, ช่วยตัวเอง, สำเร็จความใคร่, แตกใน, แตกนอก, แตกปาก, เสียบ, กระเด้า, เด้า, ฟัน\n- นวดกระปู๋, นวดเงี่ยน, น้ำแตก, น้ำพุ่ง, สวิงกิ้ง, สามเส้า, สี่เส้า, หมู่, สลับคู่\n- ท่าสุนัข, ท่าหมา, 69\n\nTHAI — สื่อลามก:\n- โป๊, หนังโป๊, หนังx, คลิปx, คลิปโป๊, คลิปหลุด, คลิปเสียว\n- หลุดดารา, หลุดนักศึกษา, หลุดvk, หลุดonlyfans, หลุด, เด็กโป๊\n- หนังav, jav, avซับไทย, uncen, หนังอาร์, โดจิน, doujin, มังงะโป๊, การ์ตูนโป๊\n- โป๊เด็ด, หีเด็ด, ควยสวย, คลิปเย็ด, หนังเย็ด\n\nTHAI — ขายบริการ / นัดพบ:\n- ขายตัว, ขายบริการ, นัดเย็ด, นัดฟัน, คู่ขา, หาคู่\n- ไซด์ไลน์, สาวไซด์ไลน์, เด็กชง, เด็กเสริฟ, พริตตี้\n- นวดน้ำมัน, อาบอบนวด, สปา, รับงาน, งานนอกสถานที่, ขายคลิป, ขายรูป, ขายหี\n\nTHAI — ไลฟ์สด / เปลือย:\n- ไลฟ์สด, liveสด, ไลฟ์สดโป๊, เปลือย, แก้ผ้า, โชว์หี, โชว์นม, โชว์เสียว\n- camfrog, bigo, stripchat, bongacams, โชว์สด\n\nTHAI — อื่นๆ:\n- โรคจิต, หื่น, ลามก, อนาจาร, ลวนลาม, ข่มขืน, น้องแนท, เยาวชน\n\n═══════════════════════════════════\n🎰 GAMBLING DETECTION — BLOCK EVERYTHING\n═══════════════════════════════════\nINTERNATIONAL BRANDS (block instantly):\n- casino, bet365, betway, betfair, draftkings, fanduel, williamhill, paddypower, unibet, bwin, 888casino, leovegas, pokerstars\n\nTHAI CASINO BRANDS (block instantly):\n- ufabet, ufa, gclub, sbobet, sagame, goldenslot, ts911\n- betflix, betflik, pgslot, joker, slotxo, ambbet, live22\n- allbet, maxbet, ibcbet, m88, w88, dafabet, fun88, 12bet, 188bet\n- lmgbet, kingkongxo, slot123, auto123, wallet123, royalslot, slotwallet\n- pussy888, 918kiss, mega888, xe88, ace333, playtech, habanero\n- pragmatik, pragmatic, pgsoft, netent, microgaming\n- baccarat (เกมคาสิโน — แต่ถ้าเป็นแบรนด์คริสตัล/สินค้าหรู Baccarat ในบริบทช้อปปิ้ง → SAFE)\n\n═══════════════════════════════════\n🎲 THAI LOTTERY (หวย) — BLOCK ALL\n═══════════════════════════════════\nKEYWORDS = BLOCK INSTANTLY:\n- lotto, lottery, huay, ruay, หวย, ล็อตโต้, ล็อตเตอรี่, สลากกินแบ่ง\n- หวยรัฐ, หวยลาว, หวยฮานอย, หวยหุ้น, หวยใต้ดิน, หวยออนไลน์, หวยออน\n- เลขเด็ด, ซื้อหวย, ขายหวย, แทงหวย, ใบหวย, ตรวจหวย\n\nTHAI LOTTERY DOMAIN PATTERNS (BLOCK if domain contains):\n- lotto + numbers (lotto432, lotto888, lotto4d, lotto6d, lotto77, lottovip)\n- huay + numbers (huay999, huay789, huay24, huay88)\n- ruay + numbers (ruay99, ruay888, ruay77)\n- หวย + numbers or tld\n- Any domain with both numbers AND หวย/ล็อต/lotto/huay = GAMBLING\n\n═══════════════════════════════════\n🧩 PATTERN MATCHING — BLOCK IF ANY\n═══════════════════════════════════\nGAMBLING PATTERNS:\n- xxxbet, betxxx, xxxslot, slotxxx, xxxcasino, pgxxx, jokerxxx\n- brand + numbers (ufabet999, betflix789, pgslot123, joker456, lotto432)\n- abbreviations (ufa, pg, joker, slot) + numbers = GAMBLING\n- auto/wallet/deposit/withdraw + slot/bet/casino = GAMBLING\n- live22, live33, live44, live + numbers\n- Any domain ending in ...slot, ...bet, ...casino, ...lotto, ...huay, ...ruay\n\nSUSPICIOUS TLDs (auto-lower threshold to 10%):\n.xyz .top .vip .cc .tk .ml .ga .cf .gq .fun .club .shop .store .online .site .icu .buzz .rest .live .bet .casino .win\n\n═══════════════════════════════════\n⚡ RULES — FOLLOW STRICTLY\n═══════════════════════════════════\n- Be EXTREMELY aggressive. When in doubt → ALWAYS block.\n- Analyze: domain name, TLD, subdomains, path, query string.\n- Thai gambling = brand + numbers (ufabet999, lotto432, pgslot123) → BLOCK\n- Abbreviations (ufa, pg, joker, slot, lotto) + numbers → BLOCK\n- slot/bet/casino/lotto/huay anywhere in domain → 90%+ confidence → BLOCK\n- SUSPICIOUS TLD + any gambling keyword → BLOCK instantly (confidence 95)\n- TWO OR MORE suspicious indicators → BLOCK\n- Confidence > 25% for harmful → block (MEGA aggressive)\n- False positive is MUCH better than false negative\n- NEVER block these safe domains: x.com, twitter.com, tesla.com, spacex.com, openai.com, github.com, youtube.com, google.com, microsoft.com, apple.com, amazon.com, wikipedia.org, reddit.com\n- Do NOT block \"sex\" as part of larger word (spacex, sussex, essex, sextant)\n- NEVER explain reasoning in prose → JSON only. NEVER output markdown. NEVER output code fences.\n\n═══════════════════════════════════\n📄 CONTENT ANALYSIS MODE (when PAGE TEXT is provided)\n═══════════════════════════════════\n- You are analyzing ACTUAL webpage text content, not just a domain name.\n- Focus 80% on CONTENT, 20% on domain name.\n- Scan for: sexual descriptions, adult service offers, gambling invitations, lottery sales, explicit anatomy references in sexual context.\n- Innocent words in non-sexual context → SAFE (e.g. medical article about breast cancer = SAFE).\n- News article mentioning gambling arrests → SAFE (context is negative/news).\n- Educational content about sex education → SAFE.\n- Any page directly offering porn, gambling, lottery, prostitution, or explicit sexual content → BLOCK.\n- Thai forums/discussions with casual sexual slang → BLOCK.\n- Any page whose PRIMARY purpose is adult/gambling → BLOCK.\n- When text is empty or too short → rely on domain name only.\n\nOUTPUT: Valid JSON only.\n{\n \"blocked\": boolean,\n \"category\": \"gambling\" | \"adult\" | \"safe\",\n \"confidence\": 0-100,\n \"reason\": \"ภาษาไทย สั้นๆ ไม่เกิน 20 คำ\"\n}`;\n\n// ─── Content-based scan: sends actual PAGE TEXT to AI ──────────\nexport async function checkContentWithAI(url, pageText, apiKey, provider = \"deepseek\") {\n const domain = extractDomain(url);\n const userMsg = `URL: ${domain}\\n\\nPAGE TEXT (first 2500 chars):\\n${pageText.substring(0, 2500)}`;\n\n switch (provider) {\n case \"claude\":\n return await callClaude(userMsg, apiKey);\n case \"deepseek\":\n return await callDeepSeek(userMsg, apiKey);\n case \"openai\":\n return await callOpenAI(userMsg, apiKey);\n default:\n return await callDeepSeek(userMsg, apiKey);\n }\n}\n\n// ─── Domain-only scan: original behavior ────────────────────────\nexport async function checkWithAI(url, apiKey, provider = \"deepseek\") {\n const domain = extractDomain(url);\n const userMsg = `Analyze this URL: ${domain}`;\n\n switch (provider) {\n case \"claude\":\n return await callClaude(userMsg, apiKey);\n case \"deepseek\":\n return await callDeepSeek(userMsg, apiKey);\n case \"openai\":\n return await callOpenAI(userMsg, apiKey);\n default:\n return await callDeepSeek(userMsg, apiKey);\n }\n}\n\nasync function callClaude(msg, apiKey) {\n const res = await fetch(\"https://api.anthropic.com/v1/messages\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": apiKey,\n \"anthropic-version\": \"2023-06-01\"\n },\n body: JSON.stringify({\n model: \"claude-haiku-4-5\",\n max_tokens: 150,\n system: SYSTEM_PROMPT,\n messages: [{ role: \"user\", content: msg }]\n })\n });\n\n if (!res.ok) {\n throw new Error(`Claude API error: ${res.status} ${res.statusText}`);\n }\n\n const data = await res.json();\n return parseAIResponse(data.content[0].text);\n}\n\nasync function callDeepSeek(msg, apiKey) {\n const res = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`\n },\n body: JSON.stringify({\n model: \"deepseek-v4-pro\",\n max_tokens: 150,\n messages: [\n { role: \"system\", content: SYSTEM_PROMPT },\n { role: \"user\", content: msg }\n ]\n })\n });\n\n if (!res.ok) {\n throw new Error(`DeepSeek API error: ${res.status} ${res.statusText}`);\n }\n\n const data = await res.json();\n return parseAIResponse(data.choices[0].message.content);\n}\n\nasync function callOpenAI(msg, apiKey) {\n const res = await fetch(\"https://api.openai.com/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`\n },\n body: JSON.stringify({\n model: \"gpt-4o-mini\",\n max_tokens: 150,\n messages: [\n { role: \"system\", content: SYSTEM_PROMPT },\n { role: \"user\", content: msg }\n ]\n })\n });\n\n if (!res.ok) {\n throw new Error(`OpenAI API error: ${res.status} ${res.statusText}`);\n }\n\n const data = await res.json();\n return parseAIResponse(data.choices[0].message.content);\n}\n\n// confidence ขั้นต่ำที่จะยอมบล็อก (สอดคล้องกับ prompt: harmful > 25% → block)\nconst AI_BLOCK_THRESHOLD = 25;\n\nfunction parseAIResponse(text) {\n if (!text || typeof text !== \"string\") {\n return { blocked: false, category: \"safe\", confidence: 0, reason: \"คำตอบจาก AI ว่างเปล่า\", source: \"ai\" };\n }\n\n try {\n // 1. Extract first JSON object { ... } via Regex\n const jsonMatch = text.match(/\\{[\\s\\S]*?\\}/);\n const jsonStr = jsonMatch ? jsonMatch[0] : text;\n const parsed = JSON.parse(jsonStr);\n\n let confidence = Number(parsed.confidence);\n if (!Number.isFinite(confidence)) confidence = parsed.blocked ? 100 : 0;\n confidence = Math.max(0, Math.min(100, confidence));\n\n const category = (parsed.category === \"adult\" || parsed.category === \"gambling\")\n ? parsed.category : \"safe\";\n const blocked = (parsed.blocked === true || String(parsed.blocked).toLowerCase() === \"true\")\n && category !== \"safe\"\n && confidence >= AI_BLOCK_THRESHOLD;\n\n return {\n blocked,\n category: blocked ? category : \"safe\",\n confidence,\n reason: parsed.reason || \"วิเคราะห์โดย AI\",\n source: \"ai\"\n };\n } catch (err) {\n // 2. Fallback: Scan raw response text for block keywords if JSON parse failed\n const lowerText = text.toLowerCase();\n const isGambling = lowerText.includes(\"gambling\") || lowerText.includes(\"พนัน\") || lowerText.includes(\"คาสิโน\") || lowerText.includes(\"สล็อต\");\n const isAdult = lowerText.includes(\"adult\") || lowerText.includes(\"porn\") || lowerText.includes(\"โป๊\") || lowerText.includes(\"18+\");\n const isBlocked = lowerText.includes('\"blocked\": true') || lowerText.includes('\"blocked\":true') || lowerText.includes(\"blocked\");\n\n if ((isGambling || isAdult) && isBlocked) {\n return {\n blocked: true,\n category: isGambling ? \"gambling\" : \"adult\",\n confidence: 90,\n reason: \"AI ตรวจพบเนื้อหาต้องห้าม\",\n source: \"ai\"\n };\n }\n\n return {\n blocked: false,\n category: \"safe\",\n confidence: 0,\n reason: \"ไม่พบคำสั่งบล็อกในตอบกลับของ AI\",\n source: \"ai\"\n };\n }\n}\n\nfunction extractDomain(url) {\n try {\n return new URL(url.startsWith(\"http\") ? url : `https://${url}`).hostname;\n } catch {\n return url;\n }\n}\n"}, "256afa651803f15f09fec3d9996a9cc8c91f7ee3": {"t": 1785758697.6218185, "n": 10410, "h": "f2bc38de1d4e2055", "c": "// aiClient.js\n// AI Abstraction Layer — รองรับหลาย Provider สลับได้ใน Settings\n\nconst SYSTEM_PROMPT = `You are GuardAI — an extremely aggressive web content safety classifier for Thailand.\nBlock hard. Block first. When in doubt → BLOCK. False positive >>> false negative.\n\n═══════════════════════════════════\n🔞 ADULT DETECTION — BLOCK AGGRESSIVELY\n═══════════════════════════════════\nENGLISH:\n- porn, xxx, sex, nude, hentai, NSFW, adult, erotic, hardcore, blowjob, milf\n- onlyfans, cam, webcam + sexual context\n- BLOCK DOMAINS: xnxx, pornhub, xvideos, redtube, brazzers, onlyfans, chaturbate, livejasmin, myfreecams, youporn, xhamster, spankbang, tnaflix\n\nTHAI — อวัยวะเพศ (BLOCK IMMEDIATELY):\n- หี, ควย, เย็ด, แตด, จิ๋ม, จิมิ, หอย, จู๋, กระเจี๊ยว, เจี๊ยว, หำ, กระจู๋\n- จ้ำบ๊ะ, โมะ, แคม, คลิตอริส, ดอก, ปี๊, ไข่, นม, หน้าอก, เต้า, หัวนม\n- ก้น, ตูด, รูตูด, รูก้น\n\nTHAI — การกระทำทางเพศ:\n- เสียว, เงี่ยน, เงี่ยนหี, เงี่ยนควย, อมควย, อมจู๋, อมหี, เลียหี, เลียไข่\n- ชักว่าว, ช่วยตัวเอง, สำเร็จความใคร่, แตกใน, แตกนอก, แตกปาก, เสียบ, กระเด้า, เด้า, ฟัน\n- นวดกระปู๋, นวดเงี่ยน, น้ำแตก, น้ำพุ่ง, สวิงกิ้ง, สามเส้า, สี่เส้า, หมู่, สลับคู่\n- ท่าสุนัข, ท่าหมา, 69\n\nTHAI — สื่อลามก:\n- โป๊, หนังโป๊, หนังx, คลิปx, คลิปโป๊, คลิปหลุด, คลิปเสียว\n- หลุดดารา, หลุดนักศึกษา, หลุดvk, หลุดonlyfans, หลุด, เด็กโป๊\n- หนังav, jav, avซับไทย, uncen, หนังอาร์, โดจิน, doujin, มังงะโป๊, การ์ตูนโป๊\n- โป๊เด็ด, หีเด็ด, ควยสวย, คลิปเย็ด, หนังเย็ด\n\nTHAI — ขายบริการ / นัดพบ:\n- ขายตัว, ขายบริการ, นัดเย็ด, นัดฟัน, คู่ขา, หาคู่\n- ไซด์ไลน์, สาวไซด์ไลน์, เด็กชง, เด็กเสริฟ, พริตตี้\n- นวดน้ำมัน, อาบอบนวด, สปา, รับงาน, งานนอกสถานที่, ขายคลิป, ขายรูป, ขายหี\n\nTHAI — ไลฟ์สด / เปลือย:\n- ไลฟ์สด, liveสด, ไลฟ์สดโป๊, เปลือย, แก้ผ้า, โชว์หี, โชว์นม, โชว์เสียว\n- camfrog, bigo, stripchat, bongacams, โชว์สด\n\nTHAI — อื่นๆ:\n- โรคจิต, หื่น, ลามก, อนาจาร, ลวนลาม, ข่มขืน, น้องแนท, เยาวชน\n\n═══════════════════════════════════\n🎰 GAMBLING DETECTION — BLOCK EVERYTHING\n═══════════════════════════════════\nINTERNATIONAL BRANDS (block instantly):\n- casino, bet365, betway, betfair, draftkings, fanduel, williamhill, paddypower, unibet, bwin, 888casino, leovegas, pokerstars\n\nTHAI CASINO BRANDS (block instantly):\n- ufabet, ufa, gclub, sbobet, sagame, goldenslot, ts911\n- betflix, betflik, pgslot, joker, slotxo, ambbet, live22\n- allbet, maxbet, ibcbet, m88, w88, dafabet, fun88, 12bet, 188bet\n- lmgbet, kingkongxo, slot123, auto123, wallet123, royalslot, slotwallet\n- pussy888, 918kiss, mega888, xe88, ace333, playtech, habanero\n- pragmatik, pragmatic, pgsoft, netent, microgaming\n- baccarat (เกมคาสิโน — แต่ถ้าเป็นแบรนด์คริสตัล/สินค้าหรู Baccarat ในบริบทช้อปปิ้ง → SAFE)\n\n═══════════════════════════════════\n🎲 THAI LOTTERY (หวย) — BLOCK ALL\n═══════════════════════════════════\nKEYWORDS = BLOCK INSTANTLY:\n- lotto, lottery, huay, ruay, หวย, ล็อตโต้, ล็อตเตอรี่, สลากกินแบ่ง\n- หวยรัฐ, หวยลาว, หวยฮานอย, หวยหุ้น, หวยใต้ดิน, หวยออนไลน์, หวยออน\n- เลขเด็ด, ซื้อหวย, ขายหวย, แทงหวย, ใบหวย, ตรวจหวย\n\nTHAI LOTTERY DOMAIN PATTERNS (BLOCK if domain contains):\n- lotto + numbers (lotto432, lotto888, lotto4d, lotto6d, lotto77, lottovip)\n- huay + numbers (huay999, huay789, huay24, huay88)\n- ruay + numbers (ruay99, ruay888, ruay77)\n- หวย + numbers or tld\n- Any domain with both numbers AND หวย/ล็อต/lotto/huay = GAMBLING\n\n═══════════════════════════════════\n🧩 PATTERN MATCHING — BLOCK IF ANY\n═══════════════════════════════════\nGAMBLING PATTERNS:\n- xxxbet, betxxx, xxxslot, slotxxx, xxxcasino, pgxxx, jokerxxx\n- brand + numbers (ufabet999, betflix789, pgslot123, joker456, lotto432)\n- abbreviations (ufa, pg, joker, slot) + numbers = GAMBLING\n- auto/wallet/deposit/withdraw + slot/bet/casino = GAMBLING\n- live22, live33, live44, live + numbers\n- Any domain ending in ...slot, ...bet, ...casino, ...lotto, ...huay, ...ruay\n\nSUSPICIOUS TLDs (auto-lower threshold to 10%):\n.xyz .top .vip .cc .tk .ml .ga .cf .gq .fun .club .shop .store .online .site .icu .buzz .rest .live .bet .casino .win\n\n═══════════════════════════════════\n⚡ RULES — FOLLOW STRICTLY\n═══════════════════════════════════\n- Be EXTREMELY aggressive. When in doubt → ALWAYS block.\n- Analyze: domain name, TLD, subdomains, path, query string.\n- Thai gambling = brand + numbers (ufabet999, lotto432, pgslot123) → BLOCK\n- Abbreviations (ufa, pg, joker, slot, lotto) + numbers → BLOCK\n- slot/bet/casino/lotto/huay anywhere in domain → 90%+ confidence → BLOCK\n- SUSPICIOUS TLD + any gambling keyword → BLOCK instantly (confidence 95)\n- TWO OR MORE suspicious indicators → BLOCK\n- Confidence > 25% for harmful → block (MEGA aggressive)\n- False positive is MUCH better than false negative\n- NEVER block these safe domains: x.com, twitter.com, tesla.com, spacex.com, openai.com, github.com, youtube.com, google.com, microsoft.com, apple.com, amazon.com, wikipedia.org, reddit.com\n- Do NOT block \"sex\" as part of larger word (spacex, sussex, essex, sextant)\n- NEVER explain reasoning in prose → JSON only. NEVER output markdown. NEVER output code fences.\n\n═══════════════════════════════════\n📄 CONTENT ANALYSIS MODE (when PAGE TEXT is provided)\n═══════════════════════════════════\n- You are analyzing ACTUAL webpage text content, not just a domain name.\n- Focus 80% on CONTENT, 20% on domain name.\n- Scan for: sexual descriptions, adult service offers, gambling invitations, lottery sales, explicit anatomy references in sexual context.\n- Innocent words in non-sexual context → SAFE (e.g. medical article about breast cancer = SAFE).\n- News article mentioning gambling arrests → SAFE (context is negative/news).\n- Educational content about sex education → SAFE.\n- Any page directly offering porn, gambling, lottery, prostitution, or explicit sexual content → BLOCK.\n- Thai forums/discussions with casual sexual slang → BLOCK.\n- Any page whose PRIMARY purpose is adult/gambling → BLOCK.\n- When text is empty or too short → rely on domain name only.\n\nOUTPUT: Valid JSON only.\n{\n \"blocked\": boolean,\n \"category\": \"gambling\" | \"adult\" | \"safe\",\n \"confidence\": 0-100,\n \"reason\": \"ภาษาไทย สั้นๆ ไม่เกิน 20 คำ\"\n}`;\n\n// ─── Content-based scan: sends actual PAGE TEXT to AI ──────────\nexport async function checkContentWithAI(url, pageText, apiKey, provider = \"deepseek\") {\n const domain = extractDomain(url);\n const userMsg = `URL: ${domain}\\n\\nPAGE TEXT (first 2500 chars):\\n${pageText.substring(0, 2500)}`;\n\n switch (provider) {\n case \"claude\":\n return await callClaude(userMsg, apiKey);\n case \"deepseek\":\n return await callDeepSeek(userMsg, apiKey);\n case \"openai\":\n return await callOpenAI(userMsg, apiKey);\n default:\n return await callDeepSeek(userMsg, apiKey);\n }\n}\n\n// ─── Domain-only scan: original behavior ────────────────────────\nexport async function checkWithAI(url, apiKey, provider = \"deepseek\") {\n const domain = extractDomain(url);\n const userMsg = `Analyze this URL: ${domain}`;\n\n switch (provider) {\n case \"claude\":\n return await callClaude(userMsg, apiKey);\n case \"deepseek\":\n return await callDeepSeek(userMsg, apiKey);\n case \"openai\":\n return await callOpenAI(userMsg, apiKey);\n default:\n return await callDeepSeek(userMsg, apiKey);\n }\n}\n\nasync function callClaude(msg, apiKey) {\n const res = await fetch(\"https://api.anthropic.com/v1/messages\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": apiKey,\n \"anthropic-version\": \"2023-06-01\"\n },\n body: JSON.stringify({\n model: \"claude-haiku-4-5\",\n max_tokens: 150,\n system: SYSTEM_PROMPT,\n messages: [{ role: \"user\", content: msg }]\n })\n });\n\n if (!res.ok) {\n throw new Error(`Claude API error: ${res.status} ${res.statusText}`);\n }\n\n const data = await res.json();\n return parseAIResponse(data.content[0].text);\n}\n\nasync function callDeepSeek(msg, apiKey) {\n const res = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`\n },\n body: JSON.stringify({\n model: \"deepseek-v4-pro\",\n max_tokens: 150,\n messages: [\n { role: \"system\", content: SYSTEM_PROMPT },\n { role: \"user\", content: msg }\n ]\n })\n });\n\n if (!res.ok) {\n throw new Error(`DeepSeek API error: ${res.status} ${res.statusText}`);\n }\n\n const data = await res.json();\n return parseAIResponse(data.choices[0].message.content);\n}\n\nasync function callOpenAI(msg, apiKey) {\n const res = await fetch(\"https://api.openai.com/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`\n },\n body: JSON.stringify({\n model: \"gpt-4o-mini\",\n max_tokens: 150,\n messages: [\n { role: \"system\", content: SYSTEM_PROMPT },\n { role: \"user\", content: msg }\n ]\n })\n });\n\n if (!res.ok) {\n throw new Error(`OpenAI API error: ${res.status} ${res.statusText}`);\n }\n\n const data = await res.json();\n return parseAIResponse(data.choices[0].message.content);\n}\n\n// confidence ขั้นต่ำที่จะยอมบล็อก (สอดคล้องกับ prompt: harmful > 25% → block)\nconst AI_BLOCK_THRESHOLD = 25;\n\nfunction parseAIResponse(text) {\n try {\n // Clean markdown code fences if present\n const clean = text.replace(/```json\\s*/g, \"\").replace(/```\\s*/g, \"\").trim();\n const parsed = JSON.parse(clean);\n\n // normalize confidence → ตัวเลข 0-100\n let confidence = Number(parsed.confidence);\n if (!Number.isFinite(confidence)) confidence = parsed.blocked ? 100 : 0;\n confidence = Math.max(0, Math.min(100, confidence));\n\n // บังคับ threshold: ต่อให้ AI ตอบ blocked=true แต่ confidence ต่ำ → ไม่บล็อก\n const category = parsed.category === \"adult\" || parsed.category === \"gambling\"\n ? parsed.category : \"safe\";\n const blocked = parsed.blocked === true\n && category !== \"safe\"\n && confidence >= AI_BLOCK_THRESHOLD;\n\n return {\n blocked,\n category: blocked ? category : \"safe\",\n confidence,\n reason: parsed.reason || \"\",\n source: \"ai\"\n };\n } catch {\n // ถ้า parse ไม่ได้ → ให้ผ่าน (fail safe)\n return {\n blocked: false,\n category: \"safe\",\n confidence: 0,\n reason: \"AI response parse error — allowed\",\n source: \"ai\"\n };\n }\n}\n\nfunction extractDomain(url) {\n try {\n return new URL(url.startsWith(\"http\") ? url : `https://${url}`).hostname;\n } catch {\n return url;\n }\n}\n"}, "eae17f60bc7116577e9063d5f1e91a183cb79166": {"t": 1786068359.157823, "n": 13360, "h": "214fdd2286fbfe9c", "c": "// aiClient.js\n// AI Abstraction Layer — รองรับหลาย Provider สลับได้ใน Settings\n\nconst SYSTEM_PROMPT = `You are GuardAI — an extremely aggressive web content safety classifier for Thailand.\nBlock hard. Block first. When in doubt → BLOCK. False positive >>> false negative.\n\n═══════════════════════════════════\n🔞 ADULT DETECTION — BLOCK AGGRESSIVELY\n═══════════════════════════════════\nENGLISH:\n- porn, xxx, sex, nude, hentai, NSFW, adult, erotic, hardcore, blowjob, milf\n- onlyfans, cam, webcam + sexual context\n- BLOCK DOMAINS: xnxx, pornhub, xvideos, redtube, brazzers, onlyfans, chaturbate, livejasmin, myfreecams, youporn, xhamster, spankbang, tnaflix\n\nTHAI — อวัยวะเพศ (BLOCK IMMEDIATELY):\n- หี, ควย, เย็ด, แตด, จิ๋ม, จิมิ, หอย, จู๋, กระเจี๊ยว, เจี๊ยว, หำ, กระจู๋\n- จ้ำบ๊ะ, โมะ, แคม, คลิตอริส, ดอก, ปี๊, ไข่, นม, หน้าอก, เต้า, หัวนม\n- ก้น, ตูด, รูตูด, รูก้น\n\nTHAI — การกระทำทางเพศ:\n- เสียว, เงี่ยน, เงี่ยนหี, เงี่ยนควย, อมควย, อมจู๋, อมหี, เลียหี, เลียไข่\n- ชักว่าว, ช่วยตัวเอง, สำเร็จความใคร่, แตกใน, แตกนอก, แตกปาก, เสียบ, กระเด้า, เด้า, ฟัน\n- นวดกระปู๋, นวดเงี่ยน, น้ำแตก, น้ำพุ่ง, สวิงกิ้ง, สามเส้า, สี่เส้า, หมู่, สลับคู่\n- ท่าสุนัข, ท่าหมา, 69\n\nTHAI — สื่อลามก:\n- โป๊, หนังโป๊, หนังx, คลิปx, คลิปโป๊, คลิปหลุด, คลิปเสียว\n- หลุดดารา, หลุดนักศึกษา, หลุดvk, หลุดonlyfans, หลุด, เด็กโป๊\n- หนังav, jav, avซับไทย, uncen, หนังอาร์, โดจิน, doujin, มังงะโป๊, การ์ตูนโป๊\n- โป๊เด็ด, หีเด็ด, ควยสวย, คลิปเย็ด, หนังเย็ด\n\nTHAI — ขายบริการ / นัดพบ:\n- ขายตัว, ขายบริการ, นัดเย็ด, นัดฟัน, คู่ขา, หาคู่\n- ไซด์ไลน์, สาวไซด์ไลน์, เด็กชง, เด็กเสริฟ, พริตตี้\n- นวดน้ำมัน, อาบอบนวด, สปา, รับงาน, งานนอกสถานที่, ขายคลิป, ขายรูป, ขายหี\n\nTHAI — ไลฟ์สด / เปลือย:\n- ไลฟ์สด, liveสด, ไลฟ์สดโป๊, เปลือย, แก้ผ้า, โชว์หี, โชว์นม, โชว์เสียว\n- camfrog, bigo, stripchat, bongacams, โชว์สด\n\nTHAI — อื่นๆ:\n- โรคจิต, หื่น, ลามก, อนาจาร, ลวนลาม, ข่มขืน, น้องแนท, เยาวชน\n\n═══════════════════════════════════\n🎰 GAMBLING DETECTION — BLOCK EVERYTHING\n═══════════════════════════════════\nINTERNATIONAL BRANDS (block instantly):\n- casino, bet365, betway, betfair, draftkings, fanduel, williamhill, paddypower, unibet, bwin, 888casino, leovegas, pokerstars\n\nTHAI CASINO BRANDS (block instantly):\n- ufabet, ufa, gclub, sbobet, sagame, goldenslot, ts911\n- betflix, betflik, pgslot, joker, slotxo, ambbet, live22\n- allbet, maxbet, ibcbet, m88, w88, dafabet, fun88, 12bet, 188bet\n- lmgbet, kingkongxo, slot123, auto123, wallet123, royalslot, slotwallet\n- pussy888, 918kiss, mega888, xe88, ace333, playtech, habanero\n- pragmatik, pragmatic, pgsoft, netent, microgaming\n- baccarat (เกมคาสิโน — แต่ถ้าเป็นแบรนด์คริสตัล/สินค้าหรู Baccarat ในบริบทช้อปปิ้ง → SAFE)\n\n═══════════════════════════════════\n🎲 THAI LOTTERY (หวย) — BLOCK ALL\n═══════════════════════════════════\nKEYWORDS = BLOCK INSTANTLY:\n- lotto, lottery, huay, ruay, หวย, ล็อตโต้, ล็อตเตอรี่, สลากกินแบ่ง\n- หวยรัฐ, หวยลาว, หวยฮานอย, หวยหุ้น, หวยใต้ดิน, หวยออนไลน์, หวยออน\n- เลขเด็ด, ซื้อหวย, ขายหวย, แทงหวย, ใบหวย, ตรวจหวย\n\nTHAI LOTTERY DOMAIN PATTERNS (BLOCK if domain contains):\n- lotto + numbers (lotto432, lotto888, lotto4d, lotto6d, lotto77, lottovip)\n- huay + numbers (huay999, huay789, huay24, huay88)\n- ruay + numbers (ruay99, ruay888, ruay77)\n- หวย + numbers or tld\n- Any domain with both numbers AND หวย/ล็อต/lotto/huay = GAMBLING\n\n═══════════════════════════════════\n🧩 PATTERN MATCHING — BLOCK IF ANY\n═══════════════════════════════════\nGAMBLING PATTERNS:\n- xxxbet, betxxx, xxxslot, slotxxx, xxxcasino, pgxxx, jokerxxx\n- brand + numbers (ufabet999, betflix789, pgslot123, joker456, lotto432)\n- abbreviations (ufa, pg, joker, slot) + numbers = GAMBLING\n- auto/wallet/deposit/withdraw + slot/bet/casino = GAMBLING\n- live22, live33, live44, live + numbers\n- Any domain ending in ...slot, ...bet, ...casino, ...lotto, ...huay, ...ruay\n\nSUSPICIOUS TLDs (auto-lower threshold to 10%):\n.xyz .top .vip .cc .tk .ml .ga .cf .gq .fun .club .shop .store .online .site .icu .buzz .rest .live .bet .casino .win\n\n═══════════════════════════════════\n⚡ RULES — FOLLOW STRICTLY\n═══════════════════════════════════\n- Be EXTREMELY aggressive. When in doubt → ALWAYS block.\n- Analyze: domain name, TLD, subdomains, path, query string.\n- Thai gambling = brand + numbers (ufabet999, lotto432, pgslot123) → BLOCK\n- Abbreviations (ufa, pg, joker, slot, lotto) + numbers → BLOCK\n- slot/bet/casino/lotto/huay anywhere in domain → 90%+ confidence → BLOCK\n- SUSPICIOUS TLD + any gambling keyword → BLOCK instantly (confidence 95)\n- TWO OR MORE suspicious indicators → BLOCK\n- Confidence > 25% for harmful → block (MEGA aggressive)\n- False positive is MUCH better than false negative\n- NEVER block these safe domains: x.com, twitter.com, tesla.com, spacex.com, openai.com, github.com, youtube.com, google.com, microsoft.com, apple.com, amazon.com, wikipedia.org, reddit.com\n- Do NOT block \"sex\" as part of larger word (spacex, sussex, essex, sextant)\n- NEVER explain reasoning in prose → JSON only. NEVER output markdown. NEVER output code fences.\n\n═══════════════════════════════════\n📄 CONTENT ANALYSIS MODE (when PAGE TEXT is provided)\n═══════════════════════════════════\n- You are analyzing ACTUAL webpage text content, not just a domain name.\n- Focus 80% on CONTENT, 20% on domain name.\n- Scan for: sexual descriptions, adult service offers, gambling invitations, lottery sales, explicit anatomy references in sexual context.\n- Innocent words in non-sexual context → SAFE (e.g. medical article about breast cancer = SAFE).\n- News article mentioning gambling arrests → SAFE (context is negative/news).\n- Educational content about sex education → SAFE.\n- Any page directly offering porn, gambling, lottery, prostitution, or explicit sexual content → BLOCK.\n- Thai forums/discussions with casual sexual slang → BLOCK.\n- Any page whose PRIMARY purpose is adult/gambling → BLOCK.\n- When text is empty or too short → rely on domain name only.\n\nOUTPUT: Valid JSON only.\n{\n \"blocked\": boolean,\n \"category\": \"gambling\" | \"adult\" | \"safe\",\n \"confidence\": 0-100,\n \"reason\": \"Short concise English explanation (under 15 words)\"\n}\nCRITICAL: Output all fields including \"reason\" strictly in ENGLISH. Never output Thai characters.`;\n\n// ─── Content-based scan: sends actual PAGE TEXT to AI ──────────\nexport async function checkContentWithAI(url, pageText, apiKey, provider = \"deepseek\") {\n const domain = extractDomain(url);\n const userMsg = `URL: ${domain}\\n\\nPAGE TEXT (first 2500 chars):\\n${pageText.substring(0, 2500)}`;\n\n switch (provider) {\n case \"claude\":\n return await callClaude(userMsg, apiKey);\n case \"deepseek\":\n return await callDeepSeek(userMsg, apiKey);\n case \"openai\":\n return await callOpenAI(userMsg, apiKey);\n default:\n return await callDeepSeek(userMsg, apiKey);\n }\n}\n\n// ─── Domain-only scan: original behavior ────────────────────────\nexport async function checkWithAI(url, apiKey, provider = \"deepseek\") {\n const domain = extractDomain(url);\n const userMsg = `Analyze this URL: ${domain}`;\n\n switch (provider) {\n case \"claude\":\n return await callClaude(userMsg, apiKey);\n case \"deepseek\":\n return await callDeepSeek(userMsg, apiKey);\n case \"openai\":\n return await callOpenAI(userMsg, apiKey);\n default:\n return await callDeepSeek(userMsg, apiKey);\n }\n}\n\nasync function callClaude(msg, apiKey) {\n const res = await fetch(\"https://api.anthropic.com/v1/messages\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": apiKey,\n \"anthropic-version\": \"2023-06-01\"\n },\n body: JSON.stringify({\n model: \"claude-3-5-haiku-20241022\",\n max_tokens: 500,\n system: SYSTEM_PROMPT,\n messages: [{ role: \"user\", content: msg }]\n })\n });\n\n if (!res.ok) {\n throw new Error(`Claude API error: ${res.status} ${res.statusText}`);\n }\n\n const data = await res.json();\n const content = data?.content?.[0]?.text || \"\";\n return parseAIResponse(content, msg);\n}\n\nasync function callDeepSeek(msg, apiKey) {\n // Try deepseek-chat (standard DeepSeek API model)\n let res = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`\n },\n body: JSON.stringify({\n model: \"deepseek-chat\",\n max_tokens: 500,\n messages: [\n { role: \"system\", content: SYSTEM_PROMPT },\n { role: \"user\", content: msg }\n ]\n })\n });\n\n if (!res.ok) {\n // Retry with deepseek-v4-pro if deepseek-chat returned error\n res = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`\n },\n body: JSON.stringify({\n model: \"deepseek-v4-pro\",\n max_tokens: 500,\n messages: [\n { role: \"system\", content: SYSTEM_PROMPT },\n { role: \"user\", content: msg }\n ]\n })\n });\n }\n\n if (!res.ok) {\n throw new Error(`DeepSeek API error: ${res.status} ${res.statusText}`);\n }\n\n const data = await res.json();\n const content = data?.choices?.[0]?.message?.content || \"\";\n return parseAIResponse(content, msg);\n}\n\nasync function callOpenAI(msg, apiKey) {\n const res = await fetch(\"https://api.openai.com/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`\n },\n body: JSON.stringify({\n model: \"gpt-4o-mini\",\n max_tokens: 500,\n messages: [\n { role: \"system\", content: SYSTEM_PROMPT },\n { role: \"user\", content: msg }\n ]\n })\n });\n\n if (!res.ok) {\n throw new Error(`OpenAI API error: ${res.status} ${res.statusText}`);\n }\n\n const data = await res.json();\n const content = data?.choices?.[0]?.message?.content || \"\";\n return parseAIResponse(content, msg);\n}\n\n// confidence ขั้นต่ำที่จะยอมบล็อก (สอดคล้องกับ prompt: harmful > 25% → block)\nconst AI_BLOCK_THRESHOLD = 25;\n\nfunction parseAIResponse(text, rawUserMsg = \"\") {\n // If response is empty or null, perform heuristic keyword inspection on rawUserMsg\n if (!text || typeof text !== \"string\" || !text.trim()) {\n const lowerMsg = (rawUserMsg || \"\").toLowerCase();\n const isGambling = lowerMsg.includes(\"gambling\") || lowerMsg.includes(\"พนัน\") || lowerMsg.includes(\"คาสิโน\")\n || lowerMsg.includes(\"สล็อต\") || lowerMsg.includes(\"slot\") || lowerMsg.includes(\"bet\") || lowerMsg.includes(\"ufa\")\n || lowerMsg.includes(\"tako\") || lowerMsg.includes(\"เครดิตฟรี\") || lowerMsg.includes(\"ฝากถอน\") || lowerMsg.includes(\"บาคาร่า\");\n const isAdult = lowerMsg.includes(\"adult\") || lowerMsg.includes(\"porn\") || lowerMsg.includes(\"โป๊\") || lowerMsg.includes(\"18+\") || lowerMsg.includes(\"xxx\") || lowerMsg.includes(\"เย็ด\") || lowerMsg.includes(\"หี\") || lowerMsg.includes(\"ควย\");\n\n if (isGambling || isAdult) {\n return {\n blocked: true,\n category: isGambling ? \"gambling\" : \"adult\",\n confidence: 90,\n reason: \"Detected forbidden keywords (Heuristic Fallback)\",\n source: \"heuristic\"\n };\n }\n\n return {\n blocked: false,\n category: \"safe\",\n confidence: 0,\n reason: \"Empty response from AI (Passed Heuristic Check)\",\n source: \"ai\"\n };\n }\n\n try {\n // 1. Extract first JSON object { ... } via Regex\n const jsonMatch = text.match(/\\{[\\s\\S]*?\\}/);\n const jsonStr = jsonMatch ? jsonMatch[0] : text;\n const parsed = JSON.parse(jsonStr);\n\n let confidence = Number(parsed.confidence);\n if (!Number.isFinite(confidence)) confidence = parsed.blocked ? 100 : 0;\n confidence = Math.max(0, Math.min(100, confidence));\n\n const category = (parsed.category === \"adult\" || parsed.category === \"gambling\")\n ? parsed.category : \"safe\";\n const blocked = (parsed.blocked === true || String(parsed.blocked).toLowerCase() === \"true\")\n && category !== \"safe\"\n && confidence >= AI_BLOCK_THRESHOLD;\n\n return {\n blocked,\n category: blocked ? category : \"safe\",\n confidence,\n reason: parsed.reason || \"Evaluated by AI\",\n source: \"ai\"\n };\n } catch (err) {\n // 2. Fallback: Scan raw response text & message for block keywords if JSON parse failed\n const lowerText = (text + \" \" + rawUserMsg).toLowerCase();\n const isGambling = lowerText.includes(\"gambling\") || lowerText.includes(\"พนัน\") || lowerText.includes(\"คาสิโน\") || lowerText.includes(\"สล็อต\") || lowerText.includes(\"tako\") || lowerText.includes(\"ufa\") || lowerText.includes(\"bet\");\n const isAdult = lowerText.includes(\"adult\") || lowerText.includes(\"porn\") || lowerText.includes(\"โป๊\") || lowerText.includes(\"18+\");\n const isBlocked = lowerText.includes('\"blocked\": true') || lowerText.includes('\"blocked\":true') || lowerText.includes(\"blocked\");\n\n if ((isGambling || isAdult) && isBlocked) {\n return {\n blocked: true,\n category: isGambling ? \"gambling\" : \"adult\",\n confidence: 90,\n reason: \"AI detected harmful content\",\n source: \"ai\"\n };\n }\n\n return {\n blocked: false,\n category: \"safe\",\n confidence: 0,\n reason: \"No block instruction in AI response\",\n source: \"ai\"\n };\n }\n}\n\nfunction extractDomain(url) {\n try {\n return new URL(url.startsWith(\"http\") ? url : `https://${url}`).hostname;\n } catch {\n return url;\n }\n}\n"}}}}