// ==UserScript== // @name 【何忆为】【Highlight Your Why】B站视频常用详情复制 // @namespace http://scriptcat.org/ // @version 1.7 // @description 回忆一下为什么你当时要复制提取这个b站视频链接,复制链接时加入【B站视频信息(短链、标题、作者、播放量、弹幕数、发布时间、总时长)和复制时间】让你看到文本后能回忆起当时复制这个链接的原因。让你不忘初心牢记使命 // @match https://www.bilibili.com/video/* // @license MIT // @icon https://cdn.jsdelivr.net/gh/ijjie/icons@main/hyw64.ico // @grant GM_setClipboard // @grant unsafeWindow // ==/UserScript== (function() { 'use strict'; // ============================================================ // 工具类 // ============================================================ /** * 将数字格式化为万/亿单位 * @param {number} num - 原始数字 * @returns {string} 格式化后的字符串 */ function formatNumber(num) { if (num >= 100000000) { return (num / 100000000).toFixed(1) + '亿'; } else if (num >= 10000) { return (num / 10000).toFixed(1) + '万'; } else { return num.toString(); } } /** * 将Unix时间戳(秒)转为日期时间字符串 * @param {number} ts - 秒级时间戳 * @returns {string} 格式:YYYY-MM-DD HH:MM:SS */ function formatTimestamp(ts) { if (!ts) { return ''; } var date = new Date(ts * 1000); var year = date.getFullYear(); var month = String(date.getMonth() + 1).padStart(2, '0'); var day = String(date.getDate()).padStart(2, '0'); var hours = String(date.getHours()).padStart(2, '0'); var minutes = String(date.getMinutes()).padStart(2, '0'); var seconds = String(date.getSeconds()).padStart(2, '0'); return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds; } /** * 将秒数转为时长格式(HH:MM:SS 或 MM:SS) * @param {number} sec - 总秒数 * @returns {string} 格式化后的时长 */ function formatDuration(sec) { if (!sec) { return ''; } var hours = Math.floor(sec / 3600); var minutes = Math.floor((sec % 3600) / 60); var seconds = sec % 60; if (hours > 0) { return hours + ':' + String(minutes).padStart(2, '0') + ':' + String(seconds).padStart(2, '0'); } else { return String(minutes).padStart(2, '0') + ':' + String(seconds).padStart(2, '0'); } } // ============================================================ // 数据获取 // ============================================================ /** * 从页面全局状态中提取视频信息 * @returns {Object|null} 包含视频信息的对象,失败返回 null */ function getVideoInfo() { var state = unsafeWindow.__INITIAL_STATE__; if (!state) { console.error('[福站何忆为_数据] 无法获取 __INITIAL_STATE__,请确认页面已加载完成'); return null; } var videoData = state.videoData || {}; // ---- 声明变量并赋值 ---- var bvid = videoData.bvid || ''; var title = videoData.title || ''; var author = videoData.owner ? videoData.owner.name : ''; var pubdate = videoData.pubdate || 0; var duration = videoData.duration || 0; var view = videoData.stat ? videoData.stat.view : 0; var danmaku = videoData.stat ? videoData.stat.danmaku : 0; console.log('[福站何忆为_数据] 读取结果:', { bvid: bvid, title: title, author: author, view: view, danmaku: danmaku, pubdate: pubdate, duration: duration }); return { bvid: bvid, title: title, author: author, pubdate: pubdate, duration: duration, view: view, danmaku: danmaku }; } // ============================================================ // 文本组装 // ============================================================ /** * 根据信息对象组装待复制的文本 * @param {Object} info - getVideoInfo 返回的信息对象 * @returns {string} 完整的复制文本 */ function buildCopyText(info) { // 格式化各字段 var viewFormatted = formatNumber(info.view); var danmakuFormatted = String(info.danmaku); var pubdateFormatted = formatTimestamp(info.pubdate); var durationFormatted = formatDuration(info.duration); var currentTime = formatTimestamp(Math.floor(Date.now() / 1000)); // ---- 逐行拼接(短链直接内联) ---- var text = ''; text += '链接: https://www.bilibili.com/video/' + info.bvid + '/ \n'; text += '【标题:' + info.title + '】\n'; text += '【作者:' + info.author + '】\n'; text += '【(播放量)' + viewFormatted + '-(弹幕数)' + danmakuFormatted + '-(发布时间)' + pubdateFormatted + '】\n'; text += '【视频总时长 ' + durationFormatted + '】\n'; text += '【复制时间:' + currentTime + '】'; console.log('[福站何忆为_拼串] 生成的复制文本:\n' + text); return text; } // ============================================================ // 界面交互 // ============================================================ /** * 复制主函数(按钮点击事件) */ function copyVideoInfo() { console.log('[福站何忆为_ui] ===== 开始复制 ====='); var info = getVideoInfo(); if (!info) { console.error('[福站何忆为_ui] 获取数据失败,终止复制'); return; } var copyText = buildCopyText(info); GM_setClipboard(copyText, 'text'); console.log('[福站何忆为_ui] 已复制到剪贴板'); // 按钮反馈:变为 ✅,2秒后恢复 📋 var btn = document.getElementById('copyBilibiliInfoBtn'); if (btn) { btn.textContent = '✅'; btn.title = '已复制'; setTimeout(function() { btn.textContent = '📋'; btn.title = '复制视频信息'; }, 2000); } console.log('[福站何忆为_ui] ===== 复制完成 ====='); } /** * 在页面右上角添加悬浮按钮(30×30 大圆角,仅图标) */ function addButton() { if (document.getElementById('copyBilibiliInfoBtn')) { return; } var btn = document.createElement('button'); btn.id = 'copyBilibiliInfoBtn'; btn.textContent = '📋'; btn.title = '复制视频信息'; // 按钮样式:30×30,大圆角 btn.style.position = 'fixed'; btn.style.top = '100px'; btn.style.right = '20px'; btn.style.zIndex = '9999'; btn.style.width = '30px'; btn.style.height = '30px'; btn.style.borderRadius = '12px'; // 大圆角 btn.style.border = 'none'; btn.style.backgroundColor = '#00a1d6'; btn.style.color = '#fff'; btn.style.fontSize = '16px'; btn.style.cursor = 'pointer'; btn.style.boxShadow = '0 2px 8px rgba(0,0,0,0.3)'; btn.style.display = 'flex'; btn.style.alignItems = 'center'; btn.style.justifyContent = 'center'; btn.style.padding = '0'; btn.onclick = copyVideoInfo; document.body.appendChild(btn); console.log('[福站何忆为_ui] 按钮已添加到页面'); } // ============================================================ // 启动 // ============================================================ if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', addButton); } else { addButton(); } })();