kines9661 commited on
Commit
103fad1
·
verified ·
1 Parent(s): ff4ce04

Upload 3 files

Browse files
Files changed (3) hide show
  1. README.md +12 -12
  2. app.py +369 -0
  3. requirements.txt +2 -0
README.md CHANGED
@@ -1,12 +1,12 @@
1
- ---
2
- title: Nao
3
- emoji: 🌖
4
- colorFrom: indigo
5
- colorTo: purple
6
- sdk: gradio
7
- sdk_version: 6.6.0
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
+ ---
2
+ title: oyz8
3
+ emoji: 🎮
4
+ colorFrom: blue
5
+ colorTo: indigo
6
+ sdk: gradio
7
+ sdk_version: "5.15.0"
8
+ app_file: app.py
9
+ pinned: false
10
+ ---
11
+
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,369 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ import os
3
+ import threading
4
+ import time
5
+ import gradio as gr
6
+ from fastapi import FastAPI
7
+ from fastapi.responses import JSONResponse
8
+ from collections import deque
9
+ from datetime import datetime
10
+ import requests
11
+ from urllib.parse import unquote
12
+
13
+ # ============== 环境变量 ==============
14
+ PANEL_NA1_COOKIES = os.environ.get("PANEL_NA1_COOKIES", "")
15
+ TG_BOT_TOKEN = os.environ.get("TG_BOT_TOKEN", "")
16
+ TG_CHAT_ID = os.environ.get("TG_CHAT_ID", "")
17
+ TG_API = os.environ.get("TG_API", "https://api.telegram.org")
18
+ PROJECT_URL = os.environ.get("PROJECT_URL", "")
19
+
20
+ # ============== 共享状态 ==============
21
+ worker_status = {
22
+ "coins": 0, "checks": 0, "status": "waiting", "afk": False,
23
+ "multiplier": 1, "active_users": 0, "session_coins": 0,
24
+ "coins_per_minute": 1, "initial_coins": 0, "napoints": 0,
25
+ "start_time": None, "keepalive": None
26
+ }
27
+ log_queue = deque(maxlen=100)
28
+
29
+ def add_log(msg):
30
+ ts = datetime.now().strftime("%H:%M:%S")
31
+ line = f"[{ts}] {msg}"
32
+ print(line)
33
+ log_queue.append(line)
34
+
35
+ # ============== 保活 ==============
36
+ def add_keepalive_task():
37
+ if not PROJECT_URL:
38
+ add_log("[KEEP] ⏭️ 未设置 PROJECT_URL")
39
+ worker_status['keepalive'] = 'skipped'
40
+ return
41
+ add_log(f"[KEEP] 🔄 添加保活...")
42
+ try:
43
+ r = requests.post("https://trans.ct8.pl/add-url", json={"url": PROJECT_URL}, timeout=30)
44
+ worker_status['keepalive'] = 'success' if r.status_code == 200 else 'failed'
45
+ add_log(f"[KEEP] {'✅ 成功' if r.status_code == 200 else '❌ 失败'}")
46
+ except Exception as e:
47
+ worker_status['keepalive'] = 'failed'
48
+ add_log(f"[KEEP] ❌ {e}")
49
+
50
+ # ============== Worker ==============
51
+ class AFKWorker:
52
+ def __init__(self):
53
+ self.session = requests.Session()
54
+ self.base = "https://panel.na1.host"
55
+ self.xsrf = None
56
+ self.coins = self.initial = 0
57
+ self._init_session()
58
+
59
+ def _init_session(self):
60
+ self.session.headers.update({
61
+ "User-Agent": "Mozilla/5.0", "Accept": "application/json",
62
+ "Referer": self.base, "Origin": self.base
63
+ })
64
+ for item in PANEL_NA1_COOKIES.split(';'):
65
+ if '=' in item:
66
+ k, v = item.strip().split('=', 1)
67
+ self.session.cookies.set(k.strip(), v.strip(), domain="panel.na1.host")
68
+ if k.strip() == "XSRF-TOKEN":
69
+ self.xsrf = unquote(v.strip())
70
+
71
+ def _h(self):
72
+ return {"X-XSRF-TOKEN": self.xsrf} if self.xsrf else {}
73
+
74
+ def _upd(self, r):
75
+ for c in r.cookies:
76
+ self.session.cookies.set(c.name, c.value, domain="panel.na1.host")
77
+ if c.name == "XSRF-TOKEN": self.xsrf = unquote(c.value)
78
+
79
+ def tg(self, msg):
80
+ if TG_BOT_TOKEN and TG_CHAT_ID:
81
+ try: requests.post(f"{TG_API}/bot{TG_BOT_TOKEN}/sendMessage", data={"chat_id": TG_CHAT_ID, "text": msg}, timeout=10)
82
+ except: pass
83
+
84
+ def login(self):
85
+ try:
86
+ r = self.session.get(f"{self.base}/api/client", headers=self._h(), timeout=30)
87
+ self._upd(r)
88
+ return r.status_code == 200
89
+ except: return False
90
+
91
+ def wallet(self):
92
+ try:
93
+ r = self.session.get(f"{self.base}/api/client/account/wallet", headers=self._h(), timeout=30)
94
+ self._upd(r)
95
+ if r.status_code == 200:
96
+ d = r.json()
97
+ self.coins = d.get('coins', 0)
98
+ worker_status['coins'] = self.coins
99
+ worker_status['napoints'] = d.get('napoints', 0)
100
+ return True
101
+ except: pass
102
+ return False
103
+
104
+ def join(self):
105
+ try:
106
+ r = self.session.post(f"{self.base}/api/client/account/afk/join", headers=self._h(), timeout=30)
107
+ self._upd(r)
108
+ return r.status_code in [200, 409]
109
+ except: return False
110
+
111
+ def status(self):
112
+ try:
113
+ r = self.session.get(f"{self.base}/api/client/account/afk/status", headers=self._h(), timeout=30)
114
+ self._upd(r)
115
+ if r.status_code == 200:
116
+ d = r.json()
117
+ worker_status.update({
118
+ 'active_users': d.get('active_users', 0),
119
+ 'multiplier': d.get('multiplier', 1),
120
+ 'coins_per_minute': d.get('coins_per_minute', 1),
121
+ 'session_coins': d.get('session', {}).get('coins_earned', 0),
122
+ 'afk': True
123
+ })
124
+ return True
125
+ except: pass
126
+ return False
127
+
128
+ def run(self):
129
+ worker_status['start_time'] = time.time()
130
+ add_log("🎮 NA1 AFK Bot 启动中...")
131
+ add_keepalive_task()
132
+
133
+ if not self.login():
134
+ worker_status['status'] = 'cookie_expired'
135
+ add_log("❌ Cookie 已过期")
136
+ self.tg("❌ Cookie 已过期")
137
+ return
138
+ add_log("✅ 登录成功")
139
+
140
+ self.wallet()
141
+ self.initial = worker_status['initial_coins'] = self.coins
142
+ add_log(f"💰 初始: {self.coins}")
143
+
144
+ self.join()
145
+ self.status()
146
+ add_log(f"📊 在线 {worker_status['active_users']} 人 | {worker_status['multiplier']}x")
147
+
148
+ worker_status['status'] = 'running'
149
+ self.tg(f"✅ AFK 启动 | 💰 {self.coins}")
150
+ last_tg = time.time()
151
+
152
+ while worker_status['status'] == 'running':
153
+ time.sleep(60)
154
+ worker_status['checks'] += 1
155
+ n = worker_status['checks']
156
+
157
+ if self.status():
158
+ add_log(f"[{n:03d}] 👥{worker_status['active_users']} ⚡{worker_status['multiplier']}x +{worker_status['session_coins']}")
159
+
160
+ if n % 5 == 0:
161
+ old = self.coins
162
+ self.wallet()
163
+ add_log(f"[SYNC] 💰{self.coins} (+{self.coins-old})")
164
+
165
+ if n % 30 == 0: self.join()
166
+
167
+ if time.time() - last_tg >= 3600:
168
+ last_tg = time.time()
169
+ self.tg(f"📊 {self.coins} (+{self.coins-self.initial})")
170
+
171
+ def auto_start():
172
+ if not PANEL_NA1_COOKIES:
173
+ add_log("❌ 未设置 PANEL_NA1_COOKIES")
174
+ worker_status['status'] = 'no_cookie'
175
+ add_keepalive_task()
176
+ return
177
+ AFKWorker().run()
178
+
179
+ threading.Thread(target=auto_start, daemon=True).start()
180
+
181
+ # ============== FastAPI ==============
182
+ app = FastAPI()
183
+
184
+ @app.get("/")
185
+ def health():
186
+ s = worker_status
187
+ return JSONResponse({
188
+ "status": "ok",
189
+ "coins": s['coins'],
190
+ "earned": s['coins'] - s.get('initial_coins', 0),
191
+ "checks": s['checks'],
192
+ "worker": s['status'],
193
+ "afk": s['afk']
194
+ })
195
+
196
+ # ============== Gradio ==============
197
+ def make_page():
198
+ s = worker_status
199
+ total = s['coins'] - s.get('initial_coins', 0)
200
+
201
+ rt = "--"
202
+ if s.get('start_time'):
203
+ m = (time.time() - s['start_time']) / 60
204
+ rt = f"{m:.0f}分" if m < 60 else f"{m/60:.1f}时"
205
+
206
+ status_map = {
207
+ "running": ("🟢", "运行中", "#10b981", "#ecfdf5"),
208
+ "waiting": ("🟡", "启动中", "#f59e0b", "#fffbeb"),
209
+ "no_cookie": ("🔴", "未配置", "#ef4444", "#fef2f2"),
210
+ "cookie_expired": ("🔴", "已过期", "#ef4444", "#fef2f2"),
211
+ }
212
+ s_icon, s_text, s_color, s_bg = status_map.get(s['status'], ("⚪", s['status'], "#6b7280", "#f3f4f6"))
213
+
214
+ ka_map = {
215
+ "success": ("🛡️", "保活", "#3b82f6", "#eff6ff"),
216
+ "failed": ("⚠️", "失败", "#ef4444", "#fef2f2"),
217
+ "skipped": ("⏭️", "跳过", "#6b7280", "#f3f4f6"),
218
+ None: ("⏳", "等待", "#f59e0b", "#fffbeb"),
219
+ }
220
+ k_icon, k_text, k_color, k_bg = ka_map.get(s.get('keepalive'), ("⏳", "等待", "#f59e0b", "#fffbeb"))
221
+
222
+ logs = "\n".join(log_queue) if log_queue else "⏳ 等待日志..."
223
+
224
+ return f'''
225
+ <div class="container">
226
+ <div class="header">
227
+ <div class="logo">🎮 NA1 AFK</div>
228
+ <div class="badge" style="background:{s_bg};color:{s_color}">{s_icon} {s_text}</div>
229
+ <div class="badge" style="background:{k_bg};color:{k_color}">{k_icon} {k_text}</div>
230
+ <div class="stats-row">
231
+ <span>💰 <b>{s['coins']:,}</b></span>
232
+ <span>📈 <b class="green">+{total:,}</b></span>
233
+ <span>👥 <b>{s.get('active_users', 0)}</b></span>
234
+ <span>⚡ <b>{s.get('multiplier', 1)}x</b></span>
235
+ <span>⏱️ <b>{rt}</b></span>
236
+ </div>
237
+ </div>
238
+
239
+ <div class="main">
240
+ <div class="card logs-card">
241
+ <div class="card-title">📋 日志 <span class="sub">#{s['checks']}</span></div>
242
+ <div class="logs">{logs}</div>
243
+ </div>
244
+
245
+ <div class="sidebar">
246
+ <div class="card">
247
+ <div class="card-title">📊 统计</div>
248
+ <div class="stat-grid">
249
+ <div class="stat-item"><div class="stat-val">{s['coins']:,}</div><div class="stat-label">金币</div></div>
250
+ <div class="stat-item"><div class="stat-val green">+{total:,}</div><div class="stat-label">收益</div></div>
251
+ <div class="stat-item"><div class="stat-val blue">+{s.get('session_coins', 0)}</div><div class="stat-label">会话</div></div>
252
+ <div class="stat-item"><div class="stat-val">{s.get('coins_per_minute', 1)}</div><div class="stat-label">/分钟</div></div>
253
+ </div>
254
+ </div>
255
+
256
+ <div class="card">
257
+ <div class="card-title">⚙️ 环境变量配置</div>
258
+ <div class="config-table">
259
+ <div class="config-row header-row">
260
+ <span class="config-name">变量名</span>
261
+ <span class="config-desc">说明</span>
262
+ </div>
263
+ <div class="config-row">
264
+ <code>PANEL_NA1_COOKIES</code>
265
+ <span>登录 Cookie <b class="req">必填</b></span>
266
+ </div>
267
+ <div class="config-row">
268
+ <code>PROJECT_URL</code>
269
+ <span>保活链接 <b class="opt">可选</b></span>
270
+ </div>
271
+ <div class="config-row">
272
+ <code>TG_BOT_TOKEN</code>
273
+ <span>TG 机器人 <b class="opt">可选</b></span>
274
+ </div>
275
+ <div class="config-row">
276
+ <code>TG_CHAT_ID</code>
277
+ <span>TG 聊天 ID <b class="opt">可选</b></span>
278
+ </div>
279
+ </div>
280
+ </div>
281
+
282
+ <div class="card">
283
+ <div class="card-title">🔗 获取保活链接</div>
284
+ <div class="help-steps">
285
+ <div class="step"><span class="step-num">1</span>打开你的 Space 页面</div>
286
+ <div class="step"><span class="step-num">2</span>点击右上角 <b>⋮</b> 菜单按钮</div>
287
+ <div class="step"><span class="step-num">3</span>选择 <b>Embed this Space</b></div>
288
+ <div class="step"><span class="step-num">4</span>复制 <b>Direct URL</b></div>
289
+ <div class="url-example">
290
+ <code>https://用户名-项目名.hf.space</code>
291
+ </div>
292
+ <div class="tip">
293
+ 💡 设置 <code>PROJECT_URL</code> 环境变量后自动启用保活,防止 Space 休眠
294
+ </div>
295
+ </div>
296
+ </div>
297
+ </div>
298
+ </div>
299
+ </div>'''
300
+
301
+ CSS = """
302
+ /* 隐藏 HuggingFace 顶部栏 */
303
+ #huggingface-space-header { display: none !important; }
304
+ footer { display: none !important; }
305
+
306
+ .gradio-container { background: #f8fafc !important; padding: 0 !important; }
307
+
308
+ .container { max-width: 1100px; margin: 0 auto; padding: 16px; font-family: system-ui, sans-serif; }
309
+
310
+ .header { display: flex; align-items: center; gap: 10px; padding: 12px 16px; background: white; border-radius: 10px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 12px; flex-wrap: wrap; }
311
+ .logo { font-size: 16px; font-weight: 700; color: #1e293b; }
312
+ .badge { display: inline-flex; align-items: center; gap: 5px; padding: 5px 10px; border-radius: 16px; font-size: 12px; font-weight: 600; }
313
+ .stats-row { display: flex; gap: 14px; margin-left: auto; font-size: 13px; color: #64748b; flex-wrap: wrap; }
314
+ .stats-row b { color: #1e293b; }
315
+ .stats-row .green { color: #10b981; }
316
+
317
+ .main { display: grid; grid-template-columns: 1fr 320px; gap: 12px; }
318
+ @media (max-width: 800px) { .main { grid-template-columns: 1fr; } }
319
+
320
+ .card { background: white; border-radius: 10px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); overflow: hidden; }
321
+ .card-title { padding: 10px 14px; font-size: 13px; font-weight: 600; color: #374151; border-bottom: 1px solid #e5e7eb; display: flex; }
322
+ .card-title .sub { margin-left: auto; color: #9ca3af; font-weight: 400; }
323
+
324
+ .logs { padding: 12px; font-family: 'SF Mono', Consolas, monospace; font-size: 11px; line-height: 1.8; color: #475569; background: #f8fafc; height: 500px; overflow-y: auto; white-space: pre-wrap; }
325
+
326
+ .sidebar { display: flex; flex-direction: column; gap: 12px; }
327
+
328
+ .stat-grid { display: grid; grid-template-columns: 1fr 1fr; }
329
+ .stat-item { padding: 12px; text-align: center; border-bottom: 1px solid #e5e7eb; }
330
+ .stat-item:nth-child(odd) { border-right: 1px solid #e5e7eb; }
331
+ .stat-item:nth-last-child(-n+2) { border-bottom: none; }
332
+ .stat-val { font-size: 20px; font-weight: 700; color: #1e293b; }
333
+ .stat-val.green { color: #10b981; }
334
+ .stat-val.blue { color: #3b82f6; }
335
+ .stat-label { font-size: 10px; color: #9ca3af; text-transform: uppercase; }
336
+
337
+ /* 配置表格 */
338
+ .config-table { padding: 0; }
339
+ .config-row { display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; border-bottom: 1px solid #f1f5f9; font-size: 11px; }
340
+ .config-row:last-child { border-bottom: none; }
341
+ .config-row.header-row { background: #f8fafc; font-weight: 600; color: #64748b; font-size: 10px; }
342
+ .config-row code { background: #f1f5f9; padding: 3px 6px; border-radius: 4px; font-size: 10px; color: #1e293b; font-family: 'SF Mono', Consolas, monospace; }
343
+ .config-row .req { color: #ef4444; font-size: 9px; }
344
+ .config-row .opt { color: #6b7280; font-size: 9px; }
345
+ .config-name { font-weight: 600; }
346
+ .config-desc { color: #64748b; }
347
+
348
+ /* 帮助步骤 */
349
+ .help-steps { padding: 12px; }
350
+ .step { display: flex; align-items: center; gap: 10px; padding: 6px 0; font-size: 12px; color: #475569; }
351
+ .step-num { width: 20px; height: 20px; background: #3b82f6; color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 600; flex-shrink: 0; }
352
+ .step b { color: #1e293b; }
353
+
354
+ .url-example { margin: 10px 0; padding: 8px 12px; background: #f1f5f9; border-radius: 6px; border-left: 3px solid #3b82f6; }
355
+ .url-example code { font-size: 11px; color: #3b82f6; font-family: 'SF Mono', Consolas, monospace; word-break: break-all; }
356
+
357
+ .tip { margin-top: 10px; padding: 10px 12px; background: #fffbeb; border-radius: 6px; font-size: 11px; color: #92400e; line-height: 1.5; }
358
+ .tip code { background: #fef3c7; padding: 2px 5px; border-radius: 3px; font-size: 10px; color: #92400e; }
359
+ """
360
+
361
+ with gr.Blocks(title="NA1 AFK", css=CSS, theme=gr.themes.Soft()) as gradio_app:
362
+ html = gr.HTML(make_page)
363
+ gr.Timer(5).tick(make_page, outputs=html)
364
+
365
+ app = gr.mount_gradio_app(app, gradio_app, path="/na1")
366
+
367
+ if __name__ == "__main__":
368
+ import uvicorn
369
+ uvicorn.run(app, host="0.0.0.0", port=7860)
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ gradio>=5.0.0
2
+ requests>=2.28.0