Greasy Fork is available in English.

网盘智能识别助手

智能识别选中文字中的🔗网盘链接和🔑提取码,识别成功打开网盘链接并自动填写提取码,省去手动复制提取码在输入的烦恼。支持识别 ✅百度网盘 ✅阿里云盘 ✅腾讯微云 ✅蓝奏云 ✅天翼云盘 ✅移动云盘 ✅迅雷云盘 ✅123云盘 ✅360云盘 ✅115网盘 ✅奶牛快传 ✅城通网盘 ✅夸克网盘 ✅FlowUs息流 ✅Chrome 扩展商店 ✅Edge 扩展商店 ✅Firefox 扩展商店 ✅Windows 应用商店。

  1. // ==UserScript==
  2. // @name 网盘智能识别助手
  3. // @namespace https://github.com/syhyz1990/panAI
  4. // @version 2.0.2
  5. // @author YouXiaoHou,52fisher
  6. // @description 智能识别选中文字中的🔗网盘链接和🔑提取码,识别成功打开网盘链接并自动填写提取码,省去手动复制提取码在输入的烦恼。支持识别 ✅百度网盘 ✅阿里云盘 ✅腾讯微云 ✅蓝奏云 ✅天翼云盘 ✅移动云盘 ✅迅雷云盘 ✅123云盘 ✅360云盘 ✅115网盘 ✅奶牛快传 ✅城通网盘 ✅夸克网盘 ✅FlowUs息流 ✅Chrome 扩展商店 ✅Edge 扩展商店 ✅Firefox 扩展商店 ✅Windows 应用商店。
  7. // @license AGPL-3.0-or-later
  8. // @homepage https://www.youxiaohou.com/tool/install-panai.html
  9. // @supportURL https://github.com/syhyz1990/panAI
  10. // @match *://*/*
  11. // @require https://unpkg.com/sweetalert2@10.16.6/dist/sweetalert2.min.js
  12. // @require https://unpkg.com/hotkeys-js@3.13.3/dist/hotkeys.min.js
  13. // @resource swalStyle https://unpkg.com/sweetalert2@10.16.6/dist/sweetalert2.min.css
  14. // @run-at document-idle
  15. // @grant GM_openInTab
  16. // @grant GM_setValue
  17. // @grant GM_getValue
  18. // @grant GM_registerMenuCommand
  19. // @grant GM_getResourceText
  20. // @grant GM_info
  21. // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjggMTI4Ij48cGF0aCBkPSJNMTAzLjYgMTA3LjRjMy41LTIuMiA4LjktNi4xIDEzLjgtMTIuNXM3LjMtMTIuNSA4LjUtMTYuNWMuNS0xLjcgMi4yLTcuNSAyLjItMTQuNyAwLTEwLjEtMy4zLTI1LjEtMTUuNC0zNi44LTE0LjUtMTQtMzIuMS0xNC4zLTM1LjctMTQuMy04IDAtMTUuNyAxLjktMjIuNiA1LjJDNDQgMjMgMzUuNyAzMS40IDMwLjggNDEuN2MtMS4zIDIuOC00IDQuNy03LjEgNS00IC4zLTcuNSA0LjQtOC45IDkuNi0uNSAxLjktMS42IDMuNS0zLjEgNC43QzQuNCA2Ni44IDAgNzUuNyAwIDg1YzAgNi44IDIuMyAxMy4xIDYuMSAxOC4yIDUuNSA3LjQgMTQuMiAxMi4yIDI0IDEyLjJoNDcuMWM0LjQgMCAxMS0uNSAxOC4zLTMuNSAzLjItMS40IDUuOS0zIDguMS00LjV6IiBmaWxsPSIjNDQ0Ii8+PHBhdGggZD0iTTExOS44IDY0LjNjLjEtMTcuMS0xMC40LTI4LTEyLjUtMzAuMUM5NSAyMi4xIDc5LjkgMjEuOCA3Ni45IDIxLjhjLTE3LjYgMC0zMy4zIDEwLjUtMzkuOSAyNi43LS42IDEuMy0xLjggMi4zLTMuNCAyLjNoLS40Yy01LjggMC0xMC42IDQuOC0xMC42IDEwLjd2LjVjMCAxLjQtLjggMi42LTEuOSAzLjNDMTMuNCA2OSA4LjggNzYuOCA4LjggODVjMCAxMi4yIDkuOSAyMi4zIDIyLjIgMjIuM2g0NS4yYzMuNi0uMSAxNy42LS45IDI5LjYtMTIgMi45LTIuOCAxMy45LTEzLjcgMTQtMzF6IiBmaWxsPSIjZGI4NDEyIi8+PHBhdGggZD0iTTExMC44IDU3LjRsLjIgMy4zYzAgMS4zLTEuMSAyLjQtMi4zIDIuNC0xLjMgMC0yLjMtMS4xLTIuMy0yLjRsLS4xLTIuOHYtLjNjMC0xLjIuOS0yLjIgMi4xLTIuM2guM2MuNyAwIDEuMy4zIDEuNy43LS4yLjEuMy41LjQgMS40em0tMy4zLTEwLjNjMCAxLjItMSAyLjMtMi4yIDIuM2gtLjFjLS44IDAtMS42LS41LTItMS4yLTQuNi04LjMtMTMuMy0xMy41LTIyLjgtMTMuNS0xLjIgMC0yLjMtMS0yLjMtMi4ydi0uMWMwLTEuMiAxLTIuMyAyLjItMi4zaC4xYTMwLjM3IDMwLjM3IDAgMCAxIDE1LjggNC40YzQuNiAyLjggOC40IDYuOCAxMS4xIDExLjUuMS4zLjIuNy4yIDEuMXpNNjkuMiA0OWwxOS40IDE0LjhjMS45IDEuNSAzLjEgMy41IDMuNSA1Ljd2LjJjLjEuNC4xLjguMSAxLjIgMCAuNi0uMSAxLjEtLjIgMS42LS40IDIuMi0xLjcgNC4yLTMuNSA1LjZMNjkuMyA5M2MtMi42IDItNS40IDIuNS03LjcgMS40LS4xLS4xLS4yLS4xLS4yLS4yLTItMS4yLTMuMi0zLjUtMy4yLTYuNHYtNi42aC01LjdjLTYuOCAwLTEyLTQuNy0xMi0xMC45IDAtNC44IDIuNi04LjUgNy4yLTEwLjMgMS4zLS41IDIuNy4yIDMuMiAxLjVzLS4xIDIuOC0xLjQgMy4zYy0yLjcgMS4xLTQgMi45LTQgNS41IDAgMy41IDMgNiA3IDZoOC4xYy41IDAgMSAuMiAxLjQuNi43LjYgMS4xIDEuNyAxLjEgMi42djguNGMwIDEuMy40IDIgLjcgMi4xLjQuMiAxLjMgMCAyLjQtLjlsMTkuMi0xNC45YzEuMi0uOSAxLjgtMi4xIDEuOC0zLjNzLS42LTIuMy0xLjctMy4xTDY2LjIgNTNjLTEuMS0uOS0yLTEuMS0yLjQtLjktLjMuMi0uNy45LS43IDIuMXY3LjZjMCAuOS0uNSAxLjctMS4yIDIuMS0uNC4zLS44LjQtMS4zLjQtMS40IDAtMi41LTEuMS0yLjUtMi41di03LjZjMC0zLjEgMS4zLTUuNSAzLjUtNi42bC43LS4zYzIuMS0uNyA0LjYtLjEgNi45IDEuN3oiIGZpbGw9IiM0NDQiLz48L3N2Zz4=
  22. // ==/UserScript==
  23.  
  24. (function () {
  25. 'use strict';
  26.  
  27. const customClass = {
  28. container: 'panai-container',
  29. popup: 'panai-popup',
  30. };
  31.  
  32. let toast = Swal.mixin({
  33. toast: true,
  34. position: 'top',
  35. showConfirmButton: false,
  36. timer: 3500,
  37. timerProgressBar: false,
  38. didOpen: (toast) => {
  39. toast.addEventListener('mouseenter', Swal.stopTimer);
  40. toast.addEventListener('mouseleave', Swal.resumeTimer);
  41. }
  42. });
  43.  
  44. let util = {
  45. clog(c) {
  46. console.group("%c %c [网盘智能识别助手]", `background:url(${GM_info.script.icon}) center center no-repeat;background-size:12px;padding:3px`, "");
  47. console.log(c);
  48. console.groupEnd();
  49. },
  50.  
  51. parseQuery(name) {
  52. let reg = new RegExp(`(?<=(?:${name})\\=)(?:wss:[a-zA-Z0-9]+|[\\w-]+)`, "i")
  53. let pd = location.href.replace(/%3A/g, ":").match(reg);
  54. if (pd) {
  55. return pd[0];
  56. }
  57. return null;
  58. },
  59.  
  60. getValue(name) {
  61. return GM_getValue(name);
  62. },
  63.  
  64. setValue(name, value) {
  65. GM_setValue(name, value);
  66. },
  67.  
  68. sleep(time) {
  69. return new Promise((resolve) => setTimeout(resolve, time));
  70. },
  71.  
  72. addStyle(id, tag, css) {
  73. tag = tag || 'style';
  74. let doc = document, styleDom = doc.getElementById(id);
  75. if (styleDom) return;
  76. let style = doc.createElement(tag);
  77. style.rel = 'stylesheet';
  78. style.id = id;
  79. tag === 'style' ? style.innerHTML = css : style.href = css;
  80. document.head.appendChild(style);
  81. },
  82.  
  83. isHidden(el) {
  84. try {
  85. return el.offsetParent === null;
  86. } catch (e) {
  87. return false;
  88. }
  89. },
  90.  
  91. isMobile: (() => !!navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone|HarmonyOS|MicroMessenger)/i))(),
  92.  
  93. query(selector) {
  94. if (Array.isArray(selector)) {
  95. let obj = null;
  96. for (let i = 0; i < selector.length; i++) {
  97. let o = document.querySelector(selector[i]);
  98. if (o) {
  99. obj = o;
  100. break;
  101. }
  102. }
  103. return obj;
  104. }
  105. return document.querySelector(selector);
  106. }
  107. };
  108.  
  109. let opt = {
  110. 'baidu': {
  111. reg: /((?:https?:\/\/)?(?:e?yun|pan)\.baidu\.com\/(doc\/|enterprise\/)?(?:s\/[\w~]*(((-)?\w*)*)?|share\/\S{4,}))/,
  112. host: /(pan|e?yun)\.baidu\.com/,
  113. input: ['#accessCode', '.share-access-code', '#wpdoc-share-page > .u-dialog__wrapper .u-input__inner'],
  114. button: ['#submitBtn', '.share-access .g-button', '#wpdoc-share-page > .u-dialog__wrapper .u-btn--primary'],
  115. name: '百度网盘',
  116. storage: 'hash'
  117. },
  118. 'aliyun': {
  119. reg: /((?:https?:\/\/)?(?:(?:www\.)?(?:aliyundrive|alipan)\.com\/s|alywp\.net)\/[a-zA-Z\d]+)/,
  120. host: /www\.(aliyundrive|alipan)\.com|alywp\.net/,
  121. input: ['form .ant-input', 'form input[type="text"]', 'input[name="pwd"]'],
  122. button: ['form .button--fep7l', 'form button[type="submit"]'],
  123. name: '阿里云盘',
  124. storage: 'hash'
  125. },
  126. 'weiyun': {
  127. reg: /((?:https?:\/\/)?share\.weiyun\.com\/[a-zA-Z\d]+)/,
  128. host: /share\.weiyun\.com/,
  129. input: ['.mod-card-s input[type=password]', 'input.pw-input'],
  130. button: ['.mod-card-s .btn-main', ".pw-btn-wrap button.btn"],
  131. name: '微云',
  132. storage: 'hash'
  133. },
  134. 'lanzou': {
  135. reg: /((?:https?:\/\/)?(?:[a-zA-Z0-9\-.]+)?(?:lanzou[a-z]|lanzn)\.com\/[a-zA-Z\d_\-]+(?:\/[\w-]+)?)/,
  136. host: /(?:[a-zA-Z\d-.]+)?(?:lanzou[a-z]|lanzn)\.com/,
  137. input: ['#pwd'],
  138. button: ['.passwddiv-btn', '#sub'],
  139. name: '蓝奏云',
  140. storage: 'hash'
  141. },
  142. 'tianyi': {
  143. reg: /((?:https?:\/\/)?cloud\.189\.cn\/(?:t\/|web\/share\?code=)?[a-zA-Z\d]+)/,
  144. host: /cloud\.189\.cn/,
  145. input: ['.access-code-item #code_txt', "input.access-code-input"],
  146. button: ['.access-code-item .visit', ".button"],
  147. name: '天翼云盘',
  148. storage: (() => util.isMobile === true ? 'local' : 'hash')(),
  149. storagePwdName: 'tmp_tianyi_pwd'
  150. },
  151. 'caiyun': {
  152. reg: /((?:https?:\/\/)?caiyun\.139\.com\/(?:m\/i|w\/i\/|web\/|front\/#\/detail)\??(?:linkID=)?[a-zA-Z\d]+)/,
  153. host: /(?:cai)?yun\.139\.com/,
  154. input: ['.token-form input[type=text]'],
  155. button: ['.token-form .btn-token'],
  156. name: '移动云盘',
  157. storage: 'local',
  158. storagePwdName: 'tmp_caiyun_pwd'
  159. },
  160. 'xunlei': {
  161. reg: /((?:https?:\/\/)?pan\.xunlei\.com\/s\/[\w-]{10,})/,
  162. host: /pan\.xunlei\.com/,
  163. input: ['.pass-input-wrap .td-input__inner'],
  164. button: ['.pass-input-wrap .td-button'],
  165. name: '迅雷云盘',
  166. storage: 'hash'
  167. },
  168. '123pan': {
  169. reg: /((?:https?:\/\/)?www\.123pan\.com\/s\/[\w-]{6,})/,
  170. host: /www\.123pan\.com/,
  171. input: ['.ca-fot input', ".appinput .appinput"],
  172. button: ['.ca-fot button', ".appinput button"],
  173. name: '123云盘',
  174. storage: 'hash'
  175. },
  176. '360': {
  177. reg: /((?:https?:\/\/)?(?:[a-zA-Z\d\-.]+)?(?:yunpan\.360\.cn|yunpan\.com)(\/lk)?\/surl_\w{6,})/,
  178. host: /[\w.]+?yunpan\.com/,
  179. input: ['.pwd-input'],
  180. button: ['.submit-btn'],
  181. name: '360云盘',
  182. storage: 'local',
  183. storagePwdName: 'tmp_360_pwd'
  184. },
  185. '115': {
  186. reg: /((?:https?:\/\/)?115\.com\/s\/[a-zA-Z\d]+)/,
  187. host: /115\.com/,
  188. input: ['.form-decode input'],
  189. button: ['.form-decode .submit a'],
  190. name: '115网盘',
  191. storage: 'hash'
  192. },
  193. 'cowtransfer': {
  194. reg: /((?:https?:\/\/)?(?:[a-zA-Z\d-.]+)?cowtransfer\.com\/s\/[a-zA-Z\d]+)/,
  195. host: /(?:[a-zA-Z\d-.]+)?cowtransfer\.com/,
  196. input: ['.receive-code-input input'],
  197. button: ['.open-button'],
  198. name: '奶牛快传',
  199. storage: 'hash'
  200. },
  201. 'ctfile': {
  202. reg: /((?:https?:\/\/)?(?:[a-zA-Z\d-.]+)?(?:ctfile|545c|u062|ghpym)\.com\/\w+\/[a-zA-Z\d-]+)/,
  203. host: /(?:[a-zA-Z\d-.]+)?(?:ctfile|545c|u062)\.com/,
  204. input: ['#passcode'],
  205. button: ['.card-body button'],
  206. name: '城通网盘',
  207. storage: 'hash'
  208. },
  209. 'quark': {
  210. reg: /((?:https?:\/\/)?pan\.quark\.cn\/s\/[a-zA-Z\d-]+)/,
  211. host: /pan\.quark\.cn/,
  212. input: ['.ant-input'],
  213. button: ['.ant-btn-primary'],
  214. name: '夸克网盘',
  215. storage: 'local',
  216. storagePwdName: 'tmp_quark_pwd'
  217. },
  218. 'vdisk': {
  219. reg: /(?:https?:\/\/)?vdisk.weibo.com\/lc\/\w+/,
  220. host: /vdisk\.weibo\.com/,
  221. input: ['#keypass', "#access_code"],
  222. button: ['.search_btn_wrap a', "#linkcommon_btn"],
  223. name: '微盘',
  224. storage: 'hash',
  225. },
  226. 'wenshushu': {
  227. reg: /((?:https?:\/\/)?(?:www\.wenshushu|ws28)\.cn\/(?:k|box|f)\/\w+)/,
  228. host: /www\.wenshushu\.cn/,
  229. input: ['.pwd-inp .ivu-input'],
  230. button: ['.pwd-inp .ivu-btn'],
  231. name: '文叔叔网盘',
  232. storage: 'hash'
  233. },
  234. 'uc': {
  235. reg: /(?:https?:\/\/)?drive\.uc\.cn\/s\/[a-zA-Z\d]+/,
  236. host: /drive\.uc\.cn/,
  237. input: ["input[class*='ShareReceivePC--input']", '.input-wrap input'],
  238. button: ["button[class*='ShareReceivePC--submit-btn'", '.input-wrap button'],
  239. name: 'UC云盘',
  240. storage: 'hash'
  241. },
  242. 'jianguoyun': {
  243. reg: /((?:https?:\/\/)?www\.jianguoyun\.com\/p\/[\w-]+)/,
  244. host: /www\.jianguoyun\.com/,
  245. input: ['input[type=password]'],
  246. button: ['.ok-button', '.confirm-button'],
  247. name: '坚果云',
  248. storage: 'hash'
  249. },
  250. 'wo': {
  251. reg: /(?:https?:\/\/)?pan\.wo\.cn\/s\/[\w_]+/,
  252. host: /(pan\.wo\.cn|panservice\.mail\.wo\.cn)/,
  253. input: ['input.el-input__inner', ".van-field__control"],
  254. button: ['.s-button', ".share-code button"],
  255. name: '联通云盘',
  256. storage: (() => util.isMobile === true ? 'local' : 'hash')(),
  257. storagePwdName: 'tmp_wo_pwd'
  258. },
  259. 'mega': {
  260. reg: /((?:https?:\/\/)?(?:mega\.nz|mega\.co\.nz)\/#F?![\w!-]+)/,
  261. host: /(?:mega\.nz|mega\.co\.nz)/,
  262. input: ['.dlkey-dialog input'],
  263. button: ['.dlkey-dialog .fm-dialog-new-folder-button'],
  264. name: 'Mega',
  265. storage: 'local'
  266. },
  267. '520vip': {
  268. reg: /((?:https?:\/\/)?www\.(?:520-vip|eos-53)\.com\/file-\d+\.html)/,
  269. host: /www\.520-vip\.com/,
  270. name: '520云盘',
  271. },
  272. '567pan': {
  273. reg: /((?:https?:\/\/)?www\.567(?:pan|yun|file|inc)\.(?:com|cn)\/file-\d+\.html)/,
  274. host: /www\.567inc\.cn/,
  275. name: '567盘',
  276. replaceHost: "www.567inc.com",
  277. },
  278. 'ayunpan': {
  279. reg: /((?:https?:\/\/)?www\.ayunpan\.com\/file-\d+\.html)/,
  280. host: /www\.ayunpan\.com/,
  281. name: 'AYunPan',
  282. },
  283. 'iycdn.com': {
  284. reg: /((?:https?:\/\/)?www\.iycdn\.com\/file-\d+\.html)/,
  285. host: /www\.iycdn\.com/,
  286. name: '爱优网盘',
  287. },
  288. 'feimaoyun': {
  289. reg: /((?:https?:\/\/)?www\.feimaoyun\.com\/s\/[0-9a-zA-Z]+)/,
  290. host: /www\.feimaoyun\.com/,
  291. name: '飞猫盘',
  292. },
  293. 'uyunp.com': {
  294. reg: /((?:https?:\/\/)?download\.uyunp\.com\/share\/s\/short\/\?surl=[0-9a-zA-Z]+)/,
  295. host: /download\.uyunp\.com/,
  296. name: '优云下载',
  297. },
  298. 'dudujb': {
  299. reg: /(?:https?:\/\/)?www\.dudujb\.com\/file-\d+\.html/,
  300. host: /www\.dudujb\.com/,
  301. name: '贵族网盘',
  302. },
  303. 'xunniu': {
  304. reg: /(?:https?:\/\/)?www\.xunniu(?:fxp|wp|fx)\.com\/file-\d+\.html/,
  305. host: /www\.xunniuwp\.com/,
  306. name: '迅牛网盘',
  307. },
  308. 'xueqiupan': {
  309. reg: /(?:https?:\/\/)?www\.xueqiupan\.com\/file-\d+\.html/,
  310. host: /www\.xueqiupan\.com/,
  311. name: '雪球云盘',
  312. },
  313. '77file': {
  314. reg: /(?:https?:\/\/)?www\.77file\.com\/s\/[a-zA-Z\d]+/,
  315. host: /www\.77file\.com/,
  316. name: '77file',
  317. },
  318. 'ownfile': {
  319. reg: /(?:https?:\/\/)?ownfile\.net\/files\/[a-zA-Z\d]+\.html/,
  320. host: /ownfile\.net/,
  321. name: 'OwnFile',
  322. },
  323. 'feiyunfile': {
  324. reg: /(?:https?:\/\/)?www\.feiyunfile\.com\/file\/[\w=]+\.html/,
  325. host: /www\.feiyunfile\.com/,
  326. name: '飞云网盘',
  327. },
  328. 'yifile': {
  329. reg: /(?:https?:\/\/)?www\.yifile\.com\/f\/\w+/,
  330. host: /www\.yifile\.com/,
  331. name: 'YiFile',
  332. },
  333. 'dufile': {
  334. reg: /(?:https?:\/\/)?dufile\.com\/file\/\w+\.html/,
  335. host: /dufile\.com/,
  336. name: 'duFile',
  337. },
  338. 'flowus': {
  339. reg: /((?:https?:\/\/)?flowus\.cn\/[\S ^\/]*\/?share\/[a-z\d]{8}-[a-z\d]{4}-[a-z\d]{4}-[a-z\d]{4}-[a-z\d]{12})/,
  340. host: /flowus\.cn/,
  341. name: 'FlowUs息流',
  342. storage: 'hash'
  343. },
  344. 'chrome': {
  345. reg: /^https?:\/\/chrome.google.com\/webstore\/.+?\/([a-z]{32})(?=[\/#?]|$)/,
  346. host: /chrome\.google\.com/,
  347. replaceHost: "chrome.crxsoso.com",
  348. name: 'Chrome商店',
  349. },
  350. 'edge': {
  351. reg: /^https?:\/\/microsoftedge.microsoft.com\/addons\/.+?\/([a-z]{32})(?=[\/#?]|$)/,
  352. host: /microsoftedge\.microsoft\.com/,
  353. replaceHost: "microsoftedge.crxsoso.com",
  354. name: 'Edge商店',
  355. },
  356. 'firefox': {
  357. reg: /^https?:\/\/(reviewers\.)?(addons\.mozilla\.org|addons(?:-dev)?\.allizom\.org)\/.*?(?:addon|review)\/([^/<>"'?#]+)/,
  358. host: /addons\.mozilla\.org/,
  359. replaceHost: "addons.crxsoso.com",
  360. name: 'Firefox商店',
  361. },
  362. 'microsoft': {
  363. reg: /^https?:\/\/(?:apps|www).microsoft.com\/(?:store|p)\/.+?\/([a-zA-Z\d]{10,})(?=[\/#?]|$)/,
  364. host: /(apps|www)\.microsoft\.com/,
  365. replaceHost: "apps.crxsoso.com",
  366. name: 'Windows商店',
  367. }
  368. };
  369.  
  370. let main = {
  371. lastText: "lorem&",
  372.  
  373. //初始化配置数据
  374. initValue() {
  375. let value = [{
  376. name: 'setting_success_times',
  377. value: 0
  378. }, {
  379. name: 'setting_auto_click_btn',
  380. value: true
  381. }, {
  382. name: 'setting_active_in_front',
  383. value: true
  384. }, {
  385. name: 'setting_timer_open',
  386. value: false
  387. }, {
  388. name: 'setting_timer',
  389. value: 5000
  390. }, {
  391. name: 'setting_hotkeys',
  392. value: 'F1'
  393. }];
  394.  
  395. value.forEach((v) => {
  396. if (util.getValue(v.name) === undefined) {
  397. util.setValue(v.name, v.value);
  398. }
  399. });
  400. },
  401.  
  402. // 监听选择事件
  403. addPageListener() {
  404. document.addEventListener("mouseup", this.smartIdentify.bind(this), true);
  405. document.addEventListener("keydown", this.pressKey.bind(this), true);
  406. },
  407.  
  408. // ⚠️可能会增加时间⚠️ 如果有需要可以增加选项
  409. // 获取选择内容的HTML和文本(增加兼容性) 或 DOM(节点遍历)
  410. getSelectionHTML(selection, isDOM = false) {
  411. const testDiv = document.createElement("div");
  412. if (!selection.isCollapsed) {
  413. // Range 转 DocumentFragment
  414. const docFragment = selection.getRangeAt(0).cloneContents();
  415. testDiv.appendChild(docFragment);
  416. }
  417. // 拼接选中文本,增加兼容
  418. return isDOM ? testDiv : selection.toString();
  419. },
  420.  
  421. smartIdentify(event, str = '') {
  422. let selection = window.getSelection();
  423. let text = str || this.getSelectionHTML(selection);
  424. if (text !== this.lastText && text !== '') { //选择相同文字或空不识别
  425. let start = performance.now();
  426. this.lastText = text;
  427. //util.clog(`当前选中文字:${text}`);
  428. let linkObj = this.parseLink(text);
  429. let link = linkObj.link;
  430. let name = linkObj.name;
  431. let pwd = this.parsePwd(text);
  432. if (!link) {
  433. linkObj = this.parseParentLink(selection);
  434. link = linkObj.link;
  435. name = linkObj.name;
  436. }
  437. if (link) {
  438. if (!/https?:\/\//.test(link)) {
  439. link = 'https://' + link;
  440. }
  441. let end = performance.now();
  442. let time = (end - start).toFixed(3);
  443. util.clog(`文本识别结果:${name} 链接:${link} 密码:${pwd} 耗时:${time}毫秒`);
  444. let option = {
  445. toast: true,
  446. showCancelButton: true,
  447. position: 'top',
  448. title: `发现<span style="color: #2778c4;margin: 0 5px;">${name}</span>链接`,
  449. html: `<span style="font-size: 0.8em;">${!!pwd ? '密码:' + pwd : '是否打开?'}</span>`,
  450. confirmButtonText: '打开',
  451. cancelButtonText: '关闭',
  452. customClass
  453. };
  454. if (util.getValue('setting_timer_open')) {
  455. option.timer = util.getValue('setting_timer');
  456. option.timerProgressBar = true;
  457. }
  458. util.setValue('setting_success_times', util.getValue('setting_success_times') + 1);
  459.  
  460. Swal.fire(option).then((res) => {
  461. this.lastText = 'lorem&';
  462. selection.empty();
  463. if (res.isConfirmed || res.dismiss === 'timer') {
  464. if (linkObj.storage == "local") {
  465. util.setValue(linkObj.storagePwdName, pwd);
  466. }
  467. let active = util.getValue('setting_active_in_front');
  468. if (pwd) {
  469. let extra = `${link}?pwd=${pwd}#${pwd}`;
  470. if (~link.indexOf('?')) {
  471. extra = `${link}&pwd=${pwd}#${pwd}`;
  472. }
  473. GM_openInTab(extra, {active});
  474. } else {
  475. GM_openInTab(`${link}`, {active});
  476. }
  477. }
  478. });
  479. }
  480. }
  481. },
  482.  
  483. pressKey(event) {
  484. if (event.key === 'Enter') {
  485. let confirmBtn = document.querySelector('.panai-container .swal2-confirm');
  486. confirmBtn && confirmBtn.click();
  487. }
  488. if (event.key === 'Escape') {
  489. let cancelBtn = document.querySelector('.panai-container .swal2-cancel');
  490. cancelBtn && cancelBtn.click();
  491. }
  492. },
  493.  
  494. addHotKey() {
  495. //获取设置中的快捷键
  496. let hotkey = util.getValue('setting_hotkeys');
  497. hotkeys(hotkey, (event, handler) => {
  498. event.preventDefault();
  499. this.showIdentifyBox();
  500. });
  501. },
  502.  
  503. //正则解析网盘链接
  504. parseLink(text = '') {
  505. let obj = {name: '', link: '', storage: '', storagePwdName: ''};
  506. if (text) {
  507. try {
  508. text = decodeURIComponent(text);
  509. } catch {
  510. }
  511. text = text.replace(/[点點]/g, '.');
  512. text = text.replace(/[\u4e00-\u9fa5()(),\u200B,\uD83C-\uDBFF\uDC00-\uDFFF]/g, '');
  513. text = text.replace(/lanzous/g, 'lanzouw'); //修正lanzous打不开的问题
  514. for (let name in opt) {
  515. let val = opt[name];
  516. if (val.reg.test(text)) {
  517. let matches = text.match(val.reg);
  518. obj.name = val.name;
  519. obj.link = matches[0];
  520. obj.storage = val.storage;
  521. obj.storagePwdName = val.storagePwdName || null;
  522. if (val.replaceHost) {
  523. obj.link = obj.link.replace(val.host, val.replaceHost);
  524. }
  525. return obj;
  526. }
  527. }
  528. }
  529. return obj;
  530. },
  531.  
  532. //正则解析超链接类型网盘链接
  533. parseParentLink(selection) {
  534. const dom = this.getSelectionHTML(selection, true).querySelector('*[href]');
  535. return this.parseLink(dom ? dom.href : "");
  536. },
  537.  
  538. //正则解析提取码
  539. parsePwd(text) {
  540. text = text.replace(/\u200B/g, '').replace('%3A', ":");
  541. text = text.replace(/(?:本帖)?隐藏的?内容[::]?/, "");
  542. let reg = /wss:[a-zA-Z0-9]+|(?<=\s*(?:密|提取|访问|訪問|key|password|pwd|#|\?p=)\s*[码碼]?\s*[::=]?\s*)[a-zA-Z0-9]{3,8}/i;
  543. if (reg.test(text)) {
  544. let match = text.match(reg);
  545. return match[0];
  546. }
  547. return '';
  548. },
  549.  
  550. //根据域名检测网盘类型
  551. panDetect() {
  552. let hostname = location.hostname;
  553. for (let name in opt) {
  554. let val = opt[name];
  555. if (val.host.test(hostname)) {
  556. return name;
  557. }
  558. }
  559. return '';
  560. },
  561.  
  562. //自动填写密码
  563. autoFillPassword() {
  564. let query = util.parseQuery('pwd|p');
  565. let hash = location.hash.slice(1).replace(/\W/g, "") //hash中可能存在密码,需要过滤掉非密码字符
  566. let pwd = query || hash;
  567. let panType = this.panDetect();
  568. for (let name in opt) {
  569. let val = opt[name];
  570. if (panType === name) {
  571. if (val.storage === 'local') {
  572. //当前local存储的密码不一定是当前链接的密码,用户可能通过url直接访问或者恢复页面,这样取出来的密码可能是其他链接的
  573. //如果能从url中获取到密码,则应该优先使用url中获取的密码
  574. //util.getValue查询不到key时,默认返回undefined,已经形成逻辑短路,此处赋空值无效也无需赋空值.详见https://github.com/syhyz1990/panAI/commit/efb6ff0c77972920b26617bb836a2e19dd14a749
  575. pwd = pwd || util.getValue(val.storagePwdName);
  576. pwd && this.doFillAction(val.input, val.button, pwd);
  577. }
  578. if (val.storage === 'hash') {
  579. if (!/^(?:wss:[a-zA-Z\d]+|[a-zA-Z0-9]{3,8})$/.test(pwd)) { //过滤掉不正常的Hash
  580. return;
  581. }
  582. pwd && this.doFillAction(val.input, val.button, pwd);
  583. }
  584. }
  585. }
  586. },
  587.  
  588. doFillAction(inputSelector, buttonSelector, pwd) {
  589. let maxTime = 10;
  590. let ins = setInterval(async () => {
  591. maxTime--;
  592. let input = util.query(inputSelector);
  593. let button = util.query(buttonSelector);
  594. if (input && !util.isHidden(input)) {
  595. clearInterval(ins);
  596. Swal.fire({
  597. toast: true,
  598. position: 'top',
  599. showCancelButton: false,
  600. showConfirmButton: false,
  601. title: 'AI已识别到密码!正自动帮您填写',
  602. icon: 'success',
  603. timer: 2000,
  604. customClass
  605. });
  606.  
  607. let lastValue = input.value;
  608. input.value = pwd;
  609. //Vue & React 触发 input 事件
  610. let event = new Event('input', {bubbles: true});
  611. let tracker = input._valueTracker;
  612. if (tracker) {
  613. tracker.setValue(lastValue);
  614. }
  615. input.dispatchEvent(event);
  616.  
  617. if (util.getValue('setting_auto_click_btn')) {
  618. await util.sleep(1000); //1秒后点击按钮
  619. button.click();
  620. }
  621. } else {
  622. maxTime === 0 && clearInterval(ins);
  623. }
  624. }, 800);
  625. },
  626.  
  627. //重置识别次数
  628. clearIdentifyTimes() {
  629. let res = Swal.fire({
  630. showCancelButton: true,
  631. title: '确定要重置识别次数吗?',
  632. icon: 'warning',
  633. confirmButtonText: '确定',
  634. cancelButtonText: '取消',
  635. customClass
  636. }).then(res => {
  637. this.lastText = 'lorem&';
  638. if (res.isConfirmed) {
  639. util.setValue('setting_success_times', 0);
  640. history.go(0);
  641. }
  642. });
  643. },
  644.  
  645. //识别输入框中的内容
  646. showIdentifyBox() {
  647. Swal.fire({
  648. title: '识别剪切板中文字',
  649. input: 'textarea',
  650. inputPlaceholder: '若选方式一,请按 Ctrl+V 粘贴要识别的文字',
  651. html: `<div style="font-size: 12px;color: #999;margin-bottom: 8px;text-align: center;">提示:在任意网页按下 <span style="font-weight: 700;">${util.getValue("setting_hotkeys")}</span> 键可快速打开本窗口。</div><div style="font-size: 14px;line-height: 22px;padding: 10px 0 5px;text-align: left;"><div style="font-size: 16px;margin-bottom: 8px;font-weight: 700;">支持以下两种方式:</div><div><b>方式一:</b>直接粘贴文字到输入框,点击“识别方框内容”按钮。</div><div><b>方式二:</b>点击“读取剪切板”按钮。<span style="color: #d14529;font-size: 12px;">会弹出“授予网站读取剪切板”权限,同意后会自动识别剪切板中的文字。</span></div></div>`,
  652. showCloseButton: false,
  653. showDenyButton: true,
  654. confirmButtonText: '识别方框内容',
  655. denyButtonText: '读取剪切板',
  656. customClass
  657. }).then(res => {
  658. if (res.isConfirmed) {
  659. this.smartIdentify(null, res.value);
  660. }
  661. if (res.isDenied) {
  662. navigator.clipboard.readText().then(text => {
  663. this.smartIdentify(null, text);
  664. }).catch(() => {
  665. toast.fire({title: '读取剪切板失败,请先授权或手动粘贴后识别!', icon: 'error'});
  666. });
  667. }
  668. });
  669. },
  670.  
  671. //显示设置
  672. showSettingBox() {
  673. let html = `<div style="font-size: 1em;">
  674. <label class="panai-setting-label">填写密码后自动提交<input type="checkbox" id="S-Auto" ${util.getValue('setting_auto_click_btn') ? 'checked' : ''} class="panai-setting-checkbox"></label>
  675. <label class="panai-setting-label">前台打开网盘标签页<input type="checkbox" id="S-Active" ${util.getValue('setting_active_in_front') ? 'checked' : ''}
  676. class="panai-setting-checkbox"></label>
  677. <label class="panai-setting-label">倒计时结束自动打开<input type="checkbox" id="S-Timer-Open" ${util.getValue('setting_timer_open') ? 'checked' : ''} class="panai-setting-checkbox"></label>
  678. <label class="panai-setting-label" id="Panai-Range-Wrapper" style="${util.getValue('setting_timer_open') ? '' : 'display: none'}"><span>倒计时 <span id="Timer-Value">(${util.getValue('setting_timer') / 1000}秒)</span></span><input type="range" id="S-Timer" min="0" max="10000" step="500" value="${util.getValue('setting_timer')}" style="width: 200px;"></label>
  679. <label class="panai-setting-label">快捷键设置<input type="text" id="S-hotkeys" value="${util.getValue('setting_hotkeys')}" style="width: 100px;"></label>
  680. </div>`;
  681. Swal.fire({
  682. title: '识别助手配置',
  683. html,
  684. icon: 'info',
  685. showCloseButton: true,
  686. confirmButtonText: '保存',
  687. footer: '<div style="text-align: center;font-size: 1em;">点击查看 <a href="https://www.youxiaohou.com/tool/install-panai.html" target="_blank">使用说明</a>,助手免费开源,Powered by <a href="https://www.youxiaohou.com">油小猴</a></div>',
  688. customClass
  689. }).then((res) => {
  690. res.isConfirmed && history.go(0);
  691. });
  692.  
  693. document.getElementById('S-Auto').addEventListener('change', (e) => {
  694. util.setValue('setting_auto_click_btn', e.target.checked);
  695. });
  696. document.getElementById('S-Active').addEventListener('change', (e) => {
  697. util.setValue('setting_active_in_front', e.target.checked);
  698. });
  699. document.getElementById('S-Timer-Open').addEventListener('change', (e) => {
  700. let rangeWrapper = document.getElementById('Panai-Range-Wrapper');
  701. e.target.checked ? rangeWrapper.style.display = 'flex' : rangeWrapper.style.display = 'none';
  702. util.setValue('setting_timer_open', e.target.checked);
  703. });
  704. document.getElementById('S-Timer').addEventListener('change', (e) => {
  705. util.setValue('setting_timer', e.target.value);
  706. document.getElementById('Timer-Value').innerText = `(${e.target.value / 1000}秒)`;
  707. });
  708. document.getElementById('S-hotkeys').addEventListener('change', (e) => {
  709. util.setValue('setting_hotkeys', e.target.value);
  710. });
  711. },
  712.  
  713. registerMenuCommand() {
  714. GM_registerMenuCommand('👀 已识别:' + util.getValue('setting_success_times') + '次', () => {
  715. this.clearIdentifyTimes();
  716. });
  717. GM_registerMenuCommand(`📋️ 识别剪切板中文字(快捷键 ${util.getValue('setting_hotkeys')})`, () => {
  718. this.showIdentifyBox();
  719. });
  720. GM_registerMenuCommand('⚙️ 设置', () => {
  721. this.showSettingBox();
  722. });
  723. },
  724.  
  725. addPluginStyle() {
  726. let style = `
  727. .panai-container { z-index: 99999!important }
  728. .panai-popup { font-size: 14px !important }
  729. .panai-setting-label { display: flex;align-items: center;justify-content: space-between;padding-top: 20px; }
  730. .panai-setting-checkbox { width: 16px;height: 16px; }
  731. `;
  732.  
  733. if (document.head) {
  734. util.addStyle('swal-pub-style', 'style', GM_getResourceText('swalStyle'));
  735. util.addStyle('panai-style', 'style', style);
  736. }
  737.  
  738. const headObserver = new MutationObserver(() => {
  739. util.addStyle('swal-pub-style', 'style', GM_getResourceText('swalStyle'));
  740. util.addStyle('panai-style', 'style', style);
  741. });
  742. headObserver.observe(document.head, {childList: true, subtree: true});
  743. },
  744.  
  745. isTopWindow() {
  746. return window.self === window.top;
  747. },
  748.  
  749. init() {
  750. this.initValue();
  751. this.addPluginStyle();
  752. this.addHotKey();
  753. this.autoFillPassword();
  754. this.addPageListener();
  755. this.isTopWindow() && this.registerMenuCommand();
  756. },
  757. };
  758.  
  759. main.init();
  760. })();