mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-30 17:32:26 +00:00
187 lines
4.1 KiB
JavaScript
187 lines
4.1 KiB
JavaScript
import { infoHashFromMagnet } from "./magnets.js";
|
|
import {
|
|
getDefaultHealth,
|
|
getHealthCached,
|
|
queueHealthCheck,
|
|
} from "./healthService.js";
|
|
|
|
const containerState = new WeakMap();
|
|
const ROOT_MARGIN = "200px 0px";
|
|
|
|
function ensureState(container) {
|
|
let state = containerState.get(container);
|
|
if (state) {
|
|
return state;
|
|
}
|
|
|
|
const pendingByCard = new WeakMap();
|
|
const observedCards = new WeakSet();
|
|
|
|
const observer = new IntersectionObserver(
|
|
(entries) => {
|
|
entries.forEach((entry) => {
|
|
const card = entry.target;
|
|
if (!(card instanceof HTMLElement)) {
|
|
return;
|
|
}
|
|
if (!entry.isIntersecting) {
|
|
return;
|
|
}
|
|
handleCardVisible({ card, pendingByCard });
|
|
});
|
|
},
|
|
{ root: null, rootMargin: ROOT_MARGIN, threshold: 0.01 }
|
|
);
|
|
|
|
state = { observer, pendingByCard, observedCards };
|
|
containerState.set(container, state);
|
|
return state;
|
|
}
|
|
|
|
function toVisual(health) {
|
|
if (!health) {
|
|
return "unknown";
|
|
}
|
|
if (health.ok && health.seeders > 0) {
|
|
return "good";
|
|
}
|
|
if (health.responded) {
|
|
if (health.seeders > 0) {
|
|
return "good";
|
|
}
|
|
return "none";
|
|
}
|
|
return "noresp";
|
|
}
|
|
|
|
function formatCount(health) {
|
|
if (!health || !Number.isFinite(health.seeders) || health.seeders <= 0) {
|
|
return "";
|
|
}
|
|
return ` (${health.seeders})`;
|
|
}
|
|
|
|
function setBadge(card, visual, health) {
|
|
const el = card.querySelector(".stream-health");
|
|
if (!el) {
|
|
return;
|
|
}
|
|
const map = {
|
|
good: {
|
|
text: "🟢",
|
|
aria: "Streamable: seeders available",
|
|
},
|
|
none: {
|
|
text: "🟡",
|
|
aria: "No seeders reported by trackers",
|
|
},
|
|
noresp: {
|
|
text: "⚫",
|
|
aria: "No tracker response",
|
|
},
|
|
checking: {
|
|
text: "🟦",
|
|
aria: "Checking stream availability",
|
|
},
|
|
unknown: {
|
|
text: "⚪",
|
|
aria: "Unknown stream availability",
|
|
},
|
|
};
|
|
const entry = map[visual] || map.unknown;
|
|
const countText = formatCount(health);
|
|
el.textContent = `${entry.text}${countText}`;
|
|
el.setAttribute("aria-label", countText ? `${entry.aria}${countText}` : entry.aria);
|
|
el.setAttribute("title", countText ? `${entry.aria}${countText}` : entry.aria);
|
|
el.dataset.streamHealthState = visual;
|
|
}
|
|
|
|
function applyHealth(card, health) {
|
|
if (!health) {
|
|
setBadge(card, "unknown");
|
|
return;
|
|
}
|
|
const visual = toVisual(health);
|
|
setBadge(card, visual, health);
|
|
}
|
|
|
|
function handleCardVisible({ card, pendingByCard }) {
|
|
if (!(card instanceof HTMLElement)) {
|
|
return;
|
|
}
|
|
const magnet = card.dataset.magnet || "";
|
|
if (!magnet) {
|
|
setBadge(card, "unknown");
|
|
return;
|
|
}
|
|
|
|
const infoHash = infoHashFromMagnet(magnet);
|
|
if (!infoHash) {
|
|
setBadge(card, "unknown");
|
|
return;
|
|
}
|
|
|
|
const cached = getHealthCached(infoHash);
|
|
if (cached) {
|
|
applyHealth(card, cached);
|
|
return;
|
|
}
|
|
|
|
if (pendingByCard.has(card)) {
|
|
return;
|
|
}
|
|
|
|
setBadge(card, "checking", getDefaultHealth());
|
|
const pending = queueHealthCheck(magnet).then((health) => {
|
|
pendingByCard.delete(card);
|
|
if (!card.isConnected) {
|
|
return;
|
|
}
|
|
applyHealth(card, health);
|
|
});
|
|
pending.catch(() => {
|
|
pendingByCard.delete(card);
|
|
if (!card.isConnected) {
|
|
return;
|
|
}
|
|
setBadge(card, "noresp");
|
|
});
|
|
pendingByCard.set(card, pending);
|
|
}
|
|
|
|
export function attachHealthBadges(container) {
|
|
if (!(container instanceof HTMLElement)) {
|
|
return;
|
|
}
|
|
const state = ensureState(container);
|
|
const cards = container.querySelectorAll(".video-card");
|
|
cards.forEach((card) => {
|
|
if (!(card instanceof HTMLElement)) {
|
|
return;
|
|
}
|
|
if (state.observedCards.has(card)) {
|
|
return;
|
|
}
|
|
state.observedCards.add(card);
|
|
state.observer.observe(card);
|
|
if (!card.dataset.magnet) {
|
|
setBadge(card, "unknown");
|
|
}
|
|
});
|
|
}
|
|
|
|
export function refreshHealthBadges(container) {
|
|
if (!(container instanceof HTMLElement)) {
|
|
return;
|
|
}
|
|
const state = containerState.get(container);
|
|
if (!state) {
|
|
return;
|
|
}
|
|
state.observer.takeRecords().forEach((entry) => {
|
|
if (entry.isIntersecting) {
|
|
handleCardVisible({ card: entry.target, pendingByCard: state.pendingByCard });
|
|
}
|
|
});
|
|
}
|