CheapShark Steam Integration

Adds current pricing info from CheapShark for other stores to the Steam Store

// ==UserScript==
// @name CheapShark Steam Integration
// @description Adds current pricing info from CheapShark for other stores to the Steam Store
// @version 0.3.0
// @author Phlebiac
// @match https://store.steampowered.com/app/*
// @connect www.cheapshark.com
// @run-at document-end
// @noframes
// @license MIT; https://opensource.org/licenses/MIT
// @namespace Phlebiac/CheapShark
// @icon https://www.cheapshark.com/img/icons/round_114.png
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_xmlhttpRequest
// @grant unsafeWindow
// ==/UserScript==

;(async () => {
  'use strict'

  const BASE_URL = 'https://www.cheapshark.com';
  const API_URL = `${BASE_URL}/api/1.0/`;
  const METACRITIC_BASE = 'https://www.metacritic.com';

  let userPrefs = {
    open_in_new_tab: GM_getValue('open_in_new_tab', true),
    show_metacritic: GM_getValue('show_metacritic', true),
    store_info_cache: GM_getValue('store_info_cache', []),
  }

  const appId = getCurrentAppId();
  if (!appId) {
    return;
  }
  injectCSS();

  if (!userPrefs.store_info_cache.length) {
    log('Requesting store data...');
    GM_xmlhttpRequest({
      method: 'GET',
      url: `${API_URL}stores`,
      onload: buildStoreCache
    });
  } else {
    //log(`Using cache of ${userPrefs.store_info_cache.length} stores`);
  }

  GM_xmlhttpRequest({
    method: 'GET',
    url: `${API_URL}deals?steamAppID=${appId}&sortBy=Price`,
    onload: addDealsToStorePage
  })

  function getCurrentAppId() {
    const urlPath = window.location.pathname;
    const appId = urlPath.match(/\/app\/(\d+)/);
    if (appId === null) {
      log('Unable to get AppId from URL path:', urlPath);
      return false;
    }
    return appId[1];
  }

  function buildStoreCache(response) {
    if (response.status === 200) {
      try {
        var stores = JSON.parse(response.responseText);
        log(`Caching data for ${stores.length} stores`);
        stores.forEach(store => { userPrefs.store_info_cache[store.storeID] = store; });
        GM_setValue('store_info_cache', userPrefs.store_info_cache);
      } catch (err) {
        log('Unable to parse CheapShark response as JSON:', response);
        log('Javascript error:', err);
      }
    } else {
      log('Got unexpected HTTP code from CheapShark:', response.status);
    }
  }

  function addDealsToStorePage(response) {
    if (response.status === 200) {
      try {
        var deals = JSON.parse(response.responseText);
        //log(`Found ${deals.length} deals`);
      } catch (err) {
        log('Unable to parse CheapShark response as JSON:', response);
        log('Javascript error:', err);
      }
    } else {
      log('Got unexpected HTTP code from CheapShark:', response.status);
    }
    
    let target = document.querySelector('.game_purchase_action');
    if (target && deals.length > 0) {
      var bestDeal = deals[0];
      let node = Object.assign(document.createElement('span'), {
        className: 'cheapshark_deals_row btnv6_blue_hoverfade btn_medium'
      });
      if (userPrefs.show_metacritic && bestDeal.metacriticScore > 0) {
        node.appendChild(showMetaCritic(bestDeal));
      }
      node.appendChild(lowestPrice(bestDeal));
      if (deals.length > 1) {
        node.appendChild(listDeals(deals));
      }
      target.insertBefore(node, target.firstChild);
      target.insertBefore(preferencesDialog(), node);
    }
  }

  function showMetaCritic(deal) {
    let node = Object.assign(document.createElement('a'), {
      className: 'cheapshark_metacritic',
      href: `${METACRITIC_BASE}${deal.metacriticLink}`,
      title: 'View on MetaCritic',
      target: userPrefs.open_in_new_tab ? '_blank' : '_self'
    });
    node.appendChild(Object.assign(document.createElement('img'), {
      src: `${METACRITIC_BASE}/MC_favicon.png`,
      className: 'cheapshark_metacritic_img',
      height: 20
    }));
    node.appendChild(Object.assign(document.createElement('span'), {
      textContent: `${deal.metacriticScore}`,
      className: `cheapshark_metacritic_score`
    }));
    return node;
  }

  function lowestPrice(deal) {
    var store = userPrefs.store_info_cache[deal.storeID];
    let node = Object.assign(document.createElement('a'), {
      className: 'cheapshark_lowest',
      href: `${BASE_URL}/redirect?dealID=${deal.dealID}`,
      title: `$${deal.salePrice} on ${store.storeName}`,
      target: userPrefs.open_in_new_tab ? '_blank' : '_self'
    });
    node.appendChild(Object.assign(document.createElement('span'), {
      textContent: `Lowest: $${deal.salePrice}`,
      className: `cheapshark_lowest_price`,
    }));
    node.appendChild(Object.assign(document.createElement('img'), {
      src: `${BASE_URL}${store.images.icon}`,
      className: 'cheapshark_lowest_img' 
    }));
    return node;
  }

  function listDeals(deals) {
    // Build out a menu option for each store deal
    let dealOptions = '';
    for (const deal of deals) {
      var store = userPrefs.store_info_cache[deal.storeID];
      dealOptions = `${dealOptions}
				<div class="cheapshark_deal_option queue_menu_option">
					<div class="cheapshark_valign queue_menu_option_label">
						<a href="${BASE_URL}/redirect?dealID=${deal.dealID}" class="option_title">
						  <img class="cheapshark_valign" src="${BASE_URL}${store.images.icon}"> $${deal.salePrice} on ${store.storeName}
						</a>
					</div>
				</div>`;
    }
    return Object.assign(document.createElement('div'), {
      className: 'cheapshark_deals_dropdown queue_control_button queue_btn_menu',
      innerHTML: `
        <div class="queue_menu_arrow">
					<span><img src="https://store.cloudflare.steamstatic.com/public/images/v6/btn_arrow_down_padded.png"></span>
			  </div>
			  <div class="cheapshark_menu_flyout queue_menu_flyout">
					<div class="queue_flyout_content">${dealOptions}
				  </div>
			  </div>`
    });
  }

  function preferencesDialog() {
    const container = Object.assign(document.createElement('span'), {
      className: 'cheapshark_prefs_icon',
      title: 'Preferences for CheapShark Steam Integration',
      textContent: '⚙'
    })

    container.addEventListener('click', () => {
      const html = `
      <div class="cheapshark_prefs">
        <div class="newmodal_prompt_description">
          New preferences will only take effect after you refresh the page.
        </div>
        <blockquote>
          <div>
            <input type="checkbox" id="cheapshark_open_in_new_tab" ${ userPrefs.open_in_new_tab ? 'checked' : '' } />
            <label for="cheapshark_open_in_new_tab">Open links in a new tab</label>
          </div>
          <div>
            <input type="checkbox" id="cheapshark_show_metacritic" ${ userPrefs.show_metacritic ? 'checked' : '' } />
            <label for="cheapshark_show_metacritic">Show MetaCritic score and link</label>
          </div>
        </blockquote>
      </div>`

      unsafeWindow.ShowDialog('CheapShark on Steam Prefs', html);

      // Handle preferences changes
      const inputs = document.querySelectorAll('.cheapshark_prefs input');

      for (const input of inputs) {
        input.addEventListener('change', event => {
          const target = event.target;
          const prefName = target.id.replace('cheapshark_', '');

          switch (target.type) {
            case 'text':
              userPrefs[prefName] = target.value;
              GM_setValue(prefName, target.value);
              break;
            case 'checkbox':
              userPrefs[prefName] = target.checked;
              GM_setValue(prefName, target.checked);
              break;
            default:
              break;
          }
        })
      }
    })

    return container;
  }

  function injectCSS() {
    GM_addStyle(`
      .cheapshark_deals_row {
        border-style: solid;
        border-color: black;
        //padding: 0 0 2px 5px;
        padding: 5px 5px 5px 5px;
        background: #4d4b49;
      }
      .cheapshark_deals_row a:hover { 
        color: white;
      }
      .cheapshark_metacritic {
        margin-right: 6px;
      }
      .cheapshark_metacritic_img {
        vertical-align: middle;
        margin-right: 4px;
      }
      .cheapshark_metacritic_score {
        font-size: 16px;
        vertical-align: middle;
      }
      .cheapshark_lowest {
        padding: 0 6px 0 6px;
      }
      .cheapshark_lowest_img {
        vertical-align: middle;
      }
      .cheapshark_lowest_price {
        font-size: 14px;
        vertical-align: middle;
        padding-right: 5px;
      }
      .cheapshark_deals_dropdown {
        //margin: -6px 0 -6px 0;
      }
      .cheapshark_menu_flyout {
        position: relative !important;
        width: unset !important;
        padding: 6px 6px;
        margin: -34px -6px -6px -240px;
        //position: absolute;
        //left: 0px;
        //margin-top: 5px;
      }
      .cheapshark_deal_option {
        padding: 2px !important;
      }
      .cheapshark_valign {
        vertical-align: middle;
      }
      .cheapshark_prefs_icon {
        font-size: 20px;
        padding: 0 2px;
        //vertical-align: top;
        cursor: pointer;
      }
      .cheapshark_prefs input[type="checkbox"], .cheapshark_prefs label {
        line-height: 20px;
        vertical-align: middle;
        display: inline-block;
        color: #66c0f4;
        cursor: pointer;
      }
      .cheapshark_prefs blockquote {
        margin: 15px 0 5px 10px;
      }`)
  }

  function log() {
    console.log('[CheapShark Steam Integration]', ...arguments)
  }
})()

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址