/*! * Discord HypeSquad Switcher — by discord.dog * Runs entirely in your browser. No server, no tracking, no install. * Source: https://discord.dog/tools/hypesquad-switcher/src * Tool: https://discord.dog/tools/hypesquad-switcher * * What it does: * 1. Reads your Discord auth token from the running Discord client * (the same token your browser already uses on this page). * 2. Calls Discord's own HypeSquad endpoint (/api/v9/hypesquad/online) * to switch your house — the exact API the Discord settings page uses. * 3. Shows the result. Nothing leaves your machine. */ (function () { "use strict"; /* ----- idempotent re-open ----- */ if (window.__discordDogHypesquad) { try { window.__discordDogHypesquad.toggle(); } catch (_) {} return; } /* ----- domain check ----- */ var host = location.hostname; var ALLOWED = ["discord.com", "canary.discord.com", "ptb.discord.com"]; if (ALLOWED.indexOf(host) === -1) { alert( "Discord HypeSquad Switcher only works on discord.com.\n\n" + "Open Discord in your browser (https://discord.com/channels/@me), " + "then click the bookmarklet from there." ); return; } /* ----- constants ----- */ var API = "/api/v9"; var BADGE_CDN = "https://cdn.discordapp.com/badge-icons"; // public_flags bit positions for each house var FLAG_BRAVERY = 1 << 6; // 64 var FLAG_BRILLIANCE = 1 << 7; // 128 var FLAG_BALANCE = 1 << 8; // 256 // Discord-published badge icon hashes (verified against the live CDN). var HOUSES = [ { id: 1, key: "bravery", name: "Bravery", tagline: "Daring & spirited", color: "#9c84ef", gradient: "linear-gradient(135deg,#9c84ef 0%,#5865f2 100%)", flag: FLAG_BRAVERY, icon: BADGE_CDN + "/8a88d63823d8a71cd5e390baa45efa02.png", }, { id: 2, key: "brilliance", name: "Brilliance", tagline: "Inquisitive & sharp", color: "#f47b67", gradient: "linear-gradient(135deg,#f47b67 0%,#ed4245 100%)", flag: FLAG_BRILLIANCE, icon: BADGE_CDN + "/011940fd013da3f7fb926e4a1cd2e618.png", }, { id: 3, key: "balance", name: "Balance", tagline: "Calm & considerate", color: "#45ddc0", gradient: "linear-gradient(135deg,#45ddc0 0%,#23a55a 100%)", flag: FLAG_BALANCE, icon: BADGE_CDN + "/3aa41de486fa12454c3761e8e223442e.png", }, ]; /* ----- token grab ----- */ // Discord's client-side bundle exposes a getToken() helper inside a webpack // module. We push a fake module so webpack hands us the __webpack_require__ // function, then walk its loaded-module cache and look for getToken(). // This technique has worked for years; if Discord ever obfuscates further // we degrade to manual paste. function grabToken() { try { var chunkName = "webpackChunkdiscord_app"; var chunk = window[chunkName]; if (!chunk || typeof chunk.push !== "function") return null; var req = null; var sentinel = "_dd_hs_" + Math.random().toString(36).slice(2); chunk.push([[sentinel], {}, function (r) { req = r; }]); if (!req || !req.c) return null; var cache = req.c; for (var k in cache) { if (!Object.prototype.hasOwnProperty.call(cache, k)) continue; var mod = cache[k]; if (!mod || !mod.exports) continue; var ex = mod.exports; var candidates = [ex.default, ex.Z, ex.ZP, ex]; for (var i = 0; i < candidates.length; i++) { var c = candidates[i]; if (c && typeof c.getToken === "function") { try { var t = c.getToken(); if (typeof t === "string" && t.length > 20) return t; } catch (_) {} } } } } catch (e) { console.warn("[hypesquad-switcher] token auto-grab failed:", e); } return null; } // Fallback A: Discord patches the main window's Storage.prototype.getItem // to hide the token. A same-origin iframe gets its own fresh Storage // prototype that Discord never touched — invoking the iframe's // Storage.prototype.getItem on the parent's localStorage bypasses the patch. // Token is stored JSON-encoded so strip surrounding quotes. function grabTokenFromIframe() { var iframe = null; try { iframe = document.createElement("iframe"); iframe.style.display = "none"; document.body.appendChild(iframe); var win = iframe.contentWindow; if (!win) return null; var raw = null; // Use the iframe's untouched Storage.prototype.getItem on parent storage if (win.Storage && win.Storage.prototype && typeof win.Storage.prototype.getItem === "function") { try { raw = win.Storage.prototype.getItem.call(window.localStorage, "token"); } catch (_) {} } // Also try the iframe's own localStorage (same underlying data store) if (typeof raw !== "string" && win.localStorage) { try { raw = win.localStorage.getItem("token") || win.localStorage.token; } catch (_) {} } if (typeof raw === "string") { if (raw.charAt(0) === '"' && raw.charAt(raw.length - 1) === '"') { raw = raw.slice(1, -1); } if (raw.length > 20) return raw; } } catch (e) { console.warn("[hypesquad-switcher] iframe token grab failed:", e); } finally { if (iframe && iframe.parentNode) iframe.parentNode.removeChild(iframe); } return null; } // Fallback B: hook fetch + XMLHttpRequest to read the Authorization header // off the next Discord API call. Discord makes API calls constantly // (presence pings, read state, etc.) so this almost always catches one // within a couple of seconds. function grabTokenFromNetwork(timeoutMs) { return new Promise(function (resolve) { var done = false; var origFetch = window.fetch; var origXhrSet = XMLHttpRequest.prototype.setRequestHeader; var timer = setTimeout(function () { finish(null); }, timeoutMs || 8000); function finish(token) { if (done) return; done = true; try { window.fetch = origFetch; } catch (_) {} try { XMLHttpRequest.prototype.setRequestHeader = origXhrSet; } catch (_) {} clearTimeout(timer); resolve(token); } function consider(val) { if (done || typeof val !== "string") return; // User tokens have no "Bot " / "Bearer " prefix; reject anything that does if (/^(?:Bot|Bearer)\s+/i.test(val)) return; if (val.length < 30 || val.length > 200) return; if (!/^[A-Za-z0-9_\-\.]+$/.test(val)) return; finish(val); } function readHeader(headers) { if (!headers) return null; try { if (typeof headers.get === "function") { return headers.get("Authorization") || headers.get("authorization"); } return headers.Authorization || headers.authorization || null; } catch (_) { return null; } } window.fetch = function (input, init) { try { if (init) consider(readHeader(init.headers)); if (!done && input && typeof input === "object") consider(readHeader(input.headers)); } catch (_) {} return origFetch.apply(this, arguments); }; XMLHttpRequest.prototype.setRequestHeader = function (name, value) { try { if (typeof name === "string" && name.toLowerCase() === "authorization") { consider(value); } } catch (_) {} return origXhrSet.apply(this, arguments); }; }); } /* ----- Discord API helpers ----- */ function api(method, path, token, body) { var opts = { method: method, credentials: "include", headers: { "Authorization": token, "Content-Type": "application/json", }, }; if (body !== undefined) opts.body = JSON.stringify(body); return fetch(API + path, opts); } function fetchMe(token) { return api("GET", "/users/@me", token).then(function (r) { if (!r.ok) throw new Error("Discord returned " + r.status + " for /users/@me"); return r.json(); }); } function setHouse(token, houseId) { return api("POST", "/hypesquad/online", token, { house_id: houseId }) .then(function (r) { // 204 = success; 4xx = error with body if (r.status === 204) return { ok: true }; return r.text().then(function (t) { return { ok: false, status: r.status, body: t }; }); }); } function clearHouse(token) { return api("DELETE", "/hypesquad/online", token).then(function (r) { if (r.status === 204) return { ok: true }; return r.text().then(function (t) { return { ok: false, status: r.status, body: t }; }); }); } function currentHouseFromFlags(flags) { flags = flags || 0; if (flags & FLAG_BRAVERY) return 1; if (flags & FLAG_BRILLIANCE) return 2; if (flags & FLAG_BALANCE) return 3; return 0; // no house } /* ----- shadow-DOM UI ----- */ function buildUI() { var hostEl = document.createElement("div"); hostEl.id = "discord-dog-hypesquad-switcher"; hostEl.style.cssText = [ "all: initial", "position: fixed", "top: 80px", "right: 32px", "z-index: 2147483647", "width: 0", "height: 0", ].join(";"); document.body.appendChild(hostEl); var root = hostEl.attachShadow({ mode: "closed" }); var css = ` :host { all: initial; } * { box-sizing: border-box; font-family: "gg sans","Helvetica Neue",Helvetica,Arial,sans-serif; } .panel { position: fixed; top: 80px; right: 32px; width: 340px; background: #232428; color: #f2f3f5; border: 1px solid #1e1f22; border-radius: 12px; box-shadow: 0 16px 48px rgba(0,0,0,.6), 0 0 0 1px rgba(255,255,255,.04) inset; font-size: 14px; overflow: hidden; user-select: none; } .header { display: flex; align-items: center; justify-content: space-between; padding: 12px 14px; background: linear-gradient(180deg,#2b2d31 0%,#232428 100%); cursor: move; border-bottom: 1px solid #1e1f22; } .brand { display: flex; align-items: center; gap: 8px; } .brand-dot { width: 8px; height: 8px; border-radius: 50%; background: linear-gradient(135deg,#5865f2,#9c84ef); } .brand-text { font-weight: 600; font-size: 13px; letter-spacing: .2px; } .brand-sub { color: #b5bac1; font-size: 11px; margin-left: 4px; } .close { width: 26px; height: 26px; display: grid; place-items: center; border-radius: 6px; background: transparent; color: #b5bac1; border: none; cursor: pointer; transition: background 120ms, color 120ms; } .close:hover { background: #35373c; color: #f2f3f5; } .body { padding: 12px 14px 14px; } .user { display: flex; align-items: center; gap: 10px; padding: 8px 10px; background: #1e1f22; border-radius: 8px; margin-bottom: 12px; } .avatar { width: 32px; height: 32px; border-radius: 50%; background: #2b2d31; flex-shrink: 0; } .user-meta { min-width: 0; flex: 1; } .user-name { font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .user-sub { font-size: 11px; color: #b5bac1; } .houses { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; } .house { position: relative; padding: 12px 10px; background: #1e1f22; border: 1.5px solid transparent; border-radius: 8px; cursor: pointer; text-align: center; transition: transform 120ms, border-color 120ms, background 120ms; } .house:hover { background: #2b2d31; transform: translateY(-1px); } .house.active { border-color: var(--hc); background: #2b2d31; } .house.disabled { opacity: .6; cursor: progress; } .house img { width: 36px; height: 36px; display: block; margin: 0 auto 6px; } .house-name { font-weight: 600; font-size: 12px; } .house-sub { font-size: 10px; color: #b5bac1; margin-top: 2px; } .house .badge-check { position: absolute; top: 6px; right: 6px; width: 16px; height: 16px; background: var(--hc); color: #fff; border-radius: 50%; font-size: 10px; display: grid; place-items: center; } .remove { margin-top: 10px; width: 100%; padding: 9px 10px; background: transparent; color: #b5bac1; border: 1px solid #3f4147; border-radius: 8px; cursor: pointer; font-size: 12px; font-weight: 600; transition: background 120ms, color 120ms; } .remove:hover { background: #ed4245; color: #fff; border-color: #ed4245; } .remove.active { border-color: #ed4245; color: #ed4245; } .remove:disabled { opacity: .6; cursor: progress; } .token-row { margin-top: 14px; padding-top: 12px; border-top: 1px solid #1e1f22; } .token-label { font-size: 10px; text-transform: uppercase; letter-spacing: .6px; color: #b5bac1; margin-bottom: 6px; } .token-box { display: flex; align-items: center; gap: 6px; padding: 6px 8px; background: #1e1f22; border-radius: 6px; font-family: ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; font-size: 11px; } .token-value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #f2f3f5; } .token-value.blurred { filter: blur(5px); user-select: none; } .icon-btn { width: 24px; height: 24px; display: grid; place-items: center; border: none; background: transparent; color: #b5bac1; border-radius: 4px; cursor: pointer; flex-shrink: 0; } .icon-btn:hover { background: #35373c; color: #f2f3f5; } .footer { margin-top: 12px; font-size: 10px; color: #6d6f78; text-align: center; line-height: 1.6; } .footer a { color: #5865f2; text-decoration: none; } .footer a:hover { text-decoration: underline; } .status { margin-top: 10px; padding: 8px 10px; border-radius: 6px; font-size: 12px; display: none; } .status.show { display: block; } .status.ok { background: rgba(35,165,90,.15); color: #57f287; } .status.err { background: rgba(237,66,69,.15); color: #fa6669; } .paste { padding: 10px; background: #1e1f22; border-radius: 8px; margin-bottom: 12px; } .paste-msg { font-size: 12px; color: #b5bac1; margin-bottom: 8px; line-height: 1.4; } .paste-msg code { display: block; margin-top: 6px; padding: 6px 8px; background: #111214; border: 1px solid #1e1f22; border-radius: 4px; font-family: ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; font-size: 9px; line-height: 1.35; word-break: break-all; color: #d4d7dc; } .bookmarklet { display: inline-block; padding: 1px 6px; background: #5865f2; color: #fff !important; border-radius: 4px; font-weight: 600; text-decoration: none !important; cursor: grab; } .bookmarklet:active { cursor: grabbing; } .detecting { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 24px 16px; text-align: center; } .detecting .spinner { margin-bottom: 12px; } .detecting-msg { font-size: 13px; font-weight: 600; color: #f2f3f5; margin-bottom: 4px; } .detecting-sub { font-size: 11px; color: #b5bac1; line-height: 1.4; } .paste input { width: 100%; padding: 8px; background: #111214; color: #f2f3f5; border: 1px solid #3f4147; border-radius: 6px; font-family: ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; font-size: 11px; outline: none; } .paste input:focus { border-color: #5865f2; } .paste button { margin-top: 8px; width: 100%; padding: 8px; background: #5865f2; color: #fff; border: none; border-radius: 6px; font-size: 12px; font-weight: 600; cursor: pointer; } .paste button:hover { background: #4752c4; } .spinner { width: 18px; height: 18px; border: 2px solid #3f4147; border-top-color: #5865f2; border-radius: 50%; animation: spin 700ms linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } .loading { display: flex; align-items: center; gap: 10px; padding: 16px; color: #b5bac1; font-size: 13px; } `; var style = document.createElement("style"); style.textContent = css; root.appendChild(style); var panel = document.createElement("div"); panel.className = "panel"; panel.innerHTML = '
' + '
' + '' + 'HypeSquad Switcher' + 'discord.dog' + '
' + '' + '
' + '
' + '
Reading your Discord session…
' + '
'; root.appendChild(panel); enableDrag(panel, panel.querySelector('[data-role="drag"]')); return { hostEl: hostEl, root: root, panel: panel }; } function svgX() { return ''; } function svgEye(open) { if (open) { return ''; } return ''; } function svgCopy() { return ''; } function svgCheck() { return ''; } function enableDrag(panel, handle) { var dx = 0, dy = 0, sx = 0, sy = 0, dragging = false; handle.addEventListener("mousedown", function (e) { if (e.target && e.target.closest && e.target.closest('[data-role="close"]')) return; dragging = true; var rect = panel.getBoundingClientRect(); sx = e.clientX; sy = e.clientY; dx = rect.left; dy = rect.top; panel.style.right = "auto"; panel.style.left = dx + "px"; panel.style.top = dy + "px"; e.preventDefault(); }); document.addEventListener("mousemove", function (e) { if (!dragging) return; var nx = dx + (e.clientX - sx); var ny = dy + (e.clientY - sy); var maxX = window.innerWidth - panel.offsetWidth - 4; var maxY = window.innerHeight - panel.offsetHeight - 4; panel.style.left = Math.max(4, Math.min(nx, maxX)) + "px"; panel.style.top = Math.max(4, Math.min(ny, maxY)) + "px"; }); document.addEventListener("mouseup", function () { dragging = false; }); } /* ----- main flow ----- */ var ui = buildUI(); var state = { token: null, me: null, currentHouseId: 0 }; ui.root.querySelector('[data-role="close"]').addEventListener("click", destroy); function destroy() { try { ui.hostEl.remove(); } catch (_) {} delete window.__discordDogHypesquad; } function toggle() { if (!ui.hostEl.isConnected) document.body.appendChild(ui.hostEl); } window.__discordDogHypesquad = { toggle: toggle, destroy: destroy }; function setBody(html) { ui.root.querySelector('[data-role="body"]').innerHTML = html; } function escapeHtml(s) { return String(s).replace(/[&<>"']/g, function (c) { return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]; }); } function avatarURL(me) { if (!me) return ""; if (me.avatar) { var ext = me.avatar.indexOf("a_") === 0 ? "gif" : "png"; return "https://cdn.discordapp.com/avatars/" + me.id + "/" + me.avatar + "." + ext + "?size=64"; } // default avatar var idx = me.discriminator && me.discriminator !== "0" ? Number(me.discriminator) % 5 : Number((BigInt(me.id) >> 22n) % 6n); return "https://cdn.discordapp.com/embed/avatars/" + idx + ".png"; } function renderMain() { var me = state.me; var cur = state.currentHouseId; var houseCards = HOUSES.map(function (h) { var active = (cur === h.id); var check = active ? '' + svgCheck() + '' : ""; return ( '
' + check + '' + escapeHtml(h.name) + '' + '
' + escapeHtml(h.name) + '
' + '
' + escapeHtml(h.tagline) + '
' + '
' ); }).join(""); var displayName = me.global_name || me.username || "Discord user"; var sub = me.username ? '@' + escapeHtml(me.username) : escapeHtml(me.id); setBody( '
' + '' + '
' + '
' + escapeHtml(displayName) + '
' + '
' + sub + '
' + '
' + '
' + '
' + houseCards + '
' + '' + '
' + '
' + '
Your token
' + '
' + '' + escapeHtml(state.token) + '' + '' + '' + '
' + '
' + '' ); wireMainEvents(); } function wireMainEvents() { var root = ui.root; var houses = root.querySelectorAll(".house, .remove"); houses.forEach(function (el) { el.addEventListener("click", function () { var id = Number(el.getAttribute("data-house")); switchHouse(id, el); }); }); var tokenEl = root.querySelector('[data-role="token"]'); var eyeBtn = root.querySelector('[data-role="eye"]'); var copyBtn = root.querySelector('[data-role="copy"]'); var revealed = false; eyeBtn.addEventListener("click", function () { revealed = !revealed; tokenEl.classList.toggle("blurred", !revealed); eyeBtn.innerHTML = svgEye(revealed); }); copyBtn.addEventListener("click", function () { navigator.clipboard.writeText(state.token).then(function () { copyBtn.style.color = "#57f287"; setTimeout(function () { copyBtn.style.color = ""; }, 1200); }).catch(function () { // fallback selection var range = document.createRange(); range.selectNode(tokenEl); var sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); }); }); } function showStatus(msg, kind) { var s = ui.root.querySelector('[data-role="status"]'); if (!s) return; s.className = "status show " + (kind || "ok"); s.textContent = msg; clearTimeout(showStatus._t); showStatus._t = setTimeout(function () { s.className = "status"; }, 4000); } function switchHouse(houseId, btn) { if (state.currentHouseId === houseId) return; var nodes = ui.root.querySelectorAll(".house, .remove"); nodes.forEach(function (n) { n.classList.add("disabled"); }); if (btn) btn.classList.add("disabled"); var promise = houseId === 0 ? clearHouse(state.token) : setHouse(state.token, houseId); promise.then(function (r) { if (r.ok) { state.currentHouseId = houseId; renderMain(); var name = houseId === 0 ? "No house" : HOUSES[houseId - 1].name; showStatus("Switched to " + name + ".", "ok"); } else { showStatus("Discord rejected the change (HTTP " + r.status + "). " + (r.body || ""), "err"); nodes.forEach(function (n) { n.classList.remove("disabled"); }); } }).catch(function (e) { showStatus("Request failed: " + (e && e.message ? e.message : e), "err"); nodes.forEach(function (n) { n.classList.remove("disabled"); }); }); } function renderPaste(reason) { setBody( '
' + '
' + escapeHtml(reason || "Couldn't read your token automatically.") + ' Paste it manually below. It never leaves your browser.
' + '' + '' + '
' + 'To find your token, drag ' + 'Grab Token' + ' to your bookmarks bar, then click it on discord.com to get your token in a popup. Or paste this in DevTools (F12) Console:' + '(function(){location.reload();var i=document.createElement("iframe");document.body.appendChild(i);alert(i.contentWindow.localStorage.token)})()' + '
' + '
' + '' ); var input = ui.root.querySelector('[data-role="paste-input"]'); var btn = ui.root.querySelector('[data-role="paste-btn"]'); function submit() { var v = input.value.trim(); if (!v) return; bootstrap(v); } btn.addEventListener("click", submit); input.addEventListener("keydown", function (e) { if (e.key === "Enter") submit(); }); input.focus(); } function renderDetecting() { setBody( '
' + '
' + '
Detecting your token…
' + '
Keep using Discord normally. This usually takes a second.
' + '
' ); } function useToken(token) { state.token = token; fetchMe(token).then(function (me) { state.me = me; state.currentHouseId = currentHouseFromFlags(me.public_flags); renderMain(); }).catch(function (e) { state.token = null; renderPaste("That token didn't work: " + (e && e.message ? e.message : e)); }); } function bootstrap(tokenOverride) { var token = tokenOverride || grabToken() || grabTokenFromIframe(); if (token) { useToken(token); return; } // Hook the network and wait for Discord's next API call renderDetecting(); grabTokenFromNetwork(8000).then(function (t) { if (t) { useToken(t); } else { renderPaste("Couldn't read your token automatically (Discord may have updated their bundle)."); } }); } bootstrap(); })();