Skip to content

Commit

Permalink
🔖 Release version 2.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
younesaassila authored Oct 5, 2023
2 parents 94bceff + 111f0cb commit 65773c4
Show file tree
Hide file tree
Showing 32 changed files with 808 additions and 377 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
- name: Run linter
run: npm run lint
- name: Run type checker
run: npx tsc --noEmit
run: npm run type-check
# - name: Run tests
# run: npm run test
- name: Build for Firefox
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ TTV LOL PRO removes _most_ livestream ads from Twitch. This is free, don't expec
**TTV LOL PRO:**

- removes _most_ livestream ads from Twitch,
- lets you whitelist channels (Firefox only),
- lets you whitelist channels,
- improves TTV LOL's popup by showing stream status,
- lets you add custom primary/fallback proxies.

Expand Down
604 changes: 379 additions & 225 deletions package-lock.json

Large diffs are not rendered by default.

13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ttv-lol-pro",
"version": "2.1.6",
"version": "2.2.0",
"description": "TTV LOL PRO removes most livestream ads from Twitch.",
"@parcel/bundler-default": {
"minBundles": 10000000,
Expand All @@ -24,6 +24,7 @@
"dev:chromium": "parcel src/manifest.json --host localhost --target webext-dev --no-hmr",
"lint": "prettier --check .",
"lint:fix": "prettier --write .",
"type-check": "tsc --noEmit",
"prebuild:firefox": "npm run clean && shx cp src/manifest.firefox.json src/manifest.json",
"prebuild:chromium": "npm run clean && shx cp src/manifest.chromium.json src/manifest.json",
"build:firefox": "parcel build src/manifest.json --target webext-prod --no-source-maps",
Expand All @@ -46,18 +47,18 @@
},
"devDependencies": {
"@parcel/config-webextension": "^2.9.3",
"@types/chrome": "^0.0.243",
"@types/ip": "^1.1.0",
"@types/webextension-polyfill": "^0.10.1",
"@types/chrome": "^0.0.246",
"@types/ip": "^1.1.1",
"@types/webextension-polyfill": "^0.10.4",
"buffer": "^6.0.3",
"os-browserify": "^0.3.0",
"parcel": "^2.9.3",
"postcss": "^8.4.27",
"postcss": "^8.4.31",
"prettier": "2.8.8",
"prettier-plugin-css-order": "^1.3.1",
"prettier-plugin-organize-imports": "^3.2.3",
"shx": "^0.3.4",
"typescript": "^5.1.6",
"typescript": "^5.2.2",
"webextension-polyfill": "^0.10.0"
},
"private": true
Expand Down
4 changes: 4 additions & 0 deletions src/background/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import onAuthRequired from "./handlers/onAuthRequired";
import onBeforeSendHeaders from "./handlers/onBeforeSendHeaders";
import onBeforeVideoWeaverRequest from "./handlers/onBeforeVideoWeaverRequest";
import onProxyRequest from "./handlers/onProxyRequest";
import onProxySettingsChange from "./handlers/onProxySettingsChanged";
import onResponseStarted from "./handlers/onResponseStarted";
import onStartupStoreCleanup from "./handlers/onStartupStoreCleanup";
import onTabCreated from "./handlers/onTabCreated";
Expand All @@ -29,6 +30,9 @@ browser.webRequest.onResponseStarted.addListener(onResponseStarted, {
});

if (isChromium) {
// Listen to whether proxy is set or not.
browser.proxy.settings.onChange.addListener(onProxySettingsChange);

// Check if there are any opened Twitch tabs on startup.
checkForOpenedTwitchTabs();

Expand Down
20 changes: 10 additions & 10 deletions src/background/handlers/checkForOpenedTwitchTabs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import browser from "webextension-polyfill";
import areAllTabsWhitelisted from "../../common/ts/areAllTabsWhitelisted";
import isChromium from "../../common/ts/isChromium";
import {
clearProxySettings,
Expand All @@ -13,17 +14,16 @@ export default function checkForOpenedTwitchTabs() {
browser.tabs
.query({ url: ["https://www.twitch.tv/*", "https://m.twitch.tv/*"] })
.then(tabs => {
const tabsIds = tabs.filter(tab => tab.id != null).map(tab => tab.id!);
if (tabsIds.length === 0) {
if (isChromium) clearProxySettings();
return;
}
console.log(
`🔍 Found ${tabsIds.length} opened Twitch tabs: ${tabsIds.join(", ")}`
);
console.log(`🔍 Found ${tabs.length} opened Twitch tabs.`);
store.state.openedTwitchTabs = tabs;

if (isChromium) {
updateProxySettings();
const allTabsAreWhitelisted = areAllTabsWhitelisted(tabs);
if (tabs.length > 0 && !allTabsAreWhitelisted) {
updateProxySettings();
} else {
clearProxySettings();
}
}
store.state.openedTwitchTabs = tabsIds;
});
}
79 changes: 38 additions & 41 deletions src/background/handlers/onBeforeVideoWeaverRequest.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { WebRequest } from "webextension-polyfill";
import filterResponseDataWrapper from "../../common/ts/filterResponseDataWrapper";
import findChannelFromTwitchTvUrl from "../../common/ts/findChannelFromTwitchTvUrl";
import findChannelFromVideoWeaverUrl from "../../common/ts/findChannelFromVideoWeaverUrl";
import getHostFromUrl from "../../common/ts/getHostFromUrl";
import { videoWeaverHostRegex } from "../../common/ts/regexes";
Expand All @@ -23,50 +24,46 @@ export default function onBeforeVideoWeaverRequest(
const textLower = text.toLowerCase();
const isAd = textLower.includes(adSignifier);
const isMidroll = textLower.includes(midrollSignifier);
if (!isAd && !isMidroll) return text;

if (isAd || isMidroll) {
const adType: AdType = isMidroll ? AdType.MIDROLL : AdType.PREROLL;
const channel = findChannelFromVideoWeaverUrl(details.url);
const isPurpleScreen = textLower.includes(
"https://help.twitch.tv/s/article/ad-experience-on-twitch"
);
let proxy: string | null = null;
if (details.proxyInfo && details.proxyInfo.type !== "direct") {
proxy = `${details.proxyInfo.host}:${details.proxyInfo.port}`;
}
const proxyTwitchWebpage = store.state.proxyTwitchWebpage;
const proxyUsherRequests = store.state.proxyUsherRequests;
const timestamp = details.timeStamp;
const videoWeaverHost = host;
const videoWeaverUrl = details.url;
const isDuplicate = store.state.adLog.some(
entry =>
entry.videoWeaverUrl === details.url &&
details.timeStamp - entry.timestamp < 1000 * 30 // 30 seconds
);
if (isDuplicate) return text;

const isDuplicate = store.state.adLog.some(
entry =>
entry.videoWeaverUrl === videoWeaverUrl &&
timestamp - entry.timestamp < 1000 * 30 // 30 seconds
);
if (isDuplicate) return text;
const channelName =
findChannelFromVideoWeaverUrl(details.url) ??
findChannelFromTwitchTvUrl(details.documentUrl);
const isPurpleScreen = textLower.includes(
"https://help.twitch.tv/s/article/ad-experience-on-twitch"
);
const proxy =
details.proxyInfo && details.proxyInfo.type !== "direct"
? `${details.proxyInfo.host}:${details.proxyInfo.port}`
: null;

const adLog = store.state.adLog.filter(
entry => timestamp - entry.timestamp < 1000 * 60 * 60 * 24 * 7 // 7 days
);
store.state.adLog = [
...adLog,
{
adType,
channel,
isPurpleScreen,
proxy,
proxyTwitchWebpage,
proxyUsherRequests,
timestamp,
videoWeaverHost,
videoWeaverUrl,
},
];
console.log(`📝 Ad log updated (${adLog.length + 1} entries).`);
console.log(text);
}
const adLog = store.state.adLog.filter(
entry => details.timeStamp - entry.timestamp < 1000 * 60 * 60 * 24 * 7 // 7 days
);
store.state.adLog = [
...adLog,
{
adType: isMidroll ? AdType.MIDROLL : AdType.PREROLL,
channel: channelName,
isPurpleScreen,
proxy,
proxyTwitchWebpage: store.state.proxyTwitchWebpage,
proxyUsherRequests: store.state.proxyUsherRequests,
anonymousMode: store.state.anonymousMode,
timestamp: details.timeStamp,
videoWeaverHost: host,
videoWeaverUrl: details.url,
},
];
console.log(`📝 Ad log updated (${store.state.adLog.length} entries).`);
console.log(text);

return text;
});
Expand Down
25 changes: 24 additions & 1 deletion src/background/handlers/onProxyRequest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Proxy } from "webextension-polyfill";
import findChannelFromTwitchTvUrl from "../../common/ts/findChannelFromTwitchTvUrl";
import findChannelFromUsherUrl from "../../common/ts/findChannelFromUsherUrl";
import findChannelFromVideoWeaverUrl from "../../common/ts/findChannelFromVideoWeaverUrl";
import getHostFromUrl from "../../common/ts/getHostFromUrl";
Expand All @@ -21,6 +22,12 @@ export default async function onProxyRequest(
const host = getHostFromUrl(details.url);
if (!host) return { type: "direct" };

const documentHost = details.documentUrl
? getHostFromUrl(details.documentUrl)
: null;
const isFromTwitchTvHost =
documentHost && twitchTvHostRegex.test(documentHost);

// Wait for the store to be ready.
if (store.readyState !== "complete") {
await new Promise(resolve => {
Expand Down Expand Up @@ -73,6 +80,13 @@ export default async function onProxyRequest(

// Usher requests.
if (store.state.proxyUsherRequests && usherHostRegex.test(host)) {
// Don't proxy Usher requests from non-supported hosts.
if (!isFromTwitchTvHost) {
console.log(
`✋ '${details.url}' from host '${documentHost}' is not supported.`
);
return { type: "direct" };
}
// Don't proxy whitelisted channels.
const channelName = findChannelFromUsherUrl(details.url);
if (isChannelWhitelisted(channelName)) {
Expand All @@ -89,8 +103,17 @@ export default async function onProxyRequest(

// Video Weaver requests.
if (videoWeaverHostRegex.test(host) && isFlagged) {
// Don't proxy Video Weaver requests from non-supported hosts.
if (!isFromTwitchTvHost) {
console.log(
`✋ '${details.url}' from host '${documentHost}' is not supported.`
);
return { type: "direct" };
}
// Don't proxy whitelisted channels.
const channelName = findChannelFromVideoWeaverUrl(details.url);
const channelName =
findChannelFromVideoWeaverUrl(details.url) ??
findChannelFromTwitchTvUrl(details.documentUrl);
if (isChannelWhitelisted(channelName)) {
console.log(`✋ Channel '${channelName}' is whitelisted.`);
return { type: "direct" };
Expand Down
10 changes: 10 additions & 0 deletions src/background/handlers/onProxySettingsChanged.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Types } from "webextension-polyfill";
import store from "../../store";

export default function onProxySettingsChange(
details: Types.SettingOnChangeDetailsType
) {
console.log(`⚙️ Proxy settings changed: ${details.levelOfControl}`);
store.state.chromiumProxyActive =
details.levelOfControl == "controlled_by_this_extension";
}
5 changes: 4 additions & 1 deletion src/background/handlers/onResponseStarted.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { WebRequest } from "webextension-polyfill";
import findChannelFromTwitchTvUrl from "../../common/ts/findChannelFromTwitchTvUrl";
import findChannelFromVideoWeaverUrl from "../../common/ts/findChannelFromVideoWeaverUrl";
import getHostFromUrl from "../../common/ts/getHostFromUrl";
import getProxyInfoFromUrl from "../../common/ts/getProxyInfoFromUrl";
Expand Down Expand Up @@ -48,7 +49,9 @@ export default function onResponseStarted(

// Video-weaver requests.
if (videoWeaverHostRegex.test(host)) {
const channelName = findChannelFromVideoWeaverUrl(details.url);
const channelName =
findChannelFromVideoWeaverUrl(details.url) ??
findChannelFromTwitchTvUrl(details.documentUrl);
const streamStatus = getStreamStatus(channelName);
const stats = streamStatus?.stats ?? { proxied: 0, notProxied: 0 };
if (!proxy) {
Expand Down
1 change: 1 addition & 0 deletions src/background/handlers/onStartupStoreCleanup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default function onStartupStoreCleanup(): void {
if (store.readyState !== "complete")
return store.addEventListener("load", onStartupStoreCleanup);

store.state.chromiumProxyActive = false;
store.state.dnsResponses = [];
store.state.openedTwitchTabs = [];
store.state.streamStatuses = {};
Expand Down
27 changes: 21 additions & 6 deletions src/background/handlers/onTabCreated.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
import { Tabs } from "webextension-polyfill";
import findChannelFromTwitchTvUrl from "../../common/ts/findChannelFromTwitchTvUrl";
import getHostFromUrl from "../../common/ts/getHostFromUrl";
import isChannelWhitelisted from "../../common/ts/isChannelWhitelisted";
import isChromium from "../../common/ts/isChromium";
import { updateProxySettings } from "../../common/ts/proxySettings";
import { twitchTvHostRegex } from "../../common/ts/regexes";
import store from "../../store";

export default function onTabCreated(tab: Tabs.Tab): void {
if (!tab.url || tab.id == null) return;
const host = getHostFromUrl(tab.url);
if (host != null && twitchTvHostRegex.test(host)) {
const url = tab.url || tab.pendingUrl;
if (!url) return;
const host = getHostFromUrl(url);
if (!host) return;

// TODO: `twitchTvHostRegex` doesn't match `appeals.twitch.tv` and
// `dashboard.twitch.tv` which means that passport requests from those
// subdomains will not be proxied. This could mess up the cookie country.
if (twitchTvHostRegex.test(host)) {
console.log(`➕ Opened Twitch tab: ${tab.id}`);
if (isChromium && store.state.openedTwitchTabs.length === 0) {
updateProxySettings();
store.state.openedTwitchTabs.push(tab);

if (isChromium) {
const channelName = findChannelFromTwitchTvUrl(url);
const isWhitelisted = channelName
? isChannelWhitelisted(channelName)
: false;
if (!isWhitelisted && !store.state.chromiumProxyActive) {
updateProxySettings();
}
}
store.state.openedTwitchTabs.push(tab.id);
}
}
20 changes: 15 additions & 5 deletions src/background/handlers/onTabRemoved.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
import areAllTabsWhitelisted from "../../common/ts/areAllTabsWhitelisted";
import isChromium from "../../common/ts/isChromium";
import { clearProxySettings } from "../../common/ts/proxySettings";
import store from "../../store";

export default function onTabRemoved(tabId: number): void {
const index = store.state.openedTwitchTabs.indexOf(tabId);
if (index !== -1) {
console.log(`➖ Closed Twitch tab: ${tabId}`);
store.state.openedTwitchTabs.splice(index, 1);
if (isChromium && store.state.openedTwitchTabs.length === 0) {
const index = store.state.openedTwitchTabs.findIndex(tab => tab.id === tabId);
if (index === -1) return;

console.log(`➖ Closed Twitch tab: ${tabId}`);
store.state.openedTwitchTabs.splice(index, 1);

if (isChromium) {
const allTabsAreWhitelisted = areAllTabsWhitelisted(
store.state.openedTwitchTabs
);
if (
(store.state.openedTwitchTabs.length === 0 || allTabsAreWhitelisted) &&
store.state.chromiumProxyActive
) {
clearProxySettings();
}
}
Expand Down
Loading

0 comments on commit 65773c4

Please sign in to comment.