【智狐】淘宝、天猫、京东、唯品会隐藏优惠券查询,自动显示历史价格和比价,拒绝虚假价格,让您购买到最优惠的商品,网购省钱小助手

无强制跳转,自动显示淘宝、天猫、京东、唯品会隐藏优惠券,包括双十一和618的价格,让你快速了解商品低价,同款商品各大平台快速自动比价,同时显示比价列表,直接点击即可跳转其他平台,历史价格与比价功能正在开发中...

  1. // ==UserScript==
  2. // @name 【智狐】淘宝、天猫、京东、唯品会隐藏优惠券查询,自动显示历史价格和比价,拒绝虚假价格,让您购买到最优惠的商品,网购省钱小助手
  3. // @name:zh 【智狐】淘宝、天猫、京东、唯品会隐藏优惠券查询,自动显示历史价格和比价,拒绝虚假价格,让您购买到最优惠的商品,网购省钱小助手
  4. // @name:zh-TW 【智狐】淘寶、天貓、京東、唯品會隱藏優惠券查詢,自动显示历史价格和比价,拒絕虛假價格,讓您購買到最優惠的商品,網購省錢小助手
  5. // @namespace zhCoupon
  6. // @version 1.8.6
  7. // @description 无强制跳转,自动显示淘宝、天猫、京东、唯品会隐藏优惠券,包括双十一和618的价格,让你快速了解商品低价,同款商品各大平台快速自动比价,同时显示比价列表,直接点击即可跳转其他平台,历史价格与比价功能正在开发中...
  8. // @description:zh 无强制跳转,自动显示淘宝、天猫、京东、唯品会隐藏优惠券,包括双十一和618的价格,让你快速了解商品低价,同款商品各大平台快速自动比价,同时显示比价列表,直接点击即可跳转其他平台,历史价格与比价功能正在开发中...
  9. // @description:zh-TW 無強製跳轉,自動顯示淘寶、天貓、京東、唯品會隱藏優惠券,包括雙十一和618的價格,讓你快速了解商品低價,同款商品各大平臺快速自動比價,同時顯示比價列表,直接點擊即可跳轉其他平臺,歷史價格與比價功能正在開發中...
  10. // @author zhihu
  11. // @run-at none
  12. // @license End-User License Agreement
  13. // @match *://*.taobao.com/*
  14. // @match *://*.tmall.com/*
  15. // @match *://chaoshi.detail.tmall.com/*
  16. // @match *://*.tmall.hk/*
  17. // @match *://*.liangxinyao.com/*
  18. // @match *://*.jd.com/*
  19. // @match *://*.jd.hk/*
  20. // @match *://*.yiyaojd.com/*
  21. // @match *://*.vip.com/*
  22. // @match *://*.vipglobal.hk/*
  23. // @exclude *://login.taobao.com/*
  24. // @exclude *://login.tmall.com/*
  25. // @exclude *://uland.taobao.com/*
  26. // @exclude *://pages.tmall.com/*
  27. // @exclude *://wq.jd.com/*
  28. // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
  29. // @grant GM_xmlhttpRequest
  30. // @grant GM.xmlHttpRequest
  31. // @grant GM_getValue
  32. // @grant GM.getValue
  33. // @grant GM_setValue
  34. // @grant GM.setValue
  35. // @antifeature referral-link 【此提示为GreasyFork代码规范要求含有查券功能的脚本必须添加,请知悉!】
  36. // @connect api.zhihupe.com
  37. // @grant unsafeWindow
  38. // ==/UserScript==
  39. (function() {
  40. 'use strict';
  41. //全局对象
  42. const window = unsafeWindow||window;
  43. const API_DOMAIN = 'https://api.zhihupe.com';
  44. //const API_DOMAIN = 'http://127.0.0.1:7001';
  45. //---------------------------公共方法开始---------------------------
  46. const Utils = {
  47. //兼容 Tampermonkey | Violentmonkey | Greasymonkey 4.0+
  48. getValue:function(name, value) {
  49. if (typeof GM_getValue === "function") {
  50. return GM_getValue(name, value);
  51. } else {
  52. return GM.getValue(name, value);
  53. }
  54. },
  55. //兼容 Tampermonkey | Violentmonkey | Greasymonkey 4.0+
  56. setValue:function(name, value) {
  57. if (typeof GM_setValue === "function") {
  58. GM_setValue(name, value);
  59. } else {
  60. GM.setValue(name, value);
  61. }
  62. },
  63. /**
  64. * 添加css
  65. * @params {String||Array} css - css样式
  66. */
  67. appendStyle(css){
  68. let style = document.createElement('style');
  69. if(css instanceof Array){
  70. style.textContent = css.join('');
  71. }else{
  72. style.textContent = css
  73. }
  74. style.type = 'text/css';
  75. let doc = document.head || document.documentElement;
  76. doc.appendChild(style);
  77. },
  78. /**
  79. * 添加js文件
  80. * @params {String} url - js文件地址
  81. */
  82. appendScript:function(type,content) {
  83. let script = document.createElement('script');
  84. if(type === 'url'){
  85. script.src = content;
  86. }else{
  87. script.innerHTML = content;
  88. }
  89. var docu = document.body;
  90. docu.appendChild(script);
  91. },
  92. getObjectValue(object,path,defValue){
  93. let obj = object;
  94. if(typeof path === 'string'){
  95. const reg = /[^\[\].]+/g;
  96. path = path.match(reg);
  97. }
  98. for(const key of path){
  99. if(!obj) {
  100. return defValue
  101. }
  102. obj = obj[key];
  103. }
  104. return obj === undefined ? defValue : obj;
  105. },
  106. getQueryParam(query,url = ''){
  107. let search = ''
  108. if(url&&url.indexOf('?' !== -1)){
  109. search = url.split('?').slice(1).join();
  110. }else{
  111. search = window.location.search.replace('?','')
  112. }
  113. const queryArr = search.split('&');
  114. let param = null;
  115. queryArr.forEach(item => {
  116. const paramArr = item.split('=');
  117. if(paramArr[0] === query){
  118. param = paramArr[1];
  119. }
  120. });
  121. return param;
  122. },
  123. getUrlid(url) {
  124. var id ="";
  125. if (url.indexOf("?") != -1) {
  126. url = url.split("?")[0]
  127. }
  128. if (url.indexOf("#") != -1) {
  129. url = url.split("#")[0]
  130. }
  131. var text = url.split("/");
  132. id = text[text.length - 1];
  133. id = id.replace(".html", "");
  134. return id
  135. },
  136. monitorElement(attr){
  137. let attrArr = [];
  138. if(attr instanceof Array&&attr.length > 0){
  139. attrArr = [...attr];
  140. }else{
  141. attrArr.push(attr)
  142. }
  143. let element = null;
  144. return new Promise((resolve, reject) => {
  145. attrArr.forEach(ele =>{
  146. let timer = null,count = 0;
  147. element = document.querySelector(ele);
  148. timer = setInterval(()=>{
  149. if(element){
  150. clearInterval(timer);
  151. resolve(element);
  152. }
  153. if(count > 50){
  154. clearInterval(timer);
  155. reject('未找到元素节点');
  156. }
  157. element = document.querySelector(ele);
  158. count ++
  159. },200)
  160. })
  161. })
  162. }
  163. }
  164. const initData = {
  165. wenku:{
  166. codeInputElement:"#code",
  167. loginCode:"123456",
  168. },
  169. coupon:{
  170. shopGuideGroupText:'捡漏导购Q群',
  171. shopGuideGroup:'188872170',
  172. hasCouponBtnText:'领券购买',
  173. noCouponBtnText:'直接购买',
  174. noCommissionBtnText:'搜索同款优惠',
  175. buyUrl:'http://tool.wezhicms.com/coupon/getscan.php',
  176. tbShortUrlApi:'https://api.shop.xuelg.com/?id={ID}&m=shangpin',
  177. tbShortUrlApiReqQuery: 'shorturl',
  178. blackElement:['.coupon-wrap','#toolbar-qrcode'],
  179. taobao:{
  180. detailCouponMountElement:['.SecurityPrice--securityPrice--25lJx-X','.Price--root--1CrVGjc'],
  181. searchGoodsCardElement:['.Content--content--sgSCZ12 .Card--doubleCardWrapper--L2XFE73','.Content--content--sgSCZ12 .Card--doubleCardWrapperMall--uPmo5Bz','.J_TItems .item .photo a']
  182. },
  183. tmall:{
  184. detailCouponMountElement:['.SecurityPrice--securityPrice--25lJx-X','.Price--root--1CrVGjc']
  185. },
  186. tmallcs:{
  187. searchGoodsCardElement:['.feeds-list .feeds-item a']
  188. },
  189. jd:{
  190. detailCouponMountElement:'#J-summary-top',
  191. searchGoodsCardElement:['.J-goods-list .gl-i-wrap .p-img a','.jSearchListArea .jItem .jPic a']
  192. }
  193. }
  194. }
  195. class Coupon{
  196. //优惠券查询地址
  197. couponApiUrl = '';
  198. //优惠券展示节点
  199. initElement = null;
  200. get isHasCoupon(){
  201. return this.couponInfo?.couponAmount > 0;
  202. }
  203. constructor(platform){
  204. this.platform = platform;
  205. //优惠券信息
  206. let couponInfo = {};
  207. Object.defineProperty(this,'couponInfo',{
  208. set(value){
  209. couponInfo = value;
  210. //更新优惠券信息
  211. this.updateCouponHtml();
  212. },
  213. get(){
  214. return couponInfo;
  215. }
  216. })
  217. }
  218. /*
  219. 获取优惠券信息
  220. return {Promise}
  221. */
  222. getCouponInfo(){
  223. if(!this.goodsId) throw new TypeError('商品ID获取失败');
  224. if(!this.couponApiUrl) throw new TypeError('优惠券查询链接不存在');
  225. return new Promise((resolve, reject) => {
  226. fetch(this.couponApiUrl,{
  227. method:'GET',
  228. mode:'cors',
  229. }).then(r=>r.json()).then(response=>{
  230. if(response.code === 1){
  231. resolve(response.data)
  232. }else{
  233. reject(new TypeError(response.message))
  234. }
  235. }).catch(err=>{
  236. reject(err)
  237. })
  238. });
  239. }
  240. getTbShortUrl(id){
  241. const api = initData.coupon.tbShortUrlApi.replace('{ID}',id)
  242. return new Promise((resolve, reject) => {
  243. fetch(api,{
  244. method:'GET',
  245. mode:'cors',
  246. }).then(r=>r.json()).then(response=>{
  247. resolve(Utils.getObjectValue(response,initData.coupon.tbShortUrlApiReqQuery));
  248. }).catch(err=>{
  249. reject(err)
  250. })
  251. });
  252. }
  253. /*
  254. *插入优惠券节点
  255. * params {ElementObject} 定位元素对象
  256. * params {String} position 插入位置
  257. */
  258. async appendCouponElement(element,position = "afterend"){
  259. //插入css
  260. this.css&&Utils.appendStyle(this.css)
  261. //插入Html
  262. this.initElement = this.loadingElement||null;
  263. if(this.initElement)element.insertAdjacentElement(position,this.initElement);
  264. }
  265. async updateCouponHtml(){
  266. if(this.initElement){
  267. this.initElement.innerHTML = this.couponHtml
  268. //添加关闭二维码事件
  269. const qr = document.querySelectorAll('.closeQr');
  270. if(qr.length > 0){
  271. console.log(qr)
  272. Array.from(qr).forEach(e=>{e.onclick = ()=> e.parentNode.style.display = 'none'})
  273. }
  274. }
  275. }
  276. async updateCouponErrorHtml(){
  277. if(this.initElement)this.initElement.innerHTML = this.couponHtml
  278. }
  279. }
  280. class DetailCoupon extends Coupon{
  281. css = `
  282. .zhihu-coupon:hover .zhihu-scan{
  283. display:block!important;
  284. }
  285. `
  286. get mobileAppText(){
  287. let text = '手机淘宝';
  288. switch (this.platform) {
  289. case 'tmall':
  290. case 'taobao':
  291. text = '手机淘宝'
  292. break;
  293. case 'jd':
  294. text = '手机京东或微信'
  295. break;
  296. }
  297. return text;
  298. }
  299. get couponHtml(){
  300. let couponHtml = '<text style="font-size: 20px;">暂未发现优惠券</text>',
  301. tipsText = initData.coupon.shopGuideGroupText + ':' + initData.coupon.shopGuideGroup,
  302. btnText = initData.coupon.noCouponBtnText,
  303. buyLink = `${initData.coupon.buyUrl}?link=${this.couponInfo.shortUrl}&platform=${encodeURIComponent(this.mobileAppText)}`,
  304. qrcodebox = `<div style="position:fixed;bottom:50px;right:50px;width: 124px; z-index: 999999;">${this.qrcodeHtml}</div>`
  305. if(this.isHasCoupon){
  306. couponHtml = `
  307. <text style="font-size: 20px;">优惠券:</text>
  308. <text style="font-size: 18px; margin-right: 2px;">¥</text>${Math.round(this.couponInfo.couponAmount)}
  309. `;
  310. tipsText = `${this.couponInfo.couponStartTime} - ${this.couponInfo.couponEndTime}`;
  311. btnText = initData.coupon.hasCouponBtnText;
  312. }else if(this.noCommission){
  313. couponHtml = '<text style="font-size: 20px;">暂未发现优惠券</text>';
  314. buyLink = '';
  315. btnText = initData.coupon.noCommissionBtnText;
  316. qrcodebox = '';
  317. }
  318. return `
  319. <div style="width: 279px; color: rgb(255, 255, 255); padding-left: 20px; box-sizing: border-box;">
  320. <div style="font-size: 28px; line-height: 28px; font-weight: 900; white-space: nowrap;">
  321. ${couponHtml}
  322. </div>
  323. <div style="margin-top: 5px;">${tipsText}</div>
  324. </div>
  325. <div style="text-align: center; width: calc(100% - 279px); font-size: 20px; color: rgb(255, 255, 255); font-weight: bold; letter-spacing: 1px; cursor: pointer;">
  326. <a target="_blank" href="${buyLink}" style="text-decoration: none; color: rgb(255, 255, 255);">${btnText}</a>
  327. </div>
  328. <div class="zhihu-scan" style="position: absolute; display: block; right: 426px; padding-right: 5px; top: -10px; width: 124px; z-index: 999999;">
  329. ${this.qrcodeHtml}
  330. </div>
  331. ${qrcodebox}
  332. `
  333. }
  334. get loadingElement(){
  335. const div = document.createElement('div');
  336. div.classList = 'zhihu-coupon';
  337. div.style = 'background-image: url(https://gw.alicdn.com/tfs/TB16d.1ykPoK1RjSZKbXXX1IXXa-665-115.png); position: relative; font-family: HelveticaNeue-Bold, "Helvetica Neue"; width: 426px; height: 75px; background-size: 100%, 100%; background-repeat: no-repeat; margin: 10px 0px 10px 10px; display: flex; align-items: center;'
  338. div.innerHTML = `
  339. <div style="width: 279px; color: rgb(255, 255, 255); padding-left: 20px; box-sizing: border-box;">
  340. <div style="font-size: 28px; line-height: 28px; font-weight: 900; white-space: nowrap;">
  341. <text style="font-size: 20px;">正在搜索优惠券···</text>
  342. </div>
  343. </div>
  344. `;
  345. return div;
  346. }
  347. get qrcodeHtml(){
  348. if(!this.couponInfo?.shortUrl) return '';
  349. return `
  350. <i class="closeQr" style="position: absolute;right: -2px;top: -2px;cursor: pointer;"><svg t="1712718308343" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1614" width="18" height="18"><path d="M512 960C264.97 960 64 759.03 64 512S264.97 64 512 64c247.04 0 448 200.97 448 448S759.04 960 512 960z m0-861.54C283.98 98.46 98.46 283.98 98.46 512S283.98 925.54 512 925.54 925.54 740.02 925.54 512 740.02 98.46 512 98.46z" fill="#666666" p-id="1615"></path><path d="M353.61 687.62c-4.41 0-8.82-1.68-12.18-5.05-6.73-6.73-6.73-17.63 0-24.37l316.78-316.78c6.73-6.73 17.63-6.73 24.37 0s6.73 17.63 0 24.37L365.79 682.57a17.14 17.14 0 0 1-12.18 5.05z" fill="#666666" p-id="1616"></path><path d="M670.39 687.62c-4.41 0-8.82-1.68-12.18-5.05L341.43 365.79c-6.73-6.73-6.73-17.63 0-24.37s17.63-6.73 24.37 0L682.58 658.2c6.73 6.73 6.73 17.63 0 24.37a17.18 17.18 0 0 1-12.19 5.05z" fill="#666666" p-id="1617"></path></svg></i>
  351. <div style="text-align: center; padding: 12px 5px 8px; background: rgb(255, 255, 255); border: 1px solid rgba(0, 0, 0, 0.08); border-radius: 12px;">
  352. <div style="margin: 0px auto; width: 100px;">
  353. <img src="http://v.zhihupe.com/enQrcode?url=${this.couponInfo.shortUrl}" style="width: 100px;">
  354. </div>
  355. <div style="margin-top: 4px;">${this.mobileAppText}扫一扫,享优惠</div>
  356. </div>
  357. `
  358. }
  359. constructor(platform){
  360. super(platform);
  361. this.addCouponElement();
  362. }
  363. async addCouponElement(){
  364. try {
  365. //获取商品ID和优惠券API链接
  366. if(this.platform == 'taobao'||this.platform == 'tmall'){
  367. this.goodsId = Utils.getQueryParam('id')||null;
  368. const shortUrl = await this.getTbShortUrl(this.goodsId);
  369. console.log(shortUrl)
  370. this.couponApiUrl = API_DOMAIN + '/api/coupon/info?platform=taobao' + '&id=' + shortUrl
  371. }else if(this.platform === 'jd'){
  372. this.goodsId = Utils.getUrlid(window.location.href)||null;
  373. this.couponApiUrl = API_DOMAIN + '/api/coupon/info?platform=jd' + '&id=' + this.goodsId;
  374. }
  375. //添加优惠券元素
  376. const attr = initData.coupon[this.platform]['detailCouponMountElement'];
  377. console.log(initData.coupon[this.platform])
  378. const element = await Utils.monitorElement(attr);
  379. this.appendCouponElement(element);
  380. //获取商品优惠券
  381. this.couponInfo = await this.getCouponInfo();
  382. } catch (error) {
  383. //添加无佣金状态
  384. this.noCommission = true;
  385. //更新无佣金html
  386. this.updateCouponErrorHtml()
  387. console.log(error)
  388. }
  389. }
  390. }
  391. class SearchCoupon extends Coupon{
  392. get couponHtml(){
  393. let html = `<span style="background: rgb(0 0 0 / 51%);border-radius: 999px;padding: 5px 10px;color: #fff;">暂无优惠</span>`
  394. if(this.isHasCoupon){
  395. html = `<span style="background: rgb(253 2 57);border-radius: 999px;padding: 5px 10px;color: #fff;">有券:减${this.couponInfo.couponAmount}元</span>`
  396. }
  397. return html
  398. }
  399. get loadingElement(){
  400. const div = document.createElement('div');
  401. div.style = 'position: absolute;top: 10px;right: 10px;z-index: 99999999;font-size: 12px;';
  402. div.innerHTML = `
  403. <span style="background: rgb(0 0 0 / 51%);border-radius: 999px;padding: 5px 10px;color: #fff;">正在搜索优惠</span>
  404. `;
  405. return div;
  406. }
  407. constructor(element,platform){
  408. super(platform);
  409. this.addCouponElement(element)
  410. }
  411. async addCouponElement(element){
  412. try {
  413. //获取商品ID和优惠券API链接
  414. if(this.platform == 'taobao'||this.platform == 'tmallcs'){
  415. const href = element.getAttribute('href');
  416. this.goodsId = Utils.getQueryParam('id',href)||null;
  417. const shortUrl = await this.getTbShortUrl(this.goodsId);
  418. console.log(shortUrl)
  419. this.couponApiUrl = API_DOMAIN + '/api/coupon/search?platform=taobao' + '&id=' + shortUrl
  420. }else if(this.platform === 'jd'){
  421. const href = element.getAttribute('href');
  422. this.goodsId = Utils.getUrlid(href)||null;
  423. this.couponApiUrl = API_DOMAIN + '/api/coupon/search?platform=jd' + '&id=' + this.goodsId;
  424. }
  425. element.style.position = 'relative';
  426. //添加优惠券节点
  427. this.appendCouponElement(element,'beforeend');
  428. //获取商品优惠券
  429. this.couponInfo = await this.getCouponInfo();
  430. } catch (error) {
  431. console.log(error)
  432. //更新无佣金html
  433. this.updateCouponErrorHtml()
  434. console.log(error.message)
  435. }
  436. }
  437. }
  438. async function getInitData(){
  439. const request = ()=>{
  440. return new Promise((resolve, reject) => {
  441. fetch(API_DOMAIN + '/api/coupon/init',{
  442. method:'GET',
  443. mode:'cors',
  444. }).then(r=>r.json()).then(response=>{
  445. if(response.code === 1){
  446. resolve(response.data)
  447. }else{
  448. resolve(null)
  449. }
  450. }).catch(err=>{
  451. resolve(null)
  452. })
  453. })
  454. }
  455. const _initData = await request();
  456. _initData&&(initData.coupon = {..._initData});
  457. }
  458. async function detailCouponInit(platform = 'taobao'){
  459. await getInitData();
  460. new DetailCoupon(platform);
  461. //移除节点
  462. if(initData.coupon.blackElement instanceof Array && initData.coupon.blackElement.length > 0){
  463. const blackElement = initData.coupon.blackElement;
  464. blackElement.forEach(item => {
  465. Utils.monitorElement(item).then(selector=>{
  466. selector.remove()
  467. })
  468. })
  469. }
  470. }
  471. async function searchCouponInit(platform = 'taobao'){
  472. await getInitData();
  473. if(!(initData.coupon?.[platform]?.searchGoodsCardElement instanceof Array)||initData.coupon?.[platform]?.searchGoodsCardElement.length === 0) return
  474. //class数组
  475. const clss = initData.coupon[platform].searchGoodsCardElement;
  476. //监听商品卡片
  477. setInterval(()=>{
  478. //遍历class数组
  479. for (let i = 0; i < clss.length; i++) {
  480. const elements = document.querySelectorAll(clss[i]);
  481. if(elements&&elements.length > 0){
  482. Array.from(elements).forEach(element=>{
  483. if(element.classList.contains('zhihu-coupon-added')) return;
  484. //添加class标记
  485. element.classList.add('zhihu-coupon-added');
  486. new SearchCoupon(element,platform);
  487. })
  488. }
  489. }
  490. },1500)
  491. }
  492. async function zhihuWenkuInit(){
  493. sessionStorage.setItem('zhihu_sign',true);
  494. }
  495. //网址匹配
  496. const siteMap = [
  497. {
  498. match:['item.taobao.com/item.htm.*'],
  499. platform:'taobao',
  500. initFunc:detailCouponInit
  501. },
  502. {
  503. match:['detail.tmall.com/item.htm.*','detail.tmall.hk/hk/item.htm.*'],
  504. platform:'tmall',
  505. initFunc:detailCouponInit
  506. },
  507. {
  508. match:['item.jd.com/.*','npcitem.jd.hk/.*','item.yiyaojd.com/.*'],
  509. platform:'jd',
  510. initFunc:detailCouponInit
  511. },
  512. {
  513. match:['s.taobao.com/search.*','suning.tmall.com/category.*'],
  514. platform:'taobao',
  515. initFunc:searchCouponInit
  516. },
  517. {
  518. match:['pages.tmall.com/wow/an/cs/search.*'],
  519. platform:'tmallcs',
  520. initFunc:searchCouponInit
  521. },
  522. {
  523. match:['search.jd.com/Search.*','list.jd.com/list.html.*','mall.jd.com/view_search.*'],
  524. platform:'jd',
  525. initFunc:searchCouponInit
  526. },
  527. {
  528. match:['wenku.zhihupe.com/tool/index.*','wenku.zhihupe.com/#.*'],
  529. initFunc:zhihuWenkuInit
  530. }
  531. ]
  532. //生成正则表达式
  533. function createReg(arr){
  534. return new RegExp(arr.join('|'))
  535. }
  536. //根据网址匹配
  537. for (const site of siteMap) {
  538. let reg = createReg(site.match)
  539. let host = window.location.hostname + window.location.pathname
  540. let result = reg.test(host)
  541. if(result){
  542. let platform = site.platform||'';
  543. return site.initFunc(platform);
  544. }
  545. }
  546. // Your code here...
  547. })();