Steam 轻便管理购物车

轻便管理购物车

目前為 2020-07-26 提交的版本,檢視 最新版本

  // ==UserScript==
  // @name         Steam 轻便管理购物车
  // @namespace    http://tampermonkey.net/
  // @version      0.4
  // @description  轻便管理购物车
  // @author       ku mi
  // @include      /https:\/\/store\.steampowered\.com\/(?!cart)\/*/
  // @match        https://steamcommunity.com/*
  // @grant        GM_xmlhttpRequest
  // @grant        GM_addStyle
  // ==/UserScript==
(() => {
class Cart {
  constructor() {
    this.sessionID = g_sessionID
    this.increment = 0
    this.firstFlag = true
    this.init()
  }
  init() {
    this.initCart()
    if (location.pathname.startsWith('/wishlist/')) {
      this.wishFun()
    } else {
      this.storeFun()
    }
  }
  setCookie() {
    const date = new Date();
    date.setTime(date.getTime() - 10 * 24 * 60 * 60 * 1000)
    const expires = "expires=" + date.toUTCString()
    document.cookie = 'shoppingCartGID=-1; ' + expires + '; path=/'
  }
  bindClick(el, fn) {
    el.addEventListener('click', fn)
  }
  initEvent() {
    let oldLineitem_gid = ''
    this.bindClick(this.toHide, e => {
      const flag = e.target.innerText === '显示'
      if (this.firstFlag) {
        this.firstFlag = false
        this.request({ url: 'https://store.steampowered.com/cart/', method: 'GET' }).then(res => this.getCartItem(res))
      }
      this.outWrapper.className = flag ? 'to_transform_show' : 'to_transform_hiden'
      e.target.innerText = flag ? '隐藏' : '显示'
    })
    this.bindClick(this.toRemoveAll, () => {
      if(location.host === 'steamcommunity.com') return ShowAlertDialog('提示','此页面无法移除所有购物车物品!!!','确定')
      ShowConfirmDialog('', '您确定要移除所有您购物车中的物品吗?', '是', '否').done(() => {
        this.setCookie()
        this.query('.cart_item', true, this.outWrapper).forEach(item => {
          item.className = 'cart_item cart_item_remove'
        })
        let time = setTimeout(() => {
          clearTimeout(time)
          this.cartWrapper.innerHTML = ''
          this.totalPrice.innerText = this.totalPrice.innerText.replace(/[\d\.,]/g, '') + ' ' + 0
        }, 300)
      })
    })
    this.bindClick(this.toCart, () => window.open('https://store.steampowered.com/cart/'))
    this.bindClick(this.outWrapper, async e => {
      const reamoveA = e.target
      const lineitem_gid = reamoveA.dataset.lineitem_gid
      if (!reamoveA.classList.contains('remove_link') || !lineitem_gid || oldLineitem_gid === lineitem_gid) return
      oldLineitem_gid = lineitem_gid
      await this.request({ url: 'https://store.steampowered.com/cart/', method: 'POST', data: `action=remove_line_item&sessionid=${this.sessionID}&lineitem_gid=${lineitem_gid}&cart=${this.cart}` })
      const cartItem = reamoveA.parentElement.parentElement.parentElement
      const removePrice = Number(reamoveA.previousElementSibling.innerText.match(/[\d\.,]+/)[0].replace(/[,\s]*/g, ''))
      const [, currency, priceNum] = this.totalPrice.innerText.match(/((?!\d).+?)([\d,\.\s]+)/)
      const rePrice = Number(priceNum.replace(/[,\s]*/g, ''))
      this.totalPrice.innerText = currency + ' ' + (rePrice - removePrice).toLocaleString()
      cartItem.className = 'cart_item cart_item_remove'
      let time = setTimeout(() => {
        clearTimeout(time)
        cartItem.remove()
      }, 300)
    })
  }
  initElement() {
    this.cartWrapper = this.query('.cart_wrapper', false, this.outWrapper)
    this.toCart = this.query('.to_cart', false, this.outWrapper)
    this.toRemoveAll = this.query('.to_removeAll', false, this.outWrapper)
    this.toHide = this.query('.to_hide', false, this.outWrapper)
    this.totalPrice = this.query('.mini_price', false, this.outWrapper)
    this.loading = this.query('.mini_loading', false, this.outWrapper)
  }
  debounce() {
    let time = true
    this.changeItem(this.wishContent)
    return () => {
      clearTimeout(time)
      time = setTimeout(() => {
        time = null
        this.changeItem(this.wishContent)
      }, 1000)
    }
  }
  changeItem() {
    ;[...this.wishContent.children].forEach(item => {
      let cart = this.query('.btn_medium:not(.already_change)', false, item)
      if (!cart) return
      const { appId } = item.dataset
      const { subs } = this.g_rgAppInfo[appId]
      const subId = subs.length ? `${subs[0].id}` : ''
      Object.assign(cart.dataset, { c_appid: appId, c_subid: subId })
      cart.href = 'javascript:void(0);'
      cart.classList.add('already_change')
    })
  }
  async request(data) {
       return new Promise((resolve, reject) => {
           this.increment++
           if (this.firstFlag) this.firstFlag = false
           this.loading.style.display = 'block'
           GM_xmlhttpRequest({
               ...data,
               headers: {
                   'Content-Type': 'application/x-www-form-urlencoded',
               },
               onload:({responseText}) => {
                 resolve(responseText)
                   if(--this.increment === 0) this.loading.style.display = 'none'
               }
           })
       })
   }
  create(el, pel) {
    const ele = document.createElement(el)
    pel.appendChild(ele)
    return ele
  }
  query(el, flag, pel = document) {
    return flag ? [...pel.querySelectorAll(el)] : pel.querySelector(el)
  }
  storeFun() {
    const bundleReg = /addBundleToCart\(\s?(\d+)(?:,\s1\s)?\)/
    const cartReg = /addToCart\(\s?(\d+)\)/
    let cartButton = this.query('.btn_green_steamui.btn_medium', true)
    if (!cartButton.length) cartButton = this.query('.btnv6_green_white_innerfade.btn_medium', true)
    if (!cartButton.length) return
      cartButton.forEach(item => {
      const subMatch = cartReg.exec(item.href)
      if (!subMatch) {
        if (item.href === 'javascript:addAllDlcToCart();') {
          item.dataset.c_dlcid = this.query(('[name="subid[]"]'), true).map(it => 'subid[]=' + it.value).join('&')
        } else {
          const bundleidMatch = bundleReg.exec(item.href)
          if (!bundleidMatch) return
          item.dataset.c_bundleid = bundleidMatch[1]
        }
      } else {
        item.dataset.c_subid = subMatch[1]
      }
      item.href = 'javascript:void(0);'
      this.bindClick(item, async e => {
        let target = e.target
        if (target.nodeName === 'SPAN') target = target.parentElement
        const { c_subid: sub, c_bundleid: bundleid, c_dlcid: dlcid } = target.dataset
        const res = await this.request({ url: 'https://store.steampowered.com/cart/', method: 'POST', data: `action=add_to_cart&${sub ? `subid=${sub}` : dlcid ? dlcid : `bundleid=${bundleid}`}&sessionid=${this.sessionID}` })
        this.getCartItem(res)
      })
    })
  }
  wishFun() {
    let time = setInterval(() => {
      this.wishContent = this.query('#wishlist_ctn')
      const wishList = this.wishContent.children
      if (!wishList.length) return
      clearInterval(time)
      this.g_rgAppInfo = g_rgAppInfo
      this.bindClick(this.wishContent, async (e) => {
        let target = e.target
        if (target.nodeName === 'SPAN') target = target.parentElement
        if (!target.classList.contains('already_change')) return
        const sub = target.dataset.c_subid
        if (!sub) return
        const res = await this.request({ url: 'https://store.steampowered.com/cart/', method: 'POST', data: `action=add_to_cart&subid=${sub}&sessionid=${this.sessionID}` })
        this.getCartItem(res)
      })
      document.onscroll = this.debounce()
    }, 2000)
  }
  getCartItem(htmlStr) {
    const cartIdReg = /<input type="hidden" name="sessionid" value="(\w+)">[\s\S]+?<input type="hidden" name="cart" value="(-1|\d+)">/
    const cartIdResule = htmlStr.match(cartIdReg)
    const [, sessionId, cartId] = htmlStr.match(cartIdReg)
    htmlStr = htmlStr.substring(cartIdResule.index)
    this.sessionID = sessionId
    this.cart = cartId
    let cartItemReg = /(<div class="cart_item">[\s\S]+?<div style="clear: left"><\/div>\s+<\/div>)/igm
    let matchItem = null
    let lastResult = null
    let cartHtml = ''
    while (matchItem = cartItemReg.exec(htmlStr)) {
      const [, str] = matchItem
      cartHtml += str
      lastResult = matchItem
    }
    this.totalPrice.innerHTML = (lastResult ? htmlStr.substring(lastResult.index) : htmlStr).match(/<div id="cart_estimated_total" class="price">([\s\S]+?)<\/div>/)[1].trim()
    this.cartWrapper.innerHTML = cartHtml
    const cartItemList = this.query('.cart_item', true, this.outWrapper)
    cartItemList.forEach(item => {
      item.classList.add('cart_item_add')
      this.query('a:not([href^=javascript])', true, item).forEach(it => (it.target = '_blank'))
      const removeLink = this.query('.remove_link', false, item)
      removeLink.dataset.lineitem_gid = removeLink.href.match(/javascript:removeFromCart\('(\d+)'\)/)[1]
      removeLink.href = 'javascript:void(0);'
    })
  }
  initCart() {
    this.outWrapper = this.create('div', document.body)
    this.outWrapper.id = 'mini_cart'
    this.outWrapper.innerHTML = `<div class="mini_ul"><div class="button_option"><button class="to_hide">显示</button><button class="to_cart">去购物车</button><button class="to_removeAll">移除所有</button></div><div class="cart_total_price"><div style="flex: 1;">预计总额</div><div class="mini_price"></div></div><div class="mini_loading"></div><div class="cart_wrapper"></div></div>`
    this.initElement()
    this.initEvent()
  }
}
new Cart()
GM_addStyle(`.cart_item_remove{animation:removeitem .3s forwards;transform-origin:center;}.cart_item_add{animation:additem .3s forwards;transform-origin:center;}@keyframes additem{from{transform:scale(0);}to{transform:scale(1);}}@keyframes removeitem{to{transform:scale(0);}}.mini_ul .cart_total_price{font-size:12px;line-height:50px;height:50px;display:flex;padding-right:20px;color:#fff;justify-content:space-between;}.mini_ul .cart_total_price .mini_price{flex:1;text-align:right;}.mini_ul .mini_loading{background-image:url(https://steamcommunity-a.akamaihd.net/public/images/login/throbber.gif);width:32px;height:32px;margin:0 auto 10px;display:none;}#mini_cart{border-radius:10px;position:fixed;width:300px;z-index:999;height:550px;right:0;bottom:0;background-color:rgba(0,0,0,.3);padding:0 15px 0 15px;box-sizing:border-box;transform:translateY(500px);}.mini_ul .cart_item{display:flex;margin-bottom:15px;font-size:12px;min-height:50px;}.mini_ul .cart_item_price.with_discount{padding:0;text-align:center;}.mini_ul .cart_item_price [class="price"]{color:#fff;}.mini_ul .cart_item_price{padding:0}.mini_ul .cart_item_img{margin:0;}.mini_ul .cart_item_price_container{order:1;width:40px;padding:0;text-align:center;}.mini_ul .original_price.price{text-decoration:line-through;color:#8F98A0;}.mini_ul .cart_item_desc{padding:0;margin:0 15px;display:flex;width:70px;flex-direction:column;}.mini_ul .cart_item_desc a{width:100%;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;}.mini_ul .cart_item_desc br{display:none;}.mini_ul .cart_item_desc_ext{text-overflow:ellipsis;overflow:hidden;white-space:nowrap;color:#ffcc6a;}.mini_ul .cart_item_img{flex-basis:120px;}.mini_ul .cart_wrapper{height:470px;overflow-y:auto;}.mini_ul .cart_wrapper::-webkit-scrollbar{height:12px;width:14px;background:transparent;z-index:12;overflow:visible;}.mini_ul .cart_wrapper::-webkit-scrollbar-thumb{width:10px;background-color:#434953;border-radius:10px;z-index:12;border:4px solid rgba(0,0,0,0);background-clip:padding-box;transition:background-color .32s ease-in-out;margin:4px;min-height:32px;min-width:32px;}.mini_ul .button_option{display:flex;justify-content:space-around;margin-top:10px;}.mini_ul .remove_link{text-decoration:none;color:#ffffff;}.mini_ul .button_option > button{border:none;outline:none;background-image:linear-gradient( to right,#47bfff 5%,#1a44c2 60%);color:#A4D7F5;font-size:12px;border-radius:7px;padding:5px 8px;width:70px;cursor:pointer;}.to_transform_hiden{animation:hidenAn .5s forwards;}.to_transform_show{animation:showAn .5s forwards;}@keyframes hidenAn{from{transform:translateY(0);}to{transform:translateY(500px);}}@keyframes showAn{from{transform:translateY(500px);}to{transform:translateY(0px);}}}`)
})()