Hana Celeste commited on
Update main.py
Browse files
main.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
| 1 |
import asyncio
|
| 2 |
-
import os
|
| 3 |
import requests
|
| 4 |
from fastapi import FastAPI
|
| 5 |
from playwright.async_api import async_playwright
|
|
@@ -8,92 +7,117 @@ app = FastAPI()
|
|
| 8 |
|
| 9 |
IMGBB_API_KEY = "093dee93ec19418d26cc76f40e053725"
|
| 10 |
|
| 11 |
-
|
| 12 |
-
PROXY_CONFIG = {
|
| 13 |
-
"server": "http://proxy.scrapingant.com:8080",
|
| 14 |
-
"username": "scrapingant&proxy_type=residential&proxy_country=VN&browser=false",
|
| 15 |
-
"password": "20e836f16c914c2f816d9d4ab53f83b0"
|
| 16 |
-
}
|
| 17 |
-
|
| 18 |
-
async def run_logic(uid: str):
|
| 19 |
async with async_playwright() as p:
|
| 20 |
-
|
|
|
|
| 21 |
context = await browser.new_context(
|
| 22 |
-
|
| 23 |
-
ignore_https_errors=True,
|
| 24 |
-
viewport={'width': 1280, 'height': 1500},
|
| 25 |
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
| 26 |
)
|
| 27 |
page = await context.new_page()
|
| 28 |
-
|
| 29 |
-
# CHẶN QUẢNG CÁO & TRACKER ĐỂ TIẾT KIỆM PROXY
|
| 30 |
-
async def block_aggressively(route):
|
| 31 |
-
if route.request.resource_type in ["image", "media", "font"] and "enka.network" not in route.request.url:
|
| 32 |
-
await route.abort()
|
| 33 |
-
elif any(x in route.request.url for x in ["google-analytics", "doubleclick", "playwire", "ads"]):
|
| 34 |
-
await route.abort()
|
| 35 |
-
else:
|
| 36 |
-
await route.continue_()
|
| 37 |
-
|
| 38 |
-
await page.route("**/*", block_aggressively)
|
| 39 |
|
| 40 |
try:
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
# Đợi hàng nhân vật xuất hiện
|
| 45 |
-
char_row = "table.custom-table tbody tr.pointer"
|
| 46 |
-
await page.wait_for_selector(char_row, timeout=60000)
|
| 47 |
-
|
| 48 |
-
# Click và đợi bảng render
|
| 49 |
-
await page.click(char_row)
|
| 50 |
-
|
| 51 |
-
# Đợi nút Download - Akasha render card khá lâu nên phải kiên nhẫn
|
| 52 |
-
dl_btn = 'button.dl-button'
|
| 53 |
-
await page.wait_for_selector(dl_btn, timeout=60000)
|
| 54 |
-
await asyncio.sleep(8) # Chờ card vẽ xong hoàn toàn
|
| 55 |
|
| 56 |
-
#
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
-
#
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
|
| 73 |
await browser.close()
|
| 74 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
|
| 76 |
-
|
| 77 |
-
if result.get('success'):
|
| 78 |
-
return result['data']['url']
|
| 79 |
-
raise Exception(f"ImgBB Fail: {result.get('error')}")
|
| 80 |
|
| 81 |
except Exception as e:
|
| 82 |
-
if 'browser' in locals():
|
| 83 |
-
|
| 84 |
-
|
| 85 |
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
@app.get("/")
|
| 98 |
def home():
|
| 99 |
-
return {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import asyncio
|
|
|
|
| 2 |
import requests
|
| 3 |
from fastapi import FastAPI
|
| 4 |
from playwright.async_api import async_playwright
|
|
|
|
| 7 |
|
| 8 |
IMGBB_API_KEY = "093dee93ec19418d26cc76f40e053725"
|
| 9 |
|
| 10 |
+
async def get_enka_data(uid: str, char_name: str = None):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
async with async_playwright() as p:
|
| 12 |
+
# Khởi tạo trình duyệt
|
| 13 |
+
browser = await p.chromium.launch(headless=True, args=['--no-sandbox'])
|
| 14 |
context = await browser.new_context(
|
| 15 |
+
viewport={'width': 1920, 'height': 1080},
|
|
|
|
|
|
|
| 16 |
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
| 17 |
)
|
| 18 |
page = await context.new_page()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
try:
|
| 21 |
+
url = f"https://enka.network/u/{uid}/"
|
| 22 |
+
print(f"📡 Truy cập Enka: {url}")
|
| 23 |
+
await page.goto(url, wait_until="networkidle", timeout=60000)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
+
# --- LOGIC CHỌN NHÂN VẬT ---
|
| 26 |
+
if char_name:
|
| 27 |
+
# Tự động viết hoa chữ cái đầu để khớp với URL ảnh của Enka (vd: citlali -> Citlali)
|
| 28 |
+
formatted_name = char_name.capitalize()
|
| 29 |
+
# Tìm avatar dựa trên thuộc tính style chứa link ảnh nhân vật
|
| 30 |
+
char_selector = f"//div[contains(@class, 'avatar')]//figure[contains(@style, '{formatted_name}')]"
|
| 31 |
+
|
| 32 |
+
try:
|
| 33 |
+
await page.wait_for_selector(char_selector, timeout=10000)
|
| 34 |
+
await page.click(char_selector)
|
| 35 |
+
print(f"✅ Đã chọn nhân vật: {formatted_name}")
|
| 36 |
+
# Đợi 2s để Enka render lại thông số nhân vật mới
|
| 37 |
+
await asyncio.sleep(2)
|
| 38 |
+
except:
|
| 39 |
+
print(f"⚠️ Không tìm thấy nhân vật '{formatted_name}', dùng mặc định.")
|
| 40 |
|
| 41 |
+
# --- LOGIC CHỤP CARD ---
|
| 42 |
+
btn_selector = 'button[data-icon="image"]'
|
| 43 |
+
await page.wait_for_selector(btn_selector, timeout=20000)
|
| 44 |
+
await page.click(btn_selector)
|
| 45 |
+
|
| 46 |
+
# Chờ ảnh Blob (Card đã render xong)
|
| 47 |
+
img_selector = '.Loda img[src^="blob:"]'
|
| 48 |
+
await page.wait_for_selector(img_selector, timeout=30000)
|
| 49 |
+
|
| 50 |
+
# Chuyển ảnh từ Blob sang Base64
|
| 51 |
+
img_base64 = await page.evaluate("""
|
| 52 |
+
async () => {
|
| 53 |
+
const img = document.querySelector('.Loda img[src^="blob:"]');
|
| 54 |
+
const res = await fetch(img.src);
|
| 55 |
+
const blob = await res.blob();
|
| 56 |
+
return new Promise(r => {
|
| 57 |
+
const reader = new FileReader();
|
| 58 |
+
reader.onloadend = () => r(reader.result.split(',')[1]);
|
| 59 |
+
reader.readAsDataURL(blob);
|
| 60 |
+
});
|
| 61 |
+
}
|
| 62 |
+
""")
|
| 63 |
|
| 64 |
await browser.close()
|
| 65 |
+
|
| 66 |
+
# --- UPLOAD IMGBB ---
|
| 67 |
+
res = requests.post(
|
| 68 |
+
"https://api.imgbb.com/1/upload",
|
| 69 |
+
data={"key": IMGBB_API_KEY, "image": img_base64}
|
| 70 |
+
)
|
| 71 |
|
| 72 |
+
return res.json().get('data', {}).get('url', "Lỗi Upload")
|
|
|
|
|
|
|
|
|
|
| 73 |
|
| 74 |
except Exception as e:
|
| 75 |
+
if 'browser' in locals():
|
| 76 |
+
await browser.close()
|
| 77 |
+
return f"Error: {str(e)}"
|
| 78 |
|
| 79 |
+
# --- ENDPOINTS ---
|
| 80 |
+
|
| 81 |
+
@app.get("/gen")
|
| 82 |
+
async def generate(uid: str, char: str = None):
|
| 83 |
+
"""
|
| 84 |
+
Endpoint lấy Card nhân vật.
|
| 85 |
+
Sử dụng: /gen?uid=890096220&char=citlali
|
| 86 |
+
"""
|
| 87 |
+
url = await get_enka_data(uid, char)
|
| 88 |
+
return {
|
| 89 |
+
"uid": uid,
|
| 90 |
+
"char_requested": char or "default",
|
| 91 |
+
"card_url": url
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
@app.get("/list")
|
| 95 |
+
async def list_chars(uid: str):
|
| 96 |
+
"""Lấy danh sách tên nhân vật đang hiển thị để Bot dễ gọi"""
|
| 97 |
+
async with async_playwright() as p:
|
| 98 |
+
browser = await p.chromium.launch(headless=True, args=['--no-sandbox'])
|
| 99 |
+
page = await browser.new_page()
|
| 100 |
+
await page.goto(f"https://enka.network/u/{uid}/", wait_until="networkidle")
|
| 101 |
+
|
| 102 |
+
chars = await page.evaluate("""
|
| 103 |
+
() => {
|
| 104 |
+
const items = document.querySelectorAll('.CharacterList .avatar figure.chara');
|
| 105 |
+
return Array.from(items).map(el => {
|
| 106 |
+
const style = el.getAttribute('style');
|
| 107 |
+
const match = style.match(/UI_AvatarIcon_Side_(.*?)\\./);
|
| 108 |
+
return match ? match[1] : null;
|
| 109 |
+
}).filter(n => n !== null);
|
| 110 |
+
}
|
| 111 |
+
""")
|
| 112 |
+
await browser.close()
|
| 113 |
+
return {"uid": uid, "available_characters": chars}
|
| 114 |
|
| 115 |
@app.get("/")
|
| 116 |
def home():
|
| 117 |
+
return {
|
| 118 |
+
"status": "Enka API Ready",
|
| 119 |
+
"usage": {
|
| 120 |
+
"get_card": "/gen?uid=890096220&char=citlali",
|
| 121 |
+
"list_chars": "/list?uid=890096220"
|
| 122 |
+
}
|
| 123 |
+
}
|