소스 검색

修「视频评论区 + 当前屏幕」取材取不到人(三层原因)

用户配置:起点=某个视频的评论区、视频来源=当前屏幕。他手动停在评论区,
点运行后报"取材设置为当前屏幕,但当前不在播放页(feed)"。

层 1:idflow 里有个全局 ensureFeed,把用户准备好的页面退掉了
  runSession 一启动就无条件 ensureFeed,把评论面板连按几次返回退到首页,
  然后 prepareVideo 报"不在播放页"。
  但各取材源自己就会在需要时导航(anchor/search 走 prepareSearch,内部有 ensureFeed),
  这个全局调用纯属多此一举,专门破坏「当前屏幕」类取材。→ 删掉。

层 2(更隐蔽):app.launchPackage() 会把已在前台的 app 重新 startActivity
  删掉 ensureFeed 后仍失败,日志显示 launch 就绪(56ms)却已回到 feed。
  真凶:launchPackage 不是"切到前台"而是"重新打开 app",即使已在前台也会再发一次
  startActivity,把叠在主界面上的播放页/评论面板清掉退回首页。56ms 完成但不可逆。
  → launchApp 先判 foregroundPackage,是就什么都不做。

层 3:walkCommenters 漏了 waitProfile
  它还是 tap → settle(3000) → where()!=='profile' 就报错的老写法,而 openRow 早就改用
  waitProfile(等 @username + 三组数字)。settle 提前返回 + where() 要求已渲染,
  两头一凑就是"人已经进主页了却判成没进去"。→ 导出 waitProfilePublic 并改用它。

另外三处提示改成可操作的:
  · 当前屏幕取材要求页面停在用户列表上,否则直接说清并要求换页面
  · prepareVideo 失败时给出三步可操作建议,不再只说"不在播放页"

真机验证(手动停在 18830 条评论的评论区)
  [launch] 已在前台,保持当前页面不动
  [idsource] 已在播放页,直接用当前屏幕的视频
  收录 4 个评论者(unknowndude7267 / dianadiaz5714 / sillypumpkin61 / startgooning)
  产物 4 行,重复 2 个

通则:**"启动应用"不能做成"重置到首页"**。凡是依赖"用户当前页面"的功能,
启动环节必须保证"已在前台就不动",否则用户准备的前置状态被引擎自己毁掉,
还表现成"你不在那个页面",让人完全想不到是引擎退的。
梦境 1 주 전
부모
커밋
5134ac4b08
5개의 변경된 파일과 125개의 추가작업 그리고 27개의 파일을 삭제
  1. 63 14
      tiktok-auto/dist/tiktok-auto.js
  2. 22 3
      tiktok-auto/src/flow.js
  3. 13 5
      tiktok-auto/src/idflow.js
  4. 25 5
      tiktok-auto/src/idsource.js
  5. 2 0
      tiktok-auto/src/profile.js

+ 63 - 14
tiktok-auto/dist/tiktok-auto.js

@@ -1,5 +1,5 @@
 /*! tiktok-auto v1.0.0
- *  built 2026-10-21T15:03:41.961Z
+ *  built 2026-10-22T06:54:25.473Z
  *  modules: logger, bridge, config, wait, layout, ui, popups, tiktok, player, comment, flow, parse, uidset, filters, profile, idsource, resolve, idflow, main
  *  由 tools/build.js 自动生成 —— 请不要直接编辑本文件,改 src/ 后重新构建。
  */
@@ -3260,11 +3260,30 @@ TikTokAuto.flow = (function () {
 
     function launchApp(config) {
         var step = 'launch';
-        logger().step(step, '启动 ' + config.target.packageName);
-        app.launchPackage(config.target.packageName);
+        var pkg = config.target.packageName;
+
+        /* ★ 已经在前台就**绝对别动它**。
+         *
+         * `app.launchPackage()` 不是"切到前台",而是"重新打开这个 app" ——
+         * 即使 app 已经在前台,它也会再发一次 startActivity,把叠在主界面上的
+         * 页面(视频播放页、评论面板、用户列表)清掉,退回首页。
+         *
+         * 对「当前屏幕」类取材这是致命的:用户手动把 TikTok 停在评论区,
+         * 那正是这个取材源的全部前提,而引擎一启动就把它退成了 feed,
+         * 然后再报"取材设置为当前屏幕,但当前不在播放页"。
+         * 实测(2026-10-22):日志里 `launch 就绪(56ms)` 看着毫秒级完成、
+         * 什么都没干,实际上页面已经被重置了 —— 时间是 56ms,副作用却不可逆。
+         */
+        if (TikTokAuto.layout.foregroundPackage() === pkg) {
+            logger().info(step, '已在前台,保持当前页面不动');
+            return true;
+        }
+
+        logger().step(step, '启动 ' + pkg);
+        app.launchPackage(pkg);
 
         var ready = TikTokAuto.wait.waitFor(step, function () {
-            return TikTokAuto.layout.foregroundPackage() === config.target.packageName ? true : null;
+            return TikTokAuto.layout.foregroundPackage() === pkg ? true : null;
         }, config.timeouts.appLaunchMs, config.probe.pollTickMs);
 
         return ready === true;
@@ -5048,6 +5067,8 @@ TikTokAuto.profile = (function () {
         switchListTab: switchListTab,
         listRows: listRows,
         isListPage: isListPage,
+        /** 给外部用的"等主页就绪":等 @username + 三组数字,见 waitProfile 的注释 */
+        waitProfilePublic: function () { return waitProfile('waitProfile'); },
         scrollList: scrollList,
         openRow: openRow,
         backToList: backToList,
@@ -5324,9 +5345,16 @@ TikTokAuto.idsource = (function () {
                 ctx.seen.add(marker);
 
                 TikTokAuto.ui.tapPoint(rows[i].x, rows[i].y);
-                TikTokAuto.profile.settle(3000);
-                if (TikTokAuto.profile.where() !== 'profile') {
-                    log.warn('idsource', '点评论者头像没有进入用户主页');
+                /* ★ 用 waitProfile(等 @username + 三组数字),而不是 settle + where()。
+                 *
+                 * 早先这里是"等页面安静 3 秒,再看 where() 是不是 profile":
+                 *   · settle 会提前返回(骨架先出、内容后加载,签名看起来没变);
+                 *   · 而 where() 要求 `@username` 已经渲染出来。
+                 * 两头一凑,就成了"人明明已经进主页了,却被判成没进去"——
+                 * 实测日志里连续两条"点评论者头像没有进入用户主页",然后一条都没取到。
+                 */
+                if (!TikTokAuto.profile.waitProfilePublic()) {
+                    log.warn('idsource', '点评论者头像没有进入用户主页(等了 10 秒)');
                     continue;
                 }
                 var profile = TikTokAuto.profile.readProfile();
@@ -5398,7 +5426,16 @@ TikTokAuto.idsource = (function () {
         }
 
         if (source.kind === 'screen') {
-            /* 当前屏幕:不做任何导航,直接把眼前这一屏当列表读 */
+            /* 当前屏幕:不做任何导航,直接把眼前这一屏当列表读。
+             *
+             * 但要先确认眼前确实是"一个用户列表" —— 否则 walkList 会一屏一屏地读,
+             * 最后报"处理 0 行",用户完全不知道是页面不对。 */
+            var state = TikTokAuto.profile.where();
+            if (!TikTokAuto.profile.isListPage(state)) {
+                log.warn('idsource', '当前屏幕取材:需要先把页面停在「用户列表」(粉丝/关注/搜索用户)上,' +
+                    '引擎不会替你跳转。现在停在「' + state + '」');
+                return 0;
+            }
             return walkList(ctx);
         }
 
@@ -5554,7 +5591,11 @@ TikTokAuto.idsource = (function () {
                 log.info('idsource', '已在播放页,直接用当前屏幕的视频');
                 return true;
             }
-            log.warn('idsource', '取材设置为「当前屏幕」,但当前不在播放页(' + state + ')');
+            /* 这里的提示必须是**可操作**的:用户能做的只有三件事 —— 自己先把页面停好、
+             * 改用关键词模式、或者换一个取材源。不能只说"不在播放页"。 */
+            log.warn('idsource', '「当前屏幕」需要用你手上已经打开的那个视频,引擎不会替你跳转。\n' +
+                '  请先在 TikTok 里打开视频(或直接打开它的评论区),再点「开始运行」。\n' +
+                '  当前页面是「' + state + '」。');
             return false;
         }
 
@@ -6412,11 +6453,19 @@ TikTokAuto.idflow = (function () {
                 summary.errors.push('启动应用失败');
                 return summary;
             }
-            progress('进入信息流', 0);
-            if (!TikTokAuto.profile.ensureFeed(config)) {
-                summary.errors.push('未能进入信息流首页');
-                return summary;
-            }
+            /* ★ 这里**刻意不做 ensureFeed**。
+             *
+             * 早先这里无条件把页面退回信息流首页,于是「当前屏幕」类的取材必然失败:
+             * 用户手动把 TikTok 停在视频评论区(那正是这个取材源的全部前提),
+             * 引擎一上来就连按几次返回退到首页,然后再报"取材设置为当前屏幕,
+             * 但当前不在播放页(feed)"。用户看到的是"我明明停在评论区,它说我不在"。
+             *
+             * 各个取材源自己知道自己需要什么前置状态,让它们自己保证:
+             *   · 指定账号 / 搜索 → 走 prepareSearch,内部会 ensureFeed;
+             *   · 视频/当前屏幕     → **不动页面**,就用用户准备好的那一屏;
+             *   · 文本文件          → 根本不需要 UI。
+             */
+            progress('准备取材', 0);
 
             if (settings.sources.length === 0) {
                 summary.errors.push('没有配置任何取材(界面里至少需要一条)');

+ 22 - 3
tiktok-auto/src/flow.js

@@ -20,11 +20,30 @@ TikTokAuto.flow = (function () {
 
     function launchApp(config) {
         var step = 'launch';
-        logger().step(step, '启动 ' + config.target.packageName);
-        app.launchPackage(config.target.packageName);
+        var pkg = config.target.packageName;
+
+        /* ★ 已经在前台就**绝对别动它**。
+         *
+         * `app.launchPackage()` 不是"切到前台",而是"重新打开这个 app" ——
+         * 即使 app 已经在前台,它也会再发一次 startActivity,把叠在主界面上的
+         * 页面(视频播放页、评论面板、用户列表)清掉,退回首页。
+         *
+         * 对「当前屏幕」类取材这是致命的:用户手动把 TikTok 停在评论区,
+         * 那正是这个取材源的全部前提,而引擎一启动就把它退成了 feed,
+         * 然后再报"取材设置为当前屏幕,但当前不在播放页"。
+         * 实测(2026-10-22):日志里 `launch 就绪(56ms)` 看着毫秒级完成、
+         * 什么都没干,实际上页面已经被重置了 —— 时间是 56ms,副作用却不可逆。
+         */
+        if (TikTokAuto.layout.foregroundPackage() === pkg) {
+            logger().info(step, '已在前台,保持当前页面不动');
+            return true;
+        }
+
+        logger().step(step, '启动 ' + pkg);
+        app.launchPackage(pkg);
 
         var ready = TikTokAuto.wait.waitFor(step, function () {
-            return TikTokAuto.layout.foregroundPackage() === config.target.packageName ? true : null;
+            return TikTokAuto.layout.foregroundPackage() === pkg ? true : null;
         }, config.timeouts.appLaunchMs, config.probe.pollTickMs);
 
         return ready === true;

+ 13 - 5
tiktok-auto/src/idflow.js

@@ -387,11 +387,19 @@ TikTokAuto.idflow = (function () {
                 summary.errors.push('启动应用失败');
                 return summary;
             }
-            progress('进入信息流', 0);
-            if (!TikTokAuto.profile.ensureFeed(config)) {
-                summary.errors.push('未能进入信息流首页');
-                return summary;
-            }
+            /* ★ 这里**刻意不做 ensureFeed**。
+             *
+             * 早先这里无条件把页面退回信息流首页,于是「当前屏幕」类的取材必然失败:
+             * 用户手动把 TikTok 停在视频评论区(那正是这个取材源的全部前提),
+             * 引擎一上来就连按几次返回退到首页,然后再报"取材设置为当前屏幕,
+             * 但当前不在播放页(feed)"。用户看到的是"我明明停在评论区,它说我不在"。
+             *
+             * 各个取材源自己知道自己需要什么前置状态,让它们自己保证:
+             *   · 指定账号 / 搜索 → 走 prepareSearch,内部会 ensureFeed;
+             *   · 视频/当前屏幕     → **不动页面**,就用用户准备好的那一屏;
+             *   · 文本文件          → 根本不需要 UI。
+             */
+            progress('准备取材', 0);
 
             if (settings.sources.length === 0) {
                 summary.errors.push('没有配置任何取材(界面里至少需要一条)');

+ 25 - 5
tiktok-auto/src/idsource.js

@@ -261,9 +261,16 @@ TikTokAuto.idsource = (function () {
                 ctx.seen.add(marker);
 
                 TikTokAuto.ui.tapPoint(rows[i].x, rows[i].y);
-                TikTokAuto.profile.settle(3000);
-                if (TikTokAuto.profile.where() !== 'profile') {
-                    log.warn('idsource', '点评论者头像没有进入用户主页');
+                /* ★ 用 waitProfile(等 @username + 三组数字),而不是 settle + where()。
+                 *
+                 * 早先这里是"等页面安静 3 秒,再看 where() 是不是 profile":
+                 *   · settle 会提前返回(骨架先出、内容后加载,签名看起来没变);
+                 *   · 而 where() 要求 `@username` 已经渲染出来。
+                 * 两头一凑,就成了"人明明已经进主页了,却被判成没进去"——
+                 * 实测日志里连续两条"点评论者头像没有进入用户主页",然后一条都没取到。
+                 */
+                if (!TikTokAuto.profile.waitProfilePublic()) {
+                    log.warn('idsource', '点评论者头像没有进入用户主页(等了 10 秒)');
                     continue;
                 }
                 var profile = TikTokAuto.profile.readProfile();
@@ -335,7 +342,16 @@ TikTokAuto.idsource = (function () {
         }
 
         if (source.kind === 'screen') {
-            /* 当前屏幕:不做任何导航,直接把眼前这一屏当列表读 */
+            /* 当前屏幕:不做任何导航,直接把眼前这一屏当列表读。
+             *
+             * 但要先确认眼前确实是"一个用户列表" —— 否则 walkList 会一屏一屏地读,
+             * 最后报"处理 0 行",用户完全不知道是页面不对。 */
+            var state = TikTokAuto.profile.where();
+            if (!TikTokAuto.profile.isListPage(state)) {
+                log.warn('idsource', '当前屏幕取材:需要先把页面停在「用户列表」(粉丝/关注/搜索用户)上,' +
+                    '引擎不会替你跳转。现在停在「' + state + '」');
+                return 0;
+            }
             return walkList(ctx);
         }
 
@@ -491,7 +507,11 @@ TikTokAuto.idsource = (function () {
                 log.info('idsource', '已在播放页,直接用当前屏幕的视频');
                 return true;
             }
-            log.warn('idsource', '取材设置为「当前屏幕」,但当前不在播放页(' + state + ')');
+            /* 这里的提示必须是**可操作**的:用户能做的只有三件事 —— 自己先把页面停好、
+             * 改用关键词模式、或者换一个取材源。不能只说"不在播放页"。 */
+            log.warn('idsource', '「当前屏幕」需要用你手上已经打开的那个视频,引擎不会替你跳转。\n' +
+                '  请先在 TikTok 里打开视频(或直接打开它的评论区),再点「开始运行」。\n' +
+                '  当前页面是「' + state + '」。');
             return false;
         }
 

+ 2 - 0
tiktok-auto/src/profile.js

@@ -779,6 +779,8 @@ TikTokAuto.profile = (function () {
         switchListTab: switchListTab,
         listRows: listRows,
         isListPage: isListPage,
+        /** 给外部用的"等主页就绪":等 @username + 三组数字,见 waitProfile 的注释 */
+        waitProfilePublic: function () { return waitProfile('waitProfile'); },
         scrollList: scrollList,
         openRow: openRow,
         backToList: backToList,