// ==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);
})();