脚本从头到尾运行顺利,但没有发生预期的结果

Bro*_* IF 4 javascript social-networking google-chrome-devtools

该项目旨在研究一种新的社交媒体:

\n

https://booyah.live/

\n

我的需求是:

\n

1 - 从遵循特定配置文件的配置文件中收集数据。

\n

2 - 我的帐户使用此数据来跟踪收集的个人资料。

\n

3 - 除其他可能的选项外,还可以取消关注我关注的个人资料。

\n

当前脚本中发现的问题:

\n

理论上正在收集配置文件数据,脚本完美运行直到最后,但由于某种原因我无法指定,它不是遵循所有收集的配置文件,而是仅遵循基本配置文件。

\n

例如:

\n

我想关注该 ID 后面的所有 250 个个人资料123456

\n

我激活booyahGetAccounts(123456);脚本

\n

理论上,最终结果是我的帐户关注了 250 个个人资料

\n

但最终结果我只关注了个人123456资料,所以我关注的人数是 1

\n

完整的项目脚本:

\n
const csrf = \'MY_CSRF_TOKEN\';\nasync function booyahGetAccounts(uid, type = \'followers\', follow = 1) {\n    if (typeof uid !== \'undefined\' && !isNaN(uid)) {\n        const loggedInUserID = window.localStorage?.loggedUID;\n        if (uid === 0) uid = loggedInUserID;\n        const unfollow = follow === -1;\n        if (unfollow) follow = 1;\n        if (loggedInUserID) {\n            if (csrf) {\n                async function getUserData(uid) {\n                    const response = await fetch(`https://booyah.live/api/v3/users/${uid}`),\n                          data = await response.json();\n                    return data.user;\n                }\n                const loggedInUserData = await getUserData(loggedInUserID),\n                      targetUserData = await getUserData(uid),\n                      followUser = uid => fetch(`https://booyah.live/api/v3/users/${loggedInUserID}/followings`, { method: (unfollow ? \'DELETE\' : \'POST\'), headers: { \'X-CSRF-Token\': csrf }, body: JSON.stringify({ followee_uid: uid, source: 43 }) }),\n                      logSep = (data = \'\', usePad = 0) => typeof data === \'string\' && usePad ? console.log((data ? data + \' \' : \'\').padEnd(50, \'\xe2\x94\x81\')) : console.log(\'\xe2\x94\x81\'.repeat(50),data,\'\xe2\x94\x81\'.repeat(50));\n                async function getList(uid, type, follow) {\n                    const isLoggedInUser = uid === loggedInUserID;\n                    if (isLoggedInUser && follow && !unfollow && type === \'followings\') {\n                        follow = 0;\n                        console.warn(\'You alredy follow your followings. `follow` mode switched to `false`. Followings will be retrieved instead of followed.\');\n                    }\n                    const userData = await getUserData(uid),\n                          totalCount = userData[type.slice(0,-1)+\'_count\'] || 0,\n                          totalCountStrLength = totalCount.toString().length;\n                    if (totalCount) {\n                        let userIDsLength = 0;\n                        const userIDs = [],\n                              nickname = userData.nickname,\n                              nicknameStr = `${nickname ? ` of ${nickname}\'s ${type}` : \'\'}`,\n                              alreadyFollowedStr = uid => `User ID ${uid} already followed by ${loggedInUserData.nickname} (Account #${loggedInUserID})`;\n                        async function followerFetch(cursor = 0) {\n                            const fetched = [];\n                            await fetch(`https://booyah.live/api/v3/users/${uid}/${type}?cursor=${cursor}&count=100`).then(res => res.json()).then(data => {\n                                const list = data[type.slice(0,-1)+\'_list\'];\n                                if (list?.length) fetched.push(...list.map(e => e.uid));\n                                if (fetched.length) {\n                                    userIDs.push(...fetched);\n                                    userIDsLength += fetched.length;\n                                    if (follow) followUser(uid);\n                                    console.log(`${userIDsLength.toString().padStart(totalCountStrLength)} (${(userIDsLength / totalCount * 100).toFixed(4)}%)${nicknameStr} ${follow ? \'followed\' : \'retrieved\'}`);\n                                    if (fetched.length === 100) {\n                                        followerFetch(data.cursor);\n                                    } else {\n                                        console.log(`END REACHED. ${userIDsLength} accounts ${follow ? \'followed\' : \'retrieved\'}.`);\n                                        if (!follow) logSep(targetList);\n                                    }\n                                }\n                            });\n                        }\n                        await followerFetch();\n                        return userIDs;\n                    } else {\n                        console.log(`This account has no ${type}.`);\n                    }\n                }\n                logSep(`${follow ? \'Following\' : \'Retrieving\'} ${targetUserData.nickname}\'s ${type}`, 1);\n                const targetList = await getList(uid, type, follow);\n            } else {\n                console.error(\'Missing CSRF token. Retrieve your CSRF token from the Network tab in your inspector by clicking into the Network tab item named "bug-report-claims" and then scrolling down in the associated details window to where you see "x-csrf-token". Copy its value and store it into a variable named "csrf" which this function will reference when you execute it.\');\n            }\n        } else {\n            console.error(\'You do not appear to be logged in. Please log in and try again.\');\n        }\n    } else {\n        console.error(\'UID not passed. Pass the UID of the profile you are targeting to this function.\');\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

当前的问题是链接中答案的延续:

\n

收集要遵循的按钮的完整列表,而无需滚动页面(DevTools Google Chrome)

\n

由于我无法就这个问题提供更多的赏金,因此我创建了这个赏金,以向任何能够修复错误并使脚本工作的人提供新的赏金。

\n
\n

访问 Booyah 网站上的帐户以用于测试:

\n

通过谷歌访问:

\n

用户:teststackoverflowbooyah@gmail.com

\n

密码: quartodemilha

\n
\n

Dan*_*cci 7

我不得不承认,阅读你的代码确实很难,我花了更少的时间从头开始重写所有内容。

声明我们需要在 Web 浏览器的 JavaScript 控制台中剪切/粘贴一段代码来存储一些数据(即关注过期和永久关注),我们需要一些考虑。

我们可以将以下内容的过期视为易失性数据:如果丢失,可以将其重置为我们丢失此数据后的 1 天。window.localStorage是存储此类数据的完美选择。如果我们更换网络浏览器,唯一的缺点是我们会失去关注的过期时间,并且我们可以容忍将它们重置为更换浏览器后的 1 天。

为了存储永久关注者列表,即使我们更改网络浏览器,我们也需要一个永久存储。我想到的最好的想法是创建一个替代帐户来关注我们永远不想停止关注的用户。在我使用的代码中uid 3186068(随机用户),一旦您创建了自己的替代帐户,只需将代码块的第一行替换为其uid

我们需要注意的另一件事是错误处理:API 可能总是有错误。我选择的方法是myFetch这样编写,如果出现错误,则重试两次相同的调用;如果错误仍然存​​在,我们可能面临暂时的booyah.live中断。也许我们只需要稍后重试。

为了尝试提供舒适的界面,代码块从window.location收集uid:要关注用户的关注者,只需将代码块剪切/粘贴到其个人资料上打开的选项卡上即可。例如,我从 上打开的选项卡运行代码。https://booyah.live/studio/123456?source=44

最后,为了取消关注用户,clean我们将在 5 分钟后调用该函数(以免与关注关注者的调用发生冲突),并在一小时后执行它完成其工作。它被编写为以原子方式访问localStorage ,因此您可以让其中许多同时在同一浏览器的不同选项卡上运行,您可以不关心它。您唯一需要注意的是,当window.location更改时,选项卡中的所有 JavaScript 事件都会重置;所以我建议在主页上保持一个选项卡打开,将代码块粘贴到上面,然后忘记这个选项卡;它将是负责取消关注用户的选项卡。然后打开其他选项卡执行您需要的操作,当您点击要关注关注者的用户时,将块粘贴到其上,等待工作完成并继续正常使用该选项卡。

// The account we use to store followings
const followingsUID = 3186068;
// Gather the loggedUID from window.localStorage
const { loggedUID } = window.localStorage;
// Gather the CSRF-Token from the cookies
const csrf = document.cookie.split("; ").reduce((ret, _) => (_.startsWith("session_key=") ? _.substr(12) : ret), null);

// APIs could have errors, let's do some retries
async function myFetch(url, options, attempt = 0) {
  try {
    const res = await fetch("https://booyah.live/api/v3/" + url, options);
    const ret = await res.json();

    return ret;
  } catch(e) {
    // After too many consecutive errors, let's abort: we need to retry later
    if(attempt === 3) throw e;

    return myFetch(url, option, attempt + 1);
  }
}

function expire(uid, add = true) {
  const { followingsExpire } = window.localStorage;
  let expires = {};

  try {
    // Get and parse followingsExpire from localStorage
    expires = JSON.parse(followingsExpire);
  } catch(e) {
    // In case of error (ex. new browsers) simply init to empty
    window.localStorage.followingsExpire = "{}";
  }

  if(! uid) return expires;

  // Set expire after 1 day
  if(add) expires[uid] = new Date().getTime() + 3600 * 24 * 1000;
  else delete expires[uid];

  window.localStorage.followingsExpire = JSON.stringify(expires);
}

async function clean() {
  try {
    const expires = expire();
    const now = new Date().getTime();

    for(const uid in expires) {
      if(expires[uid] < now) {
        await followUser(parseInt(uid), false);
        expire(uid, false);
      }
    }
  } catch(e) {}

  // Repeat clean in an hour
  window.setTimeout(clean, 3600 * 1000);
}

async function fetchFollow(uid, type = "followers", from = 0) {
  const { cursor, follower_list, following_list } = await myFetch(`users/${uid}/${type}?cursor=${from}&count=50`);
  const got = (type === "followers" ? follower_list : following_list).map(_ => _.uid);
  const others = cursor ? await fetchFollow(uid, type, cursor) : [];

  return [...got, ...others];
}

async function followUser(uid, follow = true) {
  console.log(`${follow ? "F" : "Unf"}ollowing ${uid}...`);
  return myFetch(`users/${loggedUID}/followings`, {
    method:  follow ? "POST" : "DELETE",
    headers: { "X-CSRF-Token": csrf },
    body:    JSON.stringify({ followee_uid: uid, source: 43 })
  });
}

async function doAll() {
  if(! loggedUID) throw new Error("Can't get 'loggedUID' from localStorage: try to login again");
  if(! csrf) throw new Error("Can't get session token from cookies: try to login again");

  console.log("Fetching current followings...");
  const currentFollowings = await fetchFollow(loggedUID, "followings");

  console.log("Fetching permanent followings...");
  const permanentFollowings = await fetchFollow(followingsUID, "followings");

  console.log("Syncing permanent followings...");
  for(const uid of permanentFollowings) {
    expire(uid, false);

    if(currentFollowings.indexOf(uid) === -1) {
      await followUser(uid);
      currentFollowings.push(uid);
    }
  }

  // Sync followingsExpire in localStorage
  for(const uid of currentFollowings) if(permanentFollowings.indexOf(uid) === -1) expire(uid);
  // Call first clean task in 5 minutes
  window.setTimeout(clean, 300 * 1000);

  // Gather uid from window.location
  const match = /\/studio\/(\d+)/.exec(window.location.pathname);

  if(match) {
    console.log("Fetching this user followers...");
    const followings = await fetchFollow(parseInt(match[1]));

    for(const uid of followings) {
      if(currentFollowings.indexOf(uid) === -1) {
        await followUser(uid);
        expire(uid);
      }
    }
  }

  return "Done";
}

await doAll();
Run Code Online (Sandbox Code Playgroud)

问题:我强烈怀疑 booyah.live API 存在错误

为了测试我的代码,我从https://booyah.live/studio/123456?source=44.

如果我多次运行它,我会继续得到以下输出:

Fetching current followings...
Fetching permanent followings...
Syncing permanent followings...
Following 1801775...
Following 143823...
Following 137017...
Fetching this user followers...
Following 16884042...
Following 16166724...
Run Code Online (Sandbox Code Playgroud)

某处有bug!在同一选项卡中后续执行的预期输出将是:

Fetching current followings...
Fetching permanent followings...
Syncing permanent followings...
Fetching this user followers...
Run Code Online (Sandbox Code Playgroud)

在代码中查找错误但没有成功后,我检查了booyah.live API:如果我导航以下 URL(uid代码在后续执行中继续遵循的 URL)

https://booyah.live/studio/1801775
https://booyah.live/studio/143823
https://booyah.live/studio/137017
https://booyah.live/studio/16884042
https://booyah.live/studio/16166724
Run Code Online (Sandbox Code Playgroud)

我可以清楚地看到我关注了他们,但是如果我导航https://booyah.live/following(我关注的用户列表),我找不到他们,即使我滚动页面直到最后也找不到他们。

由于我执行与网站完全相同的调用,因此我强烈怀疑该错误存在于booyah.live API 中,正是它们处理参数的方式cursor

我建议您向booyah.live支持团队开具支持票。您可以使用您向我们提供的测试帐户:我已经向您提供了执行此操作的详细信息。;)