Skip to content

Instantly share code, notes, and snippets.

@lewangdev
Last active July 3, 2023 13:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lewangdev/03c70accaabb10ba9cbb86c86d0c6602 to your computer and use it in GitHub Desktop.
Save lewangdev/03c70accaabb10ba9cbb86c86d0c6602 to your computer and use it in GitHub Desktop.
Get Twitter followings and add them to your private list
(function(window) {
function saveAsLocalStorage(username) {
// 将 Set 转换为 Array
let followerIdsArray = Array.from(followerIds);
// 存储到localStorage
localStorage.setItem('MyFollowingIds', JSON.stringify(followerIdsArray));
}
function saveAsCSV(username) {
// 将 Set 转换为 Array
let followerIdsArray = Array.from(followerIds);
// 构造 CSV 字符串
let csvContent = "data:text/csv;charset=utf-8," + followerIdsArray.join("\n");
// 创建隐藏的可下载链接
var encodedUri = encodeURI(csvContent);
var link = document.createElement("a");
link.setAttribute("href", encodedUri);
link.setAttribute("download", `${username}-MyFollowingIds.csv`);
document.body.appendChild(link); // 为了在 Firefox 中工作,需要将链接添加到文档中
// 自动点击链接进行下载
link.click();
}
// 创建一个Set,用于存储followerId,避免重复
let followerIds = new Set();
// 获取当前页面的URL
let currentURL = window.location.href;
// 解析URL以获取用户名
let urlParts = currentURL.split("/");
let username = urlParts.length > 3 ? urlParts[3] : null;
// 构造需要的URL
let targetURL = `https://twitter.com/${username}/following`;
// 检查当前页面的URL
if (currentURL === targetURL) {
// 当前页面是指定的页面,执行收集followerId的逻辑
function getFollowerIds() {
var divs = document.querySelectorAll('div[aria-label="Timeline: Following"]');
divs.forEach(function(div) {
var followers = div.querySelectorAll('a[role="link"]');
for (var i = 0; i < followers.length; i++) {
var span = followers[i].querySelector('span'); // 在每个 a 标签中查找 span 标签
if (span) { // 如果找到了 span 标签
var id = span.textContent; // 获取 span 标签的文本内容
if (id.startsWith('@')) { // 如果文本内容以 @ 开头
var followerId = id.substring(1); // 从文本内容中提取 follower ID(去掉开头的 @)
followerIds.add(followerId); // 注意这里应该添加 followerId 而不是 id
}
}
}
});
}
// 监听滚动事件
window.addEventListener('scroll', getFollowerIds);
// 每隔一秒滚动一定的距离,并输出收集到的 followerIds
let scrollByVerticalInPixel = 500;
let previousSize = -1; // 初始化Set的大小为 -1
let noChangeCount = 0; // 初始化没有变化的次数为 0
let maxNoChangeCount = 50; // 达到最大相同次数就退出
let intervalID = setInterval(() => {
// 如果Set的大小在连续maxNoChangeCount次检查中都没有变化,则清除定时器
if (followerIds.size === previousSize) {
noChangeCount += 1;
if (noChangeCount >= maxNoChangeCount) {
clearInterval(intervalID);
saveAsLocalStorage(username);
saveAsCSV(username);
console.log("MyFollowing: Stopped scrolling. Final follower IDs:", followerIds);
}
} else {
console.log("MyFollowing: Current follower IDs:", followerIds);
previousSize = followerIds.size; // 更新Set的大小
noChangeCount = 0; // 重置没有变化的次数
}
// 无论是否发生变化,都滚动页面
window.scrollBy(0, scrollByVerticalInPixel);
}, 1000);
}
})(window);
(function(window) {
// 定义一个异步的添加函数
async function addById(id) {
// 填充输入框并触发 change 事件
let nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
let changeEvent = new Event('change', { bubbles: true});
let inputBox = document.querySelector('input[aria-label="Search query"]');
nativeInputValueSetter.call(inputBox, id);
inputBox.dispatchEvent(changeEvent);
// 等待一段时间,让页面有机会更新
await new Promise(resolve => setTimeout(resolve, 2000));
// 点击“添加”按钮
let addButton = document.querySelector('div[role="button"][aria-label="Add"]');
if (addButton) {
addButton.click();
}
// 再等待一段时间,让页面有机会更新
await new Promise(resolve => setTimeout(resolve, 2000));
}
// 从 localStorage 获取 ID 列表
let storedIds = JSON.parse(localStorage.getItem('MyFollowingIds')) || [];
// 按顺序添加所有 ID
async function addAllIds() {
for (let id of storedIds) {
await addById(id);
}
}
// 开始执行
addAllIds();
})(window);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment