twitter-web-exporter icon indicating copy to clipboard operation
twitter-web-exporter copied to clipboard

导出 Following 数据时,如果有 Suspended 用户,会中止,提示 Error: Cannot read properties of undefined (reading 'screen_name')

Open justdn opened this issue 9 months ago • 2 comments

来自 Twitter 的粉丝,非常感谢作者能开发这个工具。

直接导出时卡住,Console 有输出错误。

错误信息如下:

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'created_at')
    at VMalzsdyq82vt.columnHelper.accessor.id [as accessorFn] (Twitter Web Exporter.user.js:2764:71)
    at Object.getValue (Twitter Web Exporter.user.js:19:46141)
    at Object.getValue (Twitter Web Exporter.user.js:19:45538)
    at onExport (Twitter Web Exporter.user.js:1299:30)
    at HTMLButtonElement.T (Twitter Web Exporter.user.js:3:4503)

相同界面,分页浏览 Following 数据时出现的错误

错误提示截图: image 错误文本:

Something went wrong.
Error: Cannot read properties of undefined (reading 'screen_name')

原因分析

原因是 Twitter 返回的数据中,混有 Suspended 用户。

Suspended 用户数据示例如下:

...
                                    },
                                    {
                                        "entryId": "user-1723415153759150080",
                                        "sortIndex": "1788044944628252472",
                                        "content": {
                                            "entryType": "TimelineTimelineItem",
                                            "__typename": "TimelineTimelineItem",
                                            "itemContent": {
                                                "itemType": "TimelineUser",
                                                "__typename": "TimelineUser",
                                                "user_results": {
                                                    "result": {
                                                        "__typename": "UserUnavailable",
                                                        "message": "User is suspended",
                                                        "reason": "Suspended"
                                                    }
                                                },
                                                "userDisplayType": "User"
                                            },
                                            "clientEventInfo": {
                                                "component": "FollowingSgs",
                                                "element": "user"
                                            }
                                        }
                                    },
                                    {
...

处理建议

浏览或导出时可能需要忽略 Suspended 用户数据。

找到问题就发出来了,完全不懂暴力猴工程,接下来可能会尝试修正一下,成功了来 PR 😌

justdn avatar May 08 '24 04:05 justdn

在 GPT 的帮助下,修改文件 src/modules/following/api.ts

diff --git a/src/modules/following/api.ts b/src/modules/following/api.ts
index 1e3184a..a7142a8 100644
--- a/src/modules/following/api.ts
+++ b/src/modules/following/api.ts
@@ -31,12 +31,15 @@ export const FollowingInterceptor: Interceptor = (req, res) => {
   }
 
   try {
-    const newData = extractDataFromResponse<FollowingResponse, User>(
+    const extractedData = extractDataFromResponse<FollowingResponse, User>(
       res,
       (json) => json.data.user.result.timeline.timeline.instructions,
       (entry) => entry.content.itemContent.user_results.result,
     );
 
+    // 使用 Array.filter() 方法过滤掉 __typename 不是 'User' 的数据项
+    const newData = extractedData.filter(user => user.__typename === 'User');
+
     // Add captured data to the global store.
     followingSignal.value = [...followingSignal.value, ...newData];

有效,但还是不要 PR 了,可能这不是最好的方法 ☹️

justdn avatar May 08 '24 06:05 justdn

哦哦,我没有考虑到这种情况,感谢你的反馈!

我后面有时间会针对这种情况修复一下。如果你愿意,也可以参考以下流程发个 PR,我很欢迎~(如果要 PR 的话,麻烦你基于 idb 分支修改,目前我是在这个分支上开发的)


你上面的修改只针对 Following 数据导出有效,但这个问题在导出其他用户列表时应该也可能会出现。所以更通用的解决方法是这样修改(所有用到了 extractDataFromResponse 的用户模块都需要修改):

// src/modules/following/api.ts

const newData = extractDataFromResponse<FollowingResponse, User>(
  res,
  (json) => json.data.user.result.timeline.timeline.instructions,
- (entry) => entry.content.itemContent.user_results.result,
+ (entry) => extractTimelineUser(entry.content.itemContent),
);

然后在 src/utils/api.tsextractTimelineTweet 方法的下面,类似地添加一个 extractTimelineUser 方法:

/**
 * Extract the user object from the timeline entry, ignoring unavailable users.
 */
export function extractTimelineUser(itemContent: TimelineUser): User | null {
  const user = itemContent.user_results.result;

  if (!user || user.__typename !== 'User') {
    logger.warn(
      "TimelineUser is empty. This could happen when the user's account is suspended or deleted.",
      itemContent,
    );
    return null;
  }

  return user;
}

最后,完善类型定义 src/types/user.ts

export interface TimelineUser {
  itemType: 'TimelineUser';
  __typename: 'TimelineUser';
  user_results: {
-   result: User;
+   result: User | UserUnavailable;
  };
  userDisplayType: string;
}

+export interface UserUnavailable {
+  __typename: 'UserUnavailable';
+  message: string;
+  reason: string;
+}

prinsss avatar May 08 '24 06:05 prinsss

此问题已在最新测试版中修复,如有问题欢迎反馈。

https://github.com/prinsss/twitter-web-exporter/releases/download/nightly/twitter-web-exporter.user.js

prinsss avatar Jun 17 '24 03:06 prinsss