twitter users export

export twitter follower

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==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");
})();