mirror of
https://github.com/younesaassila/ttv-lol-pro.git
synced 2025-05-08 02:14:30 +02:00
Add ad replacement logic
This commit is contained in:
parent
d125d4bacc
commit
b4bf5c2dec
@ -39,29 +39,15 @@ export function getFetch(options: FetchOptions): typeof fetch {
|
|||||||
// TODO: Clear variables on navigation.
|
// TODO: Clear variables on navigation.
|
||||||
const knownVideoWeaverUrls = new Set<string>();
|
const knownVideoWeaverUrls = new Set<string>();
|
||||||
const videoWeaverUrlsToFlag = new Map<string, number>(); // Video Weaver URLs to flag -> number of times flagged.
|
const videoWeaverUrlsToFlag = new Map<string, number>(); // Video Weaver URLs to flag -> number of times flagged.
|
||||||
const videoWeaverUrlsToIgnore = new Set<string>(); // No response check.
|
|
||||||
|
|
||||||
// TODO: Again, what happens when the user navigates to another channel?
|
// TODO: Again, what happens when the user navigates to another channel?
|
||||||
let cachedPlaybackTokenRequestHeaders: Map<string, string> | null = null;
|
let cachedPlaybackTokenRequestHeaders: Map<string, string> | null = null;
|
||||||
let cachedPlaybackTokenRequestBody: string | null = null;
|
let cachedPlaybackTokenRequestBody: string | null = null;
|
||||||
let cachedUsherRequestUrl: string | null = null;
|
let cachedUsherRequestUrl: string | null = null;
|
||||||
|
|
||||||
let currentVideoWeaversMap: Map<string, string> | null = null;
|
let assignedVideoWeaversMap: Map<string, string> | null = null;
|
||||||
let replacementVideoWeaversMap: Map<string, string> | null = null;
|
let replacementVideoWeaversMap: Map<string, string> | null = null;
|
||||||
|
let consecutiveMidrollResponses = 0;
|
||||||
function usherResponseToMap(response: string): Map<string, string> | null {
|
|
||||||
const parser = new m3u8Parser.Parser();
|
|
||||||
parser.push(response);
|
|
||||||
parser.end();
|
|
||||||
const parsedManifest = parser.manifest;
|
|
||||||
if (parsedManifest.playlists == null) return null;
|
|
||||||
return new Map(
|
|
||||||
parsedManifest.playlists.map(playlist => [
|
|
||||||
playlist.attributes.VIDEO,
|
|
||||||
playlist.uri,
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.shouldWaitForStore) {
|
if (options.shouldWaitForStore) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -87,32 +73,36 @@ export function getFetch(options: FetchOptions): typeof fetch {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TEST CODE
|
async function setReplacementVideoWeaversMap() {
|
||||||
if (options.scope === "worker") {
|
try {
|
||||||
setTimeout(async () => {
|
console.log("[TTV LOL PRO] 🔄 Checking for new Video Weaver URLs…");
|
||||||
try {
|
const newUsherManifest = await fetchReplacementUsherManifest(
|
||||||
console.log("[TTV LOL PRO] 🔄 Checking for new Video Weaver URLs…");
|
cachedUsherRequestUrl
|
||||||
const newUsherManifest = await fetchReplacementUsherManifest(
|
);
|
||||||
cachedUsherRequestUrl
|
if (newUsherManifest == null) {
|
||||||
);
|
console.log("[TTV LOL PRO] 🔄 No new Video Weaver URLs found.");
|
||||||
if (newUsherManifest == null) {
|
return;
|
||||||
console.log("[TTV LOL PRO] 🔄 No new Video Weaver URLs found.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
replacementVideoWeaversMap = usherResponseToMap(newUsherManifest);
|
|
||||||
console.log(
|
|
||||||
"[TTV LOL PRO] 🔄 Found new Video Weaver URLs:",
|
|
||||||
Object.fromEntries(replacementVideoWeaversMap?.entries() ?? [])
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(
|
|
||||||
"[TTV LOL PRO] 🔄 Failed to get new Video Weaver URLs:",
|
|
||||||
error
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}, 30000);
|
replacementVideoWeaversMap =
|
||||||
|
getVideoWeaversMapFromUsherResponse(newUsherManifest);
|
||||||
|
console.log(
|
||||||
|
"[TTV LOL PRO] 🔄 Found new Video Weaver URLs:",
|
||||||
|
Object.fromEntries(replacementVideoWeaversMap?.entries() ?? [])
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
replacementVideoWeaversMap = null;
|
||||||
|
console.error(
|
||||||
|
"[TTV LOL PRO] 🔄 Failed to get new Video Weaver URLs:",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// // TEST CODE
|
||||||
|
// if (options.scope === "worker") {
|
||||||
|
// setTimeout(setReplacementVideoWeaversMap, 30000);
|
||||||
|
// }
|
||||||
|
|
||||||
return async function fetch(
|
return async function fetch(
|
||||||
input: RequestInfo | URL,
|
input: RequestInfo | URL,
|
||||||
init?: RequestInit
|
init?: RequestInit
|
||||||
@ -200,8 +190,8 @@ export function getFetch(options: FetchOptions): typeof fetch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
flagRequest(headersMap);
|
flagRequest(headersMap);
|
||||||
// cachedPlaybackTokenRequestHeaders = headersMap;
|
cachedPlaybackTokenRequestHeaders = headersMap;
|
||||||
// cachedPlaybackTokenRequestBody = requestBody;
|
cachedPlaybackTokenRequestBody = requestBody;
|
||||||
} else if (
|
} else if (
|
||||||
requestBody != null &&
|
requestBody != null &&
|
||||||
requestBody.includes("PlaybackAccessToken")
|
requestBody.includes("PlaybackAccessToken")
|
||||||
@ -210,8 +200,8 @@ export function getFetch(options: FetchOptions): typeof fetch {
|
|||||||
"[TTV LOL PRO] 🥅 Caught GraphQL PlaybackAccessToken request. Flagging…"
|
"[TTV LOL PRO] 🥅 Caught GraphQL PlaybackAccessToken request. Flagging…"
|
||||||
);
|
);
|
||||||
flagRequest(headersMap);
|
flagRequest(headersMap);
|
||||||
// cachedPlaybackTokenRequestHeaders = headersMap;
|
cachedPlaybackTokenRequestHeaders = headersMap;
|
||||||
// cachedPlaybackTokenRequestBody = requestBody;
|
cachedPlaybackTokenRequestBody = requestBody;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,30 +216,32 @@ export function getFetch(options: FetchOptions): typeof fetch {
|
|||||||
// Video Weaver requests.
|
// Video Weaver requests.
|
||||||
if (host != null && videoWeaverHostRegex.test(host)) {
|
if (host != null && videoWeaverHostRegex.test(host)) {
|
||||||
console.debug(`[TTV LOL PRO] 🥅 Caught Video Weaver request '${url}'.`);
|
console.debug(`[TTV LOL PRO] 🥅 Caught Video Weaver request '${url}'.`);
|
||||||
// TODO: Implement replacement limit if the ad is a preroll to avoid infinite loops.
|
|
||||||
let videoWeaverUrl = url;
|
let videoWeaverUrl = url;
|
||||||
if (
|
if (replacementVideoWeaversMap != null) {
|
||||||
replacementVideoWeaversMap != null &&
|
const video = [...(assignedVideoWeaversMap?.entries() ?? [])].find(
|
||||||
replacementVideoWeaversMap.size > 0
|
|
||||||
) {
|
|
||||||
const video = [...(currentVideoWeaversMap?.entries() ?? [])].find(
|
|
||||||
([key, value]) => value === url
|
([key, value]) => value === url
|
||||||
)?.[0];
|
)?.[0];
|
||||||
if (video != null && replacementVideoWeaversMap.has(video)) {
|
if (video != null && replacementVideoWeaversMap.has(video)) {
|
||||||
videoWeaverUrl = replacementVideoWeaversMap.get(video)!;
|
videoWeaverUrl = replacementVideoWeaversMap.get(video)!;
|
||||||
} else {
|
console.log(
|
||||||
|
`[TTV LOL PRO] 🔄 Replaced Video Weaver URL '${url}' with '${videoWeaverUrl}'.`
|
||||||
|
);
|
||||||
|
} else if (replacementVideoWeaversMap.size > 0) {
|
||||||
videoWeaverUrl = [...replacementVideoWeaversMap.values()][0];
|
videoWeaverUrl = [...replacementVideoWeaversMap.values()][0];
|
||||||
|
console.log(
|
||||||
|
`[TTV LOL PRO] 🔄 Replaced Video Weaver URL '${url}' with '${videoWeaverUrl}' (fallback).`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
`[TTV LOL PRO] 🔄 No replacement Video Weaver URL found for '${url}'.`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
console.log(
|
|
||||||
`[TTV LOL PRO] 🔄 Replaced Video Weaver URL '${url}' with '${videoWeaverUrl}'.`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const isIgnoredUrl = videoWeaverUrlsToIgnore.has(videoWeaverUrl);
|
|
||||||
const isNewUrl = !knownVideoWeaverUrls.has(videoWeaverUrl);
|
const isNewUrl = !knownVideoWeaverUrls.has(videoWeaverUrl);
|
||||||
const isFlaggedUrl = videoWeaverUrlsToFlag.has(videoWeaverUrl);
|
const isFlaggedUrl = videoWeaverUrlsToFlag.has(videoWeaverUrl);
|
||||||
|
|
||||||
if (!isIgnoredUrl && (isNewUrl || isFlaggedUrl)) {
|
if (isNewUrl || isFlaggedUrl) {
|
||||||
console.log(
|
console.log(
|
||||||
`[TTV LOL PRO] 🥅 Caught ${
|
`[TTV LOL PRO] 🥅 Caught ${
|
||||||
isNewUrl
|
isNewUrl
|
||||||
@ -292,7 +284,8 @@ export function getFetch(options: FetchOptions): typeof fetch {
|
|||||||
if (host != null && usherHostRegex.test(host)) {
|
if (host != null && usherHostRegex.test(host)) {
|
||||||
responseBody = await readResponseBody();
|
responseBody = await readResponseBody();
|
||||||
console.debug("[TTV LOL PRO] 🥅 Caught Usher response.");
|
console.debug("[TTV LOL PRO] 🥅 Caught Usher response.");
|
||||||
currentVideoWeaversMap = usherResponseToMap(responseBody);
|
assignedVideoWeaversMap =
|
||||||
|
getVideoWeaversMapFromUsherResponse(responseBody);
|
||||||
replacementVideoWeaversMap = null;
|
replacementVideoWeaversMap = null;
|
||||||
const videoWeaverUrls = responseBody
|
const videoWeaverUrls = responseBody
|
||||||
.split("\n")
|
.split("\n")
|
||||||
@ -313,36 +306,24 @@ export function getFetch(options: FetchOptions): typeof fetch {
|
|||||||
if (host != null && videoWeaverHostRegex.test(host)) {
|
if (host != null && videoWeaverHostRegex.test(host)) {
|
||||||
responseBody = await readResponseBody();
|
responseBody = await readResponseBody();
|
||||||
|
|
||||||
// Check if response contains ad.
|
// Check if response contains midroll ad.
|
||||||
if (responseBody.includes("stitched-ad")) {
|
if (
|
||||||
|
responseBody.includes("stitched-ad") &&
|
||||||
|
responseBody.toLowerCase().includes("midroll")
|
||||||
|
) {
|
||||||
console.log(
|
console.log(
|
||||||
"[TTV LOL PRO] 🥅 Caught Video Weaver response containing ad."
|
"[TTV LOL PRO] 🥅 Caught Video Weaver response containing ad."
|
||||||
);
|
);
|
||||||
// if (videoWeaverUrlsToIgnore.has(url)) return response;
|
consecutiveMidrollResponses += 1;
|
||||||
// if (!videoWeaverUrlsToFlag.has(url)) {
|
// Avoid infinite loops.
|
||||||
// // Let's proxy the next request for this URL, 2 attempts left.
|
if (consecutiveMidrollResponses <= 2) {
|
||||||
// videoWeaverUrlsToFlag.set(url, 0);
|
await setReplacementVideoWeaversMap();
|
||||||
// cancelRequest();
|
} else {
|
||||||
// }
|
replacementVideoWeaversMap = null;
|
||||||
// // FIXME: This workaround doesn't work. Let's find another way.
|
}
|
||||||
// // 0: First attempt, not proxied, cancelled.
|
|
||||||
// // 1: Second attempt, proxied, cancelled.
|
|
||||||
// // 2: Third attempt, proxied, last attempt by Twitch client.
|
|
||||||
// // If the third attempt contains an ad, we have to let it through.
|
|
||||||
// const isCancellable = videoWeaverUrlsToFlag.get(url)! < 2;
|
|
||||||
// if (isCancellable) {
|
|
||||||
// cancelRequest();
|
|
||||||
// } else {
|
|
||||||
// console.error(
|
|
||||||
// "[TTV LOL PRO] ❌ Could not cancel Video Weaver response containing ad. All attempts used."
|
|
||||||
// );
|
|
||||||
// videoWeaverUrlsToFlag.delete(url); // Clear attempts.
|
|
||||||
// videoWeaverUrlsToIgnore.add(url); // Ignore this URL, there's nothing we can do.
|
|
||||||
// }
|
|
||||||
} else {
|
} else {
|
||||||
// // No ad, remove from flagged list.
|
// No ad, clear attempts.
|
||||||
// videoWeaverUrlsToFlag.delete(url);
|
consecutiveMidrollResponses = 0;
|
||||||
// videoWeaverUrlsToIgnore.delete(url);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -490,6 +471,7 @@ async function fetchReplacementPlaybackAccessToken(
|
|||||||
cachedPlaybackTokenRequestHeaders: Map<string, string> | null,
|
cachedPlaybackTokenRequestHeaders: Map<string, string> | null,
|
||||||
cachedPlaybackTokenRequestBody: string | null
|
cachedPlaybackTokenRequestBody: string | null
|
||||||
): Promise<PlaybackAccessToken | null> {
|
): Promise<PlaybackAccessToken | null> {
|
||||||
|
// FIXME: Take anonymous mode into account.
|
||||||
let request: Request;
|
let request: Request;
|
||||||
if (
|
if (
|
||||||
cachedPlaybackTokenRequestHeaders != null &&
|
cachedPlaybackTokenRequestHeaders != null &&
|
||||||
@ -589,3 +571,19 @@ async function fetchReplacementUsherManifest(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getVideoWeaversMapFromUsherResponse(
|
||||||
|
response: string
|
||||||
|
): Map<string, string> | null {
|
||||||
|
const parser = new m3u8Parser.Parser();
|
||||||
|
parser.push(response);
|
||||||
|
parser.end();
|
||||||
|
const parsedManifest = parser.manifest;
|
||||||
|
if (parsedManifest.playlists == null) return null;
|
||||||
|
return new Map(
|
||||||
|
parsedManifest.playlists.map(playlist => [
|
||||||
|
playlist.attributes.VIDEO,
|
||||||
|
playlist.uri,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user