fuck shit fuck fuck fuck fuck fuck

This commit is contained in:
Clove 2026-06-19 02:22:49 +01:00
parent 84868aba45
commit ec7f9cf1a7
3 changed files with 122 additions and 4 deletions

View File

@ -38,6 +38,17 @@
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Cool People | Clove Twilight">
<meta name="twitter:description" content="Cool people Clove knows!">
<style>
.friends-disclaimer {
margin: 2.5rem auto 0;
max-width: 60ch;
text-align: center;
font-size: .8rem;
line-height: 1.5;
opacity: .6;
}
.friends-disclaimer a { color: inherit; text-decoration: underline; }
</style>
</head>
<body>
@ -64,6 +75,12 @@
<!-- Cards are rendered by friends.js from the FRIENDS config -->
<div id="friends-root"></div>
<p class="friends-disclaimer">
Presence data is served by
<a href="https://restful.doughmination.uk" target="_blank" rel="noopener">Doughmination Restful</a>
by default, falling back to Lanyard and Dustin&rsquo;s API.
</p>
</div>
<script src="/js/core.js" data-cat="/assets/oneko/classics/classic.png"></script>

View File

@ -648,8 +648,108 @@
ws.addEventListener("error", () => { try { ws.close(); } catch (e) {} });
}
// ---- data source: self-hosted API primary, Lanyard + dstn fallback ------
// The Doughmination Restful API returns presence + full profile in one call,
// so it drives the card on its own. We poll it; only if it can't serve us
// (network error, or no live presence) do we fail over to the Lanyard
// websocket + dstn.to, the same chain the friends grid uses.
const SELF_BASE = "https://restful.doughmination.uk/v1/users/";
const SELF_POLL_MS = 20000; // own-presence refresh cadence
const SELF_GIVE_UP = 3; // consecutive misses before failing over
let selfTimer = null;
let selfMisses = 0;
let lanyardActive = false;
// self-host shape -> the Lanyard-shaped object render() already understands
function mapSelfHostToLanyard(j) {
const u = (j.data && j.data.user) || {};
const p = (j.data && j.data.presence) || {};
const plat = p.platform || {};
const dec = u.avatar_decoration;
const clan = u.clan;
return {
discord_user: {
id: u.id || DISCORD_USER_ID,
username: u.username,
global_name: u.global_name,
display_name: u.display_name,
avatar: u.avatar,
avatar_decoration_data: (dec && dec.asset) ? { asset: dec.asset } : null,
primary_guild: (clan && clan.tag)
? { tag: clan.tag, identity_enabled: true, badge: clan.badge, identity_guild_id: clan.guild_id }
: null,
public_flags: 0
},
discord_status: p.status || (p.online ? "online" : "offline"),
activities: p.activities || [],
listening_to_spotify: !!p.listening_to_spotify,
spotify: p.spotify || null,
active_on_discord_desktop: !!plat.desktop,
active_on_discord_mobile: !!plat.mobile,
active_on_discord_web: !!plat.web,
kv: {}
};
}
function renderFromSelfHost(j) {
const u = (j.data && j.data.user) || {};
render(mapSelfHostToLanyard(j));
// badges arrive pre-resolved (same consumer as dstn badges)
if (Array.isArray(j.data.badges) && j.data.badges.length) {
dstnBadges = j.data.badges;
paintBadges();
}
// profile extras: banner rebuilt from the raw hash (dodges the animated
// .gif 415), plus bio / connections / pronouns straight from the API
applyBanner(
bannerUrl(u.id || DISCORD_USER_ID, u.banner),
(typeof u.accent_color === "number") ? intToHex(u.accent_color) : null
);
renderBio(u.bio);
renderConnections(j.data.connected_accounts);
if (pronounsEl) {
if (u.pronouns) { pronounsEl.textContent = u.pronouns; pronounsEl.hidden = false; }
else pronounsEl.hidden = true;
}
}
function loadSelfHosted() {
return fetch(SELF_BASE + DISCORD_USER_ID, { cache: "no-store" })
.then(function (r) { return r.ok ? r.json().catch(function () { return null; }) : null; })
.then(function (j) {
// need a live presence object to drive the card; else fall back
if (!j || !j.success || !j.data || !j.data.user || !j.data.presence) return false;
renderFromSelfHost(j);
return true;
})
.catch(function () { return false; });
}
function startLanyardFallback() {
if (lanyardActive) return;
lanyardActive = true;
if (selfTimer) { clearInterval(selfTimer); selfTimer = null; }
connect();
loadDstn();
}
function pollSelfHost() {
if (document.hidden) return;
loadSelfHosted().then(function (ok) {
if (ok) { selfMisses = 0; return; }
if (++selfMisses >= SELF_GIVE_UP) startLanyardFallback();
});
}
// boot: Doughmination first; Lanyard/dstn only if it can't serve us
loadSelfHosted().then(function (ok) {
if (ok) {
selfMisses = 0;
selfTimer = setInterval(pollSelfHost, SELF_POLL_MS);
} else {
startLanyardFallback();
}
});
document.addEventListener("visibilitychange", () => {
if (!document.hidden && latest) updateTimes();

View File

@ -419,8 +419,9 @@
group.members.forEach(function (m) {
var refs = buildCard(m);
grid.appendChild(refs.el);
// dead alts are banned/retired — never pull live data for them
if (m.discordId && m.tier !== "dead-alt") {
// poll anyone with an ID — even "dead" alts can be semi-alive. Accounts
// that 404 everywhere are dropped automatically by the give-up logic.
if (m.discordId) {
var entry = { m: m, refs: refs, misses: 0, stop: false };
liveMembers.push(entry);
pollEntry(entry);