(async () => { "use strict"; const CACHE_HOURS = 24; const CONCURRENCY = 6; const STYLE_ID = "steam-vac-checker-styles"; const BADGE_CLASS = "vac-check-badge"; const TOOLTIP_ID = "steam-vac-checker-tooltip"; const SUMMARY_ID = "steam-vac-checker-summary"; const CACHE_PREFIX = "vaccheck:"; const CACHE_DURATION = CACHE_HOURS * 60 * 60 * 1000; const friends = [ ...document.querySelectorAll(".friend_block_v2[data-steamid]") ]; if (!friends.length) { alert("No Steam friends found on this page."); return; } function installStyles() { const style = document.getElementById(STYLE_ID) || document.createElement("style"); style.id = STYLE_ID; style.textContent = ` .friend_block_v2 { position: relative !important; } .vac-check-badge { position: fixed; top: 0; left: 0; display: inline-flex; align-items: center; box-sizing: border-box; padding: 4px 6px; border: 1px solid rgba(0, 0, 0, .45); border-radius: 2px; background: #4c7a2f; color: #fff; cursor: help; font: 700 10px/1 "Motiva Sans", Arial, sans-serif; letter-spacing: .04em; text-transform: uppercase; z-index: 2147483646 !important; } .vac-check-badge--vac { background: #a9403c; } .vac-check-badge--game { background: #a96f21; } .vac-check-badge--hidden { background: #70529a; } .vac-check-badge--clean { border-color: rgba(107, 137, 84, .4); background: rgba(72, 93, 60, .45); color: #a7b49b; font-weight: 600; } .vac-check-badge:hover, .vac-check-badge:focus-visible { z-index: 2147483647 !important; } .vac-check-tooltip { position: fixed; top: 0; left: 0; width: min(220px, calc(100vw - 24px)); box-sizing: border-box; padding: 8px 9px; border: 1px solid #3d4b57; border-radius: 2px; background: #171d25; box-shadow: 0 3px 8px rgba(0, 0, 0, .45); color: #c7d5e0; display: none; font: 400 12px/1.35 Arial, sans-serif; letter-spacing: normal; overflow-wrap: anywhere; pointer-events: none; text-align: left; text-transform: none; white-space: normal; word-break: normal; z-index: 2147483647 !important; } .vac-check-tooltip__title { display: block; margin-bottom: 4px; color: #f1f7fb; font-weight: 700; white-space: normal; } .vac-check-tooltip__detail { display: block; color: #8f9eab; white-space: normal; } .vac-check-tooltip__age { display: block; margin-top: 6px; padding-top: 6px; border-top: 1px solid rgba(199, 213, 224, .14); color: #d7b46a; font-weight: 700; white-space: normal; } .vac-check-tooltip.is-visible { display: block !important; } .vac-check-badge:focus-visible { outline: 2px solid #c7d5e0; outline-offset: 2px; } .vac-check-summary { margin-left: 10px; padding-left: 10px; border-left: 1px solid rgba(199, 213, 224, .25); color: #8f9eab; font: 400 12px/1 "Motiva Sans", Arial, sans-serif; } .vac-check-summary__count { color: #d6d7d8; font-weight: 700; } `; if (!style.isConnected) document.head.appendChild(style); } const cacheKey = id => `${CACHE_PREFIX}${id}`; async function fetchText(url) { const response = await fetch(url); if (!response.ok) { throw new Error(`Steam returned ${response.status} for ${url}`); } return response.text(); } function readCache(id) { try { const cache = JSON.parse(localStorage.getItem(cacheKey(id))); if (cache && Date.now() - cache.t < CACHE_DURATION) { return normalizeStatus(cache.d); } } catch { } return null; } function writeCache(id, data) { try { localStorage.setItem(cacheKey(id), JSON.stringify({ t: Date.now(), d: data })); } catch { } } function isPositiveFlag(value) { return value === true || value === 1 || value === "1" || value === "true"; } function normalizeStatus(status) { return { vacApi: [status?.vacApi, status?.vacAPI, status?.vacXML].some(isPositiveFlag), vacVisible: isPositiveFlag(status?.vacVisible), game: isPositiveFlag(status?.game), days: status?.days ?? "" }; } function hasBan(status) { return status.vacApi || status.vacVisible || status.game; } async function getStatus(id) { const cachedStatus = readCache(id); if (cachedStatus) return cachedStatus; const [xml, html] = await Promise.all([ fetchText(`https://steamcommunity.com/profiles/${id}/?xml=1`), fetchText(`https://steamcommunity.com/profiles/${id}/`) ]); const xmlDoc = new DOMParser().parseFromString(xml, "text/xml"); const profileBanStatus = html.match(/profile_ban_status[\s\S]*?<\/div>\s*<\/div>/i); const banContent = (profileBanStatus?.[0] ?? html).toLowerCase(); const daysMatch = banContent.match(/(\d+)\s*day\(s\)/); const status = normalizeStatus({ vacApi: xmlDoc.querySelector("vacBanned")?.textContent.trim() === "1", vacVisible: /vac ban/.test(banContent), game: /game ban/.test(banContent), days: daysMatch?.[1] ?? "" }); writeCache(id, status); return status; } function getBadgeDetails(status) { if (status.game && !(status.vacApi || status.vacVisible)) { return { className: "vac-check-badge--game", label: "Game", heading: "Game Ban detected", description: "Steam shows a Game Ban on this profile." }; } if (status.vacVisible) { return { className: "vac-check-badge--vac", label: "VAC", heading: status.game ? "VAC and Game Bans detected" : "VAC ban detected", description: status.game ? "Steam shows both a VAC ban and a Game Ban on this profile." : "Steam shows a VAC ban on this profile." }; } if (status.vacApi) { return { className: "vac-check-badge--hidden", label: "VAC*", heading: status.game ? "VAC and Game Bans reported" : "VAC ban reported by API", description: status.game ? "Steam's API data reports a VAC ban, and the profile shows a Game Ban." : "Steam's API data reports a VAC ban not shown on the profile page." }; } return { className: "vac-check-badge--clean", label: "OK", heading: "No bans detected", description: "No VAC or Game Ban was found in Steam's public profile data." }; } function fillTooltip(tooltip, details, status) { const heading = document.createElement("span"); const description = document.createElement("span"); heading.className = "vac-check-tooltip__title"; heading.textContent = details.heading; description.className = "vac-check-tooltip__detail"; description.textContent = details.description; tooltip.replaceChildren(heading, description); if (status.days) { const days = document.createElement("span"); const dayLabel = status.days === "1" ? "day" : "days"; days.className = "vac-check-tooltip__age"; days.textContent = `Last ban: ${status.days} ${dayLabel} ago.`; tooltip.append(days); } } function positionTooltip(tooltip, badge) { const badgeRect = badge.getBoundingClientRect(); const margin = 12; const desiredLeft = badgeRect.right - tooltip.offsetWidth; const maxLeft = window.innerWidth - tooltip.offsetWidth - margin; const left = Math.max(margin, Math.min(desiredLeft, maxLeft)); const below = badgeRect.bottom + 6; const above = badgeRect.top - tooltip.offsetHeight - 6; const top = below + tooltip.offsetHeight <= window.innerHeight ? below : Math.max(margin, above); tooltip.style.left = `${left}px`; tooltip.style.top = `${top}px`; } function createTooltip() { document.getElementById(TOOLTIP_ID)?.remove(); const tooltip = document.createElement("div"); tooltip.id = TOOLTIP_ID; tooltip.className = "vac-check-tooltip"; tooltip.setAttribute("role", "tooltip"); document.body.appendChild(tooltip); return tooltip; } function positionBadge(badge, friend) { const friendRect = friend.getBoundingClientRect(); badge.style.left = `${friendRect.right - badge.offsetWidth - 6}px`; badge.style.top = `${friendRect.top + 6}px`; } function createBadge(status, tooltip, friend) { const details = getBadgeDetails(status); const badge = document.createElement("span"); badge.className = `${BADGE_CLASS} ${details.className}`; badge.tabIndex = 0; badge.setAttribute("aria-describedby", TOOLTIP_ID); badge.textContent = details.label; const showTooltip = () => { fillTooltip(tooltip, details, status); tooltip.style.display = "block"; tooltip.style.visibility = "visible"; tooltip.style.opacity = "1"; tooltip.classList.add("is-visible"); positionTooltip(tooltip, badge); }; const hideTooltip = () => { tooltip.classList.remove("is-visible"); tooltip.style.display = "none"; }; badge.addEventListener("pointerenter", showTooltip); badge.addEventListener("pointerleave", hideTooltip); badge.addEventListener("mouseenter", showTooltip); badge.addEventListener("mouseleave", hideTooltip); badge.addEventListener("focus", showTooltip); badge.addEventListener("blur", hideTooltip); document.body.appendChild(badge); positionBadge(badge, friend); return badge; } function updateSummary(bannedCount) { const title = document.querySelector(".profile_friends.title_bar .profile_friends.title"); if (!title) return; document.getElementById(SUMMARY_ID)?.remove(); const summary = document.createElement("span"); const count = document.createElement("span"); summary.id = SUMMARY_ID; summary.className = "vac-check-summary"; summary.append("Banned: "); count.className = "vac-check-summary__count"; count.textContent = `${bannedCount} / ${friends.length}`; summary.append(count); title.append(summary); } installStyles(); const tooltip = createTooltip(); const positionedBadges = []; document.querySelectorAll(`.${BADGE_CLASS}`).forEach(badge => badge.remove()); function updateBadgePositions() { positionedBadges.forEach(({ badge, friend }) => { if (badge.isConnected && friend.isConnected) positionBadge(badge, friend); }); } window.addEventListener("resize", updateBadgePositions, { passive: true }); document.addEventListener("scroll", updateBadgePositions, true); let index = 0; async function worker() { let bannedCount = 0; while (index < friends.length) { const friend = friends[index++]; const id = friend.dataset.steamid; try { const status = await getStatus(id); const badge = createBadge(status, tooltip, friend); positionedBadges.push({ badge, friend }); if (hasBan(status)) { bannedCount += 1; } } catch (error) { console.error("Steam Ban Checker - ", id, error); } } return bannedCount; } const bannedCount = (await Promise.all(Array.from({ length: CONCURRENCY }, worker))) .reduce((total, count) => total + count, 0); updateSummary(bannedCount); console.log("Steam Ban Checker - finished."); })();