// ==UserScript== // @name 哔哩哔哩收藏夹导出 // @namespace https://github.com/AHCorn/Bilibili-Favlist-Export // @icon https://www.bilibili.com/favicon.ico // @version 2.0 // @description 导出哔哩哔哩收藏夹为 CSV 或 HTML 文件,以便导入 Raindrop 或 Firefox。 // @author AHCorn // @match http*://space.bilibili.com/*/* // @grant GM_addStyle // @grant GM_download // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @downloadURL none // ==/UserScript== (function () { 'use strict'; const DELAY = 2000; let csvHeaderOptions = { title: "\uFEFFtitle", url: "url", foldername: "folder", created: "created" }; let csvHeaderActive = ["\uFEFFtitle", "url", "folder", "created"]; function updateCSVHeader() { csvHeaderActive = Object.keys(csvHeaderOptions).filter(option => csvInclude[option]).map(option => csvHeaderOptions[option]); } let csvContent = csvHeaderActive.join(",") + "\n"; let htmlTemplateStart = `
`; const HTML_TEMPLATE_END = `
`; let htmlContent = ""; let globalParentFolderName = ""; let csvInclude = { title: true, url: true, foldername: true, created: true }; let exportCurrentFolderOnly = false; GM_addStyle(` #bilibili-export-panel { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: linear-gradient(135deg, #f6f8fa, #e9ecef); border-radius: 24px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1), 0 1px 8px rgba(0, 0, 0, 0.06); padding: 30px; width: 90%; max-width: 400px; display: none; z-index: 10000; font-family: 'Segoe UI', 'Roboto', sans-serif; transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); } #bilibili-export-panel h2 { margin: 0 0 20px; color: #00a1d6; font-size: 28px; text-align: center; font-weight: 700; } #current-exporting { margin-bottom: 20px; padding: 10px; background-color: rgba(0, 161, 214, 0.1); border-left: 4px solid #00a1d6; border-right: 4px solid #00a1d6; border-radius: 4px; font-size: 14px; color: #00a1d6; text-align: center; transition: all 0.5s ease; } #current-exporting.completed { background-color: rgba(76, 175, 80, 0.1); border-left-color: #4CAF50; border-right-color: #4CAF50; color: #4CAF50; } @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } } #current-exporting.completed { animation: pulse 0.5s ease-in-out; } #formatSelector { display: flex; justify-content: center; align-items: center; margin-bottom: 25px; position: relative; background-color: #e0e0e0; border-radius: 20px; padding: 5px; box-shadow: inset 0 2px 4px rgba(0,0,0,0.1); } .formatButton { z-index: 1; padding: 10px 20px; font-size: 16px; color: #3c4043; cursor: pointer; transition: color 0.3s ease-in-out; font-weight: 600; flex: 1; text-align: center; } .formatButton.selected { color: #FFF; } .slider { position: absolute; left: 5px; top: 5px; background-color: #00a1d6; border-radius: 15px; transition: transform 0.3s ease-in-out; height: calc(100% - 10px); width: calc(50% - 5px); z-index: 0; box-shadow: 0 2px 4px rgba(0,0,0,0.2); } .toggle-switch { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; padding: 10px 15px; background-color: #f1f3f4; border-radius: 12px; transition: all 0.3s ease; } .toggle-switch:hover { background-color: #e8eaed; } .toggle-switch label { font-size: 16px; color: #3c4043; font-weight: 600; } .switch { position: relative; display: inline-block; width: 52px; height: 28px; } .switch input { opacity: 0; width: 0; height: 0; } .switch-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 34px; } .switch-slider:before { position: absolute; content: ""; height: 20px; width: 20px; left: 4px; bottom: 4px; background-color: white; transition: .4s; border-radius: 50%; } input:checked + .switch-slider { background-color: #00a1d6; } input:checked + .switch-slider:before { transform: translateX(24px); } #export-button { display: block; width: 100%; padding: 12px; background-color: #00a1d6; color: white; border: none; border-radius: 12px; font-size: 18px; font-weight: bold; cursor: pointer; transition: all 0.3s ease; margin-top: 20px; position: relative; overflow: hidden; } #export-button:hover { background-color: #0091c2; transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0, 161, 214, 0.3); } #export-button:disabled { background-color: #cccccc; cursor: not-allowed; } #export-button::after { content: ''; position: absolute; top: 0; left: 0; height: 100%; width: 0%; background-color: rgba(255,255,255,0.2); transition: width 0.3s ease; } .input-group { margin-bottom: 15px; } .input-group input { width: calc(100% - 20px); padding: 10px; border: 1px solid #dadce0; border-radius: 8px; font-size: 14px; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes slideIn { from { transform: translate(-50%, -60%); } to { transform: translate(-50%, -50%); } } #bilibili-export-panel.show { display: block; animation: fadeIn 0.3s ease-out, slideIn 0.3s ease-out; } #panel-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 9999; display: none; } `); let gen = listGen(); let panel = null; let exportButton = null; let formatButtons = null; let folderInputSection = null; let bookmarkTitleInput = null; let globalFolderNameInput = null; let lastAddedFolderName = ""; let totalPage = 0; let currentPage = 0; let isExporting = false; let exportFormat = GM_getValue('exportFormat', 'csv'); function getCSVFileName() { let userName = $("#h-name").text(); return userName + "的收藏夹.csv"; } function getHTMLFileName() { let userName = $("#h-name").text(); return userName + "的收藏夹.html"; } function getFolderName() { return $("#fav-createdList-container .fav-item.cur a.text").text().trim(); } function escapeCSV(field) { return '"' + String(field).replace(/"/g, '""') + '"'; } function getCurrentTimestamp() { return Math.floor(Date.now() / 1000); } function addHTMLFolder(folderName) { let dateNow = getCurrentTimestamp(); if (folderName !== lastAddedFolderName) { if (lastAddedFolderName !== "") { htmlContent += `
\n`; } htmlContent += `
\n`; lastAddedFolderName = folderName; } } function addHTMLBookmark(folderName, title, url, created) { addHTMLFolder(folderName); let dateNow = new Date(created).getTime() / 1000; htmlContent += `