wangka-helper 0.0.0-stage → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -2
- package/bin/wangka.js +134 -0
- package/package.json +32 -4
- package/public/index.html +1170 -0
- package/src/api.js +152 -0
- package/src/captcha.js +71 -0
- package/src/config.js +78 -0
- package/src/import.js +74 -0
- package/src/license.js +107 -0
- package/src/locker.js +236 -0
- package/src/login.js +48 -0
- package/src/server.js +331 -0
- package/src/store.js +146 -0
package/README.md
CHANGED
|
@@ -1,3 +1,64 @@
|
|
|
1
|
-
#
|
|
1
|
+
# wangka — 王卡十周年活动面板
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## 安装运行
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# 全局安装
|
|
7
|
+
npm install -g wangka-helper
|
|
8
|
+
wangka
|
|
9
|
+
|
|
10
|
+
# 或直接跑,不装
|
|
11
|
+
npx wangka-helper
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
打开 http://127.0.0.1:8765
|
|
15
|
+
|
|
16
|
+
要求在 Node 18 及以上。**短信登录需要 Node 22.22+ 或 24.15+**(jsdom 30 的限制),
|
|
17
|
+
低于这个版本只能用 KSESSIONID 导入。
|
|
18
|
+
|
|
19
|
+
## 参数
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
wangka --port 9000 # 换端口
|
|
23
|
+
wangka --host 0.0.0.0 # 对外监听(面板无密码,注意风险)
|
|
24
|
+
wangka --data-dir ~/.wangka # 账号与日志目录,默认当前目录
|
|
25
|
+
wangka --lock-hour 10 --lock-minute 0
|
|
26
|
+
wangka --lock-retry 1 # 锁定失败补发次数
|
|
27
|
+
wangka --no-auto-lock # 只保留手动锁定
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 数据文件
|
|
31
|
+
|
|
32
|
+
都在 `--data-dir` 下:
|
|
33
|
+
|
|
34
|
+
| 文件 | 内容 |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `accounts.json` | 账号 Cookie + 每账号的锁定目标(权限 600) |
|
|
37
|
+
| `logs/autolock-*.jsonl` | 每次锁定一行一个账号 |
|
|
38
|
+
| `logs/lockdetail-*.json` | 同上,附带完整请求/响应,便于排查 |
|
|
39
|
+
|
|
40
|
+
`accounts.json` 是明文登录凭据,等同账号密码,不要提交到仓库或共享目录。
|
|
41
|
+
|
|
42
|
+
## 登录
|
|
43
|
+
|
|
44
|
+
两种方式:
|
|
45
|
+
|
|
46
|
+
1. **短信登录** —— 填手机号 → 发验证码 → 填验证码。发码要先过腾讯防水墙,用 jsdom 跑官方脚本取票,约 2 秒。
|
|
47
|
+
2. **导入 KSESSIONID** —— 浏览器登录 txwk.10010.com 后,从 Cookie 复制 `KSESSIONID` 粘进来。
|
|
48
|
+
|
|
49
|
+
## 会员锁定
|
|
50
|
+
|
|
51
|
+
充值活动(充值送会员)里,`chargePre` 会把当前账号锁定到某个会员上,**锁 2 小时,期间不能更换**,也不会自动扣款充值。
|
|
52
|
+
|
|
53
|
+
- **目标按账号独立设置**,选中账号后在下拉里改,立即写入 `accounts.json`
|
|
54
|
+
- **自动锁定开关是全局的**
|
|
55
|
+
- 定时器每天 10:00(UTC+8)执行:提前 20 秒预取各账号的 `giftCode`,准点只发 `chargePre`,不额外查询
|
|
56
|
+
|
|
57
|
+
每个账号用自己配置的会员,不同账号可以锁不同会员。
|
|
58
|
+
|
|
59
|
+
## 开发
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npm install
|
|
63
|
+
node bin/wangka.js
|
|
64
|
+
```
|
package/bin/wangka.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// wangka —— 王卡十周年活动面板
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { AccountStore } from '../src/store.js';
|
|
8
|
+
import { AutoLocker } from '../src/locker.js';
|
|
9
|
+
import { createServer } from '../src/server.js';
|
|
10
|
+
import { assertNotExpired } from '../src/license.js';
|
|
11
|
+
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const pkgRoot = path.resolve(__dirname, '..');
|
|
14
|
+
|
|
15
|
+
function parseArgs(argv) {
|
|
16
|
+
const out = { host: '127.0.0.1', port: 8765, dataDir: process.cwd(), hour: 10, minute: 0, retry: 1, autoLock: true, importVerify: true };
|
|
17
|
+
for (let i = 0; i < argv.length; i++) {
|
|
18
|
+
const a = argv[i];
|
|
19
|
+
const next = () => argv[++i];
|
|
20
|
+
if (a === '--host') out.host = next();
|
|
21
|
+
else if (a === '--port') out.port = Number(next());
|
|
22
|
+
else if (a === '--data-dir') out.dataDir = path.resolve(next());
|
|
23
|
+
else if (a === '--lock-hour') out.hour = Number(next());
|
|
24
|
+
else if (a === '--lock-minute') out.minute = Number(next());
|
|
25
|
+
else if (a === '--lock-retry') out.retry = Number(next());
|
|
26
|
+
else if (a === '--no-auto-lock') out.autoLock = false;
|
|
27
|
+
else if (a === '--import') out.importFile = path.resolve(next());
|
|
28
|
+
else if (a === '--import-no-verify') { out.importFile = path.resolve(next()); out.importVerify = false; }
|
|
29
|
+
else if (a === '--help' || a === '-h') out.help = true;
|
|
30
|
+
else if (a === '--version' || a === '-v') out.version = true;
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const args = parseArgs(process.argv.slice(2));
|
|
36
|
+
|
|
37
|
+
if (args.help) {
|
|
38
|
+
console.log(`wangka —— 王卡十周年活动面板
|
|
39
|
+
|
|
40
|
+
用法: wangka [选项]
|
|
41
|
+
|
|
42
|
+
--port <n> 监听端口(默认 8765)
|
|
43
|
+
--host <ip> 监听地址(默认 127.0.0.1)
|
|
44
|
+
--data-dir <dir> 账号与日志目录(默认当前目录)
|
|
45
|
+
--lock-hour <h> 自动锁定小时(默认 10,UTC+8)
|
|
46
|
+
--lock-minute <m> 自动锁定分钟(默认 0)
|
|
47
|
+
--lock-retry <n> 失败补发次数(默认 1)
|
|
48
|
+
--no-auto-lock 只保留手动锁定
|
|
49
|
+
--import <file> 启动前导入账号文件(兼容 python 版 .accounts.json)
|
|
50
|
+
--import-no-verify 导入时不验证会话(更快,但可能带入失效账号)
|
|
51
|
+
-h, --help 显示帮助
|
|
52
|
+
-v, --version 显示版本
|
|
53
|
+
`);
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
|
56
|
+
if (args.version) {
|
|
57
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(pkgRoot, 'package.json'), 'utf8'));
|
|
58
|
+
console.log(pkg.version);
|
|
59
|
+
process.exit(0);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
fs.mkdirSync(args.dataDir, { recursive: true });
|
|
63
|
+
const store = new AccountStore(path.join(args.dataDir, 'accounts.json'));
|
|
64
|
+
const locker = new AutoLocker({
|
|
65
|
+
store,
|
|
66
|
+
logDir: path.join(args.dataDir, 'logs'),
|
|
67
|
+
hour: args.hour,
|
|
68
|
+
minute: args.minute,
|
|
69
|
+
retry: args.retry,
|
|
70
|
+
});
|
|
71
|
+
locker.enabled = args.autoLock;
|
|
72
|
+
|
|
73
|
+
// 账号导入:--import 显式指定,否则自动找 python 版账号文件
|
|
74
|
+
const needImport = async (file, verify) => {
|
|
75
|
+
const { importAccounts } = await import('../src/import.js');
|
|
76
|
+
console.log(`导入账号:${file}`);
|
|
77
|
+
const r = await importAccounts({ fromFile: file, store, verify });
|
|
78
|
+
console.log(` 成功 ${r.imported} 个`);
|
|
79
|
+
if (r.invalid.length) {
|
|
80
|
+
console.log(` 失效 ${r.invalid.length} 个:${r.invalid.map((x) => x.display).join('、')}`);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
let pendingImport = null;
|
|
85
|
+
if (args.importFile) {
|
|
86
|
+
if (!fs.existsSync(args.importFile)) {
|
|
87
|
+
console.error(`找不到文件:${args.importFile}`);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
pendingImport = [args.importFile, args.importVerify];
|
|
91
|
+
} else if (!store.all().length) {
|
|
92
|
+
const candidates = [
|
|
93
|
+
path.join(args.dataDir, 'app', '.accounts.json'),
|
|
94
|
+
path.join(args.dataDir, '..', 'app', '.accounts.json'),
|
|
95
|
+
path.join(pkgRoot, '..', 'app', '.accounts.json'),
|
|
96
|
+
path.join(process.cwd(), 'app', '.accounts.json'),
|
|
97
|
+
path.join(args.dataDir, '.accounts.json'),
|
|
98
|
+
];
|
|
99
|
+
const found = candidates.find((f) => fs.existsSync(f));
|
|
100
|
+
if (found) pendingImport = [found, true];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 运行期校验:只认网络时间,不看本机时钟
|
|
104
|
+
let license;
|
|
105
|
+
try {
|
|
106
|
+
license = await assertNotExpired();
|
|
107
|
+
} catch (e) {
|
|
108
|
+
console.error(`\n[${e.code || 'ERR'}] ${e.message}`);
|
|
109
|
+
if (e.networkTime) console.error(`网络时间:${e.networkTime}`);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const server = createServer({ baseDir: pkgRoot, store, locker });
|
|
114
|
+
|
|
115
|
+
if (pendingImport) {
|
|
116
|
+
await needImport(pendingImport[0], pendingImport[1]);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
server.listen(args.port, args.host, () => {
|
|
120
|
+
console.log(`王卡助手面板: http://${args.host}:${args.port}`);
|
|
121
|
+
console.log(`数据目录: ${args.dataDir}(已载入 ${store.all().length} 个账号)`);
|
|
122
|
+
console.log(`活动时间剩余: ${license.remainText}`);
|
|
123
|
+
if (locker.enabled) {
|
|
124
|
+
locker.start();
|
|
125
|
+
const detail = locker.status().accounts.map((a) => `${a.display}→${a.targetName}`).join('、') || '无账号';
|
|
126
|
+
console.log(`自动锁定: 每天 ${String(args.hour).padStart(2, '0')}:${String(args.minute).padStart(2, '0')} (UTC+8) | ${detail}`);
|
|
127
|
+
} else {
|
|
128
|
+
console.log('自动锁定: 已禁用(仅手动)');
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const bye = () => { locker.stop(); server.close(() => process.exit(0)); setTimeout(() => process.exit(0), 500); };
|
|
133
|
+
process.on('SIGINT', bye);
|
|
134
|
+
process.on('SIGTERM', bye);
|
package/package.json
CHANGED
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wangka-helper",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "王卡十周年活动面板:登录、签到、抽奖、中奖记录、10:00 定点锁定会员",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"wangka": "./bin/wangka.js"
|
|
8
|
+
},
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=18"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"jsdom": "^30.1.3"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"bin",
|
|
17
|
+
"src",
|
|
18
|
+
"public"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"start": "node bin/wangka.js"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"wangka",
|
|
25
|
+
"10010",
|
|
26
|
+
"unicom",
|
|
27
|
+
"lottery"
|
|
28
|
+
],
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "local"
|
|
33
|
+
}
|
|
34
|
+
}
|