mirror of
https://github.com/younesaassila/ttv-lol-pro.git
synced 2025-04-29 22:14:27 +02:00
Merge pull request #340 from younesaassila/v2.3.8
Release version 2.3.8
This commit is contained in:
commit
4cf96c311c
11
.github/workflows/build.yml
vendored
11
.github/workflows/build.yml
vendored
@ -1,8 +1,17 @@
|
|||||||
name: Build and Test
|
name: Build and Test
|
||||||
on: [push, pull_request]
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
|
tags-ignore:
|
||||||
|
- "**"
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
concurrency: ci-${{ github.ref }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://discord.gg/AmtFTPwsyH">
|
<a href="https://discord.ttvlolpro.com/">
|
||||||
<img
|
<img
|
||||||
alt="Discord server"
|
alt="Discord server"
|
||||||
src="https://dcbadge.vercel.app/api/server/AmtFTPwsyH"
|
src="https://dcbadge.vercel.app/api/server/AmtFTPwsyH"
|
||||||
@ -78,7 +78,7 @@ TTV LOL PRO is a fork of TTV LOL that:
|
|||||||
|
|
||||||
TTV LOL PRO does not remove banner ads, nor does it remove ads from VODs. For the best experience, we recommend using [uBlock Origin](https://ublockorigin.com/) alongside TTV LOL PRO.
|
TTV LOL PRO does not remove banner ads, nor does it remove ads from VODs. For the best experience, we recommend using [uBlock Origin](https://ublockorigin.com/) alongside TTV LOL PRO.
|
||||||
|
|
||||||
Any questions? Please read the [wiki](https://wiki.cdn-perfprod.com/) first.
|
Any questions? Please read the [wiki](https://wiki.ttvlolpro.com/) first.
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
|
1665
package-lock.json
generated
1665
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ttv-lol-pro",
|
"name": "ttv-lol-pro",
|
||||||
"version": "2.3.7",
|
"version": "2.3.8",
|
||||||
"description": "TTV LOL PRO removes most livestream ads from Twitch.",
|
"description": "TTV LOL PRO removes most livestream ads from Twitch.",
|
||||||
"@parcel/bundler-default": {
|
"@parcel/bundler-default": {
|
||||||
"minBundles": 10000000,
|
"minBundles": 10000000,
|
||||||
@ -43,24 +43,25 @@
|
|||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bowser": "^2.11.0",
|
"bowser": "^2.11.0",
|
||||||
"ip": "^2.0.1",
|
"ip-address": "^9.0.5",
|
||||||
"m3u8-parser": "^7.1.0"
|
"m3u8-parser": "^7.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@parcel/config-webextension": "^2.12.0",
|
"@parcel/config-webextension": "^2.12.0",
|
||||||
"@types/chrome": "^0.0.267",
|
"@types/chrome": "^0.0.270",
|
||||||
"@types/ip": "^1.1.3",
|
"@types/jsbn": "^1.2.33",
|
||||||
"@types/webextension-polyfill": "^0.10.7",
|
"@types/node": "^20.16.1",
|
||||||
|
"@types/webextension-polyfill": "^0.12.0",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"os-browserify": "^0.3.0",
|
"os-browserify": "^0.3.0",
|
||||||
"parcel": "^2.12.0",
|
"parcel": "^2.12.0",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.41",
|
||||||
"prettier": "2.8.8",
|
"prettier": "2.8.8",
|
||||||
"prettier-plugin-css-order": "^1.3.1",
|
"prettier-plugin-css-order": "^1.3.1",
|
||||||
"prettier-plugin-organize-imports": "^3.2.4",
|
"prettier-plugin-organize-imports": "^3.2.4",
|
||||||
"shx": "^0.3.4",
|
"shx": "^0.3.4",
|
||||||
"typescript": "^5.4.5",
|
"typescript": "^5.5.4",
|
||||||
"webextension-polyfill": "^0.11.0"
|
"webextension-polyfill": "^0.12.0"
|
||||||
},
|
},
|
||||||
"private": true
|
"private": true
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ const pendingRequests: string[] = [];
|
|||||||
|
|
||||||
export default function onAuthRequired(
|
export default function onAuthRequired(
|
||||||
details: WebRequest.OnAuthRequiredDetailsType
|
details: WebRequest.OnAuthRequiredDetailsType
|
||||||
): void | WebRequest.BlockingResponseOrPromise {
|
): WebRequest.BlockingResponseOrPromise | undefined {
|
||||||
if (!details.isProxy) return;
|
if (!details.isProxy) return;
|
||||||
|
|
||||||
if (pendingRequests.includes(details.requestId)) {
|
if (pendingRequests.includes(details.requestId)) {
|
||||||
|
@ -8,7 +8,7 @@ import { twitchTvHostRegex } from "../../common/ts/regexes";
|
|||||||
|
|
||||||
export default function onBeforeTwitchTvSendHeaders(
|
export default function onBeforeTwitchTvSendHeaders(
|
||||||
details: WebRequest.OnBeforeSendHeadersDetailsType
|
details: WebRequest.OnBeforeSendHeadersDetailsType
|
||||||
): void | WebRequest.BlockingResponseOrPromise {
|
): WebRequest.BlockingResponseOrPromise | undefined {
|
||||||
const host = getHostFromUrl(details.url);
|
const host = getHostFromUrl(details.url);
|
||||||
if (!host || !twitchTvHostRegex.test(host)) return;
|
if (!host || !twitchTvHostRegex.test(host)) return;
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ export default function onBeforeVideoWeaverRequest(
|
|||||||
details: WebRequest.OnBeforeRequestDetailsType & {
|
details: WebRequest.OnBeforeRequestDetailsType & {
|
||||||
proxyInfo?: ProxyInfo;
|
proxyInfo?: ProxyInfo;
|
||||||
}
|
}
|
||||||
): void | WebRequest.BlockingResponseOrPromise {
|
): WebRequest.BlockingResponseOrPromise | undefined {
|
||||||
// Filter to video-weaver responses.
|
// Filter to video-weaver responses.
|
||||||
const host = getHostFromUrl(details.url);
|
const host = getHostFromUrl(details.url);
|
||||||
if (!host || !videoWeaverHostRegex.test(host)) return;
|
if (!host || !videoWeaverHostRegex.test(host)) return;
|
||||||
@ -34,9 +34,7 @@ export default function onBeforeVideoWeaverRequest(
|
|||||||
if (isDuplicate) return text;
|
if (isDuplicate) return text;
|
||||||
|
|
||||||
const channelName = findChannelFromVideoWeaverUrl(details.url);
|
const channelName = findChannelFromVideoWeaverUrl(details.url);
|
||||||
const isPurpleScreen = textLower.includes(
|
const isPurpleScreen = textLower.includes("https://help.twitch.tv/");
|
||||||
"https://help.twitch.tv/s/article/ad-experience-on-twitch"
|
|
||||||
);
|
|
||||||
const proxy =
|
const proxy =
|
||||||
details.proxyInfo && details.proxyInfo.type !== "direct"
|
details.proxyInfo && details.proxyInfo.type !== "direct"
|
||||||
? getUrlFromProxyInfo(details.proxyInfo)
|
? getUrlFromProxyInfo(details.proxyInfo)
|
||||||
|
@ -14,8 +14,8 @@ const fetchTimeoutMsOverride: Map<ProxyRequestType, number> = new Map([
|
|||||||
export default function onContentScriptMessage(
|
export default function onContentScriptMessage(
|
||||||
message: any,
|
message: any,
|
||||||
sender: Runtime.MessageSender,
|
sender: Runtime.MessageSender,
|
||||||
sendResponse: () => void
|
sendResponse: (message: any) => void
|
||||||
): true | void | Promise<any> {
|
): Promise<any> | true | undefined {
|
||||||
if (message.type === MessageType.EnableFullMode) {
|
if (message.type === MessageType.EnableFullMode) {
|
||||||
if (!sender.tab?.id) return;
|
if (!sender.tab?.id) return;
|
||||||
|
|
||||||
|
@ -27,7 +27,13 @@ export default async function onResponseStarted(
|
|||||||
const host = getHostFromUrl(details.url);
|
const host = getHostFromUrl(details.url);
|
||||||
if (!host) return;
|
if (!host) return;
|
||||||
|
|
||||||
const proxy = getProxyFromDetails(details);
|
let proxy: string | null = null;
|
||||||
|
let errorMessage: string | null = null;
|
||||||
|
try {
|
||||||
|
proxy = getProxyFromDetails(details);
|
||||||
|
} catch (error) {
|
||||||
|
errorMessage = error instanceof Error ? error.message : `${error}`;
|
||||||
|
}
|
||||||
|
|
||||||
const requestParams = {
|
const requestParams = {
|
||||||
isChromium: isChromium,
|
isChromium: isChromium,
|
||||||
@ -79,13 +85,26 @@ export default async function onResponseStarted(
|
|||||||
findChannelFromTwitchTvUrl(tabUrl);
|
findChannelFromTwitchTvUrl(tabUrl);
|
||||||
const streamStatus = getStreamStatus(channelName);
|
const streamStatus = getStreamStatus(channelName);
|
||||||
const stats = streamStatus?.stats ?? { proxied: 0, notProxied: 0 };
|
const stats = streamStatus?.stats ?? { proxied: 0, notProxied: 0 };
|
||||||
|
|
||||||
if (!proxy) {
|
if (!proxy) {
|
||||||
stats.notProxied++;
|
stats.notProxied++;
|
||||||
|
let reason = errorMessage ?? streamStatus?.reason ?? "";
|
||||||
|
try {
|
||||||
|
const proxySettings = await browser.proxy.settings.get({});
|
||||||
|
switch (proxySettings.levelOfControl) {
|
||||||
|
case "controlled_by_other_extensions":
|
||||||
|
reason = "Proxy settings controlled by other extension";
|
||||||
|
break;
|
||||||
|
case "not_controllable":
|
||||||
|
reason = "Proxy settings not controllable";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
setStreamStatus(channelName, {
|
setStreamStatus(channelName, {
|
||||||
proxied: false,
|
proxied: false,
|
||||||
proxyHost: streamStatus?.proxyHost ? streamStatus.proxyHost : undefined,
|
proxyHost: streamStatus?.proxyHost ? streamStatus.proxyHost : undefined,
|
||||||
proxyCountry: streamStatus?.proxyCountry,
|
proxyCountry: streamStatus?.proxyCountry,
|
||||||
reason: streamStatus?.reason ?? "",
|
reason,
|
||||||
stats,
|
stats,
|
||||||
});
|
});
|
||||||
console.log(
|
console.log(
|
||||||
@ -93,6 +112,7 @@ export default async function onResponseStarted(
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
stats.proxied++;
|
stats.proxied++;
|
||||||
setStreamStatus(channelName, {
|
setStreamStatus(channelName, {
|
||||||
proxied: true,
|
proxied: true,
|
||||||
@ -126,24 +146,29 @@ function getProxyFromDetails(
|
|||||||
}
|
}
|
||||||
): string | null {
|
): string | null {
|
||||||
if (isChromium) {
|
if (isChromium) {
|
||||||
|
const proxies = [
|
||||||
|
...store.state.optimizedProxies,
|
||||||
|
...store.state.normalProxies,
|
||||||
|
];
|
||||||
|
const isDnsError =
|
||||||
|
proxies.length !== 0 && store.state.dnsResponses.length === 0;
|
||||||
|
if (isDnsError) {
|
||||||
|
throw new Error(
|
||||||
|
"Cannot detect if requests are being proxied due to a DNS error"
|
||||||
|
);
|
||||||
|
}
|
||||||
const ip = details.ip;
|
const ip = details.ip;
|
||||||
if (!ip) return null;
|
if (!ip) return null;
|
||||||
const dnsResponse = store.state.dnsResponses.find(
|
const dnsResponse = store.state.dnsResponses.find(
|
||||||
dnsResponse => dnsResponse.ips.indexOf(ip) !== -1
|
dnsResponse => dnsResponse.ips.indexOf(ip) !== -1
|
||||||
);
|
);
|
||||||
if (!dnsResponse) return null;
|
if (!dnsResponse) return null;
|
||||||
const proxies = [
|
|
||||||
...store.state.optimizedProxies,
|
|
||||||
...store.state.normalProxies,
|
|
||||||
];
|
|
||||||
const proxyInfoArray = proxies.map(getProxyInfoFromUrl);
|
const proxyInfoArray = proxies.map(getProxyInfoFromUrl);
|
||||||
const possibleProxies = proxyInfoArray.filter(
|
const possibleProxies = proxyInfoArray.filter(
|
||||||
proxy => proxy.host === dnsResponse.host
|
proxy => proxy.host === dnsResponse.host
|
||||||
);
|
);
|
||||||
if (possibleProxies.length === 1)
|
if (possibleProxies.length === 0) return dnsResponse.host;
|
||||||
return getUrlFromProxyInfo(possibleProxies[0]);
|
return getUrlFromProxyInfo(possibleProxies[0]);
|
||||||
// TODO: Set reason to some error message about DNS.
|
|
||||||
return dnsResponse.host;
|
|
||||||
} else {
|
} else {
|
||||||
const proxyInfo = details.proxyInfo; // Firefox only.
|
const proxyInfo = details.proxyInfo; // Firefox only.
|
||||||
if (!proxyInfo || proxyInfo.type === "direct") return null;
|
if (!proxyInfo || proxyInfo.type === "direct") return null;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import ip from "ip";
|
import { Address4, Address6 } from "ip-address";
|
||||||
|
import isPrivateIp from "./isPrivateIp";
|
||||||
import { getProxyInfoFromUrl } from "./proxyInfo";
|
import { getProxyInfoFromUrl } from "./proxyInfo";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -12,16 +13,26 @@ export function anonymizeIpAddress(url: string): string {
|
|||||||
|
|
||||||
let proxyHost = proxyInfo.host;
|
let proxyHost = proxyInfo.host;
|
||||||
|
|
||||||
const isIPv4 = ip.isV4Format(proxyHost);
|
const isIPv4 = Address4.isValid(proxyHost);
|
||||||
const isIPv6 = ip.isV6Format(proxyHost);
|
const isIPv6 = Address6.isValid(proxyHost);
|
||||||
const isIP = isIPv4 || isIPv6;
|
const isIP = isIPv4 || isIPv6;
|
||||||
const isPublicIP = isIP && !ip.isPrivate(proxyHost);
|
const isPublicIP = isIP && !isPrivateIp(proxyHost);
|
||||||
|
|
||||||
if (isPublicIP) {
|
if (isPublicIP) {
|
||||||
if (isIPv4) {
|
if (isIPv4) {
|
||||||
proxyHost = ip.mask(proxyHost, "255.255.0.0").replace(/\.0\.0$/, ".*.*");
|
proxyHost = new Address4(proxyHost)
|
||||||
|
.correctForm()
|
||||||
|
.split(".")
|
||||||
|
.map((byte, index) => (index < 2 ? byte : "xxx"))
|
||||||
|
.join(".");
|
||||||
} else if (isIPv6) {
|
} else if (isIPv6) {
|
||||||
proxyHost = ip.mask(proxyHost, "ffff:ffff:ffff:ffff:0000:0000:0000:0000");
|
const bytes = new Address6(proxyHost).toByteArray();
|
||||||
|
const anonymizedBytes = bytes.map((byte, index) =>
|
||||||
|
index < 6 ? byte : 0x0
|
||||||
|
);
|
||||||
|
proxyHost = Address6.fromByteArray(anonymizedBytes)
|
||||||
|
.correctForm()
|
||||||
|
.replace(/::$/, "::xxxx");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
27
src/common/ts/isPrivateIp.ts
Normal file
27
src/common/ts/isPrivateIp.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { Address4, Address6 } from "ip-address";
|
||||||
|
|
||||||
|
const ip4LinkLocalSubnet = new Address4("169.254.0.0/16");
|
||||||
|
const ip4LoopbackSubnet = new Address4("127.0.0.0/8");
|
||||||
|
const ip4PrivateASubnet = new Address4("10.0.0.0/8");
|
||||||
|
const ip4PrivateBSubnet = new Address4("172.16.0.0/12");
|
||||||
|
const ip4PrivateCSubnet = new Address4("192.168.0.0/16");
|
||||||
|
|
||||||
|
export default function isPrivateIp(address: string): boolean {
|
||||||
|
try {
|
||||||
|
const ip4 = new Address4(address);
|
||||||
|
return (
|
||||||
|
ip4.isInSubnet(ip4LinkLocalSubnet) ||
|
||||||
|
ip4.isInSubnet(ip4LoopbackSubnet) ||
|
||||||
|
ip4.isInSubnet(ip4PrivateASubnet) ||
|
||||||
|
ip4.isInSubnet(ip4PrivateBSubnet) ||
|
||||||
|
ip4.isInSubnet(ip4PrivateCSubnet)
|
||||||
|
);
|
||||||
|
} catch (error) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const ip6 = new Address6(address);
|
||||||
|
return ip6.isLinkLocal() || ip6.isLoopback();
|
||||||
|
} catch (error) {}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import ip from "ip";
|
import { Address6 } from "ip-address";
|
||||||
import type { ProxyInfo } from "../../types";
|
import type { ProxyInfo } from "../../types";
|
||||||
|
|
||||||
export function getProxyInfoFromUrl(
|
export function getProxyInfoFromUrl(
|
||||||
@ -71,14 +71,14 @@ export function getUrlFromProxyInfo(proxyInfo: ProxyInfo): string {
|
|||||||
} else if (username) {
|
} else if (username) {
|
||||||
url = `${username}@`;
|
url = `${username}@`;
|
||||||
}
|
}
|
||||||
const isIPv4 = ip.isV4Format(host);
|
const isIPv6 = Address6.isValid(host);
|
||||||
const isIPv6 = ip.isV6Format(host);
|
if (isIPv6) {
|
||||||
// isV6Format() returns true for IPv4 addresses, so we need to exclude those.
|
|
||||||
if (isIPv6 && !isIPv4) {
|
|
||||||
url += `[${host}]`;
|
url += `[${host}]`;
|
||||||
} else {
|
} else {
|
||||||
url += host;
|
url += host;
|
||||||
}
|
}
|
||||||
if (port) url += `:${port}`;
|
if (port) {
|
||||||
|
url += `:${port}`;
|
||||||
|
}
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import ip from "ip";
|
import { Address4, Address6 } from "ip-address";
|
||||||
import store from "../../store";
|
import store from "../../store";
|
||||||
import type { DnsResponse, DnsResponseJson } from "../../types";
|
import type { DnsResponse, DnsResponseJson } from "../../types";
|
||||||
import { getProxyInfoFromUrl } from "./proxyInfo";
|
import { getProxyInfoFromUrl } from "./proxyInfo";
|
||||||
|
|
||||||
export default async function updateDnsResponses() {
|
export default async function updateDnsResponses() {
|
||||||
const proxies = store.state.optimizedProxiesEnabled
|
const proxies = [
|
||||||
? store.state.optimizedProxies
|
...store.state.optimizedProxies,
|
||||||
: store.state.normalProxies;
|
...store.state.normalProxies,
|
||||||
|
];
|
||||||
const proxyInfoArray = proxies.map(getProxyInfoFromUrl);
|
const proxyInfoArray = proxies.map(getProxyInfoFromUrl);
|
||||||
|
|
||||||
for (const proxyInfo of proxyInfoArray) {
|
for (const proxyInfo of proxyInfoArray) {
|
||||||
@ -25,18 +26,19 @@ export default async function updateDnsResponses() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the host is an IP address, we don't need to make a DNS request.
|
// If the host is an IP address, we don't need to make a DNS request.
|
||||||
const isIp = ip.isV4Format(host) || ip.isV6Format(host);
|
const isIp = Address4.isValid(host) || Address6.isValid(host);
|
||||||
if (isIp) {
|
if (isIp) {
|
||||||
if (dnsResponseIndex !== -1) {
|
|
||||||
store.state.dnsResponses.splice(dnsResponseIndex, 1);
|
|
||||||
}
|
|
||||||
const dnsResponse: DnsResponse = {
|
const dnsResponse: DnsResponse = {
|
||||||
host,
|
host,
|
||||||
ips: [host],
|
ips: [host],
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
ttl: Infinity,
|
ttl: Infinity,
|
||||||
};
|
};
|
||||||
store.state.dnsResponses.push(dnsResponse);
|
if (dnsResponseIndex !== -1) {
|
||||||
|
store.state.dnsResponses.splice(dnsResponseIndex, 1, dnsResponse);
|
||||||
|
} else {
|
||||||
|
store.state.dnsResponses.push(dnsResponse);
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,16 +61,17 @@ export default async function updateDnsResponses() {
|
|||||||
}
|
}
|
||||||
const { Answer } = data;
|
const { Answer } = data;
|
||||||
|
|
||||||
if (dnsResponseIndex !== -1) {
|
|
||||||
store.state.dnsResponses.splice(dnsResponseIndex, 1);
|
|
||||||
}
|
|
||||||
const dnsResponse: DnsResponse = {
|
const dnsResponse: DnsResponse = {
|
||||||
host,
|
host,
|
||||||
ips: Answer.map(answer => answer.data),
|
ips: Answer.map(answer => answer.data),
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
ttl: Math.max(Math.max(...Answer.map(answer => answer.TTL)), 300),
|
ttl: Math.max(Math.max(...Answer.map(answer => answer.TTL)), 300),
|
||||||
};
|
};
|
||||||
store.state.dnsResponses.push(dnsResponse);
|
if (dnsResponseIndex !== -1) {
|
||||||
|
store.state.dnsResponses.splice(dnsResponseIndex, 1, dnsResponse);
|
||||||
|
} else {
|
||||||
|
store.state.dnsResponses.push(dnsResponse);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import workerScriptURL from "url:../page/worker.ts";
|
|||||||
import browser, { Storage } from "webextension-polyfill";
|
import browser, { Storage } from "webextension-polyfill";
|
||||||
import findChannelFromTwitchTvUrl from "../common/ts/findChannelFromTwitchTvUrl";
|
import findChannelFromTwitchTvUrl from "../common/ts/findChannelFromTwitchTvUrl";
|
||||||
import isChromium from "../common/ts/isChromium";
|
import isChromium from "../common/ts/isChromium";
|
||||||
|
import { getStreamStatus, setStreamStatus } from "../common/ts/streamStatus";
|
||||||
import store from "../store";
|
import store from "../store";
|
||||||
import type { State } from "../store/types";
|
import type { State } from "../store/types";
|
||||||
import { MessageType } from "../types";
|
import { MessageType } from "../types";
|
||||||
@ -80,7 +81,7 @@ function onStoreChange(changes: Record<string, Storage.StorageChange>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onBackgroundMessage(message: any) {
|
function onBackgroundMessage(message: any): undefined {
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case MessageType.EnableFullModeResponse:
|
case MessageType.EnableFullModeResponse:
|
||||||
window.postMessage({
|
window.postMessage({
|
||||||
@ -145,6 +146,15 @@ function onPageMessage(event: MessageEvent) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
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:
|
case MessageType.ClearStats:
|
||||||
clearStats(message.channelName);
|
clearStats(message.channelName);
|
||||||
break;
|
break;
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "TTV LOL PRO",
|
"name": "TTV LOL PRO",
|
||||||
"description": "TTV LOL PRO removes most livestream ads from Twitch.",
|
"description": "TTV LOL PRO removes most livestream ads from Twitch.",
|
||||||
"homepage_url": "https://github.com/younesaassila/ttv-lol-pro",
|
"homepage_url": "https://github.com/younesaassila/ttv-lol-pro",
|
||||||
"version": "2.3.7",
|
"version": "2.3.8",
|
||||||
"background": {
|
"background": {
|
||||||
"service_worker": "background/background.ts",
|
"service_worker": "background/background.ts",
|
||||||
"type": "module"
|
"type": "module"
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "TTV LOL PRO",
|
"name": "TTV LOL PRO",
|
||||||
"description": "TTV LOL PRO removes most livestream ads from Twitch.",
|
"description": "TTV LOL PRO removes most livestream ads from Twitch.",
|
||||||
"homepage_url": "https://github.com/younesaassila/ttv-lol-pro",
|
"homepage_url": "https://github.com/younesaassila/ttv-lol-pro",
|
||||||
"version": "2.3.7",
|
"version": "2.3.8",
|
||||||
"background": {
|
"background": {
|
||||||
"scripts": ["background/background.ts"],
|
"scripts": ["background/background.ts"],
|
||||||
"persistent": false
|
"persistent": false
|
||||||
|
@ -113,9 +113,14 @@
|
|||||||
<label for="anonymous-mode-checkbox">Anonymous mode</label>
|
<label for="anonymous-mode-checkbox">Anonymous mode</label>
|
||||||
<br />
|
<br />
|
||||||
<small>
|
<small>
|
||||||
Watch streams as if you were logged out. This option might help
|
Watch streams as if you were logged out. This option may help
|
||||||
reduce the number of "Commercial break in progress" ads.
|
reduce the number of "Commercial break in progress" ads.
|
||||||
</small>
|
</small>
|
||||||
|
<br />
|
||||||
|
<small>
|
||||||
|
This option might prevent Drops from working. Only disable it
|
||||||
|
<b>if you are having issues</b>.
|
||||||
|
</small>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
@ -41,7 +41,8 @@ const pageState: PageState = {
|
|||||||
|
|
||||||
window.fetch = getFetch(pageState);
|
window.fetch = getFetch(pageState);
|
||||||
|
|
||||||
window.Worker = class Worker extends window.Worker {
|
const NATIVE_WORKER = window.Worker;
|
||||||
|
window.Worker = class Worker extends NATIVE_WORKER {
|
||||||
constructor(scriptURL: string | URL, options?: WorkerOptions) {
|
constructor(scriptURL: string | URL, options?: WorkerOptions) {
|
||||||
const fullUrl = toAbsoluteUrl(scriptURL.toString());
|
const fullUrl = toAbsoluteUrl(scriptURL.toString());
|
||||||
const isTwitchWorker = fullUrl.includes(".twitch.tv");
|
const isTwitchWorker = fullUrl.includes(".twitch.tv");
|
||||||
@ -49,6 +50,16 @@ window.Worker = class Worker extends window.Worker {
|
|||||||
super(scriptURL, options);
|
super(scriptURL, options);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Required for VAFT (>=12.0.0) compatibility.
|
||||||
|
const isAlreadyHooked = NATIVE_WORKER.toString().includes("twitch");
|
||||||
|
if (isAlreadyHooked) {
|
||||||
|
console.info("[TTV LOL PRO] Another Twitch ad blocker is in use.");
|
||||||
|
sendMessageToContentScript({
|
||||||
|
type: MessageType.MultipleAdBlockersInUse,
|
||||||
|
});
|
||||||
|
super(scriptURL, options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
let script = "";
|
let script = "";
|
||||||
// Fetch Twitch's script, since Firefox Nightly errors out when trying to
|
// Fetch Twitch's script, since Firefox Nightly errors out when trying to
|
||||||
// import a blob URL directly.
|
// import a blob URL directly.
|
||||||
@ -80,7 +91,7 @@ window.Worker = class Worker extends window.Worker {
|
|||||||
const newScriptURL = URL.createObjectURL(
|
const newScriptURL = URL.createObjectURL(
|
||||||
new Blob([newScript], { type: "text/javascript" })
|
new Blob([newScript], { type: "text/javascript" })
|
||||||
);
|
);
|
||||||
// Required for VAFT compatibility.
|
// Required for VAFT (<9.0.0) compatibility.
|
||||||
const wrapperScript = `
|
const wrapperScript = `
|
||||||
try {
|
try {
|
||||||
importScripts('${newScriptURL}');
|
importScripts('${newScriptURL}');
|
||||||
|
@ -173,7 +173,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://discord.gg/AmtFTPwsyH"
|
href="https://discord.ttvlolpro.com/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="list-item"
|
class="list-item"
|
||||||
>
|
>
|
||||||
@ -239,7 +239,7 @@
|
|||||||
|
|
||||||
<small class="question-info">
|
<small class="question-info">
|
||||||
Any questions? Please read the
|
Any questions? Please read the
|
||||||
<a href="https://wiki.cdn-perfprod.com/" target="_blank">wiki</a>
|
<a href="https://wiki.ttvlolpro.com/" target="_blank">wiki</a>
|
||||||
first.
|
first.
|
||||||
</small>
|
</small>
|
||||||
</main>
|
</main>
|
||||||
|
@ -8,7 +8,6 @@ import {
|
|||||||
import { alpha2 } from "../common/ts/countryCodes";
|
import { alpha2 } from "../common/ts/countryCodes";
|
||||||
import findChannelFromTwitchTvUrl from "../common/ts/findChannelFromTwitchTvUrl";
|
import findChannelFromTwitchTvUrl from "../common/ts/findChannelFromTwitchTvUrl";
|
||||||
import isChannelWhitelisted from "../common/ts/isChannelWhitelisted";
|
import isChannelWhitelisted from "../common/ts/isChannelWhitelisted";
|
||||||
import isChromium from "../common/ts/isChromium";
|
|
||||||
import store from "../store";
|
import store from "../store";
|
||||||
import type { StreamStatus } from "../types";
|
import type { StreamStatus } from "../types";
|
||||||
|
|
||||||
@ -116,7 +115,7 @@ function setProxyStatus(
|
|||||||
reasonElement.textContent = status.reason;
|
reasonElement.textContent = status.reason;
|
||||||
reasonElement.style.display = "";
|
reasonElement.style.display = "";
|
||||||
} else if (status.stats) {
|
} else if (status.stats) {
|
||||||
reasonElement.textContent = `Proxied: ${status.stats.proxied} | Not proxied: ${status.stats.notProxied}`;
|
reasonElement.textContent = getProxyStatusStatsMessage(status.stats);
|
||||||
reasonElement.style.display = "";
|
reasonElement.style.display = "";
|
||||||
} else {
|
} else {
|
||||||
reasonElement.style.display = "none";
|
reasonElement.style.display = "none";
|
||||||
@ -133,8 +132,20 @@ function setProxyStatus(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getProxyStatusStatsMessage(
|
||||||
|
stats: NonNullable<StreamStatus["stats"]>
|
||||||
|
): string {
|
||||||
|
const formatter = new Intl.NumberFormat("en-US");
|
||||||
|
return `Proxied: ${formatter.format(
|
||||||
|
stats.proxied
|
||||||
|
)} | Not proxied: ${formatter.format(stats.notProxied)}`;
|
||||||
|
}
|
||||||
|
|
||||||
function getProxyStatusMessages(status: StreamStatus): string[] {
|
function getProxyStatusMessages(status: StreamStatus): string[] {
|
||||||
const messages = [];
|
const messages = [];
|
||||||
|
if (status.reason && status.stats) {
|
||||||
|
messages.push(getProxyStatusStatsMessage(status.stats));
|
||||||
|
}
|
||||||
if (status.proxyHost) {
|
if (status.proxyHost) {
|
||||||
messages.push(`Proxy: ${anonymizeIpAddress(status.proxyHost)}`);
|
messages.push(`Proxy: ${anonymizeIpAddress(status.proxyHost)}`);
|
||||||
}
|
}
|
||||||
@ -219,6 +230,7 @@ copyDebugInfoButtonElement.addEventListener("click", async e => {
|
|||||||
`Stream status:\n`,
|
`Stream status:\n`,
|
||||||
status != null
|
status != null
|
||||||
? [
|
? [
|
||||||
|
`- Reason: ${status.reason ?? "N/A"}\n`,
|
||||||
`- Proxied: ${status.stats?.proxied ?? "N/A"}, Not proxied: ${
|
`- Proxied: ${status.stats?.proxied ?? "N/A"}, Not proxied: ${
|
||||||
status.stats?.notProxied ?? "N/A"
|
status.stats?.notProxied ?? "N/A"
|
||||||
}\n`,
|
}\n`,
|
||||||
@ -230,9 +242,7 @@ copyDebugInfoButtonElement.addEventListener("click", async e => {
|
|||||||
`- Country: ${status.proxyCountry ?? "N/A"}\n`,
|
`- Country: ${status.proxyCountry ?? "N/A"}\n`,
|
||||||
].join("")
|
].join("")
|
||||||
: "",
|
: "",
|
||||||
isChromium
|
`Proxy level of control: ${proxySettings.levelOfControl}\n`,
|
||||||
? `Proxy level of control: ${proxySettings.levelOfControl}\n`
|
|
||||||
: "",
|
|
||||||
].join("")
|
].join("")
|
||||||
: "",
|
: "",
|
||||||
store.state.adLog.length > 0
|
store.state.adLog.length > 0
|
||||||
|
@ -33,7 +33,7 @@ class Store<T extends Record<string | symbol, any>> {
|
|||||||
if (area !== this._areaName) return;
|
if (area !== this._areaName) return;
|
||||||
for (const [key, { newValue }] of Object.entries(changes)) {
|
for (const [key, { newValue }] of Object.entries(changes)) {
|
||||||
if (newValue === undefined) continue; // Ignore deletions.
|
if (newValue === undefined) continue; // Ignore deletions.
|
||||||
this._state[key as keyof T] = newValue;
|
this._state[key as keyof T] = newValue as T[keyof T];
|
||||||
}
|
}
|
||||||
this.dispatchEvent("change", changes);
|
this.dispatchEvent("change", changes);
|
||||||
});
|
});
|
||||||
@ -46,7 +46,7 @@ class Store<T extends Record<string | symbol, any>> {
|
|||||||
|
|
||||||
this._state = this._getDefaultState();
|
this._state = this._getDefaultState();
|
||||||
for (const [key, value] of Object.entries(storage)) {
|
for (const [key, value] of Object.entries(storage)) {
|
||||||
this._state[key as keyof T] = value;
|
this._state[key as keyof T] = value as T[keyof T];
|
||||||
}
|
}
|
||||||
const stateHandler = getStateHandler(this._areaName, this._state);
|
const stateHandler = getStateHandler(this._areaName, this._state);
|
||||||
const stateProxy = new Proxy(this._state, stateHandler);
|
const stateProxy = new Proxy(this._state, stateHandler);
|
||||||
|
@ -82,6 +82,7 @@ export const enum MessageType {
|
|||||||
UsherResponse = "TLP_UsherResponse",
|
UsherResponse = "TLP_UsherResponse",
|
||||||
NewPlaybackAccessToken = "TLP_NewPlaybackAccessToken",
|
NewPlaybackAccessToken = "TLP_NewPlaybackAccessToken",
|
||||||
NewPlaybackAccessTokenResponse = "TLP_NewPlaybackAccessTokenResponse",
|
NewPlaybackAccessTokenResponse = "TLP_NewPlaybackAccessTokenResponse",
|
||||||
|
MultipleAdBlockersInUse = "TLP_MultipleAdBlockersInUse",
|
||||||
ClearStats = "TLP_ClearStats",
|
ClearStats = "TLP_ClearStats",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user