// ==UserScript==
// @name B站动态助手
// @name:en Bilibili dynamic assistant
// @namespace https://github.com/2540709491/bilibili-dynamic-picker
// @version 2.5.2
// @description 通过 Bilibili 官方 API 快速定位指定时间段的动态,支持批量删除与自定义延迟
// @description:en Quickly locate the dynamics of a specified time period through the official Bilibili API, and support batch deletion and custom delays
// @author SXM
// @match https://space.bilibili.com/*/dynamic*
// @match https://t.bilibili.com/*
// @icon https://www.bilibili.com/favicon.ico
// @grant none
// @license MIT
// @downloadURL https://update.greasyfork.icu/scripts/576577/B%E7%AB%99%E5%8A%A8%E6%80%81%E5%8A%A9%E6%89%8B.user.js
// @updateURL https://update.greasyfork.icu/scripts/576577/B%E7%AB%99%E5%8A%A8%E6%80%81%E5%8A%A9%E6%89%8B.meta.js
// ==/UserScript==
(function() {
'use strict';
const API_BASE = 'https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/all';
const DELETE_API = 'https://api.bilibili.com/x/dynamic/feed/operate/remove';
const FEATURES = 'itemOpusStyle,listOnlyfans,opusBigCover,onlyfansVote,decorationCard,onlyfansAssetsV2,forwardListHidden,ugcDelete';
const PROGRESS_PREFIX = 'bili_jumper_progress_';
let isSearching = false;
let abortController = null;
let currentDelay = 50; // 默认50ms
function debugLog(...args) { console.log('[API跳转]', ...args); }
function formatDate(date) { return `${date.getFullYear()}-${date.getMonth()+1}-${date.getDate()}`; }
// 获取 bili_jct (CSRF Token)
function getCsrfToken() {
const cookies = document.cookie.split(';');
for (let c of cookies) {
c = c.trim();
if (c.startsWith('bili_jct=')) {
return c.substring('bili_jct='.length);
}
}
return null;
}
function timeAgo(ts) {
const now = Date.now() / 1000;
const diff = now - ts;
if (diff < 60) return '刚刚';
if (diff < 3600) return `${Math.floor(diff / 60)}分钟前`;
if (diff < 86400) return `${Math.floor(diff / 3600)}小时前`;
if (diff < 2592000) return `${Math.floor(diff / 86400)}天前`;
const d = new Date(ts * 1000);
return formatDate(d);
}
function extractSummary(item) {
const major = item.modules?.module_dynamic?.major;
const descText = item.modules?.module_dynamic?.desc?.text || '';
const type = major?.type;
let summary = '';
if (descText) {
summary = descText.replace(/\n/g, ' ').substring(0, 100);
if (descText.length > 100) summary += '...';
} else if (type === 'MAJOR_TYPE_OPUS' && major.opus?.summary?.text) {
summary = major.opus.summary.text.replace(/\n/g, ' ').substring(0, 100);
if (major.opus.summary.text.length > 100) summary += '...';
} else if (type === 'MAJOR_TYPE_ARCHIVE' && major.archive?.title) {
summary = '视频:' + major.archive.title;
} else if (type === 'MAJOR_TYPE_DRAW') {
const count = major.draw?.items?.length || 0;
summary = `带图动态 (${count}张图片)`;
} else if (type === 'MAJOR_TYPE_ARTICLE' && major.article?.title) {
summary = '专栏:' + major.article.title;
}
if (!summary) summary = '(无文字内容)';
return summary;
}
function extractCover(item) {
const major = item.modules?.module_dynamic?.major;
const type = major?.type;
if (type === 'MAJOR_TYPE_OPUS' && major.opus?.pics?.length) return major.opus.pics[0].url;
if (type === 'MAJOR_TYPE_DRAW' && major.draw?.items?.length) return major.draw.items[0].src;
if (type === 'MAJOR_TYPE_ARCHIVE' && major.archive?.cover) return major.archive.cover;
if (type === 'MAJOR_TYPE_ARTICLE' && major.article?.covers?.length) return major.article.covers[0];
return null;
}
function simplifyItem(item) {
return {
id_str: item.id_str,
pub_ts: item.modules?.module_author?.pub_ts,
pub_time: item.modules?.module_author?.pub_time || timeAgo(item.modules?.module_author?.pub_ts),
author_name: item.modules?.module_author?.name || '未知',
author_face: item.modules?.module_author?.face || '',
jump_url: item.basic?.jump_url || `https://t.bilibili.com/${item.id_str}`,
summary: extractSummary(item),
cover: extractCover(item),
};
}
async function customDelay() {
const input = document.getElementById('api-delay-input');
if (input) currentDelay = parseInt(input.value) || 50;
await new Promise(resolve => setTimeout(resolve, currentDelay));
}
function getProgressKey(mid) { return PROGRESS_PREFIX + mid; }
function saveProgress(mid, startStr, endStr, offset, foundItems, pageCount) {
const key = getProgressKey(mid);
const data = { startStr, endStr, offset, foundItems, pageCount, updatedAt: Date.now() };
localStorage.setItem(key, JSON.stringify(data));
}
function loadProgress(mid) {
const key = getProgressKey(mid);
const saved = localStorage.getItem(key);
if (saved) {
try { return JSON.parse(saved); } catch (e) { return null; }
}
return null;
}
function clearProgress(mid) { localStorage.removeItem(getProgressKey(mid)); }
function getHostMid() {
const url = location.href;
let match = url.match(/space\.bilibili\.com\/(\d+)/);
if (match) return match[1];
match = url.match(/t\.bilibili\.com\/(\d+)/);
if (match) return match[1];
const midMeta = document.querySelector('meta[name="bili-shortVer"]');
if (midMeta) return midMeta.getAttribute('content');
return null;
}
function createDatePickerHTML(prefix, label) {
return `
`;
}
function createPanel() {
const panel = document.createElement('div');
panel.innerHTML = `
${createDatePickerHTML('start', '起始时间')}
${createDatePickerHTML('end', '结束时间')}
⏳ 搜索进度
已翻页:0 页
已找到:0 条
当前翻至:等待中...
区间:
`;
document.body.appendChild(panel);
// 拖拽功能
const panelEl = document.getElementById('api-jumper-panel');
const headerEl = document.getElementById('api-panel-header');
let isDragging = false, startX, startY, initialLeft, initialTop;
headerEl.addEventListener('mousedown', function(e) {
// 避免拖拽时误触关闭按钮
if (e.target.id === 'api-close-btn') return;
isDragging = true;
startX = e.clientX;
startY = e.clientY;
const rect = panelEl.getBoundingClientRect();
initialLeft = rect.left;
initialTop = rect.top;
panelEl.style.right = 'auto'; // 取消右侧定位,改用left/top
document.addEventListener('mousemove', onDrag);
document.addEventListener('mouseup', onDragEnd);
});
function onDrag(e) {
if (!isDragging) return;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
panelEl.style.left = (initialLeft + dx) + 'px';
panelEl.style.top = (initialTop + dy) + 'px';
}
function onDragEnd() {
isDragging = false;
document.removeEventListener('mousemove', onDrag);
document.removeEventListener('mouseup', onDragEnd);
}
const currentYear = new Date().getFullYear();
['start', 'end'].forEach(prefix => {
const yearSelect = document.getElementById(`${prefix}-year`);
for (let y = currentYear; y >= 2009; y--) {
const opt = document.createElement('option');
opt.value = y;
opt.textContent = y + '年';
yearSelect.appendChild(opt);
}
if (prefix === 'end') yearSelect.value = currentYear;
});
function updateDayOptions(prefix) {
const year = parseInt(document.getElementById(`${prefix}-year`).value);
const month = parseInt(document.getElementById(`${prefix}-month`).value);
const daysInMonth = new Date(year, month, 0).getDate();
const daySelect = document.getElementById(`${prefix}-day`);
const currentVal = daySelect.value;
daySelect.innerHTML = '';
for (let d = 1; d <= daysInMonth; d++) {
const opt = document.createElement('option');
opt.value = d;
opt.textContent = d + '日';
daySelect.appendChild(opt);
}
if (currentVal && parseInt(currentVal) <= daysInMonth) daySelect.value = currentVal;
else daySelect.value = '';
}
['start', 'end'].forEach(prefix => {
document.getElementById(`${prefix}-year`).addEventListener('change', () => updateDayOptions(prefix));
document.getElementById(`${prefix}-month`).addEventListener('change', () => updateDayOptions(prefix));
updateDayOptions(prefix);
});
document.getElementById('api-close-btn').addEventListener('click', () => document.getElementById('api-jumper-panel').remove());
document.getElementById('api-start-btn').addEventListener('click', startApiSearch);
document.getElementById('api-stop-btn').addEventListener('click', stopSearch);
document.getElementById('api-reset-btn').addEventListener('click', () => {
const mid = getHostMid();
if (mid) { clearProgress(mid); alert('进度已重置。'); }
});
}
function getDateFromPanel(prefix) {
const year = parseInt(document.getElementById(`${prefix}-year`).value);
const month = parseInt(document.getElementById(`${prefix}-month`).value);
const dayVal = document.getElementById(`${prefix}-day`).value;
const day = dayVal ? parseInt(dayVal) : null;
if (isNaN(year) || isNaN(month)) return null;
return { year, month, day };
}
function buildDateRange() {
const start = getDateFromPanel('start');
const end = getDateFromPanel('end');
if (!start || !end) return null;
let startDate, endDate;
if (start.day) startDate = new Date(start.year, start.month - 1, start.day);
else startDate = new Date(start.year, start.month - 1, 1);
if (end.day) endDate = new Date(end.year, end.month - 1, end.day);
else endDate = new Date(end.year, end.month, 0);
if (startDate > endDate) {
alert('起始时间不能晚于结束时间');
return null;
}
return {
startDate,
endDate: new Date(endDate.getTime() + 86400000),
};
}
async function startApiSearch() {
const mid = getHostMid();
if (!mid) {
alert('未识别到 UP 主 ID');
return;
}
const range = buildDateRange();
if (!range) return;
const { startDate, endDate } = range;
const startStr = formatDate(startDate);
const endStr = formatDate(new Date(endDate.getTime() - 86400000));
document.getElementById('api-target-show').textContent = `${startStr} 至 ${endStr}`;
const savedProgress = loadProgress(mid);
let offset = null, pageCount = 0, foundItems = [];
if (savedProgress && savedProgress.startStr === startStr && savedProgress.endStr === endStr) {
const ok = confirm(`发现上次进度:已翻 ${savedProgress.pageCount} 页,找到 ${savedProgress.foundItems.length} 条。\n是否继续?`);
if (ok) {
offset = savedProgress.offset;
foundItems = savedProgress.foundItems;
pageCount = savedProgress.pageCount;
} else {
clearProgress(mid);
}
}
abortController = new AbortController();
isSearching = true;
document.getElementById('api-start-btn').style.display = 'none';
document.getElementById('api-stop-btn').style.display = 'block';
document.getElementById('api-progress').style.display = 'block';
document.getElementById('api-page-count').textContent = pageCount;
document.getElementById('api-found-count').textContent = foundItems.length;
document.getElementById('api-current-date').textContent = '(搜索中…)';
try {
while (isSearching) {
await customDelay();
pageCount++;
const params = new URLSearchParams({
host_mid: mid,
platform: 'web',
features: FEATURES,
web_location: '333.1365'
});
if (offset) params.set('offset', offset);
const resp = await fetch(`${API_BASE}?${params}`, {
credentials: 'include',
signal: abortController.signal
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
const json = await resp.json();
if (json.code !== 0) throw new Error(json.message || 'API错误');
const items = json.data?.items || [];
if (!items.length) break;
const lastTs = items[items.length-1]?.modules?.module_author?.pub_ts;
let oldestDate = null;
if (lastTs) {
oldestDate = new Date(lastTs * 1000);
document.getElementById('api-current-date').textContent = formatDate(oldestDate);
} else {
document.getElementById('api-current-date').textContent = '未知';
}
for (const item of items) {
const ts = item.modules?.module_author?.pub_ts;
if (!ts) continue;
const d = new Date(ts * 1000);
if (d >= startDate && d < endDate) {
foundItems.push(simplifyItem(item));
}
}
document.getElementById('api-page-count').textContent = pageCount;
document.getElementById('api-found-count').textContent = foundItems.length;
saveProgress(mid, startStr, endStr, offset, foundItems, pageCount);
if (oldestDate && oldestDate < startDate) { isSearching = false; break; }
if (!json.data.has_more || !json.data.offset) break;
offset = json.data.offset;
}
stopSearch();
displayResult(foundItems, startStr, endStr);
} catch (err) {
if (err.name !== 'AbortError') {
alert('搜索失败: ' + err.message);
console.error(err);
}
stopSearch();
try { saveProgress(mid, startStr, endStr, offset, foundItems, pageCount); } catch (e) {}
}
}
function stopSearch() {
isSearching = false;
if (abortController) abortController.abort();
document.getElementById('api-start-btn').style.display = 'block';
document.getElementById('api-stop-btn').style.display = 'none';
}
function displayResult(items, startStr, endStr) {
const old = document.getElementById('api-result-panel');
if (old) old.remove();
const resultDiv = document.createElement('div');
resultDiv.id = 'api-result-panel';
resultDiv.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:10001;' +
'background:rgba(255,255,255,0.98);padding:0;border-radius:16px;box-shadow:0 12px 40px rgba(0,0,0,0.25);' +
'width:600px;max-height:80vh;display:flex;flex-direction:column;overflow:hidden;';
const header = document.createElement('div');
header.style.cssText = 'padding:16px 20px;border-bottom:1px solid #eee;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;';
header.innerHTML = `
📋 ${startStr} 至 ${endStr} · 共 ${items.length} 条
`;
resultDiv.appendChild(header);
const list = document.createElement('div');
list.style.cssText = 'overflow-y:auto;padding:12px 20px 20px;flex:1;';
if (!items.length) {
list.innerHTML = '没有动态
';
} else {
items.forEach((item, index) => {
const card = document.createElement('div');
card.className = 'dynamic-card-item';
card.style.cssText = 'display:flex;align-items:flex-start;padding:14px 0;border-bottom:1px solid #f0f0f0;transition:background 0.15s;';
card.addEventListener('mouseenter', () => { if (!card.classList.contains('selected')) card.style.background = '#fafafa'; });
card.addEventListener('mouseleave', () => { if (!card.classList.contains('selected')) card.style.background = 'transparent'; });
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'dynamic-checkbox';
checkbox.dataset.id = item.id_str;
checkbox.style.cssText = 'margin-right:12px;margin-top:12px;flex-shrink:0;transform:scale(1.2);cursor:pointer;';
checkbox.addEventListener('change', () => {
if (checkbox.checked) {
card.classList.add('selected');
card.style.background = '#fff0f0';
} else {
card.classList.remove('selected');
card.style.background = 'transparent';
}
updateSelectedCount(items);
});
card.appendChild(checkbox);
const right = document.createElement('div');
right.style.cssText = 'flex:1;min-width:0;';
right.innerHTML = `
${escapeHtml(item.author_name)}
${escapeHtml(item.pub_time)}
${escapeHtml(item.summary)}
`;
if (item.cover) {
const img = document.createElement('img');
img.src = item.cover;
img.style.cssText = 'max-height:60px;max-width:90px;border-radius:6px;object-fit:cover;border:1px solid #eee;';
right.appendChild(img);
}
card.appendChild(right);
card.addEventListener('click', (e) => {
if (e.target !== checkbox) {
window.open(item.jump_url, '_blank');
}
});
list.appendChild(card);
});
}
resultDiv.appendChild(list);
document.body.appendChild(resultDiv);
// 全选按钮
document.getElementById('api-select-all').addEventListener('click', () => {
const checkboxes = document.querySelectorAll('.dynamic-checkbox');
const allChecked = Array.from(checkboxes).every(cb => cb.checked);
checkboxes.forEach(cb => {
cb.checked = !allChecked;
cb.dispatchEvent(new Event('change'));
});
document.getElementById('api-select-all').textContent = allChecked ? '全选' : '取消全选';
});
// 删除选中按钮
document.getElementById('api-delete-selected').addEventListener('click', () => {
const checked = document.querySelectorAll('.dynamic-checkbox:checked');
if (checked.length === 0) {
alert('请至少勾选一条动态');
return;
}
const ids = Array.from(checked).map(cb => cb.dataset.id);
if (confirm(`确定要删除选中的 ${ids.length} 条动态吗?此操作不可恢复!`)) {
batchDelete(ids, items);
}
});
document.getElementById('api-close-result').addEventListener('click', () => resultDiv.remove());
}
function updateSelectedCount(items) {
const count = document.querySelectorAll('.dynamic-checkbox:checked').length;
const btn = document.getElementById('api-delete-selected');
if (btn) btn.textContent = `🗑 删除选中 (${count})`;
}
async function batchDelete(ids, items) {
const csrf = getCsrfToken();
if (!csrf) {
alert('未获取到 CSRF Token (bili_jct),请刷新页面后重试。');
return;
}
let success = 0, fail = 0;
for (let i = 0; i < ids.length; i++) {
try {
await new Promise(resolve => setTimeout(resolve, 500 + Math.random() * 1200));
const resp = await fetch(`${DELETE_API}?csrf=${csrf}`, {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ dyn_id_str: ids[i] })
});
const json = await resp.json();
if (json.code === 0) {
success++;
const card = document.querySelector(`.dynamic-checkbox[data-id="${ids[i]}"]`)?.parentElement;
if (card) card.style.display = 'none';
} else {
fail++;
debugLog(`删除失败 ${ids[i]}:`, json.message);
}
} catch (e) {
fail++;
debugLog(`删除异常 ${ids[i]}:`, e.message);
}
}
alert(`删除完成:成功 ${success} 条,失败 ${fail} 条。`);
const remaining = document.querySelectorAll('.dynamic-checkbox:checked').length;
document.getElementById('api-delete-selected').textContent = `🗑 删除选中 (${remaining})`;
}
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
// 启动
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => setTimeout(createPanel, 1000));
} else {
setTimeout(createPanel, 1000);
}
console.log('%c[API跳转] v2.5.1 已加载!支持拖拽面板+批量删除+自定义延迟','color:#667eea;font-weight:bold;background:#f0f4ff;padding:4px 8px;border-radius:4px;');
})();