import o import io import json import urllib.parse import requests from fastapi import FastAPI, File, UploadFile, Form, Request, BackgroundTasks from fastapi.responses import HTMLResponse from PIL import Image import imagehash from playwright.sync_api import sync_playwright app = FastAPI() DATABASE_DIR = "scam_database" VOTES_FILE = "votes.json" # ฟังก์ชันโหลดข้อมูลโหวตจากไฟล์ JSON def load_votes(): if os.path.exists(VOTES_FILE): try: with open(VOTES_FILE, "r", encoding="utf-8") as f: return json.load(f) except Exception: return {} return {} # ฟังก์ชันบันทึกข้อมูลโหวตลงไฟล์ JSON def save_votes(votes_data): with open(VOTES_FILE, "w", encoding="utf-8") as f: json.dump(votes_data, f, ensure_ascii=False, indent=2) # ฟังก์ชันดึง Hash ของรูปทั้งหมดในฐานข้อมูล def get_database_hashes(): hashes = {} if not os.path.exists(DATABASE_DIR): os.makedirs(DATABASE_DIR) for filename in os.listdir(DATABASE_DIR): if filename.lower().endswith(('.png', '.jpg', '.jpeg', '.webp')): filepath = os.path.join(DATABASE_DIR, filename) try: img = Image.open(filepath) h = str(imagehash.phash(img)) hashes[filename] = h except Exception as e: print(f"Error loading {filename}: {e}") return hashes # ฟังก์ชันดึง IP ของผู้ใช้ def get_client_ip(request: Request): forwarded_for = request.headers.get("x-forwarded-for") if forwarded_for: return forwarded_for.split(",")[0].strip() return request.client.host if request.client else "127.0.0.1" # --------------------------------------------------------- # ระบบบอทสแกนรูปภาพจาก TikTok (เพิ่มระบบกันตรวจจับบอท) # --------------------------------------------------------- def run_tiktok_bot_task(keyword: str): db_hashes = get_database_hashes() existing_hash_objs = [imagehash.hex_to_hash(h) for h in db_hashes.values()] MATCH_THRESHOLD = 12 encoded_keyword = urllib.parse.quote(keyword) search_url = f"https://www.tiktok.com/search?q={encoded_keyword}" try: with sync_playwright() as p: # เพิ่ม args ปลอมตัวไม่ให้ TikTok บล็อกบอท browser = p.chromium.launch( headless=True, args=[ "--no-sandbox", "--disable-setuid-sandbox", "--disable-blink-features=AutomationControlled", "--disable-dev-shm-usage" ] ) context = browser.new_context( user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", viewport={"width": 1280, "height": 800}, locale="th-TH" ) page = context.new_page() # ซ่อนสถานะ webdriver page.add_init_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})") print(f"[Bot] กำลังเปิด TikTok ค้นหาคำว่า: '{keyword}'") page.goto(search_url, wait_until="domcontentloaded", timeout=45000) page.wait_for_timeout(5000) # เลื่อนหน้าจอลงหลายๆ ครั้งกระตุ้นให้คลิปโหลด for _ in range(4): page.mouse.wheel(0, 1000) page.wait_for_timeout(2000) # ค้นหาลิงก์คลิปทั้งหมดในหน้าค้นหา video_links = set() links = page.query_selector_all("a") for a in links: href = a.get_attribute("href") if href and ("/video/" in href or "/photo/" in href): if href.startswith("/"): href = "https://www.tiktok.com" + href clean_url = href.split("?")[0] video_links.add(clean_url) video_list = list(video_links) print(f"[Bot] เจอวิดีโอจากหน้าค้นหาทั้งหมด {len(video_list)} คลิป") # สแกนทีละคลิป (สูงสุด 5 คลิปแรก) for v_idx, v_url in enumerate(video_list[:5]): try: print(f"[Bot] ({v_idx+1}/{min(len(video_list), 5)}) กำลังเข้าไปสแกนคอมเมนต์คลิป: {v_url}") page.goto(v_url, wait_until="domcontentloaded", timeout=30000) page.wait_for_timeout(4000) # สกรอลล์ลงไปส่วนคอมเมนต์ page.mouse.wheel(0, 1000) page.wait_for_timeout(2500) # ดึงเฉพาะรูปภาพที่แนบมาในคอมเมนต์ comment_imgs = page.query_selector_all( "[data-e2e='comment-list'] img, [class*='comment'] img, div[class*='Comment'] img" ) print(f"[Bot] พบรูปในคอมเมนต์จำนวน {len(comment_imgs)} รูป") for img_el in comment_imgs: src = img_el.get_attribute("src") if not src or not src.startswith("http"): continue try: resp = requests.get(src, timeout=5) if resp.status_code == 200: img = Image.open(io.BytesIO(resp.content)) # กรองรูปโปรไฟล์ขนาดเล็กออก (รูปในคอมเมนต์มักจะใหญ่กว่า 120px) if img.width < 120 or img.height < 120: continue new_hash_obj = imagehash.phash(img) is_dup = False for existing_h in existing_hash_objs: if (new_hash_obj - existing_h) <= MATCH_THRESHOLD: is_dup = True break if not is_dup: filename = f"bot_{str(new_hash_obj)}.jpg" filepath = os.path.join(DATABASE_DIR, filename) img.convert("RGB").save(filepath, "JPEG") existing_hash_objs.append(new_hash_obj) print(f"[Bot] ✅ บันทึกรูปใหม่จากคอมเมนต์: {filename}") else: print(f"[Bot] ❌ รูปในคอมเมนต์ซ้ำแล้ว - ข้าม") except Exception: continue except Exception as ve: print(f"[Bot Error] เข้าดูคลิปไม่ได้: {ve}") continue browser.close() print(f"[Bot] สแกนเสร็จสิ้นสำหรับคำว่า '{keyword}'") except Exception as e: print(f"[Bot Error] {e}") # Endpoint สำหรับสั่งให้บอทเริ่มทำงาน @app.post("/bot/run") async def trigger_bot( keyword: str = Form(...), background_tasks: BackgroundTasks = BackgroundTasks() ): background_tasks.add_task(run_tiktok_bot_task, keyword) return { "success": True, "message": f"บอทเริ่มค้นหาคำว่า '{keyword}' และสแกนรูปในคอมเมนต์เรียบร้อยแล้ว" } # --------------------------------------------------------- # Endpoint เดิม (100% ตามโค้ดเดิม) # --------------------------------------------------------- @app.get("/", response_class=HTMLResponse) def serve_index(): if os.path.exists("index.html"): with open("index.html", "r", encoding="utf-8") as f: return f.read() return "

index.html not found

" @app.post("/check") async def check_image(file: UploadFile = File(...)): try: contents = await file.read() uploaded_img = Image.open(io.BytesIO(contents)) uploaded_hash_obj = imagehash.phash(uploaded_img) uploaded_hash_str = str(uploaded_hash_obj) db_hashes = get_database_hashes() votes_data = load_votes() MATCH_THRESHOLD = 12 matched_hash_key = None is_in_db = False for filename, db_hash_str in db_hashes.items(): db_hash_obj = imagehash.hex_to_hash(db_hash_str) distance = uploaded_hash_obj - db_hash_obj if distance <= MATCH_THRESHOLD: matched_hash_key = db_hash_str is_in_db = True break if not matched_hash_key: matched_hash_key = uploaded_hash_str image_vote = votes_data.get(matched_hash_key, {"likes": 0, "dislikes": 0, "users": {}}) likes = image_vote.get("likes", 0) dislikes = image_vote.get("dislikes", 0) if likes >= 100: is_scam = False result_text = "ปลอดภัย" detail_text = f"รูปภาพนี้ได้รับการยืนยันว่าปลอดภัยจากผู้ใช้งาน (กดไลก์ {likes} คน)" elif is_in_db: is_scam = True result_text = "มิจแน่นอน100%" detail_text = "พบรูปภาพตรงกับฐานข้อมูลรูปภาพโปรโมทหลอกลวง" else: is_scam = False result_text = "ไม่สามารถตรวจสอบได้" detail_text = "ยังไม่พบรูปภาพนี้ในฐานข้อมูลมิจฉาชีพ" return { "is_scam": is_scam, "result": result_text, "detail": detail_text, "image_hash": matched_hash_key, "likes": likes, "dislikes": dislikes } except Exception as e: return {"error": f"เกิดข้อผิดพลาดในการประมวลผล: {str(e)}"} @app.post("/vote") async def vote_image( request: Request, image_hash: str = Form(...), vote_type: str = Form(...), user_id: str = Form(None) ): try: votes_data = load_votes() if image_hash not in votes_data: votes_data[image_hash] = {"likes": 0, "dislikes": 0, "users": {}} elif "users" not in votes_data[image_hash]: votes_data[image_hash]["users"] = {} client_ip = get_client_ip(request) user_key = f"{client_ip}_{user_id}" if user_id else client_ip if user_key in votes_data[image_hash]["users"]: return { "success": False, "error": "คุณเคยโหวตรูปภาพนี้ไปแล้ว สามารถโหวตได้เพียง 1 ครั้งเท่านั้น" } votes_data[image_hash]["users"][user_key] = vote_type if vote_type == "like": votes_data[image_hash]["likes"] += 1 elif vote_type == "dislike": votes_data[image_hash]["dislikes"] += 1 save_votes(votes_data) likes = votes_data[image_hash]["likes"] dislikes = votes_data[image_hash]["dislikes"] db_hashes = get_database_hashes() is_in_db = image_hash in db_hashes.values() if likes >= 100: is_scam = False result_text = "ปลอดภัย" detail_text = f"รูปภาพนี้ได้รับการยืนยันว่าปลอดภัยจากผู้ใช้งาน (กดไลก์ {likes} คน)" elif is_in_db: is_scam = True result_text = "มิจแน่นอน100%" detail_text = "พบรูปภาพตรงกับฐานข้อมูลรูปภาพโปรโมทหลอกลวง" else: is_scam = False result_text = "ไม่สามารถตรวจสอบได้" detail_text = "ยังไม่พบรูปภาพนี้ในฐานข้อมูลมิจฉาชีพ" return { "success": True, "likes": likes, "dislikes": dislikes, "is_scam": is_scam, "result": result_text, "detail": detail_text } except Exception as e: return {"error": f"เกิดข้อผิดพลาดในการโหวต: {str(e)}"}