// ==UserScript==
// @name Bundle Helper
// @version 2.0.4
// @author Dillon Regimbal
// @namespace https://dillonr.com
// @description Highlights games you already own on Steam, on other sites. Modified from https://greasyfork.org/scripts/16105-bundle-helper/
// @match *://cubicbundle.com/*
// @match *://dailyindiegame.com/*
// @match *://forums.steampowered.com/forums/showthread.php?*
// @match *://www.gogobundle.com/latest/bundles/*
// @match *://otakumaker.com/*
// @match *://www.otakumaker.com/*
// @match *://otakubundle.com/latest/bundles/*
// @match *://steamcommunity.com/*/home*
// @match *://steamcommunity.com/groups/*/announcements*
// @match *://steamcompanion.com/gifts/*
// @match *://steamground.com/*
// @match *://store.steampowered.com/
// @match *://store.steampowered.com/account/notinterested/*
// @match *://store.steampowered.com/app/*
// @match *://store.steampowered.com/widget/*
// @match *://store.steampowered.com/search/*
// @match *://whosgamingnow.net/*
// @match *://www.bunchkeys.com/*
// @match *://www.bundlekings.com/*
// @match *://www.fanatical.com/*
// @match *://www.dailyindiegame.com/*
// @match *://www.gamebundle.com/*
// @match *://www.hrkgame.com/*
// @match *://www.humblebundle.com/*
// @match *://www.indiegala.com/*
// @match *://www.orlygift.com/*
// @match *://www.reddit.com/r/*/comments/*
// @match *://www.superduperbundle.com/*
// @match *://www.sgtools.info/*
// @match *://steamkeys.ovh/*
// @match *://steamdb.info/*
// @match *://itch.io/*
// @match *://*.itch.io/*
// @run-at document-start
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// @connect store.steampowered.com
// @connect www.hrkgame.com
// @connect www.fanatical.com
// @connect www.steamgifts.com
// @icon https://store.steampowered.com/favicon.ico
// @license GPL-3.0-only
// @noframes
// ==/UserScript==
// Connect to store.steampowered.com to get owner info
// Connect to www.hrkgame.com and www.fanatical.com to get Steam ID of each products
// Connect to www.steamgifts.com to get bundle threads
// License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html
// Since 2016-01-10
// https://greasyfork.org/scripts/16105-bundle-helper/
// Since 2020-05-21
// https://greasyfork.org/scripts/403878-bundle-helper
// https://github.com/dregimbal/UserScripts/blob/master/BundleHelper.user.js
(function () {
'use strict'
let write_console_messages = true
let name_profile_json = 'bh_profile_json'
let name_profile_time = 'bh_profile_time'
let owned_item_class = 'bh_owned'
let steam_profile = getSteamProfile()
let default_steam_url_selector = 'a[href*=\'store.steampowered.com/\']'
let divButton = document.createElement('div')
function attachOnLoad(callback) {
window.addEventListener('load', function (e) {
callback()
})
}
function attachOnReady(callback) {
document.addEventListener('DOMContentLoaded', function (e) {
callback()
})
}
function writeConsoleMessage(message) {
if (write_console_messages) {
console.log(message)
}
}
let timeoutList = []
let intervalList = []
function setTimeoutCustom(func, tm, params) {
let id = setTimeout(func, tm, params)
timeoutList.push(id)
return id
}
function clearTimeoutAll() {
for (let i = 0; i < timeoutList.length; i++) {
clearTimeout(timeoutList[i])
}
}
function clearIntervalAll() {
for (let i = 0; i < intervalList.length; i++) {
clearInterval(intervalList[i])
}
}
function getUnixTimestamp() {
return parseInt(Date.now() / 1000)
}
function isProfileCacheExpired() {
let isExpired = false
let timestampExpired = 15 * 60
let profileTimestamp = GM_getValue(name_profile_time, 0)
let profileTimestampDiff = getUnixTimestamp() - profileTimestamp
if (profileTimestampDiff > timestampExpired) {
isExpired = true
}
if (!isExpired) {
writeConsoleMessage('Profile Cache Updated ' + profileTimestampDiff + 's ago')
} else {
writeConsoleMessage('Profile Cache Expired: ' + profileTimestampDiff)
}
return isExpired
}
function setProfileCache(json) {
GM_setValue(name_profile_json, json)
GM_setValue(name_profile_time, getUnixTimestamp())
}
function getSteamProfile() {
if (isProfileCacheExpired()) {
updateSteamProfileCache()
}
return GM_getValue(name_profile_json, 0)
}
function markOwned(query, getElementCallback, getProductIdCallback
, classOwned, classNotInterested, classWished, getCountCallback) {
if (!document.querySelector(query)) {
// writeConsoleMessage("markOwned: empty");
return
}
if (!getElementCallback) {
getElementCallback = function (ele, type) {
// type -> 1: Owned, 2: Ignored, 3: Wishlist
return ele
}
}
if (!getProductIdCallback) {
getProductIdCallback = function (ele) {
return ele.getAttribute('href')
}
}
if (!getCountCallback) {
getCountCallback = function (appCount, subCount, appOwned, subOwned) {
}
}
if (!classOwned) {
classOwned = ''
}
if (!classNotInterested) {
classNotInterested = ''
}
if (!classWished) {
classWished = ''
}
let rgxId = /[0-9]{3,}/g
let rgxApp = /((:\/\/(store\.steampowered\.com|steamcommunity\.com|steamdb\.info)(\/agecheck)?\/app|\/steam\/apps)\/[0-9]+|^[0-9]{3,}$)/i
let rgxSub = /(:\/\/(store\.steampowered\.com|steamdb\.info)\/sub|\/steam\/subs)\/[0-9]+/i
let markFromJson = function (dataRes) {
if (!dataRes) {
writeConsoleMessage('markFromJson: empty')
return
}
let countOwned = [0, 0]
let countAll = [0, 0]
let eleApps = document.querySelectorAll(query)
writeConsoleMessage(eleApps)
for (let i = 0; i < eleApps.length; i++) {
let attrHref = getProductIdCallback(eleApps[i])
let ids = attrHref.match(rgxId)
if (ids) {
// writeConsoleMessage('Matched ID "' + ids[0] + '" from url: ' + attrHref)
let valId = parseInt(ids[0])
if (rgxApp.test(attrHref)) {
if (isAppOwned(valId)) {
let ele = getElementCallback(eleApps[i], 1)
if (ele && classOwned !== '') {
ele.classList.add(classOwned)
}
countOwned[0]++
} else if (isAppWishlisted(valId)) {
let ele = getElementCallback(eleApps[i], 3)
if (ele && classWished !== '') {
ele.classList.add(classWished)
}
} else if (isAppIgnored(valId)) {
let ele = getElementCallback(eleApps[i], 2)
if (ele && classNotInterested !== '') {
ele.classList.add(classNotInterested)
}
} else {
// writeConsoleMessage('App: Unowned - https://store.steampowered.com/app/' + valId + '/')
}
countAll[0]++
} else if (rgxSub.test(attrHref)) {
if (steam_profile.rgOwnedPackages.indexOf(valId) > -1) {
writeConsoleMessage('Sub: owned - https://store.steampowered.com/sub/' + valId + '/')
let ele = getElementCallback(eleApps[i], 1)
if (ele && classOwned !== '') {
ele.classList.add(classOwned)
}
countOwned[1]++
} else {
// writeConsoleMessage('Sub: not owned - https://store.steampowered.com/sub/' + valId + '/')
}
countAll[1]++
} else {
writeConsoleMessage('Cannot determine url type: ' + attrHref)
}
} else {
writeConsoleMessage('Cannot match ID from url: ' + attrHref)
}
}
writeConsoleMessage('App: Owned ' + countOwned[0] + '/' + countAll[0])
writeConsoleMessage('Sub: Owned ' + countOwned[1] + '/' + countAll[1])
getCountCallback(countAll[0], countAll[1], countOwned[0], countOwned[1])
}
markFromJson(steam_profile)
}
function updateSteamProfileCache() {
GM_xmlhttpRequest(
{
method: 'GET',
url: 'https://store.steampowered.com/dynamicstore/userdata/?t=' + getUnixTimestamp(),
onload: function (response) {
writeConsoleMessage('Steam User Data: ' + response.responseText.length + ' bytes')
let dataRes = JSON.parse(response.responseText)
setProfileCache(dataRes)
steam_profile = dataRes
}
})
}
// eslint-disable-next-line no-unused-vars
function createCacheResetButton() {
let divCacheResetButton = document.createElement('div')
divCacheResetButton.classList.add('bh_button')
divCacheResetButton.id = 'bh_cacheReset'
let cacheResetA = document.createElement('a')
cacheResetA.setAttribute('onclick', 'return false;')
cacheResetA.textContent = 'Reset Bundle Helper Cache'
divCacheResetButton.appendChild(cacheResetA)
document.body.appendChild(divCacheResetButton)
divCacheResetButton.addEventListener('click',
function () {
updateSteamProfileCache()
})
}
function addMarkBtnHandler(onClickFunction, argsArray) {
if (!document.body.contains(divButton)) {
document.body.appendChild(divButton)
}
divButton.addEventListener('click', () => {
onClickFunction.apply(null, argsArray)
})
}
function setElementOwned(element) {
if (typeof element !== 'undefined' && element !== null) {
element.classList.add(owned_item_class)
}
}
/**
* @description Checks the Steam game's ID against the owned apps
* @param {number} steamID The ID to check
* @returns {boolean} True when the game is owned
*/
function isAppOwned(steamID) {
if (steam_profile.rgOwnedApps.includes(parseInt(steamID))) {
writeConsoleMessage('App: Owned - https://store.steampowered.com/app/' + steamID + '/')
return true
}
// writeConsoleMessage('App: Unowned - https://store.steampowered.com/app/' + steamID + '/')
return false
}
/**
* @description Checks the Steam game's ID against the wishlisted apps
* @param {number} steamID The ID to check
* @returns {boolean} True when the game is wishlisted
*/
function isAppWishlisted(steamID) {
if (steam_profile.rgWishlist.includes(parseInt(steamID))) {
writeConsoleMessage('App: Wishlisted - https://store.steampowered.com/app/' + steamID + '/')
return true
}
return false
}
/**
* @description Checks the Steam game's ID against the ignored apps
* @param {number} steamID The ID to check
* @returns {boolean} True when the game is ignored
*/
function isAppIgnored(steamID) {
if (typeof steam_profile.rgIgnoredApps[steamID] !== 'undefined') {
writeConsoleMessage('App: Ignored - https://store.steampowered.com/app/' + steamID + '/')
return true
}
return false
}
/**
* @description Parses a string for a Steam game ID
* @param {string} str The string/URL that contains the Steam game ID
* @returns {number} Steam game ID
*/
function getSteamIDFromString(str) {
let rgxId = /[0-9]{3,}/g
let matches = str.match(rgxId)
if (matches) {
return parseInt(matches[0])
}
return null
}
/**
* Searches the document for Steam game ownership
* @param {string|null} steamLinkSelector The CSS selector to match Steam links
* @param {HTMLElement} elementToMark The element to mark as owned
* @returns {undefined}
*/
function markBySteamLinkSelector(steamLinkSelector, elementToMark) {
let selectorQuery
if (typeof steamLinkSelector === 'undefined' || steamLinkSelector === null) {
selectorQuery = default_steam_url_selector
} else {
selectorQuery = steamLinkSelector
}
document.querySelectorAll(selectorQuery).forEach(steamStoreLink => {
let steamID = getSteamIDFromString(steamStoreLink.href)
if (steamID !== null) {
if (isAppOwned(steamID)) {
if (typeof elementToMark === 'undefined' || elementToMark === null) {
// No element passed, mark the link element itself
setElementOwned(steamStoreLink)
} else if (typeof elementToMark === 'function') {
// Function passed, call the function passing in the link element
let element = elementToMark(steamStoreLink)
setElementOwned(element)
} else {
// Element passed, attempt to mark
setElementOwned(elementToMark)
}
}
}
})
}
/**
* Checks a page for Steam game ownership
* @param {string} storePageUrl The store page that contains the Steam link
* @param {string|null} steamLinkSelector The CSS selector to match Steam links
* @param {HTMLElement} elementToMark The element to mark as owned
* @returns {undefined}
*/
function markByStorePageUrl(storePageUrl, steamLinkSelector, elementToMark) {
let selector
if (typeof steamLinkSelector === 'undefined' || steamLinkSelector === null) {
selector = default_steam_url_selector
} else {
selector = steamLinkSelector
}
GM_xmlhttpRequest({
method: 'GET',
url: storePageUrl,
onload: function (response) {
let parser = new DOMParser()
let storePage = parser.parseFromString(response.responseText, 'text/html')
let steamLink = storePage.querySelector(selector)
if (steamLink !== null) {
let steamID = getSteamIDFromString(steamLink.href)
if (steamID !== null) {
if (isAppOwned(steamID)) {
setElementOwned(elementToMark)
}
}
} else {
writeConsoleMessage(`No steam links found on page "${storePageUrl}" with selector "${selector}"`)
}
}
})
return
}
/**
* Checks all matching links for Steam game ownership
* @param {string} storePageSelector The CSS selector to match store links
* @param {string} steamLinkSelector The CSS selector to match Steam links
* @param {HTMLElement} elementToMark The element to mark as owned
* @returns {undefined}
*/
function markByStorePageSelector(storePageSelector, steamLinkSelector, elementToMark) {
let storePageLinkElements = document.querySelectorAll(storePageSelector)
storePageLinkElements.forEach(storePageLinkElement => {
if (typeof elementToMark === 'undefined' || elementToMark === null) {
markByStorePageUrl(storePageLinkElement.href, steamLinkSelector, storePageLinkElement)
} else if (typeof elementToMark === 'function') {
// Function passed, call the function passing in the link element
let element = elementToMark(storePageLinkElement)
markByStorePageUrl(storePageLinkElement.href, steamLinkSelector, element)
} else {
writeConsoleMessage(storePageLinkElement)
markByStorePageUrl(storePageLinkElement, steamLinkSelector, elementToMark)
}
})
return
}
async function GetSteamAppList() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
GM_xmlhttpRequest({
method: 'GET',
url: 'https://api.steampowered.com/ISteamApps/GetAppList/v2/',
onload: response => {
setTimeout(function () {
if (response.status !== 200) {
reject([
response.status,
response.statusText,
response.readyState,
response.responseHeaders,
response.finalUrl
].join(', '))
} else {
let appList = JSON.parse(response.responseText).applist.apps
appList.forEach(app => {
app.name = app.name.toLowerCase().replace(/[^a-z0-9]/g, '')
})
resolve(appList)
}
}, 0)
}
})
}, 0)
})
}
function main() {
if (window !== window.parent) {
// https://developer.mozilla.org/en-US/docs/Web/API/Window/parent
// Don't run inside of a frame
return
}
if (!divButton) {
divButton = document.createElement('div')
}
divButton.classList.add('bh_button')
divButton.id = 'bh_markOwned'
let eleA = document.createElement('a')
eleA.setAttribute('onclick', 'return false;')
eleA.textContent = 'Mark Owned'
divButton.appendChild(eleA)
// Create button to refresh profile details
// createCacheResetButton()
GM_addStyle(
' .bh_button { '
+ ' border-radius: 2px; border: medium none; padding: 10px; display: inline-block; '
+ ' cursor: pointer; background: #67C1F5 none repeat scroll 0% 0%; '
+ ' width: 120px; text-align: center; } '
+ ' .bh_button a { '
+ ' text-decoration: none !important; color: #FFF !important; '
+ ' padding: 0px 2px; } '
+ ' .bh_button:hover a { '
+ ' color: #0079BF !important; } '
+ ' .bh_button, .bh_button a { '
+ ' font-family: Verdana; font-size: 12px; '
+ ' line-height: 16px; } '
+ ' .bh_owned { background-color: #7CA156 !important; '
+ ' transition: background 500ms ease 0s; } '
+ ' #bh_markOwned { '
+ ' position: fixed; right: 20px; bottom: 20px; z-index: 33; } '
+ ' #bh_cacheReset { '
+ ' position: fixed; right: 20px; bottom: 60px; z-index: 33; } '
+ ' #bh_OpenLib { '
+ ' position: fixed; right: 20px; bottom: 65px; z-index: 33; } '
)
let url = document.documentURI
if (url.includes('hrkgame.com')) {
GM_addStyle(
' .bh_owned { background-color: #2B823A !important;/* background-color: #97BA22 !important;*/ } '
+ ' #bh_markOwned { bottom: 40px !important; } '
+ ' #bh_cacheReset { bottom: 80px !important; } '
+ '.catalog.ui.items .item.bh_owned .content a, .catalog.ui.items .item.bh_owned .content .description {color: #333 !important;}'
+ '.hrktable_content a.browse_catalogues_items div.label.bh_owned { background-color: #2B823A!important }'
)
if (url.includes('/randomkeyshop/make-bundle')) {
let onClickFunction = function () {
document.querySelectorAll('#result div.header[data-href*=\'/games/product/\']').forEach(link => {
markByStorePageUrl(link.getAttribute('data-href'), 'a.item[href*=\'store.steampowered.com/\']', element => element.parentElement.parentElement)
})
}
addMarkBtnHandler(onClickFunction)
} else if (url.includes('/games/products/?search')) {
addMarkBtnHandler(markByStorePageSelector, ['.item a.header[href*=\'/games/product/\']', 'a.item[href*=\'store.steampowered.com/\']', element => element.parentElement.parentElement])
} else if (url.includes('/games/product/')) {
addMarkBtnHandler(markBySteamLinkSelector, ['a.item[href*=\'store.steampowered.com/\']', document.querySelector('.ui.maincontainer')])
} else {
let onClickFunction = function () {
markByStorePageSelector('.offer_column a[href*=\'/games/product/\']', 'a.item[href*=\'store.steampowered.com/\']', element => element.parentElement)
document.querySelectorAll('a.browse_catalogues_items[href*=\'/games/product/\']').forEach(link => {
if (link.textContent.includes('Steam')) {
let elementToMark = link.querySelector('div.label')
markByStorePageUrl(link.href, 'a.item[href*=\'store.steampowered.com/\']', elementToMark)
}
})
}
addMarkBtnHandler(onClickFunction)
}
} else if (url.includes('itch.io')) {
if (url.includes('/my-collections') || url.includes('/my-purchases') || url.includes('/games') || url.includes('/s/') || url.includes('/c/') || url.includes('/b/')) {
GM_addStyle(
' .grid_outer .game_grid_widget .game_cell.bh_owned span, .grid_outer .game_grid_widget .game_cell.bh_owned div, .grid_outer .game_grid_widget .game_cell.bh_owned a { color: #ffffff !important; } '
)
addMarkBtnHandler(() => {
let storePageLinkElements = document.querySelectorAll('.game_cell_data a.game_link')
storePageLinkElements.forEach(storePageLinkElement => {
if (!storePageLinkElement.href.includes('/b/')) {
// Don't search bundle pages for Steam links
markByStorePageUrl(storePageLinkElement.href, default_steam_url_selector, storePageLinkElement.parentElement.parentElement.parentElement)
}
})
})
} else if (url.includes('/recommendations')) {
GM_addStyle(
' .index_game_cell_widget.game_cell.bh_owned span, .index_game_cell_widget.game_cell.bh_owned div, .index_game_cell_widget.game_cell.bh_owned a.user_link { color: #ffffff; } '
)
addMarkBtnHandler(markByStorePageSelector, ['a.title', default_steam_url_selector, element => element.parentElement.parentElement])
}
} else if (url.includes('fanatical.com')) {
GM_addStyle(
' .bh_owned { background-color: #0c6c22 !important; } '
+ ' .bh_owned .card-body div.card-body { background-color: #0c6c22 !important; } '
+ ' .bh_owned .card-body { background-color: #0c6c22; } '
)
if (url.includes('/game/')) {
addMarkBtnHandler(markBySteamLinkSelector, [default_steam_url_selector, () => document.querySelector('.details-content-container')])
} else if (url.includes('/bundle/')) {
let obTarget_root = document.querySelector('#root')
if (obTarget_root) {
let tmOb_root = -1
let obMu_root = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (mutation.type !== 'attributes'
|| mutation.target.tagName === 'TR') {
clearTimeout(tmOb_root)
tmOb_root = setTimeoutCustom(function () {
markBySteamLinkSelector(default_steam_url_selector, element => element.parentElement
.parentElement.parentElement.parentElement)
}, 200)
}
})
})
let obConfig_root = { childList: true, subtree: true }
obMu_root.observe(obTarget_root, obConfig_root)
}
} else if (url.includes('/pick-and-mix/')) {
let onClickFunction = function () {
let hook = __REACT_DEVTOOLS_GLOBAL_HOOK__
let rootFragmentFiber = Array.from(hook.getFiberRoots(1))[0].current
let rootCompFiber = rootFragmentFiber.child
let rootComp = rootCompFiber.stateNode
let state = rootComp.props.store.getState()
let bundles = [...state.pickAndMix.all]
bundles.forEach(bundle => {
let productsArr = bundle.products
let ownedGames = []
productsArr.forEach(product => {
let id = product._id
if (isAppOwned(id)) {
writeConsoleMessage('You own game ID: ' + id + ' - "' + product.name + '"')
ownedGames.push(product.name)
}
})
if (ownedGames.length > 0) {
document.querySelectorAll('.card-overlay p').forEach(p => {
if (ownedGames.includes(p.textContent)) {
setElementOwned(p.parentElement.parentElement.parentElement.parentElement)
}
})
}})
}
addMarkBtnHandler(onClickFunction)
}
let onClickFunction = function () {
let timeouts = []
let gameUrls = []
document.querySelectorAll('a[href*=\'/game/\']').forEach(game => {
if (gameUrls.includes(game.href)) {
return
}
gameUrls.push(game.href)
timeouts.push(function () {
let gamePage = game.href.replace('/en', '').replace('/game/', '/api/products/') + '/en'
GM_xmlhttpRequest({
method: 'GET',
headers: {
'Host': 'www.fanatical.com',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:76.0) Gecko/20100101 Firefox/76.0',
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
'Referer': game.href
},
url: gamePage,
onload: function (response) {
// writeConsoleMessage('status ' + response.status + ' ' + gamePage)
if (response.status === 200) {
let apiResponse = JSON.parse(response.responseText)
if (typeof apiResponse.steam.id !== 'undefined') {
if (isAppOwned(apiResponse.steam.id)) {
setElementOwned(game.parentElement.parentElement.parentElement.parentElement)
}
}
if (timeouts.length > 0) {
setTimeout(timeouts.pop(), 50)
}
} else if (timeouts.length > 0) {
setTimeout(timeouts.pop(), 400)
}
}
})
})
})
setTimeout(timeouts.pop(), 100)
}
addMarkBtnHandler(onClickFunction)
} else if (url.includes('reddit.com')) {
GM_addStyle(
' .bh_owned , .md .bh_owned code { background-color: #DFF0D8 !important; } '
+ ' li > .bh_owned, div > p > .bh_owned { padding: 0px 2px 0px 2px; } '
)
addMarkBtnHandler(markOwned, ['td > a[href*=\'store.steampowered.com/\']', function (ele) {
return ele.parentElement.parentElement
}, null, 'bh_owned'])
setTimeout(function () {
markOwned('td > a[href*=\'store.steampowered.com/\']', function (ele) {
return ele.parentElement.parentElement
}, null, 'bh_owned')
markOwned('li > a[href*=\'store.steampowered.com/\']', function (ele) {
return ele.parentElement
}, null, 'bh_owned')
markOwned('li > p > a[href*=\'store.steampowered.com/\']', function (ele) {
return ele.parentElement.parentElement
}, null, 'bh_owned')
markOwned('div > p > a[href*=\'store.steampowered.com/\']'
, null, null, 'bh_owned')
}, 1000)
} else if (url.includes('indiegala.com')) {
GM_addStyle(
' #bh_markOwned {bottom: 70px !important;}'
+ ' .bh_owned, .bh_owned .bundle-item-trading { background-color: rgba(125, 174, 45, 0.9) !important; } '
+ ' .ig-bundle { padding-left: 3px; padding-right: 3px; margin-bottom: 3px; } '
+ ' .bh_owned.ig-bundle { background-color: rgba(125, 174, 45) !important; } '
+ ' .bh_owned.ig-bundle .bundle-item-trading { background-color: rgba(125, 174, 45, 0) !important; } '
+ ' .bh_owned .add-info-button-cont .left, .bh_owned .add-info-button-cont .palette-background-2 { '
+ ' background-color: #7DAE2D !important; } '
+ ' .bh_owned .add-info-button-cont .right .inner-info, .bh_owned .add-info-button-cont .right .palette-border-2 { '
+ ' border-color: #7DAE2D !important; } '
+ ' .bh_owned.medium-game .game-cover-medium { border: 3px solid #7DAE2D; background-color: rgba(125, 174, 45, 0.4); } '
+ ' .bh_owned.game-data-cont { background-color: #76AD1C !important; } '
+ ' .bundle-item-trading-cards-cont span { opacity: 0.7; } '
+ ' .span-title .title_game, .span-title .title_drm, .span-title .title_music { '
+ ' line-height: 43px !important; margin: 10px 0px 10px 15px !important; '
+ ' padding-left: 10px !important; border-radius: 3px !important; } '
+ ' .medium-game { min-height: 146px; } '
)
// Insert email to bundle section
let countRetryEmail = 10
let tmRetryEmail = setInterval(function () {
let eleEmail = document.querySelector('.account-email')
let eleInput = document.querySelector('.email-input')
if (eleEmail && eleInput) {
let email = eleEmail.textContent.trim()
if (email !== '') {
eleInput.value = email
clearInterval(tmRetryEmail)
}
}
if (countRetryEmail < 0) {
clearInterval(tmRetryEmail)
}
countRetryEmail--
}, 3000)
// Change title
let countRetryTitle = 10
let tmRetryTitle = setInterval(function () {
let elesPrice = document.querySelectorAll('.bundle-claim-phrase')
for (let i = elesPrice.length - 1; i > -1; i--) {
let elePrice = elesPrice[i].querySelector('span')
if (elePrice) {
let price = elePrice.textContent.trim()
if (price.indexOf('$') === 0) {
document.title = price + ' ' + document.title
clearInterval(tmRetryTitle)
break
}
}
}
if (countRetryTitle < 0) {
clearInterval(tmRetryTitle)
}
countRetryTitle--
}, 3000)
if (url.includes('indiegala.com/store/') || url.includes('indiegala.com/games') || url === 'https://www.indiegala.com/') {
let onClickFunction = function () {
let gameBrowserLinks = document.querySelectorAll('a.main-list-item-clicker')
for (let i = 0; i < gameBrowserLinks.length; i++) {
let steamID = getSteamIDFromString(gameBrowserLinks[i].href)
if (steamID !== null) {
if (isAppOwned(steamID)) {
setElementOwned(gameBrowserLinks[i].parentElement)
}
}
}
let smallListLinks = document.querySelectorAll('a.fit-click')
for (let i = 0; i < smallListLinks.length; i++) {
let steamID = getSteamIDFromString(smallListLinks[i].href)
if (steamID !== null) {
if (isAppOwned(steamID)) {
setElementOwned(smallListLinks[i].parentElement.querySelector('.item-inner'))
}
}
}
}
addMarkBtnHandler(onClickFunction)
}
} else if (url.includes('orlygift.com')) {
addMarkBtnHandler(markByStorePageSelector, ['a[href*=\'/games/\']', default_steam_url_selector, element => element.parentElement])
} else if (url.includes('cubicbundle.com')) {
GM_addStyle(
' .bh_owned { background-color: #91BA07 !important; } '
)
addMarkBtnHandler(markOwned, ['.price a[href*=\'store.steampowered.com/\']', function (ele) {
return ele.parentElement.parentElement.parentElement.parentElement.parentElement
}, null, 'bh_owned'])
} else if (url.includes('dailyindiegame.com')) {
GM_addStyle(
' .bh_owned, .bh_owned a, .bh_owned a:not(:visited) .DIG2content { color: #202020 !important; } '
)
let onClickFunction = function () {
let markMap = [{
selector: '.DIG-content a[href*=\'store.steampowered.com/\']',
callback: function (ele) {
return ele.parentElement
.parentElement.parentElement
.parentElement.parentElement
}
},
{
selector: '.DIG2content a[href*=\'store.steampowered.com/\']',
callback: function (ele) {
return ele.parentElement.parentElement
}
},
{
selector: '.DIG3_14_Gray a[href*=\'store.steampowered.com/\']',
callback: function (ele) {
return ele.parentElement.parentElement.parentElement
}
}]
for (let i = 0; i < markMap.length; i++) {
if (document.querySelectorAll(markMap[i].selector).length > 0) {
markOwned(markMap[i].selector, markMap[i].callback, null, 'bh_owned')
}
}
}
addMarkBtnHandler(onClickFunction)
} else if (url.includes('bundlekings.com')) {
addMarkBtnHandler(markOwned, ['.content-wrap a[href*=\'store.steampowered.com/\']', function (ele) {
return ele.parentElement.parentElement.parentElement
}, null, 'bh_owned'])
} else if (url.includes('otakumaker.com')) {
GM_addStyle(
' .bh_owned { background-color: #91BA07 !important; } '
)
addMarkBtnHandler(markOwned, ['.gantry-width-spacer a[href*=\'store.steampowered.com/\']', function (ele) {
return ele.parentElement.parentElement
}, null, 'bh_owned'])
} else if (url.includes('otakubundle.com')) {
GM_addStyle(
' .bh_owned { background-color: #91BA07 !important; } '
)
addMarkBtnHandler(markOwned, ['#hikashop_product_left_part > .g-grid > .g-block > .g-block > a[href*=\'store.steampowered.com/\']',
function (ele) {
return ele.parentElement.parentElement
},
null,
'bh_owned'])
} else if (url.includes('gogobundle.com')) {
GM_addStyle(
' .bh_owned { background-color: #91BA07 !important; border: 1px solid white; } '
)
addMarkBtnHandler(markOwned, ['.g-block > .g-block > a[href*=\'store.steampowered.com/\']', function (ele) {
return ele.parentElement.parentElement
}, null, 'bh_owned'])
} else if (url.includes('superduperbundle.com')) {
addMarkBtnHandler(markOwned, ['#gameslist a[href*=\'store.steampowered.com/\']', function (ele) {
return ele.parentElement.parentElement
}, null, 'bh_owned'])
} else if (url.includes('gamebundle.com')) {
GM_addStyle(
' .bh_owned { background-color: #A0CC41 !important; border-bottom: 45px solid rgba(233, 233, 233, 0.5); } '
+ ' .bh_owned .activebundle_game_bundle_debut_title { background-color: #A0CC41 !important; } '
)
addMarkBtnHandler(markOwned, ['.activebundle_game_section_full a[href*=\'store.steampowered.com/\']', function (ele) {
return ele.parentElement
}, null, 'bh_owned'])
} else if (url.includes('humblebundle.com')) {
GM_addStyle(
' .game-box img { max-height: 180px !important; max-width: 130px !important; } '
+ ' .image-grid { animation: none !important; } ' +
' .bh_owned .entity-details, .bh_owned div.entity-meta, .bh_owned div.entity-meta span.entity-title { background: #7CA156 !important; color: #FFFFFF !important; } ' +
' div.slick-track .bh_owned div.entity-meta { background: transparent !important; } '
)
if (url.includes('/games/')) {
GM_addStyle(
' .bh-owned div.dd-image-box-caption-container { background: #7CA156 !important; } ' +
' .bh-owned span.front-page-art-image-text { color: #FFFFFF !important; } '
)
let onClickFunction = function () {
GetSteamAppList()
.then(appList => {
let gameTitleElements = document.querySelectorAll('span.front-page-art-image-text')
for (let gameTitleElement of gameTitleElements) {
let gameName = gameTitleElement.textContent.toLowerCase().replace(/[^a-z0-9]/g, '')
let matches = appList.filter(app => app.name === gameName)
for (let match of matches) {
if (isAppOwned(match.appid)) {
setElementOwned(gameTitleElement.parentElement.parentElement.parentElement.parentElement)
break
}
}
}
})
}
addMarkBtnHandler(onClickFunction)
} else if (url.includes('/store')) {
let onClickFunction = function () {
GetSteamAppList()
.then(appList => {
let gameTitleElements = document.querySelectorAll('span.entity-title')
for (let gameTitleElement of gameTitleElements) {
let gameName = gameTitleElement.textContent.toLowerCase().replace(/[^a-z0-9]/g, '')
let matches = appList.filter(app => app.name === gameName)
for (let match of matches) {
if (isAppOwned(match.appid)) {
setElementOwned(gameTitleElement.parentElement.parentElement)
break
}
}
}
})
}
addMarkBtnHandler(onClickFunction)
} else if (url.includes('/subscription')) {
GM_addStyle(
' .bh-owned div { background: #7CA156 !important; } ' +
' .bh-owned .content-choice-title { color: #FFFFFF !important; } '
)
let onClickFunction = function () {
GetSteamAppList()
.then(appList => {
let gameTitleElements = document.querySelectorAll('span.content-choice-title')
for (let gameTitleElement of gameTitleElements) {
let gameName = gameTitleElement.textContent.toLowerCase().replace(/[^a-z0-9]/g, '')
let matches = appList.filter(app => app.name === gameName)
for (let match of matches) {
if (isAppOwned(match.appid)) {
setElementOwned(gameTitleElement.parentElement.parentElement)
break
}
}
}
})
}
addMarkBtnHandler(onClickFunction)
}
} else if (url.includes('steamcompanion.com')) {
GM_addStyle(
' .bh_owned.banner { margin-bottom: 5px !important; margin-top: 35px !important; '
+ ' padding-bottom: 15px !important; padding-top: 15px !important; } '
+ ' .bh_owned.giveaway-links { opacity: 0.75; } '
)
markOwned('#hero a[href*=\'store.steampowered.com/\']'
, null, null, 'bh_owned')
// Mark
{
let query = '.giveaway-links img[src^=\'https://steamcdn-a.akamaihd.net/steam/apps/\']'
let getLabelCallback = function (ele) {
return ele.parentElement.parentElement.parentElement
}
let apps = []
let eleApps = document.querySelectorAll(query)
for (let i = 0; i < eleApps.length; i++) {
let app = /[0-9]+/.exec(eleApps[i].getAttribute('src'))
if (app !== null) {
apps.push(app[0])
}
}
apps = apps.filter(function (elem, index, self) {
return index === self.indexOf(elem)
})
writeConsoleMessage('Apps: ' + apps.length)
let appAll = apps.join(',')
GM_xmlhttpRequest(
{
method: 'GET',
headers:
{
'Cache-Control': 'max-age=0'
},
url: 'https://store.steampowered.com/api/appuserdetails/?appids=' + appAll,
onload: function (response) {
let dataRes = JSON.parse(response.responseText)
let countOwned = 0
let elementApps = document.querySelectorAll(query)
for (let i = 0; i < elementApps.length; i++) {
let appUrl = elementApps[i].getAttribute('src')
if (appurl.includes('https://steamcdn-a.akamaihd.net/steam/apps/')) {
let app = /[0-9]+/.exec(appUrl)
if (app !== null) {
if (typeof dataRes[app] !== 'undefined') {
if (dataRes[app].success) {
if (dataRes[app].data.is_owned) {
let eleLabel = getLabelCallback(elementApps[i])
eleLabel.classList.add('bh_owned')
countOwned++
} else {
// writeConsoleMessage("App: not owned - http://store.steampowered.com/app/" + app + "/");
}
} else {
// writeConsoleMessage("App: not success - https://steamdb.info/app/" + app + "/");
}
}
}
}
}
writeConsoleMessage('Apps: owned - ' + countOwned)
}
// End onload
})
}
} else if (url.includes('store.steampowered.com')) {
if (url.includes('/widget/')) {
GM_addStyle(
' .bh_owned { background-color: transparent !important; } '
+ ' .bh_owned a { color: #71A034 !important; }'
)
markOwned('.main_text a[href*=\'store.steampowered.com/\']', function (ele) {
return ele.parentElement
}, null, 'bh_owned')
} else if (url.includes('/app/')) {
GM_addStyle(
' .bh_owned { '
+ ' background-color: #6D8C1A !important; '
+ ' padding: 0px 2px 0px 2px; '
+ ' } '
)
markOwned(
'.glance_details p > a[href*=\'store.steampowered.com/\']'
+ ', .game_area_dlc_bubble a[href*=\'store.steampowered.com/\']'
,
null,
null,
'bh_owned')
} else if (url.includes('/notinterested/')) {
GM_addStyle(
' .bh_owned { '
+ ' background-color: #6D8C1A !important; '
+ ' padding: 5px 100px 5px 5px !important; '
+ ' margin-left: -5px; margin-right: 50px; '
+ ' } '
)
markOwned('.ignoredapps > a[href*=\'store.steampowered.com/\']'
, null, null, 'bh_owned')
} else if (url.includes('/search/')) {
GM_addStyle(
' .bh_owned { '
+ ' background-color: #6D8C1A66 !important; '
+ ' } '
)
markOwned('.search_result_row[href*=\'store.steampowered.com/\']'
, null, null, 'bh_owned')
}
} else if (url.includes('steamcommunity.com')) {
GM_addStyle(
' .bh_owned { background-color: #71A034 !important; '
+ ' padding: 0px 2px 0px 2px; } '
+ ' .bh_owned.blotter_userstatus_game { padding: 0px; border-color: #71A034; } '
)
if (url.includes('/home')) {
let querySteamHome = '.blotter_gamepurchase_details a[href*=\'store.steampowered.com/\']:not(.bh_owned) '
+ ', .blotter_author_block a[href*=\'store.steampowered.com/\']:not(.bh_owned) '
+ ', .blotter_author_block a[href*=\'steamcommunity.com/app/\']:not(.bh_owned) '
+ ', .blotter_daily_rollup_line a[href*=\'steamcommunity.com/app/\']:not(.bh_owned) '
markOwned(querySteamHome, function (ele, type) {
if (type === 1) {
if (ele.classList.contains('blotter_userstats_game')) {
ele.parentElement.classList.add('bh_owned')
} else {
ele.classList.add('bh_owned')
}
}
})
let targetObMark = document.getElementById('blotter_content')
if (targetObMark) {
let tmObMark = -1
let obMark = new MutationObserver(function (mutations) {
mutations.forEach(function () {
clearTimeout(tmObMark)
tmObMark = setTimeout(function (querySteamH) {
markOwned(querySteamH, function (ele, type) {
if (type === 1 && !ele.classList.contains('blotter_userstats_game')) {
ele.classList.add('bh_owned')
}
})
}, 100, querySteamHome)
})
})
let configObMark = { childList: true }
obMark.observe(targetObMark, configObMark)
}
} else if (url.includes('/announcements')) {
markOwned('.announcement_body a[href*=\'store.steampowered.com/\']'
, null, null, 'bh_owned')
}
} else if (url.includes('forums.steampowered.com')) {
GM_addStyle(
' .bh_owned { background-color: #71A034 !important; '
+ ' padding: 0px 2px 0px 2px;'
+ ' } '
)
markOwned('div[id^=\'post_message\'] a[href*=\'store.steampowered.com/\']'
, null, null, 'bh_owned')
} else if (url.includes('whosgamingnow.net')) {
if (url.includes('/discussion')) {
GM_addStyle(
' .bh_owned { '
+ ' padding: 0px 2px 0px 2px;'
+ ' } '
)
markOwned('.MessageList a[href*=\'store.steampowered.com/\']'
, null, null, 'bh_owned')
} else if (url.includes('/redeem')) {
GM_addStyle(
' .bh_owned { '
+ ' border: 1px solid #FFF;'
+ ' } '
+ ' .bh_owned .BoxArt { '
+ ' border: 0px !important;'
+ ' } '
)
markOwned('.GameInfo a[href*=\'store.steampowered.com/\']', function (ele) {
return ele.parentElement.parentElement.parentElement
})
} else if (url.includes('/giveaway')) {
GM_addStyle(
' .bh_owned { '
+ ' border: 5px solid #7CA156;'
+ ' } '
)
markOwned('img[src*=\'://cdn.akamai.steamstatic.com/steam/\']'
, null, null, 'bh_owned')
}
} else if (url.includes('steamground.com') && url.includes('/wholesale')) {
GM_addStyle(
' .bh_owned { background-color: #48B24B !important; } '
+ ' .bh_owned .wholesale-card_title { color: #373d41 !important; } '
+ ' .bh_steam { display: none; } '
)
let elesTitle = document.querySelectorAll('.wholesale-card_title')
if (elesTitle.length > 0) {
GM_xmlhttpRequest(
{
method: 'GET',
url: 'https://www.steamgifts.com/discussion/iy081/steamground-wholesale-build-a-bundle',
onload: function (response) {
let data = response.responseText
let eleContainer = document.createElement('div')
eleContainer.innerHTML = data
let eleComment = eleContainer.querySelector('.comment__description')
if (eleComment) {
let elesGame = eleComment.querySelectorAll('table td:nth-child(1) a[href*=\'store.steampowered.com/\']')
if (elesGame.length > 0) {
let arrTitle = []
for (let i = 0; i < elesTitle.length; i++) {
arrTitle.push(elesTitle[i].textContent.trim())
}
for (let i = 0; i < elesGame.length; i++) {
let isMatch = false
let game = elesGame[i].textContent.trim().toLowerCase()
for (let j = 0; j < elesTitle.length; j++) {
let title = elesTitle[j].textContent.trim().toLowerCase()
if (game === title
|| (title.indexOf('|') > -1 && game === title.replace('|', ':'))
|| (game === 'ball of light' && title === 'ball of light (journey)')
|| (game === 'its your last chance in new school' && title === 'it is yоur last chance in new schооl')
|| (game === 'shake your money simulator 2016' && title === 'shake your money simulator')
|| (game === 'spakoyno: back to the ussr 2.0' && title === 'spakoyno back to the ussr 2.0')
|| (game === 'or' && title === 'or!')) {
isMatch = true
arrTitle = arrTitle.filter(function (value) {
return value !== elesTitle[j].textContent.trim()
})
}
if (isMatch) {
let elemA = document.createElement('a')
elemA.classList.add('bh_steam')
elemA.href = elesGame[i].href
elesTitle[j].parentElement.parentElement.appendChild(elemA)
break
}
}
if (!isMatch) {
writeConsoleMessage('Not match: ' + elesGame[i].href + ' ' + elesGame[i].textContent)
}
}
if (arrTitle.length > 0) {
writeConsoleMessage('Not match: ' + arrTitle.length)
for (let i = 0; i < arrTitle.length; i++) {
writeConsoleMessage('Not match: ' + arrTitle[i])
}
}
markOwned('.wholesale-card > a[href*=\'store.steampowered.com/\']', function (ele) {
return ele.parentElement
}, null, 'bh_owned')
}
}
}
// End onload
})
}
} else if (url.includes('bunchkeys.com')) {
GM_addStyle(
' .bh_owned { border: #B5D12E 3px solid !important; '
+ ' margin-left: -3px; margin-top: -3px; } '
)
addMarkBtnHandler(markOwned, [default_steam_url_selector, function (ele) {
return ele.parentElement
}, null, 'bh_owned'])
} else if (url.includes('sgtools.info')) {
GM_addStyle(
' .bh_owned { background-color: #71A034 !important; } '
)
if (url.includes('/lastbundled')) {
markOwned('#content > div > table > tbody > tr > td > a[href*=\'store.steampowered.com/\']', function (ele) {
return ele.parentElement.parentElement
}, null, 'bh_owned')
} else if (url.includes('/deals')) {
markOwned('.deal_game_image > img[src*=\'cdn.akamai.steamstatic.com/steam/\']', function (ele) {
return ele.parentElement
}, null, 'bh_owned')
} else if (url.includes('/whitelisted')) {
markOwned('.cmGame > a[href*=\'store.steampowered.com/\']', function (ele) {
return ele.parentElement
}, null, 'bh_owned')
}
} else if (url.includes('steamkeys.ovh')) {
markOwned('td > a[href*=\'store.steampowered.com/\']', function (ele) {
return ele.parentElement.parentElement
}, null, 'bh_owned')
} else if (url.includes('steamdb.info')) {
if (window !== window.parent) {
return
}
GM_addStyle(
' .bh_owned, tr.bh_owned td { background-color: #DDF7D3 !important; } '
+ ' .bh_owned_transparent { background-color: #bcf0a880 !important; } '
)
markOwned(' \
#apps .app \
, #dlc .app \
, .container > .table .app \
, .sales-section .app \
, .page-search .app \
', null, function (ele) {
return ele.getAttribute('data-appid')
}, 'bh_owned')
markOwned(' \
#subs .package \
, .sales-section .package \
, .page-search .package \
', null, function (ele) {
return '/steam/subs/' + ele.getAttribute('data-subid')
}, 'bh_owned')
markOwned('.table-products .app'
, null, function (ele) {
return ele.getAttribute('data-appid')
}, 'bh_owned_transparent')
markOwned('.app-history .appid'
, function (ele) {
return ele.parentElement
}, function (ele) {
return ele.textContent.trim()
}, 'bh_owned')
}
window.addEventListener('beforeunload', function () {
clearTimeoutAll()
clearIntervalAll()
})
}
attachOnReady(main)
}())