Greasy Fork

Greasy Fork is available in English.

Steam 轻便管理购物车

轻便管理购物车

当前为 2020-07-26 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

  // ==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);}}}`)
})()