- // ==UserScript==
- // @name 18Comic 之路
- // @namespace http://tampermonkey.net/
- // @version 0.2
- // @license MIT
- // @description JM / 18Comic 车牌号划词查询工具
- // @author zyf722
- // @match *://weibo.com/*
- // @match *://*.weibo.com/*
- // @match *://*.weibo.cn/*
- // @match *://tieba.baidu.com/*
- // @match *://*.bilibili.com/
- // @match *://*.bilibili.com/*
- // @icon https://www.google.com/s2/favicons?sz=64&domain=18comic.vip
- // @grant GM_xmlhttpRequest
- // @grant GM_registerMenuCommand
- // @grant GM_setValue
- // @grant GM_getValue
- // ==/UserScript==
-
- (function() {
- 'use strict';
-
- // Site source selection
- var JM_SITE = GM_getValue("JM_SITE", "18comic.vip");
- var JM_CURRENT = GM_getValue("JM_CURRENT", 0);
- const updateSite = (site) => {
- JM_SITE = site;
- GM_setValue("JM_SITE", JM_SITE);
- }
- const updateCurrent = (current) => {
- JM_CURRENT = current;
- GM_setValue("JM_CURRENT", JM_CURRENT);
- }
- const sources = [
- "18comic.vip",
- "18comic.org",
- "jmcomic1.me",
- "18comic-palworld.vip",
- "18comic-c.art"
- ];
- const updateMenuCommandFactory = (index) => {
- return () => {
- updateSite(sources[index]);
- GM_registerMenuCommand("线路 " + (JM_CURRENT + 1) + ": " + sources[JM_CURRENT], updateMenuCommandFactory(JM_CURRENT), {id: JM_CURRENT});
- updateCurrent(index);
- GM_registerMenuCommand("✅ 线路 " + (index + 1) + ": " + sources[index], updateMenuCommandFactory(index), {id: index});
- };
- }
- for (var i = 0; i < sources.length; i++) {
- GM_registerMenuCommand((JM_CURRENT === i ? "✅ " : "") + "线路 " + (i+1) + ": " + sources[i], updateMenuCommandFactory(i), {id: i});
- };
-
- // Util functions
- const createElementWithAttr = (tag, attr) => {
- const element = document.createElement(tag);
-
- if (attr) {
- Object.entries(attr).forEach(([key, value]) => {
- element.setAttribute(key, value);
- });
- }
- return element;
- };
-
- const createSVGPath = (path) => {
- const p = document.createElementNS("http://www.w3.org/2000/svg", 'path');
- p.setAttribute('d', path);
- p.setAttribute('fill', 'currentColor');
- return p;
- };
-
- const createSVGElement = (path, viewBox, width, height, attr) => {
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
- svg.setAttribute('viewBox', viewBox);
- svg.setAttribute('width', width);
- svg.setAttribute('height', height);
- if (attr) {
- Object.entries(attr).forEach(([key, value]) => {
- svg.setAttribute(key, value);
- });
- }
-
- if (typeof path === 'string') {
- const p = createSVGPath(path);
- svg.appendChild(p);
- return [svg, p];
- } else if (Array.isArray(path)) {
- const paths = path.map(p => {
- const newPath = createSVGPath(p);
- svg.appendChild(newPath);
- return newPath;
- });
- return [svg, ...paths];
- } else {
- throw new Error('Invalid path type');
- }
- };
-
- const popupWindow = createElementWithAttr('div', {id: 'jm-popup', class: 'jm-select-none'});
- document.body.appendChild(popupWindow);
-
- const numberContainer = createElementWithAttr('div', {id: 'jm-number-container'});
- popupWindow.appendChild(numberContainer);
-
- const LOADING_ICON = "M512 170.666667a341.333333 341.333333 0 1 0 0 682.666666 341.333333 341.333333 0 0 0 0-682.666666zM85.333333 512C85.333333 276.352 276.352 85.333333 512 85.333333s426.666667 191.018667 426.666667 426.666667-191.018667 426.666667-426.666667 426.666667S85.333333 747.648 85.333333 512z m426.666667-256a42.666667 42.666667 0 0 1 42.666667 42.666667v195.669333l115.498666 115.498667a42.666667 42.666667 0 0 1-60.330666 60.330666l-128-128A42.666667 42.666667 0 0 1 469.333333 512V298.666667a42.666667 42.666667 0 0 1 42.666667-42.666667z";
- const FAIL_ICON = "M512 97.52381c228.912762 0 414.47619 185.563429 414.47619 414.47619s-185.563429 414.47619-414.47619 414.47619S97.52381 740.912762 97.52381 512 283.087238 97.52381 512 97.52381z m0 73.142857C323.486476 170.666667 170.666667 323.486476 170.666667 512s152.81981 341.333333 341.333333 341.333333 341.333333-152.81981 341.333333-341.333333S700.513524 170.666667 512 170.666667z m129.29219 160.304762l51.736381 51.736381L563.687619 512l129.316571 129.29219-51.73638 51.736381L512 563.687619l-129.29219 129.316571-51.736381-51.73638L460.312381 512l-129.316571-129.26781 51.73638-51.73638L512 460.263619l129.26781-129.29219z";
- const SUCCESS_ICON = 'M512 97.52381c228.912762 0 414.47619 185.563429 414.47619 414.47619s-185.563429 414.47619-414.47619 414.47619S97.52381 740.912762 97.52381 512 283.087238 97.52381 512 97.52381z m0 73.142857C323.486476 170.666667 170.666667 323.486476 170.666667 512s152.81981 341.333333 341.333333 341.333333 341.333333-152.81981 341.333333-341.333333S700.513524 170.666667 512 170.666667z m193.194667 145.188571l52.467809 50.956191-310.662095 319.683047-156.379429-162.230857 52.662858-50.761143 103.936 107.812572 257.974857-265.45981z';
- // Define the warning icon SVG path
- const WARNING_ICON = "M545.718857 130.608762c11.337143 6.265905 20.699429 15.555048 26.989714 26.819048l345.014858 617.667047a68.87619 68.87619 0 0 1-26.989715 93.915429c-10.313143 5.705143-21.942857 8.704-33.718857 8.704H166.985143A69.266286 69.266286 0 0 1 97.52381 808.643048c0-11.751619 2.998857-23.28381 8.752761-33.548191l344.990477-617.642667a69.656381 69.656381 0 0 1 94.451809-26.819047zM512 191.000381L166.985143 808.643048H856.990476L512 191.000381zM546.718476 670.47619v69.071239h-69.461333V670.47619h69.485714z m0-298.374095v252.318476h-69.461333V372.102095h69.485714z";
-
- // Create an SVG element for the number icon
- const [numberIcon, numberIconPath] = createSVGElement(LOADING_ICON, '0 0 1024 1024', '16px', '16px', {id: 'jm-number-icon'});
- numberContainer.appendChild(numberIcon);
-
- // Create a div element for the number text
- const numberText = createElementWithAttr('div', {id: 'jm-number', class: 'jm-select-none jm-overflow'});
- numberContainer.appendChild(numberText);
-
- // Create an anchor element for the title text
- const titleText = createElementWithAttr('a', {id: 'jm-title-text', class: 'jm-select-none jm-overflow jm-title'});
- titleText.setAttribute('target', '_blank');
- titleText.setAttribute('rel', 'noopener noreferrer');
- popupWindow.appendChild(titleText);
-
- // Create a div element for the title loading text
- const titleLoadingText = createElementWithAttr('div', {id: 'jm-title-loading', class: 'jm-select-none jm-title'});
- titleLoadingText.innerHTML = '加载中...';
- popupWindow.appendChild(titleLoadingText);
-
- // Function to toggle the loading status
- const toggleLoading = (status) => {
- if (status === "loading") {
- titleLoadingText.style.display = 'inline';
- titleText.style.display = 'none';
- numberIconPath.setAttribute('d', LOADING_ICON);
- numberText.style.color = numberIcon.style.color = "black";
- } else if (status === "fail") {
- titleLoadingText.style.display = 'none';
- titleText.style.display = 'inline';
- numberIconPath.setAttribute('d', FAIL_ICON);
- numberText.style.color = numberIcon.style.color = "red";
- } else if (status === "done") {
- titleLoadingText.style.display = 'none';
- titleText.style.display = 'inline';
- numberIconPath.setAttribute('d', SUCCESS_ICON);
- numberText.style.color = numberIcon.style.color = "green";
- } else if (status === "warning") {
- titleLoadingText.style.display = 'none';
- titleText.style.display = 'inline';
- numberIconPath.setAttribute('d', WARNING_ICON);
- numberText.style.color = numberIcon.style.color = "orange";
- }
- };
-
- // Create a button element for the copy button
- const copyBtn = createElementWithAttr('button', {id: 'jm-copy'});
- popupWindow.appendChild(copyBtn);
-
- // Define the copy icon SVG path
- const DONE_ICON = "M512 16C238.066 16 16 238.066 16 512s222.066 496 496 496 496-222.066 496-496S785.934 16 512 16z m0 96c221.064 0 400 178.902 400 400 0 221.064-178.902 400-400 400-221.064 0-400-178.902-400-400 0-221.064 178.902-400 400-400m280.408 260.534l-45.072-45.436c-9.334-9.41-24.53-9.472-33.94-0.136L430.692 607.394l-119.584-120.554c-9.334-9.41-24.53-9.472-33.94-0.138l-45.438 45.072c-9.41 9.334-9.472 24.53-0.136 33.942l181.562 183.032c9.334 9.41 24.53 9.472 33.94 0.136l345.178-342.408c9.408-9.336 9.468-24.532 0.134-33.942z";
- const COPY_ICON = 'M931.882 131.882l-103.764-103.764A96 96 0 0 0 760.236 0H416c-53.02 0-96 42.98-96 96v96H160c-53.02 0-96 42.98-96 96v640c0 53.02 42.98 96 96 96h448c53.02 0 96-42.98 96-96v-96h160c53.02 0 96-42.98 96-96V199.764a96 96 0 0 0-28.118-67.882zM596 928H172a12 12 0 0 1-12-12V300a12 12 0 0 1 12-12h148v448c0 53.02 42.98 96 96 96h192v84a12 12 0 0 1-12 12z m256-192H428a12 12 0 0 1-12-12V108a12 12 0 0 1 12-12h212v176c0 26.51 21.49 48 48 48h176v404a12 12 0 0 1-12 12z m12-512h-128V96h19.264c3.182 0 6.234 1.264 8.486 3.514l96.736 96.736a12 12 0 0 1 3.514 8.486V224z';
-
- // Create an SVG element for the copy button icon
- const [copyBtnIcon, copyBtnCopyPath, copyBtnDonePath] = createSVGElement([COPY_ICON, DONE_ICON], '0 0 1024 1024', '16px', '16px', {id: 'jm-copy-icon'});
- copyBtnDonePath.classList.toggle('jm-copy-icon-hide');
- copyBtnCopyPath.classList.add('jm-copy-icon');
- copyBtnDonePath.classList.add('jm-copy-icon');
- copyBtn.appendChild(copyBtnIcon);
-
- // Function to disable the copy button
- const disableBtn = (status) => {
- copyBtn.disabled = status;
- copyBtn.style.pointerEvents = status ? 'none' : 'auto';
- copyBtnIcon.setAttribute('color', status ? 'gray' : 'dodgerblue');
- };
- disableBtn(true);
-
- // Create a style element for the CSS styles
- const style = createElementWithAttr('style');
- style.innerHTML = `
- .jm-select-none {
- -webkit-touch-callout: none;
- -webkit-user-select: none;
- -khtml-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- user-select: none;
- }
-
- .jm-overflow {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
-
- #jm-popup {
- position: absolute;
- background-color: #fff;
- padding: 10px;
- margin-top: 10px;
- border: 1px solid #ddd;
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
- z-index: 999999999999;
- display: none;
- max-width: 25%;
- column-gap: 10px;
- align-items: center;
- }
-
- .jm-title {
- max-width: 100%;
- font-size: 14px;
- grid-column: 1;
- grid-row: 2;
- }
-
- #jm-title-text {
- display: none;
- }
-
- #jm-number-container {
- max-width: 100%;
- grid-column: 1;
- grid-row: 1;
- display: flex;
- align-items: center;
- }
-
- #jm-number {
- font-size: 18px;
- font-weight: bold;
- }
-
- #jm-number-icon {
- margin-right: 5px;
- }
-
- #jm-copy {
- border: none;
- background-color: #fff;
- width: 32px;
- height: 32px;
- font-size: 16px;
- cursor: pointer;
- grid-column: 2;
- grid-row: 1 / 3;
- transition: background-color 0.3s;
- }
-
- #jm-copy:hover:not(:disabled) {
- background-color: #f6f6f6;
- }
-
- #jm-copy:active:not(:disabled) {
- background-color: #e6e6e6;
- }
-
- .jm-copy-icon {
- transition: opacity 0.25s;
- }
-
- .jm-copy-icon-hide {
- opacity: 0;
- }
- `;
- document.head.appendChild(style);
-
- // Function to fetch the title of a URL
- const fetchTitle = (url, callback) => {
- console.log("fetching " + url);
- GM_xmlhttpRequest({
- method: "GET",
- url: url,
- onload: function(response) {
- const title = response.responseText.match(/<title[^>]*>([^<]+)<\/title>/)[1];
- callback(title.replace(" Comics - 禁漫天堂", ""));
- }
- });
- };
-
- // Function to copy the title text to the clipboard
- const copyToClipboard = (event) => {
- navigator.clipboard.writeText(titleText.innerText);
- copyBtn.style.pointerEvents = 'none';
- copyBtnCopyPath.classList.toggle('jm-copy-icon-hide');
- setTimeout(() => {
- copyBtnDonePath.classList.toggle('jm-copy-icon-hide');
- }, 250);
- setTimeout(() => {
- copyBtnDonePath.classList.toggle('jm-copy-icon-hide');
- setTimeout(() => {
- copyBtnCopyPath.classList.toggle('jm-copy-icon-hide');
- copyBtn.style.pointerEvents = 'auto';
- }, 250);
- }, 1500);
- };
- copyBtn.addEventListener('click', copyToClipboard);
-
- // Function to show the popup window
- const showPopup = (event) => {
- const selectedText = window.getSelection();
-
- // Check if mouse is inside the popup window
- if (!event.target.closest('#jm-popup')) {
- popupWindow.style.display = 'none';
- disableBtn(true);
- }
-
- if(selectedText.toString().trim() !== '') {
- const number = selectedText.toString().replace(/\D/g, '');
-
- if (popupWindow.style.display !== 'grid' && number !== "") {
- const range = selectedText.getRangeAt(0);
- const rect = range.getBoundingClientRect();
- const url = "https://" + JM_SITE + "/album/" + number;
-
- const activeEl = document.activeElement;
- if(['TEXTAREA','INPUT'].includes(activeEl.tagName)) rect = activeEl.getBoundingClientRect();
-
- const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
-
- const top = Math.floor(scrollTop + rect.top + rect.height);
- const left = Math.floor(rect.left);
-
- if(top === 0 && left === 0){
- return;
- }
-
- popupWindow.style.left = left + 'px';
- popupWindow.style.top = top + 'px';
-
- numberText.innerHTML = number;
- numberText.style.color = "";
-
- toggleLoading("loading");
-
- popupWindow.style.display = 'grid';
-
- // Special optimization for nbnhhsh
- const nbnhhsh = document.getElementsByClassName("nbnhhsh-box nbnhhsh-box-pop")[0];
- if (nbnhhsh) nbnhhsh.style.top = (parseInt(nbnhhsh.style.top) + 80) + "px";
-
- fetchTitle(url, (title) => {
- titleText.href = url;
- if (title === "Just a moment...") {
- titleText.innerHTML = titleText.title = "自动获取失败,请手动点击链接";
- toggleLoading("warning");
- } else if (title === "禁漫天堂") {
- titleText.innerHTML = titleText.title = "无效车牌"
- toggleLoading("fail");
- } else {
- titleText.innerHTML = titleText.title = title
- toggleLoading("done");
- disableBtn(false);
- }
- });
- }
- }
- }
-
- // Function to show the popup window after a delay
- const _showPopup = (event) => {
- // Delay window.getSelection() to get the correct selected text
- setTimeout(() => {
- showPopup(event);
- }, 1);
- }
-
- // Add event listeners to show the popup window
- document.addEventListener('mouseup', _showPopup);
- document.addEventListener('keyup', _showPopup);
- })();