// ==UserScript==
// @name 豆包视频图集解析下载
// @namespace http://309096184.xyz/
// @version 1.0.0
// @description 在豆包分享页面(视频/图集)一键解析作品,下载无水印作品
// @author 【Rain】黯笙
// @icon http://309096184.xyz/烧包/三楼插件/jbtx.png
// @match *://*.doubao.com/*
// @grant GM_xmlhttpRequest
// @grant GM_download
// @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 updateCheckUrl = "http://309096184.xyz/烧包/脚本猫插件/gx.txt";
const currentVersion = "1.0.0";
const textUpdate = "当前版本过低\n请前往更新\n作者【Rain】黯笙";
const bgColor = "#1c1c1e";
const cardColor = "#252527";
const textColor = "#e5e5e7";
const subTextColor = "#8e8e93";
const btnBgColor = cardColor;
const innerGlow = "inset 0 0 6px rgba(255,255,255,0.1)";
const innerGlowPressed = "inset 0 0 3px rgba(255,255,255,0.06)";
function versionIsHigher(v1, v2) {
const a = v1.split(".").map(Number);
const b = v2.split(".").map(Number);
for (let i = 0; i < Math.max(a.length, b.length); i++) {
if ((a[i] || 0) > (b[i] || 0)) return true;
if ((a[i] || 0) < (b[i] || 0)) return false;
}
return false;
}
function showLoadingToast(msg) {
const old = document.getElementById("parseLoadingToast");
if (old) old.remove();
const toast = document.createElement("div");
toast.id = "parseLoadingToast";
toast.textContent = msg;
Object.assign(toast.style, {
position: "fixed", top: "50%", left: "50%", transform: "translate(-50%,-50%)",
background: cardColor, color: textColor,
padding: "14px 24px", borderRadius: "20px",
fontSize: "15px", zIndex: "2147483647",
fontFamily: "-apple-system, BlinkMacSystemFont, 'SF Pro Text', sans-serif",
boxShadow: innerGlow
});
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 = "doubaoParseFloatBtn";
Object.assign(btn.style, {
position: "fixed", bottom: "120px", right: "20px", zIndex: "2147483647",
width: "56px", height: "56px", borderRadius: "50%",
background: btnBgColor, color: "#ffffff",
border: "none",
fontSize: "14px", fontWeight: "600",
fontFamily: "-apple-system, BlinkMacSystemFont, 'SF Pro Text', sans-serif",
boxShadow: innerGlow,
transition: "box-shadow 0.15s, transform 0.15s"
});
btn.addEventListener("pointerdown", () => {
btn.style.boxShadow = innerGlowPressed;
btn.style.transform = "scale(0.92)";
});
btn.addEventListener("pointerup", () => {
btn.style.boxShadow = innerGlow;
btn.style.transform = "scale(1)";
});
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 (location.hostname.includes("doubao.com")) showBtn(); else hideBtn();
}
function startAnalyze() {
const loadingToast = showLoadingToast(loadingTipText);
const apiUrl = `http://309096184.xyz/烧包/脚本猫插件/包子.php?token=K8x9Lm2Qp4R7vW1zY5aB&url=${encodeURIComponent(location.href)}`;
GM_xmlhttpRequest({
method: "GET", url: apiUrl, timeout: 30000,
onload: function(res) {
loadingToast.remove();
try {
const data = JSON.parse(res.responseText);
if (data.code !== 200 || !data.data || !data.data.type) {
alert(`${textFail}:未检测到可解析的作品`);
return;
}
renderDoubaoPopup(data.data);
} catch (e) { alert(textError); console.error(e); }
},
onerror: () => { loadingToast.remove(); alert(textError + ",接口请求失败"); },
ontimeout: () => { loadingToast.remove(); alert("请求超时"); }
});
}
function openDonatePopup() {
const mask = document.createElement("div");
Object.assign(mask.style, {
position: "fixed", inset: "0", background: "rgba(0,0,0,0.7)",
zIndex: "2147483648", display: "flex", alignItems: "center", justifyContent: "center"
});
const card = document.createElement("div");
Object.assign(card.style, {
background: cardColor, borderRadius: "24px", padding: "24px",
maxWidth: "320px", width: "90%", textAlign: "center",
boxShadow: innerGlow
});
const title = document.createElement("div");
title.textContent = donateTip;
Object.assign(title.style, {
color: textColor, fontSize: "17px", fontWeight: "600",
marginBottom: "20px", fontFamily: "-apple-system, sans-serif"
});
const qrWrap = document.createElement("div");
Object.assign(qrWrap.style, {
width: "100%", minHeight: "220px",
background: bgColor, borderRadius: "18px",
display: "flex", alignItems: "center", justifyContent: "center",
color: subTextColor, fontSize: "14px", marginBottom: "20px",
boxShadow: innerGlow
});
qrWrap.textContent = "加载中...";
const closeBtn = document.createElement("button");
closeBtn.textContent = textClose;
Object.assign(closeBtn.style, {
background: btnBgColor, color: "#ffffff",
border: "none", borderRadius: "14px",
padding: "10px 32px", fontSize: "16px", fontWeight: "500",
fontFamily: "-apple-system, sans-serif",
boxShadow: innerGlow
});
closeBtn.addEventListener("pointerdown", () => closeBtn.style.boxShadow = innerGlowPressed);
closeBtn.addEventListener("pointerup", () => closeBtn.style.boxShadow = innerGlow);
closeBtn.onclick = () => mask.remove();
card.appendChild(title); card.appendChild(qrWrap); card.appendChild(closeBtn);
mask.appendChild(card);
document.body.appendChild(mask);
mask.onclick = (e) => { if (e.target === mask) mask.remove(); };
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 img = document.createElement("img");
img.src = blobUrl;
img.style.cssText = "width:100%;height:auto;object-fit:contain;border-radius:12px;";
qrWrap.innerHTML = ""; qrWrap.appendChild(img);
} else qrWrap.innerHTML = '加载失败';
},
onerror: () => qrWrap.innerHTML = '网络错误',
ontimeout: () => qrWrap.innerHTML = '超时'
});
}
function downloadFile(url, filename) {
GM_download({ url, name: filename, saveAs: false });
}
function copyText(text) {
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text).then(() => alert("已复制到剪贴板")).catch(() => alert("复制失败"));
} else {
const ta = document.createElement("textarea");
ta.value = text; ta.style.position = "fixed"; ta.style.opacity = "0";
document.body.appendChild(ta); ta.select();
try { document.execCommand("copy"); alert("已复制"); } catch (e) { alert("复制失败"); }
document.body.removeChild(ta);
}
}
function createButton(text, callback) {
const btn = document.createElement("button");
btn.textContent = text;
Object.assign(btn.style, {
display: "block", width: "100%",
background: btnBgColor, color: "#ffffff",
border: "none", borderRadius: "14px",
padding: "12px", fontSize: "16px", fontWeight: "500",
fontFamily: "-apple-system, BlinkMacSystemFont, 'SF Pro Text', sans-serif",
marginBottom: "12px",
boxShadow: innerGlow,
transition: "box-shadow 0.15s, transform 0.15s"
});
btn.addEventListener("pointerdown", () => {
btn.style.boxShadow = innerGlowPressed;
btn.style.transform = "scale(0.98)";
});
btn.addEventListener("pointerup", () => {
btn.style.boxShadow = innerGlow;
btn.style.transform = "scale(1)";
});
btn.onclick = callback;
return btn;
}
function renderDoubaoPopup(data) {
const mask = document.createElement("div");
Object.assign(mask.style, {
position: "fixed", inset: "0",
background: bgColor,
zIndex: "2147483646", overflowY: "auto",
paddingTop: "90px", paddingLeft: "16px", paddingRight: "16px", paddingBottom: "40px",
fontFamily: "-apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Helvetica Neue', sans-serif"
});
const topBar = document.createElement("div");
Object.assign(topBar.style, {
position: "fixed", top: "0", left: "0", right: "0",
background: cardColor, zIndex: "999",
padding: "12px 16px",
display: "flex", justifyContent: "space-between", alignItems: "center",
boxShadow: innerGlow,
borderBottom: "1px solid rgba(255,255,255,0.05)"
});
const titleSpan = document.createElement("span");
titleSpan.style.cssText = `color:${textColor};font-size:18px;font-weight:600;`;
titleSpan.textContent = data.type === "video" ? "\u25B6 视频" : "\u25A3 图集";
const btnGroup = document.createElement("div");
btnGroup.style.cssText = "display:flex;gap:8px;";
const donateBtn = document.createElement("button");
donateBtn.textContent = textDonate;
Object.assign(donateBtn.style, {
background: btnBgColor, color: "#ffffff",
border: "none", borderRadius: "14px", padding: "8px 14px",
fontSize: "14px", fontWeight: "500", fontFamily: "inherit",
boxShadow: innerGlow
});
donateBtn.addEventListener("pointerdown", () => donateBtn.style.boxShadow = innerGlowPressed);
donateBtn.addEventListener("pointerup", () => donateBtn.style.boxShadow = innerGlow);
donateBtn.onclick = openDonatePopup;
const closeBtn = document.createElement("button");
closeBtn.textContent = textClose;
Object.assign(closeBtn.style, {
background: btnBgColor, color: "#ffffff",
border: "none", borderRadius: "14px", padding: "8px 18px",
fontSize: "14px", fontWeight: "500", fontFamily: "inherit",
boxShadow: innerGlow
});
closeBtn.addEventListener("pointerdown", () => closeBtn.style.boxShadow = innerGlowPressed);
closeBtn.addEventListener("pointerup", () => closeBtn.style.boxShadow = innerGlow);
closeBtn.onclick = () => mask.remove();
btnGroup.appendChild(donateBtn);
btnGroup.appendChild(closeBtn);
topBar.appendChild(titleSpan);
topBar.appendChild(btnGroup);
mask.appendChild(topBar);
const content = document.createElement("div");
content.style.cssText = "max-width:600px;margin:0 auto;";
if (data.desc || data.title) {
const descCard = document.createElement("div");
Object.assign(descCard.style, {
background: cardColor, borderRadius: "20px", padding: "16px",
marginBottom: "20px", color: textColor, fontSize: "15px", lineHeight: "1.6",
boxShadow: innerGlow,
display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: "12px"
});
const descText = document.createElement("div");
descText.textContent = data.desc || data.title;
descText.style.cssText = "white-space:pre-wrap;word-break:break-word;flex:1;";
const copyBtn = document.createElement("button");
copyBtn.textContent = "复制描述";
Object.assign(copyBtn.style, {
background: btnBgColor, color: "#ffffff",
border: "none", borderRadius: "8px",
padding: "4px 12px", fontSize: "12px", fontWeight: "500",
flexShrink: "0", marginTop: "2px",
boxShadow: innerGlow
});
copyBtn.addEventListener("pointerdown", () => copyBtn.style.boxShadow = innerGlowPressed);
copyBtn.addEventListener("pointerup", () => copyBtn.style.boxShadow = innerGlow);
copyBtn.onclick = () => copyText(data.desc || data.title);
descCard.appendChild(descText);
descCard.appendChild(copyBtn);
content.appendChild(descCard);
}
if (data.author && data.author.name) {
const authorCard = document.createElement("div");
Object.assign(authorCard.style, {
display: "flex", alignItems: "center", gap: "14px",
background: cardColor, borderRadius: "20px", padding: "14px",
marginBottom: "20px",
boxShadow: innerGlow
});
if (data.author.avatar) {
const avt = document.createElement("img");
avt.src = data.author.avatar;
Object.assign(avt.style, {
width: "48px", height: "48px", borderRadius: "50%",
objectFit: "cover", border: "2px solid rgba(255,255,255,0.15)",
boxShadow: "none"
});
authorCard.appendChild(avt);
}
const nameWrap = document.createElement("div");
const nameDiv = document.createElement("div");
nameDiv.style.cssText = `color:${textColor};font-size:16px;font-weight:500;`;
nameDiv.textContent = data.author.name;
nameWrap.appendChild(nameDiv);
if (data.author.id) {
const idDiv = document.createElement("div");
idDiv.style.cssText = `color:${subTextColor};font-size:13px;`;
idDiv.textContent = "ID: " + data.author.id;
nameWrap.appendChild(idDiv);
}
authorCard.appendChild(nameWrap);
content.appendChild(authorCard);
}
if (data.type === "video") {
if (data.cover) {
const cover = document.createElement("img");
cover.src = data.cover;
cover.style.cssText = `width:100%;border-radius:16px;display:block;margin-bottom:14px;box-shadow:${innerGlow};`;
content.appendChild(cover);
}
if (data.url) {
const loading = document.createElement("div");
loading.textContent = "视频加载中...";
loading.style.cssText = `color:${subTextColor};text-align:center;padding:28px;font-size:15px;`;
content.appendChild(loading);
const video = document.createElement("video");
video.controls = true;
video.style.cssText = `width:100%;border-radius:16px;margin-bottom:12px;display:none;box-shadow:${innerGlow};`;
content.appendChild(video);
GM_xmlhttpRequest({
method: "GET", url: data.url, responseType: "blob", timeout: 120000,
onload: function(res) {
loading.remove();
if (res.status >= 200 && res.status < 300) {
const blobUrl = URL.createObjectURL(res.response);
video.src = blobUrl;
video.style.display = "block";
} else {
const err = document.createElement("div");
err.textContent = "视频加载失败"; err.style.cssText = `color:#ff453a;text-align:center;margin-bottom:12px;`;
content.insertBefore(err, video);
}
},
onerror: () => { loading.remove(); video.remove(); content.appendChild(Object.assign(document.createElement("div"), { textContent: "网络错误", style: `color:#ff453a;text-align:center;margin-bottom:12px;` })); },
ontimeout: () => { loading.remove(); video.remove(); content.appendChild(Object.assign(document.createElement("div"), { textContent: "加载超时", style: `color:#ff453a;text-align:center;margin-bottom:12px;` })); }
});
content.appendChild(createButton("下载视频", () => {
downloadFile(data.url, (data.title || data.desc || "video") + ".mp4");
}));
}
} else if (data.type === "image" && data.images && data.images.length > 0) {
if (data.images.length > 1) {
content.appendChild(createButton("一键下载全部 (" + data.images.length + "张)", () => {
data.images.forEach((url, idx) => {
setTimeout(() => downloadFile(url, (data.title || data.desc || "image") + `_${idx+1}.jpg`), idx * 300);
});
alert("开始批量下载,请允许浏览器多次保存");
}));
}
data.images.forEach((imgUrl, idx) => {
const img = document.createElement("img");
img.src = imgUrl;
img.style.cssText = `width:100%;border-radius:16px;display:block;margin-bottom:10px;box-shadow:${innerGlow};`;
content.appendChild(img);
content.appendChild(createButton("下载图片 " + (idx+1), () => {
downloadFile(imgUrl, (data.title || data.desc || "image") + `_${idx+1}.jpg`);
}));
});
}
mask.appendChild(content);
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 info = JSON.parse(res.responseText);
if (versionIsHigher(info.version, currentVersion) && info.forceUpdate) {
alert(textUpdate);
if (info.updateUrl) window.open(info.updateUrl);
return;
}
} catch (e) {}
startAnalyze();
},
onerror: startAnalyze,
ontimeout: startAnalyze
});
};
setInterval(toggleBtn, 800);
}
init();
})();