导出 B 站视频笔记

export bilibili video note

// ==UserScript==
// @name         导出 B 站视频笔记
// @namespace    bilibili-video-note-export
// @version      0.1.0
// @author       showlotus
// @description  export bilibili video note
// @license      MIT
// @icon         https://www.google.com/s2/favicons?sz=64&domain=bilibili.com
// @match        https://www.bilibili.com/video/*
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/index.js
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @run-at       document-end
// ==/UserScript==

(t=>{if(typeof GM_addStyle=="function"){GM_addStyle(t);return}const o=document.createElement("style");o.textContent=t,document.head.append(o)})(' *,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }#bilibili-video-note-export .ml-\\[10px\\]{margin-left:10px}#bilibili-video-note-export .box-border{box-sizing:border-box}#bilibili-video-note-export .block{display:block}#bilibili-video-note-export .flex{display:flex}#bilibili-video-note-export .h-\\[85px\\]{height:85px}#bilibili-video-note-export .cursor-pointer{cursor:pointer}#bilibili-video-note-export .flex-col{flex-direction:column}#bilibili-video-note-export .items-center{align-items:center}#bilibili-video-note-export .justify-between{justify-content:space-between}#bilibili-video-note-export .gap-1{gap:.25rem}#bilibili-video-note-export .gap-2{gap:.5rem}#bilibili-video-note-export .rounded-md{border-radius:.375rem}#bilibili-video-note-export .border{border-width:1px}#bilibili-video-note-export .border-b{border-bottom-width:1px}#bilibili-video-note-export .border-l-0{border-left-width:0px}#bilibili-video-note-export .border-r-0{border-right-width:0px}#bilibili-video-note-export .border-t{border-top-width:1px}#bilibili-video-note-export .border-solid{border-style:solid}#bilibili-video-note-export .border-\\[\\#00aeec\\]{--tw-border-opacity: 1;border-color:rgb(0 174 236 / var(--tw-border-opacity, 1))}#bilibili-video-note-export .border-\\[\\#e3e5e7\\]{--tw-border-opacity: 1;border-color:rgb(227 229 231 / var(--tw-border-opacity, 1))}#bilibili-video-note-export .bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}#bilibili-video-note-export .px-3{padding-left:.75rem;padding-right:.75rem}#bilibili-video-note-export .px-5{padding-left:1.25rem;padding-right:1.25rem}#bilibili-video-note-export .py-1{padding-top:.25rem;padding-bottom:.25rem}#bilibili-video-note-export .py-3{padding-top:.75rem;padding-bottom:.75rem}#bilibili-video-note-export .text-sm{font-size:.875rem;line-height:1.25rem}#bilibili-video-note-export .text-\\[\\#00aeec\\]{--tw-text-opacity: 1;color:rgb(0 174 236 / var(--tw-text-opacity, 1))}.note-pc .note-container .note-header{min-height:62px}.note-pc .note-container .note-content.bilibili-video-note-export__after-note-export{height:calc(100% - 147px)}.note-pc .note-container .note-up.note-detail-up{margin-bottom:0!important}.note-pc .note-container .editor-innter{margin-top:16px!important}.note-pc.is-exporting,.note-pc.is-copying{height:auto!important}.note-pc.is-exporting #bilibili-video-note-export__export-image,.note-pc.is-copying #bilibili-video-note-export__copy-image{cursor:not-allowed;pointer-events:none;background-color:#00b5f6!important;color:#fff!important}.note-pc.is-exporting #bilibili-video-note-export__export-image:after{content:"\u5BFC\u51FA\u4E2D..."}.note-pc.is-copying #bilibili-video-note-export__copy-image:after{content:"\u590D\u5236\u4E2D..."}.note-pc.is-exporting .note-operation,.note-pc.is-copying .note-operation{display:none!important}.note-pc.is-exporting #bilibili-video-note-export>div,.note-pc.is-copying #bilibili-video-note-export>div{position:relative}.note-pc.is-exporting #bilibili-video-note-export>div:after,.note-pc.is-copying #bilibili-video-note-export>div:after{content:"";position:absolute;bottom:-1px;left:0;width:30%;height:1px;background:linear-gradient(90deg,transparent 0%,rgba(255,255,255,.2) 20%,#00aeec 50%,rgba(255,255,255,.2) 80%,transparent 100%);animation:loading 1.75s ease-in-out infinite alternate}@keyframes loading{0%{transform:translate(-50%)}50%{transform:translate(calc(85 / 30 * 100%))}to{transform:translate(-50%)}}#bilibili-video-note-export .after\\:content-\\[attr\\(data-text\\)\\]:after{--tw-content: attr(data-text);content:var(--tw-content)}#bilibili-video-note-export .hover\\:border-transparent:hover{border-color:transparent}#bilibili-video-note-export .hover\\:bg-\\[\\#00b5f6\\]:hover{--tw-bg-opacity: 1;background-color:rgb(0 181 246 / var(--tw-bg-opacity, 1))}#bilibili-video-note-export .hover\\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))} ');

(function (modernScreenshot) {
  'use strict';

  var _GM_getValue = /* @__PURE__ */ (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)();
  var _GM_setValue = /* @__PURE__ */ (() => typeof GM_setValue != "undefined" ? GM_setValue : void 0)();
  const $ = (selector) => {
    return document.querySelector(selector);
  };
  const $$ = (selector) => {
    return document.querySelectorAll(selector);
  };
  const logger = {
    debug: (...args) => {
      return;
    },
    info: (...args) => {
      console.info("%c[bilibili-video-note-export]", "color: #2196F3", ...args);
    },
    warn: (...args) => {
      console.warn("%c[bilibili-video-note-export]", "color: #FF9800", ...args);
    },
    error: (...args) => {
      console.error("%c[bilibili-video-note-export]", "color: #F44336", ...args);
    }
  };
  const watchElementVisibility = (selector, callback, options = { immediate: true }) => {
    const getEl = () => {
      return typeof selector === "string" ? document.querySelector(selector) : selector;
    };
    const intersectionObserver = new IntersectionObserver(
      (entries) => {
        const entry = entries[0];
        if (entry.isIntersecting) {
          callback(true);
        } else {
          callback(false);
        }
      },
      {
        // 当元素进入视口时触发
        threshold: 0.01
      }
    );
    const mutationObserver = new MutationObserver(() => {
      const el2 = getEl();
      if (el2) {
        intersectionObserver.unobserve(el2);
        intersectionObserver.observe(el2);
      }
    });
    mutationObserver.observe(document.body, {
      childList: true,
      subtree: true
    });
    const el = getEl();
    if (el && options.immediate) {
      const rect = el.getBoundingClientRect();
      const isVisible = rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth);
      if (isVisible) {
        callback(true);
      }
    }
    return () => {
      intersectionObserver.disconnect();
      mutationObserver.disconnect();
    };
  };
  const convertImagesToBase64 = async (el) => {
    const images = Array.from(el.querySelectorAll("img"));
    for (const img of images) {
      const src = img.src;
      img.removeAttribute("loading");
      if (src.startsWith("data:")) continue;
      try {
        const response = await fetch(src, { mode: "cors" });
        const blob = await response.blob();
        const base64 = await new Promise((resolve, reject) => {
          const reader = new FileReader();
          reader.onloadend = () => resolve(reader.result);
          reader.onerror = reject;
          reader.readAsDataURL(blob);
        });
        if (img.parentElement && img.parentElement.tagName.toLowerCase() === "picture") {
          const sources = img.parentElement.querySelectorAll("source");
          sources.forEach((source) => source.setAttribute("srcset", base64));
        }
        img.src = base64;
      } catch (err) {
        logger.warn(`无法转换图片为 base64:${src}`, err);
      }
    }
  };
  const copyScreenshotToClipboard = async (el) => {
    await convertImagesToBase64(el);
    const blob = await modernScreenshot.domToBlob(el, { type: "image/png" });
    const item = new ClipboardItem({ [blob.type]: blob });
    await navigator.clipboard.write([item]);
    logger.info("图片已复制到剪贴板");
  };
  const exportScreenshotToImage = async (el) => {
    await convertImagesToBase64(el);
    return modernScreenshot.domToImage(el, {
      debug: true,
      progress: (current, total) => {
      }
    }).then((img) => {
      const link = document.createElement("a");
      link.download = document.title + "-笔记截图.png";
      link.href = img.src;
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
    });
  };
  const mount = () => {
    const APP_ID = "bilibili-video-note-export";
    const root = document.createElement("div");
    root.setAttribute("id", APP_ID);
    let unwatchNoteDetail;
    const unwatchNotePc = watchElementVisibility("div.note-pc", (isNotePcShow) => {
      if (!isNotePcShow) {
        return;
      }
      if (unwatchNoteDetail) return;
      unwatchNoteDetail = watchElementVisibility("div.note-detail", (noteDetailShow) => {
        var _a, _b, _c, _d, _e, _f;
        if (noteDetailShow) {
          const el = $(`div#${APP_ID}`);
          if (el) {
            el.style.display = "block";
            (_a = $("div.note-container div.note-content")) == null ? void 0 : _a.classList.add(
              "bilibili-video-note-export__after-note-export"
            );
            (_b = $("div.note-container div.ql-editor")) == null ? void 0 : _b.setAttribute(
              "id",
              "bilibili-video-note-export__ql-editor"
            );
            const exportStyle = _GM_getValue("export-style", "default");
            if (exportStyle === "simple") {
              (_c = $("div#bilibili-video-note-export__ql-editor")) == null ? void 0 : _c.classList.remove("ql-editor");
            } else {
              (_d = $("div#bilibili-video-note-export__ql-editor")) == null ? void 0 : _d.classList.add("ql-editor");
            }
            const includeAuthorInfo = _GM_getValue("include-author-info", true);
            if (includeAuthorInfo) {
              $("div.note-container div.note-up.note-detail-up").style.display = "flex";
            } else {
              $("div.note-container div.note-up.note-detail-up").style.display = "none";
            }
            return;
          }
          root.innerHTML = /* html */
          `
          <div class="h-[85px] py-3 px-5 flex flex-col justify-between gap-1 box-border bg-white border-t border-b border-solid border-l-0 border-r-0 border-[#e3e5e7] text-sm">
            <div class="flex gap-2">
              <span>样式:</span>
              <div class="flex gap-2 items-center">
                <input type="radio" id="bilibili-video-note-export__default-style" name="export-style" value="default" checked />
                <label for="bilibili-video-note-export__default-style">默认样式</label>
              </div>
              <div class="flex gap-2 items-center">
                <input type="radio" id="bilibili-video-note-export__simple-style" name="export-style" value="simple" />
                <label for="bilibili-video-note-export__simple-style">简洁样式</label>
              </div>
              <div class="flex gap-2 items-center ml-[10px]">
                <input type="checkbox" id="bilibili-video-note-export__include-author-info" name="include-author-info" checked />
                <label for="bilibili-video-note-export__include-author-info">包含发布者</label>
              </div>
            </div>
            <div class="flex items-center gap-2">
              <span>操作:</span>
              <div id="bilibili-video-note-export__copy-image" class="py-1 px-3 border-[#00aeec] border-solid border rounded-md text-[#00aeec] cursor-pointer hover:bg-[#00b5f6] hover:border-transparent hover:text-white after:content-[attr(data-text)]" data-text="复制为图片"></div>
              <div id="bilibili-video-note-export__export-image" class="py-1 px-3 border-[#00aeec] border-solid border rounded-md text-[#00aeec] cursor-pointer hover:bg-[#00b5f6] hover:border-transparent hover:text-white after:content-[attr(data-text)]" data-text="导出为图片"></div>
            </div>
          </div>
        `;
          (_e = $("div.note-container")) == null ? void 0 : _e.insertBefore(root, $("div.note-container div.note-content"));
          $$('input[name="export-style"]').forEach((radio) => {
            radio.addEventListener("change", (e) => {
              const target = e.target;
              if (target.value === "simple") {
                $("div#bilibili-video-note-export__ql-editor").classList.remove("ql-editor");
              } else {
                $("div#bilibili-video-note-export__ql-editor").classList.add("ql-editor");
              }
              _GM_setValue("export-style", target.value);
            });
          });
          $('input[name="include-author-info"]').addEventListener("change", (e) => {
            const target = e.target;
            if (target.checked) {
              $("div.note-container div.note-up.note-detail-up").style.display = "flex";
            } else {
              $("div.note-container div.note-up.note-detail-up").style.display = "none";
            }
            _GM_setValue("include-author-info", target.checked);
          });
          $("div#bilibili-video-note-export__copy-image").addEventListener("click", async () => {
            $("div.note-pc").classList.add("is-copying");
            await copyScreenshotToClipboard($("div.note-container div.note-content"));
            $("div.note-pc").classList.remove("is-copying");
          });
          $("div#bilibili-video-note-export__export-image").addEventListener("click", async () => {
            logger.debug("导出为图片", {
              exportStyle: _GM_getValue("export-style", "default"),
              includeAuthorInfo: _GM_getValue("include-author-info", true)
            });
            $("div.note-pc").classList.add("is-exporting");
            await exportScreenshotToImage($("div.note-container div.note-content"));
            $("div.note-pc").classList.remove("is-exporting");
          });
        } else {
          (_f = $("div.note-container div.note-content")) == null ? void 0 : _f.classList.remove(
            "bilibili-video-note-export__after-note-export"
          );
          const el = $(`div#${APP_ID}`);
          if (el) {
            el.style.display = "none";
          }
        }
      });
    });
    logger.info("插件运行中...");
    return () => {
      var _a, _b;
      (_a = $("div.note-container div.note-content")) == null ? void 0 : _a.classList.remove(
        "bilibili-video-note-export__after-note-export"
      );
      $("div.note-pc").classList.remove("is-copying", "is-exporting");
      (_b = $(`div#${APP_ID}`)) == null ? void 0 : _b.remove();
      unwatchNotePc();
      unwatchNoteDetail == null ? void 0 : unwatchNoteDetail();
    };
  };
  mount();

})(modernScreenshot);