// ==UserScript==
// @name 微博推送助手
// @namespace http://tampermonkey.net/
// @version 1.4.1
// @description 在微博页面添加推送按钮,支持快速复制博文内容、下载图片视频资源并自动发送到自己微博。v1.4新增跨域名数据传递支持,解决www.weibo.com和weibo.com之间的数据同步问题,v1.4.1移除调试信息,代码更加简洁。
// @author You
// @match https://weibo.com/*
// @match https://www.weibo.com/*
// @match https://m.weibo.cn/*
// @grant GM_addStyle
// @grant GM_setClipboard
// @grant GM_xmlhttpRequest
// @grant GM_download
// @grant GM_openInTab
// @grant GM_setValue
// @grant GM_getValue
// @connect *
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// 全局变量
let downloadedFiles = [];
let currentPostContent = '';
let isProcessing = false;
// 添加样式
GM_addStyle(`
.weibo-pusher-btn {
display: inline-block !important;
margin-left: 10px;
padding: 4px 12px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 20px;
cursor: pointer;
font-size: 12px;
border: none;
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
transition: all 0.3s ease;
font-weight: 500;
}
.weibo-pusher-btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
background: linear-gradient(135deg, #764ba2 0%, #667eea 100%);
}
.weibo-pusher-btn:active {
transform: translateY(0);
}
.weibo-pusher-btn.processing {
background: linear-gradient(135deg, #ffa726 0%, #ff7043 100%);
cursor: not-allowed;
animation: processing-pulse 1.5s ease-in-out infinite;
}
@keyframes processing-pulse {
0%, 100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.8;
transform: scale(1.02);
}
}
.weibo-pusher-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
z-index: 10000;
display: none;
backdrop-filter: blur(5px);
}
.weibo-pusher-modal-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
border-radius: 12px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
max-width: 600px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
padding: 0;
}
.weibo-pusher-modal-header {
padding: 20px 24px 16px;
border-bottom: 1px solid #f0f0f0;
display: flex;
justify-content: space-between;
align-items: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 12px 12px 0 0;
}
.weibo-pusher-modal-title {
font-size: 18px;
font-weight: 600;
margin: 0;
}
.weibo-pusher-modal-close {
cursor: pointer;
font-size: 24px;
color: white;
opacity: 0.8;
transition: opacity 0.2s;
}
.weibo-pusher-modal-close:hover {
opacity: 1;
}
.weibo-pusher-modal-body {
padding: 24px;
}
.weibo-pusher-content-preview {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 16px;
margin-bottom: 20px;
max-height: 200px;
overflow-y: auto;
white-space: pre-wrap;
font-size: 14px;
line-height: 1.6;
}
.weibo-pusher-media-list {
margin-bottom: 20px;
}
.weibo-pusher-media-item {
display: flex;
align-items: center;
padding: 12px;
border: 1px solid #e9ecef;
border-radius: 8px;
margin-bottom: 8px;
background: white;
cursor: pointer;
transition: background-color 0.2s ease;
}
.weibo-pusher-media-item:hover {
background: #f8f9fa;
}
.weibo-pusher-media-item.selected {
background: #e7f3ff;
border-color: #667eea;
}
.weibo-pusher-media-preview {
width: 60px;
height: 60px;
object-fit: cover;
border-radius: 6px;
margin-right: 12px;
}
.weibo-pusher-media-info {
flex: 1;
min-width: 0;
}
.weibo-pusher-media-name {
font-size: 14px;
font-weight: 500;
margin-bottom: 4px;
word-break: break-all;
}
.weibo-pusher-media-size {
font-size: 12px;
color: #666;
}
.weibo-pusher-media-status {
font-size: 12px;
padding: 2px 8px;
border-radius: 12px;
margin-left: 8px;
}
.weibo-pusher-media-status.downloading {
background: #fff3cd;
color: #856404;
}
.weibo-pusher-media-status.completed {
background: #d4edda;
color: #155724;
}
.weibo-pusher-media-status.failed {
background: #f8d7da;
color: #721c24;
}
.weibo-pusher-actions {
display: flex;
gap: 12px;
justify-content: flex-end;
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #f0f0f0;
}
.weibo-pusher-action-btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
}
.weibo-pusher-action-btn.primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.weibo-pusher-action-btn.primary:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.weibo-pusher-action-btn.secondary {
background: #f8f9fa;
color: #495057;
border: 1px solid #dee2e6;
}
.weibo-pusher-action-btn.secondary:hover {
background: #e9ecef;
}
.weibo-pusher-action-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none !important;
}
.weibo-pusher-progress {
margin-top: 16px;
padding: 12px;
background: #f8f9fa;
border-radius: 6px;
font-size: 14px;
color: #495057;
}
.weibo-pusher-toast {
position: fixed;
top: 20px;
right: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
padding: 16px 20px;
z-index: 10001;
max-width: 400px;
transform: translateX(100%);
transition: transform 0.3s ease;
}
.weibo-pusher-toast.show {
transform: translateX(0);
}
.weibo-pusher-toast.success {
border-left: 4px solid #28a745;
}
.weibo-pusher-toast.error {
border-left: 4px solid #dc3545;
}
.weibo-pusher-toast.info {
border-left: 4px solid #17a2b8;
}
`);
// 工具函数
function showToast(message, type = 'info', duration = 3000) {
const toast = document.createElement('div');
toast.className = `weibo-pusher-toast ${type}`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => toast.classList.add('show'), 100);
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => document.body.removeChild(toast), 300);
}, duration);
}
// 保存内容到localStorage和Cookie用于跨页面传递
function saveContentForPaste(content, mediaFileNames = []) {
const data = {
content: content,
mediaFiles: mediaFileNames,
timestamp: Date.now()
};
try {
// 主要使用localStorage
localStorage.setItem('weibo_pusher_content', JSON.stringify(data));
// 同时保存到Cookie作为备用(设置为在.weibo.com域下共享)
try {
const cookieData = encodeURIComponent(JSON.stringify(data));
document.cookie = `weibo_pusher_content=${cookieData}; path=/; domain=.weibo.com; max-age=900`; // 15分钟
} catch (cookieError) {
// Cookie保存失败时静默处理,localStorage成功即可
}
} catch (error) {
console.error('微博推送助手: 保存数据失败', error);
}
}
// 从Cookie获取数据
function getDataFromCookie() {
try {
const cookies = document.cookie.split(';');
for (let cookie of cookies) {
const [name, value] = cookie.trim().split('=');
if (name === 'weibo_pusher_content') {
const decodedValue = decodeURIComponent(value);
return JSON.parse(decodedValue);
}
}
} catch (error) {
// 静默处理Cookie读取错误
}
return null;
}
// 获取保存的内容(不清除,只检查)
function getSavedContent() {
let data = null;
try {
// 首先尝试从localStorage获取
const saved = localStorage.getItem('weibo_pusher_content');
if (saved) {
data = JSON.parse(saved);
} else {
// 如果localStorage没有数据,尝试从Cookie获取
data = getDataFromCookie();
if (data) {
// 将Cookie数据同步到localStorage
localStorage.setItem('weibo_pusher_content', JSON.stringify(data));
}
}
if (data) {
const age = Date.now() - data.timestamp;
// 检查是否过期(15分钟)- 不删除,只是返回null
if (age < 15 * 60 * 1000) {
return data;
} else {
return null;
}
}
} catch (error) {
// 静默处理获取数据错误
}
return null;
}
// 清除保存的内容
function clearSavedContent() {
localStorage.removeItem('weibo_pusher_content');
// 同时清除Cookie
document.cookie = 'weibo_pusher_content=; path=/; domain=.weibo.com; max-age=0';
}
// 清理过期数据
function cleanupExpiredData() {
try {
let hasCleanup = false;
// 清理localStorage中的过期数据
const saved = localStorage.getItem('weibo_pusher_content');
if (saved) {
const data = JSON.parse(saved);
const age = Date.now() - data.timestamp;
// 如果数据超过15分钟,清理掉
if (age > 15 * 60 * 1000) {
localStorage.removeItem('weibo_pusher_content');
hasCleanup = true;
}
}
// 清理Cookie中的过期数据
const cookieData = getDataFromCookie();
if (cookieData) {
const age = Date.now() - cookieData.timestamp;
if (age > 15 * 60 * 1000) {
document.cookie = 'weibo_pusher_content=; path=/; domain=.weibo.com; max-age=0';
hasCleanup = true;
}
}
return hasCleanup;
} catch (error) {
// 静默处理清理错误
}
return false;
}
// 获取并清除保存的内容(兼容原有功能)
function getAndClearSavedContent() {
const data = getSavedContent();
if (data) {
clearSavedContent();
}
return data;
}
// 自动粘贴功能
function tryAutoPaste() {
const savedData = getSavedContent(); // 先不清除数据
if (!savedData) {
return false;
}
// 无论是否找到发博框,都先复制到剪贴板作为备份
GM_setClipboard(savedData.content);
// 查找微博主页的发博框 - 扩展选择器列表
const composeSelectors = [
// 2024年最新版微博发博框
'.Form_input_2gtXx',
'textarea.Form_input_2gtXx',
'.wbpro-form textarea',
'.Form_wbproform_3UDoi textarea',
'div[contenteditable="true"]',
'.woo-editable-text[contenteditable="true"]',
// 通用contenteditable选择器(优先级更高)
'[contenteditable="true"]:not([role="textbox"]):not(.woo-comment-input)',
'[contenteditable="true"][placeholder*="想说"]',
'[contenteditable="true"][placeholder*="分享"]',
'[contenteditable="true"][placeholder*="新鲜事"]',
// 新版微博发博框
'.woo-box-flex textarea',
'.woo-input-main textarea',
'.woo-input-wrap textarea',
'.woo-editable-text textarea',
'.Feed_publish textarea',
'.publish-content textarea',
'.composer-input textarea',
// 常见的textarea选择器
'textarea[placeholder*="分享"]',
'textarea[placeholder*="想说"]',
'textarea[placeholder*="新鲜事"]',
'textarea[placeholder*="有什么新鲜事"]',
'textarea[placeholder*="想法"]',
'textarea[placeholder*="今天"]',
'.CommentInput_wrap_3s1XL textarea',
'.publisherTextarea',
// 主页发博区域
'.woo-publish-main [contenteditable="true"]',
'.publish-main [contenteditable="true"]',
'.woo-publish-wrap [contenteditable="true"]',
'.publish-wrap [contenteditable="true"]',
'.composer-wrap [contenteditable="true"]',
// 通用发博区域
'.publish-box textarea',
'.composer-box textarea',
'.feed-publish textarea',
'#Publisher textarea',
'.wb-publisher textarea',
'.wb-compose textarea',
'.weibo-composer textarea',
// 移动端适配
'.m-publish textarea',
'.mobile-composer textarea'
];
let composeBox = null;
let foundSelector = '';
for (const selector of composeSelectors) {
try {
composeBox = document.querySelector(selector);
if (composeBox) {
foundSelector = selector;
break;
}
} catch (error) {
// 静默处理选择器错误
}
}
if (composeBox) {
// 设置内容
try {
if (composeBox.tagName.toLowerCase() === 'textarea') {
composeBox.value = savedData.content;
composeBox.dispatchEvent(new Event('input', { bubbles: true }));
composeBox.dispatchEvent(new Event('change', { bubbles: true }));
composeBox.dispatchEvent(new Event('focus', { bubbles: true }));
} else if (composeBox.getAttribute('contenteditable') === 'true') {
// 对于contenteditable元素,将换行转换为<br>标签
const htmlContent = savedData.content.replace(/\n/g, '<br>');
composeBox.innerHTML = htmlContent;
composeBox.dispatchEvent(new Event('input', { bubbles: true }));
composeBox.dispatchEvent(new Event('change', { bubbles: true }));
composeBox.dispatchEvent(new Event('focus', { bubbles: true }));
// 将光标定位到末尾
const range = document.createRange();
const selection = window.getSelection();
range.selectNodeContents(composeBox);
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
} else {
composeBox.textContent = savedData.content;
composeBox.dispatchEvent(new Event('input', { bubbles: true }));
composeBox.dispatchEvent(new Event('change', { bubbles: true }));
composeBox.dispatchEvent(new Event('focus', { bubbles: true }));
}
// 聚焦到发博框
composeBox.focus();
// 成功粘贴后清除数据
clearSavedContent();
let message = '✅ 内容已自动填入发博框并复制到剪贴板!';
if (savedData.mediaFiles && savedData.mediaFiles.length > 0) {
message += `\n📁 请手动上传 ${savedData.mediaFiles.length} 个媒体文件:\n${savedData.mediaFiles.join(', ')}`;
}
showToast(message, 'success', 5000);
return true; // 表示成功
} catch (error) {
// 静默处理设置内容错误
}
} else {
let message = '⚠️ 未找到发博框,内容已复制到剪贴板';
if (savedData.mediaFiles && savedData.mediaFiles.length > 0) {
message += `\n📁 需要上传 ${savedData.mediaFiles.length} 个文件:\n${savedData.mediaFiles.join(', ')}`;
}
showToast(message, 'info', 5000);
}
return false; // 表示未成功粘贴
}
// 通过文本内容查找元素的辅助函数
function findElementsByText(parentElement, searchText) {
if (!parentElement) return [];
const result = [];
const allElements = parentElement.querySelectorAll('*');
allElements.forEach(element => {
if (element.textContent && element.textContent.includes(searchText)) {
result.push(element);
}
});
return result;
}
// 检查并展开折叠内容
async function expandContent(element) {
if (!element) return false;
// 可能的展开按钮选择器
const expandSelectors = [
// 新版微博
'button.woo-button-flat.woo-button-s[class*="ContentMore"]',
'button.woo-button-flat.woo-button-s.ContentMore',
'button[class*="expand"]',
'button[class*="Expand"]',
'div[class*="expand"]',
'a[class*="expand"]',
// 旧版微博
'a[action-type="fl_unfold"]',
'.WB_text_opt',
'.WB_text a[onclick*="unfold"]',
// 特殊的span展开按钮
'span.expand',
'.expand'
];
// 搜索展开按钮
let expandButton = null;
// 首先尝试使用标准选择器
for (const selector of expandSelectors) {
try {
expandButton = element.querySelector(selector) || element.parentNode.querySelector(selector);
if (expandButton) {
break;
}
} catch (error) {
console.error('微博推送助手: 选择器错误', selector, error);
}
}
// 如果没找到,尝试通过文本内容查找
if (!expandButton) {
// 查找包含"展开"或"展开全文"的元素
const textButtons = [
...findElementsByText(element, '展开'),
...findElementsByText(element, '展开全文')
];
// 筛选可能的按钮元素,包括span元素
const possibleButtons = textButtons.filter(el => {
const tagName = el.tagName.toLowerCase();
return tagName === 'button' || tagName === 'a' || tagName === 'span' ||
el.role === 'button' || el.getAttribute('role') === 'button' ||
el.classList.contains('expand') || // 特别处理展开类
el.onclick || el.getAttribute('onclick');
});
if (possibleButtons.length > 0) {
expandButton = possibleButtons[0];
}
}
// 如果找到展开按钮,点击它
if (expandButton) {
try {
// 对于span.expand元素尝试多种点击方式
if (expandButton.tagName.toLowerCase() === 'span' && expandButton.classList.contains('expand')) {
// 方法1: 直接点击
expandButton.click();
// 方法2: 创建点击事件
const clickEvent = document.createEvent('MouseEvents');
clickEvent.initEvent('click', true, true);
expandButton.dispatchEvent(clickEvent);
// 方法3: 尝试点击父元素
if (expandButton.parentElement) {
expandButton.parentElement.click();
}
// 方法4: 尝试移除展开元素并修改父元素样式以显示全部内容
const parentEl = expandButton.parentElement;
if (parentEl && parentEl.classList.contains('detail_wbtext_4CRf9')) {
// 保存原始内容但移除展开按钮
const fullText = parentEl.innerHTML.replace(/<span class="expand">展开<\/span>/, '');
parentEl.innerHTML = fullText;
// 移除可能的最大高度限制
parentEl.style.maxHeight = 'none';
parentEl.style.overflow = 'visible';
}
} else {
// 正常点击
expandButton.click();
}
// 等待内容展开
await new Promise(resolve => setTimeout(resolve, 800)); // 增加等待时间,确保内容展开
return true;
} catch (error) {
console.error('微博推送助手: 展开内容时出错', error);
}
}
return false;
}
// 提取博文内容
async function extractPostContent(postElement) {
const contentSelectors = [
// 原有的选择器
'.detail_wbtext_4CRf9',
'.wbpro-feed-content .detail_text_1U10O',
'.wbpro-feed-content',
'.Feed_body_3R0rO .Feed_content_2YeHn',
'.WB_text',
'.WB_detail .WB_text',
'[node-type="feed_list_content"]',
'.weibo-text',
'.m-text-box',
// 新增对微博详情页内容的支持
'.WB_detail_warp .WB_text',
'.WB_detail_wrap .WB_text',
'.detail_main_3PZZf .WB_text',
'.detail_card_main_4fhCu .WB_text',
'.detail_body_1Dpoa .WB_text',
'.detail_info_4spYr .WB_text',
'.WB_feed_type .WB_text',
'.wb-detail .WB_text',
'.wb-item .WB_text',
// 新版详情页选择器
'.Feed_main_1b1q9 .WB_text',
'.Feed_detail_1b1q9 .WB_text',
'.WBDetail_main_3u .WB_text',
'.WBDetail_wrap_3u .WB_text',
'.wbpro-detail-content .WB_text',
'.wbpro-detail-wrapper .WB_text',
// 通用详情页内容选择器
'.WB_detail .WB_text',
'.detail-content',
'.weibo-detail-content',
'.post-content .WB_text',
'.wb-content',
'.detail_text',
'.woo-detail-content',
'.woo-detail-text'
];
let contentElement = null;
for (const selector of contentSelectors) {
contentElement = postElement.querySelector(selector);
if (contentElement) break;
}
if (!contentElement) return '';
// 先尝试展开折叠内容
try {
await expandContent(contentElement);
} catch (error) {
console.error('微博推送助手: 展开内容时出错', error);
// 继续处理,即使展开失败,也尝试获取已有内容
}
// 创建一个副本来处理内容,避免修改原始DOM
const tempContainer = document.createElement('div');
tempContainer.innerHTML = contentElement.innerHTML;
// 移除脚本添加的按钮
const scriptButtons = tempContainer.querySelectorAll('.weibo-pusher-btn, .copy-btn, .extract-links-btn, .copy-format-btn');
scriptButtons.forEach(btn => {
if (btn.parentNode) {
btn.parentNode.removeChild(btn);
}
});
// 移除"收起"按钮和相关元素
const hideElements = tempContainer.querySelectorAll([
'a.woo-box-flex[role="button"]', // 新版收起按钮
'button[content="收起"]', // 收起按钮
'a[action-type="fl_fold"]', // 旧版收起按钮
'.WB_text_opt_fold', // 旧版收起区域
'.content_opt_fold', // 收起选项区域
'span.expand' // 展开按钮
].join(', '));
hideElements.forEach(el => {
if (el.parentNode) {
el.parentNode.removeChild(el);
}
});
// 单独处理包含"收起"、"展开"文本的元素
const hideSpans = [
...findElementsByText(tempContainer, '收起'),
...findElementsByText(tempContainer, '展开'),
...findElementsByText(tempContainer, '展开全文')
];
hideSpans.forEach(span => {
const text = span.textContent.trim();
if (text === '收起' || text === '展开' || text === '展开全文') {
if (span.parentNode) {
span.parentNode.removeChild(span);
}
}
});
// 改进的文本提取方法,更好地保留换行格式
function extractTextWithLineBreaks(element) {
let text = '';
function processNode(node) {
if (node.nodeType === Node.TEXT_NODE) {
// 文本节点,直接添加内容
text += node.textContent;
} else if (node.nodeType === Node.ELEMENT_NODE) {
const tagName = node.tagName.toLowerCase();
// 在块级元素前后添加换行
const blockElements = ['div', 'p', 'br', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li'];
const isBlockElement = blockElements.includes(tagName);
if (isBlockElement && text && !text.endsWith('\n')) {
text += '\n';
}
// 处理特殊的br标签
if (tagName === 'br') {
text += '\n';
return;
}
// 递归处理子节点
for (let child of node.childNodes) {
processNode(child);
}
// 在块级元素后添加换行
if (isBlockElement && text && !text.endsWith('\n')) {
text += '\n';
}
}
}
processNode(element);
return text;
}
// 使用改进的方法提取文本
let text = extractTextWithLineBreaks(tempContainer);
// 清理内容
text = text.replace(/\s*展开全文\s*/g, '')
.replace(/\s*收起全文\s*/g, '')
.replace(/\s*全文\s*/g, '')
.replace(/\s*展开\s*/g, '')
.replace(/\s*收起\s*/g, '')
.replace(/📤\s*推送到我的微博\s*/g, '') // 移除脚本按钮文本
.replace(/[\u200B-\u200D\uFEFF]/g, '') // 移除零宽字符
.replace(/\n{3,}/g, '\n\n') // 合并多余换行
.replace(/^\s+|\s+$/g, '') // 移除首尾空白
.replace(/[ \t]+\n/g, '\n') // 移除行尾空格
.replace(/\n[ \t]+/g, '\n'); // 移除行首空格
return text;
}
// 提取媒体资源
function extractMediaResources(postElement) {
const mediaList = [];
// 生成时间戳前缀,便于用户识别下载的文件
const timestamp = new Date().toISOString().slice(0, 16).replace(/[-:T]/g, '');
const prefix = `微博推送_${timestamp}_`;
// 首先尝试找到博文内容区域,限制搜索范围
const contentSelectors = [
// 原有的选择器
'.detail_wbtext_4CRf9',
'.wbpro-feed-content .detail_text_1U10O',
'.wbpro-feed-content',
'.Feed_body_3R0rO .Feed_content_2YeHn',
'.WB_text',
'.WB_detail .WB_text',
'[node-type="feed_list_content"]',
'.weibo-text',
'.m-text-box',
// 新增对微博详情页的支持
'.WB_detail_warp .WB_text',
'.WB_detail_wrap .WB_text',
'.detail_main_3PZZf .WB_text',
'.detail_card_main_4fhCu .WB_text',
'.detail_body_1Dpoa .WB_text',
'.detail_info_4spYr .WB_text',
'.WB_feed_type .WB_text',
'.wb-detail .WB_text',
'.wb-item .WB_text',
// 新版详情页选择器
'.Feed_main_1b1q9 .WB_text',
'.Feed_detail_1b1q9 .WB_text',
'.WBDetail_main_3u .WB_text',
'.WBDetail_wrap_3u .WB_text',
'.wbpro-detail-content .WB_text',
'.wbpro-detail-wrapper .WB_text',
// 通用媒体容器
'.detail-content',
'.weibo-detail-content',
'.post-content',
'.wb-content'
];
// 查找媒体容器(通常在内容区域的同级或父级)
const mediaContainerSelectors = [
// 原有的选择器
'.wbpro-feed-content',
'.Feed_body_3R0rO',
'.WB_detail',
'.WB_feed_detail',
'.WB_cardwrap',
'[node-type="feed_list_item"]',
// 新增对微博详情页的支持
'.WB_detail_warp',
'.WB_detail_wrap',
'.detail_main_3PZZf',
'.detail_card_main_4fhCu',
'.detail_body_1Dpoa',
'.detail_info_4spYr',
'.WB_feed_type',
'.wb-detail',
'.wb-item',
// 新版详情页媒体容器
'.Feed_main_1b1q9',
'.Feed_detail_1b1q9',
'.WBDetail_main_3u',
'.WBDetail_wrap_3u',
'.wbpro-detail-content',
'.wbpro-detail-wrapper',
// 通用媒体容器
'.detail-content',
'.weibo-detail-content',
'.post-content',
'.wb-content'
];
let searchContainer = postElement;
// 尝试找到更精确的搜索容器
for (const selector of mediaContainerSelectors) {
const container = postElement.querySelector(selector);
if (container) {
searchContainer = container;
break;
}
}
// 如果没找到专门的媒体容器,尝试找到内容区域作为搜索范围
if (searchContainer === postElement) {
for (const selector of contentSelectors) {
const contentArea = postElement.querySelector(selector);
if (contentArea && contentArea.parentElement) {
searchContainer = contentArea.parentElement;
break;
}
}
}
// 提取图片 - 使用更精确的选择器,排除头像等
const imageSelectors = [
'img[src*="sinaimg.cn"][src*="large"]', // 优先高清图片
'img[src*="sinaimg.cn"][src*="bmiddle"]', // 中等尺寸图片
'img[src*="sinaimg.cn"][src*="orj360"]', // 原图
'img[src*="sinaimg.cn"]:not([src*="avatar"]):not([src*="head"])', // 排除头像
'img[src*="weibo"]:not([src*="avatar"]):not([src*="head"])', // 微博图片,排除头像
'.WB_pic img',
'.media-pic img',
'.picture img',
'.woo-picture-img'
];
let images = [];
for (const selector of imageSelectors) {
const foundImages = searchContainer.querySelectorAll(selector);
if (foundImages.length > 0) {
images = [...foundImages];
break;
}
}
images.forEach((img, index) => {
let src = img.src;
// 跳过明显不是博文内容的图片
if (src.includes('avatar') || src.includes('head') ||
src.includes('icon') || src.includes('emoji') ||
img.width < 50 || img.height < 50) {
return;
}
// 获取高清图片链接
if (src.includes('thumbnail') || src.includes('bmiddle') || src.includes('orj360')) {
src = src.replace(/\/thumbnail\/|\/bmiddle\/|\/orj360\//g, '/large/');
}
if (src.includes('?')) {
src = src.split('?')[0];
}
mediaList.push({
type: 'image',
url: src,
name: `${prefix}image_${index + 1}.jpg`,
element: img
});
});
// 提取视频 - 限制在搜索容器内
const videos = searchContainer.querySelectorAll('video, .WB_video video, .media-video video');
videos.forEach((video, index) => {
let src = video.src || video.querySelector('source')?.src;
if (src) {
mediaList.push({
type: 'video',
url: src,
name: `${prefix}video_${index + 1}.mp4`,
element: video
});
}
});
// 提取视频链接(通过data属性)- 限制在搜索容器内
const videoContainers = searchContainer.querySelectorAll('[video-sources], [action-data*="video"]');
videoContainers.forEach((container, index) => {
const videoData = container.getAttribute('video-sources') || container.getAttribute('action-data');
if (videoData) {
try {
const data = JSON.parse(videoData);
if (data.mp4_hd_url || data.mp4_url) {
mediaList.push({
type: 'video',
url: data.mp4_hd_url || data.mp4_url,
name: `${prefix}video_${mediaList.filter(m => m.type === 'video').length + 1}.mp4`,
element: container
});
}
} catch (e) {
// 解析视频数据失败,跳过
}
}
});
return mediaList;
}
// 下载媒体文件
function downloadMedia(mediaItem, onProgress) {
return new Promise((resolve, reject) => {
onProgress('downloading');
GM_xmlhttpRequest({
method: 'GET',
url: mediaItem.url,
responseType: 'blob',
headers: {
'Referer': 'https://weibo.com/',
'User-Agent': navigator.userAgent
},
onload: function(response) {
if (response.status === 200) {
const blob = response.response;
const url = URL.createObjectURL(blob);
// 使用GM_download下载文件
GM_download(url, mediaItem.name, url);
onProgress('completed');
resolve({
...mediaItem,
blob: blob,
localUrl: url
});
} else {
onProgress('failed');
reject(new Error(`下载失败: ${response.status}`));
}
},
onerror: function(error) {
onProgress('failed');
reject(error);
}
});
});
}
// 创建推送模态框
function createPushModal(content, mediaList) {
const modal = document.createElement('div');
modal.className = 'weibo-pusher-modal';
modal.innerHTML = `
<div class="weibo-pusher-modal-content">
<div class="weibo-pusher-modal-header">
<h3 class="weibo-pusher-modal-title">微博推送助手</h3>
<span class="weibo-pusher-modal-close">×</span>
</div>
<div class="weibo-pusher-modal-body">
<h4>博文内容预览:</h4>
<div class="weibo-pusher-content-preview">${content}</div>
<h4>媒体资源 (${mediaList.length}个):
${mediaList.length > 0 ? `<label style="font-weight: normal; font-size: 14px; margin-left: 10px;">
<input type="checkbox" id="selectAllMedia" checked> 全选
</label>` : ''}
</h4>
<div class="weibo-pusher-media-list">
${mediaList.map((media, index) => `
<div class="weibo-pusher-media-item" data-index="${index}">
<label style="margin-right: 10px;">
<input type="checkbox" class="media-checkbox" data-index="${index}" checked>
</label>
${media.type === 'image' ?
`<img class="weibo-pusher-media-preview" src="${media.url}" alt="预览">` :
`<div class="weibo-pusher-media-preview" style="background: #f0f0f0; display: flex; align-items: center; justify-content: center; color: #666;">📹</div>`
}
<div class="weibo-pusher-media-info">
<div class="weibo-pusher-media-name">${media.name}</div>
<div class="weibo-pusher-media-size">${media.type === 'image' ? '图片' : '视频'}</div>
</div>
<div class="weibo-pusher-media-status" data-status="pending">等待下载</div>
</div>
`).join('')}
</div>
<div class="weibo-pusher-actions">
<button class="weibo-pusher-action-btn secondary" id="copyContentBtn">仅复制内容</button>
<button class="weibo-pusher-action-btn primary" id="downloadAndPushBtn">下载选中资源并推送</button>
</div>
<div class="weibo-pusher-progress" id="progressInfo" style="display: none;"></div>
</div>
</div>
`;
document.body.appendChild(modal);
// 绑定事件
const closeBtn = modal.querySelector('.weibo-pusher-modal-close');
const copyBtn = modal.querySelector('#copyContentBtn');
const downloadBtn = modal.querySelector('#downloadAndPushBtn');
const progressInfo = modal.querySelector('#progressInfo');
const selectAllCheckbox = modal.querySelector('#selectAllMedia');
const mediaCheckboxes = modal.querySelectorAll('.media-checkbox');
// 全选/取消全选功能
if (selectAllCheckbox) {
selectAllCheckbox.onchange = () => {
const isChecked = selectAllCheckbox.checked;
mediaCheckboxes.forEach(checkbox => {
checkbox.checked = isChecked;
});
updateDownloadButtonText();
updateMediaItemStyles();
};
}
// 单个媒体复选框变化时更新全选状态
mediaCheckboxes.forEach(checkbox => {
checkbox.onchange = () => {
const checkedCount = Array.from(mediaCheckboxes).filter(cb => cb.checked).length;
if (selectAllCheckbox) {
selectAllCheckbox.checked = checkedCount === mediaCheckboxes.length;
selectAllCheckbox.indeterminate = checkedCount > 0 && checkedCount < mediaCheckboxes.length;
}
updateDownloadButtonText();
updateMediaItemStyles();
};
});
// 添加整行点击功能
const mediaItems = modal.querySelectorAll('.weibo-pusher-media-item');
mediaItems.forEach((item, index) => {
item.onclick = (e) => {
// 如果点击的是checkbox本身,不处理(避免重复触发)
if (e.target.type === 'checkbox') return;
const checkbox = item.querySelector('.media-checkbox');
if (checkbox) {
checkbox.checked = !checkbox.checked;
checkbox.dispatchEvent(new Event('change'));
}
};
});
// 更新媒体项的视觉状态
function updateMediaItemStyles() {
mediaItems.forEach((item, index) => {
const checkbox = item.querySelector('.media-checkbox');
if (checkbox && checkbox.checked) {
item.classList.add('selected');
} else {
item.classList.remove('selected');
}
});
}
// 初始化媒体项样式
updateMediaItemStyles();
// 更新下载按钮文本
function updateDownloadButtonText() {
const checkedCount = Array.from(mediaCheckboxes).filter(cb => cb.checked).length;
if (checkedCount === 0) {
downloadBtn.textContent = '仅推送文本内容';
} else {
downloadBtn.textContent = `下载选中资源(${checkedCount}个)并推送`;
}
}
// 初始化按钮文本
updateDownloadButtonText();
closeBtn.onclick = () => {
document.body.removeChild(modal);
};
modal.onclick = (e) => {
if (e.target === modal) {
document.body.removeChild(modal);
}
};
copyBtn.onclick = () => {
GM_setClipboard(content);
showToast('内容已复制到剪贴板', 'success');
};
downloadBtn.onclick = async () => {
// 获取选中的媒体
const selectedMedia = mediaList.filter((_, index) => {
const checkbox = modal.querySelector(`.media-checkbox[data-index="${index}"]`);
return checkbox && checkbox.checked;
});
if (selectedMedia.length === 0) {
// 如果没有选中媒体,保存内容并打开发博页面
saveContentForPaste(content, []);
// 使用当前域名跳转,确保localStorage可访问
const targetUrl = window.location.hostname === 'www.weibo.com' ? 'https://www.weibo.com/' : 'https://weibo.com/';
GM_openInTab(targetUrl, { active: true });
showToast('📋 将为您打开微博主页,内容会自动填入并复制到剪贴板', 'success');
document.body.removeChild(modal);
return;
}
downloadBtn.disabled = true;
downloadBtn.textContent = '下载中...';
progressInfo.style.display = 'block';
progressInfo.textContent = '开始下载选中的媒体资源...';
try {
downloadedFiles = [];
const downloadedFileNames = [];
for (let i = 0; i < selectedMedia.length; i++) {
const media = selectedMedia[i];
const originalIndex = mediaList.indexOf(media);
const statusElement = modal.querySelector(`[data-index="${originalIndex}"] .weibo-pusher-media-status`);
progressInfo.textContent = `正在下载 ${i + 1}/${selectedMedia.length}: ${media.name}`;
try {
const downloadedFile = await downloadMedia(media, (status) => {
statusElement.textContent = status === 'downloading' ? '下载中...' :
status === 'completed' ? '已完成' : '下载失败';
statusElement.className = `weibo-pusher-media-status ${status}`;
});
downloadedFiles.push(downloadedFile);
downloadedFileNames.push(downloadedFile.name);
} catch (error) {
console.error('下载失败:', error);
statusElement.textContent = '下载失败';
statusElement.className = 'weibo-pusher-media-status failed';
}
}
progressInfo.textContent = `下载完成!已下载 ${downloadedFiles.length}/${selectedMedia.length} 个文件。正在跳转到微博主页...`;
// 保存内容和文件名列表,用于自动粘贴
saveContentForPaste(content, downloadedFileNames);
// 打开微博发布页面
setTimeout(() => {
// 使用当前域名跳转,确保localStorage可访问
const targetUrl = window.location.hostname === 'www.weibo.com' ? 'https://www.weibo.com/' : 'https://weibo.com/';
GM_openInTab(targetUrl, { active: true });
showToast(`📋 下载完成!内容会自动填入并复制到剪贴板,请手动上传 ${downloadedFiles.length} 个文件`, 'success', 6000);
document.body.removeChild(modal);
}, 1000);
} catch (error) {
console.error('下载过程出错:', error);
progressInfo.textContent = '下载过程中出现错误,请重试。';
showToast('下载失败,请重试', 'error');
} finally {
downloadBtn.disabled = false;
updateDownloadButtonText();
}
};
modal.style.display = 'block';
return modal;
}
// 处理推送
async function handlePush(postElement) {
if (isProcessing) {
showToast('正在处理中,请稍候...', 'info');
return;
}
// 在开始新的推送前,清理可能的过期数据
cleanupExpiredData();
isProcessing = true;
// 找到当前的推送按钮并更新状态
const pushBtn = postElement.querySelector('.weibo-pusher-btn');
const originalText = pushBtn ? pushBtn.textContent : '';
try {
// 更新按钮状态
if (pushBtn) {
pushBtn.textContent = '正在提取内容...';
pushBtn.disabled = true;
pushBtn.classList.add('processing');
}
// 提取内容和媒体
const content = await extractPostContent(postElement);
const mediaList = extractMediaResources(postElement);
if (!content && mediaList.length === 0) {
showToast('未找到可推送的内容', 'error');
return;
}
currentPostContent = content;
// 显示推送模态框
createPushModal(content, mediaList);
} catch (error) {
console.error('处理推送时出错:', error);
showToast('处理失败,请重试', 'error');
} finally {
// 恢复按钮状态
if (pushBtn) {
pushBtn.textContent = originalText;
pushBtn.disabled = false;
pushBtn.classList.remove('processing');
}
isProcessing = false;
}
}
// 添加推送按钮
function addPushButton(postElement) {
// 避免重复添加
if (postElement.querySelector('.weibo-pusher-btn')) {
return;
}
// 查找合适的位置插入按钮
const buttonContainers = [
// 原有的选择器
'.detail_wbtext_4CRf9',
'.wbpro-feed-content',
'.detail_text_1U10O',
'.Feed_body_3R0rO .Feed_content_2YeHn',
'.WB_detail .WB_text',
'.WB_text',
'[node-type="feed_list_content"]',
'.weibo-text',
// 新增对微博详情页的支持
'.WB_detail_warp .WB_text',
'.WB_detail_wrap .WB_text',
'.detail_main_3PZZf .WB_text',
'.detail_card_main_4fhCu .WB_text',
'.detail_body_1Dpoa .WB_text',
'.detail_info_4spYr .WB_text',
'.WB_feed_type .WB_text',
'.wb-detail .WB_text',
'.wb-item .WB_text',
// 新版详情页选择器
'.Feed_main_1b1q9 .WB_text',
'.Feed_detail_1b1q9 .WB_text',
'.WBDetail_main_3u .WB_text',
'.WBDetail_wrap_3u .WB_text',
'.wbpro-detail-content .WB_text',
'.wbpro-detail-wrapper .WB_text',
// 通用详情页按钮容器
'.detail-content',
'.weibo-detail-content',
'.post-content .WB_text',
'.wb-content',
'.detail_text',
'.woo-detail-content',
'.woo-detail-text'
];
let targetContainer = null;
for (const selector of buttonContainers) {
targetContainer = postElement.querySelector(selector);
if (targetContainer) {
break;
}
}
if (!targetContainer) {
return;
}
// 创建推送按钮
const pushBtn = document.createElement('button');
pushBtn.className = 'weibo-pusher-btn';
pushBtn.textContent = '📤 推送到我的微博';
pushBtn.title = '复制内容并下载媒体资源,然后推送到自己的微博';
pushBtn.onclick = async (e) => {
e.preventDefault();
e.stopPropagation();
await handlePush(postElement);
};
// 插入按钮
targetContainer.appendChild(pushBtn);
}
// 扫描页面并添加按钮
function scanAndAddButtons() {
const postSelectors = [
// 原有的选择器
'article.Feed_wrap_3v9LH',
'.Feed_body_3R0rO',
'.wbpro-feed-content',
'.WB_detail',
'.WB_cardwrap',
'[node-type="feed_list_item"]',
'.weibo-item',
// 新增对微博详情页的支持
'.WB_detail_warp',
'.WB_detail_wrap',
'.detail_main_3PZZf',
'.detail_card_main_4fhCu',
'.detail_body_1Dpoa',
'.detail_info_4spYr',
'.WB_feed_type .WB_detail',
'.WB_feed_type .WB_cardwrap',
'.WB_feed_type',
'.wb-item',
'.wb-detail',
// 对新版详情页的支持
'.Feed_main_1b1q9',
'.Feed_detail_1b1q9',
'.WBDetail_main_3u',
'.WBDetail_wrap_3u',
'.wbpro-detail-wrapper',
'.wbpro-detail-content',
// 通用容器选择器
'[data-pid]',
'[mid]',
'[post-id]',
'.post-detail',
'.weibo-detail'
];
postSelectors.forEach(selector => {
const posts = document.querySelectorAll(selector);
posts.forEach((post, index) => {
addPushButton(post);
});
});
}
// 监听页面变化
function startObserver() {
const observer = new MutationObserver((mutations) => {
let shouldScan = false;
mutations.forEach(mutation => {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
shouldScan = true;
}
});
if (shouldScan) {
setTimeout(scanAndAddButtons, 500);
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
// 初始化
function init() {
// 检查是否在微博主页,如果是则尝试自动粘贴
if ((window.location.hostname === 'weibo.com' || window.location.hostname === 'www.weibo.com') &&
window.location.pathname === '/') {
// 延迟检测发博框,确保页面完全加载
setTimeout(() => {
tryAutoPaste();
}, 2000);
}
// 等待页面加载完成
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => {
scanAndAddButtons();
startObserver();
}, 1000);
});
} else {
setTimeout(() => {
scanAndAddButtons();
startObserver();
}, 1000);
}
// 定期扫描
setInterval(scanAndAddButtons, 3000);
// 如果在主页,检查是否有待粘贴的内容并自动粘贴
if ((window.location.hostname === 'weibo.com' || window.location.hostname === 'www.weibo.com') &&
window.location.pathname === '/') {
// 等待页面加载完成后,检查一次是否有待粘贴内容
setTimeout(() => {
const savedData = getSavedContent();
if (savedData) {
const success = tryAutoPaste();
if (!success) {
// 如果第一次失败,等待3秒后再试一次
setTimeout(() => {
tryAutoPaste();
}, 3000);
}
}
}, 3000); // 等待3秒确保页面完全加载
}
}
// 启动脚本
init();
})();