mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2025-12-10 21:47:06 +03:00
Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into pr/Acors24/2086
This commit is contained in:
@@ -55,7 +55,7 @@ export function getHashParams(): Record<string, unknown> {
|
||||
|
||||
export function getExistingChapters(currentVideoID: VideoID, duration: number): SponsorTime[] {
|
||||
const chaptersBox = document.querySelector("ytd-macro-markers-list-renderer");
|
||||
const title = document.querySelector("[target-id=engagement-panel-macro-markers-auto-chapters] #title-text");
|
||||
const title = chaptersBox?.closest("ytd-engagement-panel-section-list-renderer")?.querySelector("#title-text.ytd-engagement-panel-title-header-renderer");
|
||||
if (title?.textContent?.includes("Key moment")) return [];
|
||||
|
||||
const chapters: SponsorTime[] = [];
|
||||
|
||||
@@ -9,8 +9,8 @@ import { FetchResponse, sendRequestToCustomServer } from "../../maze-utils/src/b
|
||||
* @param address The address to add to the SponsorBlock server address
|
||||
* @param callback
|
||||
*/
|
||||
export function asyncRequestToCustomServer(type: string, url: string, data = {}): Promise<FetchResponse> {
|
||||
return sendRequestToCustomServer(type, url, data);
|
||||
export function asyncRequestToCustomServer(type: string, url: string, data = {}, headers = {}): Promise<FetchResponse> {
|
||||
return sendRequestToCustomServer(type, url, data, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -20,10 +20,10 @@ export function asyncRequestToCustomServer(type: string, url: string, data = {})
|
||||
* @param address The address to add to the SponsorBlock server address
|
||||
* @param callback
|
||||
*/
|
||||
export async function asyncRequestToServer(type: string, address: string, data = {}): Promise<FetchResponse> {
|
||||
export async function asyncRequestToServer(type: string, address: string, data = {}, headers = {}): Promise<FetchResponse> {
|
||||
const serverAddress = Config.config.testingServer ? CompileConfig.testingServerAddress : Config.config.serverAddress;
|
||||
|
||||
return await (asyncRequestToCustomServer(type, serverAddress + address, data));
|
||||
return await (asyncRequestToCustomServer(type, serverAddress + address, data, headers));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
104
src/utils/segmentData.ts
Normal file
104
src/utils/segmentData.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { DataCache } from "../../maze-utils/src/cache";
|
||||
import { getHash, HashedValue } from "../../maze-utils/src/hash";
|
||||
import Config from "../config";
|
||||
import * as CompileConfig from "../../config.json";
|
||||
import { ActionType, ActionTypes, SponsorSourceType, SponsorTime, VideoID } from "../types";
|
||||
import { getHashParams } from "./pageUtils";
|
||||
import { asyncRequestToServer } from "./requests";
|
||||
|
||||
const segmentDataCache = new DataCache<VideoID, SegmentResponse>(() => {
|
||||
return {
|
||||
segments: null,
|
||||
status: 200
|
||||
};
|
||||
}, 5);
|
||||
|
||||
const pendingList: Record<VideoID, Promise<SegmentResponse>> = {};
|
||||
|
||||
export interface SegmentResponse {
|
||||
segments: SponsorTime[] | null;
|
||||
status: number;
|
||||
}
|
||||
|
||||
export async function getSegmentsForVideo(videoID: VideoID, ignoreCache: boolean): Promise<SegmentResponse> {
|
||||
if (!ignoreCache) {
|
||||
const cachedData = segmentDataCache.getFromCache(videoID);
|
||||
if (cachedData) {
|
||||
segmentDataCache.cacheUsed(videoID);
|
||||
return cachedData;
|
||||
}
|
||||
}
|
||||
|
||||
if (pendingList[videoID]) {
|
||||
return await pendingList[videoID];
|
||||
}
|
||||
|
||||
const pendingData = fetchSegmentsForVideo(videoID);
|
||||
pendingList[videoID] = pendingData;
|
||||
|
||||
const result = await pendingData;
|
||||
delete pendingList[videoID];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function fetchSegmentsForVideo(videoID: VideoID): Promise<SegmentResponse> {
|
||||
const categories: string[] = Config.config.categorySelections.map((category) => category.name);
|
||||
|
||||
const extraRequestData: Record<string, unknown> = {};
|
||||
const hashParams = getHashParams();
|
||||
if (hashParams.requiredSegment) extraRequestData.requiredSegment = hashParams.requiredSegment;
|
||||
|
||||
const hashPrefix = (await getHash(videoID, 1)).slice(0, 4) as VideoID & HashedValue;
|
||||
const response = await asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, {
|
||||
categories: CompileConfig.categoryList,
|
||||
actionTypes: ActionTypes,
|
||||
...extraRequestData
|
||||
}, {
|
||||
"X-CLIENT-NAME": `${chrome.runtime.id}/v${chrome.runtime.getManifest().version}`
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const enabledActionTypes = getEnabledActionTypes();
|
||||
|
||||
const receivedSegments: SponsorTime[] = JSON.parse(response.responseText)
|
||||
?.filter((video) => video.videoID === videoID)
|
||||
?.map((video) => video.segments)?.[0]
|
||||
?.filter((segment) => enabledActionTypes.includes(segment.actionType) && categories.includes(segment.category))
|
||||
?.map((segment) => ({
|
||||
...segment,
|
||||
source: SponsorSourceType.Server
|
||||
}))
|
||||
?.sort((a, b) => a.segment[0] - b.segment[0]);
|
||||
|
||||
if (receivedSegments && receivedSegments.length) {
|
||||
const result = {
|
||||
segments: receivedSegments,
|
||||
status: response.status
|
||||
};
|
||||
|
||||
segmentDataCache.setupCache(videoID).segments = result.segments;
|
||||
return result;
|
||||
} else {
|
||||
// Setup with null data
|
||||
segmentDataCache.setupCache(videoID);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
segments: null,
|
||||
status: response.status
|
||||
};
|
||||
}
|
||||
|
||||
function getEnabledActionTypes(forceFullVideo = false): ActionType[] {
|
||||
const actionTypes = [ActionType.Skip, ActionType.Poi, ActionType.Chapter];
|
||||
if (Config.config.muteSegments) {
|
||||
actionTypes.push(ActionType.Mute);
|
||||
}
|
||||
if (Config.config.fullVideoSegments || forceFullVideo) {
|
||||
actionTypes.push(ActionType.Full);
|
||||
}
|
||||
|
||||
return actionTypes;
|
||||
}
|
||||
@@ -1,10 +1,15 @@
|
||||
import { isOnInvidious, parseYouTubeVideoIDFromURL } from "../../maze-utils/src/video";
|
||||
import Config from "../config";
|
||||
import { getVideoLabel } from "./videoLabels";
|
||||
import { setThumbnailListener } from "../../maze-utils/src/thumbnailManagement";
|
||||
import { getThumbnailSelector, setThumbnailListener } from "../../maze-utils/src/thumbnailManagement";
|
||||
import { VideoID } from "../types";
|
||||
import { getSegmentsForVideo } from "./segmentData";
|
||||
|
||||
export async function labelThumbnails(thumbnails: HTMLImageElement[]): Promise<void> {
|
||||
await Promise.all(thumbnails.map((t) => labelThumbnail(t)));
|
||||
export async function handleThumbnails(thumbnails: HTMLImageElement[]): Promise<void> {
|
||||
await Promise.all(thumbnails.map((t) => {
|
||||
labelThumbnail(t);
|
||||
setupThumbnailHover(t);
|
||||
}));
|
||||
}
|
||||
|
||||
export async function labelThumbnail(thumbnail: HTMLImageElement): Promise<HTMLElement | null> {
|
||||
@@ -13,9 +18,7 @@ export async function labelThumbnail(thumbnail: HTMLImageElement): Promise<HTMLE
|
||||
return null;
|
||||
}
|
||||
|
||||
const link = (isOnInvidious() ? thumbnail.parentElement : thumbnail.querySelector("#thumbnail")) as HTMLAnchorElement
|
||||
if (!link || link.nodeName !== "A" || !link.href) return null; // no link found
|
||||
const videoID = parseYouTubeVideoIDFromURL(link.href)?.videoID;
|
||||
const videoID = extractVideoID(thumbnail);
|
||||
if (!videoID) {
|
||||
hideThumbnailLabel(thumbnail);
|
||||
return null;
|
||||
@@ -37,6 +40,47 @@ export async function labelThumbnail(thumbnail: HTMLImageElement): Promise<HTMLE
|
||||
return overlay;
|
||||
}
|
||||
|
||||
export async function setupThumbnailHover(thumbnail: HTMLImageElement): Promise<void> {
|
||||
// Cache would be reset every load due to no SPA
|
||||
if (isOnInvidious()) return;
|
||||
|
||||
const mainElement = thumbnail.closest("#dismissible") as HTMLElement;
|
||||
if (mainElement) {
|
||||
mainElement.removeEventListener("mouseenter", thumbnailHoverListener);
|
||||
mainElement.addEventListener("mouseenter", thumbnailHoverListener);
|
||||
}
|
||||
}
|
||||
|
||||
function thumbnailHoverListener(e: MouseEvent) {
|
||||
if (!chrome.runtime?.id) return;
|
||||
|
||||
const thumbnail = (e.target as HTMLElement).querySelector(getThumbnailSelector()) as HTMLImageElement;
|
||||
if (!thumbnail) return;
|
||||
|
||||
// Pre-fetch data for this video
|
||||
const videoID = extractVideoID(thumbnail);
|
||||
if (videoID) {
|
||||
void getSegmentsForVideo(videoID, false);
|
||||
}
|
||||
}
|
||||
|
||||
function getLink(thumbnail: HTMLImageElement): HTMLAnchorElement | null {
|
||||
if (isOnInvidious()) {
|
||||
return thumbnail.parentElement as HTMLAnchorElement | null;
|
||||
} else if (thumbnail.nodeName.toLowerCase() === "yt-thumbnail-view-model") {
|
||||
return thumbnail.closest("yt-lockup-view-model")?.querySelector("a.yt-lockup-metadata-view-model-wiz__title");
|
||||
} else {
|
||||
return thumbnail.querySelector("#thumbnail");
|
||||
}
|
||||
}
|
||||
|
||||
function extractVideoID(thumbnail: HTMLImageElement): VideoID | null {
|
||||
const link = getLink(thumbnail);
|
||||
if (!link || link.nodeName !== "A" || !link.href) return null; // no link found
|
||||
|
||||
return parseYouTubeVideoIDFromURL(link.href)?.videoID;
|
||||
}
|
||||
|
||||
function getOldThumbnailLabel(thumbnail: HTMLImageElement): HTMLElement | null {
|
||||
return thumbnail.querySelector(".sponsorThumbnailLabel") as HTMLElement | null;
|
||||
}
|
||||
@@ -109,7 +153,7 @@ function insertSBIconDefinition() {
|
||||
}
|
||||
|
||||
export function setupThumbnailListener(): void {
|
||||
setThumbnailListener(labelThumbnails, () => {
|
||||
setThumbnailListener(handleThumbnails, () => {
|
||||
insertSBIconDefinition();
|
||||
}, () => Config.isReady());
|
||||
}
|
||||
Reference in New Issue
Block a user