bilibili 成分查询

bilibili 共同关注一键查询(自主查询版)

当前为 2021-07-11 提交的版本,查看 最新版本

// ==UserScript==
// @name         bilibili 成分查询
// @namespace    https://github.com/sparanoid/userscript
// @supportURL   https://github.com/sparanoid/userscript/issues
// @version      0.1.1
// @description  bilibili 共同关注一键查询(自主查询版)
// @author       Sparanoid
// @match        https://*.bilibili.com/*
// @icon         https://experiments.sparanoid.net/favicons/v2/www.bilibili.com.ico
// @grant        none
// @run-at       document-start
// ==/UserScript==

window.addEventListener('load', () => {
  const DEBUG = true;
  const NAMESPACE = 'bilibili-social-check';
  const apiBase = 'https://api.bilibili.com';
  const feedbackUrl = 'https://t.bilibili.com/545085157213602473';

  console.log(`${NAMESPACE} loaded`);

  async function fetchResult(url = '', data = {}) {
    const response = await fetch(url, {
      credentials: 'include',
    });
    return response.json();
  }

  function debug(description = '', msg = '', force = false) {
    if (DEBUG || force) {
      console.log(`${NAMESPACE}: ${description}`, msg)
    }
  }

  function formatDate(timestamp) {
    let date = timestamp.toString().length === 10 ? new Date(+timestamp * 1000) : new Date(+timestamp);
    return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
  }

  function rateColor(percent) {
    return `hsl(${100 - percent}, 70%, 45%)`;
  }

  function percentDisplay(num) {
    return num.toFixed(2).replace('.00', '');
  }

  function attachElwww(item) {
    let injectWrap = item.querySelector('.con .info');

    // .text - comment content
    // .text-con - reply content
    let content = item.querySelector('.con .text') || item.querySelector('.reply-con .text-con');
    let id = item.dataset.id;

    // Simple way to attach element on replies initially loaded with comment
    // which wouldn't trigger mutation inside observeComments
    let replies = item.querySelectorAll('.con .reply-box .reply-item');
    if (replies.length > 0) {
      [...replies].map(reply => {
        attachElwww(reply);
      });
    }

    if (injectWrap.querySelector('.asoulcnki')) {
      debug('already loaded for this comment');
    } else {
      // Insert asoulcnki check button
      let asoulcnkiEl = document.createElement('span');

      asoulcnkiEl.classList.add('asoulcnki', 'btn-hover', 'btn-highlight');
      asoulcnkiEl.innerHTML = '狠狠地查';
      asoulcnkiEl.addEventListener('click', e => {
        let contentPrepared = '';

        // Copy meme icons alt text
        for (let node of content.childNodes.values()) {
          if (node.nodeType === 3) {
            contentPrepared += node.textContent;
          } else if (node.nodeName === 'IMG' && node.nodeType === 1) {
            contentPrepared += node.alt;
          } else if (node.nodeName === 'BR' && node.nodeType === 1) {
            contentPrepared += '\n';
          } else if (node.nodeName === 'A' && node.nodeType === 1 && node.classList.contains('comment-jump-url')) {
            contentPrepared += node.href.replace(/https?:\/\/www\.bilibili\.com\/video\//, '');
          } else {
            contentPrepared += node.innerText;
          }
        }

        // Need regex to stripe `回复 @username  :`
        let contentProcessed = contentPrepared.replace(/回复 @.*:/, '');
        debug('content processed', contentProcessed);

        // ask to confirm if words count not enough
        if (contentProcessed.length < 10 && !confirm('内容过短(少于 10 字),可能无法得到正确结果,是否继续查询?')) return;

        fetchResult(`${apiBase}/v1/api/check`, {
          text: contentProcessed
        })
        .then(data => {
          debug('data returned', data);

          let resultContent = '';

          if (data.code !== 0) {
            resultContent = `返回结果错误,可能是文本内容过短,或请访问 <a href="${apiBase}/" target="_blank">枝网</a> 查看服务是否正常`;
          } else {
            let result = data.data;
            let startTime = result.start_time;
            let endTime = result.end_time;
            let rate = result.rate * 100;
            let relatedItems = result.related;

            resultContent = `<a href="${apiBase}" target="_blank">枝网</a>文本复制检测报告(Chrome 脚本版/<a href="${feedbackUrl}" target="_blank">反馈</a>)
查重时间:${formatDate(Date.now())}
数据范围:${formatDate(startTime)} - ${formatDate(endTime)}
总文字复制比:<b style="color: ${rateColor(rate)}">${percentDisplay(rate)}%</b>\n`;

            if (relatedItems.length === 0) {
              resultContent += `一眼原创,再偷必究(查重结果仅作娱乐参考)`;
            } else {
              let selfOriginal = +relatedItems[0][1].rpid === +id ? `(<span style="color: blue;">本文原创,已收录</span>)` : '';

              resultContent += `重复次数:${relatedItems.length}${selfOriginal}\n`;

              relatedItems.map((item, idx) => {
                let rate = item[0] * 100;

                resultContent += `#${idx + 1} <span style="color: ${rateColor(rate)}">${percentDisplay(rate)}%</span> <a href="${item[2].trim()}" title="${item[1].content}" target="_blank">${item[2].trim()}</a>
发布于:${formatDate(item[1].ctime)}
作者:@${item[1].m_name} (UID <a href="https://space.bilibili.com/${item[1].mid}" target="_blank">${item[1].mid}</a>)\n\n`;
              });

              resultContent += `查重结果仅作娱乐参考,请注意辨别是否为原创`;
            }
          }

          // Insert result
          let resultWrap = document.createElement('div');

          resultWrap.style.padding = '.5rem';
          resultWrap.style.margin = '.5rem 0';
          resultWrap.style.background = 'hsla(0, 0%, 50%, .1)';
          resultWrap.style.borderRadius = '4px';
          resultWrap.style.whiteSpace = 'pre';
          resultWrap.classList.add('asoulcnki-result');
          resultWrap.innerHTML = resultContent;

          // Remove previous result if exists
          if (injectWrap.querySelector('.asoulcnki-result')) {
            injectWrap.querySelector('.asoulcnki-result').remove();
          }
          injectWrap.append(resultWrap);
        });
      }, false);

      injectWrap.append(asoulcnkiEl);

      // Insert comment ID link
      let idLink = document.createElement('a');

      idLink.innerHTML = '#';
      idLink.setAttribute('title', '当前评论 ID: ' + id);
      idLink.setAttribute('href', '#reply' + id);
      idLink.style.marginRight = '.25em';

      injectWrap.prepend(idLink);
    }
  }

  function attachEl(wrapper, output) {
    let content = document.createElement('div');
    content.innerText = output;

    wrapper.append(content);
  }

  function processFollowings(wrapper, id, output, iteration) {
    let outputlist = '';

    fetchResult(`${apiBase}/x/relation/same/followings?vmid=${id}&pn=${iteration}`).then(data => {
      debug('data returned', data);

      if (data.code !== 0) {
        outputlist = data.message;
      } else {
        let result = data.data;
        let total = result.total;
        let items = result.list;

        items.map(item => {
          outputlist += item.uname + '\n';
        });

        if (items.length > 0) {
          debug('try next page', iteration + 1);

          setTimeout(() => {
            processFollowings(wrapper, id, output, iteration + 1);
          }, 200);
        } else {
          debug('loop finished');
        }

        attachEl(wrapper, outputlist);
      }
    });
  }

  function observeCard(wrapper) {
    let iteration = 1;
    let resultContent = '';
    let id = wrapper.querySelector('.info .user .name')?.href.split('/').slice(-1)[0];

    // ensure user id exists
    debug('current uid', id);

    if (id) {
      // Create output wrapper and limit height
      let injectWrap = wrapper;
      let contentWrap = document.createElement('div');

      contentWrap.style.overflowY = 'auto';
      contentWrap.style.maxHeight = '300px';
      contentWrap.style.paddingTop = '1rem';
      contentWrap.style.marginTop = '1rem';
      contentWrap.style.borderTop = '1px solid #eee';

      contentWrap.classList.add(`${NAMESPACE}-wrap`);

      injectWrap.append(contentWrap);

      processFollowings(contentWrap, id, resultContent, iteration);
    }
  }

  // .user-card loads dynamcially. So observe it first
  const wrapperObserver = new MutationObserver((mutationsList, observer) => {

    for (const mutation of mutationsList) {

      if (mutation.type === 'childList') {

        [...mutation.addedNodes].map(item => {
          debug('mutation wrapper added', item);

          let classNames = [
            'user-card', // normal card, global, comments avatar, comment mentions, and etc.
            'userinfo-wrapper', // card in dongtai mentions
          ]

          if (classNames.some(className => item.classList?.contains(className))) {
            debug('mutation wrapper added (found target)', item);

            observeCard(item);
          }
        })
      }
    }
  });
  wrapperObserver.observe(document.body, { attributes: false, childList: true, subtree: true });

}, false);