Files
SponsorBlock/src/utils/videoLabels.ts
mini-bomba bf6626f4b3 Improve error handling
- pass errors from background threads back to clients
- log all HTTP request errors and display them to the user where
  possible
2025-08-28 21:49:06 +02:00

97 lines
2.9 KiB
TypeScript

import { Category, CategorySkipOption, VideoID } from "../types";
import { getHash } from "../../maze-utils/src/hash";
import { logWarn } from "./logger";
import { asyncRequestToServer } from "./requests";
import { getCategorySelection } from "./skipRule";
import { FetchResponse, logRequest } from "../../maze-utils/src/background-request-proxy";
export interface VideoLabelsCacheData {
category: Category;
hasStartSegment: boolean;
}
export interface LabelCacheEntry {
timestamp: number;
videos: Record<VideoID, VideoLabelsCacheData>;
}
const labelCache: Record<string, LabelCacheEntry> = {};
const cacheLimit = 1000;
async function getLabelHashBlock(hashPrefix: string): Promise<LabelCacheEntry | null> {
// Check cache
const cachedEntry = labelCache[hashPrefix];
if (cachedEntry) {
return cachedEntry;
}
let response: FetchResponse;
try {
response = await asyncRequestToServer("GET", `/api/videoLabels/${hashPrefix}?hasStartSegment=true`);
} catch (e) {
console.error("[SB] Caught error while fetching video labels", e)
return null;
}
if (response.status !== 200) {
logRequest(response, "SB", "video labels");
// No video labels or server down
labelCache[hashPrefix] = {
timestamp: Date.now(),
videos: {},
};
return null;
}
try {
const data = JSON.parse(response.responseText);
const newEntry: LabelCacheEntry = {
timestamp: Date.now(),
videos: Object.fromEntries(data.map(video => [video.videoID, {
category: video.segments[0]?.category,
hasStartSegment: video.hasStartSegment
}])),
};
labelCache[hashPrefix] = newEntry;
if (Object.keys(labelCache).length > cacheLimit) {
// Remove oldest entry
const oldestEntry = Object.entries(labelCache).reduce((a, b) => a[1].timestamp < b[1].timestamp ? a : b);
delete labelCache[oldestEntry[0]];
}
return newEntry;
} catch (e) {
logWarn(`Error parsing video labels: ${e}`);
return null;
}
}
export async function getVideoLabel(videoID: VideoID): Promise<Category | null> {
const prefix = (await getHash(videoID, 1)).slice(0, 4);
const result = await getLabelHashBlock(prefix);
if (result) {
const category = result.videos[videoID]?.category;
if (category && getCategorySelection(result.videos[videoID]).option !== CategorySkipOption.Disabled) {
return category;
} else {
return null;
}
}
return null;
}
export async function getHasStartSegment(videoID: VideoID): Promise<boolean | null> {
const prefix = (await getHash(videoID, 1)).slice(0, 4);
const result = await getLabelHashBlock(prefix);
if (result) {
return result?.videos[videoID]?.hasStartSegment ?? false;
}
return null;
}