cortico-world-mosey 2.4.0 → 2.4.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/CHANGELOG.md +18 -0
- package/package.json +1 -1
- package/src/douyin.js +95 -10
- package/src/world.js +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# 更新日志
|
|
2
2
|
|
|
3
|
+
## 2.4.1
|
|
4
|
+
|
|
5
|
+
**修抖音登录:不再隔几小时掉一次,扫完码也会立刻收窗。**
|
|
6
|
+
|
|
7
|
+
### 掉线:两个原因,都修了
|
|
8
|
+
|
|
9
|
+
- **登录态备份**:每 10 分钟把 cookie 存一份到 `stroll/douyin-cookies.json`,启动时如果 profile 里没有 `sessionid` 就补回去。以前进程被强杀(清残留进程用的是 `Stop-Process -Force`)时 Chromium 来不及把 cookie 落盘,轮换过的 `sessionid` 一丢就得重新扫码。
|
|
10
|
+
- **风控不再被当成掉线**:只有接口返回 `code 8`(真未登录)才判定掉线;`2154 / 2483` 这些「要验证 / 风控」的返回以前也被当成未登录,于是面板显示「未登录」,白扫一次码,过一会儿它自己又好了。
|
|
11
|
+
|
|
12
|
+
### 登录后窗口挂很久
|
|
13
|
+
|
|
14
|
+
两处都改了:
|
|
15
|
+
|
|
16
|
+
- 以前登录成功后会先**另开一页读昵称**(几秒到十几秒),读完才关窗。现在昵称直接从登录校验里带出来,扫完就关。
|
|
17
|
+
- 更关键的是 `sessionAlive` 有 5 分钟缓存:扫码**之前**面板查过一次「未登录」,这个结论被缓存住了,扫完码仍然返回未登录,非要等缓存过期才认。现在登录轮询每次都强制刷新,扫完 1-2 秒内收窗。
|
|
18
|
+
|
|
3
19
|
## 2.4.0
|
|
4
20
|
|
|
5
21
|
**有人在视频评论区 @ 她,现在她会知道了。**
|
|
@@ -22,6 +38,8 @@
|
|
|
22
38
|
|
|
23
39
|
## 2.3.0
|
|
24
40
|
|
|
41
|
+
> 这一版被 npm 暂存(staged),没能上架;内容已随 2.4.0 一起发布。
|
|
42
|
+
|
|
25
43
|
**B站也能抽帧了——用播放器进度条的预览图,不用登录、不开播放页、不抢你的浏览器。**
|
|
26
44
|
|
|
27
45
|
以前设置里的「看视频抽几帧」只对抖音有效:抖音没有字幕,只能靠截图看画面;B站是纯接口读取(字幕/弹幕/评论),从头到尾没打开过播放页,所以那个数字对 B站一直是空的。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cortico-world-mosey",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "摸鱼(Mosey):让 Coopanion 的桌宠自己去抖音和 B站逛一圈。评论和私信都能收能回;能一起看视频(B站带字幕弹幕,抖音可自动连刷);关注流、周报、本地书库;按额度点赞、评论、关注、发动态。默认人审,三把工具、少 token。\n\nQQ 交流群:745879632",
|
|
6
6
|
"license": "MIT",
|
package/src/douyin.js
CHANGED
|
@@ -758,6 +758,8 @@ export class DouyinBrowser {
|
|
|
758
758
|
});
|
|
759
759
|
this.context = context;
|
|
760
760
|
this.headless = headless !== false;
|
|
761
|
+
// 上次进程被强杀、cookie 没来得及落盘时,用备份补一次(缺 sessionid 才补)
|
|
762
|
+
await this.restoreCookies().catch(() => {});
|
|
761
763
|
return context;
|
|
762
764
|
})()
|
|
763
765
|
.catch((error) => {
|
|
@@ -1109,6 +1111,60 @@ export class DouyinBrowser {
|
|
|
1109
1111
|
return null;
|
|
1110
1112
|
}
|
|
1111
1113
|
|
|
1114
|
+
get cookieFile() {
|
|
1115
|
+
return path.join(this.dataDir, 'stroll', 'douyin-cookies.json');
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
/**
|
|
1119
|
+
* 把当前登录态另存一份到磁盘。
|
|
1120
|
+
* profile 里的 Cookies 由 Chromium 自己管:进程被强杀(reapOrphans 用的是 Stop-Process -Force)、
|
|
1121
|
+
* 或者没来得及 flush 时,最近轮换过的 sessionid 会丢,下次启动就变成「未登录」——
|
|
1122
|
+
* 表现出来就是每隔几个小时要重新扫码。有这份备份就能在启动时补回去。
|
|
1123
|
+
*/
|
|
1124
|
+
async saveCookies({ force = false } = {}) {
|
|
1125
|
+
if (!this.context) return 0;
|
|
1126
|
+
const now = Date.now();
|
|
1127
|
+
if (!force && this.cookiesSavedAt && now - this.cookiesSavedAt < 10 * 60 * 1000) return 0;
|
|
1128
|
+
this.cookiesSavedAt = now;
|
|
1129
|
+
const list = await this.context.cookies().catch(() => []);
|
|
1130
|
+
const useful = list.filter((c) => /(^|\.)(douyin|bytedance|amemv|snssdk)\.(com|cn)$/i.test(String(c.domain ?? '')));
|
|
1131
|
+
if (!useful.length) return 0;
|
|
1132
|
+
try {
|
|
1133
|
+
fs.mkdirSync(path.dirname(this.cookieFile), { recursive: true });
|
|
1134
|
+
fs.writeFileSync(this.cookieFile, JSON.stringify({ at: new Date(now).toISOString(), cookies: useful }), { mode: 0o600 });
|
|
1135
|
+
} catch {
|
|
1136
|
+
return 0;
|
|
1137
|
+
}
|
|
1138
|
+
return useful.length;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
/** 启动时把备份补回 profile;只在缺 sessionid 时补,别覆盖页面刚写进去的新 cookie。 */
|
|
1142
|
+
async restoreCookies() {
|
|
1143
|
+
if (!this.context) return 0;
|
|
1144
|
+
let saved = null;
|
|
1145
|
+
try {
|
|
1146
|
+
saved = JSON.parse(fs.readFileSync(this.cookieFile, 'utf8'));
|
|
1147
|
+
} catch {
|
|
1148
|
+
return 0;
|
|
1149
|
+
}
|
|
1150
|
+
const cookies = Array.isArray(saved?.cookies) ? saved.cookies : [];
|
|
1151
|
+
if (!cookies.length) return 0;
|
|
1152
|
+
const now = Date.now();
|
|
1153
|
+
const live = await this.context.cookies().catch(() => []);
|
|
1154
|
+
const alive = live.some(
|
|
1155
|
+
(c) => (c.name === 'sessionid' || c.name === 'sid_tt') && (!c.expires || c.expires < 0 || c.expires * 1000 > now),
|
|
1156
|
+
);
|
|
1157
|
+
if (alive) return 0;
|
|
1158
|
+
// 过期的丢掉;session cookie(expires<=0)补一个 30 天,免得下次又被当成会话 cookie 丢掉
|
|
1159
|
+
const fixed = cookies
|
|
1160
|
+
.filter((c) => !c.expires || c.expires <= 0 || c.expires * 1000 > now)
|
|
1161
|
+
.map((c) => ({ ...c, expires: c.expires && c.expires > 0 ? c.expires : Math.floor(now / 1000) + 30 * 86400 }));
|
|
1162
|
+
if (!fixed.length) return 0;
|
|
1163
|
+
await this.context.addCookies(fixed).catch(() => {});
|
|
1164
|
+
this.log?.info?.('stroll douyin cookies restored', { n: fixed.length, from: saved.at });
|
|
1165
|
+
return fixed.length;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1112
1168
|
async login() {
|
|
1113
1169
|
return this.serialize(async () => {
|
|
1114
1170
|
await this.launch({ headless: false, force: true });
|
|
@@ -1136,15 +1192,28 @@ export class DouyinBrowser {
|
|
|
1136
1192
|
this.loginPoller = setInterval(async () => {
|
|
1137
1193
|
try {
|
|
1138
1194
|
if (!this.context) return;
|
|
1195
|
+
/**
|
|
1196
|
+
* 每次都要重新问一次服务端。
|
|
1197
|
+
* sessionAlive 有 5 分钟缓存:扫码之前面板查过一次「未登录」,这个 false 会被缓存住,
|
|
1198
|
+
* 扫完码 isLoggedIn 仍然返回 false,于是白白等到缓存过期才收窗——
|
|
1199
|
+
* 这就是「登录完了那个网页还挂着好久」的真正原因。
|
|
1200
|
+
*/
|
|
1201
|
+
this.sessionOkAt = 0;
|
|
1139
1202
|
if (await this.isLoggedIn()) {
|
|
1140
1203
|
this.markLoggedIn();
|
|
1141
1204
|
clearInterval(this.loginPoller);
|
|
1142
1205
|
this.loginPoller = null;
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1206
|
+
/**
|
|
1207
|
+
* 收窗要快:以前这里先 `selfProfile()`(另开一页读昵称,几秒到十几秒),
|
|
1208
|
+
* 读完才 close,主人在桌面上看着一个登录窗挂在那一动不动。
|
|
1209
|
+
* 昵称/uid 其实 sessionAlive() 已经顺手填好了,不用再跑一趟。
|
|
1210
|
+
*/
|
|
1211
|
+
await this.saveCookies({ force: true }).catch(() => {});
|
|
1146
1212
|
await this.close();
|
|
1147
|
-
|
|
1213
|
+
// 收窗后别留着「未登录」的旧缓存,不然面板还要再等 5 分钟才变绿
|
|
1214
|
+
this.sessionOk = true;
|
|
1215
|
+
this.sessionOkAt = Date.now();
|
|
1216
|
+
this.log?.info?.('stroll: 抖音登录成功,已切回后台', { who: this.selfName || '' });
|
|
1148
1217
|
} else if (Date.now() - started > 5 * 60 * 1000) {
|
|
1149
1218
|
clearInterval(this.loginPoller);
|
|
1150
1219
|
this.loginPoller = null;
|
|
@@ -1155,7 +1224,7 @@ export class DouyinBrowser {
|
|
|
1155
1224
|
} catch {
|
|
1156
1225
|
// 下一轮再看
|
|
1157
1226
|
}
|
|
1158
|
-
},
|
|
1227
|
+
}, 1200);
|
|
1159
1228
|
}
|
|
1160
1229
|
|
|
1161
1230
|
/**
|
|
@@ -1182,13 +1251,27 @@ export class DouyinBrowser {
|
|
|
1182
1251
|
})
|
|
1183
1252
|
.catch(() => ({ code: -1, name: '', uid: '' }));
|
|
1184
1253
|
const code = res?.code ?? -1;
|
|
1185
|
-
// 8 = 用户未登录;-1 是没问到,别拿它当结论
|
|
1186
|
-
if (code === -1) return null;
|
|
1187
1254
|
if (res.name) this.selfName = res.name;
|
|
1188
1255
|
if (res.uid) this.selfUid = res.uid;
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1256
|
+
/**
|
|
1257
|
+
* 只有下面三种结论:
|
|
1258
|
+
* 0 → 活着
|
|
1259
|
+
* 8 → 真的未登录(要重新扫码)
|
|
1260
|
+
* 其它 → 不知道(-1 没问到;2154/2483 是风控/需要验证)
|
|
1261
|
+
* 以前把「其它」一律当成掉线,结果风控一来面板就显示未登录,
|
|
1262
|
+
* 主人白扫一次码,过一会儿又好了——「每隔几小时掉线」有一半是这个误报。
|
|
1263
|
+
*/
|
|
1264
|
+
if (code === 0) {
|
|
1265
|
+
this.sessionOk = true;
|
|
1266
|
+
this.sessionOkAt = now;
|
|
1267
|
+
return true;
|
|
1268
|
+
}
|
|
1269
|
+
if (code === 8) {
|
|
1270
|
+
this.sessionOk = false;
|
|
1271
|
+
this.sessionOkAt = now;
|
|
1272
|
+
return false;
|
|
1273
|
+
}
|
|
1274
|
+
return null;
|
|
1192
1275
|
}
|
|
1193
1276
|
|
|
1194
1277
|
/** 她自己这个号的昵称(群里用来区分「谁说的」,拿不到就空串)。 */
|
|
@@ -6871,6 +6954,8 @@ export class DouyinBrowser {
|
|
|
6871
6954
|
if (this.closeTimer) clearTimeout(this.closeTimer);
|
|
6872
6955
|
this.closeTimer = null;
|
|
6873
6956
|
this.holdOpen = false;
|
|
6957
|
+
// 关窗之前先备份登录态:进程被强杀时 Chromium 不一定来得及把 cookie 落盘
|
|
6958
|
+
await this.saveCookies({ force: true }).catch(() => {});
|
|
6874
6959
|
await this.closeWatch().catch(() => {});
|
|
6875
6960
|
await this.page?.close().catch(() => {});
|
|
6876
6961
|
this.page = null;
|
package/src/world.js
CHANGED
|
@@ -1239,6 +1239,9 @@ export class StrollWorld {
|
|
|
1239
1239
|
const health = await this.douyin.watchHealth?.().catch(() => null);
|
|
1240
1240
|
if (health) await this.repairWatch(health).catch(() => {});
|
|
1241
1241
|
}
|
|
1242
|
+
// 每 10 分钟把抖音登录态备份一次(内部自己节流):
|
|
1243
|
+
// 进程被强杀时 Chromium 不一定来得及把 cookie 落盘,丢了就得重新扫码
|
|
1244
|
+
if (useDy) this.douyin.saveCookies?.().catch(() => {});
|
|
1242
1245
|
// 群聊:观察器掉了就补挂(30 秒最多试一次)。
|
|
1243
1246
|
// 群名留空时靠自动识别;每 3 分钟滚动会话列表做一次深度读取,
|
|
1244
1247
|
// 这样刚被拉进的新群不用等旧名单过期就能被发现。
|