// ==UserScript== // @name 哔记-B Note (B站笔记插件) // @namespace http://tampermonkey.net/ // @version 1.6 // @description 可替代B站原有笔记功能的油猴插件(时间戳、截图、本地导入导出、字幕遮挡、快捷键、markdown写作) // @author XYZ // @match *://*.bilibili.com/video/* // @require https://code.jquery.com/jquery-3.6.0.min.js // @require https://code.jquery.com/ui/1.12.1/jquery-ui.js // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.5.0/jszip.min.js // @require https://unpkg.com/axios@1.1.2/dist/axios.min.js // @license MIT License // @grant GM_registerMenuCommand // @grant GM_getValue // @grant GM_setValue // @icon data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIxLjUiIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBhcmlhLWhpZGRlbj0idHJ1ZSI+CiAgPHBhdGggc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBkPSJNMTIgNy41aDEuNW0tMS41IDNoMS41bS03LjUgM2g3LjVtLTcuNSAzaDcuNW0zLTloMy4zNzVjLjYyMSAwIDEuMTI1LjUwNCAxLjEyNSAxLjEyNVYxOGEyLjI1IDIuMjUgMCAwMS0yLjI1IDIuMjVNMTYuNSA3LjVWMThhMi4yNSAyLjI1IDAgMDAyLjI1IDIuMjVNMTYuNSA3LjVWNC44NzVjMC0uNjIxLS41MDQtMS4xMjUtMS4xMjUtMS4xMjVINC4xMjVDMy41MDQgMy43NSAzIDQuMjU0IDMgNC44NzVWMThhMi4yNSAyLjI1IDAgMDAyLjI1IDIuMjVoMTMuNU02IDcuNWgzdjNINnYtM3oiPjwvcGF0aD4KPC9zdmc+ // @downloadURL https://update.greasyfork.icu/scripts/475808/%E5%93%94%E8%AE%B0-B%20Note%20%28B%E7%AB%99%E7%AC%94%E8%AE%B0%E6%8F%92%E4%BB%B6%29.user.js // @updateURL https://update.greasyfork.icu/scripts/475808/%E5%93%94%E8%AE%B0-B%20Note%20%28B%E7%AB%99%E7%AC%94%E8%AE%B0%E6%8F%92%E4%BB%B6%29.meta.js // ==/UserScript== (function () { 'use strict'; // 填写你的github的token以及repo(仓库名) let token = ''; let repo = ''; // Add the TOAST UI Editor CSS $('head').append(''); // CSS const style = ` .floating-btn { width: 60px; height: 15px; font-size: 10px; cursor: pointer; border: none; outline: none; background: transparent; color: white; font-family: 'Times New Roman', Times, serif; font-weight: 100; position: relative; transition: all 0.5s; z-index: 1; } .floating-btn::before { content: ""; position: absolute; top: 0; left: 0; width: 0.5px; height: 100%; background-color: white; z-index: -1; transition: all 0.5s; padding-left: 2px; } .floating-btn:hover::before { width: 100%; content: "选集"; } .floating-btn:hover { color: black; } .floating-btn:active:before { background: #b9b9b9; } .switch1 { position: relative; display: inline-block; width: 60px; height: 25px; } .switch1 input { display: none; } .slider1 { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #3C3C3C; -webkit-transition: .4s; transition: .4s; border-radius: 34px; } .slider1:before { position: absolute; content: ""; height: 25px; width: 10px; left: 0px; bottom: 0px; background-color: white; -webkit-transition: .4s; transition: .4s; border-radius: 10%; } input:checked + .slider1 { background-color: #0E6EB8; } input:focus + .slider1 { box-shadow: 0 0 1px #2196F3; } input:checked + .slider1:before { -webkit-transform: translateX(25px); -ms-transform: translateX(25px); transform: translateX(85px); } .slider1:after { content: '本地'; color: white; display: block; position: absolute; transform: translate(-50%,-50%); top: 50%; left: 50%; font-size: 10px; font-family: Verdana, sans-serif; } input:checked + .slider1:after { content: '在线'; } `; $('head').append(''); // Add the TOAST UI Editor JS const scriptEditor = document.createElement('script'); scriptEditor.src = 'https://uicdn.toast.com/editor/latest/toastui-editor-all.js'; document.body.appendChild(scriptEditor); // Add the JQuery UI $('head').append(''); // Bilibili AVI switch function selectAV1() { const av1Radio = document.querySelector('input.bui-radio-input[value="3"][name="bui-radio3"]'); if (av1Radio && !av1Radio.checked) { av1Radio.click(); } } function waitForElement(selector, callback) { const element = document.querySelector(selector); if (element) { callback(); } else { setTimeout(() => waitForElement(selector, callback), 500); } } waitForElement('input.bui-radio-input[value="3"][name="bui-radio3"]', selectAV1); // Determine if it is a multi-part video. let isCur = false; let curList; let originalcurListParent; if (document.querySelector('.cur-list')) { isCur = true; curList = $('#multi_page'); originalcurListParent = $('#multi_page').parent() } else { isCur = false; } // Create a switch using SVG. function createSVGIcon(svgContent) { const svgIcon = $(svgContent); svgIcon.css({ width: '24px', height: '24px', verticalAlign: 'middle', marginRight: '5px' }); return svgIcon; } const openEditorIcon = ''; const closeEditorIcon = ''; // Create the button const openEditorButton = $(''); openEditorButton.append(createSVGIcon(openEditorIcon)); $('body').append(openEditorButton); const toggleButton = $(''); const toggleButtonText = $('打开哔记'); toggleButton.append(createSVGIcon(openEditorIcon)).append(toggleButtonText); $('body').append(toggleButton); const buttonStyles = ` .B-Note-button { position: fixed; bottom: 10px; right: -60px; width: 100px; height: 35px; z-index: 10000; background-color: white; border: none; cursor: pointer; transition: right 0.3s, background-color 0.3s; } .B-Note-button:hover { right: 0px; } `; const styleElement = $(''); styleElement.text(buttonStyles); $('head').append(styleElement); // video element var videoElement = document.querySelector('video'); var lastMarkedTime = null; let saveButton; let helpButton; let editor; let editorDiv; let isEditorOpen = false; let embedMode = false; let videoWrapper = $('#bilibili-player'); let isUpload = false; let originalVideoWrapperParent; let originalContainerStyle; let originalDisplayStatus = []; let isScrollbarDisabled = false; // Get the current date, title, and current webpage link. function getPageInfo() { let currentDate = new Date(); let formattedDate = `${currentDate.getFullYear()}年${currentDate.getMonth() + 1}月${currentDate.getDate()}日`; let pageTitle = document.title; let pageLink = window.location.href; return { formattedDate, pageTitle, pageLink }; } let pageInfo = getPageInfo(); // Use IndexedDB to automatically back up notes. const dbName = 'BNoteDB'; const storeName = 'notes'; let db; const openRequest = indexedDB.open(dbName, 1); openRequest.onupgradeneeded = function (e) { const db = e.target.result; if (!db.objectStoreNames.contains(storeName)) { db.createObjectStore(storeName, { keyPath: 'pageTitle' }); } }; openRequest.onsuccess = function (e) { db = e.target.result; }; function saveNoteToDB() { if (isEditorOpen) { let { formattedDate, pageTitle, pageLink } = getPageInfo(); const content = editor.getMarkdown(); const timestamp = Date.now(); const note = { pageTitle, content, timestamp }; const transaction = db.transaction(storeName, 'readwrite'); const store = transaction.objectStore(storeName); store.put(note); } } setInterval(saveNoteToDB, 120000); // Upload to Github async function handleImageInsertion() { if (!isUpload) { return; } let content = editor.getMarkdown(); const regex = /!\[.*?\]\(data:image\/.*?;base64,.*?\)/g; const matches = content.match(regex); if (matches) { const newContent = await matches.reduce(async (prevContentPromise, match) => { const prevContent = await prevContentPromise; const base64 = match.substring(match.indexOf('base64,') + 7, match.lastIndexOf(')')); const blob = base64ToBlob(base64); const imageUrl = await uploadImageToGitHub(blob); return prevContent.replace(match, ``); }, Promise.resolve(content)); editor.setMarkdown(newContent); } } function base64ToBlob(base64) { const binary = atob(base64); const array = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) { array[i] = binary.charCodeAt(i); } return new Blob([array], { type: 'image/png' }); } async function uploadImageToGitHub(blob) { const branch = 'main'; const currentDate = new Date(); currentDate.setMinutes(currentDate.getMinutes() + currentDate.getTimezoneOffset() + 8 * 60); const year = currentDate.getFullYear(); const month = currentDate.getMonth() + 1; const day = currentDate.getDate(); const hours = currentDate.getHours(); const minutes = currentDate.getMinutes(); const seconds = currentDate.getSeconds(); const time = `${hours}-${minutes}-${seconds}`; const pageInfo = getPageInfo(); const invalidChars = /[<>:"/\\|?*]/g; const cleanedPageTitle = pageInfo.pageTitle.replace(invalidChars, ''); const path = `images/B-Note/${year}/${month}/${day}/${cleanedPageTitle}/${time}.png`; const url = `https://api.github.com/repos/${repo}/contents/${path}`; const base64 = await blobToBase64(blob); const payload = { message: 'Upload image', content: base64, branch: branch, }; const response = await axios.put(url, payload, { headers: { 'Authorization': `token ${token}`, 'Content-Type': 'application/json', }, }); return response.data.content.download_url; } function blobToBase64(blob) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result.split(',')[1]); reader.onerror = reject; reader.readAsDataURL(blob); }); } const container = $('
'); // Function to create the editor function createEditor() { container.css({ position: 'fixed', top: '8%', right: '0%', width: '32%', height: '86%', zIndex: 9998, backgroundColor: '#fff', border: '1px solid #ccc', borderRadius: '5px', padding: '0px', overflow: 'hidden', }); $('body').append(container); // Make the container resizable container.resizable({ handles: 'n, e, s, w, ne, se, sw, nw', minWidth: 300, minHeight: 200, resize: function (event, ui) { const newHeight = ui.size.height - 80; editorDiv.height(newHeight + 'px'); } }); const handle = $('