BL Data Query Helper

try to take over the world!

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name         BL Data Query Helper
// @namespace    http://tampermonkey.net/
// @version      0.1
// @include      http*://www.blushmark.com/*
// @description  try to take over the world!
// @author       You
// @match        http://*/*
// @grant        GM_addStyle
// ==/UserScript==

const willRequests = []
const onGoingRequests = []
const maxConcurrency = 5

const beginRequest = (func) => {
  if (func) {
    if (onGoingRequests.length < maxConcurrency) {
      onGoingRequests.push(func)
      func()
    } else {
      willRequests.push(func)
    }
  }
}
const stopRequest = () => {
  onGoingRequests.pop()
  if (willRequests.length > 0) {
    const func = willRequests.shift()
    onGoingRequests.push(func)
    func()
  }
}

// 强依赖网站的参数
// 1.cookie中的 hasLogin, login_token
// 2.商品div中的class goods-item
// 3.商品div中的class goods-item 的上级div 要有 data-goods-id data-style-id参数

const getQueryStringValue = (key) => {
  return decodeURIComponent(window.location.search.replace(new RegExp('^(?:.*[&\\?]' + encodeURIComponent(key).replace(/[\.\+\*]/g, '\\$&') + '(?:\\=([^&]*))?)?.*$', 'i'), '$1'))
}

const getHost = () => {
  const hostName = window.location.hostname
  let host = 'https://www.blushmark.com/prod'
  if (hostName.startsWith('ft') || hostName.startsWith('ft-x')) {
    host = `https://${hostName}/test`
  } else if (hostName.startsWith('p')) {
    host = `https://${hostName}/pre`
  }
  return host
}

const getUrl = (url, params = {}) => {
  let host = getHost()
  console.log('url====>', url)
  console.log('host====>', host)
  console.log('params====>', params)
  const keys = Object.keys(params)
  keys && keys.map((key) => {
    url = url + (url.includes('?') ? '&' : '?') + `${key}=${params[key]}`
  })
  return host + url
}

const GET = (path, params, callback) => {
  beginRequest(() => {
    const xhr = new XMLHttpRequest()
    const url = getUrl(path, params)
    console.log('url====>', url)
    xhr.open('GET', url, true)
    xhr.onreadystatechange = function() {
      if (xhr.readyState == 4) {
        const info = JSON.parse(xhr.responseText)
        if (info && info.code == 0) {
          callback(info.data)
        } else {
          callback(undefined)
        }
        stopRequest()
      }
    }
    xhr.send()
  })
}

const fetchData = (goodsId, styleId, callback) => {
  const isAllListPage = window.location.pathname.startsWith('/categories/0_0/')
  const isClearance = window.location.pathname.startsWith('/clearance')
  const isSortByNew = window.location.search.includes('sort_by=new')
  const params = {
    styleId: styleId,
    callback: goodsId,
    type: (isAllListPage ? getQueryStringValue('type') : ''),
    pageType: (isClearance ? 'clearance' : isSortByNew ? 'just_in' : 'normal')
  }
  GET('/1.0/plugin/ctr-detail', params, callback)
}

const indexGetData = (ec, el, list_page_path, callback) => {
  const params = {
    path: list_page_path,
    ec: ec,
    el: el
  }
  GET('/1.0/home/plugin-ctr', params, callback)
}

const productGetData = (goods_id, style_id, size_id, callback) => {
  const params = {
    goods_id: goods_id,
    style_id: style_id,
    size_id: size_id
  }
  GET('/1.0/home/getStockTransit', params, callback)
}

const HTTP = {
    fetchData,
    indexGetData,
    productGetData
}

const elementIdentifier = 'chrome-extension-info'
const elementProductIdentifer = 'chrome-extension-info-product'
const MOUSE_VISITED_CLASS_NAME = 'goods-item'
const MOUSE_VISITED_CLASS_NAME_INDEX = 'ctr-index'
const MOUSE_VISITED_CLASS_NAME_PRODUCT = 'ctr-product'

// 强依赖网站的参数
// 1.cookie中的 hasLogin, login_token
// 2.商品div中的class goods-item
// 3.商品div中的class goods-item 的上级div 要有 data-goods-id data-style-id参数
const createRowElement = (dataElement, text) => {
  const container = document.createElement('span')
  container.className = 'extension-container'
  const textElement = document.createTextNode(text)
  container.appendChild(textElement)
  dataElement.appendChild(container)
  return container
}

/**
 * 获取containerElement
 * @param element
 */
const getContainerElement = (element) => {
  let containerElement = null
  if (element && element.className.indexOf(MOUSE_VISITED_CLASS_NAME) >= 0) {
    // 商品在此获取数据
    const findGoodsImageElementFunc = (elements) => {
      elements && Array.from(elements).some(e => {
        if (e && e.classList && e.classList.value.indexOf('goods-item-pic') >= 0) {
          containerElement = e
          return true
        }
        findGoodsImageElementFunc(e.children)
      })
    }
    findGoodsImageElementFunc(element.children)
  } else {
    // 首页和详情页在此获取数据
    containerElement = element
  }
  return containerElement
}

const showListCover = (containerElement) => {
  if (!containerElement || !containerElement.parentElement) {
    return
  }
  // 商品获取方式
  const goodsId = containerElement.parentElement.getAttribute('data-goods-id')
  const styleId = containerElement.parentElement.getAttribute('data-style-id')
  const pageType = containerElement.parentElement.getAttribute('page-type')
  // 参数不合法 退出
  if (!goodsId || !styleId || !pageType) return

  const children = Array.from(containerElement.children)
  let isExist = false
  children && children.map(e => {
    if (e.id === elementIdentifier) {
      if (e.dataId === goodsId + '' + styleId) {
        isExist = true
      } else {
        // 存在一个其他商品的数据 移除
        e.parentNode.removeChild(e)
      }
    }
  })
  // 已经存在了 不再次展示
  if (isExist) return
  const dataElement = document.createElement('div')
  dataElement.id = elementIdentifier
  dataElement.dataId = goodsId + '' + styleId
  dataElement.style.zIndex = 100

  const ctrElement = createRowElement(dataElement, 'CTR: -%')
  let justInElement
  if (pageType == 'just_in') {
    justInElement = createRowElement(dataElement, 'BST_CTR: -')
  }
  const crElement = createRowElement(dataElement, 'CR: -%')
  const ctrCrElement = createRowElement(dataElement, pageType == 'just_in' ? 'BST_CTR*CR: -' : 'CTR*CR: -')
  const saleElement = createRowElement(dataElement, 'SAL: -')
  const averageElement = createRowElement(dataElement, 'AVE: -')
  const clickElement = createRowElement(dataElement, 'CLI: -')
  const impressionElement = createRowElement(dataElement, 'IMP: -')
  const userElement = createRowElement(dataElement, 'DET: -')
  const ostElement = createRowElement(dataElement, 'OST: -')
  const abElement = createRowElement(dataElement, '')
  abElement.style.display = 'none'
  containerElement && containerElement.appendChild(dataElement)
  HTTP.fetchData(goodsId, styleId, (data) => {
    if (data) {
      const { cr, ctr, sales, viewCount, clickCount, showNumber, onSaleTime, ab_test, ctrCr, goodsSales, bestSellerCtr, averageDailySales } = data
      ctrElement.textContent = 'CTR: ' + ctr
      crElement.textContent = 'CR: ' + cr
      saleElement.textContent = 'SAL: ' + sales + '(' + goodsSales + ')'
      clickElement.textContent = 'CLI: ' + clickCount
      impressionElement.textContent = 'IMP: ' + viewCount
      userElement.textContent = 'DET: ' + showNumber
      ostElement.textContent = 'OST: ' + onSaleTime
      ctrCrElement.textContent = (pageType == 'just_in' ? 'BST_CTR*CR:' : 'CTR*CR: ') + ctrCr
      averageElement.textContent = 'AVE: ' + averageDailySales
      if (ab_test) {
        abElement.textContent = 'A/B Test'
        abElement.style.display = 'block'
      }

      if (pageType == 'just_in') {
        if (bestSellerCtr == undefined) {
          justInElement.parentElement.removeChild(justInElement)
        } else { justInElement.textContent = 'BST_CTR:' + bestSellerCtr }
      }
    }
  })
}

const showHomeCover = (element, containerElement) => {
  if (!element || !containerElement) {
    return
  }
  const children = Array.from(containerElement.children)
  let isExist = false
  children && children.map(e => {
    if (e.id === elementIdentifier) {
      isExist = true
    }
  })
  // 已经存在了 不再次展示
  if (isExist) return
  const ec = element.getAttribute('ec')
  const el = element.getAttribute('el')
  const list_page_path = element.getAttribute('list_page_path')
  // 参数不合法
  if (!ec || !el || !list_page_path) return

  const dataElement = document.createElement('div')
  dataElement.id = elementIdentifier
  dataElement.style.zIndex = 100

  const ctrElement = createRowElement(dataElement, 'CTR: -%')
  const clickElement = createRowElement(dataElement, 'Click: -')
  const viewElement = createRowElement(dataElement, 'Impression: -')
  HTTP.indexGetData(ec, el, list_page_path, (data) => {
    if (data) {
      const { ctr, click_count, view_count } = data
      ctrElement.textContent = 'CTR: ' + (ctr || '0%')
      clickElement.textContent = 'Click: ' + (click_count || 0)
      viewElement.textContent = 'Impression: ' + (view_count || 0)
    }
  })
  containerElement && containerElement.appendChild(dataElement)
}

const showDetailCover = (element, containerElement) => {
  if (!element || !containerElement) {
    return
  }
  const children = Array.from(containerElement.children)
  let isExist = false
  children && children.map(e => {
    if (e.id === elementProductIdentifer && e.style_id === element.getAttribute('selectedColorId')) {
      isExist = true
    }

    if (e.style_id !== element.getAttribute('selectedColorId')) {
      if (e.id === elementProductIdentifer) {
        e && e.parentNode.removeChild(e)
      }
    }
  })

  // 已经存在了 不再次展示
  if (isExist) return

  const goods_id = element.getAttribute('selectedGoodsId')
  const style_id = element.getAttribute('selectedColorId')
  const size_id = element.getAttribute('selectedSizeId')
  // 参数不合法
  if (!goods_id || !style_id || !size_id) return

  const dataElement = document.createElement('div')
  dataElement.id = elementProductIdentifer
  dataElement.style.zIndex = 1
  const showElement = createRowElement(dataElement, '-')
  dataElement.style_id = style_id
  const showElement2 = createRowElement(dataElement, '')
  HTTP.productGetData(goods_id, style_id, size_id, (data) => {
    if (data) {
      if (data.has_virtual) {
        const result = data.stockTransit && data.stockTransit.split('[')
        showElement.innerHTML = result[0] + '<br>[' + result[1]
      } else {
        showElement.innerHTML = data.stockTransit
      }
      if (data.has_virtual) { showElement2.textContent = 'Virtual' }
    }
  })
  containerElement && containerElement.appendChild(dataElement)
}

const isLogin = () => {
  return document.cookie && document.cookie.includes('hasLogin=1;')
}

const beginTimer = () => {
  console.log('开始脚本====>')
  // 每秒检查 一次
  setInterval(() => {
    // 遍历所有节点,取所有的商品模块
    // 判断该商品模块是否已经有
    // 如果没有,则加入模块,并请求接口
    // 如果已经有了,则啥都不做
    if (isLogin()) {
    // 如果是首页商品不展示内容
    // 列表页面
      const elements = Array.from(document.getElementsByClassName(MOUSE_VISITED_CLASS_NAME))
      if (window.location.pathname != '/' && elements && elements.length > 0) {
        elements.map((element) => {
          let containerElement = getContainerElement(element)
          showListCover(containerElement)
        })
      }
      // 首页
      const indexElements = Array.from(document.getElementsByClassName(MOUSE_VISITED_CLASS_NAME_INDEX))
      if (indexElements && indexElements.length > 0) {
        indexElements.map((element) => {
          showHomeCover(element, element)
        })
      }
      // 详情页面
      const productElements = Array.from(document.getElementsByClassName(MOUSE_VISITED_CLASS_NAME_PRODUCT))
      if (productElements && productElements.length > 0) {
        productElements.map((element) => {
          showDetailCover(element, element)
        })
      }
    }
  }, 1000)
}

const main = () => {
  beginTimer()
}

main()


GM_addStyle(`
#chrome-extension-info-product{
    position: absolute;
    top: 0;
    bottom:0;
    right: 0;
    left: 0;
    height: 35px;
}

#chrome-extension-info {
    position: absolute;
    bottom: 0;
    right: 0;
    left: 0;
    min-height: 60px;
    background: rgba(255, 255, 255, 0.8);
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    padding: 10px 0px;
    min-width: 150px;
}

#chrome-extension-info .extension-container {
    min-width: calc((100% - 84px)/ 2);
    display: inline-block;
    font-size: 12px;
    color: #333;
    font-family: 'Montserrat SemiBold';
    line-height: 13px;
    text-align: left;
    margin-left: 20px;
    padding-left: 8px;
    position: relative;
    margin-bottom: 5px;
}

#chrome-extension-info .extension-container::before {
    content: '';
    position: absolute;
    left: -8px;
    top: 0px;
    width: 10px;
    height: 10px;
    border: 1px solid #FFFFFF;
    background: #F2CE99;
    border-radius: 10px;
    line-height: 13px;
}

#chrome-extension-info .extension-container:nth-child(2n+1)::before {
    background: #F2CE99;
    border: 1px solid #FFFFFF;
}

#chrome-extension-info .extension-container:nth-child(2n)::before {
    background: #93D1F5;
    border: 1px solid #FFFFFF;
}

#chrome-extension-info-product .extension-container{
    position: absolute;
    left: 0;
    width: 100%;
    height: 15px;
    text-align: center;
}

#chrome-extension-info-product .extension-container:nth-child(1){
    bottom:-15px;
    color: red;
}

#chrome-extension-info-product .extension-container:nth-child(2){
    top:-15px;
    color: blue;
}
`)