网页通用验证码

解放眼睛和双手,自动识别并填入数字,字母验证码。新版本支持识别滑动验证码。

目前為 2022-05-12 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name 网页通用验证码
  3. // @namespace http://tampermonkey.net/
  4. // @version 3.1.
  5. // @description 解放眼睛和双手,自动识别并填入数字,字母验证码。新版本支持识别滑动验证码。
  6. // @author 哈士奇
  7.  
  8. // @include http://*
  9. // @include https://*
  10. // @license MIT
  11.  
  12. // @grant unsafeWindow
  13. // @grant GM_addStyle
  14. // @grant GM_listValues
  15. // @grant GM_addValueChangeListener
  16. // @grant GM_removeValueChangeListener
  17. // @grant GM_setValue
  18. // @grant GM_getValue
  19. // @grant GM_deleteValue
  20. // @grant GM_log
  21. // @grant GM_getResourceText
  22. // @grant GM_getResourceURL
  23. // @grant GM_registerMenuCommand
  24. // @grant GM_unregisterMenuCommand
  25. // @grant GM_xmlhttpRequest
  26. // @grant GM_download
  27. // @grant GM_getTab
  28. // @grant GM_saveTab
  29. // @grant GM_getTabs
  30. // @grant GM_notification
  31. // @grant GM_setClipboard
  32. // @grant GM_info
  33. // @grant GM_xmlhttpRequest
  34. // @connect *
  35. // @require https://cdn.jsdelivr.net/npm/vue@2.6.12
  36. // @require https://unpkg.com/element-ui/lib/index.js
  37. // @resource elementUIcss https://unpkg.com/element-ui/lib/theme-chalk/index.css
  38.  
  39. // @run-at document-end
  40. // ==/UserScript==
  41.  
  42. (function () {
  43. // GM_setValue('tipsConfig',"")
  44. var elementUIcss = GM_getResourceText("elementUIcss");
  45. GM_addStyle(elementUIcss);
  46.  
  47. function getStyle(el) {
  48. // 获取元素样式
  49. if (window.getComputedStyle) {
  50. return window.getComputedStyle(el, null);
  51. } else {
  52. return el.currentStyle;
  53. }
  54. }
  55.  
  56. function init() {
  57. //简化各种api和初始化全局变量
  58. CUR_URL = window.location.href;
  59. DOMAIN = CUR_URL.split("//")[1].split("/")[0];
  60. SLIDE_STORE_KEY = "husky_" + "slidePath" + location.host;
  61. NORMAL_STORE_KEY = "husky_" + "normalPath" + location.host;
  62. selector = document.querySelector.bind(document);
  63. selectorAll = document.querySelectorAll.bind(document);
  64. getItem = localStorage.getItem.bind(localStorage);
  65. setItem = localStorage.setItem.bind(localStorage);
  66. }
  67.  
  68. function getNumber(str) {
  69. return Number(str.split(".")[0].replace(/[^0-9]/gi, ""));
  70. }
  71.  
  72. function isNumber(value) {
  73. if (!value && value !== 0) {
  74. return false;
  75. }
  76. value = Number(value);
  77. return typeof value === "number" && !isNaN(value);
  78. }
  79.  
  80. function getEleTransform(el) {
  81. const style = window.getComputedStyle(el, null);
  82. var transform =
  83. style.getPropertyValue("-webkit-transform") ||
  84. style.getPropertyValue("-moz-transform") ||
  85. style.getPropertyValue("-ms-transform") ||
  86. style.getPropertyValue("-o-transform") ||
  87. style.getPropertyValue("transform") ||
  88. "null";
  89. return transform && transform.split(",")[4];
  90. }
  91.  
  92. class Captcha {
  93. // 识别网页中的验证码
  94. constructor() {
  95. this.imgCache = [];
  96. this.inputTags = [];
  97. this.recommendPath = {};
  98. this.checkTimer = null;
  99. this.listenLoadSuccess = false;
  100.  
  101. window.addEventListener("load", async () => {
  102. this.listenLoadSuccess = true;
  103. this.init();
  104. });
  105. setTimeout(() => {
  106. if (!this.listenLoadSuccess) {
  107. this.listenLoadSuccess = true;
  108. this.init();
  109. }
  110. }, 5000);
  111. }
  112.  
  113. doCheckTask() {
  114. this.findCaptcha();
  115. this.checkSlideCaptcha();
  116. }
  117. init() {
  118. if (blackListCheck()) {
  119. return;
  120. }
  121. this.manualLocateCaptcha();
  122. this.doCheckTask();
  123.  
  124. const MutationObserver =
  125. window.MutationObserver ||
  126. window.WebKitMutationObserver ||
  127. window.MozMutationObserver;
  128. const body = document.body;
  129.  
  130. const Observer = new MutationObserver((mutations, instance) => {
  131. if (blackListCheck()) {
  132. return;
  133. }
  134. for (let i = 0; i < mutations.length; i++) {
  135. const el = mutations[i].target;
  136. const tagName = mutations[i].target.tagName.toLowerCase();
  137. let checkList = [];
  138. checkList.push(el.getAttribute("id"));
  139. checkList.push(el.className);
  140. checkList.push(el.getAttribute("alt"));
  141. checkList.push(el.getAttribute("src"));
  142. checkList.push(el.getAttribute("name"));
  143. checkList = checkList.filter((item) => item);
  144.  
  145. for (let x = 0; x < checkList.length; x++) {
  146. if (
  147. /.*(code|captcha|验证码|login|点击|verify|yzm|yanzhengma|滑块|拖动|拼图|yidun|slide).*/im.test(
  148. checkList[x].toString().toLowerCase()
  149. ) ||
  150. tagName === "img" ||
  151. tagName === "iframe"
  152. ) {
  153. if (!this.checkTimer) {
  154. this.checkTimer = setTimeout(() => {
  155. this.doCheckTask();
  156. }, 0);
  157. } else {
  158. window.clearTimeout(this.checkTimer);
  159. this.checkTimer = setTimeout(() => {
  160. this.doCheckTask();
  161. }, 2000);
  162. }
  163. return;
  164. }
  165. }
  166. }
  167. });
  168. Observer.observe(body, {
  169. childList: true,
  170. subtree: true,
  171. attributes: true,
  172. });
  173. }
  174. dataURLtoFile(dataURL, filename = "captcha.jpg") {
  175. // base64转图片文件
  176. var arr = dataURL.split(","),
  177. mime =
  178. (arr[0].match(/:(.*?);/) && arr[0].match(/:(.*?);/)[1]) ||
  179. "image/png",
  180. bstr = atob(arr[1]),
  181. n = bstr.length,
  182. u8arr = new Uint8Array(n);
  183. while (n--) {
  184. u8arr[n] = bstr.charCodeAt(n);
  185. }
  186. return new File([u8arr], filename, { type: mime });
  187. }
  188. async getRecommendPath() {
  189. let requestUrl =
  190. "http://101.43.206.185:7000/cssPath?href=" +
  191. location.href.split("?")[0];
  192. try {
  193. GM_xmlhttpRequest({
  194. method: "get",
  195. url: requestUrl,
  196. onload: async (res) => {
  197. if (res.status === 200 && res.response) {
  198. let data = (res.response && JSON.parse(res.response)) || {};
  199. const { path, recommendTimes = 0 } = data;
  200. if (path && recommendTimes) {
  201. let inputSelector = path.split("$$")[0];
  202. let imgSelector = path.split("$$")[1];
  203. if (
  204. selector(inputSelector) &&
  205. selector(imgSelector) &&
  206. selector(imgSelector).getAttribute("src") &&
  207. selector(inputSelector).getAttribute("type") === "text"
  208. ) {
  209. let dataURL = await this.handleImg(selector(imgSelector));
  210. try {
  211. if (!this.hasRequest(dataURL, { record: true })) {
  212. let code = await this.request(
  213. this.dataURLtoFile(dataURL),
  214. this.cssPath(selector(inputSelector)) +
  215. "$$" +
  216. this.cssPath(selector(imgSelector)),
  217. selector(imgSelector).getAttribute("src")
  218. );
  219. if (code) {
  220. selector(inputSelector).value = code;
  221. if (typeof Vue !== "undefined") {
  222. new Vue().$message.success("获取验证码成功");
  223. }
  224. console.log("正在使用共享验证码功能获取验证码");
  225. } else {
  226. console.error("验证码为空,请检查图片是否正确");
  227. }
  228. }
  229. } catch (error) {
  230. console.log(error);
  231. // if (typeof Vue !== "undefined") {
  232. // new Vue().$message.error("获取验证码失败");
  233. // }
  234. }
  235. }
  236. }
  237. }
  238. },
  239. onerror: function (err) {
  240. console.log("推荐路径请求失败:" + err);
  241. },
  242. });
  243. } catch (error) {
  244. console.log(error);
  245. }
  246. }
  247. getCaptchaFeature(el) {
  248. // 获取验证码特征
  249. let checkList = [];
  250. checkList.push(el.getAttribute("id"));
  251. checkList.push(el.className);
  252. checkList.push(el.getAttribute("alt"));
  253. checkList.push(el.getAttribute("src"));
  254. checkList.push(el.getAttribute("name"));
  255.  
  256. return checkList;
  257. }
  258. cssPath = (el) => {
  259. // 获取元素css path
  260. if (!(el instanceof Element)) return;
  261. var path = [];
  262. while (el.nodeType === Node.ELEMENT_NODE) {
  263. var selector = el.nodeName.toLowerCase();
  264. if (el.id) {
  265. selector += "#" + el.id;
  266. path.unshift(selector);
  267. break;
  268. } else {
  269. var sib = el,
  270. nth = 1;
  271. while ((sib = sib.previousElementSibling)) {
  272. if (sib.nodeName.toLowerCase() == selector) nth++;
  273. }
  274. if (nth != 1) selector += ":nth-of-type(" + nth + ")";
  275. }
  276. path.unshift(selector);
  277. el = el.parentNode;
  278. }
  279. return path.join(" > ");
  280. };
  281.  
  282. manualLocateCaptcha() {
  283. let imgs = [];
  284. let inputTags = [];
  285. let cssPathStore = {};
  286. let finish = false;
  287. this.vue = new Vue();
  288. this.isIframe = top !== self;
  289. var onTagClick = (e) => {
  290. let el = e.target;
  291. let tagName = el.tagName;
  292. if (tagName.toLowerCase() === "input") {
  293. let type = el.getAttribute("type");
  294. if (type && type !== "text") {
  295. this.vue.$message.error(
  296. "提醒:当前点击输入框type=" + type + ",请选择文本输入框"
  297. );
  298. } else {
  299. cssPathStore.input = this.cssPath(el);
  300. this.vue.$message.success("您已成功选择输入框");
  301. }
  302. } else {
  303. cssPathStore.img = this.cssPath(el);
  304. this.vue.$message.success("您已成功选择验证码图片");
  305. }
  306. if (cssPathStore.input && cssPathStore.img) {
  307. GM_setValue(NORMAL_STORE_KEY, JSON.stringify(cssPathStore));
  308. imgs.forEach((img) => {
  309. img && img.removeEventListener("click", onTagClick);
  310. }, false);
  311. inputTags.forEach((input) => {
  312. input.removeEventListener("click", onTagClick);
  313. }, false);
  314. setTimeout(() => {
  315. this.vue.$message.success("选择完毕,赶快试试吧");
  316. captchaInstance.doCheckTask();
  317. }, 3000);
  318. finish = true;
  319. }
  320. };
  321. var onMenuClick = (e) => {
  322. if (this.isIframe) {
  323. alert("当前脚本处于iframe中,暂不支持该操作,快让作者优化吧");
  324. return;
  325. }
  326. finish = false;
  327. cssPathStore = {};
  328. GM_deleteValue(NORMAL_STORE_KEY);
  329. this.vue.$alert("接下来请点击验证码图片和输入框", "操作提示", {
  330. confirmButtonText: "确定",
  331. callback: () => {
  332. setTimeout(() => {
  333. imgs.forEach((img) => {
  334. img && img.removeEventListener("click", onTagClick);
  335. }, false);
  336. inputTags.forEach((input) => {
  337. input.removeEventListener("click", onTagClick);
  338. }, false);
  339. if (!finish) {
  340. this.vue.$notify.success({
  341. title: "提示",
  342. message: "已退出手动选择验证码模式。",
  343. offset: 100,
  344. });
  345. }
  346. }, 20000);
  347. },
  348. });
  349.  
  350. // alert("请点击验证码和输入框各一次。");
  351. imgs = [...selectorAll("img")];
  352. inputTags = [...selectorAll("input")];
  353. imgs.forEach((img) => {
  354. img.addEventListener("click", onTagClick);
  355. }, false);
  356. inputTags.forEach((input) => {
  357. input.addEventListener("click", onTagClick);
  358. }, false);
  359. };
  360. GM_registerMenuCommand("手动选择验证码和输入框", onMenuClick);
  361. }
  362. handleImg(img) {
  363. return new Promise((resolve, reject) => {
  364. try {
  365. // 图片没设置跨域,可采用图片转canvas转base64的方式
  366. let dataURL = null;
  367.  
  368. const action = () => {
  369. let canvas = document.createElement("canvas");
  370. canvas.width = img.naturalWidth;
  371. canvas.height = img.naturalHeight;
  372. let ctx = canvas.getContext("2d");
  373. ctx.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight);
  374. dataURL = canvas.toDataURL("image/png");
  375. resolve(dataURL);
  376. };
  377. if (!img.src.includes(";base64,")) {
  378. img.onload = function () {
  379. action();
  380. };
  381. if (img.complete) {
  382. action();
  383. } else {
  384. img.onload = function () {
  385. action();
  386. };
  387. }
  388. } else {
  389. dataURL = img.src;
  390. resolve(dataURL);
  391. }
  392. } catch (error) {
  393. console.error("error:" + error);
  394. // 这块处理比较复杂,待优化
  395. // 图片设置跨域,重新请求图片内容后转base64,相当于替用户点击了“换一张图片”
  396. // if (this.times >= 1) {
  397. // return;
  398. // }
  399. // if (typeof Vue !== "undefined") {
  400. // new Vue().$notify.success({
  401. // title: "温馨提示",
  402. // message: "当前验证码结果可能和图片显示不一致,请放心提交。",
  403. // offset: 100,
  404. // });
  405. // }
  406.  
  407. // this.times++;
  408. // GM_xmlhttpRequest({
  409. // method: "get",
  410. // url: img.src,
  411. // responseType: "blob",
  412. // onload: (res) => {
  413. // if (res.status === 200) {
  414. // let blob = res.response;
  415. // let fileReader = new FileReader();
  416. // fileReader.onloadend = (e) => {
  417. // let base64 = e.target.result;
  418. // resolve(base64);
  419. // };
  420. // fileReader.readAsDataURL(blob);
  421. // } else {
  422. // console.log("图片转换blob失败");
  423. // console.log(res);
  424. // reject();
  425. // }
  426. // },
  427. // onerror: function(err) {
  428. // console.log("图片请求失败:" + err);
  429. // reject();
  430. // },
  431. // });
  432. }
  433. });
  434. }
  435. hasRequest(dataURL, config = {}) {
  436. let startIndex = config.type === "url" ? 0 : dataURL.length - 100;
  437. let imgClips = dataURL.slice(startIndex, dataURL.length);
  438. if (this.imgCache.includes(imgClips)) {
  439. return true;
  440. }
  441. if (config.record) {
  442. this.imgCache.push(imgClips);
  443. }
  444. return false;
  445. }
  446. request(file, path, src) {
  447. try {
  448. if (!file) {
  449. console.error("缺少file参数");
  450. return Promise.reject();
  451. }
  452.  
  453. return new Promise((resolve, reject) => {
  454. let host = location.href;
  455. let href = location.href.split("?")[0].split("#")[0];
  456. if (self === top) {
  457. host = location.host;
  458. }
  459. let formData = new FormData();
  460. let detail = {
  461. path,
  462. src,
  463. host,
  464. href,
  465. };
  466. formData.append("img", file);
  467. formData.append("detail", JSON.stringify(detail));
  468. // let requestUrl = "http://192.168.31.184:7000/captcha";
  469. let requestUrl = "http://101.43.206.185:7000/captcha";
  470. GM_xmlhttpRequest({
  471. method: "post",
  472. url: requestUrl,
  473. data: formData,
  474. onload: function (response) {
  475. if (response.status === -1) {
  476. console.error("获取验证码失败:" + response);
  477. reject();
  478. } else {
  479. let data = response.response;
  480. if (data.length < 50) {
  481. data = JSON.parse(data);
  482. if (data.code) {
  483. resolve(data.code);
  484. } else {
  485. let date = new Date().getDate();
  486. let tipsConfig = {
  487. date,
  488. times: 1,
  489. };
  490. let cache =
  491. GM_getValue("tipsConfig") &&
  492. JSON.parse(GM_getValue("tipsConfig"));
  493. if (cache && cache.times > 3) {
  494. } else {
  495. if (!cache) {
  496. GM_setValue("tipsConfig", JSON.stringify(tipsConfig));
  497. } else {
  498. cache.times = cache.times + 1;
  499. GM_setValue("tipsConfig", JSON.stringify(cache));
  500. }
  501. if (typeof Vue !== "undefined") {
  502. new Vue().$message.error(data.msg);
  503. }
  504. }
  505.  
  506. console.error("获取验证码失败:" , data);
  507. reject();
  508. }
  509. } else {
  510. console.error("获取验证码失败:", response);
  511. console.dir(data);
  512. reject();
  513. }
  514. }
  515. },
  516. onerror: function (err) {
  517. console.error(err);
  518. reject();
  519. },
  520. });
  521. });
  522. } catch (error) {
  523. console.log(error);
  524. }
  525. }
  526. async findCaptcha() {
  527. // 先读取用户手动设置的验证码配置
  528. let cache = GM_getValue(NORMAL_STORE_KEY);
  529. let captchaPath = cache && JSON.parse(cache);
  530. if (
  531. captchaPath &&
  532. captchaPath.input &&
  533. captchaPath.img &&
  534. selector(captchaPath.input) &&
  535. selector(captchaPath.img)
  536. ) {
  537. let dataURL = await this.handleImg(selector(captchaPath.img));
  538. try {
  539. if (!this.hasRequest(dataURL, { record: true })) {
  540. let code = await this.request(
  541. this.dataURLtoFile(dataURL),
  542. this.cssPath(selector(captchaPath.input)) +
  543. "$$" +
  544. this.cssPath(selector(captchaPath.img)),
  545. selector(captchaPath.img).getAttribute("src")
  546. );
  547. if (code) {
  548. selector(captchaPath.input).value = code.trim();
  549. console.log("正在使用用户自定义验证码位置数据获取验证码");
  550. return;
  551. } else {
  552. console.error("验证码为空,请检查图片是否正确");
  553. }
  554. }
  555. } catch (error) {
  556. console.log(error);
  557. }
  558. return;
  559. }
  560. // 自动寻找验证码和输入框
  561. let captchaMap = [];
  562. let imgs = [...selectorAll("img")];
  563. imgs.forEach((img) => {
  564. let checkList = [
  565. ...this.getCaptchaFeature(img),
  566. ...this.getCaptchaFeature(img.parentNode),
  567. ];
  568. checkList = checkList.filter((item) => item);
  569. let isInvalid =
  570. ["#", "about:blank"].includes(img.getAttribute("src")) ||
  571. !img.getAttribute("src");
  572.  
  573. for (let i = 0; i < checkList.length; i++) {
  574. if (
  575. /.*(code|captcha|验证码|login|点击|verify|yzm|yanzhengma).*/im.test(
  576. checkList[i].toLowerCase()
  577. ) &&
  578. img.width > 30 &&
  579. img.width < 150 &&
  580. img.height < 80 &&
  581. !isInvalid
  582. ) {
  583. captchaMap.push({ img: img, input: null });
  584. break;
  585. }
  586. }
  587. });
  588. captchaMap.forEach((item) => {
  589. let imgEle = item.img;
  590. let parentNode = imgEle.parentNode;
  591. for (let i = 0; i < 4; i++) {
  592. // 以当前可能是验证码的图片为基点,向上遍历四层查找可能的Input输入框
  593. if (!parentNode) {
  594. return;
  595. }
  596. let inputTags = [...parentNode.querySelectorAll("input")];
  597. if (inputTags.length) {
  598. let input = inputTags.pop();
  599. let type = input.getAttribute("type");
  600. while (type !== "text" && inputTags.length) {
  601. if (type === "password") {
  602. break;
  603. }
  604. input = inputTags.pop();
  605. type = input.getAttribute("type");
  606. }
  607. let inputWidth = getStyle(input).width.replace(/[^0-9]/gi, "");
  608. // let inputHeight = getStyle(input).height.replace(/[^0-9]/gi, "");
  609. if (!type || (type === "text" && inputWidth > 50)) {
  610. // 兼容各种奇葩情况
  611. item.input = input;
  612. break;
  613. }
  614. if (type === "password") {
  615. // 验证码一般在密码框后面,遍历到密码框了就大概率说明没有验证码
  616. break;
  617. }
  618. }
  619. parentNode = parentNode.parentNode;
  620. }
  621. });
  622. // console.log(captchaMap);
  623. if (!captchaMap.length) {
  624. const { path, recommendTimes } = this.recommendPath;
  625. if (path) {
  626. let inputSelector = path.split("$$")[0];
  627. let imgSelector = path.split("$$")[1];
  628. if (selector(inputSelector) && selector(imgSelector)) {
  629. let dataURL = await this.handleImg(selector(imgSelector));
  630. try {
  631. if (!this.hasRequest(dataURL, { record: true })) {
  632. selector(inputSelector).value = await this.request(
  633. this.dataURLtoFile(dataURL),
  634. path,
  635. item.img.getAttribute("src")
  636. );
  637. if (typeof Vue !== "undefined") {
  638. new Vue().$message.success("获取验证码成功");
  639. }
  640. }
  641. } catch (error) {
  642. console.log(error);
  643. // if (typeof Vue !== "undefined") {
  644. // new Vue().$message.error("获取验证码失败");
  645. // }
  646. }
  647. }
  648. }
  649. }
  650. captchaMap = captchaMap.filter((item) => item.input);
  651. captchaMap.forEach(async (item, index) => {
  652. let dataURL = await this.handleImg(item.img);
  653. try {
  654. if (!this.hasRequest(dataURL, { record: true })) {
  655. let code = await this.request(
  656. this.dataURLtoFile(dataURL),
  657. this.cssPath(item.input) + "$$" + this.cssPath(item.img),
  658. item.img.getAttribute("src")
  659. );
  660. if (code) {
  661. item.input.value = code;
  662. if (typeof Vue !== "undefined") {
  663. new Vue().$message.success("获取验证码成功");
  664. }
  665. console.log("正在使用自动寻找验证码功能获取验证码");
  666. } else {
  667. if (index === captchaMap.length - 1) {
  668. this.getRecommendPath();
  669. }
  670. console.error("验证码为空,请检查图片是否正确");
  671. }
  672. }
  673. } catch (error) {
  674. if (index === captchaMap.length - 1) {
  675. this.getRecommendPath();
  676. }
  677. console.log(error);
  678. // if (typeof Vue !== "undefined") {
  679. // new Vue().$message.error("获取验证码失败");
  680. // }
  681. }
  682. });
  683. }
  684. getImgViaBlob(url) {
  685. return new Promise((resolve, reject) => {
  686. try {
  687. GM_xmlhttpRequest({
  688. method: "get",
  689. url,
  690. responseType: "blob",
  691. onload: (res) => {
  692. if (res.status === 200) {
  693. let blob = res.response;
  694. let fileReader = new FileReader();
  695. fileReader.onloadend = (e) => {
  696. let base64 = e.target.result;
  697. if (base64.length > 20) {
  698. resolve(base64);
  699. } else {
  700. alert(
  701. "验证码助手:当前网站验证码图片禁止跨域访问,待作者优化。"
  702. );
  703. handleClearMenuClick();
  704. reject("base64图片长度不够");
  705. throw "getImgViaBlob: base64图片长度不够";
  706. }
  707. };
  708. fileReader.readAsDataURL(blob);
  709. } else {
  710. console.log("图片转换blob失败");
  711. console.log(res);
  712. reject();
  713. }
  714. },
  715. onerror: function (err) {
  716. console.log("图片请求失败:" + err);
  717. reject();
  718. },
  719. });
  720. } catch (error) {
  721. console.log(error);
  722. reject();
  723. }
  724. });
  725. }
  726. elDisplay(el) {
  727. if (!el) {
  728. return false;
  729. }
  730.  
  731. while (el) {
  732. if (!(el instanceof Element)) {
  733. return true;
  734. }
  735. if (getStyle(el).display === "none") {
  736. return false;
  737. }
  738. el = el.parentNode;
  739. }
  740. return true;
  741. }
  742. checkSlideCaptcha() {
  743. const check = async () => {
  744. const slideCache =
  745. (GM_getValue(SLIDE_STORE_KEY) &&
  746. JSON.parse(GM_getValue(SLIDE_STORE_KEY))) ||
  747. {};
  748. const { bgImg, targetImg, moveItem } = slideCache;
  749. if (
  750. bgImg &&
  751. targetImg &&
  752. moveItem &&
  753. selector(targetImg) &&
  754. selector(bgImg) &&
  755. selector(moveItem) &&
  756. this.elDisplay(selector(targetImg)) &&
  757. this.elDisplay(selector(bgImg)) &&
  758. this.elDisplay(selector(moveItem))
  759. ) {
  760. const target_url =
  761. selector(targetImg).getAttribute("src") ||
  762. getStyle(selector(targetImg))["background-image"].split('"')[1];
  763. const bg_url =
  764. selector(bgImg).getAttribute("src") ||
  765. getStyle(selector(bgImg))["background-image"].split('"')[1];
  766. if (!this.hasRequest(target_url, { record: true, type: "url" })) {
  767. const target_base64 = await this.getImgViaBlob(target_url);
  768. const bg_base64 = await this.getImgViaBlob(bg_url);
  769. return new Promise(async (resolve, reject) => {
  770. let host = location.href;
  771. let href = location.href.split("?")[0].split("#")[0];
  772. if (self === top) {
  773. host = location.host;
  774. }
  775. let detail = {
  776. path: slideCache,
  777. host,
  778. href,
  779. };
  780. let formData = new FormData();
  781. let requestUrl = "http://101.43.206.185:7000/slideCaptcha";
  782. let targetWidth = getNumber(getStyle(selector(targetImg)).width);
  783. let bgWidth = getNumber(getStyle(selector(bgImg)).width);
  784. formData.append("target_img", this.dataURLtoFile(target_base64));
  785. formData.append("bg_img", this.dataURLtoFile(bg_base64));
  786. formData.append("targetWidth", targetWidth);
  787. formData.append("bgWidth", bgWidth);
  788. formData.append("detail", JSON.stringify(detail));
  789. GM_xmlhttpRequest({
  790. method: "post",
  791. url: requestUrl,
  792. data: formData,
  793. onload: (response) => {
  794. const data = JSON.parse(response.response);
  795. this.moveSideCaptcha(
  796. selector(targetImg),
  797. selector(moveItem),
  798. data.result.target[0]
  799. );
  800. // resolve()
  801. },
  802. onerror: function (err) {
  803. console.error(err);
  804. reject();
  805. },
  806. });
  807. });
  808. }
  809. }
  810. };
  811. check();
  812. // const interval = 3000;
  813. // simulateInterval(check, interval);
  814. }
  815. moveSideCaptcha(targetImg, moveItem, distance) {
  816. if (distance === 0) {
  817. console.log("distance", distance);
  818. return;
  819. }
  820. var btn = moveItem;
  821. let target = targetImg;
  822.  
  823. let varible = null;
  824. let targetLeft = Number(getStyle(target).left.replace("px", "")) || 0;
  825. let targetParentLeft =
  826. Number(getStyle(target.parentNode).left.replace("px", "")) || 0;
  827. let targetTransform = Number(getEleTransform(target)) || 0;
  828. let targetParentTransform =
  829. Number(getEleTransform(target.parentNode)) || 0;
  830.  
  831. var mousedown = document.createEvent("MouseEvents");
  832. var rect = btn.getBoundingClientRect();
  833. var x = rect.x;
  834. var y = rect.y;
  835. mousedown.initMouseEvent(
  836. "mousedown",
  837. true,
  838. true,
  839. document.defaultView,
  840. 0,
  841. x,
  842. y,
  843. x,
  844. y,
  845. false,
  846. false,
  847. false,
  848. false,
  849. 0,
  850. null
  851. );
  852. btn.dispatchEvent(mousedown);
  853.  
  854. var dx = 0;
  855. var dy = 0;
  856. var interval = setInterval(function () {
  857. var mousemove = document.createEvent("MouseEvents");
  858. var _x = x + dx;
  859. var _y = y + dy;
  860. mousemove.initMouseEvent(
  861. "mousemove",
  862. true,
  863. true,
  864. document.defaultView,
  865. 0,
  866. _x,
  867. _y,
  868. _x,
  869. _y,
  870. false,
  871. false,
  872. false,
  873. false,
  874. 0,
  875. null
  876. );
  877. btn.dispatchEvent(mousemove);
  878. btn.dispatchEvent(mousemove);
  879.  
  880. let newTargetLeft =
  881. Number(getStyle(target).left.replace("px", "")) || 0;
  882. let newTargetParentLeft =
  883. Number(getStyle(target.parentNode).left.replace("px", "")) || 0;
  884. let newTargetTransform = Number(getEleTransform(target)) || 0;
  885. let newTargetParentTransform =
  886. Number(getEleTransform(target.parentNode)) || 0;
  887.  
  888. if (newTargetLeft !== targetLeft) {
  889. varible = newTargetLeft;
  890. } else if (newTargetParentLeft !== targetParentLeft) {
  891. varible = newTargetParentLeft;
  892. } else if (newTargetTransform !== targetTransform) {
  893. varible = newTargetTransform;
  894. } else if (newTargetParentTransform != targetParentTransform) {
  895. varible = newTargetParentTransform;
  896. }
  897. if (varible >= distance) {
  898. clearInterval(interval);
  899. var mouseup = document.createEvent("MouseEvents");
  900. mouseup.initMouseEvent(
  901. "mouseup",
  902. true,
  903. true,
  904. document.defaultView,
  905. 0,
  906. _x,
  907. _y,
  908. _x,
  909. _y,
  910. false,
  911. false,
  912. false,
  913. false,
  914. 0,
  915. null
  916. );
  917. setTimeout(() => {
  918. btn.dispatchEvent(mouseup);
  919. }, Math.ceil(Math.random() * 2000));
  920. } else {
  921. if (dx >= distance - 20) {
  922. dx += Math.ceil(Math.random() * 2);
  923. } else {
  924. dx += Math.ceil(Math.random() * 10);
  925. }
  926. let sign = Math.random() > 0.5 ? -1 : 1;
  927. dy += Math.ceil(Math.random() * 3 * sign);
  928. }
  929. }, 10);
  930. setTimeout(() => {
  931. clearInterval(interval);
  932. }, 10000);
  933. }
  934. }
  935.  
  936. function getEleCssPath(el) {
  937. // 获取元素css path
  938. if (!(el instanceof Element)) return;
  939. var path = [];
  940. while (el.nodeType === Node.ELEMENT_NODE) {
  941. var selector = el.nodeName.toLowerCase();
  942. if (el.id) {
  943. selector += "#" + el.id;
  944. path.unshift(selector);
  945. break;
  946. } else {
  947. var sib = el,
  948. nth = 1;
  949. while ((sib = sib.previousElementSibling)) {
  950. if (sib.nodeName.toLowerCase() == selector) nth++;
  951. }
  952. if (nth != 1) selector += ":nth-of-type(" + nth + ")";
  953. }
  954. path.unshift(selector);
  955. el = el.parentNode;
  956. }
  957. return path.join(" > ");
  958. }
  959.  
  960. function handleSlideMenuClick({ isPostmessage } = {}) {
  961. if (top === self) {
  962. alert("请点击滑动验证码的大图片,小图片,滑块。");
  963. }
  964. this.vue = new Vue();
  965. this.isIframe = top !== self;
  966. GM_deleteValue(SLIDE_STORE_KEY);
  967.  
  968. let imgs = [...selectorAll("img")];
  969. let divTags = [...selectorAll("div")];
  970. imgs.forEach((img) => {
  971. img.addEventListener("click", onSlideTagClick);
  972. }, false);
  973. divTags.forEach((input) => {
  974. input.addEventListener("click", onSlideTagClick);
  975. }, false);
  976.  
  977. setTimeout(() => {
  978. imgs.forEach((img) => {
  979. img && img.removeEventListener("click", onSlideTagClick);
  980. }, false);
  981. divTags.forEach((input) => {
  982. input.removeEventListener("click", onSlideTagClick);
  983. }, false);
  984. }, 30000);
  985.  
  986. if (!isPostmessage) {
  987. if (self === top) {
  988. const iframes = [...selectorAll("iframe")];
  989. iframes.forEach((iframe) => {
  990. iframe.contentWindow.postMessage(
  991. {
  992. sign: "husky",
  993. action: "handleSlideMenuClick",
  994. },
  995. "*"
  996. );
  997. });
  998. } else {
  999. window.postMessage(
  1000. {
  1001. sign: "husky",
  1002. action: "handleSlideMenuClick",
  1003. },
  1004. "*"
  1005. );
  1006. }
  1007. }
  1008. }
  1009.  
  1010. let noticeTimer = 0;
  1011.  
  1012. function notice(msg) {
  1013. if (noticeTimer) {
  1014. clearTimeout(noticeTimer);
  1015. } else {
  1016. setTimeout(() => new Vue().$message.success(msg));
  1017. }
  1018. noticeTimer = setTimeout(() => new Vue().$message.success(msg), 1000);
  1019. }
  1020.  
  1021. var onSlideTagClick = (e) => {
  1022. let el = e.target;
  1023. let tagName = el.tagName.toLowerCase();
  1024. let width = Number(getNumber(getStyle(el).width)) || 0;
  1025. const vue = new Vue();
  1026. let height = Number(getNumber(getStyle(el).height)) || 0;
  1027. let position = getStyle(el).position;
  1028. let pathCache =
  1029. (GM_getValue(SLIDE_STORE_KEY) &&
  1030. JSON.parse(GM_getValue(SLIDE_STORE_KEY))) ||
  1031. {};
  1032. if (tagName === "img") {
  1033. if (width >= height && width > 150) {
  1034. let newValue = { ...pathCache, bgImg: getEleCssPath(el) };
  1035. GM_setValue(SLIDE_STORE_KEY, JSON.stringify(newValue));
  1036. pathCache = newValue;
  1037. notice("您已成功选择大图片");
  1038. } else if (width < 100 && height >= width - 5) {
  1039. let newValue = { ...pathCache, targetImg: getEleCssPath(el) };
  1040. GM_setValue(SLIDE_STORE_KEY, JSON.stringify(newValue));
  1041. pathCache = newValue;
  1042. notice("您已成功选择小图片");
  1043. }
  1044. } else {
  1045. let curEl = el;
  1046. for (let i = 0; i < 3; i++) {
  1047. if (!curEl || curEl === Window) {
  1048. break;
  1049. }
  1050. position = getStyle(curEl).position;
  1051. let bgUrl = getStyle(curEl)["backgroundImage"];
  1052. width = Number(getNumber(getStyle(curEl).width)) || 0;
  1053. height = Number(getNumber(getStyle(curEl).height)) || 0;
  1054.  
  1055. if (position === "absolute" && width < 100 && height < 100) {
  1056. let newValue = { ...pathCache, moveItem: getEleCssPath(curEl) };
  1057. GM_setValue(SLIDE_STORE_KEY, JSON.stringify(newValue));
  1058. pathCache = newValue;
  1059. notice("您已成功选择滑块");
  1060. break;
  1061. }
  1062. let reg = /url\("(.+)"\)/im;
  1063.  
  1064. if (bgUrl && bgUrl.match(reg)) {
  1065. if (width >= height && width > 150) {
  1066. let newValue = { ...pathCache, bgImg: getEleCssPath(curEl) };
  1067. GM_setValue(SLIDE_STORE_KEY, JSON.stringify(newValue));
  1068. pathCache = newValue;
  1069. notice("您已成功选择大图片");
  1070. break;
  1071. } else if (width < 100 && height >= width - 5) {
  1072. let newValue = { ...pathCache, targetImg: getEleCssPath(curEl) };
  1073. GM_setValue(SLIDE_STORE_KEY, JSON.stringify(newValue));
  1074. pathCache = newValue;
  1075. notice("您已成功选择小图片");
  1076. break;
  1077. }
  1078. }
  1079. curEl = curEl.parentNode;
  1080. }
  1081.  
  1082. curEl = el;
  1083. const firstImg = curEl.querySelector("img");
  1084. firstImg && onSlideTagClick({ target: firstImg });
  1085. }
  1086.  
  1087. const finish = Object.keys(pathCache).filter((item) => item).length == 3;
  1088. if (finish) {
  1089. let imgs = [...selectorAll("img")];
  1090. let divTags = [...selectorAll("div")];
  1091. imgs.forEach((img) => {
  1092. img && img.removeEventListener("click", onSlideTagClick);
  1093. }, false);
  1094. divTags.forEach((div) => {
  1095. div.removeEventListener("click", onSlideTagClick);
  1096. }, false);
  1097. setTimeout(() => {
  1098. vue.$message.success("选择完毕,赶快试试吧");
  1099. captchaInstance.doCheckTask();
  1100. }, 3000);
  1101. }
  1102. };
  1103.  
  1104. GM_registerMenuCommand("手动定位滑动验证码", handleSlideMenuClick);
  1105.  
  1106. function handleClearMenuClick() {
  1107. GM_listValues().forEach((name) => {
  1108. if (name.includes("husky")) {
  1109. GM_deleteValue(name);
  1110. }
  1111. });
  1112. }
  1113.  
  1114. GM_registerMenuCommand("清空所有验证码配置", handleClearMenuClick);
  1115.  
  1116. function cleanCurrentPage() {
  1117. GM_deleteValue(SLIDE_STORE_KEY);
  1118. GM_deleteValue(NORMAL_STORE_KEY);
  1119. }
  1120. GM_registerMenuCommand("清空当前页面验证码配置", cleanCurrentPage);
  1121.  
  1122. let blackListMenuId = null;
  1123.  
  1124. function blackListCheck() {
  1125. let key = location.host + location.pathname + "_black";
  1126. let data = GM_getValue(key) && JSON.parse(GM_getValue(key));
  1127. if (blackListMenuId) {
  1128. GM_unregisterMenuCommand(blackListMenuId);
  1129. }
  1130. if (data) {
  1131. blackListMenuId = GM_registerMenuCommand(
  1132. "标记当前网站有验证码",
  1133. labelWebsite
  1134. );
  1135. } else {
  1136. blackListMenuId = GM_registerMenuCommand(
  1137. "标记当前网站没有验证码",
  1138. labelWebsite
  1139. );
  1140. }
  1141. return data;
  1142. }
  1143.  
  1144. function labelWebsite() {
  1145. let key = location.host + location.pathname + "_black";
  1146. let data = GM_getValue(key) && JSON.parse(GM_getValue(key));
  1147. if (data) {
  1148. GM_setValue(key, "false");
  1149. } else {
  1150. GM_setValue(key, "true");
  1151. }
  1152. notice(
  1153. "操作成功," + (data ? "已标记网站有验证码" : "已标记网站没有验证码")
  1154. );
  1155. if (data) {
  1156. captchaInstance = captchaInstance || new Captcha();
  1157. captchaInstance.init();
  1158. }
  1159. blackListCheck();
  1160. }
  1161. blackListCheck();
  1162.  
  1163. var captchaInstance = null;
  1164.  
  1165. function main() {
  1166. window.addEventListener("DOMContentLoaded", function () {
  1167. init();
  1168. captchaInstance = new Captcha();
  1169. });
  1170. }
  1171.  
  1172. const actions = {
  1173. handleSlideMenuClick: handleSlideMenuClick,
  1174. };
  1175.  
  1176. window.addEventListener(
  1177. "message",
  1178. (event) => {
  1179. const { data = {} } = event || {};
  1180. const { sign, action } = data;
  1181. if (sign === "husky") {
  1182. if (action && actions[action]) {
  1183. actions[action]({ isPostmessage: true });
  1184. }
  1185. }
  1186. },
  1187. false
  1188. );
  1189. main();
  1190. })();

QingJ © 2025

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