tt-help-cli-ycl 1.3.17 → 1.3.19
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/package.json +1 -1
- package/scripts/run-explore copy.bat +68 -0
- package/src/cli/attach.js +54 -34
- package/src/watch/data-store.js +78 -31
- package/src/watch/server.js +17 -1
package/package.json
CHANGED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
@ECHO OFF
|
|
2
|
+
SETLOCAL
|
|
3
|
+
|
|
4
|
+
SET "PACKAGENAME=tt-help-cli-ycl"
|
|
5
|
+
SET "TARGET_SERVER=http://117.71.53.99:17301"
|
|
6
|
+
SET "CONFIG_PATH=%USERPROFILE%\.tt-help.json"
|
|
7
|
+
|
|
8
|
+
ECHO ========================================
|
|
9
|
+
ECHO tt-help-cli-ycl one-click launcher (Windows CMD)
|
|
10
|
+
ECHO ========================================
|
|
11
|
+
|
|
12
|
+
REM ---------- 1. Check/install latest version ----------
|
|
13
|
+
FOR /F "delims=" %%V IN ('npm view %PACKAGENAME% version 2^>nul') DO SET "LATEST_VERSION=%%V"
|
|
14
|
+
|
|
15
|
+
IF NOT DEFINED LATEST_VERSION (
|
|
16
|
+
ECHO [ERROR] Cannot get latest version from npm
|
|
17
|
+
EXIT /B 1
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
FOR /F "tokens=2 delims=@" %%V IN ('npm list -g %PACKAGENAME% --depth=0 2^>nul ^| findstr /i "%PACKAGENAME%"') DO SET "INSTALLED_VERSION=%%V"
|
|
21
|
+
|
|
22
|
+
IF NOT DEFINED INSTALLED_VERSION (
|
|
23
|
+
ECHO [INFO] %PACKAGENAME% not installed, installing latest...
|
|
24
|
+
CALL npm install -g %PACKAGENAME%
|
|
25
|
+
IF %ERRORLEVEL% EQU 0 (
|
|
26
|
+
ECHO [OK] Installed: %LATEST_VERSION%
|
|
27
|
+
) ELSE (
|
|
28
|
+
ECHO [ERROR] Install failed, run manually: npm install -g %PACKAGENAME%
|
|
29
|
+
EXIT /B 1
|
|
30
|
+
)
|
|
31
|
+
) ELSE IF "%INSTALLED_VERSION%"=="%LATEST_VERSION%" (
|
|
32
|
+
ECHO [OK] %PACKAGENAME% is up to date: %LATEST_VERSION%
|
|
33
|
+
) ELSE (
|
|
34
|
+
ECHO [INFO] Current: %INSTALLED_VERSION%, Latest: %LATEST_VERSION%
|
|
35
|
+
ECHO [INFO] Upgrading to latest...
|
|
36
|
+
CALL npm install -g %PACKAGENAME%
|
|
37
|
+
IF %ERRORLEVEL% EQU 0 (
|
|
38
|
+
ECHO [OK] Upgraded: %LATEST_VERSION%
|
|
39
|
+
) ELSE (
|
|
40
|
+
ECHO [WARN] Upgrade failed, run manually: npm install -g %PACKAGENAME%
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
REM ---------- 2. Check/set server config ----------
|
|
45
|
+
SET "CURRENT_SERVER="
|
|
46
|
+
IF EXIST "%CONFIG_PATH%" (
|
|
47
|
+
FOR /F "delims=" %%S IN ('node -e "try{const c=JSON.parse(require('fs').readFileSync('%CONFIG_PATH%','utf-8'));console.log(c.server||'')}catch(e){}" 2^>nul') DO SET "CURRENT_SERVER=%%S"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
IF "%CURRENT_SERVER%"=="%TARGET_SERVER%" (
|
|
51
|
+
ECHO [OK] Server config is correct: %TARGET_SERVER%
|
|
52
|
+
) ELSE (
|
|
53
|
+
IF "%CURRENT_SERVER%"=="" (
|
|
54
|
+
ECHO [INFO] Current server: not set, target: %TARGET_SERVER%
|
|
55
|
+
) ELSE (
|
|
56
|
+
ECHO [INFO] Current server: %CURRENT_SERVER%, target: %TARGET_SERVER%
|
|
57
|
+
)
|
|
58
|
+
ECHO [INFO] Setting server config...
|
|
59
|
+
node -e "const fs=require('fs'),path=require('path');const p=path.join(require('os').homedir(),'.tt-help.json');let c={};try{c=JSON.parse(fs.readFileSync(p,'utf-8'))}catch(e){}c.server='%TARGET_SERVER%';fs.writeFileSync(p,JSON.stringify(c,null,2),'utf-8');console.log(' Written to: '+p);"
|
|
60
|
+
ECHO [OK] Server config set
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
REM ---------- 3. Start tt-help explore ----------
|
|
64
|
+
ECHO.
|
|
65
|
+
ECHO ========================================
|
|
66
|
+
ECHO Starting tt-help explore
|
|
67
|
+
ECHO ========================================
|
|
68
|
+
tt-help explore --port 9223 --profile p9223
|
package/src/cli/attach.js
CHANGED
|
@@ -22,10 +22,11 @@ async function apiGet(url) {
|
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
|
|
26
|
+
async function apiPost(url, body) {
|
|
27
|
+
return withRetry(`POST ${url}`, async () => {
|
|
27
28
|
const res = await fetch(url, {
|
|
28
|
-
method: '
|
|
29
|
+
method: 'POST',
|
|
29
30
|
headers: { 'Content-Type': 'application/json' },
|
|
30
31
|
body: JSON.stringify(body),
|
|
31
32
|
});
|
|
@@ -111,37 +112,56 @@ export async function handleAttach(options) {
|
|
|
111
112
|
})
|
|
112
113
|
);
|
|
113
114
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
115
|
+
let successCount = 0;
|
|
116
|
+
let failCount = 0;
|
|
117
|
+
let needRestart = false;
|
|
118
|
+
|
|
119
|
+
// 收集抓取成功的任务,记录抓取失败的
|
|
120
|
+
const successTasks = [];
|
|
121
|
+
for (const result of results) {
|
|
122
|
+
if (result.status === 'fulfilled') {
|
|
123
|
+
const { uniqueId, info, error } = result.value;
|
|
124
|
+
if (error) {
|
|
125
|
+
if (isBrowserClosedError(error)) {
|
|
126
|
+
needRestart = true;
|
|
127
|
+
}
|
|
128
|
+
console.error(` ✗ @${uniqueId} 获取失败: ${error.message || '未知错误'}`);
|
|
129
|
+
failCount++;
|
|
130
|
+
} else if (info) {
|
|
131
|
+
successTasks.push({ uniqueId, info });
|
|
132
|
+
} else {
|
|
133
|
+
console.error(` ✗ @${uniqueId} 未获取到用户信息`);
|
|
134
|
+
failCount++;
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
console.error(` ✗ 任务执行异常: ${result.reason?.message || '未知错误'}`);
|
|
138
|
+
failCount++;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 批量提交成功的结果
|
|
143
|
+
if (successTasks.length > 0) {
|
|
144
|
+
try {
|
|
145
|
+
const batchRet = await apiPost(`${serverUrl}/api/user-info-batch`, { updates: successTasks });
|
|
146
|
+
if (batchRet && batchRet.results) {
|
|
147
|
+
for (const r of batchRet.results) {
|
|
148
|
+
if (r.ok) {
|
|
149
|
+
successCount++;
|
|
150
|
+
console.error(` ✓ @${r.uniqueId} 已提交更新`);
|
|
151
|
+
} else {
|
|
152
|
+
failCount++;
|
|
153
|
+
console.error(` ✗ @${r.uniqueId} 提交失败: ${r.error}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
successCount = successTasks.length;
|
|
158
|
+
console.error(` ✓ 批量提交完成 (${successTasks.length} 条)`);
|
|
159
|
+
}
|
|
160
|
+
} catch (err) {
|
|
161
|
+
failCount += successTasks.length;
|
|
162
|
+
console.error(` ✗ 批量提交失败: ${err.message}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
145
165
|
|
|
146
166
|
console.error(` 本批结果: ${successCount} 成功, ${failCount} 失败\n`);
|
|
147
167
|
|
package/src/watch/data-store.js
CHANGED
|
@@ -138,44 +138,69 @@ export function createStore(filePath) {
|
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
// 异步 save:不阻塞事件循环,排队保证顺序
|
|
142
|
+
// 多次 save 调用之间如果已有 save 在进行,只标记需要再次保存
|
|
143
|
+
let saveInFlight = false;
|
|
144
|
+
let pendingSave = false;
|
|
145
|
+
|
|
141
146
|
function save() {
|
|
142
147
|
if (!filePath) return;
|
|
148
|
+
if (saveInFlight) {
|
|
149
|
+
pendingSave = true;
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
doSave();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function doSave() {
|
|
156
|
+
saveInFlight = true;
|
|
143
157
|
const resolved = path.resolve(filePath);
|
|
144
158
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
159
|
+
try {
|
|
160
|
+
// 将 done 用户归档到独立文件
|
|
161
|
+
if (doneArchivePath && data.length > 1000) {
|
|
162
|
+
const active = [];
|
|
163
|
+
const toArchive = [];
|
|
164
|
+
for (const u of data) {
|
|
165
|
+
if (u.status === 'done') {
|
|
166
|
+
toArchive.push(u);
|
|
167
|
+
} else {
|
|
168
|
+
active.push(u);
|
|
169
|
+
}
|
|
154
170
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
171
|
+
if (toArchive.length > 0) {
|
|
172
|
+
doneArchive = doneArchive.concat(toArchive);
|
|
173
|
+
data.length = 0;
|
|
174
|
+
data.push(...active);
|
|
175
|
+
rebuildIndex();
|
|
176
|
+
doneUidIndex = new Map();
|
|
177
|
+
for (let i = 0; i < doneArchive.length; i++) {
|
|
178
|
+
doneUidIndex.set(doneArchive[i].uniqueId, i);
|
|
179
|
+
}
|
|
164
180
|
}
|
|
165
181
|
}
|
|
166
|
-
}
|
|
167
182
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
183
|
+
const json = JSON.stringify(data);
|
|
184
|
+
const tmpPath = resolved + '.tmp';
|
|
185
|
+
await fs.promises.writeFile(tmpPath, json, 'utf-8');
|
|
186
|
+
await fs.promises.rename(tmpPath, resolved);
|
|
187
|
+
|
|
188
|
+
// 写 done 归档
|
|
189
|
+
if (doneArchivePath && doneArchive.length > 0) {
|
|
190
|
+
const doneJson = JSON.stringify(doneArchive);
|
|
191
|
+
const doneTmp = doneArchivePath + '.tmp';
|
|
192
|
+
await fs.promises.writeFile(doneTmp, doneJson, 'utf-8');
|
|
193
|
+
await fs.promises.rename(doneTmp, doneArchivePath);
|
|
194
|
+
}
|
|
195
|
+
} catch (e) {
|
|
196
|
+
console.error(`[DataStore] save error: ${e.message}`);
|
|
197
|
+
} finally {
|
|
198
|
+
saveInFlight = false;
|
|
199
|
+
// 如果有排队的 save,继续执行
|
|
200
|
+
if (pendingSave) {
|
|
201
|
+
pendingSave = false;
|
|
202
|
+
doSave();
|
|
203
|
+
}
|
|
179
204
|
}
|
|
180
205
|
}
|
|
181
206
|
|
|
@@ -605,6 +630,28 @@ export function createStore(filePath) {
|
|
|
605
630
|
return { ok: true, userUpdateCount: user.userUpdateCount };
|
|
606
631
|
}
|
|
607
632
|
|
|
633
|
+
function batchUpdateUserInfo(updates) {
|
|
634
|
+
const results = [];
|
|
635
|
+
for (const item of updates) {
|
|
636
|
+
const user = getUser(item.uniqueId);
|
|
637
|
+
if (!user) {
|
|
638
|
+
results.push({ uniqueId: item.uniqueId, error: 'user not found' });
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
for (const key of Object.keys(item.info)) {
|
|
642
|
+
if (key === 'uniqueId' || key === 'sources') continue;
|
|
643
|
+
if (item.info[key] !== undefined && item.info[key] !== null && item.info[key] !== '') {
|
|
644
|
+
user[key] = item.info[key];
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
user.userUpdateCount = (user.userUpdateCount || 0) + 1;
|
|
648
|
+
user.updatedAt = Date.now();
|
|
649
|
+
results.push({ uniqueId: item.uniqueId, ok: true, userUpdateCount: user.userUpdateCount });
|
|
650
|
+
}
|
|
651
|
+
save();
|
|
652
|
+
return results;
|
|
653
|
+
}
|
|
654
|
+
|
|
608
655
|
// 视频登记
|
|
609
656
|
function registerVideos(sourceUser, videoList, locationCreated, ttSeller) {
|
|
610
657
|
if (!videoList || !Array.isArray(videoList) || videoList.length === 0) {
|
|
@@ -649,7 +696,7 @@ export function createStore(filePath) {
|
|
|
649
696
|
getPendingUsers, getProcessedUsers, getAllUsers, getStats,
|
|
650
697
|
claimNextJob, commitJob, commitNewExplore, resetJob, togglePin,
|
|
651
698
|
getNextRedoJob, commitRedoJob,
|
|
652
|
-
getPendingUserUpdateTasks, updateUserInfo,
|
|
699
|
+
getPendingUserUpdateTasks, updateUserInfo, batchUpdateUserInfo,
|
|
653
700
|
reportClientError, deleteClientError, getClientErrors,
|
|
654
701
|
registerVideos, getVideos, getVideoCount,
|
|
655
702
|
stopBackup,
|
package/src/watch/server.js
CHANGED
|
@@ -309,7 +309,23 @@ export function startWatchServer(outputFile, port = 3000, existingStore) {
|
|
|
309
309
|
return;
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
-
|
|
312
|
+
if (req.method === 'POST' && routePath === '/api/user-info-batch') {
|
|
313
|
+
try {
|
|
314
|
+
const body = await readBody(req);
|
|
315
|
+
const updates = body.updates || [];
|
|
316
|
+
const results = store.batchUpdateUserInfo(updates);
|
|
317
|
+
const okCount = results.filter(r => r.ok).length;
|
|
318
|
+
const errCount = results.filter(r => r.error).length;
|
|
319
|
+
const ts = new Date().toISOString().slice(11, 19);
|
|
320
|
+
console.error(`[JOB ${ts}] USER-INFO-BATCH: ${okCount} ok, ${errCount} error (total=${updates.length})`);
|
|
321
|
+
sendJSON(res, 200, { results, total: updates.length, ok: okCount, error: errCount });
|
|
322
|
+
} catch (e) {
|
|
323
|
+
sendJSON(res, 400, { error: e.message });
|
|
324
|
+
}
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const userInfoCommitMatch = routePath.match(/^\/api\/user-info\/([^/]+)$/);
|
|
313
329
|
if (req.method === 'PUT' && userInfoCommitMatch) {
|
|
314
330
|
const uniqueId = userInfoCommitMatch[1];
|
|
315
331
|
try {
|