您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
炸号微博一键备份,支持JSON和HTML导出
// ==UserScript== // @name 炸号微博备份 // @namespace https://dun.mianbaoduo.com/@fun // @version 0.8 // @description 炸号微博一键备份,支持JSON和HTML导出 // @author fun // @match *://weibo.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=weibo.com // @grant none // @license GPL // ==/UserScript== (function () { "use strict"; let wrapper = document.createElement("div"); let backup = document.createElement("div"); backup.innerHTML = `<div><svg id="bIndicator" style="vertical-align: -12px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" class="woo-spinner-main Scroll_loadingIcon_2nyZ4"><g fill="none" stroke-width="5" stroke-miterlimit="10" stroke="currentColor" style="animation: 2s linear 0s infinite normal none running woo-spinner-_-rotate; height: 50px; transform-origin: center center; width: 50px;"><circle cx="25" cy="25" r="20" opacity=".3"></circle><circle cx="25" cy="25" r="20" stroke-dasharray="25,200" stroke-linecap="round" style="animation: 1.5s ease-in-out 0s infinite normal none running woo-spinner-_-dash;"></circle></g></svg><span id="bMSG"></span></div> <div style="text-align: center;"><a href="https://dun.mianbaoduo.com/@fun" target="_blank" style="border-radius: 0.166667rem; display: inline-block; font-weight: bold; color: #ca3a1f; margin-left: 0px; padding: 3px 14px;font-size: 13px;text-align: center;border: 1px solid #cfcfcf;margin-top: 8px;">打赏<span style="font-size: 16px; vertical-align: -2px; margin-left: 5px;">😋</span></a></div> `; function download(content, fileName, contentType) { var a = document.createElement("a"); var file = new Blob([content], { type: contentType }); a.href = URL.createObjectURL(file); a.download = fileName; a.click(); } // HTML模板 const htmlTemplate = `<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>微博数据可视化</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; padding: 20px; } .container { max-width: 1200px; margin: 0 auto; background: white; border-radius: 15px; box-shadow: 0 20px 40px rgba(0,0,0,0.1); overflow: hidden; } .header { background: linear-gradient(135deg, #ff6b6b, #ee5a52); color: white; padding: 30px; text-align: center; } .header h1 { font-size: 2.5em; margin-bottom: 10px; } .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; padding: 30px; background: #f8f9fa; } .stat-card { background: white; padding: 25px; border-radius: 10px; text-align: center; box-shadow: 0 5px 15px rgba(0,0,0,0.08); transition: transform 0.3s ease; } .stat-card:hover { transform: translateY(-5px); } .stat-number { font-size: 2.5em; font-weight: bold; color: #ff6b6b; margin-bottom: 10px; } .stat-label { color: #666; font-size: 14px; } .content { padding: 30px; } .filters { display: flex; flex-wrap: wrap; gap: 15px; margin-bottom: 30px; padding: 20px; background: #f8f9fa; border-radius: 10px; } .filter-group { display: flex; flex-direction: column; gap: 5px; } .filter-group label { font-size: 14px; color: #666; font-weight: 500; } .filter-input { padding: 8px 12px; border: 2px solid #ddd; border-radius: 5px; font-size: 14px; transition: border-color 0.3s ease; } .filter-input:focus { outline: none; border-color: #ff6b6b; } .results-info { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 15px 20px; background: #f8f9fa; border-radius: 8px; flex-wrap: wrap; gap: 10px; } .results-count { color: #666; font-size: 14px; } .per-page-group { display: flex; align-items: center; gap: 10px; } .per-page-group label { font-size: 14px; color: #666; } .per-page-select { padding: 5px 10px; border: 2px solid #ddd; border-radius: 5px; font-size: 14px; background: white; } .weibo-list { display: grid; gap: 20px; } .weibo-item { background: white; border: 1px solid #eee; border-radius: 10px; padding: 20px; transition: all 0.3s ease; box-shadow: 0 2px 10px rgba(0,0,0,0.05); } .weibo-item:hover { box-shadow: 0 10px 30px rgba(0,0,0,0.1); transform: translateY(-2px); } .weibo-header { display: flex; align-items: center; margin-bottom: 15px; gap: 15px; } .avatar { width: 50px; height: 50px; border-radius: 50%; object-fit: cover; border: 2px solid #ff6b6b; } .user-info { flex: 1; } .username { font-weight: bold; color: #333; margin-bottom: 5px; } .time { color: #999; font-size: 12px; } .weibo-content { line-height: 1.6; color: #333; margin-bottom: 15px; } .retweet { background: #f8f9fa; border-left: 4px solid #ff6b6b; padding: 15px; margin: 15px 0; border-radius: 0 8px 8px 0; } .retweet-header { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; } .retweet-avatar { width: 30px; height: 30px; border-radius: 50%; object-fit: cover; } .retweet-user { font-weight: bold; color: #ff6b6b; font-size: 14px; } .retweet-content { color: #555; line-height: 1.5; } .images-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px; margin-top: 15px; } .weibo-image { width: 100%; max-width: 300px; border-radius: 8px; cursor: pointer; transition: transform 0.3s ease; } .weibo-image:hover { transform: scale(1.05); } .stats-details { display: flex; gap: 20px; margin-top: 15px; padding-top: 15px; border-top: 1px solid #eee; font-size: 14px; color: #666; } .pagination { display: flex; justify-content: center; align-items: center; gap: 10px; margin-top: 30px; padding: 20px; flex-wrap: wrap; } .page-btn { padding: 8px 15px; border: 1px solid #ddd; background: white; border-radius: 5px; cursor: pointer; transition: all 0.3s ease; min-width: 40px; } .page-btn:hover, .page-btn.active { background: #ff6b6b; color: white; border-color: #ff6b6b; } .page-btn:disabled { opacity: 0.5; cursor: not-allowed; } .page-info { color: #666; font-size: 14px; margin: 0 10px; } @media (max-width: 768px) { .container { margin: 10px; border-radius: 10px; } .header { padding: 20px; } .header h1 { font-size: 2em; } .stats { grid-template-columns: repeat(2, 1fr); padding: 20px; gap: 15px; } .filters { flex-direction: column; gap: 10px; } .weibo-header { flex-direction: column; align-items: flex-start; gap: 10px; } .results-info { flex-direction: column; align-items: flex-start; gap: 15px; } .pagination { justify-content: center; gap: 5px; } .page-btn { padding: 6px 12px; font-size: 12px; } } </style> </head> <body> <div class="container"> <div class="header"> <h1>🐦 微博数据可视化</h1> <p>您的微博备份数据 - 生成时间: {{EXPORT_TIME}}</p> </div> <div class="stats"> <div class="stat-card"> <div class="stat-number" id="totalCount">0</div> <div class="stat-label">总微博数</div> </div> <div class="stat-card"> <div class="stat-number" id="retweetCount">0</div> <div class="stat-label">转发数</div> </div> <div class="stat-card"> <div class="stat-number" id="imageCount">0</div> <div class="stat-label">图片数</div> </div> <div class="stat-card"> <div class="stat-number" id="daySpan">0</div> <div class="stat-label">时间跨度(天)</div> </div> </div> <div class="content"> <div class="filters"> <div class="filter-group"> <label>搜索内容</label> <input type="text" class="filter-input" id="searchInput" placeholder="搜索微博内容..."> </div> <div class="filter-group"> <label>开始日期</label> <input type="date" class="filter-input" id="startDate"> </div> <div class="filter-group"> <label>结束日期</label> <input type="date" class="filter-input" id="endDate"> </div> <div class="filter-group"> <label>类型</label> <select class="filter-input" id="typeFilter"> <option value="all">全部</option> <option value="original">原创</option> <option value="retweet">转发</option> <option value="with-images">有图片</option> </select> </div> </div> <div class="results-info"> <div class="results-count" id="resultsCount"></div> <div class="per-page-group"> <label for="perPageSelect">每页显示:</label> <select id="perPageSelect" class="per-page-select"> <option value="5">5条</option> <option value="10" selected>10条</option> <option value="20">20条</option> <option value="50">50条</option> <option value="100">100条</option> </select> </div> </div> <div class="weibo-list" id="weiboList"></div> <div class="pagination" id="pagination"></div> </div> </div> <script> // 嵌入的微博数据 const WEIBO_DATA = {{WEIBO_DATA}}; let allData = WEIBO_DATA; let filteredData = [...allData]; let currentPage = 1; let itemsPerPage = 10; // 页面加载完成后初始化 document.addEventListener('DOMContentLoaded', function() { initApp(); }); function initApp() { updateStats(); initFilters(); renderWeibos(); // 绑定每页显示条数选择器 document.getElementById('perPageSelect').addEventListener('change', (e) => { itemsPerPage = parseInt(e.target.value); currentPage = 1; renderWeibos(); }); } function updateStats() { const totalCount = allData.length; const retweetCount = allData.filter(item => item.retweeted_status).length; const imageCount = allData.reduce((sum, item) => sum + (item.images?.length || 0), 0); // 计算时间跨度 const dates = allData.map(item => new Date(item.created_at)).filter(d => !isNaN(d)); const daySpan = dates.length > 0 ? Math.ceil((Math.max(...dates) - Math.min(...dates)) / (1000 * 60 * 60 * 24)) : 0; document.getElementById('totalCount').textContent = totalCount; document.getElementById('retweetCount').textContent = retweetCount; document.getElementById('imageCount').textContent = imageCount; document.getElementById('daySpan').textContent = daySpan; } function initFilters() { // 设置日期范围 const dates = allData.map(item => new Date(item.created_at)).filter(d => !isNaN(d)); if (dates.length > 0) { const minDate = new Date(Math.min(...dates)); const maxDate = new Date(Math.max(...dates)); document.getElementById('startDate').value = minDate.toISOString().split('T')[0]; document.getElementById('endDate').value = maxDate.toISOString().split('T')[0]; } // 绑定过滤器事件 document.getElementById('searchInput').addEventListener('input', applyFilters); document.getElementById('startDate').addEventListener('change', applyFilters); document.getElementById('endDate').addEventListener('change', applyFilters); document.getElementById('typeFilter').addEventListener('change', applyFilters); } function applyFilters() { const searchTerm = document.getElementById('searchInput').value.toLowerCase(); const startDate = new Date(document.getElementById('startDate').value); const endDate = new Date(document.getElementById('endDate').value); const typeFilter = document.getElementById('typeFilter').value; filteredData = allData.filter(item => { // 搜索过滤 if (searchTerm && !item.text.toLowerCase().includes(searchTerm)) { return false; } // 日期过滤 const itemDate = new Date(item.created_at); if (!isNaN(startDate) && itemDate < startDate) return false; if (!isNaN(endDate) && itemDate > endDate) return false; // 类型过滤 if (typeFilter === 'original' && item.retweeted_status) return false; if (typeFilter === 'retweet' && !item.retweeted_status) return false; if (typeFilter === 'with-images' && (!item.images || item.images.length === 0)) return false; return true; }); currentPage = 1; renderWeibos(); } function updateResultsCount() { const startIndex = (currentPage - 1) * itemsPerPage + 1; const endIndex = Math.min(currentPage * itemsPerPage, filteredData.length); const totalPages = Math.ceil(filteredData.length / itemsPerPage); const resultsText = filteredData.length === 0 ? '没有找到匹配的微博' : \`显示第 \${startIndex}-\${endIndex} 条,共 \${filteredData.length} 条微博 (第 \${currentPage}/\${totalPages} 页)\`; document.getElementById('resultsCount').textContent = resultsText; } function renderWeibos() { const container = document.getElementById('weiboList'); const startIndex = (currentPage - 1) * itemsPerPage; const endIndex = startIndex + itemsPerPage; const pageData = filteredData.slice(startIndex, endIndex); // 更新结果计数 updateResultsCount(); if (pageData.length === 0) { container.innerHTML = \` <div style="text-align: center; padding: 40px; color: #999;"> <p style="font-size: 18px;">📭 没有找到匹配的微博</p> <p style="margin-top: 10px;">尝试调整筛选条件</p> </div> \`; document.getElementById('pagination').innerHTML = ''; return; } container.innerHTML = pageData.map((item, index) => { const user = item.raw?.user || {}; const avatar = user.profile_image_url || 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIHZpZXdCb3g9IjAgMCA1MCA1MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGNpcmNsZSBjeD0iMjUiIGN5PSIyNSIgcj0iMjUiIGZpbGw9IiNmMGYwZjAiLz4KPHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4PSIxMyIgeT0iMTMiPgo8cGF0aCBkPSJNMjAgMjFWMTlBNCA0IDAgMCAwIDEyIDEzSDhBNCA0IDAgMCAwIDQgMTdWMTlNMTYgN0E0IDQgMCAxIDEgOCA3QTQgNCAwIDAgMSAxNiA3WiIgc3Ryb2tlPSIjOTk5IiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8L3N2Zz4KPC9zdmc+'; const globalIndex = startIndex + index + 1; return \` <div class="weibo-item"> <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 15px;"> <div class="weibo-header" style="margin-bottom: 0;"> <img src="\${avatar}" alt="头像" class="avatar" onerror="this.src='data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIHZpZXdCb3g9IjAgMCA1MCA1MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGNpcmNsZSBjeD0iMjUiIGN5PSIyNSIgcj0iMjUiIGZpbGw9IiNmMGYwZjAiLz4KPHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4PSIxMyIgeT0iMTMiPgo8cGF0aCBkPSJNMjAgMjFWMTlBNCA0IDAgMCAwIDEyIDEzSDhBNCA0IDAgMCAwIDQgMTdWMTlNMTYgN0E0IDQgMCAxIDEgOCA3QTQgNCAwIDAgMSAxNiA3WiIgc3Ryb2tlPSIjOTk5IiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8L3N2Zz4KPC9zdmc+'"> <div class="user-info"> <div class="username">\${user.screen_name || '微博用户'}</div> <div class="time">\${formatDate(item.created_at)}</div> </div> </div> <div style="background: #ff6b6b; color: white; padding: 3px 8px; border-radius: 12px; font-size: 12px; min-width: 24px; text-align: center;"> #\${globalIndex} </div> </div> <div class="weibo-content"> \${formatText(item.text)} </div> \${item.retweeted_status ? generateRetweetHTML(item.retweeted_status, item.raw?.retweeted_status) : ''} \${item.images && item.images.length > 0 ? \` <div class="images-grid"> \${item.images.map(img => \` <img src="\${img}" alt="微博图片" class="weibo-image" onclick="openImage('\${img}')"> \`).join('')} </div> \` : ''} \${item.raw ? \` <div class="stats-details"> <span>💖 \${item.raw.attitudes_count || 0}</span> <span>🔄 \${item.raw.reposts_count || 0}</span> <span>💬 \${item.raw.comments_count || 0}</span> <span>👁️ \${item.raw.reads_count || 0}</span> </div> \` : ''} </div> \`; }).join(''); renderPagination(); } function generateRetweetHTML(retweetedStatus, rawRetweetedStatus) { const retweetUser = rawRetweetedStatus?.user || {}; const retweetAvatar = retweetUser.profile_image_url || 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMzAiIHZpZXdCb3g9IjAgMCAzMCAzMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGNpcmNsZSBjeD0iMTUiIGN5PSIxNSIgcj0iMTUiIGZpbGw9IiNmMGYwZjAiLz4KPHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4PSI3IiB5PSI3Ij4KPHBhdGggZD0iTTEzIDEzVjEyQTMgMyAwIDAgMCA3IDlINUEzIDMgMCAwIDAgMiAxMlYxM005IDVBMyAzIDAgMSAxIDMgNUEzIDMgMCAwIDEgOSA1WiIgc3Ryb2tlPSIjOTk5IiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+Cjwvc3ZnPgo8L3N2Zz4='; return \` <div class="retweet"> <div class="retweet-header"> <img src="\${retweetAvatar}" alt="转发作者头像" class="retweet-avatar" onerror="this.src='data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMzAiIHZpZXdCb3g9IjAgMCAzMCAzMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGNpcmNsZSBjeD0iMTUiIGN5PSIxNSIgcj0iMTUiIGZpbGw9IiNmMGYwZjAiLz4KPHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4PSI3IiB5PSI3Ij4KPHBhdGggZD0iTTEzIDEzVjEyQTMgMyAwIDAgMCA3IDlINUEzIDMgMCAwIDAgMiAxMlYxM005IDVBMyAzIDAgMSAxIDMgNUEzIDMgMCAwIDEgOSA1WiIgc3Ryb2tlPSIjOTk5IiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+Cjwvc3ZnPgo8L3N2Zz4='"> <span class="retweet-user">\${retweetUser.screen_name || '微博用户'}</span> <span style="color: #999; font-size: 12px;">\${rawRetweetedStatus?.created_at ? formatDate(rawRetweetedStatus.created_at) : ''}</span> </div> <div class="retweet-content"> \${formatText(retweetedStatus.text)} </div> \${retweetedStatus.images && retweetedStatus.images.length > 0 ? \` <div class="images-grid"> \${retweetedStatus.images.map(img => \` <img src="\${img}" alt="转发微博图片" class="weibo-image" onclick="openImage('\${img}')"> \`).join('')} </div> \` : ''} </div> \`; } function renderPagination() { const totalPages = Math.ceil(filteredData.length / itemsPerPage); const pagination = document.getElementById('pagination'); if (totalPages <= 1) { pagination.innerHTML = ''; return; } let paginationHTML = ''; paginationHTML += \`<button class="page-btn" \${currentPage === 1 ? 'disabled' : ''} onclick="changePage(\${currentPage - 1})">‹ 上一页</button>\`; paginationHTML += \`<span class="page-info">第 \${currentPage} / \${totalPages} 页</span>\`; if (totalPages > 5) { if (currentPage > 3) { paginationHTML += \`<button class="page-btn" onclick="changePage(1)">1</button>\`; if (currentPage > 4) { paginationHTML += \`<span class="page-info">...</span>\`; } } } for (let i = Math.max(1, currentPage - 2); i <= Math.min(totalPages, currentPage + 2); i++) { paginationHTML += \`<button class="page-btn \${i === currentPage ? 'active' : ''}" onclick="changePage(\${i})">\${i}</button>\`; } if (totalPages > 5) { if (currentPage < totalPages - 2) { if (currentPage < totalPages - 3) { paginationHTML += \`<span class="page-info">...</span>\`; } paginationHTML += \`<button class="page-btn" onclick="changePage(\${totalPages})">\${totalPages}</button>\`; } } paginationHTML += \`<button class="page-btn" \${currentPage === totalPages ? 'disabled' : ''} onclick="changePage(\${currentPage + 1})">下一页 ›</button>\`; pagination.innerHTML = paginationHTML; } function changePage(page) { currentPage = page; renderWeibos(); window.scrollTo(0, 0); } function formatDate(dateStr) { try { const date = new Date(dateStr); return date.toLocaleString('zh-CN'); } catch { return dateStr; } } function formatText(text) { if (!text) return ''; let cleanText = text .replace(/<br\\s*\\/?>/gi, '\\n') .replace(/<span[^>]*class="expand"[^>]*>.*?<\\/span>/gi, '') .replace(/<[^>]+>/g, '') .replace(/ /g, ' ') .replace(/</g, '<') .replace(/>/g, '>') .replace(/&/g, '&') .replace(/"/g, '"'); return cleanText .replace(/\\n/g, '<br>') .replace(/https?:\\/\\/[^\\s]+/g, '<a href="$&" target="_blank" style="color: #ff6b6b;">$&</a>') .replace(/@([^@\\s]+)/g, '<span style="color: #ff6b6b;">@$1</span>') .replace(/#([^#\\s]+)#/g, '<span style="color: #ff6b6b;">#$1#</span>'); } function openImage(src) { window.open(src, '_blank'); } </script> </body> </html>`; async function fetchContent(uid = 0, page = 1, type = "my") { let api = `https://weibo.com/ajax/statuses/mymblog?uid=${uid}&page=${page}&feature=0`; if (type === "fav") { api = `https://weibo.com/ajax/favorites/all_fav?uid=${uid}&page=${page}`; } if (type === "like") { api = `https://weibo.com/ajax/statuses/likelist?uid=${uid}&page=${page}`; } const req = await fetch(api, { headers: { accept: "application/json, text/plain, */*", "accept-language": "zh-CN,zh;q=0.9,en-IN;q=0.8,en;q=0.7,ar;q=0.6", "sec-ch-ua": '" Not A;Brand";v="99", "Chromium";v="101", "Google Chrome";v="101"', "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": '"macOS"', "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-origin", "x-requested-with": "XMLHttpRequest", }, referrer: `https://weibo.com/u/${uid}`, referrerPolicy: "strict-origin-when-cross-origin", body: null, method: "GET", mode: "cors", credentials: "include", }); const data = await req.json(); return data; } async function fetchAll(type = "my") { var uid = $CONFIG.uid; let page = 1; let allPageData = []; let noMore = false; for (let index = 0; index < Infinity; index++) { console.log("scan", "page", page); printLog(`正在备份第 ${page} 页`); for (let index = 0; index < 10; index++) { const pageData = await fetchContent(uid, page, type); if (pageData.ok) { const dataList = type === "fav" ? pageData.data : pageData.data.list; allPageData.push(dataList); if (dataList.length === 0) noMore = true; break; } await new Promise((resolve) => { setTimeout(resolve, 8 * 1000); }); console.log("retry", index); printLog( `[重试]备份第 ${page} 页,错误内容: ${JSON.stringify(pageData)}` ); } page++; if (noMore) break; await new Promise((resolve) => { setTimeout(resolve, 5 * 1000); }); } console.log("all done"); printLog(`备份完毕! 打开【下载内容】查看数据文件`); const parsed = allPageData.reduce((all, dataList) => { dataList.forEach((c) => { const formatted = { images: c.pic_ids && c.pic_ids.map((d) => { return c.pic_infos[d].large.url; }), text: c.text, created_at: c.created_at, raw: c, }; if (c.retweeted_status) { formatted.retweeted_status = { text: c.retweeted_status.text, images: c.retweeted_status.pic_ids && c.retweeted_status.pic_ids.map((d) => { return c.retweeted_status.pic_infos[d].large.url; }), }; } all.push(formatted); }); return all; }, []); console.log("data", allPageData, parsed); // 生成文件名时间戳 const timestamp = Date.now(); const typeText = type === 'fav' ? '收藏' : type === 'like' ? '赞' : '微博'; // 下载JSON文件 download( JSON.stringify(parsed, null, 2), `weibo-${timestamp}-${type}.json`, "application/json" ); // 生成并下载HTML文件 const exportTime = new Date().toLocaleString('zh-CN'); const htmlContent = htmlTemplate .replace('{{WEIBO_DATA}}', JSON.stringify(parsed)) .replace('{{EXPORT_TIME}}', exportTime); download( htmlContent, `weibo-${timestamp}-${type}.html`, "text/html" ); } function printLog(msg) { tip.innerText = msg; } backup.setAttribute( "style", "display:none; background: white; color: black; font-size: 13px; padding: 10px 10px 15px 10px;" ); const title = document.createElement("h2"); title.innerHTML = "微博备份"; title.setAttribute("style", "font-size: 15px;color: black;margin: 15px 0;"); wrapper.appendChild(title); wrapper.appendChild(backup); document.body.appendChild(wrapper); wrapper.setAttribute( "style", `position: fixed; border-radius: 3px; background: white; top: 80px; right: 20px; z-index: 100000; padding:10px 15px; text-align: center; ` ); let started = false; let allButtons = []; function showAll() { allButtons.forEach((btn) => { btn.style.display = "block"; }); } function hideAll() { allButtons.forEach((btn) => { btn.style.display = "none"; }); } function createExport(name, type) { let btn = document.createElement("button"); wrapper.appendChild(btn); btn.innerHTML = name; btn.setAttribute( "style", `border-radius: 0.166667rem; display: block; font-weight: bold; color: #444; margin:0 auto; padding: 5px 14px;font-size: 13px;text-align: center;border: 1px solid #cfcfcf;margin-top: 3px; cursor: pointer; margin-bottom: 7px;` ); btn.addEventListener("click", async () => { if (started) { alert("备份正在进行中..."); return; } started = true; hideAll(); backup.style.display = "block"; indicator.style.display = "inline-block"; console.log("fetchAll", type); await fetchAll(type); started = false; showAll(); indicator.style.display = "none"; backup.style.display = "none"; }); allButtons.push(btn); } let tip = document.getElementById("bMSG"); let indicator = document.getElementById("bIndicator"); createExport("备份我的微博", "my"); createExport("备份我的收藏", "fav"); createExport("备份我的赞", "like"); })();