替换 Bilibili 部分链接为带 href 的常规 a 元素

替换 Bilibili 部分链接为带 href 的常规 a 元素,使其右键菜单出现“在新标签页打开链接”“在新窗口打开链接”等选项

// ==UserScript==
// @name         替换 Bilibili 部分链接为带 href 的常规 a 元素
// @namespace    myitian.bili.replaceToHrefA
// @version      0.4
// @description  替换 Bilibili 部分链接为带 href 的常规 a 元素,使其右键菜单出现“在新标签页打开链接”“在新窗口打开链接”等选项
// @author       Myitian
// @license      MIT
// @match        https://*.bilibili.com/*
// @icon         https://www.bilibili.com/favicon.ico
// @grant        none
// ==/UserScript==

var running = false;
var eventTarget = new EventTarget();
var event = new CustomEvent('proc_done');
var waitingCount = 0;

replace();
window.addEventListener('mousedown', replace);
window.addEventListener('keydown', replace);

function getPromiseFromEvent(target, event) {
  return new Promise((resolve) => {
    const listener = () => {
      target.removeEventListener(event, listener);
      resolve();
    }
    target.addEventListener(event, listener);
  })
}

async function replace() {
  if (waitingCount > 2) {
      console.log('[RBLTHA] Too many waiting replacement. Canceled new replacement.');
      return;
  }
  if (running) {
      waitingCount++;
      await getPromiseFromEvent(eventTarget, 'proc_done');
      waitingCount--;
  }
  running = true;
  var es;
  var c = 0;
  es = document.querySelectorAll('a.jump-link[data-url]:not([data-rbltha-replaced])');
  for (let e of es) {
    replaceUrl(e);
    c++;
  }
  es = document.querySelectorAll('a.jump-link[data-user-id]:not([data-rbltha-replaced])');
  for (let e of es) {
    replaceUserId(e);
    c++;
  }
  es = document.querySelectorAll('.opus-text-rich-hl.at[data-rid]:not([data-rbltha-replaced])');
  for (let e of es) {
    replaceUserId(changeElementTagName(e, 'a'), 'data-rid');
    c++;
  }
  es = document.querySelectorAll('.root-reply-avatar[data-user-id]:not([data-rbltha-replaced]),.user-name[data-user-id]:not([data-rbltha-replaced]),.sub-user-name[data-user-id]:not([data-rbltha-replaced]),.sub-reply-avatar[data-user-id]:not([data-rbltha-replaced])');
  for (let e of es) {
    replaceUserId(changeElementTagName(e, 'a'));
    c++;
  }
  running = false;
  console.log('[RBLTHA] Replacement completed. ' + c + ' element(s) effected.');
  eventTarget.dispatchEvent(event);
}

function replaceUrl(e) {
  e.href = e.dataset.url;
  e.setAttribute("data-rbltha-replaced", "");
  e.target = '_blank';
  e.onclick = null;
  e.addEventListener('click', stopImmediatePropagation, true);
}

function replaceUserId(e, attr = 'data-user-id') {
  e.href = 'https://space.bilibili.com/' + e.getAttribute(attr);
  e.setAttribute("data-rbltha-replaced", "");
  e.target = '_blank';
  e.onclick = null;
  e.addEventListener('click', stopImmediatePropagation, true);
}

function changeElementTagName(element, newname) {
  var ne = document.createElement(newname);
  for (let t of element.attributes) {
    ne.setAttribute(t.name, t.value)
  }
  for (let t of element.childNodes) {
    ne.appendChild(t);
  }

  var p = element.parentElement;
  var nxt = element.nextSibling;
  p.removeChild(element);
  p.insertBefore(ne, nxt);
  return ne;
}

function stopImmediatePropagation(event) {
  event.stopImmediatePropagation();
}