Hana Celeste commited on
Commit
d35b63b
·
verified ·
1 Parent(s): 66f26de

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +159 -123
main.py CHANGED
@@ -1,163 +1,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=['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'])
14
  context = await browser.new_context(
15
- viewport={'width': 1280, 'height': 800},
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
  if fast_mode:
21
- # Chặn bớt tài nguyên nhưng vẫn để lại Images vì cần link Namecard và Stats icon
22
- await page.route("**/*", lambda route: route.abort() if route.request.resource_type in ["media", "font"] else route.continue_())
 
 
 
 
 
 
 
 
23
 
24
- url = f"https://enka.network/u/{uid}/"
25
- await page.goto(url, wait_until="networkidle", timeout=60000)
26
- return browser, page
27
 
 
28
  @app.get("/info")
29
  async def info(uid: str):
30
- start_time = time.time()
31
- browser, page = await get_page(uid, fast_mode=True)
 
 
 
 
32
  try:
33
- # BƯỚC MỚI: Click lấy Namecard
34
- namecard_url = None
35
- try:
36
- # Tìm nút Danh Thiếp qua data-icon="cards"
37
- card_btn = 'button[data-icon="cards"]'
38
- await page.wait_for_selector(card_btn, timeout=5000)
39
- await page.click(card_btn)
40
-
41
- # Đợi panel Loda hiện lên
42
- namecard_selector = '.Loda .bg'
43
- await page.wait_for_selector(namecard_selector, timeout=5000)
44
-
45
- # Lấy link ảnh từ background-image
46
- namecard_url = await page.evaluate("""
47
- () => {
48
- const bgDiv = document.querySelector('.Loda .bg');
49
- if (!bgDiv) return null;
50
- const style = bgDiv.style.backgroundImage;
51
- const match = style.match(/url\\(["']?(.*?)["']?\\)/);
52
- return match ? "https://enka.network" + match[1] : null;
53
- }
54
- """)
55
-
56
- # Đóng panel để tránh block UI (không bắt buộc nhưng nên làm)
57
- await page.keyboard.press("Escape")
58
- except Exception as e:
59
- print(f"Lỗi lấy Namecard: {e}")
60
-
61
- # LẤY CÁC THÔNG TIN CÒN LẠI
62
  data = await page.evaluate("""
63
- () => {
64
- const name = document.querySelector('.details h1')?.innerText;
65
- const signature = document.querySelector('.signature')?.innerText;
66
- const avatar_img = document.querySelector('.avatar-icon img')?.getAttribute('src');
67
-
68
- const arContainer = document.querySelector('.ar');
69
- let ar = null, wl = null;
70
- if (arContainer) {
71
- const text = arContainer.innerText;
72
- const arMatch = text.match(/AR\\s*(\\d+)/i);
73
- const wlMatch = text.match(/WL\\s*(\\d+)/i);
74
- ar = arMatch ? arMatch[1] : null;
75
- wl = wlMatch ? wlMatch[1] : null;
76
- }
77
 
78
- const stats = Array.from(document.querySelectorAll('tr.stat')).map(tr => {
79
- const val = tr.querySelector('td:first-child')?.innerText.trim();
80
- const label = tr.querySelector('td:last-child')?.innerText.trim();
81
- const iconTd = tr.querySelector('td.icon');
82
- let iconUrl = null;
83
- if (iconTd) {
84
- const style = iconTd.getAttribute('style');
85
- const match = style.match(/url\\(["']?(.*?)["']?\\)/);
86
- if (match && match[1]) iconUrl = "https://enka.network" + match[1].replace(/\\\\/g, "");
87
- }
88
- return { value: val, label: label, icon: iconUrl };
89
- });
90
-
91
- const characters = Array.from(document.querySelectorAll('.CharacterList .avatar')).map(el => {
92
- const figure = el.querySelector('figure.chara');
93
- if (!figure) return null;
94
- const style = figure.getAttribute('style');
95
- const icon_path = style.match(/url\\((.*)\\)/)[1].replace(/["']/g, "");
96
- const level = el.querySelector('.level')?.innerText || "0";
97
- const charName = icon_path.split('_').pop().split('.')[0];
98
- return { name: charName, level, icon: "https://enka.network" + icon_path };
99
- }).filter(i => i !== null);
100
-
101
- return {
102
- player: { name, ar, wl, signature, avatar: avatar_img ? "https://enka.network" + avatar_img : null, stats },
103
- characters: characters
104
- };
105
- }
106
  """)
107
-
108
- await browser.close()
109
- # Gộp Namecard vào JSON kết quả
110
- data['player']['namecard'] = namecard_url
111
-
112
- return {
113
- "execution_time": f"{round(time.time() - start_time, 2)}s",
114
  "uid": uid,
 
115
  **data
116
  }
 
 
 
 
117
  except Exception as e:
118
- if 'browser' in locals(): await browser.close()
119
  return {"error": str(e)}
120
 
 
 
121
  @app.get("/gen")
122
- async def generate(uid: str, char: str = None):
123
- start_time = time.time()
124
- browser, page = await get_page(uid, fast_mode=False)
 
125
  try:
126
- if char:
127
- formatted_char = char.strip().capitalize()
128
- char_selector = f"//div[contains(@class, 'avatar')]//figure[contains(@style, '{formatted_char}')]"
129
- try:
130
- await page.wait_for_selector(char_selector, timeout=5000)
131
- await page.click(char_selector)
132
- await asyncio.sleep(1)
133
- except: pass
134
 
135
  await page.click('button[data-icon="image"]')
136
  await page.wait_for_selector('.Loda img[src^="blob:"]', timeout=20000)
137
-
138
  img_base64 = await page.evaluate("""
139
- async () => {
140
- const img = document.querySelector('.Loda img[src^="blob:"]');
141
- const res = await fetch(img.src);
142
- const blob = await res.blob();
143
- return new Promise(r => {
144
- const reader = new FileReader();
145
- reader.onloadend = () => r(reader.result.split(',')[1]);
146
- reader.readAsDataURL(blob);
147
- });
148
- }
 
149
  """)
150
- await browser.close()
151
- res = requests.post("https://api.imgbb.com/1/upload", data={"key": IMGBB_API_KEY, "image": img_base64})
 
 
 
 
 
 
 
 
 
 
 
152
  return {
153
- "execution_time": f"{round(time.time() - start_time, 2)}s",
154
  "uid": uid,
155
- "card_url": res.json().get('data', {}).get('url')
 
156
  }
 
157
  except Exception as e:
158
- if 'browser' in locals(): await browser.close()
159
  return {"error": str(e)}
160
 
 
 
161
  @app.get("/")
162
  def home():
163
- return {"status": "Enka API Pro Max is Live"}
 
 
 
 
 
 
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
+ }