From 3536898e23d3609ad86395c27eb84b9bdc86a72a Mon Sep 17 00:00:00 2001 From: younesaassila <47226184+younesaassila@users.noreply.github.com> Date: Fri, 31 Jan 2025 23:07:37 +0100 Subject: [PATCH] Add WIP implementation of #177 --- src/common/ts/wasChannelSubscriber.ts | 12 ++ src/content/content.ts | 167 +++++++++++++++++--------- src/options/options.ts | 10 ++ src/options/page.html | 17 +++ src/page/getFetch.ts | 49 +++++++- src/store/getDefaultState.ts | 2 + src/store/types.ts | 2 + src/types.ts | 2 + 8 files changed, 206 insertions(+), 55 deletions(-) create mode 100644 src/common/ts/wasChannelSubscriber.ts diff --git a/src/common/ts/wasChannelSubscriber.ts b/src/common/ts/wasChannelSubscriber.ts new file mode 100644 index 00000000..0a372439 --- /dev/null +++ b/src/common/ts/wasChannelSubscriber.ts @@ -0,0 +1,12 @@ +import store from "../../store"; + +export default function wasChannelSubscriber( + channelName: string | null +): boolean { + if (!channelName) return false; + const activeChannelSubscriptionsLower = + store.state.activeChannelSubscriptions.map(channel => + channel.toLowerCase() + ); + return activeChannelSubscriptionsLower.includes(channelName.toLowerCase()); +} diff --git a/src/content/content.ts b/src/content/content.ts index 7477d6f4..120bc2aa 100644 --- a/src/content/content.ts +++ b/src/content/content.ts @@ -2,8 +2,10 @@ import pageScriptURL from "url:../page/page.ts"; import workerScriptURL from "url:../page/worker.ts"; import browser, { Storage } from "webextension-polyfill"; import findChannelFromTwitchTvUrl from "../common/ts/findChannelFromTwitchTvUrl"; +import isChannelWhitelisted from "../common/ts/isChannelWhitelisted"; import isChromium from "../common/ts/isChromium"; import { getStreamStatus, setStreamStatus } from "../common/ts/streamStatus"; +import wasChannelSubscriber from "../common/ts/wasChannelSubscriber"; import store from "../store"; import type { State } from "../store/types"; import { MessageType } from "../types"; @@ -64,6 +66,7 @@ function onStoreChange(changes: Record) { // This is mainly to reduce the amount of messages sent to the page script. // (Also to reduce the number of console logs.) const ignoredKeys: (keyof State)[] = [ + "activeChannelSubscriptions", "adLog", "dnsResponses", "openedTwitchTabs", @@ -102,61 +105,117 @@ function onPageMessage(event: MessageEvent) { const message = event.data?.message; if (!message) return; - switch (message.type) { - case MessageType.GetStoreState: - const sendStoreState = () => { - window.postMessage({ - type: MessageType.PageScriptMessage, - message: { - type: MessageType.GetStoreStateResponse, - state: JSON.parse(JSON.stringify(store.state)), - }, - }); - }; - if (store.readyState === "complete") sendStoreState(); - else store.addEventListener("load", sendStoreState); - break; - case MessageType.EnableFullMode: - try { - browser.runtime.sendMessage(message); - } catch (error) { - console.error( - "[TTV LOL PRO] Failed to send EnableFullMode message", - error - ); - } - break; - case MessageType.DisableFullMode: - try { - browser.runtime.sendMessage(message); - } catch (error) { - console.error( - "[TTV LOL PRO] Failed to send DisableFullMode message", - error - ); + // GetStoreState + if (message.type === MessageType.GetStoreState) { + const sendStoreState = () => { + window.postMessage({ + type: MessageType.PageScriptMessage, + message: { + type: MessageType.GetStoreStateResponse, + state: JSON.parse(JSON.stringify(store.state)), + }, + }); + }; + if (store.readyState === "complete") sendStoreState(); + else store.addEventListener("load", sendStoreState); + } + // EnableFullMode + else if (message.type === MessageType.EnableFullMode) { + try { + browser.runtime.sendMessage(message); + } catch (error) { + console.error( + "[TTV LOL PRO] Failed to send EnableFullMode message", + error + ); + } + } + // DisableFullMode + else if (message.type === MessageType.DisableFullMode) { + try { + browser.runtime.sendMessage(message); + } catch (error) { + console.error( + "[TTV LOL PRO] Failed to send DisableFullMode message", + error + ); + } + } + // ChannelSubscriptionStatus + else if (message.type === MessageType.ChannelSubscriptionStatus) { + const { channelName, isSubscribed, scope } = message; + const wasSubscribed = wasChannelSubscriber(channelName); + let isWhitelisted = isChannelWhitelisted(channelName); + console.log( + "[TTV LOL PRO] Received channel subscription status message. Current state:", + { + wasSubscribed, + isSubscribed, + isWhitelisted, } - break; - case MessageType.UsherResponse: - try { - browser.runtime.sendMessage(message); - } catch (error) { - console.error( - "[TTV LOL PRO] Failed to send UsherResponse message", - error - ); + ); + if (store.state.whitelistChannelSubscriptions && channelName != null) { + if (!wasSubscribed && isSubscribed) { + store.state.activeChannelSubscriptions.push(channelName); + // Add to whitelist. + if (!isWhitelisted) { + console.log(`[TTV LOL PRO] Adding '${channelName}' to whitelist.`); + store.state.whitelistedChannels.push(channelName); + isWhitelisted = true; + } + } else if (wasSubscribed && !isSubscribed) { + store.state.activeChannelSubscriptions = + store.state.activeChannelSubscriptions.filter( + c => c.toLowerCase() !== channelName.toLowerCase() + ); + // Remove from whitelist. + if (isWhitelisted) { + console.log( + `[TTV LOL PRO] Removing '${channelName}' from whitelist.` + ); + store.state.whitelistedChannels = + store.state.whitelistedChannels.filter( + c => c.toLowerCase() !== channelName.toLowerCase() + ); + isWhitelisted = false; + } } - break; - case MessageType.MultipleAdBlockersInUse: - const channelName = findChannelFromTwitchTvUrl(location.href); - if (!channelName) break; - const streamStatus = getStreamStatus(channelName); - setStreamStatus(channelName, { - ...(streamStatus ?? { proxied: false }), - reason: "Another Twitch ad blocker is in use", - }); - break; - case MessageType.ClearStats: - clearStats(message.channelName); - break; + } + console.log("[TTV LOL PRO] Sending channel subscription status response."); + window.postMessage({ + type: + scope === "page" // TODO: Is this necessary? Isn't the scope always "worker"? + ? MessageType.PageScriptMessage + : MessageType.WorkerScriptMessage, + message: { + type: MessageType.ChannelSubscriptionStatusResponse, + isWhitelisted: isWhitelisted, + }, + }); + } + // UsherResponse + else if (message.type === MessageType.UsherResponse) { + try { + browser.runtime.sendMessage(message); + } catch (error) { + console.error( + "[TTV LOL PRO] Failed to send UsherResponse message", + error + ); + } + } + // MultipleAdBlockersInUse + else if (message.type === MessageType.MultipleAdBlockersInUse) { + const channelName = findChannelFromTwitchTvUrl(location.href); + if (!channelName) return; + const streamStatus = getStreamStatus(channelName); + setStreamStatus(channelName, { + ...(streamStatus ?? { proxied: false }), + reason: "Another Twitch ad blocker is in use", + }); + } + // ClearStats + else if (message.type === MessageType.ClearStats) { + clearStats(message.channelName); } } diff --git a/src/options/options.ts b/src/options/options.ts index 078e0b4d..97c0c099 100644 --- a/src/options/options.ts +++ b/src/options/options.ts @@ -75,6 +75,9 @@ const passportLevelProxyUsageWwwElement = $( const whitelistedChannelsListElement = $( "#whitelisted-channels-list" ) as HTMLUListElement; +const whitelistSubscriptionsCheckboxElement = $( + "#whitelist-subscriptions-checkbox" +) as HTMLInputElement; // Proxies const optimizedProxiesInputElement = $("#optimized") as HTMLInputElement; const optimizedProxiesListElement = $( @@ -163,6 +166,12 @@ function main() { return [true]; }, }); + whitelistSubscriptionsCheckboxElement.checked = + store.state.whitelistChannelSubscriptions; + whitelistSubscriptionsCheckboxElement.addEventListener("change", () => { + store.state.whitelistChannelSubscriptions = + whitelistSubscriptionsCheckboxElement.checked; + }); // Proxies if (store.state.optimizedProxiesEnabled) optimizedProxiesInputElement.checked = true; @@ -548,6 +557,7 @@ exportButtonElement.addEventListener("click", () => { optimizedProxies: store.state.optimizedProxies, optimizedProxiesEnabled: store.state.optimizedProxiesEnabled, passportLevel: store.state.passportLevel, + whitelistChannelSubscriptions: store.state.whitelistChannelSubscriptions, whitelistedChannels: store.state.whitelistedChannels, }; saveFile( diff --git a/src/options/page.html b/src/options/page.html index ad6e9d2d..d11724ce 100644 --- a/src/options/page.html +++ b/src/options/page.html @@ -138,6 +138,23 @@

Whitelisted channels

Twitch tabs are whitelisted channels. + diff --git a/src/page/getFetch.ts b/src/page/getFetch.ts index 32b507c4..b65dca19 100644 --- a/src/page/getFetch.ts +++ b/src/page/getFetch.ts @@ -265,7 +265,42 @@ export function getFetch(pageState: PageState): typeof fetch { encodeURIComponent('"player_type":"frontpage"') ); const channelName = findChannelFromUsherUrl(url); - const isWhitelisted = isChannelWhitelisted(channelName, pageState); + let isWhitelisted = isChannelWhitelisted(channelName, pageState); + if ( + pageState.state?.whitelistChannelSubscriptions && + channelName != null + ) { + const wasSubscribed = wasChannelSubscriber(channelName, pageState); + const isSubscribed = url.includes( + encodeURIComponent('"subscriber":true') + ); + // const isSubscribed = url.includes( + // encodeURIComponent("aminematue") + // ); + const hasSubStatusChanged = + (wasSubscribed && !isSubscribed) || (!wasSubscribed && isSubscribed); + if (hasSubStatusChanged) { + console.log( + "[TTV LOL PRO] Channel subscription status changed. Sending messageā€¦" + ); + try { + const response = + await pageState.sendMessageToContentScriptAndWaitForResponse( + pageState.scope, + { + type: MessageType.ChannelSubscriptionStatus, + scope: pageState.scope, + channelName, + isSubscribed, + }, + MessageType.ChannelSubscriptionStatusResponse + ); + if (typeof response.isWhitelisted === "boolean") { + isWhitelisted = response.isWhitelisted; + } + } catch {} + } + } if (!isLivestream || isFrontpage || isWhitelisted) { console.log( "[TTV LOL PRO] Not flagging Usher request: not a livestream, is frontpage, or is whitelisted." @@ -623,6 +658,18 @@ function isChannelWhitelisted( return whitelistedChannelsLower.includes(channelName.toLowerCase()); } +function wasChannelSubscriber( + channelName: string | null | undefined, + pageState: PageState +): boolean { + if (!channelName) return false; + const activeChannelSubscriptionsLower = + pageState.state?.activeChannelSubscriptions.map(channel => + channel.toLowerCase() + ) ?? []; + return activeChannelSubscriptionsLower.includes(channelName.toLowerCase()); +} + async function flagRequest( request: Request, requestType: ProxyRequestType, diff --git a/src/store/getDefaultState.ts b/src/store/getDefaultState.ts index 4b37bfa9..ea99dc2f 100644 --- a/src/store/getDefaultState.ts +++ b/src/store/getDefaultState.ts @@ -3,6 +3,7 @@ import type { State } from "./types"; export default function getDefaultState() { const state: State = { + activeChannelSubscriptions: [], adLog: [], adLogEnabled: true, adLogLastSent: 0, @@ -18,6 +19,7 @@ export default function getDefaultState() { passportLevel: 0, streamStatuses: {}, videoWeaverUrlsByChannel: {}, + whitelistChannelSubscriptions: true, whitelistedChannels: [], }; return state; diff --git a/src/store/types.ts b/src/store/types.ts index b8e9f331..f244c2d2 100644 --- a/src/store/types.ts +++ b/src/store/types.ts @@ -6,6 +6,7 @@ export type ReadyState = "loading" | "complete"; export type StorageAreaName = "local" | "managed" | "sync"; export interface State { + activeChannelSubscriptions: string[]; adLog: AdLogEntry[]; adLogEnabled: boolean; adLogLastSent: number; @@ -19,6 +20,7 @@ export interface State { passportLevel: number; streamStatuses: Record; videoWeaverUrlsByChannel: Record; + whitelistChannelSubscriptions: boolean; whitelistedChannels: string[]; } diff --git a/src/types.ts b/src/types.ts index ca7d7a43..aea52675 100644 --- a/src/types.ts +++ b/src/types.ts @@ -79,6 +79,8 @@ export const enum MessageType { EnableFullMode = "TLP_EnableFullMode", EnableFullModeResponse = "TLP_EnableFullModeResponse", DisableFullMode = "TLP_DisableFullMode", + ChannelSubscriptionStatus = "TLP_ChannelSubscriptionStatus", + ChannelSubscriptionStatusResponse = "TLP_ChannelSubscriptionStatusResponse", UsherResponse = "TLP_UsherResponse", NewPlaybackAccessToken = "TLP_NewPlaybackAccessToken", NewPlaybackAccessTokenResponse = "TLP_NewPlaybackAccessTokenResponse",