Skip to content

Commit

Permalink
🔀 Merge pull request #44 from younesaassila/v1.6.0
Browse files Browse the repository at this point in the history
🔖 Release version 1.6.0
  • Loading branch information
younesaassila authored Dec 31, 2022
2 parents 9f8507c + 25ec6ac commit 1a4b897
Show file tree
Hide file tree
Showing 31 changed files with 856 additions and 214 deletions.
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<h1 align="center">
<img src="src/assets/icon.png" height="100" width="100" alt="Icon" />
<img src="src/images/brand/icon.png" height="100" width="100" alt="Icon" />
<br />
TTV LOL PRO
<br />
Expand Down Expand Up @@ -39,8 +39,9 @@ This fork:
- disables TTV LOL for channels you are subscribed to,
- lets you whitelist channels,
- improves TTV LOL's popup by showing stream status and "Whitelist" button,
- lets you add custom primary/fallback proxies,
- falls back to the stream with ads if the API server errors out.
- falls back to the stream with ads if the API server errors out,
- improves your privacy by removing your Twitch token from API requests,
- lets you add custom primary/fallback proxies.

**Recommendations:**

Expand All @@ -49,9 +50,14 @@ This fork:
- remove banner ads,
- block ads on VODs.

## Screenshots
## Screenshot

![Popup](https://i.imgur.com/VucfuL6.png)
<div align="center">
<img
src="https://user-images.githubusercontent.com/47226184/210093901-2d0c7f62-5e1f-4ce2-83f3-e35812361e20.png"
alt="Popup on Firefox"
/>
</div>

## Installation

Expand Down
77 changes: 75 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ttv-lol-pro",
"version": "1.5.1",
"version": "1.6.0",
"description": "TTV LOL PRO removes livestream ads from Twitch",
"@parcel/bundler-default": {
"minBundles": 10000000,
Expand Down Expand Up @@ -37,6 +37,7 @@
"@parcel/config-webextension": "^2.8.2",
"@types/semver-compare": "^1.0.1",
"@types/webextension-polyfill": "^0.9.2",
"amazon-ivs-player": "^1.14.0",
"parcel": "^2.8.2",
"postcss": "^8.4.20",
"prettier": "^2.8.1",
Expand Down
Binary file removed src/assets/icon.png
Binary file not shown.
26 changes: 18 additions & 8 deletions src/background/background.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import browser from "webextension-polyfill";
import isChrome from "../common/ts/isChrome";
import onBeforeRequest from "./handlers/onBeforeRequest";
import onBeforeSendHeaders from "./handlers/onBeforeSendHeaders";
import onHeadersReceived from "./handlers/onHeadersReceived";
import onStartup from "./handlers/onStartup";
import onApiHeadersReceived from "./handlers/onApiHeadersReceived";
import onBeforeManifestRequest from "./handlers/onBeforeManifestRequest";
import onBeforeSendApiHeaders from "./handlers/onBeforeSendApiHeaders";
import onBeforeVideoWeaverRequest from "./handlers/onBeforeVideoWeaverRequest";
import onStartupUpdateCheck from "./handlers/onStartupUpdateCheck";

// Check for updates on Chrome startup.
if (isChrome) browser.runtime.onStartup.addListener(onStartup);
if (isChrome) browser.runtime.onStartup.addListener(onStartupUpdateCheck);

// Redirect the HLS master manifest request to TTV LOL's API.
browser.webRequest.onBeforeRequest.addListener(
onBeforeRequest,
onBeforeManifestRequest,
{
urls: [
"https://usher.ttvnw.net/api/channel/hls/*",
Expand All @@ -20,16 +21,25 @@ browser.webRequest.onBeforeRequest.addListener(
["blocking"]
);

// Detect midrolls by looking for an ad signifier in the video weaver response.
browser.webRequest.onBeforeRequest.addListener(
onBeforeVideoWeaverRequest,
{
urls: ["https://*.ttvnw.net/*"], // Immediately filtered to video-weaver URLs in handler.
},
["blocking"]
);

// Add the `X-Donate-To` header to API requests.
browser.webRequest.onBeforeSendHeaders.addListener(
onBeforeSendHeaders,
onBeforeSendApiHeaders,
{ urls: ["https://api.ttv.lol/playlist/*", "https://api.ttv.lol/vod/*"] },
["blocking", "requestHeaders"]
);

// Monitor API error responses.
browser.webRequest.onHeadersReceived.addListener(
onHeadersReceived,
onApiHeadersReceived,
{ urls: ["https://api.ttv.lol/playlist/*", "https://api.ttv.lol/vod/*"] },
["blocking"]
);
61 changes: 61 additions & 0 deletions src/background/handlers/onApiHeadersReceived.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { WebRequest } from "webextension-polyfill";
import { TTV_LOL_API_URL_REGEX } from "../../common/ts/regexes";
import store from "../../store";
import type { StreamStatus } from "../../types";

export default function onApiHeadersReceived(
details: WebRequest.OnHeadersReceivedDetailsType
): WebRequest.BlockingResponseOrPromise {
const streamId = getStreamIdFromUrl(details.url);
if (!streamId) return {};

const isServerError = 500 <= details.statusCode && details.statusCode < 600;
if (isServerError) {
// Add error to stream status.
const status = getStreamStatusFromStreamId(streamId);
const errors = status.errors || [];
errors.push({
timestamp: Date.now(),
status: details.statusCode,
});

store.state.streamStatuses[streamId] = {
...status,
errors: errors,
proxyCountry: undefined, // Reset proxy country on error.
};
console.log(`${streamId}: ${status.errors.length + 1} errors`);
console.log(`${streamId}: Redirect canceled (Error ${details.statusCode})`);

return {
cancel: true, // This forces Twitch to retry the request (up to 2 times).
};
} else {
// Clear errors if server is not returning 5xx.
const status = getStreamStatusFromStreamId(streamId);
store.state.streamStatuses[streamId] = {
...status,
errors: [],
};

return {};
}
}

function getStreamIdFromUrl(url: string): string | undefined {
const match = TTV_LOL_API_URL_REGEX.exec(url);
if (!match) return;
const [, streamId] = match;
return streamId;
}

function getStreamStatusFromStreamId(streamId: string): StreamStatus {
const status = store.state.streamStatuses[streamId];
const defaultStatus = {
redirected: true,
reason: "",
errors: [],
} as StreamStatus;

return status || defaultStatus;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { TWITCH_API_URL_REGEX } from "../../common/ts/regexes";
import store from "../../store";
import { PlaylistType, Token } from "../../types";

export default function onBeforeRequest(
export default function onBeforeManifestRequest(
details: WebRequest.OnBeforeRequestDetailsType
): WebRequest.BlockingResponse | Promise<WebRequest.BlockingResponse> {
): WebRequest.BlockingResponseOrPromise {
const match = TWITCH_API_URL_REGEX.exec(details.url);
if (!match) return {};
const [, _type, streamId, _params] = match;
Expand Down
59 changes: 59 additions & 0 deletions src/background/handlers/onBeforeSendApiHeaders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { WebRequest } from "webextension-polyfill";
import filterResponseDataWrapper from "../../common/ts/filterResponseDataWrapper";
import { TTV_LOL_API_URL_REGEX } from "../../common/ts/regexes";
import store from "../../store";

const PROXY_COUNTRY_REGEX = /USER-COUNTRY="([A-Z]+)"/i;

export default function onBeforeSendApiHeaders(
details: WebRequest.OnBeforeSendHeadersDetailsType
): WebRequest.BlockingResponseOrPromise {
const requestHeaders = details.requestHeaders || [];
requestHeaders.push({
name: "X-Donate-To",
value: "https://ttv.lol/donate",
});

const response = {
requestHeaders: requestHeaders,
} as WebRequest.BlockingResponse;

filterResponseDataWrapper(details, text => {
const streamId = getStreamIdFromUrl(details.url);
const proxyCountry = extractProxyCountryFromManifest(text);
if (!streamId || !proxyCountry) return text;

setStreamStatusProxyCountry(streamId, proxyCountry);

return text;
});

return response;
}

function getStreamIdFromUrl(url: string): string | undefined {
const match = TTV_LOL_API_URL_REGEX.exec(url);
if (!match) return;
const [, streamId] = match;
return streamId;
}

function extractProxyCountryFromManifest(text: string): string | undefined {
const match = PROXY_COUNTRY_REGEX.exec(text);
if (!match) return;
const [, proxyCountry] = match;
return proxyCountry;
}

function setStreamStatusProxyCountry(
streamId: string,
proxyCountry: string
): void {
const status = store.state.streamStatuses[streamId];
if (!status) return;

store.state.streamStatuses[streamId] = {
...status,
proxyCountry: proxyCountry,
};
}
Loading

0 comments on commit 1a4b897

Please sign in to comment.