// ==UserScript==
// @name 【网购小秘】自动查询淘宝、天猫、京东等隐藏的大额优惠券和优惠活动,能省就省,不花冤枉钱!
// @namespace Xanthella_Coupon_Secretary_helper
// @version 3.0.1
// @description 网购小秘,不花冤枉钱!!!支持平台:京东、淘宝、天猫、天猫超市、天猫国际、京东国际、京东图书、京东大药房、阿里大药房、唯品会等;功能:1、搜索商品时会自动查询标注有优惠券和活动的商品,无需进入详情页,方便快捷;2、浏览商品详情页时脚本会自动查询商品是否有隐藏的优惠券;简单好用,低侵入~
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAldJREFUWEfVl0FrE0EUx9+kLaQHMXgSxbqgtza2BXto2OLuJ/AoiAcVeugHyNnkJtRLC/bSUz+CJ+kpEwjU2kODiTcPQUEs9JDiwUhpnsxsN5ndmdl92yYEB4YQ5uX9f+8/b2cnDCY82IT14UoA2AIPIOdJeIZPAFkdoM9ZEXjWgjIDYCtXAYZvLEIcEKtZQMgAsmrGajHhsOLAjXAgq7Jiv0Jxgw7QZpgkEHeGLSApNykI27LyoEpE32ZxBILoQlYAzhbQT7JWgUiNlT1M2ScM7U+ofrD9SpNStiEjQHpzqc16LYDNmjvobH+6Kbv/9O/Mfmtq/m2Sa6vsy25+qv+wd5H7doCP1sPYst8wnhGaA1KYQfRx634HEFOM20WA/E0zgxpXmAMQczg4IFTjIDoAd4cdr/680wi+6YmHUSqA4+qQAUDkfDABDJ93NYVI3jsLHLANsS6mcMjiUtlrRDTpAJTHhRAzMoB7hWVN7kf3OA2Bl71G5Byh98Bl6pLzGkr3XxmF3tXXxgsQFz/sHEnBT53P8vPryUc46/2CFWfWBnJ1B4Tlzxa3B4m3+HvYru8Yhe4UZuDp0g3T2mgAROXP915a7R4LgGp/UvWCygpAOgdqbgUYaDceFeBBdT6x2R47s+Y++K8BRMlhE3pbLxId2PBumdev44Ca8UPzN/zsnmsiYu9XnLzsAeMYFcBR54+W/25h2i4cRiP46W9DSxOmHXGkdQqASLTJXfMbkaSSEEQGGI8L2ikoUK13wsubke0fEN0LhLoIjl9EwgSkSyldLXvkxAH+AbnpFjBc9Yl8AAAAAElFTkSuQmCC
// @author Xanthella,huahuacat
// @match *://*.taobao.com/*
// @match *://*.tmall.com/*
// @match *://www.vipglobal.hk/detail-*
// @match *://*.tmall.hk/*
// @match *://chaoshi.detail.tmall.com/*
// @match *://*.liangxinyao.com/*
// @match *://pages.tmall.com/wow/an/cs/search**
// @match *://detail.vip.com/detail-*
// @match *://*.jd.com/*
// @match *://*.jd.hk/*
// @match *://category.vip.com/suggest.php**
// @match *://item.jkcsjd.com/*
// @match *://*.yiyaojd.com/*
// @match *://*.vip.com/*
// @match *://www.vipglobal.hk
// @match *://list.vip.com/*.html
// @exclude *://jianghu.taobao.com/*
// @exclude *://login.taobao.com/*
// @exclude *://map.taobao.com/*
// @exclude *://uland.taobao.com/*
// @exclude *://creator.guanghe.taobao.com/*
// @exclude *://myseller.taobao.com/*
// @exclude *://qn.taobao.com/*
// @exclude *://jingfen.jd.com/*
// @exclude *://jmw.jd.com/*
// @exclude *://passport.jd.com/*
// @exclude *://passport.shop.jd.com/*
// @exclude *://passport.vip.com/*
// @exclude *://huodong.taobao.com/wow/z/guang/gg_publish/*
// @grant GM_info
// @grant GM_download
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_openInTab
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @run-at document-end
// @license AGPL License
// @noframes
// @copyright Xanthella,huahuacat
// ==/UserScript==
(function() {
'use strict';
/**
* AGPL 协议全称为 GNU Affero General Public License(GNU Affero 通用公共许可证),它是一种自由软件许可证。
* AGPL 协议的主要特点是在传统的开源软件许可证(如 GPL)的基础上,加强了对软件在网络环境中使用的限制。
* GPL 是 GNU General Public License(GNU 通用公共许可证)的缩写,本脚本遵循AGPL协议!满足其开源原则、Copyleft原则、附带源代码、允许收费等原则
*
* 脚本中部分工具类搜索自互联网,并没有明确的出处。因此在此说明!如有侵权请留言告知!以上~
* 领券部分源码借鉴自:https://github.com/huahuacatTX/greasyfork/blob/main/pro_huahuacat_union.js
* 特此声明!
*
* 在原基础上去掉了一些高侵入的设计,优化了部分代码
*/
//基础工具类
class BaseObject {
getParamterBySuffix(url=window.location.href, suffix="html"){
if(url.indexOf("?")!=-1){
url = url.split("?")[0];
}
if(url.indexOf("#")!=-1){
url = url.split("#")[0];
}
var splitText = url.split("/");
var idText = splitText[splitText.length-1];
idText = idText.replace(".html","");
return idText;
}
getPlatform(url = window.location.host){
let platform = "";
if(/tmall\./.test(url)){
platform = "tmall";
}else if(/(taobao|liangxinyao)\.(com|hk)/.test(url)){
platform = "taobao";
}else if(/(jd|yiyaojd|jkcsjd)\.(com|hk)/.test(url)){
platform = "jd";
}else if(/(vip|vipglobal)\.(com|hk)/.test(url)){
platform = "vpinhui";
}
return platform;
}
getParamterBySearch(paramsString=window.location.href, tag){
if(paramsString.indexOf("?")!=-1){
paramsString = paramsString.split('?')[1]; // Extract the query string
}
const params = new URLSearchParams(paramsString);
return params.get(tag);
}
request(method, url, param) {
if(!method){
method = "get";
}
if(!url){
return new Promise(function(resolve, reject){
reject({"code":"exception", "result":null});
});
}
if(!param){
param = {};
}
method = method.toUpperCase();
let config = {
method: method
};
if (method === 'POST') {
config.headers['Content-Type'] = 'application/json';
config.body = JSON.stringify(param);
}
return new Promise(function(resolve, reject){
fetch(url, config).then(response => response.text()).then(text => {
resolve({"code":"ok", "result":text});
}).catch(error => {
reject({"code":"exception", "result":null});
});
});
}
querySelectorAsync(selector, target = document.body, allowEmpty = true, delay=10, maxDelay=10 * 1000){
return new Promise((resolve,reject) =>{
if(selector.toUpperCase()=="BODY"){
resolve(document.body);
return;
}
if(selector.toUpperCase()=="HTML"){
resolve(document.html);
return;
}
let totalDelay = 0;
let element = target.querySelector(selector);
let result = allowEmpty ? !!element : (!!element && !!element.innerHTML);
if(result){
resolve(element);
return;
}
const elementInterval = setInterval(()=>{
if(totalDelay >= maxDelay){
clearInterval(elementInterval);
resolve(null);
return;
}
element = target.querySelector(selector);
result = allowEmpty ? !!element : (!!element && !!element.innerHTML);
if(result){
clearInterval(elementInterval);
resolve(element);
}else{
totalDelay += delay;
}
}, delay);
});
}
openInTab(url, options={"active":true, "insert":true, "setParent":true}){
if (typeof GM_openInTab === "function") {
GM_openInTab(url, options);
} else {
GM.openInTab(url, options);
}
}
encodeText(text){
if(!text){
return "";
}
text = text.replace(/\t|\r/g, "");
return encodeURIComponent(text);
}
isElementDisplayed(element) {
if (element.offsetParent!== null) {
return true;
}
const style = window.getComputedStyle(element);
return style.display!== "none";
}
}
class Matrix {
constructor(data) {
this.data = data;
this.rows = data.length;
this.cols = data[0].length;
}
add(matrix) {
if (this.rows !== matrix.rows || this.cols !== matrix.cols) {
throw new Error("error");
}
const result = [];
for (let i = 0; i < this.rows; i++) {
const row = [];
for (let j = 0; j < this.cols; j++) {
row.push(this.data[i][j] + matrix.data[i][j]);
}
result.push(row);
}
return new Matrix(result);
}
subtract(matrix) {
if (this.rows !== matrix.rows || this.cols !== matrix.cols) {
throw new Error("error");
}
const result = [];
for (let i = 0; i < this.rows; i++) {
const row = [];
for (let j = 0; j < this.cols; j++) {
row.push(this.data[i][j] - matrix.data[i][j]);
}
result.push(row);
}
return new Matrix(result);
}
multiply(matrix) {
if (this.cols !== matrix.rows) {
throw new Error("error");
}
const result = [];
for (let i = 0; i < this.rows; i++) {
const row = [];
for (let j = 0; j < matrix.cols; j++) {
let sum = 0;
for (let k = 0; k < this.cols; k++) {
sum += this.data[i][k] * matrix.data[k][j];
}
row.push(sum);
}
result.push(row);
}
return new Matrix(result);
}
transpose() {
const result = [];
for (let i = 0; i < this.cols; i++) {
const row = [];
for (let j = 0; j < this.rows; j++) {
row.push(this.data[j][i]);
}
result.push(row);
}
return new Matrix(result);
}
print() {
this.data.forEach(row => {
console.log(row.join(' '));
});
}
}
//面向详情相关类
class DetailPageObject extends BaseObject{
constructor(){
super();
this.generateIsResult = true;
this.baseUrl = "https://t.jtm.pub";
}
isRun(){
const currentHost = window.location.host;
return ["detail.tmall.com", "item.taobao.com", "item.jd.com", "item.yiyaojd.com", "npcitem.jd.hk",
"detail.tmall.hk", "detail.vip.com", "item.jkcsjd.com"
].map((host)=>currentHost.indexOf(host)!=-1).some((result)=>result);
}
getProductsInfo(platform){
var goodsId = "";
var goodsName = "";
const href = window.location.href;
if(platform=="taobao"){
goodsId = this.getParamterBySearch(window.location.search, "id");
try{
const titleObj = document.querySelector("[class^='ItemTitle--']");
if(!!titleObj){
goodsName = titleObj.textContent;
}
}catch(e){}
}else if(platform=="tmall"){
goodsId = this.getParamterBySearch(window.location.search, "id");
try{
const titleObj = document.querySelector("[class^='ItemTitle--']");
if(!!titleObj){
goodsName = titleObj.textContent;
}
}catch(e){}
}else if(platform=="jd"){
goodsId = this.getParamterBySuffix(href);
try{
const titleObj = document.querySelector("[class='sku-name']");
if(!!titleObj){
goodsName = titleObj.textContent;
}
}catch(e){}
}else if(platform=="vpinhui"){
goodsId = this.getParamterBySuffix(href).replace("detail-","");
const titleObj = document.querySelector("[class='pib-title-detail']");
if(!!titleObj){
goodsName = titleObj.textContent;
}
}
return {"goodsId":goodsId, "goodsName":this.encodeText(goodsName)};
}
async getHandlerElement(handler){
const getElement = async (handler)=>{
const promiseArray = [];
const handlers = handler.split("@");
for(let i=0; i<handlers.length; i++){
const eleName = handlers[i];
if(!eleName){
continue;
}
if(eleName=="body"){
promiseArray.push(
new Promise((resolve,reject) =>{ resolve(document.body) })
);
}else if(eleName=="html"){
promiseArray.push(
new Promise((resolve,reject) =>{ resolve(document.html) })
);
}else{
promiseArray.push(this.querySelectorAsync(eleName, document.body, true, 10, 1500));
}
}
const element = await Promise.race(promiseArray);
return element ? element : null;
}
const element = await getElement(handler);
return new Promise((resolve,reject) =>{
resolve(element);
});
}
async generateHtml(platform, goodsId, goodsName){
if(!platform || !goodsId){
return "kong";
}
let addition = "";
if(platform=="vpinhui"){
const vip = goodsId.split("-");
addition = vip[0];
goodsId = vip[1];
}
const goodsCouponUrl = this.baseUrl+"/api/coupon/query?no=6&version=1.0.2&platform="+platform+"&id="+goodsId+"&q="+goodsName+"&addition="+addition;
try{
const data = await this.request("GET", goodsCouponUrl, null);
if(data.code=="ok" && !!data.result){
const json = JSON.parse(data.result);
await this.generateCoupon(platform, json.data);
await this.generateQrcode(platform, json.mscan);
//开启插入检测
let heartms = 0;
const HEART_DELAY = 1500, MAX_MS = 1000*30;
const generateResultInterval = setInterval(async ()=>{
if(this.generateIsResult){
if(document.querySelector("*[name='exist-llkbccxs-9246-hi']") || heartms>=MAX_MS){
clearInterval(generateResultInterval);
}else{
await this.generateCoupon(platform, json.data);
}
}
heartms += HEART_DELAY;
}, HEART_DELAY);
}
}catch(e){console.log(e);}
}
async generateCoupon(platform, result){
try{
this.generateIsResult = false;
if(!result || result==="null" || !result.hasOwnProperty("css") || !result.hasOwnProperty("html") || !result.hasOwnProperty("handler")){
return;
}
const {css, html, handler, templateId} = result;
if(!css || !html || !handler){
return;
}
GM_addStyle(css);
// 添加HTML, 需要动态检测元素
const handlerElement = await this.getHandlerElement(handler);
if(!handlerElement){
return;
}
if(platform=="taobao"){
handlerElement.parentNode.insertAdjacentHTML('afterend', html);
}else if(platform=="tmall"){
handlerElement.parentNode.insertAdjacentHTML('afterend', html);
}else if(platform=="jd"){
handlerElement.insertAdjacentHTML('afterend', html);
}else if(platform=="vpinhui"){
handlerElement.insertAdjacentHTML('afterend', html);
}
const templateElement = document.querySelector("div[id='"+templateId+"']");
if(!templateElement){
return;
}
const couponId = templateElement.getAttribute("data-id");
const goodsPrivateUrl = this.baseUrl+"/api/private/change/coupon?no=6&v=1.0.2&platform="+platform+"&id=";
if(!/\d/.test(couponId)){
return;
}
setInterval(()=>{
templateElement.querySelectorAll("*").forEach((element)=>{
element.removeAttribute("data-spm-anchor-id");
});
},400);
const couponElementA = templateElement.querySelector("a[name='cpShUrl']"), clickedTag = "aclicked";
if(couponElementA){
couponElementA.addEventListener("click",()=>{
event.stopPropagation();
event.preventDefault();
if(couponElementA.getAttribute(clickedTag)){
return;
}
couponElementA.setAttribute(clickedTag, "true");
const href = couponElementA.getAttribute("href");
if(href && href.indexOf("https://")!=-1){
this.openInTab(href);
couponElementA.removeAttribute(clickedTag);
}else{
this.request("GET", goodsPrivateUrl+couponId, null).then((privateResultData)=>{
if(privateResultData.code==="ok" && !!privateResultData.result){
let url = JSON.parse(privateResultData.result).url;
if(url){
this.openInTab(url);
}
}
couponElementA.removeAttribute(clickedTag);
}).then(()=>{
couponElementA.removeAttribute(clickedTag);
});
}
});
}
const canvasElement = document.querySelector("#ca"+templateId);
if(!canvasElement){
return;
}
const qrcodeResultData = await this.request("GET", goodsPrivateUrl+couponId, null);
if(!!qrcodeResultData && qrcodeResultData.code==="ok" && !!qrcodeResultData.result){
let img = JSON.parse(qrcodeResultData.result).img;
if(!!img){
let cxt = canvasElement.getContext("2d");
let imgData = new Image();
imgData.src = img;
imgData.onload=function(){
cxt.drawImage(imgData, 0, 0, imgData.width, imgData.height);
}
}
}
}catch(e){
console.log(e);
}finally{
this.generateIsResult = true;
}
}
async generateQrcode(platform, mscan){
if(!mscan || mscan==="null" || !mscan.hasOwnProperty("mount")
|| !mscan.hasOwnProperty("html")|| !mscan.hasOwnProperty("qrcode")){
return;
}
const {mount, html, qrcode, iden} = mscan;
if(!!mount && !!html && !!qrcode && !!iden){
const mountElement = await this.querySelectorAsync(mount, document.body, true, 10, 1500);
if(mountElement){
mountElement.insertAdjacentHTML('afterend', html);
let canvasElement = document.getElementById("mscan"+iden);
let width = canvasElement.getAttribute("width");
let height = canvasElement.getAttribute("height");
let cxt = canvasElement.getContext("2d");
let imgData = new Image();
imgData.src = qrcode;
imgData.onload=function(){
cxt.drawImage(imgData, 0, 0, width, height);
}
}
}
}
start(){
if(!this.isRun()){
return;
}
const platform = this.getPlatform();
if(platform){
const goodsData = this.getProductsInfo(platform);
this.generateHtml(platform, goodsData.goodsId, goodsData.goodsName);
}
}
}
//面向搜索相关类
class SearchPageObject extends BaseObject{
constructor(){
super();
this.intervalIsRunComplete = true;
this.searchAttribute = "query-good-job";
this.baseUrl = "https://t.jtm.pub";
}
isRun(){
const visitHref = window.location.href;
return [
/^https:\/\/www\.(taobao|jd|tmall)\.(com|hk)(\/|\/\?)?/i,//淘宝/天猫/京东首页
/^https:\/\/(shop(\d+)|s)\.taobao\.com/i, //搜索或者未装修主页
/(pages|list)\.tmall\.(com|hk)/i,
/tmall\.com\/(category|search|shop|\?q=)/i,
/maiyao\.liangxinyao\.com/i,
/search\.jd\.(com|hk)/i,
/pro\.jd\.com\/mall/i,
/jd\.com\/view_search/i, //商店主页
/category\.vip\.com/i,
/(list|category)\.vip\.com/i
].map((reg)=>(new RegExp(reg)).test(visitHref)).some((res)=>res);
}
requestConf(){
return new Promise((resolve, reject) => {
this.request("GET", this.baseUrl+"/api/plugin/load/conf", null).then((data)=>{
if(data.code=="ok" && !!data.result){
resolve(data.result);
}else{
resolve(null);
}
});
});
}
pickupElements(confString){
const visitHref = window.location.href;
const selectorElementList = new Array();
let confFilter = confString;
try{
confFilter = confFilter.replace(/\\\\/g,"\\");
}catch(e){}
const confJson = JSON.parse(confFilter);
for(let key in confJson){
if(!confJson.hasOwnProperty(key)){
continue;
}
for(let i=0; i<confJson[key].length; i++){
const itemJson = confJson[key][i];
if(!itemJson.hasOwnProperty("elements") || !itemJson.hasOwnProperty("matches")){
continue;
}
const {elements, matches} = itemJson;
const isMatch = matches.map((reg)=>(new RegExp(reg, "i")).test(visitHref)).some((res)=>res);
if(isMatch){
for(let j=0; j<elements.length; j++){
selectorElementList.push({
"element":elements[j]["element"],
"findA":elements[j]["findA"],
"page":elements[j]["page"]
});
}
}
}
}
return selectorElementList;
}
transformElements(selectors){
const items = [];
selectors.forEach((elementObj)=>{
if(elementObj.element){
const elements = document.querySelectorAll(elementObj.element+":not(["+this.searchAttribute+"='true'])");
//console.log("elements",elements.length);
elements.forEach((element)=>{
if(element){
items.push({"element":element, "findA": elementObj.findA, "page":elementObj.page});
}
});
}
});
if(items.length>0){
this.queryAll(items);
}
}
queryAll(items){
this.intervalIsRunComplete = false;
const promises = [];
items.forEach((item)=>{
promises.push(this.queryOne(item));
});
Promise.all(promises).then((result)=>{
this.intervalIsRunComplete = true;
});
}
queryOne(item){
const { element, page, findA} = item;
const self = this;
return new Promise(function(resolve, reject){
if(!self.isElementDisplayed(element)){ //如果元素已经隐藏,则不执行
resolve("cacel");
return;
}
if(element.getAttribute(self.searchAttribute)){ //当存在时,说明已经处理过了
resolve("processed");
return;
}
element.setAttribute(self.searchAttribute, "true");
element.style.position = "relative";
element.addEventListener("click", function(e){
element.insertAdjacentHTML('beforeend', self.browsedHtml);
});
let goodsDetailUrl = null;
if(findA==="this"){ //说明本身就是A标签
goodsDetailUrl = element.getAttribute("href");
}else if(/^child@/.test(findA)){
const elementA = element.querySelector(findA.replace(/^child@/,""));
if(elementA){
goodsDetailUrl = elementA.getAttribute("href");
}
}
if(!goodsDetailUrl){
resolve("exception-url-null");
return;
}
let analysisData = null;
if(/^jd_/.test(page)){
let jdId = self.getParamterBySuffix(goodsDetailUrl);
if(!!jdId) analysisData = {"id":jdId, "platform":"jd"};
}else if(/^vpinhui_/.test(page)){
let vipId = self.getParamterBySuffix(goodsDetailUrl).replace("detail-","");;
if(!!vipId){
analysisData = {"id":vipId.split("-")[1], "platform":"vpinhui"};
}
}else{
let platform = self.getPlatform(goodsDetailUrl);
let id = self.getParamterBySearch(goodsDetailUrl, "id");
if(platform && id){
analysisData = {"id":id, "platform":platform};
}
}
if(!analysisData){
resolve("exception-data-null");
return;
}
const searchUrl = self.baseUrl+"/api/ebusiness/q/c?p="+analysisData.platform+"&id="+analysisData.id+"&no=6";
self.request("GET", searchUrl, null).then((data)=>{
if(data.code=="ok" && !!data.result){
const {tip, encryptLink} = JSON.parse(data.result);
if(tip){
element.insertAdjacentHTML('beforeend', tip);
}
if(encryptLink){
let decryptUrl = null;
try{
const decryptLink = atob(encryptLink);
decryptUrl = decryptLink.split('').reverse().join('');
}catch(e){}
if(decryptUrl){
self.relativeJu(page, element, decryptUrl);
}
}
}
resolve("success");
}).catch(()=>{
resolve("error");
});
});
}
relativeJu(page, element, decryptUrl){
const self = this;
try{
if(page.indexOf("jd_")!=-1){
element.querySelectorAll("a").forEach((element_a)=>{
if(element_a.getAttribute("href").indexOf("item.jd.com")!=-1){
element_a.removeAttribute(onclick);
element_a.addEventListener("click", function(e){
e.preventDefault();
e.stopPropagation();
self.openInTab(decryptUrl);
});
}
});
}
else if(page.indexOf("taobao_")!=-1 || page.indexOf("tmall_")!=-1){
element.addEventListener("click", function(e){
const target = e.target
const tagName = target.tagName.toUpperCase();
let isPreventDefault = false;
if(tagName==="A"){ //只有点击A标签才去判断
const href = target.getAttribute("href");
const isDetail = /(detail|item)\.(tmall|taobao)\.com\/item\.htm/.test(href);
if(isDetail){
isPreventDefault = true;
}
}else{
isPreventDefault = true;
}
if(isPreventDefault){
e.preventDefault();
e.stopPropagation();
self.openInTab(decryptUrl);
}
});
}
else if(page.indexOf("vpinhui_")!=-1){
element.querySelectorAll("a").forEach((element_a)=>{
if(element_a.getAttribute("href").indexOf("detail.vip.com/detail-")!=-1){
element_a.addEventListener("click", function(e){
e.preventDefault();
e.stopPropagation();
self.openInTab(decryptUrl);
});
}
});
}
}catch(e){
console.log(e);
}
}
start(){
if(this.isRun()){
this.requestConf().then((confString)=>{
const selectors = this.pickupElements(confString);
if(this.intervalIsRunComplete){
this.transformElements(selectors);
}
setInterval(()=>{
if(this.intervalIsRunComplete){
this.transformElements(selectors);
}
}, 999);
});
}
}
}
(new DetailPageObject()).start();
(new SearchPageObject()).start();
})();