Greasy Fork

Greasy Fork is available in English.

bilibili 成分查询

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

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         bilibili 成分查询
// @namespace    https://github.com/sparanoid/userscript
// @supportURL   https://github.com/sparanoid/userscript/issues
// @version      0.1.0
// @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 injectWrap = wrapper.querySelector('.info');

    let content = document.createElement('div');
    content.innerText = output;

    injectWrap.append(content);
  }

  async 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];

    debug('current uid', id);

    if (id) {
      processFollowings(wrapper, 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);