Upload webapp.txt
Browse files- webapp.txt +1030 -0
webapp.txt
ADDED
|
@@ -0,0 +1,1030 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="zh-CN">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
|
| 6 |
+
<title>Nine7频道助手</title>
|
| 7 |
+
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
| 8 |
+
<style>
|
| 9 |
+
:root {
|
| 10 |
+
--bg: var(--tg-theme-bg-color, #1a1a2e);
|
| 11 |
+
--card: var(--tg-theme-secondary-bg-color, #16213e);
|
| 12 |
+
--text: var(--tg-theme-text-color, #eaeaea);
|
| 13 |
+
--hint: var(--tg-theme-hint-color, #8a8a9a);
|
| 14 |
+
--accent: var(--tg-theme-button-color, #0f3460);
|
| 15 |
+
--btn-text: var(--tg-theme-button-text-color, #ffffff);
|
| 16 |
+
--link: var(--tg-theme-link-color, #e94560);
|
| 17 |
+
--danger: #e94560;
|
| 18 |
+
--success: #27ae60;
|
| 19 |
+
--radius: 14px;
|
| 20 |
+
--shadow: 0 2px 16px rgba(0,0,0,.18);
|
| 21 |
+
}
|
| 22 |
+
* { margin:0; padding:0; box-sizing:border-box; }
|
| 23 |
+
body { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; background:var(--bg); color:var(--text); min-height:100vh; padding-bottom:80px; overflow-x:hidden; }
|
| 24 |
+
.header { background:linear-gradient(135deg,#0f3460 0%,#533483 50%,#e94560 100%); padding:20px 20px 24px; text-align:center; position:relative; overflow:hidden; }
|
| 25 |
+
.header::after { content:''; position:absolute; bottom:-20px; left:-10%; right:-10%; height:40px; background:var(--bg); border-radius:50% 50% 0 0; }
|
| 26 |
+
.header h1 { font-size:20px; color:#fff; font-weight:700; }
|
| 27 |
+
.header p { font-size:12px; color:rgba(255,255,255,.7); margin-top:4px; }
|
| 28 |
+
.header-content { display:flex; align-items:center; justify-content:center; gap:14px; }
|
| 29 |
+
.avatar { width:48px; height:48px; border-radius:50%; border:2px solid rgba(255,255,255,.3); object-fit:cover; }
|
| 30 |
+
.avatar-placeholder { width:48px; height:48px; border-radius:50%; border:2px solid rgba(255,255,255,.3); background:rgba(255,255,255,.15); display:flex; align-items:center; justify-content:center; font-size:22px; }
|
| 31 |
+
.header-text { text-align:left; }
|
| 32 |
+
.header-text h1 { font-size:18px; }
|
| 33 |
+
.header-text p { font-size:11px; margin-top:2px; }
|
| 34 |
+
.header-greeting { font-size:13px; color:rgba(255,255,255,.85); margin-top:2px; font-weight:500; }
|
| 35 |
+
.bottom-nav { position:fixed; bottom:0; left:0; right:0; display:flex; background:var(--card); border-top:1px solid rgba(255,255,255,.06); z-index:100; padding-bottom:env(safe-area-inset-bottom); }
|
| 36 |
+
.nav-item { flex:1; text-align:center; padding:8px 0 6px; cursor:pointer; transition:all .2s; -webkit-tap-highlight-color:transparent; }
|
| 37 |
+
.nav-item .icon { font-size:20px; }
|
| 38 |
+
.nav-item .label { font-size:10px; color:var(--hint); margin-top:2px; }
|
| 39 |
+
.nav-item.active .label { color:var(--link); font-weight:600; }
|
| 40 |
+
.nav-item.active .icon { transform:scale(1.15); }
|
| 41 |
+
.page { display:none; padding:16px; animation:fadeUp .3s; }
|
| 42 |
+
.page.active { display:block; }
|
| 43 |
+
@keyframes fadeUp { from{opacity:0;transform:translateY(12px)} to{opacity:1;transform:translateY(0)} }
|
| 44 |
+
.card { background:var(--card); border-radius:var(--radius); padding:16px; margin-bottom:12px; box-shadow:var(--shadow); position:relative; border:1px solid rgba(255,255,255,.04); }
|
| 45 |
+
.card-title { font-size:14px; font-weight:600; margin-bottom:8px; display:flex; align-items:center; gap:8px; }
|
| 46 |
+
.card-body { font-size:13px; color:var(--hint); line-height:1.7; }
|
| 47 |
+
.card-body code { background:rgba(255,255,255,.08); padding:2px 6px; border-radius:4px; font-size:12px; color:var(--link); }
|
| 48 |
+
.card-body .ch-name { color:var(--text); font-weight:600; font-size:12px; }
|
| 49 |
+
.card-actions { display:flex; gap:8px; margin-top:12px; flex-wrap:wrap; }
|
| 50 |
+
.stat-row { display:flex; justify-content:space-between; padding:8px 0; border-bottom:1px solid rgba(255,255,255,.04); }
|
| 51 |
+
.stat-row:last-child { border:none; }
|
| 52 |
+
.stat-val { color:var(--link); font-weight:600; }
|
| 53 |
+
.btn { display:inline-flex; align-items:center; gap:6px; padding:10px 18px; border-radius:10px; border:none; font-size:13px; font-weight:600; cursor:pointer; transition:all .2s; -webkit-tap-highlight-color:transparent; }
|
| 54 |
+
.btn:active { transform:scale(.96); }
|
| 55 |
+
.btn-primary { background:linear-gradient(135deg,#0f3460,#533483); color:#fff; }
|
| 56 |
+
.btn-danger { background:rgba(233,69,96,.15); color:var(--danger); }
|
| 57 |
+
.btn-sm { padding:6px 12px; font-size:12px; border-radius:8px; }
|
| 58 |
+
.btn-ghost { background:rgba(255,255,255,.06); color:var(--text); }
|
| 59 |
+
.btn-success { background:rgba(39,174,96,.15); color:var(--success); }
|
| 60 |
+
.btn-block { width:100%; justify-content:center; }
|
| 61 |
+
.fab { position:fixed; bottom:80px; right:20px; width:52px; height:52px; border-radius:50%; background:linear-gradient(135deg,#e94560,#533483); color:#fff; font-size:24px; display:flex; align-items:center; justify-content:center; box-shadow:0 4px 20px rgba(233,69,96,.4); cursor:pointer; z-index:50; transition:all .2s; border:none; }
|
| 62 |
+
.fab:active { transform:scale(.9); }
|
| 63 |
+
.tool-grid { display:grid; grid-template-columns:1fr 1fr; gap:12px; }
|
| 64 |
+
.tool-card { background:var(--card); border-radius:var(--radius); padding:20px 14px; text-align:center; cursor:pointer; transition:all .2s; border:1px solid rgba(255,255,255,.04); box-shadow:var(--shadow); }
|
| 65 |
+
.tool-card:active { transform:scale(.96); }
|
| 66 |
+
.tool-card .t-icon { font-size:28px; margin-bottom:8px; }
|
| 67 |
+
.tool-card .t-name { font-size:13px; font-weight:600; }
|
| 68 |
+
.tool-card .t-desc { font-size:11px; color:var(--hint); margin-top:4px; }
|
| 69 |
+
.modal-overlay { position:fixed; inset:0; background:rgba(0,0,0,.65); z-index:200; display:none; align-items:flex-end; justify-content:center; animation:fadeIn .2s; }
|
| 70 |
+
.modal-overlay.open { display:flex; }
|
| 71 |
+
@keyframes fadeIn { from{opacity:0} to{opacity:1} }
|
| 72 |
+
@keyframes slideUp { from{transform:translateY(100%)} to{transform:translateY(0)} }
|
| 73 |
+
.modal { background:var(--bg); width:100%; max-width:500px; max-height:88vh; border-radius:20px 20px 0 0; padding:20px; overflow-y:auto; animation:slideUp .3s; }
|
| 74 |
+
.modal-title { font-size:17px; font-weight:700; margin-bottom:16px; display:flex; align-items:center; gap:8px; }
|
| 75 |
+
.modal-close { margin-left:auto; background:rgba(255,255,255,.08); border:none; color:var(--hint); width:30px; height:30px; border-radius:50%; font-size:16px; cursor:pointer; }
|
| 76 |
+
.form-group { margin-bottom:14px; }
|
| 77 |
+
.form-label { font-size:12px; color:var(--hint); margin-bottom:6px; display:block; font-weight:500; }
|
| 78 |
+
.form-input,.form-select { width:100%; padding:12px 14px; background:var(--card); border:1px solid rgba(255,255,255,.08); border-radius:10px; color:var(--text); font-size:14px; outline:none; transition:border .2s; }
|
| 79 |
+
.form-input:focus { border-color:var(--link); }
|
| 80 |
+
.form-input::placeholder { color:var(--hint); }
|
| 81 |
+
.form-hint { font-size:11px; color:var(--hint); margin-top:4px; }
|
| 82 |
+
.channel-picker { display:flex; flex-wrap:wrap; gap:8px; margin-top:8px; }
|
| 83 |
+
.ch-chip { padding:6px 12px; background:rgba(255,255,255,.06); border-radius:20px; font-size:12px; cursor:pointer; transition:all .2s; border:1px solid transparent; }
|
| 84 |
+
.ch-chip:active,.ch-chip.picked { border-color:var(--link); background:rgba(233,69,96,.1); }
|
| 85 |
+
.segment { display:flex; background:var(--card); border-radius:10px; padding:3px; margin-bottom:16px; }
|
| 86 |
+
.seg-btn { flex:1; text-align:center; padding:8px; font-size:13px; border-radius:8px; cursor:pointer; transition:all .2s; color:var(--hint); border:none; background:transparent; }
|
| 87 |
+
.seg-btn.active { background:linear-gradient(135deg,#0f3460,#533483); color:#fff; font-weight:600; }
|
| 88 |
+
.toast { position:fixed; top:20px; left:50%; transform:translateX(-50%) translateY(-80px); background:var(--card); color:var(--text); padding:12px 24px; border-radius:12px; font-size:13px; z-index:999; box-shadow:0 4px 24px rgba(0,0,0,.3); transition:transform .3s; border:1px solid rgba(255,255,255,.06); }
|
| 89 |
+
.toast.show { transform:translateX(-50%) translateY(0); }
|
| 90 |
+
.toast.success { border-left:3px solid var(--success); }
|
| 91 |
+
.toast.error { border-left:3px solid var(--danger); }
|
| 92 |
+
.empty { text-align:center; padding:40px 20px; color:var(--hint); }
|
| 93 |
+
.empty .e-icon { font-size:48px; margin-bottom:12px; }
|
| 94 |
+
.empty .e-text { font-size:14px; }
|
| 95 |
+
.badge { display:inline-block; padding:2px 8px; border-radius:6px; font-size:11px; font-weight:600; }
|
| 96 |
+
.badge-info { background:rgba(15,52,96,.4); color:#5dade2; }
|
| 97 |
+
.badge-warn { background:rgba(233,69,96,.15); color:var(--danger); }
|
| 98 |
+
.spinner { width:24px; height:24px; border:3px solid rgba(255,255,255,.1); border-top-color:var(--link); border-radius:50%; animation:spin .6s linear infinite; margin:20px auto; }
|
| 99 |
+
@keyframes spin { to{transform:rotate(360deg)} }
|
| 100 |
+
.target-row { background:rgba(255,255,255,.03); border:1px solid rgba(255,255,255,.06); border-radius:10px; padding:12px; margin-bottom:8px; }
|
| 101 |
+
.target-row .form-label { margin-top:8px; }
|
| 102 |
+
.target-row .form-label:first-child { margin-top:0; }
|
| 103 |
+
.target-mini { display:flex; justify-content:space-between; align-items:center; padding:10px 12px; background:rgba(255,255,255,.03); border:1px solid rgba(255,255,255,.06); border-radius:10px; margin-bottom:8px; font-size:13px; }
|
| 104 |
+
.target-mini .info { color:var(--hint); flex:1; overflow:hidden; text-overflow:ellipsis; }
|
| 105 |
+
.target-mini .info .ch-name { color:var(--text); font-weight:600; }
|
| 106 |
+
|
| 107 |
+
/* 格式工具栏 */
|
| 108 |
+
.format-toolbar { display:flex; gap:4px; margin-bottom:6px; flex-wrap:wrap; }
|
| 109 |
+
.format-toolbar button { padding:6px 10px; border:1px solid rgba(255,255,255,.1); background:rgba(255,255,255,.05); color:var(--text); border-radius:6px; font-size:13px; cursor:pointer; transition:all .15s; min-width:34px; }
|
| 110 |
+
.format-toolbar button:active { background:rgba(233,69,96,.25); transform:scale(.92); }
|
| 111 |
+
.format-toolbar button:hover { background:rgba(255,255,255,.1); }
|
| 112 |
+
|
| 113 |
+
/* 预览区 */
|
| 114 |
+
.preview-box { background:var(--card); border:1px solid rgba(255,255,255,.08); border-radius:10px; padding:12px 14px; font-size:14px; min-height:50px; line-height:1.6; word-break:break-word; color:var(--text); }
|
| 115 |
+
.preview-box .tg-spoiler { background:var(--hint); color:transparent; border-radius:4px; padding:0 3px; cursor:pointer; transition:all .2s; }
|
| 116 |
+
.preview-box .tg-spoiler:hover,.preview-box .tg-spoiler.revealed { color:var(--text); background:rgba(255,255,255,.1); }
|
| 117 |
+
.preview-box blockquote { border-left:3px solid var(--hint); padding-left:8px; margin:6px 0; color:var(--hint); }
|
| 118 |
+
.preview-box pre { background:rgba(0,0,0,.3); border-radius:6px; padding:8px; font-size:12px; overflow-x:auto; }
|
| 119 |
+
.preview-box code { background:rgba(255,255,255,.08); padding:1px 5px; border-radius:4px; font-size:12px; color:var(--link); }
|
| 120 |
+
.preview-box a { color:var(--link); text-decoration:underline; }
|
| 121 |
+
|
| 122 |
+
/* 媒体预览 */
|
| 123 |
+
.media-preview-box { margin-top:8px; position:relative; }
|
| 124 |
+
.media-preview-box img,.media-preview-box video { max-width:100%; max-height:200px; border-radius:8px; display:block; }
|
| 125 |
+
.media-preview-box .media-clear { position:absolute; top:4px; right:4px; background:rgba(0,0,0,.7); color:#fff; border:none; border-radius:50%; width:24px; height:24px; font-size:14px; cursor:pointer; display:flex; align-items:center; justify-content:center; }
|
| 126 |
+
</style>
|
| 127 |
+
</head>
|
| 128 |
+
<body>
|
| 129 |
+
|
| 130 |
+
<div class="header">
|
| 131 |
+
<div class="header-content">
|
| 132 |
+
<div id="avatarContainer"><div class="avatar-placeholder">👤</div></div>
|
| 133 |
+
<div class="header-text">
|
| 134 |
+
<h1>Nine7<img src="https://lin7zhi.pages.dev/file/1777856634364_nine7.png"
|
| 135 |
+
style="width:22px;height:22px;vertical-align:middle;margin-right:6px;border-radius:6px;filter:drop-shadow(0 0 6px rgba(233,69,96,.4))"
|
| 136 |
+
onerror="this.style.display='none'">频道助手</h1>
|
| 137 |
+
<p class="header-greeting" id="headerGreeting">你好!</p>
|
| 138 |
+
<p id="headerSub">加载中...</p>
|
| 139 |
+
</div>
|
| 140 |
+
</div>
|
| 141 |
+
</div>
|
| 142 |
+
|
| 143 |
+
<div class="toast" id="toast"></div>
|
| 144 |
+
|
| 145 |
+
<!-- ===== 首页 ===== -->
|
| 146 |
+
<div class="page active" id="page-home">
|
| 147 |
+
<div class="card"><div class="card-title">⚙️ 系统状态</div><div class="card-body" id="statusInfo"><div class="spinner"></div></div></div>
|
| 148 |
+
<div class="card"><div class="card-title">📊 概览</div><div class="card-body" id="overview"><div class="spinner"></div></div></div>
|
| 149 |
+
</div>
|
| 150 |
+
|
| 151 |
+
<!-- ===== 同步页 ===== -->
|
| 152 |
+
<div class="page" id="page-sync"><div id="syncList"></div><button class="fab" onclick="openModal('modalAddSync')">+</button></div>
|
| 153 |
+
|
| 154 |
+
<!-- ===== 工具箱 ===== -->
|
| 155 |
+
<div class="page" id="page-tools">
|
| 156 |
+
<div class="tool-grid">
|
| 157 |
+
<div class="tool-card" onclick="openModal('modalBtnNew')"><div class="t-icon">🆕</div><div class="t-name">发按钮消息</div><div class="t-desc">支持媒体+格式+预览</div></div>
|
| 158 |
+
<div class="tool-card" onclick="openModal('modalBtnOld')"><div class="t-icon">🔘</div><div class="t-name">改旧按钮</div><div class="t-desc">给旧消息加/改/删按钮</div></div>
|
| 159 |
+
<div class="tool-card" onclick="openModal('modalGenDir')"><div class="t-icon">🗂️</div><div class="t-name">手动目录</div><div class="t-desc">扫描频道生成标签目录</div></div>
|
| 160 |
+
<div class="tool-card" onclick="openModal('modalReplace')"><div class="t-icon">🔄</div><div class="t-name">替换标签</div><div class="t-desc">批量替换/删除标签</div></div>
|
| 161 |
+
<div class="tool-card" onclick="openModal('modalBackup')"><div class="t-icon">🚀</div><div class="t-name">智能备份</div><div class="t-desc">跨频道全量搬运</div></div>
|
| 162 |
+
<div class="tool-card" onclick="openModal('modalBtnMulti')"><div class="t-icon">🔗</div><div class="t-name">多按钮消息</div><div class="t-desc">添加多个按钮行</div></div>
|
| 163 |
+
<div class="tool-card" onclick="openModal('modalBatchCreate')"><div class="t-icon">🏢</div><div class="t-name">批量建频道</div><div class="t-desc">一键批量建频道</div></div>
|
| 164 |
+
<div class="tool-card" onclick="openModal('modalBackupMembers')"><div class="t-icon">👥</div><div class="t-name">备份成员</div><div class="t-desc">导出频道成员ID和用户名</div></div>
|
| 165 |
+
</div>
|
| 166 |
+
</div>
|
| 167 |
+
|
| 168 |
+
<!-- ===== 任务页 ===== -->
|
| 169 |
+
<div class="page" id="page-tasks">
|
| 170 |
+
<div class="segment">
|
| 171 |
+
<button class="seg-btn active" onclick="switchTaskTab(this,'statsList')">📈 统计任务</button>
|
| 172 |
+
<button class="seg-btn" onclick="switchTaskTab(this,'dirsList')">📂 目录任务</button>
|
| 173 |
+
</div>
|
| 174 |
+
<div id="statsList"></div>
|
| 175 |
+
<div id="dirsList" style="display:none"></div>
|
| 176 |
+
<button class="fab" onclick="openAddTask()">+</button>
|
| 177 |
+
</div>
|
| 178 |
+
|
| 179 |
+
<!-- ===== 地址簿页 ===== -->
|
| 180 |
+
<div class="page" id="page-addr"><div id="addrList"></div><button class="fab" onclick="openModal('modalAddAddr')">+</button></div>
|
| 181 |
+
|
| 182 |
+
<!-- ===== 底栏 ===== -->
|
| 183 |
+
<div class="bottom-nav">
|
| 184 |
+
<div class="nav-item active" onclick="switchPage('home',this)"><div class="icon">🏠</div><div class="label">首页</div></div>
|
| 185 |
+
<div class="nav-item" onclick="switchPage('sync',this)"><div class="icon">🔄</div><div class="label">同步</div></div>
|
| 186 |
+
<div class="nav-item" onclick="switchPage('tools',this)"><div class="icon">🧰</div><div class="label">工具箱</div></div>
|
| 187 |
+
<div class="nav-item" onclick="switchPage('tasks',this)"><div class="icon">📊</div><div class="label">任务</div></div>
|
| 188 |
+
<div class="nav-item" onclick="switchPage('addr',this)"><div class="icon">📔</div><div class="label">地址簿</div></div>
|
| 189 |
+
</div>
|
| 190 |
+
|
| 191 |
+
<!-- ========== 同步组弹窗 ========== -->
|
| 192 |
+
<div class="modal-overlay" id="modalAddSync"><div class="modal">
|
| 193 |
+
<div class="modal-title">➕ 添加同步组 <button class="modal-close" onclick="closeModal('modalAddSync')">✕</button></div>
|
| 194 |
+
<div class="form-group"><label class="form-label">同步组名称 (可选)</label><input class="form-input" id="syncName" placeholder="如: 主频道分发"></div>
|
| 195 |
+
<div class="form-group"><label class="form-label">源频道 ID</label><input class="form-input" id="syncSrc" placeholder="-100xxxxxxxxxx"><div class="channel-picker" id="pickerSyncSrc"></div></div>
|
| 196 |
+
<p class="form-label" style="font-weight:700;color:var(--text)">目标频道 (可多个)</p>
|
| 197 |
+
<div id="syncTgtContainer"><div class="form-group"><input class="form-input sync-tgt" placeholder="-100xxxxxxxxxx"><div class="channel-picker"></div></div></div>
|
| 198 |
+
<button class="btn btn-ghost btn-sm" onclick="addSyncTgtRow()" style="margin-bottom:12px">➕ 添加目标频道</button>
|
| 199 |
+
<button class="btn btn-primary btn-block" onclick="addSyncGroup()">✅ 创建同步组</button>
|
| 200 |
+
</div></div>
|
| 201 |
+
|
| 202 |
+
<!-- ========== 地址簿弹窗 ========== -->
|
| 203 |
+
<div class="modal-overlay" id="modalAddAddr"><div class="modal">
|
| 204 |
+
<div class="modal-title">📔 添加频道 <button class="modal-close" onclick="closeModal('modalAddAddr')">✕</button></div>
|
| 205 |
+
<div class="form-group"><label class="form-label">频道 ID</label><input class="form-input" id="addrId" placeholder="-100xxxxxxxxxx"></div>
|
| 206 |
+
<div class="form-group"><label class="form-label">备注名称</label><input class="form-input" id="addrName" placeholder="如: 主频道"></div>
|
| 207 |
+
<button class="btn btn-primary btn-block" onclick="addChannel()">✅ 保存</button>
|
| 208 |
+
</div></div>
|
| 209 |
+
|
| 210 |
+
<!-- ========== 🆕 发按钮消息(媒体+格式+预览)========== -->
|
| 211 |
+
<div class="modal-overlay" id="modalBtnNew"><div class="modal">
|
| 212 |
+
<div class="modal-title">🆕 发送消息 <button class="modal-close" onclick="closeModal('modalBtnNew')">✕</button></div>
|
| 213 |
+
<div class="form-group">
|
| 214 |
+
<label class="form-label">频道 ID</label>
|
| 215 |
+
<input class="form-input" id="bnCh" placeholder="-100xxxxxxxxxx">
|
| 216 |
+
<div class="channel-picker" id="pickerBnCh"></div>
|
| 217 |
+
</div>
|
| 218 |
+
<div class="form-group">
|
| 219 |
+
<label class="form-label">📎 附加媒体文件 (可选)</label>
|
| 220 |
+
<input type="file" id="bnMedia" accept="image/*,video/*,.gif" class="form-input" style="padding:8px" onchange="previewMedia(this,'bnMediaPreview')">
|
| 221 |
+
<div class="form-hint">支持图片/视频/GIF,不选则发纯文本消息</div>
|
| 222 |
+
<div class="media-preview-box" id="bnMediaPreview"></div>
|
| 223 |
+
</div>
|
| 224 |
+
<div class="form-group">
|
| 225 |
+
<label class="form-label">消息正文 (支持 HTML 格式)</label>
|
| 226 |
+
<div class="format-toolbar">
|
| 227 |
+
<button type="button" onclick="insertFmt('b')" title="粗体"><b>B</b></button>
|
| 228 |
+
<button type="button" onclick="insertFmt('i')" title="斜体"><i>I</i></button>
|
| 229 |
+
<button type="button" onclick="insertFmt('u')" title="下划线"><u>U</u></button>
|
| 230 |
+
<button type="button" onclick="insertFmt('s')" title="删除线"><s>S</s></button>
|
| 231 |
+
<button type="button" onclick="insertFmt('code')" title="行内代码"></></button>
|
| 232 |
+
<button type="button" onclick="insertFmt('tg-spoiler')" title="遮罩文字">👁</button>
|
| 233 |
+
<button type="button" onclick="insertLink()" title="超链接">🔗</button>
|
| 234 |
+
<button type="button" onclick="insertFmt('blockquote')" title="引用块">❝</button>
|
| 235 |
+
<button type="button" onclick="insertFmt('pre')" title="代码块">PRE</button>
|
| 236 |
+
</div>
|
| 237 |
+
<textarea class="form-input" id="bnText" rows="5" placeholder="输入消息内容,点击上方按钮快速插入格式..." oninput="updatePreview()"></textarea>
|
| 238 |
+
</div>
|
| 239 |
+
<div class="form-group">
|
| 240 |
+
<label class="form-label">📱 实时预览</label>
|
| 241 |
+
<div class="preview-box" id="bnPreview"><span style="color:var(--hint)">输入内容后预览将显示在这里</span></div>
|
| 242 |
+
</div>
|
| 243 |
+
<div class="form-group"><label class="form-label">按钮文字 (可选,留空则无按钮)</label><input class="form-input" id="bnBtnText" placeholder="🔗 点击进入主群"></div>
|
| 244 |
+
<div class="form-group"><label class="form-label">按钮链接 (可选)</label><input class="form-input" id="bnUrl" placeholder="https://..." type="url"></div>
|
| 245 |
+
<button class="btn btn-primary btn-block" onclick="sendBtnNew()">🚀 发送</button>
|
| 246 |
+
</div></div>
|
| 247 |
+
|
| 248 |
+
<!-- ========== 改旧按钮 ========== -->
|
| 249 |
+
<div class="modal-overlay" id="modalBtnOld"><div class="modal">
|
| 250 |
+
<div class="modal-title">🔘 给旧消息加/改按钮 <button class="modal-close" onclick="closeModal('modalBtnOld')">✕</button></div>
|
| 251 |
+
<div class="form-group"><label class="form-label">频道 ID</label><input class="form-input" id="boCh" placeholder="-100xxxxxxxxxx"><div class="channel-picker" id="pickerBoCh"></div></div>
|
| 252 |
+
<div class="form-group"><label class="form-label">消息 ID 或链接</label><input class="form-input" id="boMsg" placeholder="消息 ID 或链接"></div>
|
| 253 |
+
<div class="form-group"><label class="form-label">按钮文字 (输入"删除"以移除按钮)</label><input class="form-input" id="boBtnText" placeholder="按钮文字 或 删除"></div>
|
| 254 |
+
<div class="form-group"><label class="form-label">跳转链接</label><input class="form-input" id="boUrl" placeholder="https://..." type="url"></div>
|
| 255 |
+
<button class="btn btn-primary btn-block" onclick="sendBtnOld()">✅ 提交</button>
|
| 256 |
+
</div></div>
|
| 257 |
+
|
| 258 |
+
<!-- ========== 多按钮消息 ========== -->
|
| 259 |
+
<div class="modal-overlay" id="modalBtnMulti"><div class="modal">
|
| 260 |
+
<div class="modal-title">🔗 多按钮消息 <button class="modal-close" onclick="closeModal('modalBtnMulti')">✕</button></div>
|
| 261 |
+
<div class="form-group"><label class="form-label">频道 ID</label><input class="form-input" id="bmCh" placeholder="-100xxxxxxxxxx"><div class="channel-picker" id="pickerBmCh"></div></div>
|
| 262 |
+
<div class="form-group"><label class="form-label">消息正文 (支持 HTML)</label><textarea class="form-input" id="bmText" rows="3" placeholder="消息内容..."></textarea></div>
|
| 263 |
+
<div id="bmBtnsContainer"><div class="form-group" style="display:flex;gap:8px"><input class="form-input" placeholder="按钮文字" style="flex:1"><input class="form-input" placeholder="https://链接" style="flex:1"></div></div>
|
| 264 |
+
<button class="btn btn-ghost btn-sm" onclick="addBtnRow()" style="margin-bottom:12px">➕ 再加一行按钮</button>
|
| 265 |
+
<button class="btn btn-primary btn-block" onclick="sendBtnMulti()">🚀 发送</button>
|
| 266 |
+
</div></div>
|
| 267 |
+
|
| 268 |
+
<!-- ========== 手动目录 ========== -->
|
| 269 |
+
<div class="modal-overlay" id="modalGenDir"><div class="modal">
|
| 270 |
+
<div class="modal-title">🗂️ 生成手动目录 <button class="modal-close" onclick="closeModal('modalGenDir')">✕</button></div>
|
| 271 |
+
<div class="form-group"><label class="form-label">频道 ID</label><input class="form-input" id="gdCh" placeholder="-100xxxxxxxxxx"><div class="channel-picker" id="pickerGdCh"></div></div>
|
| 272 |
+
<p class="form-hint">⚠️ 扫描可能需要较长时间,结果将发送到机器人对话中</p>
|
| 273 |
+
<button class="btn btn-primary btn-block" style="margin-top:12px" onclick="genDir()">🔍 开始扫描</button>
|
| 274 |
+
</div></div>
|
| 275 |
+
|
| 276 |
+
<!-- ========== 替换标签 ========== -->
|
| 277 |
+
<div class="modal-overlay" id="modalReplace"><div class="modal">
|
| 278 |
+
<div class="modal-title">🔄 批量替换标签 <button class="modal-close" onclick="closeModal('modalReplace')">✕</button></div>
|
| 279 |
+
<div class="form-group"><label class="form-label">频道 ID</label><input class="form-input" id="rpCh" placeholder="-100xxxxxxxxxx"><div class="channel-picker" id="pickerRpCh"></div></div>
|
| 280 |
+
<div class="form-group"><label class="form-label">旧标签 (带 #)</label><input class="form-input" id="rpOld" placeholder="#旧标签"></div>
|
| 281 |
+
<div class="form-group"><label class="form-label">新标签 (输入"删除"则彻底移除)</label><input class="form-input" id="rpNew" placeholder="#新标签 或 删除"></div>
|
| 282 |
+
<button class="btn btn-primary btn-block" onclick="replaceTag()">🚀 开始替换</button>
|
| 283 |
+
</div></div>
|
| 284 |
+
|
| 285 |
+
<!-- ========== 智能备份(多目标)========== -->
|
| 286 |
+
<div class="modal-overlay" id="modalBackup"><div class="modal">
|
| 287 |
+
<div class="modal-title">🚀 智能备份 <button class="modal-close" onclick="closeModal('modalBackup')">✕</button></div>
|
| 288 |
+
<div id="backupGroupBtns" style="margin-bottom:12px"></div>
|
| 289 |
+
<hr style="border-color:rgba(255,255,255,.06);margin:12px 0">
|
| 290 |
+
<p class="form-label" style="margin-bottom:8px">✏️ 手动指定通道:</p>
|
| 291 |
+
<div class="form-group"><label class="form-label">源频道 ID</label><input class="form-input" id="bkSrc" placeholder="-100xxxxxxxxxx"><div class="channel-picker" id="pickerBkSrc"></div></div>
|
| 292 |
+
<p class="form-label" style="font-weight:700;color:var(--text)">目标频道 (可多个)</p>
|
| 293 |
+
<div id="bkTgtContainer"><div class="form-group"><input class="form-input bk-tgt" placeholder="-100xxxxxxxxxx"><div class="channel-picker"></div></div></div>
|
| 294 |
+
<button class="btn btn-ghost btn-sm" onclick="addBkTgtRow()" style="margin-bottom:12px">➕ 添加目标频道</button>
|
| 295 |
+
<div class="form-group"><label class="form-label">源频道最新消息链接</label><input class="form-input" id="bkLink" placeholder="https://t.me/..."></div>
|
| 296 |
+
<div class="form-group" style="display:flex;align-items:center;justify-content:space-between;background:rgba(255,255,255,.03);padding:14px;border-radius:10px;border:1px solid rgba(255,255,255,.06)">
|
| 297 |
+
<div>
|
| 298 |
+
<div style="font-size:13px;font-weight:600">🔄 洗文件 MD5</div>
|
| 299 |
+
<div style="font-size:11px;color:var(--hint);margin-top:2px">下载→改哈希→重传,速度较慢但文件唯一</div>
|
| 300 |
+
</div>
|
| 301 |
+
<label style="position:relative;display:inline-block;width:48px;height:26px;flex-shrink:0;margin-left:12px">
|
| 302 |
+
<input type="checkbox" id="bkWash" style="opacity:0;width:0;height:0">
|
| 303 |
+
<span style="position:absolute;cursor:pointer;inset:0;background:rgba(255,255,255,.12);border-radius:26px;transition:.3s"></span>
|
| 304 |
+
<span id="bkWashDot" style="position:absolute;left:3px;top:3px;width:20px;height:20px;background:#fff;border-radius:50%;transition:.3s"></span>
|
| 305 |
+
</label>
|
| 306 |
+
</div>
|
| 307 |
+
<button class="btn btn-primary btn-block" onclick="startBackup()">🚀 开始备份</button>
|
| 308 |
+
</div></div>
|
| 309 |
+
|
| 310 |
+
<!-- ========== 创建统计任务 ========== -->
|
| 311 |
+
<div class="modal-overlay" id="modalAddStat"><div class="modal">
|
| 312 |
+
<div class="modal-title">📈 创建统计任务 <button class="modal-close" onclick="closeModal('modalAddStat')">✕</button></div>
|
| 313 |
+
<div class="form-group"><label class="form-label">任务名称</label><input class="form-input" id="stName" placeholder="日常早报统计"></div>
|
| 314 |
+
<div class="form-group"><label class="form-label">频道 ID</label><input class="form-input" id="stCh" placeholder="-100xxxxxxxxxx"><div class="channel-picker" id="pickerStCh"></div></div>
|
| 315 |
+
<div class="form-group"><label class="form-label">消息 ID 或链接</label><input class="form-input" id="stMsg" placeholder="311"></div>
|
| 316 |
+
<div class="form-group"><label class="form-label">统计表标题</label><input class="form-input" id="stTitle" placeholder="🔥 霸榜热评区:"></div>
|
| 317 |
+
<div class="form-group"><label class="form-label">显示前几名</label><input class="form-input" id="stTopN" placeholder="10" type="number"></div>
|
| 318 |
+
<div class="form-group"><label class="form-label">触发标签 (带 #)</label><input class="form-input" id="stTrig" placeholder="#更新"></div>
|
| 319 |
+
<div class="form-group"><label class="form-label">更新频率 (分钟)</label><input class="form-input" id="stIntv" placeholder="15" type="number"></div>
|
| 320 |
+
<div class="form-group"><label class="form-label">寿命期限 (天)</label><input class="form-input" id="stDura" placeholder="7" type="number"></div>
|
| 321 |
+
<div class="form-group"><label class="form-label">屏蔽名单 (空格隔开,无则留空)</label><input class="form-input" id="stBlack" placeholder="名字A 名字B"></div>
|
| 322 |
+
<div class="form-group"><label class="form-label">屏蔽区标题 (留空则默认)</label><input class="form-input" id="stBlTitle" placeholder="🚫本月轮换限制:"></div>
|
| 323 |
+
<div class="form-group">
|
| 324 |
+
<label class="form-label">📥 上期榜单消息 ID (可选,自动提取名字加入屏蔽)</label>
|
| 325 |
+
<input class="form-input" id="stExtractMsg" placeholder="留空则跳过">
|
| 326 |
+
<div class="form-hint">💡 输入上一期统计消息 ID 或链接,系统自动提取上榜名字加入屏蔽名单</div>
|
| 327 |
+
</div>
|
| 328 |
+
<button class="btn btn-primary btn-block" onclick="addStatTask()">✅ 创建任务</button>
|
| 329 |
+
</div></div>
|
| 330 |
+
|
| 331 |
+
<!-- ========== 创建自动目录 ========== -->
|
| 332 |
+
<div class="modal-overlay" id="modalAddDir"><div class="modal">
|
| 333 |
+
<div class="modal-title">🗂️ 创建自动目录 <button class="modal-close" onclick="closeModal('modalAddDir')">✕</button></div>
|
| 334 |
+
<div class="form-group"><label class="form-label">任务名称</label><input class="form-input" id="drName" placeholder="主频道自动目录"></div>
|
| 335 |
+
<div class="form-group"><label class="form-label">🔍 扫描频道 ID</label><input class="form-input" id="drScanCh" placeholder="-100xxxxxxxxxx"><div class="channel-picker" id="pickerDrScan"></div></div>
|
| 336 |
+
<hr style="border-color:rgba(255,255,255,.06);margin:12px 0">
|
| 337 |
+
<p class="form-label" style="font-weight:700;color:var(--text);margin-bottom:8px">📌 目标频道</p>
|
| 338 |
+
<div id="drTargetsContainer">
|
| 339 |
+
<div class="target-row" data-idx="0">
|
| 340 |
+
<label class="form-label">目标 1 - 频道 ID</label>
|
| 341 |
+
<input class="form-input dr-tgt-ch" placeholder="-100xxxxxxxxxx">
|
| 342 |
+
<div class="channel-picker"></div>
|
| 343 |
+
<label class="form-label">目标 1 - 消息 ID</label>
|
| 344 |
+
<input class="form-input dr-tgt-msg" placeholder="消息ID">
|
| 345 |
+
</div>
|
| 346 |
+
</div>
|
| 347 |
+
<button class="btn btn-ghost btn-sm" onclick="addDirTargetRow()" style="margin-bottom:12px">➕ 添加更多目标</button>
|
| 348 |
+
<hr style="border-color:rgba(255,255,255,.06);margin:12px 0">
|
| 349 |
+
<div class="form-group"><label class="form-label">屏蔽标签 (空格隔开,无则留空)</label><input class="form-input" id="drBlack" placeholder="#通知 #避雷"></div>
|
| 350 |
+
<button class="btn btn-primary btn-block" onclick="addDirTask()">✅ 创建</button>
|
| 351 |
+
</div></div>
|
| 352 |
+
|
| 353 |
+
<!-- ========== 添加目录目标 ========== -->
|
| 354 |
+
<div class="modal-overlay" id="modalAddDirTarget"><div class="modal">
|
| 355 |
+
<div class="modal-title">➕ 添加目标频道 <button class="modal-close" onclick="closeModal('modalAddDirTarget')">✕</button></div>
|
| 356 |
+
<div class="form-group"><label class="form-label">目标频道 ID</label><input class="form-input" id="adtCh" placeholder="-100xxxxxxxxxx"><div class="channel-picker" id="pickerAdtCh"></div></div>
|
| 357 |
+
<div class="form-group"><label class="form-label">承载消息 ID</label><input class="form-input" id="adtMsg" placeholder="消息ID"></div>
|
| 358 |
+
<button class="btn btn-primary btn-block" onclick="submitAddDirTarget()">✅ 添加</button>
|
| 359 |
+
</div></div>
|
| 360 |
+
|
| 361 |
+
<!-- ========== 移除目录目标 ========== -->
|
| 362 |
+
<div class="modal-overlay" id="modalRmDirTarget"><div class="modal">
|
| 363 |
+
<div class="modal-title">➖ 移除目标频道 <button class="modal-close" onclick="closeModal('modalRmDirTarget')">✕</button></div>
|
| 364 |
+
<div id="rmDirTargetList"></div>
|
| 365 |
+
</div></div>
|
| 366 |
+
|
| 367 |
+
<!-- ========== 编辑弹窗 ========== -->
|
| 368 |
+
<div class="modal-overlay" id="modalEdit"><div class="modal">
|
| 369 |
+
<div class="modal-title" id="editTitle">✏️ 编辑 <button class="modal-close" onclick="closeModal('modalEdit')">✕</button></div>
|
| 370 |
+
<div class="form-group">
|
| 371 |
+
<label class="form-label" id="editLabel">新值</label>
|
| 372 |
+
<input class="form-input" id="editVal">
|
| 373 |
+
<div class="channel-picker" id="pickerEditVal"></div>
|
| 374 |
+
</div>
|
| 375 |
+
<button class="btn btn-primary btn-block" onclick="submitEdit()">✅ 确认修改</button>
|
| 376 |
+
</div></div>
|
| 377 |
+
|
| 378 |
+
<!-- ========== 批量建频道 ========== -->
|
| 379 |
+
<div class="modal-overlay" id="modalBatchCreate"><div class="modal">
|
| 380 |
+
<div class="modal-title">🏢 批量创建频道 <button class="modal-close" onclick="closeModal('modalBatchCreate')">✕</button></div>
|
| 381 |
+
<div class="form-group">
|
| 382 |
+
<label class="form-label">频道名称列表 (每行一个)</label>
|
| 383 |
+
<textarea class="form-input" id="bcNames" rows="5" placeholder="频道名称A 频道名称B 频道名称C"></textarea>
|
| 384 |
+
</div>
|
| 385 |
+
<div class="form-group">
|
| 386 |
+
<label class="form-label">每个名称创建数量</label>
|
| 387 |
+
<input class="form-input" id="bcCount" type="number" value="1" min="1" placeholder="例如 9 → 名1..名9">
|
| 388 |
+
<div class="form-hint">💡 数量 1 不加后缀,数量 3 则创建:名称1、名称2、名称3</div>
|
| 389 |
+
</div>
|
| 390 |
+
<div class="form-group">
|
| 391 |
+
<label class="form-label">自动拉入的用户/机器人 ID</label>
|
| 392 |
+
<input class="form-input" id="bcUsers" placeholder="@your_bot 123456789">
|
| 393 |
+
<div class="form-hint">💡 空格、逗号或换行隔开</div>
|
| 394 |
+
</div>
|
| 395 |
+
<p class="form-hint" style="color:var(--danger)">⚠️ 耗时较长,请到 Bot 对话查看进度</p>
|
| 396 |
+
<button class="btn btn-primary btn-block" style="margin-top:12px" onclick="startBatchCreate()">🚀 开始批量创建</button>
|
| 397 |
+
</div></div>
|
| 398 |
+
|
| 399 |
+
<!-- ========== 🆕 备份频道成员 ========== -->
|
| 400 |
+
<div class="modal-overlay" id="modalBackupMembers"><div class="modal">
|
| 401 |
+
<div class="modal-title">👥 备份频道成员 <button class="modal-close" onclick="closeModal('modalBackupMembers')">✕</button></div>
|
| 402 |
+
<div class="form-group">
|
| 403 |
+
<label class="form-label">频道 / 群组 ID</label>
|
| 404 |
+
<input class="form-input" id="bmChId" placeholder="-100xxxxxxxxxx">
|
| 405 |
+
<div class="channel-picker" id="pickerBmChId"></div>
|
| 406 |
+
</div>
|
| 407 |
+
<div class="form-hint" style="margin-bottom:12px">
|
| 408 |
+
📋 将导出所有成员的 UserID、Username、昵称等信息为 CSV 文件<br>
|
| 409 |
+
☁️ 文件将同时上传 WebDAV (<code>members/</code> 目录) 并发送到 Bot 对话
|
| 410 |
+
</div>
|
| 411 |
+
<button class="btn btn-primary btn-block" onclick="backupMembers()">📥 开始导出</button>
|
| 412 |
+
</div></div>
|
| 413 |
+
|
| 414 |
+
<script>
|
| 415 |
+
const tg = window.Telegram.WebApp;
|
| 416 |
+
tg.ready(); tg.expand(); tg.enableClosingConfirmation();
|
| 417 |
+
|
| 418 |
+
const API = '/api';
|
| 419 |
+
const initData = tg.initData || '';
|
| 420 |
+
const user = tg.initDataUnsafe?.user;
|
| 421 |
+
let userData = null;
|
| 422 |
+
let currentTaskTab = 'stats';
|
| 423 |
+
let editCtx = {};
|
| 424 |
+
let dirTargetCtx = {};
|
| 425 |
+
|
| 426 |
+
if (user) {
|
| 427 |
+
const ac = document.getElementById('avatarContainer');
|
| 428 |
+
if (user.photo_url) {
|
| 429 |
+
ac.innerHTML = `<img class="avatar" src="${user.photo_url}" onerror="this.outerHTML='<div class=\\'avatar-placeholder\\'>${user.first_name?user.first_name[0]:'👤'}</div>'">`;
|
| 430 |
+
} else {
|
| 431 |
+
ac.innerHTML = `<div class="avatar-placeholder">${user.first_name?user.first_name[0]:'👤'}</div>`;
|
| 432 |
+
}
|
| 433 |
+
const h = new Date().getHours();
|
| 434 |
+
let g = '🌙 晚上好';
|
| 435 |
+
if(h>=5&&h<12) g='🌅 早上好'; else if(h>=12&&h<14) g='☀️ 中午好'; else if(h>=14&&h<18) g='🌤 下午好';
|
| 436 |
+
document.getElementById('headerGreeting').textContent = `${g},${user.first_name}!`;
|
| 437 |
+
document.getElementById('headerSub').textContent = `UID: ${user.id}`;
|
| 438 |
+
} else {
|
| 439 |
+
document.getElementById('headerGreeting').textContent = '';
|
| 440 |
+
document.getElementById('headerSub').textContent = '未检测到用户';
|
| 441 |
+
}
|
| 442 |
+
|
| 443 |
+
function chLabel(id) {
|
| 444 |
+
if(!id) return '<code>未设置</code>';
|
| 445 |
+
const ab = userData?.address_book||{};
|
| 446 |
+
const name = ab[id]||ab[String(id)];
|
| 447 |
+
return name ? `<span class="ch-name">📔${name}</span> <code>${id}</code>` : `<code>${id}</code>`;
|
| 448 |
+
}
|
| 449 |
+
function chNameOnly(id) {
|
| 450 |
+
if(!id) return '未设置';
|
| 451 |
+
const ab = userData?.address_book||{};
|
| 452 |
+
const name = ab[id]||ab[String(id)];
|
| 453 |
+
return name ? `📔${name} (${id})` : String(id);
|
| 454 |
+
}
|
| 455 |
+
|
| 456 |
+
function toast(msg, type='success') {
|
| 457 |
+
const t = document.getElementById('toast');
|
| 458 |
+
t.textContent = msg;
|
| 459 |
+
t.className = `toast show ${type}`;
|
| 460 |
+
setTimeout(()=>t.className='toast', 2800);
|
| 461 |
+
}
|
| 462 |
+
|
| 463 |
+
async function api(path, method='GET', body=null) {
|
| 464 |
+
const opts = { method, headers:{'Content-Type':'application/json','X-Init-Data':initData} };
|
| 465 |
+
if(body) opts.body = JSON.stringify(body);
|
| 466 |
+
return (await fetch(API+path, opts)).json();
|
| 467 |
+
}
|
| 468 |
+
|
| 469 |
+
function switchPage(name, el) {
|
| 470 |
+
document.querySelectorAll('.page').forEach(p=>p.classList.remove('active'));
|
| 471 |
+
document.querySelectorAll('.nav-item').forEach(n=>n.classList.remove('active'));
|
| 472 |
+
document.getElementById('page-'+name).classList.add('active');
|
| 473 |
+
el.classList.add('active');
|
| 474 |
+
if(name==='sync') renderSync();
|
| 475 |
+
if(name==='tasks') renderTasks();
|
| 476 |
+
if(name==='addr') renderAddr();
|
| 477 |
+
if(name==='home') loadHome();
|
| 478 |
+
}
|
| 479 |
+
|
| 480 |
+
function openModal(id) {
|
| 481 |
+
document.getElementById(id).classList.add('open');
|
| 482 |
+
renderChannelPickers();
|
| 483 |
+
if(id==='modalBackup') renderBackupGroups();
|
| 484 |
+
}
|
| 485 |
+
function closeModal(id) { document.getElementById(id).classList.remove('open'); }
|
| 486 |
+
document.querySelectorAll('.modal-overlay').forEach(o=>{
|
| 487 |
+
o.addEventListener('click',e=>{if(e.target===o) o.classList.remove('open');});
|
| 488 |
+
});
|
| 489 |
+
|
| 490 |
+
/* ====== 频道快选 ====== */
|
| 491 |
+
function renderChannelPickers() {
|
| 492 |
+
if(!userData) return;
|
| 493 |
+
const ab = userData.address_book||{};
|
| 494 |
+
document.querySelectorAll('.channel-picker').forEach(picker => {
|
| 495 |
+
picker.innerHTML = '';
|
| 496 |
+
Object.entries(ab).forEach(([cid,name])=>{
|
| 497 |
+
const chip = document.createElement('div');
|
| 498 |
+
chip.className = 'ch-chip';
|
| 499 |
+
chip.textContent = `📔 ${name}`;
|
| 500 |
+
chip.onclick = () => {
|
| 501 |
+
const inp = picker.previousElementSibling;
|
| 502 |
+
if(inp && (inp.classList.contains('form-input')||inp.tagName==='INPUT')) inp.value = cid;
|
| 503 |
+
chip.classList.add('picked');
|
| 504 |
+
setTimeout(()=>chip.classList.remove('picked'),600);
|
| 505 |
+
try{tg.HapticFeedback.impactOccurred('light')}catch(e){}
|
| 506 |
+
};
|
| 507 |
+
picker.appendChild(chip);
|
| 508 |
+
});
|
| 509 |
+
});
|
| 510 |
+
}
|
| 511 |
+
|
| 512 |
+
/* ====== 🆕 格式工具栏 ====== */
|
| 513 |
+
function insertFmt(tag) {
|
| 514 |
+
const ta = document.getElementById('bnText');
|
| 515 |
+
const start = ta.selectionStart, end = ta.selectionEnd;
|
| 516 |
+
const sel = ta.value.substring(start, end);
|
| 517 |
+
const wrapped = `<${tag}>${sel || '文本'}</${tag}>`;
|
| 518 |
+
ta.value = ta.value.substring(0, start) + wrapped + ta.value.substring(end);
|
| 519 |
+
ta.focus();
|
| 520 |
+
const newPos = start + tag.length + 2 + (sel||'文本').length;
|
| 521 |
+
ta.setSelectionRange(start + tag.length + 2, newPos);
|
| 522 |
+
updatePreview();
|
| 523 |
+
try{tg.HapticFeedback.impactOccurred('light')}catch(e){}
|
| 524 |
+
}
|
| 525 |
+
|
| 526 |
+
function insertLink() {
|
| 527 |
+
const ta = document.getElementById('bnText');
|
| 528 |
+
const start = ta.selectionStart, end = ta.selectionEnd;
|
| 529 |
+
const sel = ta.value.substring(start, end);
|
| 530 |
+
const linkText = sel || '链接文字';
|
| 531 |
+
const wrapped = `<a href="https://">${linkText}</a>`;
|
| 532 |
+
ta.value = ta.value.substring(0, start) + wrapped + ta.value.substring(end);
|
| 533 |
+
ta.focus();
|
| 534 |
+
const urlStart = start + 9; // position after href="
|
| 535 |
+
ta.setSelectionRange(urlStart, urlStart + 8);
|
| 536 |
+
updatePreview();
|
| 537 |
+
}
|
| 538 |
+
|
| 539 |
+
function updatePreview() {
|
| 540 |
+
const text = document.getElementById('bnText').value;
|
| 541 |
+
const preview = document.getElementById('bnPreview');
|
| 542 |
+
if(!text.trim()) {
|
| 543 |
+
preview.innerHTML = '<span style="color:var(--hint)">输入内容后预览将显示在这里</span>';
|
| 544 |
+
return;
|
| 545 |
+
}
|
| 546 |
+
// 安全地渲染预览(只允许 Telegram 支持的标签)
|
| 547 |
+
let html = text;
|
| 548 |
+
// 将换行转为 <br>
|
| 549 |
+
html = html.replace(/\n/g, '<br>');
|
| 550 |
+
preview.innerHTML = html;
|
| 551 |
+
// 遮罩文字点击切换
|
| 552 |
+
preview.querySelectorAll('.tg-spoiler').forEach(el=>{
|
| 553 |
+
el.onclick = ()=> el.classList.toggle('revealed');
|
| 554 |
+
});
|
| 555 |
+
}
|
| 556 |
+
|
| 557 |
+
/* ====== 🆕 媒体预览 ====== */
|
| 558 |
+
function previewMedia(input, previewId) {
|
| 559 |
+
const container = document.getElementById(previewId);
|
| 560 |
+
container.innerHTML = '';
|
| 561 |
+
if(!input.files||!input.files[0]) return;
|
| 562 |
+
const file = input.files[0];
|
| 563 |
+
const url = URL.createObjectURL(file);
|
| 564 |
+
let el;
|
| 565 |
+
if(file.type.startsWith('video/')) {
|
| 566 |
+
el = document.createElement('video');
|
| 567 |
+
el.src = url; el.controls = true; el.muted = true;
|
| 568 |
+
} else {
|
| 569 |
+
el = document.createElement('img');
|
| 570 |
+
el.src = url;
|
| 571 |
+
}
|
| 572 |
+
const clearBtn = document.createElement('button');
|
| 573 |
+
clearBtn.className = 'media-clear';
|
| 574 |
+
clearBtn.innerHTML = '✕';
|
| 575 |
+
clearBtn.onclick = ()=>{ input.value=''; container.innerHTML=''; };
|
| 576 |
+
container.appendChild(el);
|
| 577 |
+
container.appendChild(clearBtn);
|
| 578 |
+
}
|
| 579 |
+
|
| 580 |
+
/* ====== 首页 ====== */
|
| 581 |
+
async function loadHome() {
|
| 582 |
+
const data = await api('/data');
|
| 583 |
+
if(!data.ok) return;
|
| 584 |
+
userData = data.user;
|
| 585 |
+
document.getElementById('statusInfo').innerHTML = `
|
| 586 |
+
<div class="stat-row"><span>☁️ 云端路径</span><span class="stat-val" style="font-size:11px;max-width:160px;overflow:hidden;text-overflow:ellipsis">${data.webdav_url||'未设置'}</span></div>
|
| 587 |
+
<div class="stat-row"><span>🗺️ 消息映射</span><span class="stat-val">${data.msg_count} 条</span></div>
|
| 588 |
+
<div class="stat-row"><span>🤖 Userbot</span><span class="stat-val">${data.userbot?'✅ 在线':'❌ 离线'}</span></div>`;
|
| 589 |
+
const g=userData.groups||[], s=userData.stats_tasks||[], d=userData.dir_tasks||[], ab=userData.address_book||{};
|
| 590 |
+
document.getElementById('overview').innerHTML = `
|
| 591 |
+
<div class="stat-row"><span>🔄 同步组</span><span class="stat-val">${g.length}</span></div>
|
| 592 |
+
<div class="stat-row"><span>📈 统计任务</span><span class="stat-val">${s.length}</span></div>
|
| 593 |
+
<div class="stat-row"><span>📂 目录任务</span><span class="stat-val">${d.length}</span></div>
|
| 594 |
+
<div class="stat-row"><span>📔 地址簿</span><span class="stat-val">${Object.keys(ab).length} 个频道</span></div>`;
|
| 595 |
+
}
|
| 596 |
+
|
| 597 |
+
/* ====== 同步组 ====== */
|
| 598 |
+
function addSyncTgtRow() {
|
| 599 |
+
const c = document.getElementById('syncTgtContainer');
|
| 600 |
+
const div = document.createElement('div');
|
| 601 |
+
div.className = 'form-group';
|
| 602 |
+
div.innerHTML = `<input class="form-input sync-tgt" placeholder="-100xxxxxxxxxx"><div class="channel-picker"></div>`;
|
| 603 |
+
c.appendChild(div);
|
| 604 |
+
renderChannelPickers();
|
| 605 |
+
}
|
| 606 |
+
|
| 607 |
+
function renderSync() {
|
| 608 |
+
const list = document.getElementById('syncList');
|
| 609 |
+
const groups = userData?.groups||[];
|
| 610 |
+
if(!groups.length) {
|
| 611 |
+
list.innerHTML='<div class="empty"><div class="e-icon">🔄</div><div class="e-text">暂无同步任务<br>点击右下角 + 创建</div></div>';
|
| 612 |
+
return;
|
| 613 |
+
}
|
| 614 |
+
list.innerHTML = groups.map((g,i)=>{
|
| 615 |
+
let tgts = g.tgt; if(typeof tgts==='string') tgts=[tgts];
|
| 616 |
+
const tgtHtml = (tgts||[]).map(t=>`<div style="margin-left:8px">▪️ ${chLabel(t)}</div>`).join('');
|
| 617 |
+
const nm = g.name||`同步组 ${i+1}`;
|
| 618 |
+
return `<div class="card">
|
| 619 |
+
<div class="card-title">📦 ${nm}</div>
|
| 620 |
+
<div class="card-body">源频道: ${chLabel(g.src)}<br>目标频道:<br>${tgtHtml}</div>
|
| 621 |
+
<div class="card-actions"><button class="btn btn-danger btn-sm" onclick="delSync(${i})">🗑 删除</button></div>
|
| 622 |
+
</div>`;
|
| 623 |
+
}).join('');
|
| 624 |
+
}
|
| 625 |
+
|
| 626 |
+
async function addSyncGroup() {
|
| 627 |
+
const name = document.getElementById('syncName').value.trim();
|
| 628 |
+
const src = document.getElementById('syncSrc').value.trim();
|
| 629 |
+
const tgts = [];
|
| 630 |
+
document.querySelectorAll('#syncTgtContainer .sync-tgt').forEach(i=>{const v=i.value.trim();if(v)tgts.push(v);});
|
| 631 |
+
if(!src||!tgts.length) return toast('请填写源频道和至少一个目标','error');
|
| 632 |
+
const res = await api('/groups','POST',{name,src,tgt:tgts});
|
| 633 |
+
if(res.ok){toast('✅ 同步组已创建');closeModal('modalAddSync');userData=res.user;renderSync();}
|
| 634 |
+
else toast(res.msg||'失败','error');
|
| 635 |
+
}
|
| 636 |
+
|
| 637 |
+
async function delSync(idx) {
|
| 638 |
+
tg.showConfirm('确认删除该同步组?',async(ok)=>{
|
| 639 |
+
if(!ok)return;
|
| 640 |
+
const res=await api(`/groups/${idx}`,'DELETE');
|
| 641 |
+
if(res.ok){toast('已删除');userData=res.user;renderSync();}
|
| 642 |
+
});
|
| 643 |
+
}
|
| 644 |
+
|
| 645 |
+
/* ====== 地址簿 ====== */
|
| 646 |
+
function renderAddr() {
|
| 647 |
+
const list=document.getElementById('addrList'), ab=userData?.address_book||{}, entries=Object.entries(ab);
|
| 648 |
+
if(!entries.length){list.innerHTML='<div class="empty"><div class="e-icon">📔</div><div class="e-text">暂无保存的频道<br>点击右下角 + 添加</div></div>';return;}
|
| 649 |
+
list.innerHTML=entries.map(([c,n])=>`
|
| 650 |
+
<div class="card"><div class="card-title">📔 ${n}</div><div class="card-body"><code>${c}</code></div>
|
| 651 |
+
<div class="card-actions"><button class="btn btn-danger btn-sm" onclick="delAddr('${c}')">🗑 删除</button></div></div>`).join('');
|
| 652 |
+
}
|
| 653 |
+
async function addChannel() {
|
| 654 |
+
const id=document.getElementById('addrId').value.trim(), name=document.getElementById('addrName').value.trim();
|
| 655 |
+
if(!id||!name) return toast('请填写完整','error');
|
| 656 |
+
const res=await api('/channels','POST',{id,name});
|
| 657 |
+
if(res.ok){toast('✅ 已添加');closeModal('modalAddAddr');userData=res.user;renderAddr();renderChannelPickers();}
|
| 658 |
+
}
|
| 659 |
+
async function delAddr(cid) {
|
| 660 |
+
tg.showConfirm('确认删除?',async(ok)=>{
|
| 661 |
+
if(!ok)return;
|
| 662 |
+
const res=await api(`/channels/${encodeURIComponent(cid)}`,'DELETE');
|
| 663 |
+
if(res.ok){toast('已删除');userData=res.user;renderAddr();renderChannelPickers();}
|
| 664 |
+
});
|
| 665 |
+
}
|
| 666 |
+
|
| 667 |
+
/* ====== 任务 ====== */
|
| 668 |
+
function switchTaskTab(el,listId) {
|
| 669 |
+
document.querySelectorAll('.seg-btn').forEach(b=>b.classList.remove('active'));
|
| 670 |
+
el.classList.add('active');
|
| 671 |
+
currentTaskTab=listId==='statsList'?'stats':'dirs';
|
| 672 |
+
document.getElementById('statsList').style.display=listId==='statsList'?'':'none';
|
| 673 |
+
document.getElementById('dirsList').style.display=listId==='dirsList'?'':'none';
|
| 674 |
+
}
|
| 675 |
+
function openAddTask() { openModal(currentTaskTab==='stats'?'modalAddStat':'modalAddDir'); }
|
| 676 |
+
function renderTasks() { renderStatsTasks(); renderDirsTasks(); }
|
| 677 |
+
|
| 678 |
+
function renderStatsTasks() {
|
| 679 |
+
const list=document.getElementById('statsList'), tasks=userData?.stats_tasks||[];
|
| 680 |
+
if(!tasks.length){list.innerHTML='<div class="empty"><div class="e-icon">📈</div><div class="e-text">暂无统计任务</div></div>';return;}
|
| 681 |
+
list.innerHTML=tasks.map((t,i)=>`
|
| 682 |
+
<div class="card">
|
| 683 |
+
<div class="card-title">🎯 ${t.task_name||'未命名'} <span class="badge badge-info">每${t.interval||60}分钟</span></div>
|
| 684 |
+
<div class="card-body">
|
| 685 |
+
频道: ${chLabel(t.channel_id)} | 消息: <code>${t.msg_id}</code><br>
|
| 686 |
+
表头: <code>${t.table_title||''}</code><br>
|
| 687 |
+
触发: <code>${t.trigger_tag||''}</code> | 前 <code>${t.top_n||10}</code> 名 | 寿命 <code>${t.duration||7}</code> 天<br>
|
| 688 |
+
🚫 屏蔽: <code>${(t.stats_blacklist||[]).join(', ')||'无'}</code><br>
|
| 689 |
+
📝 屏蔽标题: <code>${t.blacklist_title||'默认'}</code>
|
| 690 |
+
</div>
|
| 691 |
+
<div class="card-actions">
|
| 692 |
+
<button class="btn btn-ghost btn-sm" onclick="editField('stats',${i},'task_name','任务名称')">✏️名称</button>
|
| 693 |
+
<button class="btn btn-ghost btn-sm" onclick="editField('stats',${i},'channel_id','频道ID',true)">✏️频道</button>
|
| 694 |
+
<button class="btn btn-ghost btn-sm" onclick="editField('stats',${i},'msg_id','消息ID')">✏️消息</button>
|
| 695 |
+
<button class="btn btn-ghost btn-sm" onclick="editField('stats',${i},'table_title','表头标题')">✏️表头</button>
|
| 696 |
+
<button class="btn btn-ghost btn-sm" onclick="editField('stats',${i},'trigger_tag','触发标签')">✏️标签</button>
|
| 697 |
+
<button class="btn btn-ghost btn-sm" onclick="editField('stats',${i},'top_n','上榜名额')">🏆名额</button>
|
| 698 |
+
<button class="btn btn-ghost btn-sm" onclick="editField('stats',${i},'interval','更新频率(分钟)')">⏱频率</button>
|
| 699 |
+
<button class="btn btn-ghost btn-sm" onclick="editField('stats',${i},'duration','寿命(天)')">⏳寿命</button>
|
| 700 |
+
<button class="btn btn-ghost btn-sm" onclick="editField('stats',${i},'add_stats_bl','添加屏蔽(空格隔开)')">🚫加屏蔽</button>
|
| 701 |
+
<button class="btn btn-ghost btn-sm" onclick="editField('stats',${i},'rm_stats_bl','解除屏蔽(空格隔开)')">✅删屏蔽</button>
|
| 702 |
+
<button class="btn btn-ghost btn-sm" onclick="editField('stats',${i},'blacklist_title','屏蔽区标题')">📝屏蔽标题</button>
|
| 703 |
+
<button class="btn btn-ghost btn-sm" onclick="editField('stats',${i},'extract_blacklist','上期榜单消息ID')">📥提取屏蔽</button>
|
| 704 |
+
<button class="btn btn-danger btn-sm" onclick="delTask('stats',${i})">🗑 删除</button>
|
| 705 |
+
</div>
|
| 706 |
+
</div>`).join('');
|
| 707 |
+
}
|
| 708 |
+
|
| 709 |
+
function renderDirsTasks() {
|
| 710 |
+
const list=document.getElementById('dirsList'), tasks=userData?.dir_tasks||[];
|
| 711 |
+
if(!tasks.length){list.innerHTML='<div class="empty"><div class="e-icon">📂</div><div class="e-text">暂无目录任务</div></div>';return;}
|
| 712 |
+
list.innerHTML=tasks.map((t,i)=>{
|
| 713 |
+
const targets=t.targets||[];
|
| 714 |
+
const tgtHtml=targets.length?targets.map((tgt,ti)=>
|
| 715 |
+
`<div class="target-mini"><span class="info">${ti+1}. ${chLabel(tgt.channel_id)} | 消息 <code>${tgt.msg_id}</code></span></div>`
|
| 716 |
+
).join(''):'<span style="color:var(--hint)">暂无目标</span>';
|
| 717 |
+
return `<div class="card">
|
| 718 |
+
<div class="card-title">🗂️ ${t.task_name||'未命名'} <span class="badge badge-info">每${t.interval||15}分钟</span></div>
|
| 719 |
+
<div class="card-body">
|
| 720 |
+
🔍 扫描: ${chLabel(t.scan_id)}<br>
|
| 721 |
+
📌 目标频道:<br>${tgtHtml}
|
| 722 |
+
屏蔽: <code>${(t.blacklist||[]).join(', ')||'无'}</code><br>
|
| 723 |
+
已收录: <code>${(t.tags_cache||[]).length}</code> 个标签
|
| 724 |
+
</div>
|
| 725 |
+
<div class="card-actions">
|
| 726 |
+
<button class="btn btn-success btn-sm" onclick="openAddTargetModal(${i})">➕目标</button>
|
| 727 |
+
<button class="btn btn-ghost btn-sm" onclick="openRmTargetModal(${i})">➖目标</button>
|
| 728 |
+
<button class="btn btn-ghost btn-sm" onclick="editField('dirs',${i},'add_blacklist','追加屏蔽标签')">➕屏蔽</button>
|
| 729 |
+
<button class="btn btn-ghost btn-sm" onclick="editField('dirs',${i},'rm_blacklist','移除屏蔽标签')">➖屏蔽</button>
|
| 730 |
+
<button class="btn btn-ghost btn-sm" onclick="editField('dirs',${i},'interval','扫描频率(分钟)')">⏱频率</button>
|
| 731 |
+
<button class="btn btn-danger btn-sm" onclick="delTask('dirs',${i})">🗑 删除</button>
|
| 732 |
+
</div>
|
| 733 |
+
</div>`;
|
| 734 |
+
}).join('');
|
| 735 |
+
}
|
| 736 |
+
|
| 737 |
+
async function addStatTask() {
|
| 738 |
+
const body = {
|
| 739 |
+
task_name:document.getElementById('stName').value.trim(),
|
| 740 |
+
channel_id:document.getElementById('stCh').value.trim(),
|
| 741 |
+
msg_id:document.getElementById('stMsg').value.trim(),
|
| 742 |
+
table_title:document.getElementById('stTitle').value.trim(),
|
| 743 |
+
top_n:parseInt(document.getElementById('stTopN').value)||10,
|
| 744 |
+
trigger_tag:document.getElementById('stTrig').value.trim(),
|
| 745 |
+
interval:parseInt(document.getElementById('stIntv').value)||15,
|
| 746 |
+
duration:parseInt(document.getElementById('stDura').value)||7,
|
| 747 |
+
stats_blacklist:document.getElementById('stBlack').value.trim(),
|
| 748 |
+
blacklist_title:document.getElementById('stBlTitle').value.trim()||'🚫本月轮换限制:',
|
| 749 |
+
extract_from_msg:document.getElementById('stExtractMsg').value.trim()
|
| 750 |
+
};
|
| 751 |
+
if(!body.task_name||!body.channel_id||!body.msg_id) return toast('请填写必填项','error');
|
| 752 |
+
if(!body.trigger_tag.startsWith('#')) return toast('触发标签必须以#开头','error');
|
| 753 |
+
const res=await api('/stats','POST',body);
|
| 754 |
+
if(res.ok){
|
| 755 |
+
toast('✅ 统计任务已创建');closeModal('modalAddStat');userData=res.user;renderStatsTasks();
|
| 756 |
+
if(body.extract_from_msg) toast('🔍 正在后台提取屏蔽名单...');
|
| 757 |
+
} else toast(res.msg||'创建失败','error');
|
| 758 |
+
}
|
| 759 |
+
|
| 760 |
+
let dirTargetCounter = 1;
|
| 761 |
+
function addDirTargetRow() {
|
| 762 |
+
dirTargetCounter++;
|
| 763 |
+
const c = document.getElementById('drTargetsContainer');
|
| 764 |
+
const div = document.createElement('div');
|
| 765 |
+
div.className = 'target-row';
|
| 766 |
+
div.innerHTML = `
|
| 767 |
+
<label class="form-label">目标 ${dirTargetCounter} - 频道 ID</label>
|
| 768 |
+
<input class="form-input dr-tgt-ch" placeholder="-100xxxxxxxxxx">
|
| 769 |
+
<div class="channel-picker"></div>
|
| 770 |
+
<label class="form-label">目标 ${dirTargetCounter} - 消息 ID</label>
|
| 771 |
+
<input class="form-input dr-tgt-msg" placeholder="消息ID">`;
|
| 772 |
+
c.appendChild(div);
|
| 773 |
+
renderChannelPickers();
|
| 774 |
+
}
|
| 775 |
+
|
| 776 |
+
async function addDirTask() {
|
| 777 |
+
const name=document.getElementById('drName').value.trim();
|
| 778 |
+
const scanCh=document.getElementById('drScanCh').value.trim();
|
| 779 |
+
const bl=document.getElementById('drBlack').value.trim();
|
| 780 |
+
const rows=document.querySelectorAll('#drTargetsContainer .target-row');
|
| 781 |
+
const targets=[];
|
| 782 |
+
rows.forEach(r=>{
|
| 783 |
+
const ch=r.querySelector('.dr-tgt-ch').value.trim();
|
| 784 |
+
const msg=r.querySelector('.dr-tgt-msg').value.trim();
|
| 785 |
+
if(ch&&msg) targets.push({channel_id:ch,msg_id:msg});
|
| 786 |
+
});
|
| 787 |
+
if(!name||!scanCh||!targets.length) return toast('请填写完整','error');
|
| 788 |
+
const res=await api('/dirs','POST',{task_name:name,scan_id:scanCh,targets:targets,blacklist:bl?bl.split(/\s+/):[]});
|
| 789 |
+
if(res.ok){toast('✅ 目录任务已创建');closeModal('modalAddDir');userData=res.user;renderDirsTasks();}
|
| 790 |
+
else toast(res.msg||'创建失败','error');
|
| 791 |
+
}
|
| 792 |
+
|
| 793 |
+
function openAddTargetModal(idx) {
|
| 794 |
+
dirTargetCtx={idx};
|
| 795 |
+
document.getElementById('adtCh').value='';
|
| 796 |
+
document.getElementById('adtMsg').value='';
|
| 797 |
+
openModal('modalAddDirTarget');
|
| 798 |
+
}
|
| 799 |
+
async function submitAddDirTarget() {
|
| 800 |
+
const ch=document.getElementById('adtCh').value.trim();
|
| 801 |
+
const msg=document.getElementById('adtMsg').value.trim();
|
| 802 |
+
if(!ch||!msg) return toast('请填写完整','error');
|
| 803 |
+
const res=await api(`/dirs/${dirTargetCtx.idx}`,'PUT',{field:'add_target',value:JSON.stringify({channel_id:ch,msg_id:msg})});
|
| 804 |
+
if(res.ok){toast('✅ 目标已添加');closeModal('modalAddDirTarget');userData=res.user;renderDirsTasks();}
|
| 805 |
+
else toast(res.msg||'失败','error');
|
| 806 |
+
}
|
| 807 |
+
|
| 808 |
+
function openRmTargetModal(idx) {
|
| 809 |
+
dirTargetCtx={idx};
|
| 810 |
+
const task=userData.dir_tasks[idx];
|
| 811 |
+
const targets=task?.targets||[];
|
| 812 |
+
const list=document.getElementById('rmDirTargetList');
|
| 813 |
+
if(!targets.length){list.innerHTML='<p style="color:var(--hint)">暂无目标频道</p>';openModal('modalRmDirTarget');return;}
|
| 814 |
+
list.innerHTML=targets.map((tgt,ti)=>`
|
| 815 |
+
<div class="target-mini">
|
| 816 |
+
<span class="info">${ti+1}. ${chNameOnly(tgt.channel_id)} | 消息 ${tgt.msg_id}</span>
|
| 817 |
+
<button class="btn btn-danger btn-sm" onclick="rmDirTarget(${idx},${ti})">❌</button>
|
| 818 |
+
</div>`).join('');
|
| 819 |
+
openModal('modalRmDirTarget');
|
| 820 |
+
}
|
| 821 |
+
async function rmDirTarget(taskIdx,targetIdx) {
|
| 822 |
+
const res=await api(`/dirs/${taskIdx}`,'PUT',{field:'rm_target',value:String(targetIdx)});
|
| 823 |
+
if(res.ok){toast('✅ 已移除');userData=res.user;renderDirsTasks();openRmTargetModal(taskIdx);}
|
| 824 |
+
else toast(res.msg||'失败','error');
|
| 825 |
+
}
|
| 826 |
+
|
| 827 |
+
async function delTask(type,idx) {
|
| 828 |
+
tg.showConfirm('确认删除此任务?',async(ok)=>{
|
| 829 |
+
if(!ok)return;
|
| 830 |
+
const path=type==='stats'?`/stats/${idx}`:`/dirs/${idx}`;
|
| 831 |
+
const res=await api(path,'DELETE');
|
| 832 |
+
if(res.ok){toast('已删除');userData=res.user;renderTasks();}
|
| 833 |
+
});
|
| 834 |
+
}
|
| 835 |
+
|
| 836 |
+
/* ====== 编辑弹窗 ====== */
|
| 837 |
+
const CHANNEL_FIELDS = ['channel_id','scan_id'];
|
| 838 |
+
function editField(type,idx,field,label,showChPicker) {
|
| 839 |
+
editCtx = {type,idx,field};
|
| 840 |
+
document.getElementById('editTitle').innerHTML = `✏️ 编辑 - ${label} <button class="modal-close" onclick="closeModal('modalEdit')">✕</button>`;
|
| 841 |
+
document.getElementById('editLabel').textContent = label;
|
| 842 |
+
document.getElementById('editVal').value = '';
|
| 843 |
+
document.getElementById('editVal').placeholder = `请输入新的${label}`;
|
| 844 |
+
const pickerEl = document.getElementById('pickerEditVal');
|
| 845 |
+
pickerEl.style.display = (showChPicker||CHANNEL_FIELDS.includes(field)) ? '' : 'none';
|
| 846 |
+
openModal('modalEdit');
|
| 847 |
+
}
|
| 848 |
+
|
| 849 |
+
async function submitEdit() {
|
| 850 |
+
const val=document.getElementById('editVal').value.trim();
|
| 851 |
+
if(!val) return toast('不能为空','error');
|
| 852 |
+
const {type,idx,field}=editCtx;
|
| 853 |
+
const path=type==='stats'?`/stats/${idx}`:`/dirs/${idx}`;
|
| 854 |
+
const res=await api(path,'PUT',{field,value:val});
|
| 855 |
+
if(res.ok){
|
| 856 |
+
if(field==='extract_blacklist') toast('🔍 正在后台读取榜单...');
|
| 857 |
+
else toast('✅ 已修改');
|
| 858 |
+
closeModal('modalEdit');
|
| 859 |
+
if(res.user){userData=res.user;renderTasks();}
|
| 860 |
+
} else toast(res.msg||'修改失败','error');
|
| 861 |
+
}
|
| 862 |
+
|
| 863 |
+
/* ====== 🆕 发送消息(支持媒体文件)====== */
|
| 864 |
+
async function sendBtnNew() {
|
| 865 |
+
const chId = document.getElementById('bnCh').value.trim();
|
| 866 |
+
const text = document.getElementById('bnText').value;
|
| 867 |
+
const btnText = document.getElementById('bnBtnText').value.trim();
|
| 868 |
+
const url = document.getElementById('bnUrl').value.trim();
|
| 869 |
+
const fileInput = document.getElementById('bnMedia');
|
| 870 |
+
|
| 871 |
+
if(!chId) return toast('请填写频道ID','error');
|
| 872 |
+
if(!text && (!fileInput.files||!fileInput.files[0])) return toast('请输入内容或选择媒体文件','error');
|
| 873 |
+
|
| 874 |
+
// 有媒体文件时用 FormData 上传
|
| 875 |
+
if(fileInput.files && fileInput.files[0]) {
|
| 876 |
+
const formData = new FormData();
|
| 877 |
+
formData.append('ch_id', chId);
|
| 878 |
+
formData.append('text', text);
|
| 879 |
+
formData.append('btn_text', btnText);
|
| 880 |
+
formData.append('url', url);
|
| 881 |
+
formData.append('media', fileInput.files[0]);
|
| 882 |
+
|
| 883 |
+
try {
|
| 884 |
+
const resp = await fetch(API+'/btn_new_media', {
|
| 885 |
+
method:'POST',
|
| 886 |
+
headers:{'X-Init-Data':initData},
|
| 887 |
+
body: formData
|
| 888 |
+
});
|
| 889 |
+
const res = await resp.json();
|
| 890 |
+
if(res.ok){toast('✅ 消息已发送');closeModal('modalBtnNew');}
|
| 891 |
+
else toast(res.msg||'发送失败','error');
|
| 892 |
+
} catch(e) { toast('发送出错: '+e.message,'error'); }
|
| 893 |
+
} else {
|
| 894 |
+
// 纯文本模式
|
| 895 |
+
const body = {ch_id:chId, text, btn_text:btnText, url};
|
| 896 |
+
const res = await api('/btn_new','POST',body);
|
| 897 |
+
if(res.ok){toast('✅ 消息已发送');closeModal('modalBtnNew');}
|
| 898 |
+
else toast(res.msg||'发送失败','error');
|
| 899 |
+
}
|
| 900 |
+
}
|
| 901 |
+
|
| 902 |
+
async function sendBtnOld() {
|
| 903 |
+
const body={ch_id:document.getElementById('boCh').value.trim(),msg_id:document.getElementById('boMsg').value.trim(),
|
| 904 |
+
btn_text:document.getElementById('boBtnText').value.trim(),url:document.getElementById('boUrl').value.trim()};
|
| 905 |
+
if(!body.ch_id||!body.msg_id||!body.btn_text) return toast('请填写必填项','error');
|
| 906 |
+
const res=await api('/btn_old','POST',body);
|
| 907 |
+
if(res.ok){toast('✅ 操作成功');closeModal('modalBtnOld');}else toast(res.msg||'失败','error');
|
| 908 |
+
}
|
| 909 |
+
|
| 910 |
+
function addBtnRow() {
|
| 911 |
+
const c=document.getElementById('bmBtnsContainer');
|
| 912 |
+
const div=document.createElement('div');
|
| 913 |
+
div.className='form-group';div.style.cssText='display:flex;gap:8px';
|
| 914 |
+
div.innerHTML=`<input class="form-input" placeholder="按钮文字" style="flex:1"><input class="form-input" placeholder="https://链接" style="flex:1">`;
|
| 915 |
+
c.appendChild(div);
|
| 916 |
+
}
|
| 917 |
+
|
| 918 |
+
async function sendBtnMulti() {
|
| 919 |
+
const ch=document.getElementById('bmCh').value.trim(), text=document.getElementById('bmText').value;
|
| 920 |
+
const rows=document.querySelectorAll('#bmBtnsContainer .form-group'), buttons=[];
|
| 921 |
+
rows.forEach(r=>{const ins=r.querySelectorAll('input');if(ins[0].value.trim()&&ins[1].value.trim()) buttons.push({text:ins[0].value.trim(),url:ins[1].value.trim()});});
|
| 922 |
+
if(!ch||!text||!buttons.length) return toast('请填写完整','error');
|
| 923 |
+
const res=await api('/btn_multi','POST',{ch_id:ch,text,buttons});
|
| 924 |
+
if(res.ok){toast('✅ 已发送');closeModal('modalBtnMulti');}else toast(res.msg||'失败','error');
|
| 925 |
+
}
|
| 926 |
+
|
| 927 |
+
async function genDir() {
|
| 928 |
+
const ch=document.getElementById('gdCh').value.trim();
|
| 929 |
+
if(!ch) return toast('请输入频道ID','error');
|
| 930 |
+
const res=await api('/gen_dir','POST',{ch_id:ch});
|
| 931 |
+
toast(res.ok?'✅ 扫描已启动,请在 Bot 对话中查看结果':(res.msg||'失败'),res.ok?'success':'error');
|
| 932 |
+
if(res.ok) closeModal('modalGenDir');
|
| 933 |
+
}
|
| 934 |
+
|
| 935 |
+
async function replaceTag() {
|
| 936 |
+
const body={ch_id:document.getElementById('rpCh').value.trim(),old_tag:document.getElementById('rpOld').value.trim(),
|
| 937 |
+
new_tag:document.getElementById('rpNew').value.trim()};
|
| 938 |
+
if(!body.ch_id||!body.old_tag) return toast('请填写必填项','error');
|
| 939 |
+
if(!body.old_tag.startsWith('#')) return toast('旧标签需以#开头','error');
|
| 940 |
+
const res=await api('/replace_tag','POST',body);
|
| 941 |
+
toast(res.ok?'✅ 替换任务已启动':(res.msg||'失败'),res.ok?'success':'error');
|
| 942 |
+
if(res.ok) closeModal('modalReplace');
|
| 943 |
+
}
|
| 944 |
+
|
| 945 |
+
function renderBackupGroups() {
|
| 946 |
+
const c=document.getElementById('backupGroupBtns'), groups=userData?.groups||[];
|
| 947 |
+
if(!groups.length){c.innerHTML='<p style="color:var(--hint);font-size:13px">暂无同步组可快速备份</p>';return;}
|
| 948 |
+
c.innerHTML=groups.map((g,i)=>{
|
| 949 |
+
const nm=g.name||`组${i+1}`;
|
| 950 |
+
return `<button class="btn btn-ghost btn-sm" style="margin:4px" onclick="quickBackup(${i})">📦 ${nm}: ${chNameOnly(g.src)}</button>`;
|
| 951 |
+
}).join('');
|
| 952 |
+
}
|
| 953 |
+
|
| 954 |
+
function quickBackup(idx) {
|
| 955 |
+
const g=userData.groups[idx]; if(!g) return;
|
| 956 |
+
document.getElementById('bkSrc').value = g.src||'';
|
| 957 |
+
const container=document.getElementById('bkTgtContainer');
|
| 958 |
+
let tgts=g.tgt; if(typeof tgts==='string') tgts=[tgts];
|
| 959 |
+
container.innerHTML='';
|
| 960 |
+
(tgts||[]).forEach(t=>{
|
| 961 |
+
const div=document.createElement('div');
|
| 962 |
+
div.className='form-group';
|
| 963 |
+
div.innerHTML=`<input class="form-input bk-tgt" placeholder="-100xxxxxxxxxx" value="${t}"><div class="channel-picker"></div>`;
|
| 964 |
+
container.appendChild(div);
|
| 965 |
+
});
|
| 966 |
+
renderChannelPickers();
|
| 967 |
+
toast('✅ 已自动填充,请粘贴最新消息链接后点击备份');
|
| 968 |
+
}
|
| 969 |
+
|
| 970 |
+
function addBkTgtRow() {
|
| 971 |
+
const c=document.getElementById('bkTgtContainer');
|
| 972 |
+
const div=document.createElement('div');
|
| 973 |
+
div.className='form-group';
|
| 974 |
+
div.innerHTML=`<input class="form-input bk-tgt" placeholder="-100xxxxxxxxxx"><div class="channel-picker"></div>`;
|
| 975 |
+
c.appendChild(div);
|
| 976 |
+
renderChannelPickers();
|
| 977 |
+
}
|
| 978 |
+
|
| 979 |
+
async function startBackup() {
|
| 980 |
+
const src=document.getElementById('bkSrc').value.trim();
|
| 981 |
+
const link=document.getElementById('bkLink').value.trim();
|
| 982 |
+
const wash=document.getElementById('bkWash').checked;
|
| 983 |
+
const tgts=[];
|
| 984 |
+
document.querySelectorAll('#bkTgtContainer .bk-tgt').forEach(i=>{const v=i.value.trim();if(v)tgts.push(v);});
|
| 985 |
+
if(!src||!tgts.length||!link) return toast('请填写完整','error');
|
| 986 |
+
const mode=wash?'🔄 洗MD5':'📦 普通';
|
| 987 |
+
const res=await api('/backup','POST',{src,tgt:tgts,link,wash});
|
| 988 |
+
toast(res.ok?`✅ 备份已启动 (${mode}, ${tgts.length}个目标)`:(res.msg||'失败'),res.ok?'success':'error');
|
| 989 |
+
if(res.ok) closeModal('modalBackup');
|
| 990 |
+
}
|
| 991 |
+
|
| 992 |
+
/* ====== 🆕 备份频道成员 ====== */
|
| 993 |
+
async function backupMembers() {
|
| 994 |
+
const chId = document.getElementById('bmChId').value.trim();
|
| 995 |
+
if(!chId) return toast('请输入频道ID','error');
|
| 996 |
+
const res = await api('/backup_members','POST',{ch_id:chId});
|
| 997 |
+
if(res.ok){
|
| 998 |
+
toast('✅ 导出任务已启动,请在 Bot 对话查看结果');
|
| 999 |
+
closeModal('modalBackupMembers');
|
| 1000 |
+
} else toast(res.msg||'失败','error');
|
| 1001 |
+
}
|
| 1002 |
+
|
| 1003 |
+
/* ====== 批量建频道 ====== */
|
| 1004 |
+
async function startBatchCreate() {
|
| 1005 |
+
const names=document.getElementById('bcNames').value.trim();
|
| 1006 |
+
const users=document.getElementById('bcUsers').value.trim();
|
| 1007 |
+
const count=parseInt(document.getElementById('bcCount').value)||1;
|
| 1008 |
+
if(!names) return toast('请输入频道名称','error');
|
| 1009 |
+
const res=await api('/batch_create','POST',{names,users,count});
|
| 1010 |
+
if(res.ok){toast('✅ 批量创建已启动!');closeModal('modalBatchCreate');}
|
| 1011 |
+
else toast(res.msg||'提交失败','error');
|
| 1012 |
+
}
|
| 1013 |
+
|
| 1014 |
+
/* ====== 洗MD5开关交互 ====== */
|
| 1015 |
+
document.getElementById('bkWash').addEventListener('change', function() {
|
| 1016 |
+
const dot=document.getElementById('bkWashDot');
|
| 1017 |
+
if(this.checked){
|
| 1018 |
+
dot.style.left='25px';
|
| 1019 |
+
dot.parentElement.previousElementSibling.style.background='rgba(233,69,96,.5)';
|
| 1020 |
+
} else {
|
| 1021 |
+
dot.style.left='3px';
|
| 1022 |
+
dot.parentElement.previousElementSibling.style.background='rgba(255,255,255,.12)';
|
| 1023 |
+
}
|
| 1024 |
+
try{tg.HapticFeedback.impactOccurred('light')}catch(e){}
|
| 1025 |
+
});
|
| 1026 |
+
|
| 1027 |
+
loadHome();
|
| 1028 |
+
</script>
|
| 1029 |
+
</body>
|
| 1030 |
+
</html>
|