mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-06 21:02:28 +00:00
Refactor app orchestrator into Application with dependency injection
This commit is contained in:
1 parent
ef6b8bf1cf
commit
cf34e28a47
17 files changed
+525
-375
No files matched your search
@@ -0,0 +1,13 @@
|
||||
// js/utils/fakeDecrypt.js
|
||||
|
||||
/**
|
||||
* Simple "decryption" placeholder for private videos.
|
||||
*/
|
||||
export function fakeDecrypt(str) {
|
||||
if (typeof str !== "string") {
|
||||
return "";
|
||||
}
|
||||
return str.split("").reverse().join("");
|
||||
}
|
||||
|
||||
export default fakeDecrypt;
|
||||
@@ -0,0 +1,59 @@
|
||||
// js/utils/magnetValidators.js
|
||||
|
||||
import { safeDecodeMagnet } from "../magnetUtils.js";
|
||||
|
||||
/**
|
||||
* Basic validation for BitTorrent magnet URIs.
|
||||
*
|
||||
* Returns `true` only when the value looks like a magnet link that WebTorrent
|
||||
* understands (`magnet:` scheme with at least one `xt=urn:btih:<info-hash>`
|
||||
* entry, where `<info-hash>` is either a 40-character hex digest or a
|
||||
* 32-character base32 digest). Magnets that only contain BitTorrent v2 hashes
|
||||
* (e.g. `btmh`) are treated as unsupported.
|
||||
*/
|
||||
export function isValidMagnetUri(magnet) {
|
||||
const trimmed = typeof magnet === "string" ? magnet.trim() : "";
|
||||
if (!trimmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const decoded = safeDecodeMagnet(trimmed);
|
||||
const candidate = decoded || trimmed;
|
||||
|
||||
if (/^[0-9a-f]{40}$/i.test(candidate)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = new URL(candidate);
|
||||
if (parsed.protocol.toLowerCase() !== "magnet:") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const xtValues = parsed.searchParams.getAll("xt");
|
||||
if (!xtValues.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const hexPattern = /^[0-9a-f]{40}$/i;
|
||||
const base32Pattern = /^[A-Z2-7]{32}$/;
|
||||
|
||||
return xtValues.some((value) => {
|
||||
if (typeof value !== "string") return false;
|
||||
const match = value.trim().match(/^urn:btih:([a-z0-9]+)$/i);
|
||||
if (!match) return false;
|
||||
|
||||
const infoHash = match[1];
|
||||
if (hexPattern.test(infoHash)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const upperHash = infoHash.toUpperCase();
|
||||
return infoHash.length === 32 && base32Pattern.test(upperHash);
|
||||
});
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export default isValidMagnetUri;
|
||||
@@ -0,0 +1,82 @@
|
||||
// js/utils/mediaLoader.js
|
||||
|
||||
/**
|
||||
* Simple IntersectionObserver-based lazy loader for images (or videos).
|
||||
*
|
||||
* Usage:
|
||||
* const mediaLoader = new MediaLoader();
|
||||
* mediaLoader.observe(imgElement);
|
||||
*
|
||||
* This will load the real image source from `imgElement.dataset.lazy`
|
||||
* once the image enters the viewport.
|
||||
*/
|
||||
export class MediaLoader {
|
||||
constructor(rootMargin = "50px") {
|
||||
this.observer = new IntersectionObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
if (!entry.isIntersecting) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const el = entry.target;
|
||||
const lazySrc =
|
||||
typeof el.dataset.lazy === "string" ? el.dataset.lazy.trim() : "";
|
||||
|
||||
if (!lazySrc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const fallbackSrc =
|
||||
(typeof el.dataset.fallbackSrc === "string"
|
||||
? el.dataset.fallbackSrc.trim()
|
||||
: "") ||
|
||||
el.getAttribute("data-fallback-src") ||
|
||||
"";
|
||||
|
||||
if (el.tagName === "IMG" || el.tagName === "IMAGE") {
|
||||
el.src = lazySrc;
|
||||
if (fallbackSrc) {
|
||||
el.onerror = () => {
|
||||
el.src = fallbackSrc;
|
||||
};
|
||||
}
|
||||
} else if (el.tagName === "VIDEO") {
|
||||
el.src = lazySrc;
|
||||
if (fallbackSrc) {
|
||||
el.poster = fallbackSrc;
|
||||
}
|
||||
} else {
|
||||
el.style.backgroundImage = `url('${lazySrc}')`;
|
||||
}
|
||||
|
||||
el.dataset.thumbnailLoaded = "true";
|
||||
this.observer.unobserve(el);
|
||||
}
|
||||
}, {
|
||||
rootMargin,
|
||||
threshold: 0.01,
|
||||
});
|
||||
}
|
||||
|
||||
observe(el) {
|
||||
if (!el || typeof el !== "object") {
|
||||
return;
|
||||
}
|
||||
|
||||
if ("decoding" in HTMLImageElement.prototype) {
|
||||
el.decoding = el.decoding || "async";
|
||||
}
|
||||
|
||||
const lazySrc =
|
||||
typeof el.dataset.lazy === "string" ? el.dataset.lazy.trim() : "";
|
||||
if (lazySrc) {
|
||||
this.observer.observe(el);
|
||||
}
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.observer.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
export default MediaLoader;
|
||||
@@ -0,0 +1,27 @@
|
||||
// js/utils/pointer.js
|
||||
|
||||
export function pointerArrayToKey(pointer) {
|
||||
if (!Array.isArray(pointer) || pointer.length < 2) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const type = pointer[0] === "a" ? "a" : pointer[0] === "e" ? "e" : "";
|
||||
if (!type) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const value =
|
||||
typeof pointer[1] === "string" ? pointer[1].trim().toLowerCase() : "";
|
||||
if (!value) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const relay =
|
||||
pointer.length > 2 && typeof pointer[2] === "string"
|
||||
? pointer[2].trim()
|
||||
: "";
|
||||
|
||||
return relay ? `${type}:${value}:${relay}` : `${type}:${value}`;
|
||||
}
|
||||
|
||||
export default pointerArrayToKey;
|
||||
@@ -0,0 +1,29 @@
|
||||
// js/utils/videoDeduper.js
|
||||
|
||||
/**
|
||||
* Given an array of video objects, return only the newest (by created_at) for
|
||||
* each videoRootId. If no videoRootId is present, treat the video’s own ID as
|
||||
* its root.
|
||||
*/
|
||||
export function dedupeToNewestByRoot(videos) {
|
||||
const map = new Map();
|
||||
|
||||
for (const vid of videos || []) {
|
||||
if (!vid) {
|
||||
continue;
|
||||
}
|
||||
const rootId = vid.videoRootId || vid.id;
|
||||
if (!rootId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const existing = map.get(rootId);
|
||||
if (!existing || vid.created_at > existing.created_at) {
|
||||
map.set(rootId, vid);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(map.values());
|
||||
}
|
||||
|
||||
export default dedupeToNewestByRoot;
|
||||
Reference in new issue
Block a user