Greasy Fork is available in English.

twitter users export

export twitter follower

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         twitter users export
// @namespace    https://dun.mianbaoduo.com/@fun
// @version      0.1
// @description  export twitter follower
// @author       fun
// @match        *://twitter.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=twitter.com
// @grant        none
// @license      GPL
// ==/UserScript==

(function () {
  "use strict";

  let authorizationCode = `Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA`;

  function wait(ms) {
    return new Promise((resolve, reject) => {
      setTimeout(resolve, ms);
    });
  }

  function getCookie(name) {
    const value = `; ${document.cookie}`;
    const parts = value.split(`; ${name}=`);
    if (parts.length === 2) return parts.pop().split(";").shift();
  }

  function getUserMeta(user) {
    if (user.__typename != "User") return null;
    const {
      screen_name,
      entities,
      created_at,
      followers_count,
      friends_count,
      description,
      location,
      verified,
      statuses_count,
      profile_image_url_https,
      following,
      name,
    } = user.legacy;
    return {
      avatar: profile_image_url_https,
      userId: user.rest_id,
      created_at,
      description,
      followers_count,
      friends_count,
      location,
      verified,
      name,
      screen_name,
      statuses_count,
      following,
      url:
        entities.url && entities.url.urls.length
          ? entities.url.urls[0].expanded_url
          : null,
    };
  }

  class TwitterAPIHelper {
    static async fetchInfo(id) {
      const variables = encodeURIComponent(
        `{"screen_name":"${id}","withSafetyModeUserFields":true,"withSuperFollowsUserFields":false}`
      );
      const req = await fetch(
        "https://twitter.com/i/api/graphql/B-dCk4ph5BZ0UReWK590tw/UserByScreenName?variables=" +
          variables,
        {
          headers: {
            accept: "*/*",
            "accept-language": "zh-CN,zh;q=0.9,en-IN;q=0.8,en;q=0.7,ar;q=0.6",
            authorization: authorizationCode,
            "content-type": "application/json",
            "sec-ch-ua":
              '"Google Chrome";v="93", " Not;A Brand";v="99", "Chromium";v="93"',
            "sec-ch-ua-mobile": "?0",
            "sec-ch-ua-platform": '"macOS"',
            "sec-fetch-dest": "empty",
            "sec-fetch-mode": "cors",
            "sec-fetch-site": "same-origin",
            "x-csrf-token": getCookie("ct0"),
            "x-twitter-active-user": "yes",
            "x-twitter-auth-type": "OAuth2Session",
            "x-twitter-client-language": "en",
          },
          referrer: "https://twitter.com/avinash_81",
          referrerPolicy: "strict-origin-when-cross-origin",
          body: null,
          method: "GET",
          mode: "cors",
          credentials: "include",
        }
      );
      const info = await req.json();
      console.log(info);
      if (!info.data.user) return null;
      return info.data.user && getUserMeta(info.data.user.result);
    }

    static async getFollowing(id, count = 20) {
      // const csfrToken = getCookie('ct0');
      const args = {
        userId: "" + id + "",
        count: count,
        withTweetQuoteCount: false,
        includePromotedContent: false,
        withSuperFollowsUserFields: true,
        withUserResults: true,
        withNftAvatar: false,
        withBirdwatchPivots: false,
        withReactionsMetadata: false,
        withReactionsPerspective: false,
        withSuperFollowsTweetFields: true,
      };
      const data = encodeURIComponent(JSON.stringify(args));
      // const authorizationCode = this.authorization;
      if (authorizationCode == null) {
        throw new Error("please open twitter");
      }
      const req = await fetch(
        "https://twitter.com/i/api/graphql/Fl6SSu1BCrwN6m-rLacKqg/Following?variables=" +
          data,
        {
          headers: {
            accept: "*/*",
            "accept-language": "zh-CN,zh;q=0.9,en-IN;q=0.8,en;q=0.7,ar;q=0.6",
            authorization: authorizationCode,
            "content-type": "application/json",
            "sec-ch-ua":
              '"Google Chrome";v="93", " Not;A Brand";v="99", "Chromium";v="93"',
            "sec-ch-ua-mobile": "?0",
            "sec-ch-ua-platform": '"macOS"',
            "sec-fetch-dest": "empty",
            "sec-fetch-mode": "cors",
            "sec-fetch-site": "same-origin",
            "x-csrf-token": getCookie("ct0"),
            "x-twitter-active-user": "yes",
            "x-twitter-auth-type": "OAuth2Session",
            "x-twitter-client-language": "en",
          },
          referrer: "https://twitter.com/",
          referrerPolicy: "strict-origin-when-cross-origin",
          body: null,
          method: "GET",
          mode: "cors",
          credentials: "include",
        }
      );
      const followings = await req.json();
      // console.log(followings);
      const items =
        followings.data.user.result.timeline.timeline.instructions.find(
          (_) => _.type == "TimelineAddEntries"
        );
      // console.log(items);
      const users = items.entries
        .filter((_) => _.content.entryType == "TimelineTimelineItem")
        .map((_) => {
          const userItem = _.content.itemContent.user_results.result;
          const parsedMeta = getUserMeta(userItem);
          return {
            ...parsedMeta,
            sortIndex: _.sortIndex,
          };
        });
      return users;
    }

    static async getFollowers(id, count = 20, cursor = null) {
      const args = {
        userId: id,
        count: 20,
        cursor,
        includePromotedContent: false,
        withSuperFollowsUserFields: true,
        withDownvotePerspective: false,
        withReactionsMetadata: false,
        withReactionsPerspective: false,
        withSuperFollowsTweetFields: true,
      };

      const features = encodeURIComponent(
        JSON.stringify({
          dont_mention_me_view_api_enabled: true,
          interactive_text_enabled: true,
          responsive_web_uc_gql_enabled: false,
          vibe_tweet_context_enabled: false,
          responsive_web_edit_tweet_api_enabled: false,
          standardized_nudges_misinfo: false,
          responsive_web_enhance_cards_enabled: false,
        })
      );

      const data = encodeURIComponent(JSON.stringify(args));
      if (authorizationCode == null) {
        throw new Error("please open twitter");
      }
      const req = await fetch(
        "https://twitter.com/i/api/graphql/Fl6SSu1BCrwN6m-rLacKqg/Followers?variables=" +
          data +
          "&features=" +
          features,
        {
          headers: {
            accept: "*/*",
            "accept-language": "zh-CN,zh;q=0.9,en-IN;q=0.8,en;q=0.7,ar;q=0.6",
            authorization: authorizationCode,
            "content-type": "application/json",
            "sec-ch-ua":
              '"Google Chrome";v="93", " Not;A Brand";v="99", "Chromium";v="93"',
            "sec-ch-ua-mobile": "?0",
            "sec-ch-ua-platform": '"macOS"',
            "sec-fetch-dest": "empty",
            "sec-fetch-mode": "cors",
            "sec-fetch-site": "same-origin",
            "x-csrf-token": getCookie("ct0"),
            "x-twitter-active-user": "yes",
            "x-twitter-auth-type": "OAuth2Session",
            "x-twitter-client-language": "en",
          },
          referrer: "https://twitter.com/",
          referrerPolicy: "strict-origin-when-cross-origin",
          body: null,
          method: "GET",
          mode: "cors",
          credentials: "include",
        }
      );
      const followings = await req.json();
      // console.log(followings);
      const items =
        followings.data.user.result.timeline.timeline.instructions.find(
          (_) => _.type == "TimelineAddEntries"
        );
      // console.log(items);
      const cursors = items.entries
        .filter((_) => _.content.entryType == "TimelineTimelineCursor")
        .reduce((all, item) => {
          all[item.content.cursorType] = item.content.value;
          return all;
        }, {});
      const users = items.entries
        .filter((_) => _.content.entryType == "TimelineTimelineItem")
        .map((_) => {
          const userItem = _.content.itemContent.user_results.result;
          const parsedMeta = getUserMeta(userItem);
          return {
            ...parsedMeta,
            sortIndex: _.sortIndex,
          };
        });
      return {
        users,
        cursors,
      };
    }
  }

  class LSStore {
    async get(key) {
      const value = localStorage.getItem(key);
      if (value) {
        return JSON.parse(value);
      }
      return null;
    }

    async set(key, value) {
      return localStorage.setItem(key, JSON.stringify(value));
    }
  }

  async function getUsers(userName) {
    const user = await TwitterAPIHelper.fetchInfo(userName);
    console.log("user", user);
    let cursors = null;
    let userPools = [];
    for (let index = 0; index < Infinity; index++) {
      const followers = await TwitterAPIHelper.getFollowers(
        user.userId,
        20,
        cursors ? cursors.Bottom : null
      );
      printLog(`正在导出第 ${index+1} 页`);
      cursors = followers.cursors;
      console.log("followers", followers);

      if (followers.users.length === 0) break;
      if (userPools.length > 20) {
        userPools.shift();
      }
      userPools.push(followers.users);
      await new Promise((resolve, reject) => {
        setTimeout(resolve, 8 * 1000);
      });
    }

    download(
      []
        .concat(
          [
            [
              "userId",
              "name",
              "screen_name",
              "created_at",
              "followers_count",
              "url",
              "link"
            ].join(","),
          ],
          userPools
            .reduce((all, users) => {
              users.forEach((_) => {
                all.push(_);
              });
              return all;
            }, [])
            .map((_) => {
              return [
                _.userId,
                `"${_.name}"`,
                _.screen_name,
                _.created_at,
                //   description,
                _.followers_count,
                //   friends_count,
                //   location,
                //   verified,

                //   statuses_count,
                //   following,
                _.url,
                `https://twitter.com/${_.screen_name}`,
              ].join(",");
            })
        )
        .join("\n"),
      userName + ".csv",
      "text/plain"
    );

    //   return userPools;
  }

  let wrapper = document.createElement("div");

  let backup = document.createElement("div");

  backup.innerHTML = `<div><span id="bMSG"></span></div>
<div style="text-align: center;"></div>  `;

  function download(content, fileName, contentType) {
    var a = document.createElement("a");
    var file = new Blob([content], { type: contentType });
    a.href = URL.createObjectURL(file);
    a.download = fileName;
    a.click();
  }

  function printLog(msg) {
    tip.innerText = msg;
  }

  backup.setAttribute(
    "style",
    "display:none; background: white; color: black; font-size: 13px; padding: 10px 10px 15px 10px;"
  );

  //   backup.appendChild(tip);
  const title = document.createElement("h2");
  title.innerHTML = "粉丝导出";

  title.setAttribute("style", "font-size: 15px;color: black;margin: 15px 0;");
  wrapper.appendChild(title);
  wrapper.appendChild(backup);

  document.body.appendChild(wrapper);
  wrapper.setAttribute(
    "style",
    `position: fixed;
    border-radius: 3px;
    background: white;
    top: 80px;
    right: 20px;
    z-index: 100000;
    padding:10px 15px;
text-align: center; 
border: 1px solid #eee;
    border-radius: 5px;
   `
  );

  let started = false;

  let allButtons = [];
  function showAll() {
    allButtons.forEach((btn) => {
      btn.style.display = "block";
    });
  }

  function hideAll() {
    allButtons.forEach((btn) => {
      btn.style.display = "none";
    });
  }

  function createExport(name, type) {
    let btn = document.createElement("button");
    wrapper.appendChild(btn);
    btn.innerHTML = name;
    btn.setAttribute(
      "style",
      `border-radius: 0.166667rem; display: block; font-weight: bold; color: #444; margin:0 auto; padding: 5px 14px;font-size: 13px;text-align: center;border: 1px solid #cfcfcf;margin-top: 3px; cursor: pointer;  margin-bottom: 7px;`
    );
    btn.addEventListener("click", async () => {
      if (started) {
        alert("started");
        return;
      }
      started = true;
      hideAll();
      backup.style.display = "block";
    //   indicator.style.display = "inline-block";
      const tabTile = document.querySelector('[property="al:android:url"]');
      if (!tabTile) {
        alert('user not found')
        return;
      }
      const userName = document
        .querySelector('[property="al:android:url"]')
        .content.split("screen_name=")[1];
      console.log("getUsers", userName);
      await getUsers(userName);
      started = false;
      showAll();
    //   indicator.style.display = "none";
    });

    allButtons.push(btn);
  }
  let tip = document.getElementById("bMSG");
//   let indicator = document.getElementById("bIndicator");

  createExport("导出", "like");
})();