// ==UserScript==
// @name:zh-CN 淘宝搜索PLUS
// @name:zh-TW 淘寶搜索PLUS
// @name taobao search plus
// @description:zh-CN 淘宝搜索页面自动加载店铺评分,无需鼠标悬停查看!快速查看店铺评分以及同行对比! | 搜索页面自定义排序方式与显示方式
// @description:zh-TW 淘寶搜索頁面自動加載店鋪評分,無需鼠標懸停查看!快速查看店鋪評分以及同行對比! | 搜索頁面自定義排序方式與顯示方式
// @description Auto load shop information, customize the default sort order and ui style(gird or list).
// @icon https://www.taobao.com/favicon.ico
// @version 1.0.1
// @author olOwOlo
// @namespace https://olowolo.com
// @homepage https://github.com/olOwOlo/script/tree/master/taobao-search-plus
// @supportURL https://github.com/olOwOlo/script
// @license MIT
// @match https://s.taobao.com/search?*
// @run-at document-start
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @downloadURL none
// ==/UserScript==
//
/*
* Copyright (c) 2017 olOwOlo
* Released under the MIT License.
*/
(function () {
'use strict'
const DEBUG = false
function myLog (str) {
if (DEBUG) window.console.log(str)
}
(function () {
let globalSort = typeof GM_getValue('sort') === 'undefined' ? 'default' : GM_getValue('sort')
let globalStyle = typeof GM_getValue('style') === 'undefined' ? 'grid' : GM_getValue('style')
myLog(`[sort = ${GM_getValue('sort')}, style = ${GM_getValue('style')}]`)
function parseArgument () {
const argvMap = new Map()
const argvArray = location.search.substr(1).split('&')
for (const string of argvArray) {
argvMap.set(...string.split('='))
}
return argvMap
}
const argv = parseArgument()
let href = location.href
if (globalSort !== 'default' && typeof argv.get('sort') === 'undefined') {
href += `&sort=${globalSort}`
}
if (globalStyle !== 'grid' && typeof argv.get('style') === 'undefined') {
href += `&style=${globalStyle}`
}
if (href !== location.href) {
myLog(`redirect... Old href = [${location.href}]`)
location.href = href
}
const sortOption = ['default', 'renqi-desc', 'sale-desc', 'credit-desc', 'price-asc', 'price-desc', 'total-asc', 'total-desc']
const styleOption = ['grid', 'list']
class Setting {
// 传入数据{sort: sort, style: style}为有效值
constructor (setting) {
this.sort = setting.sort
this.style = setting.style
}
toString () {
return `[sort = ${this.sort}, style = ${this.style}]`
}
createUI () {
const userInterface = document.createElement('div')
userInterface.innerHTML = '
'
document.body.appendChild(userInterface)
// display: none
this.modal = document.getElementById('taobao-plus-modal')
this.modal.style.display = 'none'
// init checked
document.getElementsByName('sortOption')[sortOption.indexOf(this.sort)].checked = true
document.getElementsByName('styleOption')[styleOption.indexOf(this.style)].checked = true
/** return checked value */
function getRadioValue (radioName) {
const elements = document.getElementsByName(radioName);
for (const element of elements) {
if (element.checked) return element.value
}
}
// bind event
document.getElementById('cancel').onclick = () => this.toggle()
document.getElementById('submit').onclick = () => {
const sort = getRadioValue('sortOption')
const style = getRadioValue('styleOption')
GM_setValue('sort', sort)
globalSort = sort
GM_setValue('style', style)
globalStyle = style
this.toggle()
}
}
static isCreated () {
return document.getElementById('taobao-plus-modal') !== null
}
toggle () {
if (!Setting.isCreated()) {
GM_addStyle('.taobao-plus-container{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}.taobao-plus-container.modal-open{overflow:hidden;}.taobao-plus-container .modal-open .modal{overflow-x:hidden;overflow-y:auto;}.taobao-plus-container .fade.in{opacity:1;}.taobao-plus-container .modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0;}.taobao-plus-container .fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear;}.taobao-plus-container .modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0);}.taobao-plus-container .modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;}.taobao-plus-container .modal-dialog{position:relative;width:auto;margin:10px;}.taobao-plus-container .modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5);}.taobao-plus-container .modal-header{padding:15px;border-bottom:1px solid #e5e5e5;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;content:" ";clear:both;}.taobao-plus-container .modal-title{margin:0;line-height:1.42857143;}.taobao-plus-container h4{font-size:18px;font-family:inherit;font-weight:500;color:inherit;}.taobao-plus-container p{margin:0 0 10px;}.taobao-plus-container .modal-body{position:relative;padding:15px;}.taobao-plus-container .radio{position:relative;display:block;margin-top:10px;margin-bottom:10px;}.taobao-plus-container .radio+.radio{margin-top:-5px;}.taobao-plus-container .radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer;}.taobao-plus-container label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700;}.taobao-plus-container .radio input[type=radio]{position:absolute;margin-top:4px\\9;margin-left:-20px;}.taobao-plus-container input[type=radio]{margin:4px 0 0;margin-top:1px\\9;line-height:normal;}.taobao-plus-container input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0;}.taobao-plus-container .btn{display:inline-block;margin-right: 10px;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px;}.taobao-plus-container .btn-default{color:#333;background-color:#fff;border-color:#ccc;}.taobao-plus-container .btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c;}.taobao-plus-container hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee;}@media (min-width:768px){.taobao-plus-container .modal-dialog{width:600px;margin:30px auto;}.taobao-plus-container .modal-sm{width:300px;}.taobao-plus-container .modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5);}}')
this.createUI(this.sort, this.style)
}
this.modal.style.display === 'none'
? this.modal.style.display = 'block'
: this.modal.style.display = 'none'
}
}
let settingEntry = null
GM_registerMenuCommand('淘宝搜索PLUS设置', () => {
if (settingEntry === null) settingEntry = new Setting({sort: globalSort, style: globalStyle})
settingEntry.toggle()
})
})()
document.addEventListener("DOMContentLoaded", () => {
myLog('The taobao-search-load-shop-info scrip start.')
setTimeout(tryLoad, 500)
GM_addStyle('.my-shopinfo-grid{min-width:auto!important;padding:0 5px 5px!important;border:unset!important;-webkit-box-shadow:unset!important;box-shadow:unset!important}.my-shopinfo-grid .score-box{margin-left:5px;margin-top:auto!important;border-top:unset!important;padding:unset!important}.my-shopinfo-list{float:left;min-width:auto!important;padding:5px!important;border:unset!important;-webkit-box-shadow:unset!important;box-shadow:unset!important}.my-shopinfo-list .score-box{margin-top:auto!important;border-top:unset!important;padding:unset!important}.m-itemlist .items .item.J_MouserOnverReq{height:425px!important}@media(max-width:1250px){.my-shopinfo-grid .score{text-align:center}.my-shopinfo-grid .score .percent{float:none!important}.m-itemlist .items .item.J_MouserOnverReq{height:390px!important}}')
GM_addStyle('.m-widget-shopinfo .score{margin:3px 0;height:18px;white-space:nowrap} .m-widget-shopinfo .score .text{float:left;width:96px;color:#3c3c3c}.m-widget-shopinfo .score .highlight{float:left;padding:0 3px;color:#fff} .m-widget-shopinfo .score .percent{float:left;width:52px;text-align:right}.m-widget-shopinfo .morethan .highlight{background:#f40}.m-widget-shopinfo .morethan .percent{color:#f40}.m-widget-shopinfo .equalthan .highlight{background:#f40} .m-widget-shopinfo .equalthan .percent{color:#f40}.m-widget-shopinfo .lessthan .highlight{background:#00ba97}.m-widget-shopinfo .lessthan .percent{color:#00ba97}')
let handleTaskRuning = false
let taskQueue = []
let fakeElementQueue = []
let fakeMirror = []
/* 捕获 排序|筛选|分页 请求 */
{
const MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver
const target = document.querySelector('head')
const observer = new MutationObserver((mutations) =>
mutations.forEach((mutation) => {
if (mutation.addedNodes) {
mutation.addedNodes.forEach((node) => {
const src = node.getAttribute('src')
if (src !== null && src.includes('https://s.taobao.com/search?')) {
taskQueue = []
fakeElementQueue = []
fakeMirror = []
setTimeout(() => tryLoad(), 1000)
}
})
}
})
)
const config = { childList: true }
observer.observe(target, config)
}
/**
* 加载店铺评分
* @param rest 剩余尝试次数
* @returns {Promise.
}
*/
async function tryLoad (rest = 10) {
let {items, state} = getItemsAndPageState()
if (checkIfSafe() && typeof items !== 'undefined'){
const length = items.length
myLog(`Find ${length} Items`)
const date = new Date()
for (const item of items) {
const shopId = getShopId(item)
if (shopId !== null) taskQueue.push({shopId: shopId, item: item, state: state})
item.classList.add('flag-last')
}
await handleTask(state)
const realLength = items.length
myLog(`The Real Length is ${realLength}.`)
for (let index = length; index < realLength; index++) {
const shopId = getShopId(items[index])
if (shopId !== null) taskQueue.push({shopId: shopId, item: items[index], state: state})
items[index].classList.add('flag-last')
}
if (!handleTaskRuning) await handleTask(state)
myLog(`Task [${date}] finished.`)
} else {
myLog(`Items Not Found or unsafe. Rest attempt time is ${rest}.`)
if (rest !== 0) setTimeout(() => tryLoad(rest - 1), 500)
}
}
async function handleTask (state) {
handleTaskRuning = true
while (taskQueue.length !== 0 || fakeElementQueue.length !== 0) {
myLog(`handleTask start. taskQueue.length = ${taskQueue.length}, fakeElementQueue.length = ${fakeElementQueue.length}`)
while (taskQueue.length !== 0) {
myLog('handle a taskQueue.')
const {shopId, item, state} = taskQueue.shift()
requestAndCreateShopInfo(shopId, item, state)
await sleep(250)
}
await sleep(500)
fakeMirror = fakeElementQueue
fakeElementQueue = []
while (fakeMirror.length !== 0) {
myLog('handle a fakeElementQueue.')
const item = fakeMirror.shift()
const shopId = getShopId(item)
if (shopId !== null) taskQueue.push({shopId: shopId, item: item, state: state})
}
}
handleTaskRuning = false
}
/**
* 检查 是否载入了新商品
* @returns {boolean}
*/
function checkIfSafe () {
return document.getElementsByClassName('flag-last').length === 0
}
/**
* 获取商品信息、列表形式
* @returns {*}
*/
function getItemsAndPageState () {
let items = document.getElementsByClassName('item J_MouserOnverReq')
if (items.length !== 0) {
return {items: items, state: 'grid'}
} else if ((items = document.getElementsByClassName('item g-clearfix')).length !== 0) {
return {items: items, state: 'list'}
} else {
return {}
}
}
function getShopId (item) {
let shopInfo
if ((shopInfo = item.getElementsByClassName('J_ShopInfo')).length !== 0 ||
(shopInfo = item.getElementsByClassName('dsr-old J_Dsr')).length !== 0) {
return shopInfo[0].getAttribute('data-userid')
} else if (item.getElementsByClassName('fake-line').length !== 0) {
fakeElementQueue.push(item)
return null
} else {
window.console.log('无法从页面中获取店铺id...')
return null
}
}
/**
* 请求并创建店铺数据
* @param sid shop id
* @param item
* @param state
*/
function requestAndCreateShopInfo (sid, item, state) {
//https://s.taobao.com 2454747176
fetch(`/api?sid=${sid}&callback=shopcard&app=api&m=get_shop_card`, {
method: "GET", mode: 'same-origin', credentials: 'same-origin'
}).then((response) => {
if (response.status >= 200 && response.status < 300) return response
const error = new Error(response.statusText)
error.response = response
throw error
}).then((response) => {
return response.text()
}).then((text) => {
text = text.trim()
createShopInfoElement(item, JSON.parse(text.slice(9, text.length - 2)), state)
}).catch(err => window.console.log(err))
}
/**
* 创建店铺信息元素
* @param itemElement
* @param shopInfo
* @param state grid or list
*/
function createShopInfoElement (itemElement, shopInfo, state) {
const SMALL_LAPTOP = document.body.scrollWidth <= 1250
const highlightFormat = (state === 'grid' && !SMALL_LAPTOP) ? '均值' : '比同行均值'
const shopInfoElement = document.createElement('div')
shopInfoElement.setAttribute('class', `m-widget-shopinfo my-shopinfo-${state}`)
shopInfoElement.innerHTML = `
-
如实描述:${shopInfo.matchDescription}
${shopInfo.descriptionCompared.text.replace(highlightFormat, '')}
${shopInfo.descriptionCompared.rate}
-
服务态度:${shopInfo.serviceAttitude}
${shopInfo.attitudeCompared.text.replace(highlightFormat, '')}
${shopInfo.attitudeCompared.rate}
-
物流服务:${shopInfo.deliverySpeed}
${shopInfo.deliveryCompared.text.replace(highlightFormat, '')}
${shopInfo.deliveryCompared.rate}
`
state === 'grid'
? itemElement.appendChild(shopInfoElement)
: itemElement.getElementsByClassName('col col-5')[0].appendChild(shopInfoElement)
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
})
})()