Last active
May 26, 2023 04:17
-
-
Save DrayChou/b1f4b6445f5946d7136ff55da65d216a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name 抓取30天之前的推文ID | |
// @namespace dray | |
// @version 1.0 | |
// @description 抓取 Twitter 我的推文页面中30天之前的所有推文的ID | |
// @author dray | |
// @match https://twitter.com/* | |
// @grant none | |
// @run-at document-start | |
// @require unsafe-inline | |
// @require unsafe-eval | |
// @connect * | |
// @run-at document-idle | |
// ==/UserScript== | |
(function () { | |
"use strict"; | |
// 删除多少天之前的推文 | |
var deletefromdays = 30; | |
// 获取推文元素 | |
var tweetScreenNameIds = []; | |
// 存储页面高度 | |
var scrollHeight = 0; | |
const getOwnerScreenName = () => { | |
var t1 = document | |
.querySelector('div[data-testid="UserName"]') | |
.innerText.trim(); | |
if (t1.length < 1) { | |
return ""; | |
} | |
var t2 = t1.split("@"); | |
if (t2.length < 2) { | |
return ""; | |
} | |
return t2[1]; | |
}; | |
const query30dayTweet = (ownerScreenName, next) => { | |
var tweets = []; | |
var tweetList = document.querySelector( | |
'section[aria-labelledby="accessible-list-1"]>div[aria-label]' | |
); | |
if (tweetList != null) { | |
tweets = tweetList.querySelectorAll('[data-testid="tweet"]'); | |
} | |
if (tweets.length == 0) { | |
tweetList = document.querySelector( | |
'section[aria-labelledby="accessible-list-0"]>div[aria-label]' | |
); | |
if (tweetList != null) { | |
tweets = tweetList.querySelectorAll('[data-testid="tweet"]'); | |
} | |
} | |
if (tweets.length == 0) { | |
console.log("tweets.length == 0"); | |
return; | |
} | |
// 获取3天之前的时间戳(毫秒) | |
var thirtyDaysAgo = | |
new Date().getTime() - deletefromdays * 24 * 60 * 60 * 1000; | |
// 遍历推文元素,获取30天之前发布的推文ID | |
for (var tweet of tweets) { | |
// 获取推文发布时间 | |
var time = tweet.querySelector("time").getAttribute("datetime"); | |
// 将时间转换为时间戳(毫秒) | |
var timestamp = new Date(time).getTime(); | |
// 如果推文发布时间早于30天之前,则将推文ID存入tweetScreenNameIds数组 | |
if (timestamp < thirtyDaysAgo) { | |
// 获取推文ID | |
var url = tweet | |
.querySelector("time") | |
.parentElement.getAttribute("href"); | |
// 从 url 中取得当前的 screen_name 和 当前的 tweetId | |
var screen_name = url.split("/")[1]; | |
var tweetId = url.split("/")[3]; | |
console.log(screen_name, tweetId); | |
tweetScreenNameIds.push([screen_name, tweetId]); | |
} | |
} | |
}; | |
const delete30dayTweet = () => { | |
// 在 a 标签 href="/settings/profile" 的按钮旁边添加 删除30天之前推文 的按钮 | |
var a = document.querySelector('a[href="/settings/profile"]'); | |
if (a == null) { | |
alert("請先進入個人主頁面"); | |
return; | |
} | |
// 获取推文列表容器元素 | |
// 输出结果 | |
console.log("tweetScreenNameIds.length", tweetScreenNameIds.length); | |
if (tweetScreenNameIds.length == 0) { | |
console.log("没有需要删除的推文了"); | |
// 把页面滚动条滚动到最下面 | |
window.scrollTo(0, document.body.scrollHeight); | |
setTimeout(function () { | |
// 如果窗口页面高度跟之前比较没有变化,那么认为已经加载完毕了,停止 | |
if (scrollHeight == document.body.scrollHeight) { | |
console.log("已经到底了"); | |
return; | |
} | |
scrollHeight = document.body.scrollHeight; | |
query30dayTweet(); | |
setTimeout(function () { | |
delete30dayTweet(); | |
}, 1000); | |
}, 1000); | |
return; | |
} | |
// 获取当前页面的用户名 screen_name | |
var owner_screen_name = getOwnerScreenName(); | |
// 如果 tweetScreenNameIds 里有ID,那么弹出第一个元素 | |
var kv = tweetScreenNameIds.shift(); | |
var sName = kv[0]; | |
var tId = kv[1]; | |
var tweetUrl = document.querySelector(`a[href="/${sName}/status/${tId}"]`); | |
if (tweetUrl == null) { | |
console.log("tweet == null", sName, tId); | |
delete30dayTweet(); | |
return; | |
} | |
// 备份这些文件到文本文件并下载到本地 | |
// 创建需要下载的文本内容 | |
const content = | |
tweetUrl.closest("article").innerText + | |
"\n\nhttps://twitter.com/" + | |
tweetUrl.href; | |
// 创建 Blob 对象 | |
const blob = new Blob([content], { type: "text/plain;charset=utf-8" }); | |
// 生成下载链接 | |
const url = URL.createObjectURL(blob); | |
// 创建 a 标签用于下载 | |
const link = document.createElement("a"); | |
link.href = url; | |
link.download = sName + "-" + tId + ".txt"; | |
// 触发点击事件 | |
document.body.appendChild(link); | |
link.click(); | |
// 释放 URL 对象 | |
URL.revokeObjectURL(url); | |
// 如果是我自己的推文,那么删除 | |
if (sName === owner_screen_name) { | |
// 找到 刪除 这个文字所在的 div 元素所在的删除按钮 | |
var caret = tweetUrl | |
.closest("article") | |
.querySelector('div[data-testid="caret"]'); | |
if (caret == null) { | |
console.log("caret == null", sName, tId); | |
delete30dayTweet(); | |
return; | |
} | |
caret.click(); | |
// 200 毫秒之后点击删除按钮 | |
setTimeout(function () { | |
var menuitem = document.querySelector( | |
'div[role="menu"] div[role="menuitem"][tabindex="0"]' | |
); | |
if (menuitem == null) { | |
console.log("menuitem == null", sName, tId); | |
delete30dayTweet(); | |
return; | |
} | |
menuitem.click(); | |
//200 毫秒之后点击确认 | |
setTimeout(function () { | |
var confirmationSheetConfirm = document.querySelector( | |
'div[data-testid="confirmationSheetConfirm"]' | |
); | |
if (confirmationSheetConfirm == null) { | |
console.log("confirmationSheetConfirm == null", sName, tId); | |
delete30dayTweet(); | |
return; | |
} | |
confirmationSheetConfirm.click(); | |
}, 200); | |
}, 200); | |
} else { | |
// 否则,找到 转推按钮,点击取消转推 | |
var unretweet = tweetUrl | |
.closest("article") | |
.querySelector('div[data-testid="unretweet"]'); | |
if (unretweet == null) { | |
console.log("unretweet == null", sName, tId); | |
delete30dayTweet(); | |
return; | |
} | |
unretweet.click(); | |
// 点击转推按钮 | |
// 200 毫秒后,点击确认取消转推按钮 | |
setTimeout(function () { | |
var unretweetConfirm = document.querySelector( | |
'div[role="menu"] div[data-testid="unretweetConfirm"]' | |
); | |
if (unretweetConfirm == null) { | |
console.log("unretweetConfirm == null", sName, tId); | |
delete30dayTweet(); | |
return; | |
} | |
unretweetConfirm.click(); | |
}, 200); | |
} | |
// 如果 tweetScreenNameIds 里有ID,那么回调他自己 | |
setTimeout(function () { | |
delete30dayTweet(); | |
}, 1000); | |
}; | |
// 在页面加载完成后执行 | |
window.onload = function () { | |
console.log("页面加载完成!"); | |
// 延迟10秒执行下面的代码 | |
setTimeout(function () { | |
// 执行添加按钮的代码 | |
// 添加浮动悬浮窗,悬浮在右侧上下居中,里面是一个按钮,按钮名称是 删除30天之前推文 | |
var div = document.createElement("div"); | |
div.style.position = "fixed"; | |
div.style.right = "0px"; | |
div.style.top = "30%"; | |
div.style.transform = "translateY(-70%)"; | |
div.style.zIndex = "9999"; | |
div.style.padding = "10px"; | |
div.style.backgroundColor = "#1da1f2"; | |
div.style.color = "white"; | |
div.style.border = "none"; | |
div.style.borderRadius = "5px"; | |
div.style.fontSize = "16px"; | |
div.style.fontWeight = "bold"; | |
div.style.cursor = "pointer"; | |
div.style.width = "140px"; | |
// button 之前添加一个输入框,提示用户输入要删除多少天前的推文 | |
div.innerHTML = `<input type="number" style="padding: 10px; border-radius: 5px; margin-bottom: 10px; width: 108px;" placeholder="请输入要删除多少天前的推文" /><br/><button style="padding: 10px; background-color: #1da1f2; color: white; border-radius: 5px; font-size: 16px;">删除之前的推文</button>`; | |
div.querySelector("button").onclick = delete30dayTweet; | |
// input 框的内容如果发生改变,那么修改全局变量 deletefromdays 的值 | |
div.querySelector("input").onchange = function (e) { | |
// 转为 int 数字 | |
deletefromdays = parseInt(e.target.value); | |
}; | |
div.querySelector("input").value = deletefromdays; | |
document.body.appendChild(div); | |
console.log("添加按钮完成!"); | |
}, 3000); | |
}; | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment