Hana Celeste commited on
Commit
715605d
·
verified ·
1 Parent(s): ca18ca2

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +97 -174
main.py CHANGED
@@ -1,199 +1,122 @@
1
- import time
2
  import asyncio
3
- import aiohttp
 
4
  from fastapi import FastAPI
5
  from playwright.async_api import async_playwright
6
- from cachetools import TTLCache
7
-
8
- # ================= CONFIG =================
9
- IMGBB_API_KEY = "093dee93ec19418d26cc76f40e053725"
10
- ENKA_URL = "https://enka.network/u/{uid}/"
11
-
12
- # cache RAM: 100 UID, sống 60s
13
- INFO_CACHE = TTLCache(maxsize=100, ttl=60)
14
 
15
- # playwright globals
16
- playwright = None
17
- browser = None
18
- context = None
19
 
20
- app = FastAPI(title="Enka API Pro Max")
21
-
22
-
23
- # ================= STARTUP / SHUTDOWN =================
24
- @app.on_event("startup")
25
- async def startup():
26
- global playwright, browser, context
27
-
28
- playwright = await async_playwright().start()
29
- browser = await playwright.chromium.launch(
30
- headless=True,
31
- args=[
32
- "--no-sandbox",
33
- "--disable-dev-shm-usage",
34
- "--disable-gpu",
35
- "--disable-background-networking",
36
- "--disable-extensions",
37
- "--disable-sync",
38
- "--disable-default-apps",
39
- ]
40
- )
41
 
 
 
 
 
 
 
 
 
 
42
  context = await browser.new_context(
43
- viewport={"width": 1280, "height": 800},
44
- user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
45
  )
46
-
47
-
48
- @app.on_event("shutdown")
49
- async def shutdown():
50
- await context.close()
51
- await browser.close()
52
- await playwright.stop()
53
-
54
-
55
- # ================= UTILS =================
56
- async def new_page(fast_mode=True):
57
  page = await context.new_page()
58
 
59
- if fast_mode:
60
- await page.route(
61
- "**/*",
62
- lambda route: route.abort()
63
- if route.request.resource_type in [
64
- "media", "font", "stylesheet", "other"
65
- ]
66
- else route.continue_()
67
- )
 
 
 
 
 
 
 
68
 
69
- return page
70
-
71
-
72
- # ================= /INFO =================
73
  @app.get("/info")
74
  async def info(uid: str):
75
- if uid in INFO_CACHE:
76
- return {"cached": True, **INFO_CACHE[uid]}
77
-
78
- start = time.time()
79
- page = await new_page(fast_mode=True)
80
-
81
  try:
82
- await page.goto(
83
- ENKA_URL.format(uid=uid),
84
- wait_until="domcontentloaded",
85
- timeout=30000
86
- )
87
-
88
  data = await page.evaluate("""
89
- () => {
90
- const q = s => document.querySelector(s);
91
- const bg = el => {
92
- const m = el?.style?.backgroundImage?.match(/url\\(["']?(.*?)["']?\\)/);
93
- return m ? "https://enka.network" + m[1] : null;
94
- };
95
-
96
- const arText = q('.ar')?.innerText || "";
97
-
98
- return {
99
- player: {
100
- name: q('.details h1')?.innerText || null,
101
- signature: q('.signature')?.innerText || null,
102
- avatar: q('.avatar-icon img')?.src
103
- ? "https://enka.network" + q('.avatar-icon img').getAttribute("src")
104
- : null,
105
- ar: arText.match(/AR\\s*(\\d+)/)?.[1] || null,
106
- wl: arText.match(/WL\\s*(\\d+)/)?.[1] || null,
107
- },
108
- characters: [...document.querySelectorAll('.CharacterList .avatar')]
109
- .map(e => {
110
- const f = e.querySelector('figure');
111
- if (!f) return null;
112
- const icon = f.style.backgroundImage
113
- ?.match(/url\\((.*)\\)/)?.[1];
114
- return icon ? {
115
- icon: "https://enka.network" + icon.replace(/['"]/g, ""),
116
- level: e.querySelector('.level')?.innerText || "0"
117
- } : null;
118
- })
119
- .filter(Boolean)
120
- };
121
- }
 
 
 
 
 
 
 
 
 
122
  """)
123
-
124
- await page.close()
125
-
126
- result = {
127
- "uid": uid,
128
- "execution_time": f"{round(time.time() - start, 2)}s",
129
- **data
130
- }
131
-
132
- INFO_CACHE[uid] = result
133
- return result
134
-
135
  except Exception as e:
136
- await page.close()
137
  return {"error": str(e)}
138
 
139
-
140
- # ================= /GEN =================
141
  @app.get("/gen")
142
- async def generate(uid: str):
143
- start = time.time()
144
- page = await new_page(fast_mode=False)
145
-
146
  try:
147
- await page.goto(
148
- ENKA_URL.format(uid=uid),
149
- wait_until="domcontentloaded",
150
- timeout=30000
151
- )
152
 
153
  await page.click('button[data-icon="image"]')
154
- await page.wait_for_selector('.Loda img[src^="blob:"]', timeout=20000)
155
-
156
- img_base64 = await page.evaluate("""
157
- async () => {
158
- const img = document.querySelector('.Loda img');
159
- const res = await fetch(img.src);
160
- const blob = await res.blob();
161
-
162
- return await new Promise(resolve => {
163
- const r = new FileReader();
164
- r.onloadend = () => resolve(r.result.split(',')[1]);
165
- r.readAsDataURL(blob);
166
- });
167
- }
168
- """)
169
-
170
- await page.close()
171
-
172
- async with aiohttp.ClientSession() as session:
173
- async with session.post(
174
- "https://api.imgbb.com/1/upload",
175
- data={
176
- "key": IMGBB_API_KEY,
177
- "image": img_base64
178
- }
179
- ) as r:
180
- js = await r.json()
181
-
182
- return {
183
- "uid": uid,
184
- "execution_time": f"{round(time.time() - start, 2)}s",
185
- "card_url": js.get("data", {}).get("url")
186
- }
187
-
188
  except Exception as e:
189
- await page.close()
190
  return {"error": str(e)}
191
-
192
-
193
- # ================= ROOT =================
194
- @app.get("/")
195
- def home():
196
- return {
197
- "status": "Enka API Pro Max is Live",
198
- "cache_size": len(INFO_CACHE)
199
- }
 
 
1
  import asyncio
2
+ import requests
3
+ import time
4
  from fastapi import FastAPI
5
  from playwright.async_api import async_playwright
 
 
 
 
 
 
 
 
6
 
7
+ app = FastAPI()
 
 
 
8
 
9
+ IMGBB_API_KEY = "093dee93ec19418d26cc76f40e053725"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
+ async def get_page(uid: str, fast_mode=False):
12
+ p = await async_playwright().start()
13
+ browser = await p.chromium.launch(headless=True, args=[
14
+ '--no-sandbox',
15
+ '--disable-setuid-sandbox',
16
+ '--disable-dev-shm-usage',
17
+ '--disable-accelerated-2d-canvas',
18
+ '--disable-gpu'
19
+ ])
20
  context = await browser.new_context(
21
+ viewport={'width': 1280, 'height': 800},
22
+ 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"
23
  )
 
 
 
 
 
 
 
 
 
 
 
24
  page = await context.new_page()
25
 
26
+ # CHẶN TỐI ĐA ĐỂ TĂNG TỐC BĂNG THÔNG
27
+ async def block_waste(route):
28
+ bad_types = ["media", "font", "other"]
29
+ if fast_mode: bad_types.extend(["image", "stylesheet"])
30
+
31
+ if route.request.resource_type in bad_types or "google" in route.request.url:
32
+ await route.abort()
33
+ else:
34
+ await route.continue_()
35
+
36
+ await page.route("**/*", block_waste)
37
+
38
+ url = f"https://enka.network/u/{uid}/"
39
+ # wait_until="commit" là nhanh nhất, bắt đầu ngay khi có phản hồi từ server
40
+ await page.goto(url, wait_until="domcontentloaded", timeout=60000)
41
+ return browser, page
42
 
 
 
 
 
43
  @app.get("/info")
44
  async def info(uid: str):
45
+ start_time = time.time()
46
+ browser, page = await get_page(uid, fast_mode=True)
 
 
 
 
47
  try:
48
+ # Thực hiện Click Namecard song song với việc lấy Data để tiết kiệm thời gian
49
+ await page.click('button[data-icon="cards"]', timeout=3000).catch(lambda e: None)
50
+
 
 
 
51
  data = await page.evaluate("""
52
+ async () => {
53
+ const getPlayer = () => {
54
+ const name = document.querySelector('.details h1')?.innerText;
55
+ const signature = document.querySelector('.signature')?.innerText;
56
+ const arText = document.querySelector('.ar')?.innerText || "";
57
+ const stats = Array.from(document.querySelectorAll('tr.stat')).map(tr => {
58
+ const style = tr.querySelector('td.icon')?.getAttribute('style') || "";
59
+ const iconMatch = style.match(/url\\(["']?(.*?)["']?\\)/);
60
+ return {
61
+ value: tr.querySelector('td:first-child')?.innerText.trim(),
62
+ label: tr.querySelector('td:last-child')?.innerText.trim(),
63
+ icon: iconMatch ? "https://enka.network" + iconMatch[1].replace(/\\\\/g, "") : null
64
+ };
65
+ });
66
+ const ncBg = document.querySelector('.Loda .bg')?.style.backgroundImage;
67
+ const ncMatch = ncBg ? ncBg.match(/url\\(["']?(.*?)["']?\\)/) : null;
68
+
69
+ return {
70
+ name,
71
+ signature,
72
+ ar: arText.match(/AR\\s*(\\d+)/i)?.[1],
73
+ wl: arText.match(/WL\\s*(\\d+)/i)?.[1],
74
+ avatar: "https://enka.network" + document.querySelector('.avatar-icon img')?.getAttribute('src'),
75
+ namecard: ncMatch ? "https://enka.network" + ncMatch[1] : null,
76
+ stats
77
+ };
78
+ };
79
+
80
+ const getChars = () => {
81
+ return Array.from(document.querySelectorAll('.CharacterList .avatar')).map(el => {
82
+ const style = el.querySelector('figure.chara')?.getAttribute('style') || "";
83
+ const path = style.match(/url\\((.*)\\)/)?.[1].replace(/["']/g, "");
84
+ return {
85
+ name: path?.split('_').pop().split('.')[0],
86
+ level: el.querySelector('.level')?.innerText,
87
+ icon: "https://enka.network" + path
88
+ };
89
+ }).filter(i => i.name);
90
+ };
91
+
92
+ return { player: getPlayer(), characters: getChars() };
93
+ }
94
  """)
95
+
96
+ await browser.close()
97
+ return {"execution_time": f"{round(time.time() - start_time, 2)}s", "uid": uid, **data}
 
 
 
 
 
 
 
 
 
98
  except Exception as e:
99
+ if 'browser' in locals(): await browser.close()
100
  return {"error": str(e)}
101
 
 
 
102
  @app.get("/gen")
103
+ async def generate(uid: str, char: str = None):
104
+ start_time = time.time()
105
+ browser, page = await get_page(uid, fast_mode=False)
 
106
  try:
107
+ if char:
108
+ target = char.strip().capitalize()
109
+ await page.click(f"//figure[contains(@style, '{target}')]", timeout=5000).catch(lambda e: None)
110
+ await asyncio.sleep(0.8) # Giảm sleep xuống mức tối thiểu
 
111
 
112
  await page.click('button[data-icon="image"]')
113
+ img_el = await page.wait_for_selector('.Loda img[src^="blob:"]', timeout=15000)
114
+
115
+ img_base64 = await page.evaluate("async () => { const res = await fetch(document.querySelector('.Loda img').src); const blob = await res.blob(); return new Promise(r => { const rd = new FileReader(); rd.onloadend = () => r(rd.result.split(',')[1]); rd.readAsDataURL(blob); }); }")
116
+
117
+ await browser.close()
118
+ res = requests.post("https://api.imgbb.com/1/upload", data={"key": IMGBB_API_KEY, "image": img_base64}, timeout=15)
119
+ return {"execution_time": f"{round(time.time() - start_time, 2)}s", "uid": uid, "card_url": res.json().get('data', {}).get('url')}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  except Exception as e:
121
+ if 'browser' in locals(): await browser.close()
122
  return {"error": str(e)}