Isolate and switch OWOT/Uvias accounts.
// ==UserScript==
// @name OWOT Hotswap
// @namespace gimmickCellar
// @version 1.1
// @description Isolate and switch OWOT/Uvias accounts.
// @author gimmickCellar
// @match *://*.ourworldoftext.com/*
// @match *://ourworldoftext.com/*
// @match http://localhost:8080/*
// @match http://127.0.0.1:8080/*
// @match *://uvias.com/*
// @match *://*.uvias.com/*
// @license unlicense
// @grant GM_cookie
// @run-at document-idle
// ==/UserScript==
(async function() {
'use strict';
const host = location.hostname;
if (!host.endsWith('ourworldoftext.com') && host !== 'localhost' && host !== '127.0.0.1') return;
if (typeof GM_cookie === 'undefined') return;
const KEY = 'owot_ultimate_accounts';
const base = host.includes('ourworldoftext.com') ? '.ourworldoftext.com' : host;
const isTest = host.includes('test.') || host === 'localhost' || host === '127.0.0.1';
const loginUrl = `https://uvias.com/api/loginto/${isTest ? 'owottest' : 'owot'}`;
let accounts = JSON.parse(localStorage.getItem(KEY) || '{}');
const getC = (d) => new Promise(res => GM_cookie.list(d, (c, e) => res(e ? [] : (c || []))));
const setC = (d) => new Promise(res => GM_cookie.set(d, res));
const delC = (d) => new Promise(res => GM_cookie.delete(d, res));
const getStorage = () => {
let s = {};
for (let i = 0; i < localStorage.length; i++) {
let k = localStorage.key(i);
if (k !== KEY) s[k] = localStorage.getItem(k);
}
return s;
};
const setStorage = (s) => {
let keys = [];
for (let i = 0; i < localStorage.length; i++) {
let k = localStorage.key(i);
if (k !== KEY) keys.push(k);
}
keys.forEach(k => localStorage.removeItem(k));
if (s) Object.keys(s).forEach(k => localStorage.setItem(k, s[k]));
};
const getMe = () => {
if (window.state?.userModel) return window.state.userModel.username;
let t = document.getElementById('topbar');
return t ? t.innerText.match(/Logged in as\s+([^\s:()]+)/)?.[1].trim() : null;
};
// Keep stored credentials up to date to prevent inactivity logouts
const sync = async () => {
const me = getMe();
if (!me) return;
let owotC = await getC({ url: location.href });
let auth = owotC.find(c => c.name === 'token' || c.name === 'sessionid');
let uviasC = await getC({ url: 'https://uvias.com/' });
if (auth) {
auth.domain = base;
accounts[me] = { auth, uviasC, storage: getStorage() };
localStorage.setItem(KEY, JSON.stringify(accounts));
}
};
await sync();
setInterval(sync, 30000);
const swap = async (user) => {
let acc = accounts[user];
if (!acc) return;
let curOwot = await getC({ url: location.href });
for (let c of curOwot) if (c.name === 'token' || c.name === 'sessionid') await delC({ url: location.href, name: c.name });
let curUv = await getC({ url: 'https://uvias.com/' });
for (let c of curUv) await delC({ url: 'https://uvias.com/', name: c.name });
if (acc.auth) {
let d = acc.auth;
await setC({ url: location.href, name: d.name, value: d.value, domain: d.domain, path: d.path || '/', secure: d.secure, httpOnly: d.httpOnly });
}
if (acc.uviasC) {
for (let c of acc.uviasC) await setC({ url: 'https://uvias.com/', name: c.name, value: c.value, path: c.path || '/', secure: c.secure, httpOnly: c.httpOnly });
}
setStorage(acc.storage);
location.reload();
};
const draw = () => {
if (document.getElementById('ac-mgr-ui')) return;
const me = getMe();
const ui = document.createElement('div');
ui.id = 'ac-mgr-ui';
ui.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:#E5E5FF;border:2px solid #000;padding:15px;z-index:1000001;font-family:Verdana;font-size:12px;min-width:280px;box-shadow:5px 5px 0px rgba(0,0,0,0.2);';
ui.innerHTML = '<strong>Account Manager</strong><hr style="border:0;border-top:1px solid #000;margin:10px 0;"><div id="ac-list" style="max-height:250px;overflow-y:auto;"></div>';
const list = ui.querySelector('#ac-list');
Object.keys(accounts).forEach(u => {
const row = document.createElement('div');
row.style.cssText = 'display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid #bbb;align-items:center;';
const btn = document.createElement('a');
btn.href = '#';
btn.innerText = u + (u === me ? " (Active)" : "");
btn.style.cssText = `color:${u === me ? 'green' : 'blue'}; font-weight:${u === me ? 'bold' : 'normal'}; text-decoration:underline;`;
if (u !== me) btn.onclick = () => swap(u);
const del = document.createElement('span');
del.innerHTML = '✖';
del.style.cssText = 'color:red;cursor:pointer;font-weight:bold;padding:0 5px;';
del.onclick = () => {
if (confirm(`Forget ${u}?`)) {
delete accounts[u];
localStorage.setItem(KEY, JSON.stringify(accounts));
ui.remove(); ovl.remove(); draw();
}
};
row.append(btn, del);
list.append(row);
});
const btns = document.createElement('div');
btns.style.marginTop = '15px';
const add = document.createElement('button');
add.innerText = '+ Add Account';
add.onclick = async () => {
let owot = await getC({ url: location.href });
for (let c of owot) if (c.name === 'token' || c.name === 'sessionid') await delC({ url: location.href, name: c.name });
let uv = await getC({ url: 'https://uvias.com/' });
for (let c of uv) await delC({ url: 'https://uvias.com/', name: c.name });
setStorage({});
location.href = loginUrl;
};
const cls = document.createElement('button');
cls.innerText = 'Close';
cls.style.marginLeft = '8px';
cls.onclick = () => { ui.remove(); ovl.remove(); };
btns.append(add, cls);
ui.append(btns);
const ovl = document.createElement('div');
ovl.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.4);z-index:1000000;';
ovl.onclick = () => { ui.remove(); ovl.remove(); };
document.body.append(ovl, ui);
};
if (window.location.pathname.length >= 1) {
const check = setInterval(() => {
if (window.w?.menu) {
clearInterval(check);
window.w.menu.addOption("Account Manager", draw);
}
}, 400);
}
const t = document.getElementById('topbar');
if (t) {
const a = document.createElement('a');
a.href = "#"; a.innerText = "Account Manager"; a.style.fontWeight = "bold";
a.onclick = (e) => { e.preventDefault(); draw(); };
t.append(document.createTextNode(' | '), a);
}
})();