/*!
* 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 =
'<div class="header" data-role="drag">' +
'<div class="brand">' +
'<span class="brand-dot"></span>' +
'<span class="brand-text">HypeSquad Switcher</span>' +
'<span class="brand-sub">discord.dog</span>' +
'</div>' +
'<button class="close" title="Close" data-role="close" aria-label="Close">' +
svgX() +
'</button>' +
'</div>' +
'<div class="body" data-role="body">' +
'<div class="loading"><div class="spinner"></div><span>Reading your Discord session…</span></div>' +
'</div>';
root.appendChild(panel);
enableDrag(panel, panel.querySelector('[data-role="drag"]'));
return { hostEl: hostEl, root: root, panel: panel };
}
function svgX() {
return '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"><path d="M6 6l12 12M18 6l-12 12"/></svg>';
}
function svgEye(open) {
if (open) {
return '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>';
}
return '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.94 17.94A10.94 10.94 0 0 1 12 20c-7 0-11-8-11-8a19.7 19.7 0 0 1 4.22-5.15M9.9 4.24A10.94 10.94 0 0 1 12 4c7 0 11 8 11 8a19.55 19.55 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>';
}
function svgCopy() {
return '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
}
function svgCheck() {
return '<svg viewBox="0 0 24 24" width="10" height="10" fill="none" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 13l4 4L19 7"/></svg>';
}
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 ? '<span class="badge-check">' + svgCheck() + '</span>' : "";
return (
'<div class="house' + (active ? ' active' : '') + '" data-house="' + h.id + '" style="--hc:' + h.color + '">' +
check +
'<img alt="' + escapeHtml(h.name) + '" src="' + h.icon + '" loading="lazy" />' +
'<div class="house-name">' + escapeHtml(h.name) + '</div>' +
'<div class="house-sub">' + escapeHtml(h.tagline) + '</div>' +
'</div>'
);
}).join("");
var displayName = me.global_name || me.username || "Discord user";
var sub = me.username ? '@' + escapeHtml(me.username) : escapeHtml(me.id);
setBody(
'<div class="user">' +
'<img class="avatar" alt="" src="' + avatarURL(me) + '"/>' +
'<div class="user-meta">' +
'<div class="user-name">' + escapeHtml(displayName) + '</div>' +
'<div class="user-sub">' + sub + '</div>' +
'</div>' +
'</div>' +
'<div class="houses">' + houseCards + '</div>' +
'<button class="remove' + (cur === 0 ? ' active' : '') + '" data-house="0">' +
(cur === 0 ? 'No house (current)' : 'Remove HypeSquad badge') +
'</button>' +
'<div class="status" data-role="status"></div>' +
'<div class="token-row">' +
'<div class="token-label">Your token</div>' +
'<div class="token-box">' +
'<span class="token-value blurred" data-role="token">' + escapeHtml(state.token) + '</span>' +
'<button class="icon-btn" data-role="eye" title="Show / hide" aria-label="Show or hide token">' + svgEye(false) + '</button>' +
'<button class="icon-btn" data-role="copy" title="Copy" aria-label="Copy token">' + svgCopy() + '</button>' +
'</div>' +
'</div>' +
'<div class="footer">' +
'Built by <a href="https://discord.dog" target="_blank" rel="noopener">Discord.dog</a> · ' +
'Runs in browser · ' +
'<a href="https://discord.dog/tools/hypesquad-switcher/src" target="_blank" rel="noopener">Source code</a>' +
'</div>'
);
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(
'<div class="paste">' +
'<div class="paste-msg">' + escapeHtml(reason || "Couldn't read your token automatically.") +
' Paste it manually below. It never leaves your browser.</div>' +
'<input type="password" placeholder="Paste your Discord token" data-role="paste-input" autocomplete="off" />' +
'<button data-role="paste-btn">Continue</button>' +
'<div class="paste-msg" style="margin-top:8px;font-size:10px;">' +
'To find your token, drag ' +
'<a class="bookmarklet" href="javascript:(function()%7Blocation.reload()%3Bvar%20i%20%3D%20document.createElement('iframe')%3Bdocument.body.appendChild(i)%3Balert(i.contentWindow.localStorage.token)%7D)()">Grab Token</a>' +
' to your bookmarks bar, then click it on discord.com to get your token in a popup. Or paste this in DevTools (F12) Console:' +
'<code>(function(){location.reload();var i=document.createElement("iframe");document.body.appendChild(i);alert(i.contentWindow.localStorage.token)})()</code>' +
'</div>' +
'</div>' +
'<div class="footer">' +
'Built by <a href="https://discord.dog" target="_blank" rel="noopener">Discord.dog</a> · ' +
'Runs in browser · ' +
'<a href="https://discord.dog/tools/hypesquad-switcher/src" target="_blank" rel="noopener">Source code</a>' +
'</div>'
);
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(
'<div class="detecting">' +
'<div class="spinner"></div>' +
'<div class="detecting-msg">Detecting your token…</div>' +
'<div class="detecting-sub">Keep using Discord normally. This usually takes a second.</div>' +
'</div>'
);
}
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();
})();