🏠 Home 

网购查券小帮手,不花冤枉钱,可自动查询淘宝、天猫、京东、唯品会、天猫超市等隐藏的大额优惠券和优惠活动。脚本持续维护更新~

网购查券小帮手,不花冤枉钱!!!支持平台:京东、淘宝、天猫、天猫超市、天猫国际、京东国际、京东图书、京东大药房、阿里大药房、唯品会等;功能:1、搜索商品时会自动查询标注有优惠券和活动的商品,无需进入详情页,方便快捷;2、浏览商品详情页时脚本会自动查询商品是否有隐藏的优惠券;3、浏览记录标注(本地存储、可手动清空);4、网页显示优化;脚本长期维护更新,请放心使用~


安装此脚本?
  1. // ==UserScript==
  2. // @name 网购查券小帮手,不花冤枉钱,可自动查询淘宝、天猫、京东、唯品会、天猫超市等隐藏的大额优惠券和优惠活动。脚本持续维护更新~
  3. // @name:zh 网购查券小帮手,不花冤枉钱,可自动查询淘宝、天猫、京东、唯品会、天猫超市等隐藏的大额优惠券和优惠活动。脚本持续维护更新~
  4. // @name:zh-TW 網購查券小幫手,不花冤枉錢,可自動查詢淘寶、天貓、京東、唯品會、天貓超市等隱藏的大額優惠券和優惠活動。指令碼或直譯式程式持續維護更新~
  5. // @description 网购查券小帮手,不花冤枉钱!!!支持平台:京东、淘宝、天猫、天猫超市、天猫国际、京东国际、京东图书、京东大药房、阿里大药房、唯品会等;功能:1、搜索商品时会自动查询标注有优惠券和活动的商品,无需进入详情页,方便快捷;2、浏览商品详情页时脚本会自动查询商品是否有隐藏的优惠券;3、浏览记录标注(本地存储、可手动清空);4、网页显示优化;脚本长期维护更新,请放心使用~
  6. // @description:zh 网购查券小帮手,不花冤枉钱!!!支持平台:京东、淘宝、天猫、天猫超市、天猫国际、京东国际、京东图书、京东大药房、阿里大药房、唯品会等;功能:1、搜索商品时会自动查询标注有优惠券和活动的商品,无需进入详情页,方便快捷;2、浏览商品详情页时脚本会自动查询商品是否有隐藏的优惠券;3、浏览记录标注(本地存储、可手动清空);4、网页显示优化;脚本长期维护更新,请放心使用~
  7. // @description:zh-TW 網購查券小幫手,不花冤枉錢!!!支援平台:京東、淘寶、天貓、天貓超市、天貓國際、京東國際、京東圖書、京東大藥房、阿里大藥房、唯品會等;功能:1、搜索商品時會自動查詢標註有優惠券和活動的商品,無需進入詳情頁,方便快捷;2、瀏覽商品詳情頁時指令碼或直譯式程式會自動查詢商品是否有隱藏的優惠券;3、瀏覽記錄標註(本地存儲、可手動清空);4、網頁顯示優化;指令碼或直譯式程式長期維護更新,請放心使用~
  8. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAklJREFUWEfVl6tOA0EUhs+GOwkCX0hDQiEQHqBV9RTHxSCQaFBggAcAjQTJzdUQDEHQBDQkFEMC78CtZMnPcNqzw9xolzSM2U53uuc7//nP7DSK4zimNo7oXwHcPhDdPMSEKwZfJ7JqjutkNvq6ho4gBRDo6LwROOThs0WiuWLkXeoEaCawjBgCYQVA8K29dPy5sWQvixHgN8E7O1TOfT2N3PFd7UPN32tEL29ENggjwMKmP3MEQVAG8BX7+ZVobTGi8eHkyh8AMNvxuftxA/3hgeWTckNEK/NJYyYAXMHRWlMjEXV3+XJV98uXZhUBABAeCQCX9Aeb/paSaMvbZgBdhTqAz3gAqD4SlStuf5TyKkMbACClCnUAl/y93UT76xGVK3ZpOftSIaJS3g0wnSeaKShF6wDoed5apZQIDrfvrqYHIMvgBRgcUDgAwEAZXIMN5iqBEcBkQPQ4Wo4BELz6pOaQmefycy5DXg/IhOoKmABYfv6B9ACMtHOoDCk/h3jACGDygNxwUAJkjKCQEG5nANxjyUMAgj2gA7AHuM7sB9l2TQOY2pANmHYJjG1o2oj+CsC4ESFL3Qe2EmAtZJcl0MvjakNu6cRGhImuggRAbdFiIQOtansZSfl/AOgqNPvatUHqwY0AUgW5EYVk7lsjpee13iNZWiro5wAngPRDGirYghtLoMuI/eH0qvkjGJ8PbOUJ+mNychHT2bWvwo37vFXLo1dLAPxj9P3dY0z3329EfR8YzRCNDSXPfD7sIAV8D2nlftsBPgHDwZmwII4MigAAAABJRU5ErkJggg==
  9. // @namespace chaowantianxia_coupon_downloader
  10. // @version 1.0.7
  11. // @author 潮玩天下
  12. // @match *://*.taobao.com/*
  13. // @match *://*.tmall.com/*
  14. // @match *://*.tmall.hk/*
  15. // @match *://*.liangxinyao.com/*
  16. // @match *://chaoshi.detail.tmall.com/*
  17. // @match *://pages.tmall.com/wow/an/cs/search**
  18. // @match *://*.jd.com/*
  19. // @match *://*.jd.hk/*
  20. // @match *://item.jkcsjd.com/*
  21. // @match *://*.yiyaojd.com/*
  22. // @match *://www.vipglobal.hk
  23. // @match *://*.vip.com/*
  24. // @match *://detail.vip.com/detail-*
  25. // @match *://www.vipglobal.hk/detail-*
  26. // @match *://category.vip.com/suggest.php**
  27. // @match *://list.vip.com/*.html
  28. // @match *://*.suning.com/*
  29. // @exclude *://map.taobao.com/*
  30. // @exclude *://creator.guanghe.taobao.com/*
  31. // @exclude *://myseller.taobao.com/*
  32. // @exclude *://qn.taobao.com/*
  33. // @exclude *://jianghu.taobao.com/*
  34. // @exclude *://uland.taobao.com/*
  35. // @exclude *://jingfen.jd.com/*
  36. // @exclude *://passport.jd.com/*
  37. // @exclude *://jmw.jd.com/*
  38. // @exclude *://login.taobao.com/*
  39. // @exclude *://passport.shop.jd.com/*
  40. // @exclude *://huodong.taobao.com/wow/z/guang/gg_publish/*
  41. // @exclude *://passport.vip.com/*
  42. // @exclude *://passport.suning.com/*
  43. // @grant GM_openInTab
  44. // @grant GM.openInTab
  45. // @grant GM_getValue
  46. // @grant GM.getValue
  47. // @grant GM_setValue
  48. // @grant GM.setValue
  49. // @grant GM_addStyle
  50. // @grant GM_xmlhttpRequest
  51. // @grant GM.xmlHttpRequest
  52. // @grant GM_registerMenuCommand
  53. // @license AGPL License
  54. // @antifeature referral-link 【此提示为GreasyFork代码规范要求含有查券功能的脚本必须添加,请知悉!】
  55. // @charset UTF-8
  56. // @run-at document-idle
  57. // ==/UserScript==
  58. (function() {
  59. 'use strict';
  60. /**
  61. * AGPL 协议全称为 GNU Affero General Public License(GNU Affero 通用公共许可证),它是一种自由软件许可证。
  62. * AGPL 协议的主要特点是在传统的开源软件许可证(如 GPL)的基础上,加强了对软件在网络环境中使用的限制。
  63. * GPL 是 GNU General Public License(GNU 通用公共许可证)的缩写,本脚本遵循AGPL协议!满足其开源原则、Copyleft原则、附带源代码、允许收费等原则
  64. *
  65. * 脚本中部分工具类搜索自互联网,并没有明确的出处。因此在此说明!如有侵权请留言告知!以上~
  66. * 领券部分源码借鉴自:https://github.com/huahuacatTX/greasyfork/blob/main/pro_huahuacat_union.js
  67. * 特此声明!
  68. */
  69. const browsingLocalStorageKey = "browsing_history_local_storage_key";
  70. const details = ["detail.tmall.com", "item.taobao.com", "item.jd.com", "item.yiyaojd.com", "npcitem.jd.hk","detail.tmall.hk", "detail.vip.com", "item.jkcsjd.com", "product.suning.com"];
  71. const browsedHtml = `<div style="position:absolute;white-space: nowrap; top:7px;padding:2px 5px;font-size:11px;background-color:rgb(3,106,251);color:#FFF;z-index:9999999999999;border-radius:20px;right:10px;"><b>已浏览</b></div>`;
  72. function quickSort(arr) {
  73. if (arr.length <= 1) {
  74. return arr;
  75. }
  76. const pivot = arr[arr.length - 1], left = [], right = [];
  77. for (let i = 0; i < arr.length - 1; i++) {
  78. if (arr[i] < pivot) {
  79. left.push(arr[i]);
  80. } else {
  81. right.push(arr[i]);
  82. }
  83. }
  84. return [...quickSort(left), pivot,...quickSort(right)];
  85. }
  86. function obtainParameterBySuffix(url=window.location.href, suffix = "html"){
  87. if(url.indexOf("?")!=-1) url = url.split("?")[0];
  88. if(url.indexOf("#")!=-1) url = url.split("#")[0];
  89. const urls = url.split("/");
  90. return urls[urls.length-1].replace(`.${suffix}`, "");
  91. }
  92. function suningParameter(url){
  93. const regex = /product\.suning\.com\/(\d+\/\d+)\.html/;
  94. const match = url.match(regex);
  95. if(match){
  96. return match[1].replace(/\//g, '-');
  97. }
  98. return null;
  99. }
  100. //时间格式化
  101. function formatDate(date, format = 'YYYY-MM-DD HH:mm:ss') {
  102. const year = date.getFullYear();
  103. const month = String(date.getMonth() + 1).padStart(2, '0');
  104. const day = String(date.getDate()).padStart(2, '0');
  105. const hours = String(date.getHours()).padStart(2, '0');
  106. const minutes = String(date.getMinutes()).padStart(2, '0');
  107. const seconds = String(date.getSeconds()).padStart(2, '0');
  108. return format
  109. .replace('YYYY', year)
  110. .replace('MM', month)
  111. .replace('DD', day)
  112. .replace('HH', hours)
  113. .replace('mm', minutes)
  114. .replace('ss', seconds);
  115. }
  116. function isArray(obj) {
  117. return Array.isArray(obj);
  118. }
  119. function isObject(obj) {
  120. return typeof obj === 'object' && obj!== null &&!Array.isArray(obj);
  121. }
  122. function getSearchParameter(paramsString=window.location.href, tag) {
  123. if (paramsString.indexOf("?")!== -1) {
  124. paramsString = paramsString.split('?')[1];
  125. }
  126. return (new URLSearchParams(paramsString)).get(tag);
  127. }
  128. function randomInt(min, max) {
  129. return Math.floor(Math.random() * (max - min + 1)) + min;
  130. }
  131. //基于tampermonkey网络请求方法
  132. function gmRequest(method, url, param){
  133. if(!param){
  134. param = {};
  135. }
  136. return new Promise((resolve, reject)=> {
  137. GM_xmlhttpRequest({
  138. url: url,
  139. method: method,
  140. data: param,
  141. onload: function (response) {
  142. const status = response.status;
  143. if (status === 200 || status === "200") {
  144. const responseText = response.responseText;
  145. resolve({ "code": "ok", "r###lt": responseText });
  146. } else {
  147. reject({ "code": "exception", "r###lt": null });
  148. }
  149. }
  150. });
  151. });
  152. }
  153. //基于原生浏览器的跨域请求
  154. function crossRequest(method = "GET", url, param = {}) {
  155. if (!url) {
  156. return Promise.reject({ "code": "exception", "r###lt": null });
  157. }
  158. const config = { method: method.toUpperCase() };
  159. if (config.method === "POST") {
  160. config.headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
  161. config.body = new URLSearchParams(param).toString();
  162. }
  163. return fetch(url, config)
  164. .then(response => response.ok ? response.text() : Promise.reject(response.statusText))
  165. .then(r###lt => ({ "code": "ok", "r###lt": r###lt }))
  166. .catch(error => ({ "code": "exception", "r###lt": error }));
  167. }
  168. //同一的网络请求方法
  169. function makeRequest(method, url, param, isCrossOrigin=false) {
  170. if(isCrossOrigin){
  171. return crossRequest(method, url, param);
  172. }else{
  173. return gmRequest(method, url, param);
  174. }
  175. }
  176. //防抖函数
  177. function debounce(func, delay) {
  178. let timer;
  179. return function() {
  180. clearTimeout(timer);
  181. timer = setTimeout(() => {
  182. func.apply(this, arguments);
  183. }, delay);
  184. };
  185. }
  186. async function findElementAsync(selector, target = document.body, allowEmpty = true, delay = 10, maxDelay = 10 * 1000) {
  187. return new Promise((resolve, reject) => {
  188. if (selector.toUpperCase() === "BODY") {
  189. resolve(document.body);
  190. return;
  191. }
  192. if (selector.toUpperCase() === "HTML") {
  193. resolve(document.html);
  194. return;
  195. }
  196. let totalDelay = 0;
  197. let element = target.querySelector(selector);
  198. let r###lt = allowEmpty?!!element : (!!element &&!!element.innerHTML);
  199. if (r###lt) {
  200. resolve(element);
  201. return;
  202. }
  203. const interval = setInterval(() => {
  204. if (totalDelay >= maxDelay) {
  205. clearInterval(interval);
  206. resolve(null);
  207. return;
  208. }
  209. element = target.querySelector(selector);
  210. r###lt = allowEmpty?!!element : (!!element &&!!element.innerHTML);
  211. if (r###lt) {
  212. clearInterval(interval);
  213. resolve(element);
  214. } else {
  215. totalDelay += delay;
  216. }
  217. }, delay);
  218. });
  219. }
  220. function hashFunction(str) {
  221. let hash = 0;
  222. for (let i = 0; i < str.length; i++) {
  223. hash = ((hash << 5) - hash) + str.charCodeAt(i);
  224. hash &= hash; // Convert to 32bit integer
  225. }
  226. return hash;
  227. }
  228. function getLocalStorageValue(name, value=null) {
  229. let storageValue = value;
  230. if (typeof GM_getValue === "function") {
  231. storageValue = GM_getValue(name, value);
  232. } else if(typeof GM.setValue === "function"){
  233. storageValue = GM.getValue(name, value);
  234. }else{
  235. var arr = window.localStorage.getItem(name);
  236. if(arr != null){
  237. storageValue = arr
  238. }
  239. }
  240. return storageValue;
  241. }
  242. function setLocalStorageValue(name, value){
  243. if (typeof GM_setValue === "function") {
  244. GM_setValue(name, value);
  245. } else if(typeof GM.setValue === "function"){
  246. GM.setValue(name, value);
  247. }else{
  248. window.localStorage.setItem(name, value)
  249. }
  250. }
  251. function openInTab(url, options={"active":true, "insert":true, "setParent":true}){
  252. if (typeof GM_openInTab === "function") {
  253. GM_openInTab(url, options);
  254. } else {
  255. GM.openInTab(url, options);
  256. }
  257. }
  258. function getPlatform(host = window.location.host){
  259. let platform = "";
  260. if(host.indexOf(".taobao.")!=-1 || host.indexOf(".liangxinyao.")!=-1){
  261. platform = "taobao";
  262. }else if(host.indexOf(".tmall.")!=-1){
  263. platform = "tmall";
  264. }else if(host.indexOf(".jd.")!=-1 || host.indexOf(".yiyaojd.")!=-1 || host.indexOf(".jkcsjd.")!=-1){
  265. platform = "jd";
  266. }else if(host.indexOf(".vip.")!=-1 || host.indexOf(".vipglobal.")!=-1){
  267. platform = "vpinhui";
  268. }else if(host.indexOf(".suning.")!=-1){
  269. platform = "suning";
  270. }
  271. return platform;
  272. }
  273. function encodeTitle(title){
  274. if(!title){
  275. return "";
  276. }
  277. return encodeURIComponent(title.replace(/\t|\r/g,""));
  278. }
  279. GM_registerMenuCommand("清除商品标记记录", ()=> {
  280. if(confirm('此弹窗来自脚本-网购助手\n是否要移除所有的浏览记录?移除后将不可恢复...')){
  281. setLocalStorageValue(browsingLocalStorageKey,[]); //已浏览标识
  282. }
  283. });
  284. const CouponDiscoverer = {
  285. generateR###ltFlag:true,
  286. isActive:function(){
  287. const currentHost = window.location.host;
  288. return details.map((host)=>currentHost.indexOf(host)!=-1).some((r###lt)=>r###lt);
  289. },
  290. getGoodsInfo:function(platform){
  291. var goodsId = "";
  292. var goodsName = "";
  293. const href = window.location.href;
  294. if(platform=="taobao"){
  295. goodsId = getSearchParameter(window.location.search, "id");
  296. try{
  297. const titleObj = document.querySelector("[class^='ItemTitle--']");
  298. if(!!titleObj){
  299. goodsName = titleObj.textContent;
  300. }
  301. }catch(e){}
  302. }else if(platform=="tmall"){
  303. goodsId = getSearchParameter(window.location.search, "id");
  304. try{
  305. const titleObj = document.querySelector("[class^='ItemTitle--']");
  306. if(!!titleObj){
  307. goodsName = titleObj.textContent;
  308. }
  309. }catch(e){}
  310. }else if(platform=="jd"){
  311. goodsId = obtainParameterBySuffix(href);
  312. try{
  313. const titleObj = document.querySelector("[class='sku-name']");
  314. if(!!titleObj){
  315. goodsName = titleObj.textContent;
  316. }
  317. }catch(e){}
  318. }else if(platform=="vpinhui"){
  319. goodsId = obtainParameterBySuffix(href).replace("detail-","");
  320. const titleObj = document.querySelector("[class='pib-title-detail']");
  321. if(!!titleObj){
  322. goodsName = titleObj.textContent;
  323. }
  324. }else if(platform=="suning"){
  325. goodsId = suningParameter(href);
  326. const titleObj = document.querySelector("#itemDisplayName");
  327. if(!!titleObj){
  328. goodsName = titleObj.textContent;
  329. }
  330. }
  331. return {"goodsId":goodsId, "goodsName":encodeTitle(goodsName)};
  332. },
  333. browsingHistory:function(platform, goodsId){
  334. let histories = getLocalStorageValue(browsingLocalStorageKey, []);
  335. let saveContent = platform+"_"+goodsId;
  336. if(!histories.includes(saveContent)){
  337. histories.unshift(saveContent);
  338. setLocalStorageValue(browsingLocalStorageKey, histories.slice(0,60));
  339. }
  340. },
  341. getHandlerElement:async function(handler){
  342. const getElement = async (handler)=>{
  343. const promiseArray = [];
  344. const handlers = handler.split("@");
  345. for(let i=0; i<handlers.length; i++){
  346. const eleName = handlers[i];
  347. if(!eleName){
  348. continue;
  349. }
  350. if(eleName=="body"){
  351. promiseArray.push(
  352. new Promise((resolve,reject) =>{ resolve(document.body) })
  353. );
  354. }else if(eleName=="html"){
  355. promiseArray.push(
  356. new Promise((resolve,reject) =>{ resolve(document.html) })
  357. );
  358. }else{
  359. promiseArray.push(findElementAsync(eleName, document.body, true, 10, 1500));
  360. }
  361. }
  362. const element = await Promise.race(promiseArray);
  363. return element ? element : null;
  364. }
  365. const element = await getElement(handler);
  366. return new Promise((resolve,reject) =>{
  367. resolve(element);
  368. });
  369. },
  370. generateHtml:async function(platform, goodsId, goodsName){
  371. if(!platform || !goodsId){
  372. return "kong";
  373. }
  374. let addition = "";
  375. if(platform=="vpinhui"){
  376. const vip = goodsId.split("-");
  377. addition = vip[0];
  378. goodsId = vip[1];
  379. }
  380. // const testDiv = document.createElement("div");
  381. // testDiv.setAttribute("style", "fint-size:14px;width:200px;height:500px;z-index:99999;position:fixed;top:0px;left:0px;background:#ccc;");
  382. // document.body.append(testDiv);
  383. this.browsingHistory(platform, goodsId);
  384. const goodsCouponUrl = "https://t.jtm.pub/api/coupon/query?no=3&version=1.0.2&platform="+platform+"&id="+goodsId+"&q="+goodsName+"&addition="+addition;
  385. //console.log("goodsCouponUrl",goodsCouponUrl);
  386. try{
  387. // const currentMS = (new Date()).getTime();
  388. // testDiv.insertAdjacentHTML('beforeend', "<div>请求</div>");
  389. // testDiv.insertAdjacentHTML('beforeend', "<div>链接:<a href='"+goodsCouponUrl+"' target='_blank'>【【【【点我跳转】】】</a></div>");
  390. // openInTab(goodsCouponUrl,{"active":false, "insert":true, "setParent":true});
  391. const data = await makeRequest("GET", goodsCouponUrl, null, true);
  392. // testDiv.insertAdjacentHTML('beforeend', "<div>请求---------end,"+((new Date()).getTime() - currentMS)+"</div>");
  393. if(data.code=="ok" && !!data.r###lt){
  394. const json = JSON.parse(data.r###lt);
  395. await this.generateCoupon(platform, json.data);
  396. await this.generateQrcode(platform, json.mscan);
  397. //开启插入检测
  398. let heartms = 0;
  399. const HEART_DELAY = 1500, MAX_MS = 1000*30;
  400. const generateR###ltInterval = setInterval(async ()=>{
  401. if(this.generateR###ltFlag){
  402. if(document.querySelector("*[name='exist-llkbccxs-9246-hi']") || heartms>=MAX_MS){
  403. clearInterval(generateR###ltInterval);
  404. }else{
  405. await this.generateCoupon(platform, json.data);
  406. }
  407. }
  408. heartms += HEART_DELAY;
  409. }, HEART_DELAY);
  410. }
  411. }catch(e){console.log(e);}
  412. },
  413. generateCoupon:async function(platform, r###lt){
  414. try{
  415. this.generateR###ltFlag = false;
  416. if(!r###lt || r###lt==="null" || !r###lt.hasOwnProperty("css") || !r###lt.hasOwnProperty("html") || !r###lt.hasOwnProperty("handler")){
  417. return;
  418. }
  419. const {css, html, handler, templateId} = r###lt;
  420. if(!css || !html || !handler){
  421. return;
  422. }
  423. GM_addStyle(css);
  424. // 添加HTML, 需要动态检测元素
  425. const handlerElement = await this.getHandlerElement(handler);
  426. if(!handlerElement){
  427. return;
  428. }
  429. if(platform=="taobao"){
  430. handlerElement.parentNode.insertAdjacentHTML('afterend', html);
  431. }else if(platform=="tmall"){
  432. handlerElement.parentNode.insertAdjacentHTML('afterend', html);
  433. }else if(platform=="jd"){
  434. handlerElement.insertAdjacentHTML('afterend', html);
  435. }else if(platform=="vpinhui"){
  436. handlerElement.insertAdjacentHTML('afterend', html);
  437. }else if(platform=="suning"){
  438. handlerElement.insertAdjacentHTML('afterend', html);
  439. }
  440. const templateElement = document.querySelector("div[id='"+templateId+"']");
  441. if(!templateElement){
  442. return;
  443. }
  444. const couponId = templateElement.getAttribute("data-id");
  445. const goodsPrivateUrl = "https://t.jtm.pub/api/private/change/coupon?no=3&v=1.0.2&platform="+platform+"&id=";
  446. if(!/\d/.test(couponId)){
  447. return;
  448. }
  449. setInterval(()=>{
  450. templateElement.querySelectorAll("*").forEach((element)=>{
  451. element.removeAttribute("data-spm-anchor-id");
  452. });
  453. },400);
  454. const couponElementA = templateElement.querySelector("a[name='cpShUrl']"), clickedTag = "aclicked";
  455. if(couponElementA){
  456. couponElementA.addEventListener("click",()=>{
  457. event.stopPropagation();
  458. event.preventDefault();
  459. if(couponElementA.getAttribute(clickedTag)){
  460. return;
  461. }
  462. couponElementA.setAttribute(clickedTag, "true");
  463. const href = couponElementA.getAttribute("href");
  464. if(href && href.indexOf("https://")!=-1){
  465. openInTab(href);
  466. couponElementA.removeAttribute(clickedTag);
  467. }else{
  468. makeRequest("GET", goodsPrivateUrl+couponId, null, true).then((privateR###ltData)=>{
  469. if(privateR###ltData.code==="ok" && !!privateR###ltData.r###lt){
  470. let url = JSON.parse(privateR###ltData.r###lt).url;
  471. if(url){
  472. openInTab(url);
  473. }
  474. }
  475. couponElementA.removeAttribute(clickedTag);
  476. }).then(()=>{
  477. couponElementA.removeAttribute(clickedTag);
  478. });
  479. }
  480. });
  481. }
  482. const canvasElement = document.querySelector("#ca"+templateId);
  483. if(!canvasElement){
  484. return;
  485. }
  486. const qrcodeR###ltData = await makeRequest("GET", goodsPrivateUrl+couponId, null, true);
  487. if(!!qrcodeR###ltData && qrcodeR###ltData.code==="ok" && !!qrcodeR###ltData.r###lt){
  488. let img = JSON.parse(qrcodeR###ltData.r###lt).img;
  489. if(!!img){
  490. let cxt = canvasElement.getContext("2d");
  491. let imgData = new Image();
  492. imgData.src = img;
  493. imgData.onload=function(){
  494. cxt.drawImage(imgData, 0, 0, imgData.width, imgData.height);
  495. }
  496. }
  497. }
  498. }catch(e){
  499. console.log(e);
  500. }finally{
  501. this.generateR###ltFlag = true;
  502. }
  503. },
  504. generateQrcode:async function(platform, mscan){
  505. if(!mscan || mscan==="null" || !mscan.hasOwnProperty("mount")
  506. || !mscan.hasOwnProperty("html")|| !mscan.hasOwnProperty("qrcode")){
  507. return;
  508. }
  509. const {mount, html, qrcode, iden} = mscan;
  510. if(!!mount && !!html && !!qrcode && !!iden){
  511. const mountElement = await findElementAsync(mount, document.body, true, 10, 1500);
  512. if(mountElement){
  513. mountElement.insertAdjacentHTML('afterend', html);
  514. let canvasElement = document.getElementById("mscan"+iden);
  515. let width = canvasElement.getAttribute("width");
  516. let height = canvasElement.getAttribute("height");
  517. let cxt = canvasElement.getContext("2d");
  518. let imgData = new Image();
  519. imgData.src = qrcode;
  520. imgData.onload=function(){
  521. cxt.drawImage(imgData, 0, 0, width, height);
  522. }
  523. }
  524. }
  525. },
  526. manageSkuConstraints:function(platform){ //如果sku太多就折叠
  527. if(platform=="tmall" || platform=="taobao"){
  528. findElementAsync("[class='skuItemWrapper']", document.body, false, 10, 1500).then((skuItemWrapper)=>{
  529. if(skuItemWrapper != null){
  530. const { style } = skuItemWrapper;
  531. style.maxHeight = "400px";
  532. style.overflow = "auto";
  533. }
  534. }).catch(()=>{console.log(e);});
  535. }else if(platform=="jd"){
  536. const skuItemWrapper = document.querySelector("#choose-attrs");
  537. if(skuItemWrapper){
  538. const { style } = skuItemWrapper;
  539. style.maxHeight = "400px";
  540. style.overflow = "auto";
  541. }
  542. }
  543. },
  544. start:function(){
  545. if(!this.isActive()){
  546. return;
  547. }
  548. const platform = getPlatform();
  549. if(!platform){
  550. return;
  551. }
  552. this.manageSkuConstraints(platform);
  553. const goodsData = this.getGoodsInfo(platform);
  554. this.generateHtml(platform, goodsData.goodsId, goodsData.goodsName);
  555. }
  556. };
  557. /**
  558. * 模拟map数据结构
  559. */
  560. class MyMap {
  561. constructor() {
  562. this.map = {};
  563. }
  564. set(key, value) {
  565. this.map[key] = value;
  566. }
  567. get(key) {
  568. return this.map[key];
  569. }
  570. has(key) {
  571. return key in this.map;
  572. }
  573. delete(key) {
  574. if (this.has(key)) {
  575. delete this.map[key];
  576. return true;
  577. }
  578. return false;
  579. }
  580. keys() {
  581. return Object.keys(this.map);
  582. }
  583. values() {
  584. return Object.values(this.map);
  585. }
  586. entries() {
  587. return Object.entries(this.map);
  588. }
  589. }
  590. const CouponSearcher = {
  591. intervalCompleted:true,
  592. getHistories:function(){
  593. return getLocalStorageValue(browsingLocalStorageKey, []);
  594. },
  595. isRun:function(){
  596. const visitHref = window.location.href;
  597. return [
  598. /^https:\/\/www\.taobao\.com(\/|\/\?)?/i,//淘宝首页
  599. /^https:\/\/s\.taobao\.com/i,
  600. /^https:\/\/shop(\d+)\.taobao\.com/i,
  601. /^https:\/\/www\.tmall\.com(\/|\/\?)?/i,//天猫首页
  602. /pages\.tmall\.com/i,
  603. /list\.tmall\.com/i,
  604. /list\.tmall\.hk/i,
  605. /tmall\.com\/category/i,
  606. /tmall\.com\/search/i,
  607. /tmall\.com\/shop/i,
  608. /tmall\.com\/\?q=/i,
  609. /maiyao\.liangxinyao\.com/i,
  610. /^https:\/\/www\.jd\.com(\/|\/\?)?/i, //京东主页
  611. /search\.jd\.com/i,
  612. /search\.jd\.hk/i,
  613. /pro\.jd\.com\/mall/i,
  614. /jd\.com\/view_search/i, //商店主页
  615. /category\.vip\.com/i,
  616. /list\.vip\.com/i,
  617. /^https:\/\/(?!product|dfp\.)([^\/]+)\.suning\.com\//i
  618. ].map((reg)=>(new RegExp(reg)).test(visitHref)).some((res)=>res);
  619. },
  620. requestConf:function(){
  621. return new Promise((resolve, reject) => {
  622. makeRequest("GET", "https://t.jtm.pub/api/plugin/load/conf", null, true).then((data)=>{
  623. if(data.code=="ok" && !!data.r###lt){
  624. resolve(data.r###lt);
  625. }else{
  626. resolve(null);
  627. }
  628. });
  629. });
  630. },
  631. pickupsoso:function(confString,platform){ //收集列表的元素
  632. const visitHref = window.location.href;
  633. const selectorElementList = new Array();
  634. let confFilter = confString;
  635. try{
  636. confFilter = confFilter.replace(/\\\\/g,"\\");
  637. }catch(e){}
  638. const confJson = JSON.parse(confFilter);
  639. if(confJson.hasOwnProperty(platform)){
  640. const platformConfJson = confJson[platform];
  641. for(let i=0; i<platformConfJson.length; i++){
  642. const itemJson = platformConfJson[i];
  643. if(!itemJson.hasOwnProperty("elements") || !itemJson.hasOwnProperty("matches")){
  644. continue;
  645. }
  646. const {elements, matches} = itemJson;
  647. const isMatch = matches.map((reg)=>(new RegExp(reg, "i")).test(visitHref)).some((res)=>res);
  648. if(isMatch){
  649. for(let j=0; j<elements.length; j++){
  650. selectorElementList.push({
  651. "element":elements[j]["element"],
  652. "findA":elements[j]["findA"],
  653. "page":elements[j]["page"]
  654. });
  655. }
  656. }
  657. }
  658. }
  659. return selectorElementList;
  660. },
  661. transformElements:function(selectors){
  662. const items = [];
  663. selectors.forEach((elementObj)=>{
  664. if(elementObj.element){
  665. const elements = document.querySelectorAll(elementObj.element + ":not([chaowantianxia='true'])");
  666. elements.forEach((element)=>{
  667. if(element){
  668. items.push({"element":element, "findA": elementObj.findA, "page":elementObj.page});
  669. }
  670. });
  671. }
  672. });
  673. if(items.length>0){
  674. this.queryAll(items);
  675. }
  676. //console.log("items",items);
  677. },
  678. queryAll:function(items){
  679. this.intervalCompleted = false;
  680. const histories = this.getHistories();
  681. this.processLinksInBatches(items, 18, histories).then((r###lt)=>{
  682. this.intervalCompleted = true;
  683. });
  684. },
  685. processLinksInBatches: async function(items, batchSize, histories) {
  686. const r###lts = [];
  687. for (let i = 0; i < items.length; i += batchSize) {
  688. const batch = items.slice(i, i + batchSize); // 获取当前批次的链接
  689. const batchR###lts = await Promise.all( // 同时处理当前批次中的所有请求
  690. batch.map(item => this.queryOne(item, histories))
  691. );
  692. r###lts.push(...batchR###lts); // 保存批次结果
  693. }
  694. return r###lts; // 返回所有结果
  695. },
  696. queryOne:function(item, histories){
  697. const { element, page, findA} = item;
  698. const self = this;
  699. return new Promise(function(resolve, reject){
  700. if(element.getAttribute("chaowantianxia")){ //当存在时,说明已经处理过了
  701. resolve("processed");
  702. return;
  703. }
  704. element.setAttribute("chaowantianxia", "true");
  705. element.style.position = "relative";
  706. element.addEventListener("click", function(e){
  707. element.insertAdjacentHTML('beforeend', browsedHtml);
  708. });
  709. let goodsDetailUrl = null;
  710. if(findA==="this"){ //说明本身就是A标签
  711. goodsDetailUrl = element.getAttribute("href");
  712. }else if(/^child@/.test(findA)){
  713. const elementA = element.querySelector(findA.replace(/^child@/,""));
  714. if(elementA){
  715. goodsDetailUrl = elementA.getAttribute("href");
  716. }
  717. }
  718. if(!goodsDetailUrl){
  719. resolve("exception-url-null");
  720. return;
  721. }
  722. //获取ID每个平台不同
  723. let analysisData = null;
  724. if(/^jd_/.test(page)){
  725. let jdId = obtainParameterBySuffix(goodsDetailUrl);
  726. if(!!jdId) analysisData = {"id":jdId, "platform":"jd"};
  727. }else if(/^vpinhui_/.test(page)){
  728. let vipId = obtainParameterBySuffix(goodsDetailUrl).replace("detail-","");;
  729. if(!!vipId){
  730. analysisData = {"id":vipId.split("-")[1], "platform":"vpinhui"};
  731. }
  732. }else if(/suning_/.test(page)){
  733. let suningId = suningParameter(goodsDetailUrl);
  734. if(!!suningId) analysisData = {"id":suningId, "platform":"suning"};
  735. }
  736. else{
  737. let platform = getPlatform(goodsDetailUrl);
  738. let id = getSearchParameter(goodsDetailUrl, "id");
  739. if(platform && id){
  740. analysisData = {"id":id, "platform":platform};
  741. }
  742. }
  743. if(!analysisData){
  744. resolve("exception-data-null");
  745. return;
  746. }
  747. if(histories.includes(analysisData.platform + "_" + analysisData.id)){
  748. element.insertAdjacentHTML('beforeend', browsedHtml);
  749. }
  750. const searchUrl = "https://t.jtm.pub/api/ebusiness/q/c?p="+analysisData.platform+"&id="+analysisData.id+"&no=3";
  751. makeRequest("GET", searchUrl, null, true).then((data)=>{
  752. if(data.code=="ok" && !!data.r###lt){
  753. const {id, tip, encryptLink} = JSON.parse(data.r###lt);
  754. if(tip){
  755. element.insertAdjacentHTML('beforeend', tip);
  756. console.log("coupon exist", id);
  757. }
  758. if(encryptLink){
  759. console.log("jood job!", id);
  760. let decryptUrl = null;
  761. try{
  762. const decryptLink = atob(encryptLink);
  763. decryptUrl = decryptLink.split('').reverse().join('');
  764. }catch(e){}
  765. if(decryptUrl){
  766. self.relativeClickTo(page, element, decryptUrl);
  767. }
  768. }
  769. }
  770. resolve("success");
  771. }).catch(()=>{
  772. resolve("error");
  773. });
  774. });
  775. },
  776. relativeClickTo:function(page, element, decryptUrl){
  777. const self = this;
  778. try{
  779. if(page.indexOf("jd_")!=-1){
  780. element.querySelectorAll("a").forEach((element_a)=>{
  781. if(element_a.getAttribute("href").indexOf("item.jd.com")!=-1){
  782. element_a.removeAttribute(onclick);
  783. element_a.addEventListener("click", function(e){
  784. e.preventDefault();
  785. e.stopPropagation();
  786. openInTab(decryptUrl);
  787. });
  788. }
  789. });
  790. }
  791. else if(page.indexOf("taobao_")!=-1 || page.indexOf("tmall_")!=-1){
  792. element.addEventListener("click", function(e){
  793. const target = e.target
  794. const tagName = target.tagName.toUpperCase();
  795. let isPreventDefault = false;
  796. if(tagName==="A"){ //只有点击A标签才去判断
  797. const href = target.getAttribute("href");
  798. const isDetail = [/detail\.tmall\.com/, /item\.taobao\.com/]
  799. .map((reg)=> reg.test(href))
  800. .some((r###lt) => r###lt);
  801. if(isDetail){
  802. isPreventDefault = true;
  803. }
  804. }else{
  805. isPreventDefault = true;
  806. }
  807. if(isPreventDefault){
  808. e.preventDefault();
  809. e.stopPropagation();
  810. openInTab(decryptUrl);
  811. }
  812. });
  813. }
  814. else if(page.indexOf("vpinhui_")!=-1){
  815. element.querySelectorAll("a").forEach((element_a)=>{
  816. if(element_a.getAttribute("href").indexOf("detail.vip.com")!=-1){
  817. element_a.addEventListener("click", function(e){
  818. e.preventDefault();
  819. e.stopPropagation();
  820. openInTab(decryptUrl);
  821. });
  822. }
  823. });
  824. }
  825. else if(page.indexOf("suning_")!=-1){
  826. element.querySelectorAll("a").forEach((element_a)=>{
  827. if(element_a.getAttribute("href").indexOf("product.suning.com")!=-1){
  828. element_a.addEventListener("click", function(e){
  829. e.preventDefault();
  830. e.stopPropagation();
  831. openInTab(decryptUrl);
  832. });
  833. }
  834. });
  835. }
  836. }catch(e){
  837. console.log(e);
  838. }
  839. },
  840. start:function(){
  841. if(this.isRun()){
  842. const platform = getPlatform();
  843. this.requestConf().then((confString)=>{
  844. const selectors = this.pickupsoso(confString, (platform=="tmall"? "taobao" : platform));
  845. //console.log("selectors",selectors);
  846. if(this.intervalCompleted){
  847. this.transformElements(selectors);
  848. }
  849. setInterval(()=>{
  850. if(this.intervalCompleted){
  851. this.transformElements(selectors);
  852. }
  853. }, 1500);
  854. });
  855. }
  856. }
  857. };
  858. [CouponDiscoverer, CouponSearcher].forEach((fun)=>{
  859. fun.start();
  860. });
  861. })();