// ==UserScript==
// @name X(Twitter)推特解析下载
// @namespace http://309096184.xyz/
// @version 1.0.0
// @description 自动识别X/Twitter推文链接,一键解析无水印视频下载
// @author 【Rain】黯笙
// @icon http://309096184.xyz/x/tb.webp
// @match *://*/*
// @grant GM_xmlhttpRequest
// @run-at document-start
// @license 黯笙
// ==/UserScript==
(function() {
const textBtn = "解析";
const textFail = "解析失败";
const textError = "解析出错";
const textClose = "关闭";
const loadingTipText = "正在解析中,请稍等";
const textDonate = "赞助作者";
const donateTip = "感谢支持作者【Rain】黯笙";
const donateImgUrl = "http://309096184.xyz/x/zz.png";
const API_TOKEN = "7kRf2Dp9vBnXtG5cLz0JmY8aP3sE6wU1hQ4dF";
const BACKEND_URL = "http://309096184.xyz/x/x.php";
const updateCheckUrl = "http://309096184.xyz/xcj/gx.txt";
const currentVersion = "1.0.0";
const textUpdate = "当前版本过低\n请前往葫芦侠三楼更新\n作者【Rain】黯笙";
function versionIsHigher(v1, v2) {
const arr1 = v1.split(".").map(Number);
const arr2 = v2.split(".").map(Number);
const maxLen = Math.max(arr1.length, arr2.length);
for(let i = 0; i < maxLen; i++){
const num1 = arr1[i] || 0;
const num2 = arr2[i] || 0;
if(num1 > num2) return true;
if(num1 < num2) return false;
}
return false;
}
function cleanTweetUrl(url) {
let u = url.trim();
u = u.replace(/#.*/, "");
u = u.replace(/(\/status\/\d+).*/, "$1");
return u;
}
function isTweetUrl(url) {
return /(?:twitter\.com|x\.com)\/[^\/]+\/status\/\d+/i.test(url);
}
function showLoadingToast(msg) {
const oldToast = document.getElementById("parseLoadingToast");
if (oldToast) oldToast.remove();
const toast = document.createElement("div");
toast.id = "parseLoadingToast";
toast.textContent = msg;
toast.style.cssText = [
"position:fixed !important",
"top:50% !important",
"left:50% !important",
"transform:translate(-50%,-50%) !important",
"background:rgba(0,0,0,0.75) !important",
"color:#fff !important",
"padding:14px 26px !important",
"border-radius:8px !important",
"font-size:15px !important",
"z-index:2147483647 !important",
"white-space:nowrap !important",
"font-family:Arial,Helvetica,sans-serif !important",
"writing-mode:horizontal-tb !important",
"direction:ltr !important",
"text-align:center !important"
].join(";");
document.body.appendChild(toast);
return toast;
}
let analyzeBtn = null;
function createAnalyzeBtn() {
if (analyzeBtn) return analyzeBtn;
const btn = document.createElement("button");
btn.textContent = textBtn;
btn.id = "xParseFloatBtn";
btn.style.cssText = [
"position:fixed !important",
"bottom:120px !important",
"right:20px !important",
"z-index:2147483647 !important",
"width:60px !important",
"height:60px !important",
"border-radius:50% !important",
"background:#007aff !important",
"color:#ffffff !important",
"border:none !important",
"font-size:14px !important",
"font-weight:600 !important",
"box-shadow:0 6px 16px rgba(0,122,255,0.4) !important",
"pointer-events:auto !important",
"margin:0 !important",
"padding:0 !important",
"line-height:60px !important",
"text-align:center !important",
"font-family:Arial,Helvetica,sans-serif !important",
"writing-mode:horizontal-tb !important",
"direction:ltr !important",
"text-shadow:none !important",
"text-transform:none !important",
"letter-spacing:normal !important",
"word-spacing:normal !important",
"clip:auto !important",
"clip-path:none !important",
"transform:none !important"
].join(";");
return btn;
}
function showBtn() {
if (!analyzeBtn) {
analyzeBtn = createAnalyzeBtn();
}
if (!analyzeBtn.parentElement) {
document.body.appendChild(analyzeBtn);
}
analyzeBtn.style.display = "block";
}
function hideBtn() {
if (analyzeBtn) {
analyzeBtn.style.display = "none";
}
}
function toggleBtn() {
if (isTweetUrl(location.href)) {
showBtn();
} else {
hideBtn();
}
}
function startAnalyze() {
const loadingToast = showLoadingToast(loadingTipText);
const cleanUrl = cleanTweetUrl(location.href);
const fullApi = `${BACKEND_URL}?token=${API_TOKEN}&url=${encodeURIComponent(cleanUrl)}`;
GM_xmlhttpRequest({
method: "GET",
url: fullApi,
timeout: 30000,
onload: function(res) {
loadingToast.remove();
try {
const data = JSON.parse(res.responseText);
if (data.code !== 200) {
alert(`${textFail}:${data.message}`);
return;
}
const resData = data.data;
renderPopup(resData);
} catch (e) {
alert(textError);
console.log("解析异常:", e);
}
},
onerror: () => {
loadingToast.remove();
alert(textError);
},
ontimeout: () => {
loadingToast.remove();
alert("请求超时,请稍后重试");
}
});
}
function openDonatePopup() {
const donateMask = document.createElement("div");
donateMask.style.cssText = [
"position:fixed !important",
"inset:0 !important",
"background:rgba(0,0,0,0.85) !important",
"z-index:2147483648 !important",
"display:flex !important",
"flex-direction:column !important",
"align-items:center !important",
"justify-content:center !important",
"padding:20px !important",
"box-sizing:border-box !important"
].join(";");
const wrapBox = document.createElement("div");
wrapBox.style.cssText = [
"background:#111 !important",
"border-radius:16px !important",
"padding:24px !important",
"max-width:320px !important",
"width:100% !important",
"text-align:center !important"
].join(";");
const title = document.createElement("div");
title.style.cssText = [
"color:#fff !important",
"font-size:18px !important",
"margin-bottom:16px !important",
"font-weight:bold !important"
].join(";");
title.textContent = donateTip;
const qrWrap = document.createElement("div");
qrWrap.style.cssText = [
"width:100% !important",
"min-height:240px !important",
"background:#222 !important",
"border-radius:12px !important",
"margin-bottom:20px !important",
"display:flex !important",
"align-items:center !important",
"justify-content:center !important",
"color:#aaa !important"
].join(";");
qrWrap.textContent = "二维码加载中...";
const closeDonateBtn = document.createElement("button");
closeDonateBtn.textContent = textClose;
closeDonateBtn.style.cssText = [
"background:#007aff !important",
"color:#fff !important",
"border:none !important",
"border-radius:12px !important",
"padding:8px 24px !important",
"font-size:15px !important"
].join(";");
closeDonateBtn.onclick = () => donateMask.remove();
wrapBox.appendChild(title);
wrapBox.appendChild(qrWrap);
wrapBox.appendChild(closeDonateBtn);
donateMask.appendChild(wrapBox);
document.body.appendChild(donateMask);
GM_xmlhttpRequest({
method:"GET",
url:donateImgUrl,
responseType:"blob",
timeout:10000,
onload:function(res){
if(res.status >=200 && res.status <300){
const blobUrl = URL.createObjectURL(res.response);
const qrImg = document.createElement("img");
qrImg.src = blobUrl;
qrImg.style.cssText = "width:100%;height:auto;object-fit:contain;border-radius:12px;";
qrWrap.innerHTML = "";
qrWrap.appendChild(qrImg);
}else{
qrWrap.innerHTML = '二维码加载失败,链接访问异常';
}
},
onerror:function(){
qrWrap.innerHTML = '网络请求失败,请检查服务器';
},
ontimeout:function(){
qrWrap.innerHTML = '图片加载超时';
}
});
donateMask.onclick = function(e) {
if(e.target === donateMask) donateMask.remove();
}
}
function renderPopup(resData) {
const mask = document.createElement("div");
mask.style.cssText = [
"position:fixed !important",
"inset:0 !important",
"background:#000000 !important",
"z-index:2147483646 !important",
"overflow-y:auto !important",
"padding:24px 12px 40px !important",
"box-sizing:border-box !important"
].join(";");
const topBar = document.createElement("div");
topBar.style.cssText = [
"display:flex !important",
"justify-content:space-between !important",
"align-items:center !important",
"gap:10px !important",
"margin-bottom:20px !important",
"padding:0 4px !important"
].join(";");
const pageTitle = document.createElement("span");
pageTitle.style.cssText = [
"color:#fff !important",
"font-size:18px !important",
"font-weight:500 !important",
"font-family:Arial,Helvetica,sans-serif !important"
].join(";");
const donateBtn = document.createElement("button");
donateBtn.textContent = textDonate;
donateBtn.style.cssText = [
"background:#ff3b30 !important",
"color:#fff !important",
"border:0 !important",
"border-radius:14px !important",
"padding:9px 14px !important",
"font-size:14px !important",
"font-family:Arial,Helvetica,sans-serif !important"
].join(";");
donateBtn.onclick = openDonatePopup;
const closeBtn = document.createElement("button");
closeBtn.textContent = textClose;
closeBtn.style.cssText = [
"background:#007aff !important",
"color:#fff !important",
"border:0 !important",
"border-radius:14px !important",
"padding:9px 22px !important",
"font-size:16px !important",
"font-family:Arial,Helvetica,sans-serif !important"
].join(";");
closeBtn.onclick = () => mask.remove();
const btnWrap = document.createElement("div");
btnWrap.style.cssText = "display:flex; gap:8px;";
btnWrap.appendChild(donateBtn);
btnWrap.appendChild(closeBtn);
topBar.appendChild(pageTitle);
topBar.appendChild(btnWrap);
mask.appendChild(topBar);
const authorBox = document.createElement("div");
authorBox.style.cssText = [
"display:flex !important",
"align-items:center !important",
"gap:10px !important",
"background:#121212 !important",
"border-radius:16px !important",
"padding:10px !important",
"margin-bottom:16px !important"
].join(";");
const avatarImg = document.createElement("img");
avatarImg.src = resData.author.avatar;
avatarImg.style.cssText = [
"width:48px !important",
"height:48px !important",
"border-radius:50% !important",
"object-fit:cover !important"
].join(";");
const nameWrap = document.createElement("div");
const nameText = document.createElement("div");
nameText.style.cssText = [
"color:#fff !important",
"font-size:16px !important",
"font-family:Arial,Helvetica,sans-serif !important"
].join(";");
nameText.textContent = resData.author.name;
const userText = document.createElement("div");
userText.style.cssText = [
"color:#aaa !important",
"font-size:13px !important",
"font-family:Arial,Helvetica,sans-serif !important"
].join(";");
userText.textContent = "@" + resData.author.username;
nameWrap.appendChild(nameText);
nameWrap.appendChild(userText);
authorBox.appendChild(avatarImg);
authorBox.appendChild(nameWrap);
mask.appendChild(authorBox);
const tweetText = document.createElement("div");
tweetText.style.cssText = [
"color:#eee !important",
"font-size:15px !important",
"white-space:pre-wrap !important",
"margin-bottom:20px !important",
"line-height:1.6 !important",
"padding:0 4px !important",
"font-family:Arial,Helvetica,sans-serif !important"
].join(";");
tweetText.textContent = resData.tweet_info.text;
mask.appendChild(tweetText);
if (resData.video_list && resData.video_list.length > 0) {
pageTitle.textContent = "🎬 X视频解析下载";
resData.video_list.forEach(videoItem => {
const videoCard = document.createElement("div");
videoCard.style.cssText = [
"background:#121212 !important",
"border-radius:16px !important",
"overflow:hidden !important",
"padding:8px !important",
"margin-bottom:16px !important",
"box-shadow:0 4px 14px rgba(0,0,0,0.5) !important"
].join(";");
const coverImg = document.createElement("img");
coverImg.src = videoItem.cover;
coverImg.style.cssText = [
"width:100% !important",
"border-radius:12px !important",
"display:block !important"
].join(";");
const video = document.createElement("video");
video.src = videoItem.best_download_url;
video.controls = true;
video.style.cssText = [
"width:100% !important",
"border-radius:12px !important",
"margin-top:8px !important"
].join(";");
videoCard.appendChild(coverImg);
videoCard.appendChild(video);
mask.appendChild(videoCard);
});
} else {
pageTitle.textContent = "📝 纯文字推文";
const tip = document.createElement("div");
tip.style.cssText = [
"color:#999 !important",
"text-align:center !important",
"padding:30px 0 !important",
"font-family:Arial,Helvetica,sans-serif !important"
].join(";");
tip.textContent = "这条推文无视频资源";
mask.appendChild(tip);
}
document.body.appendChild(mask);
}
function init() {
if (!document.body) {
setTimeout(init, 50);
return;
}
toggleBtn();
analyzeBtn.onclick = () => {
GM_xmlhttpRequest({
method: "GET",
url: updateCheckUrl,
timeout: 5000,
onload: function(res) {
try {
const updateInfo = JSON.parse(res.responseText);
if (versionIsHigher(updateInfo.version, currentVersion) && updateInfo.forceUpdate) {
alert(textUpdate);
if (updateInfo.updateUrl) {
window.open(updateInfo.updateUrl);
}
return;
}
startAnalyze();
} catch (err) {
startAnalyze();
}
},
onerror: startAnalyze,
ontimeout: startAnalyze
});
};
setInterval(toggleBtn, 800);
}
init();
})();