您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
B站专栏 Markdown 编辑器
当前为
- // ==UserScript==
- // @name Bilibili-Markdown
- // @namespace https://github.com/LuckyPuppy514
- // @version 1.0.3
- // @author LuckyPuppy514
- // @copyright 2023, Grant LuckyPuppy514 (https://github.com/LuckyPuppy514)
- // @license MIT
- // @description B站专栏 Markdown 编辑器
- // @homepage https://github.com/LuckyPuppy514/Bilibili-Markdown
- // @icon https://article.biliimg.com/bfs/article/3e927f211d063b57cd39c4041ac2d07fd959726c.png
- // @match https://member.bilibili.com/article-text/home*
- // @require https://unpkg.com/jquery@3.2.1/dist/jquery.min.js
- // ==/UserScript==
- "use strict";
- console.log(`
- 🅱️Ⓜ️🅱️Ⓜ️🅱️Ⓜ️🅱️Ⓜ️🅱️Ⓜ️🅱️Ⓜ️🅱️Ⓜ️🅱️Ⓜ️🅱️Ⓜ️🅱️Ⓜ️🅱️Ⓜ️🅱️Ⓜ️
- Ⓜ️ 🅱️
- 🅱️ Bilibili-Markdown Ⓜ️
- Ⓜ️ 🅱️
- 🅱️ https://github.com/LuckyPuppy514/Bilibili-Markdown Ⓜ️
- Ⓜ️ 🅱️
- 🅱️ 2023 @LuckyPuppy514 Ⓜ️
- Ⓜ️ 🅱️
- 🅱️Ⓜ️🅱️Ⓜ️🅱️Ⓜ️🅱️Ⓜ️🅱️Ⓜ️🅱️Ⓜ️🅱️Ⓜ️🅱️Ⓜ️🅱️Ⓜ️🅱️Ⓜ️🅱️Ⓜ️🅱️Ⓜ️
- `);
- // markdown 编辑器地址
- // const BILIBILI_MARKDOWN_URL = "http://127.0.0.1:5500/web/tampermonkey/Bilibili-Markdown/index.html";
- const BILIBILI_MARKDOWN_URL = "https://www.lckp.top/bilibili-markdown/index.html";
- // id / name 公共前缀
- const PREFIX = "bilibili-markdown-";
- // 等待时间(ms)
- const waitTime = {
- long: 2500,
- normal: 1000,
- short: 600,
- };
- // localStorage key
- const key = {
- isMarkdown: "isMarkdown",
- isFullscreen: "isFullscreen"
- }
- // element id
- const eid = {
- button: {
- switchToHtmlEditor: `${PREFIX}switch-to-html-editor-button`
- },
- iframe: {
- main: `${PREFIX}main-iframe`
- }
- };
- // element
- const elements = {
- // 附加
- switchToMarkdownEditorButton: undefined,
- mainIframe: undefined,
- // 原有
- editorBox: undefined,
- loading: undefined,
- save: undefined,
- mbpreview: undefined
- };
- // class name
- const cname = {
- fullscreen: `${PREFIX}fullscreen`,
- toast: `${PREFIX}toast`,
- };
- // z-index
- const zIndex = {
- first: 999999,
- second: 999998
- };
- // display
- const display = {
- none: "none",
- block: "block"
- }
- var needReload;
- var bilibili;
- var bilibiliMarkdown;
- const CSS = `
- /*切换 markdown 编辑器按钮*/
- #${eid.button.switchToHtmlEditor} {
- font-size: 22px;
- border-width: 0px 1px 0px 0px;
- border-style: solid;
- border-color: white;
- margin-left: -9px;
- padding-right: 5px;
- }
- /*markdown 编辑器 iframe*/
- #${eid.iframe.main} {
- width: 100%;
- height: 480px;
- z-index: ${zIndex.second};
- border: none;
- display: none;
- }
- /*全屏*/
- .${cname.fullscreen} {
- position: fixed !important;
- top: 0 !important;
- left: 0 !important;
- bottom: 0 !important;
- right: 0 !important;
- width: 100% !important;
- height: 100% !important;
- border: none !important;
- margin: 0 !important;
- padding: 0 !important;
- overflow: hidden !important;
- z-index: ${zIndex.second} !important;
- }
- /*消息*/
- .${cname.toast} {
- max-width: 60%;
- min-width: 160px;
- padding: 0 14px;
- height: 50px;
- color: rgb(255, 255, 255);
- line-height: 50px;
- text-align: center;
- border-radius: 4px;
- position: fixed;
- top: 6%;
- left: 50%;
- transform: translate(-50%, -50%);
- z-index: ${zIndex.first};
- background: rgba(119, 199, 104, 0.9);
- font-size: 14px;
- box-shadow: 0px 0px 10px rgba(119, 199, 104, 0.9);
- }
- /*手机端预览*/
- .preview-mask,
- .preview-mask .preview-content {
- padding-top: 35px !important;
- z-index: ${zIndex.first} !important;
- }
- `;
- const HTML = `
- <iframe id="${eid.iframe.main}" src="${BILIBILI_MARKDOWN_URL}"></iframe>
- `;
- function appendCSS() {
- let css = document.createElement("style");
- css.innerHTML = CSS.trim();
- document.head.appendChild(css);
- }
- function appendHTML() {
- let div = document.createElement("div");
- div.innerHTML = HTML.trim();
- document.getElementsByClassName("editor-wrap")[0].appendChild(div);
- }
- function appendSwitchToMarkdownEditorButton() {
- let button = document.createElement('li');
- button.id = eid.button.switchToHtmlEditor;
- button.className = 'toolbar-item left';
- button.innerHTML = 'Ⓜ️';
- document.getElementsByClassName('editor-toolbar clearfix')[0].prepend(button);
- }
- function getAllElement() {
- elements.switchToMarkdownEditorButton = document.getElementById(eid.button.switchToHtmlEditor);
- elements.mainIframe = document.getElementById(eid.iframe.main);
- elements.editorBox = document.getElementsByClassName("editor-box")[0];
- elements.save = document.getElementsByClassName("ui-btn white")[0];
- elements.mbpreview = document.getElementsByClassName("ui-btn white")[1];
- elements.loading = document.getElementById("loading");
- elements.loading.innerHTML = elements.loading.innerHTML.replace("玩儿命加载中", "处理中,请稍后");
- elements.loading.style.zIndex = zIndex.first;
- }
- function addListener() {
- elements.switchToMarkdownEditorButton.onclick = async function () {
- if (!bilibili.aid) {
- bilibili.aid = await bilibili.getAidFromLocalStorage();
- }
- if (bilibili.aid) {
- bilibili.switchToMarkdownEditor();
- } else {
- Toast("矮油,起码写个标题嘛~");
- }
- }
- elements.save.onclick = function () {
- if (localStorage.getItem(key.isMarkdown)) {
- bilibili.loading();
- setTimeout(() => {
- bilibiliMarkdown.save();
- }, waitTime.normal);
- }
- }
- elements.mbpreview.onclick = function () {
- if (localStorage.getItem(key.isMarkdown) && needReload) {
- setTimeout(() => {
- bilibiliMarkdown.save();
- bilibili.mbpreview();
- }, waitTime.normal);
- }
- }
- }
- // 显示消息
- function Toast(msg, duration) {
- duration = isNaN(duration) ? 2000 : duration;
- let div = document.createElement("div");
- div.innerHTML = msg;
- div.className = cname.toast;
- document.body.appendChild(div);
- setTimeout(function () {
- div.style.opacity = "0";
- setTimeout(function () { document.body.removeChild(div) }, 500);
- }, duration);
- }
- // webp 转 jpg
- function webpToJpg(webp) {
- return new Promise(function (resolve, reject) {
- let image = new Image();
- image.src = URL.createObjectURL(webp);
- image.onload = function () {
- let canvas = document.createElement("canvas");
- canvas.width = image.width;
- canvas.height = image.height;
- canvas.getContext("2d").drawImage(image, 0, 0);
- let blob = dataURLtoBlob(canvas.toDataURL("image/jpeg"));
- file = new File([blob], blob.name, {
- type: blob.type,
- });
- resolve(file);
- }
- });
- }
- function dataURLtoBlob(dataurl) {
- var arr = dataurl.split(','),
- mime = arr[0].match(/:(.*?);/)[1],
- bstr = atob(arr[1]),
- n = bstr.length,
- u8arr = new Uint8Array(n);
- while (n--) {
- u8arr[n] = bstr.charCodeAt(n);
- }
- return new Blob([u8arr], { type: mime });
- }
- function sleep(ms) {
- return new Promise(resolve => setTimeout(resolve, ms));
- }
- class Bilibili {
- constructor() {
- this.api = {
- upcover: "https://api.bilibili.com/x/article/creative/article/upcover",
- addupdate: "https://api.bilibili.com/x/article/creative/draft/addupdate",
- }
- this.page = {
- edit: "https://member.bilibili.com/platform/upload/text/edit",
- pcpreview: "https://www.bilibili.com/read/pcpreview",
- home: "https://member.bilibili.com/article-text/home"
- }
- this.csrf = this.getCsrf();
- this.aid = this.getAidFromLocation();
- this.addListener();
- this.uploading = 0;
- this.uploadList = new Map();
- }
- getCsrf() {
- let cookie = document.cookie;
- let csrf = cookie.substring(cookie.indexOf("bili_jct"));
- csrf = csrf.substring(9, csrf.indexOf(";"));
- return csrf;
- }
- getAidFromLocation() {
- let aid = undefined;
- let aids = window.location.href.match(/aid=[0-9]+/g);
- if (aids && aids.length > 0) {
- aid = aids[0].replace("aid=", "");
- }
- if (aid && aid.toString().length > 5) {
- return aid;
- }
- return undefined;
- }
- async getAidFromLocalStorage() {
- let aid = undefined;
- // 等待 TIMEOUT_TIME 后读取 localStorage (更新需要时间)
- this.loading();
- await new Promise((resolve, reject) => {
- setTimeout(() => {
- aid = JSON.parse(localStorage.bili_localDraft).id;
- resolve();
- }, waitTime.long);
- })
- this.hideLoading();
- if (aid && aid.toString().length > 5) {
- // 新建专栏跳转编辑页面
- if (window.location.href.endsWith("?")) {
- top.location.href = this.page.edit + "?aid=" + aid;
- }
- return aid;
- }
- return undefined;
- }
- addListener() {
- window.addEventListener("message", function (event) {
- bilibili[event.data.method](event.data.param);
- }, false);
- }
- switchToMarkdownEditor() {
- localStorage.setItem(key.isMarkdown, true);
- elements.mainIframe.style.display = display.block;
- elements.editorBox.style.display = display.none;
- if (localStorage.getItem(key.isFullscreen)) {
- localStorage.removeItem(key.isFullscreen);
- this.switchToFullscreen();
- }
- }
- switchToHtmlEditor() {
- localStorage.removeItem(key.isMarkdown);
- elements.mainIframe.style.display = display.none;
- elements.editorBox.style.display = display.block;
- document.body.style.overflowY = "";
- if (needReload) {
- needReload = false;
- location.reload();
- }
- }
- switchToFullscreen(param) {
- if (param && param.isFullscreen != undefined) {
- if (param.isFullscreen === true) {
- fullscreen();
- if (top != self) {
- top.location.href = bilibili.page.home + "?aid=" + bilibili.aid;
- }
- } else {
- exitFullscreen();
- }
- } else {
- if (localStorage.getItem(key.isFullscreen)) {
- exitFullscreen();
- } else {
- fullscreen();
- }
- }
- function fullscreen() {
- localStorage.setItem(key.isFullscreen, true);
- elements.mainIframe.className = cname.fullscreen;
- document.body.style.overflowY = "hidden";
- }
- function exitFullscreen() {
- localStorage.removeItem(key.isFullscreen);
- elements.mainIframe.className = "";
- document.body.style.overflowY = "";
- }
- }
- loading() {
- elements.loading.style.display = display.block;
- setTimeout(this.hideLoading, waitTime.long);
- }
- hideLoading() {
- elements.loading.style.display = display.none;
- }
- pcpreview() {
- window.open(this.page.pcpreview + "?aid=" + this.aid);
- }
- mbpreview() {
- if (needReload) {
- localStorage.setItem(key.needMbpreview, true);
- location.reload();
- } else {
- localStorage.removeItem(key.needMbpreview);
- document.getElementsByClassName("ui-btn white")[1].click();
- }
- }
- async appendImage(param) {
- bilibiliMarkdown.appendImage(await this.uploadImage(param.image));
- }
- toBLink(param) {
- this.loading();
- let link = param.link;
- let xhr = new XMLHttpRequest();
- xhr.open("get", link, true);
- xhr.responseType = "blob";
- xhr.onload = async function () {
- let image = new File([xhr.response], link.substring(link.lastIndexOf('/') + 1));
- let bLink = await bilibili.uploadImage(image);
- bilibiliMarkdown.toBLink(link, bLink);
- bilibili.hideLoading();
- }
- xhr.send();
- }
- async uploadImage(image) {
- let name = image.name;
- let bLink = this.uploadList.get(name);
- if (bLink) {
- if(bLink == "uploading"){
- return undefined;
- } else {
- return bLink;
- }
- } else {
- this.uploadList.set(name, "uploading");
- }
- // webp 转 jpg
- if (name.endsWith(".webp")) {
- image = await webpToJpg(image);
- }
- bLink = "图片上传B站失败,请重试";
- let formData = new FormData();
- formData.append("binary", image);
- formData.append("csrf", this.csrf);
- // 限制上传频率
- let that = this;
- while (that.uploading > 0) {
- await sleep(waitTime.normal);
- }
- that.uploading++;
- $.ajax({
- type: "POST",
- contentType: false,
- processData: false,
- async: false,
- data: formData,
- url: bilibili.api.upcover,
- xhrFields: {
- withCredentials: true
- },
- success: function (res) {
- if (res && res.data) {
- bLink = res.data.url;
- that.uploadList.set(name, bLink);
- } else {
- that.uploadList.delete(name);
- Toast("上传失败:" + JSON.stringify(res));
- }
- }
- })
- // 释放限制频率锁
- setTimeout(() => {
- that.uploading--;
- }, waitTime.normal);
- return bLink;
- }
- async tableToImage(html, tables) {
- if (tables && tables.size > 0) {
- for (let [oldHtml, image] of tables) {
- let bLink = await this.uploadImage(image);
- let newHtml = `<figure contenteditable="false" class="img-box"><img referrerpolicy="no-referrer" src="${bLink}"><figcaption class="caption" contenteditable="false"></figcaption></figure>`;
- html = html.replaceAll(oldHtml, newHtml);
- }
- }
- return html;
- }
- async save(param) {
- let html = param.html ? param.html : "";
- // 保存到本地
- localStorage.setItem(PREFIX + this.aid, param.markdown);
- // 表格转图片
- html = await this.tableToImage(html, param.tables);
- // 提取内容
- let words = html.replace(/<(h[1-6]|code)[^>]*>[^<]*<\/\1>/g, "")
- .replace(/<[^>]*>/g, "")
- .replace(/[\s| |\n\|\r]*/g, "");
- // 提取总结
- let summary = words.slice(0, 100);
- // B站接口参数
- let biliLocalDraft = JSON.parse(localStorage.bili_localDraft);
- $.ajax({
- type: "POST",
- data: {
- title: biliLocalDraft.title,
- content: html,
- summary: summary,
- words: words.length,
- category: biliLocalDraft.category,
- list_id: biliLocalDraft.list_id,
- tid: biliLocalDraft.template.id,
- reprint: 0,
- media_id: biliLocalDraft.media_id,
- spoiler: biliLocalDraft.is_spoiler ? "1" : "0",
- original: biliLocalDraft.isOriginal,
- aid: biliLocalDraft.id,
- csrf: this.csrf
- },
- url: bilibili.api.addupdate,
- xhrFields: {
- withCredentials: true
- },
- success: function (res) {
- bilibili.hideLoading();
- if (res && res.code == 0) {
- if (localStorage.getItem(key.needMbpreview)) {
- location.reload();
- } else {
- needReload = true;
- Toast(" 草稿已保存 ");
- }
- } else {
- Toast("保存失败: " + JSON.stringify(res));
- }
- },
- error: function (err) {
- Toast("保存失败: " + JSON.stringify(err.message));
- }
- });
- }
- }
- class BilibiliMarkdown {
- constructor() {
- setTimeout(() => {
- this.hello();
- if (bilibili.aid) {
- if (localStorage.getItem(key.isMarkdown)) {
- bilibili.switchToMarkdownEditor();
- }
- let markdown = localStorage.getItem(PREFIX + bilibili.aid);
- if (markdown) {
- this.setMarkdown(markdown);
- }
- if (localStorage.getItem(key.needMbpreview)) {
- bilibili.mbpreview();
- }
- }
- }, waitTime.short);
- }
- message(method, param) {
- elements.mainIframe.contentWindow.postMessage({ method: method, param: param }, BILIBILI_MARKDOWN_URL);
- }
- hello() {
- this.message(this.hello.name);
- }
- save() {
- this.message(this.save.name);
- }
- toBLink(link, bLink) {
- if(bLink){
- this.message(this.toBLink.name, { link: link, bLink: bLink });
- }
- }
- appendImage(bLink) {
- this.message(this.appendImage.name, { bLink: bLink });
- }
- setMarkdown(markdown) {
- this.message(this.setMarkdown.name, { markdown: markdown });
- }
- }
- window.onload = function () {
- setTimeout(() => {
- let saveButton = document.getElementsByClassName("ui-btn white")[0];
- if (!saveButton || saveButton.innerHTML != "存草稿") {
- console.log("文章已提交");
- return;
- }
- appendCSS();
- appendHTML();
- appendSwitchToMarkdownEditorButton();
- getAllElement();
- addListener();
- bilibili = new Bilibili();
- bilibiliMarkdown = new BilibiliMarkdown();
- }, waitTime.short);
- }