go / app.py
Tanzai2's picture
Update app.py
ca7ae35 verified
Raw
History Blame
13.7 kB
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 "<h1>index.html not found</h1>"
@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)}"}