18Comic 之路

JM / 18Comic 车牌号划词查询工具

目前为 2024-02-22 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name 18Comic 之路
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.2
  5. // @license MIT
  6. // @description JM / 18Comic 车牌号划词查询工具
  7. // @author zyf722
  8. // @match *://weibo.com/*
  9. // @match *://*.weibo.com/*
  10. // @match *://*.weibo.cn/*
  11. // @match *://tieba.baidu.com/*
  12. // @match *://*.bilibili.com/
  13. // @match *://*.bilibili.com/*
  14. // @icon https://www.google.com/s2/favicons?sz=64&domain=18comic.vip
  15. // @grant GM_xmlhttpRequest
  16. // @grant GM_registerMenuCommand
  17. // @grant GM_setValue
  18. // @grant GM_getValue
  19. // ==/UserScript==
  20.  
  21. (function() {
  22. 'use strict';
  23.  
  24. // Site source selection
  25. var JM_SITE = GM_getValue("JM_SITE", "18comic.vip");
  26. var JM_CURRENT = GM_getValue("JM_CURRENT", 0);
  27. const updateSite = (site) => {
  28. JM_SITE = site;
  29. GM_setValue("JM_SITE", JM_SITE);
  30. }
  31. const updateCurrent = (current) => {
  32. JM_CURRENT = current;
  33. GM_setValue("JM_CURRENT", JM_CURRENT);
  34. }
  35. const sources = [
  36. "18comic.vip",
  37. "18comic.org",
  38. "jmcomic1.me",
  39. "18comic-palworld.vip",
  40. "18comic-c.art"
  41. ];
  42. const updateMenuCommandFactory = (index) => {
  43. return () => {
  44. updateSite(sources[index]);
  45. GM_registerMenuCommand("线路 " + (JM_CURRENT + 1) + ": " + sources[JM_CURRENT], updateMenuCommandFactory(JM_CURRENT), {id: JM_CURRENT});
  46. updateCurrent(index);
  47. GM_registerMenuCommand("✅ 线路 " + (index + 1) + ": " + sources[index], updateMenuCommandFactory(index), {id: index});
  48. };
  49. }
  50. for (var i = 0; i < sources.length; i++) {
  51. GM_registerMenuCommand((JM_CURRENT === i ? "✅ " : "") + "线路 " + (i+1) + ": " + sources[i], updateMenuCommandFactory(i), {id: i});
  52. };
  53.  
  54. // Util functions
  55. const createElementWithAttr = (tag, attr) => {
  56. const element = document.createElement(tag);
  57.  
  58. if (attr) {
  59. Object.entries(attr).forEach(([key, value]) => {
  60. element.setAttribute(key, value);
  61. });
  62. }
  63. return element;
  64. };
  65.  
  66. const createSVGPath = (path) => {
  67. const p = document.createElementNS("http://www.w3.org/2000/svg", 'path');
  68. p.setAttribute('d', path);
  69. p.setAttribute('fill', 'currentColor');
  70. return p;
  71. };
  72.  
  73. const createSVGElement = (path, viewBox, width, height, attr) => {
  74. const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
  75. svg.setAttribute('viewBox', viewBox);
  76. svg.setAttribute('width', width);
  77. svg.setAttribute('height', height);
  78. if (attr) {
  79. Object.entries(attr).forEach(([key, value]) => {
  80. svg.setAttribute(key, value);
  81. });
  82. }
  83.  
  84. if (typeof path === 'string') {
  85. const p = createSVGPath(path);
  86. svg.appendChild(p);
  87. return [svg, p];
  88. } else if (Array.isArray(path)) {
  89. const paths = path.map(p => {
  90. const newPath = createSVGPath(p);
  91. svg.appendChild(newPath);
  92. return newPath;
  93. });
  94. return [svg, ...paths];
  95. } else {
  96. throw new Error('Invalid path type');
  97. }
  98. };
  99.  
  100. const popupWindow = createElementWithAttr('div', {id: 'jm-popup', class: 'jm-select-none'});
  101. document.body.appendChild(popupWindow);
  102.  
  103. const numberContainer = createElementWithAttr('div', {id: 'jm-number-container'});
  104. popupWindow.appendChild(numberContainer);
  105.  
  106. 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";
  107. 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";
  108. 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';
  109. // Define the warning icon SVG path
  110. 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";
  111.  
  112. // Create an SVG element for the number icon
  113. const [numberIcon, numberIconPath] = createSVGElement(LOADING_ICON, '0 0 1024 1024', '16px', '16px', {id: 'jm-number-icon'});
  114. numberContainer.appendChild(numberIcon);
  115.  
  116. // Create a div element for the number text
  117. const numberText = createElementWithAttr('div', {id: 'jm-number', class: 'jm-select-none jm-overflow'});
  118. numberContainer.appendChild(numberText);
  119.  
  120. // Create an anchor element for the title text
  121. const titleText = createElementWithAttr('a', {id: 'jm-title-text', class: 'jm-select-none jm-overflow jm-title'});
  122. titleText.setAttribute('target', '_blank');
  123. titleText.setAttribute('rel', 'noopener noreferrer');
  124. popupWindow.appendChild(titleText);
  125.  
  126. // Create a div element for the title loading text
  127. const titleLoadingText = createElementWithAttr('div', {id: 'jm-title-loading', class: 'jm-select-none jm-title'});
  128. titleLoadingText.innerHTML = '加载中...';
  129. popupWindow.appendChild(titleLoadingText);
  130.  
  131. // Function to toggle the loading status
  132. const toggleLoading = (status) => {
  133. if (status === "loading") {
  134. titleLoadingText.style.display = 'inline';
  135. titleText.style.display = 'none';
  136. numberIconPath.setAttribute('d', LOADING_ICON);
  137. numberText.style.color = numberIcon.style.color = "black";
  138. } else if (status === "fail") {
  139. titleLoadingText.style.display = 'none';
  140. titleText.style.display = 'inline';
  141. numberIconPath.setAttribute('d', FAIL_ICON);
  142. numberText.style.color = numberIcon.style.color = "red";
  143. } else if (status === "done") {
  144. titleLoadingText.style.display = 'none';
  145. titleText.style.display = 'inline';
  146. numberIconPath.setAttribute('d', SUCCESS_ICON);
  147. numberText.style.color = numberIcon.style.color = "green";
  148. } else if (status === "warning") {
  149. titleLoadingText.style.display = 'none';
  150. titleText.style.display = 'inline';
  151. numberIconPath.setAttribute('d', WARNING_ICON);
  152. numberText.style.color = numberIcon.style.color = "orange";
  153. }
  154. };
  155.  
  156. // Create a button element for the copy button
  157. const copyBtn = createElementWithAttr('button', {id: 'jm-copy'});
  158. popupWindow.appendChild(copyBtn);
  159.  
  160. // Define the copy icon SVG path
  161. 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";
  162. 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';
  163.  
  164. // Create an SVG element for the copy button icon
  165. const [copyBtnIcon, copyBtnCopyPath, copyBtnDonePath] = createSVGElement([COPY_ICON, DONE_ICON], '0 0 1024 1024', '16px', '16px', {id: 'jm-copy-icon'});
  166. copyBtnDonePath.classList.toggle('jm-copy-icon-hide');
  167. copyBtnCopyPath.classList.add('jm-copy-icon');
  168. copyBtnDonePath.classList.add('jm-copy-icon');
  169. copyBtn.appendChild(copyBtnIcon);
  170.  
  171. // Function to disable the copy button
  172. const disableBtn = (status) => {
  173. copyBtn.disabled = status;
  174. copyBtn.style.pointerEvents = status ? 'none' : 'auto';
  175. copyBtnIcon.setAttribute('color', status ? 'gray' : 'dodgerblue');
  176. };
  177. disableBtn(true);
  178.  
  179. // Create a style element for the CSS styles
  180. const style = createElementWithAttr('style');
  181. style.innerHTML = `
  182. .jm-select-none {
  183. -webkit-touch-callout: none;
  184. -webkit-user-select: none;
  185. -khtml-user-select: none;
  186. -moz-user-select: none;
  187. -ms-user-select: none;
  188. user-select: none;
  189. }
  190.  
  191. .jm-overflow {
  192. overflow: hidden;
  193. text-overflow: ellipsis;
  194. white-space: nowrap;
  195. }
  196.  
  197. #jm-popup {
  198. position: absolute;
  199. background-color: #fff;
  200. padding: 10px;
  201. margin-top: 10px;
  202. border: 1px solid #ddd;
  203. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
  204. z-index: 999999999999;
  205. display: none;
  206. max-width: 25%;
  207. column-gap: 10px;
  208. align-items: center;
  209. }
  210.  
  211. .jm-title {
  212. max-width: 100%;
  213. font-size: 14px;
  214. grid-column: 1;
  215. grid-row: 2;
  216. }
  217.  
  218. #jm-title-text {
  219. display: none;
  220. }
  221.  
  222. #jm-number-container {
  223. max-width: 100%;
  224. grid-column: 1;
  225. grid-row: 1;
  226. display: flex;
  227. align-items: center;
  228. }
  229.  
  230. #jm-number {
  231. font-size: 18px;
  232. font-weight: bold;
  233. }
  234.  
  235. #jm-number-icon {
  236. margin-right: 5px;
  237. }
  238.  
  239. #jm-copy {
  240. border: none;
  241. background-color: #fff;
  242. width: 32px;
  243. height: 32px;
  244. font-size: 16px;
  245. cursor: pointer;
  246. grid-column: 2;
  247. grid-row: 1 / 3;
  248. transition: background-color 0.3s;
  249. }
  250.  
  251. #jm-copy:hover:not(:disabled) {
  252. background-color: #f6f6f6;
  253. }
  254.  
  255. #jm-copy:active:not(:disabled) {
  256. background-color: #e6e6e6;
  257. }
  258.  
  259. .jm-copy-icon {
  260. transition: opacity 0.25s;
  261. }
  262.  
  263. .jm-copy-icon-hide {
  264. opacity: 0;
  265. }
  266. `;
  267. document.head.appendChild(style);
  268.  
  269. // Function to fetch the title of a URL
  270. const fetchTitle = (url, callback) => {
  271. console.log("fetching " + url);
  272. GM_xmlhttpRequest({
  273. method: "GET",
  274. url: url,
  275. onload: function(response) {
  276. const title = response.responseText.match(/<title[^>]*>([^<]+)<\/title>/)[1];
  277. callback(title.replace(" Comics - 禁漫天堂", ""));
  278. }
  279. });
  280. };
  281.  
  282. // Function to copy the title text to the clipboard
  283. const copyToClipboard = (event) => {
  284. navigator.clipboard.writeText(titleText.innerText);
  285. copyBtn.style.pointerEvents = 'none';
  286. copyBtnCopyPath.classList.toggle('jm-copy-icon-hide');
  287. setTimeout(() => {
  288. copyBtnDonePath.classList.toggle('jm-copy-icon-hide');
  289. }, 250);
  290. setTimeout(() => {
  291. copyBtnDonePath.classList.toggle('jm-copy-icon-hide');
  292. setTimeout(() => {
  293. copyBtnCopyPath.classList.toggle('jm-copy-icon-hide');
  294. copyBtn.style.pointerEvents = 'auto';
  295. }, 250);
  296. }, 1500);
  297. };
  298. copyBtn.addEventListener('click', copyToClipboard);
  299.  
  300. // Function to show the popup window
  301. const showPopup = (event) => {
  302. const selectedText = window.getSelection();
  303.  
  304. // Check if mouse is inside the popup window
  305. if (!event.target.closest('#jm-popup')) {
  306. popupWindow.style.display = 'none';
  307. disableBtn(true);
  308. }
  309.  
  310. if(selectedText.toString().trim() !== '') {
  311. const number = selectedText.toString().replace(/\D/g, '');
  312.  
  313. if (popupWindow.style.display !== 'grid' && number !== "") {
  314. const range = selectedText.getRangeAt(0);
  315. const rect = range.getBoundingClientRect();
  316. const url = "https://" + JM_SITE + "/album/" + number;
  317.  
  318. const activeEl = document.activeElement;
  319. if(['TEXTAREA','INPUT'].includes(activeEl.tagName)) rect = activeEl.getBoundingClientRect();
  320.  
  321. const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
  322.  
  323. const top = Math.floor(scrollTop + rect.top + rect.height);
  324. const left = Math.floor(rect.left);
  325.  
  326. if(top === 0 && left === 0){
  327. return;
  328. }
  329.  
  330. popupWindow.style.left = left + 'px';
  331. popupWindow.style.top = top + 'px';
  332.  
  333. numberText.innerHTML = number;
  334. numberText.style.color = "";
  335.  
  336. toggleLoading("loading");
  337.  
  338. popupWindow.style.display = 'grid';
  339.  
  340. // Special optimization for nbnhhsh
  341. const nbnhhsh = document.getElementsByClassName("nbnhhsh-box nbnhhsh-box-pop")[0];
  342. if (nbnhhsh) nbnhhsh.style.top = (parseInt(nbnhhsh.style.top) + 80) + "px";
  343.  
  344. fetchTitle(url, (title) => {
  345. titleText.href = url;
  346. if (title === "Just a moment...") {
  347. titleText.innerHTML = titleText.title = "自动获取失败,请手动点击链接";
  348. toggleLoading("warning");
  349. } else if (title === "禁漫天堂") {
  350. titleText.innerHTML = titleText.title = "无效车牌"
  351. toggleLoading("fail");
  352. } else {
  353. titleText.innerHTML = titleText.title = title
  354. toggleLoading("done");
  355. disableBtn(false);
  356. }
  357. });
  358. }
  359. }
  360. }
  361.  
  362. // Function to show the popup window after a delay
  363. const _showPopup = (event) => {
  364. // Delay window.getSelection() to get the correct selected text
  365. setTimeout(() => {
  366. showPopup(event);
  367. }, 1);
  368. }
  369.  
  370. // Add event listeners to show the popup window
  371. document.addEventListener('mouseup', _showPopup);
  372. document.addEventListener('keyup', _showPopup);
  373. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址