Skip to content

Commit

Permalink
Remove twitch.tv integration for NFL
Browse files Browse the repository at this point in the history
  • Loading branch information
Your Name committed Dec 17, 2024
1 parent 1c0c795 commit bbc9323
Show file tree
Hide file tree
Showing 6 changed files with 26 additions and 188 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<img src="https://i.imgur.com/FIGZdR3.png">
</p>

Current version: **4.1.3**
Current version: **4.1.4**

# About
This takes ESPN+, ESPN, FOX Sports, CBS Sports, Paramount+, Gotham Sports, NFL, B1G+, NESN, Mountain West, FloSports, or MLB.tv programming and transforms it into a "live TV" experience with virtual linear channels. It will discover what is on, and generate a schedule of channels that will give you M3U and XMLTV files that you can import into something like [Jellyfin](https://jellyfin.org) or [Channels](https://getchannels.com).
Expand Down Expand Up @@ -117,7 +117,6 @@ If you don't have an NFL+ subscription, you can use these providers to access ga
| Provider Name | Description |
|---|---|
| Amazon Prime | Get TNF games from Amazon Prime |
| Twitch | Get TNF games from Twitch.tv (no auth required) |
| Peacock | Get SNF games from Peacock |
| TV Provider | Get in-market games from your TV Provider |
| Sunday Ticket | Get out-of-market games from Youtube |
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "eplustv",
"version": "4.1.3",
"version": "4.1.4",
"description": "",
"scripts": {
"start": "ts-node -r tsconfig-paths/register index.tsx",
Expand Down
170 changes: 20 additions & 150 deletions services/nfl-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import axios from 'axios';
import moment from 'moment';
import jwt_decode from 'jwt-decode';

import {okHttpUserAgent, userAgent} from './user-agent';
import {okHttpUserAgent} from './user-agent';
import {configPath} from './config';
import {useNfl} from './networks';
import {ClassTypeWithoutMethods, IEntry, IHeaders, IProvider} from './shared-interfaces';
import {db} from './database';
import {getRandomHex, getRandomUUID} from './shared-helpers';
import {getRandomUUID} from './shared-helpers';
import {useLinear} from './channels';
import {debug} from './debug';

Expand Down Expand Up @@ -119,39 +119,6 @@ const CLIENT_SECRET = ['q', 'G', 'h', 'E', 'v', '1', 'R', 't', 'I', '2', 'S', 'f

const TV_CLIENT_SECRET = ['u', 'o', 'C', 'y', 'y', 'k', 'y', 'U', 'w', 'D', 'b', 'f', 'Q', 'Z', 'r', '2'].join('');

const TWITCH_CLIENT_ID = [
'k',
'i',
'm',
'n',
'e',
'7',
'8',
'k',
'x',
'3',
'n',
'c',
'x',
'6',
'b',
'r',
'g',
'o',
'4',
'm',
'v',
'6',
'w',
'k',
'i',
'5',
'h',
'1',
'k',
'o',
].join('');

const DEVICE_INFO = {
capabilities: {},
ctvDevice: 'AndroidTV',
Expand Down Expand Up @@ -194,27 +161,14 @@ const DEFAULT_CATEGORIES = ['NFL', 'NFL+', 'Football'];

const nflConfigPath = path.join(configPath, 'nfl_tokens.json');

export type TOtherAuth = 'prime' | 'tve' | 'peacock' | 'sunday_ticket' | 'twitch';
export type TOtherAuth = 'prime' | 'tve' | 'peacock' | 'sunday_ticket';

interface INFLJwt {
dmaCode: string;
plans: {plan: string; status: string}[];
networks?: {[key: string]: string};
}

interface ITwitchAccessTokenRes {
data: ITwitchAccessToken;
}

interface ITwitchAccessToken {
streamPlaybackAccessToken: ITwitchStreamToken;
}

interface ITwitchStreamToken {
value: string;
signature: string;
}

const parseAirings = async (events: INFLEvent[]) => {
const now = moment();
const endDate = moment().add(2, 'days').endOf('day');
Expand Down Expand Up @@ -291,7 +245,6 @@ class NflHandler {
public peacockUUID?: string;
public youTubeUserId?: string;
public youTubeUUID?: string;
public twitchDeviceId?: string;

public initialize = async () => {
const setup = (await db.providers.count({name: 'nfl'})) > 0 ? true : false;
Expand Down Expand Up @@ -444,24 +397,10 @@ class NflHandler {
this.checkTVEEventAccess(i) ||
// Peacock
(i.authorizations.peacock && this.checkPeacockAccess()) ||
// Prime || Twitch.tv
(i.authorizations.amazon_prime && (this.checkPrimeAccess() || this.checkTwitchAccess()))
// Prime
(i.authorizations.amazon_prime && this.checkPrimeAccess())
) {
if (i.authorizations.amazon_prime) {
if (this.checkTwitchAccess()) {
events.push({
...i,
externalId: `${i.externalId}-twitch`,
networks: ['Twitch'],
});
}

if (this.checkPrimeAccess()) {
events.push(i);
}
} else {
events.push(i);
}
events.push(i);
}
} else if (
i.callSign === 'NFLNRZ' &&
Expand Down Expand Up @@ -520,97 +459,31 @@ class NflHandler {
const isGame =
event.channel !== 'NFLNETWORK' && event.channel !== 'NFLDIGITAL1_OO_v3' && event.channel !== 'NFLNRZ';

const isTwitch = event.feed === 'Twitch';

if (!isTwitch) {
const url = ['https://', 'api.nfl.com/', 'play/v1/asset/', id].join('');

const {data} = await axios.post(
url,
{
...(this.checkTVEAccess() && {
idp: this.mvpdIdp,
mvpdUUID: this.mvpdUUID,
mvpdUserId: this.mvpdUserId,
networks: event.feed || 'NFLN',
}),
},
{
headers: {
'Content-Type': 'application/json',
'User-Agent': okHttpUserAgent,
authorization: `Bearer ${isGame ? this.access_token : this.tv_access_token}`,
},
},
);

return [data.accessUrl, {}];
} else {
try {
const channel = event.name.indexOf('Vision') > -1 ? 'primevision' : 'primevideo';

const accessToken = await this.getTwitchAccessToken(channel);

const url = [
'https://usher.ttvnw.net',
'/api/channel/hls/',
`${channel}.m3u8`,
'?client_id=',
TWITCH_CLIENT_ID,
'&token=',
accessToken.value,
'&sig=',
accessToken.signature,
'&allow_source=true',
'&allow_audio_only=false',
].join('');

return [url, {}];
} catch (e) {
console.error(e);
console.log('Could not start playback from Twitch');
}
}
} catch (e) {
console.error(e);
console.log('Could not start playback');
}
};
const url = ['https://', 'api.nfl.com/', 'play/v1/asset/', id].join('');

private getTwitchAccessToken = async (channel: string): Promise<ITwitchStreamToken> => {
try {
const {data} = await axios.post<ITwitchAccessTokenRes>(
'https://gql.twitch.tv/gql',
const {data} = await axios.post(
url,
{
extensions: {
persistedQuery: {
sha256Hash: 'ed230aa1e33e07eebb8928504583da78a5173989fadfb1ac94be06a04f3cdbe9',
version: 1,
},
},
operationName: 'PlaybackAccessToken',
variables: {
isLive: true,
isVod: false,
login: channel,
platform: 'web',
playerType: 'site',
vodID: '',
},
...(this.checkTVEAccess() && {
idp: this.mvpdIdp,
mvpdUUID: this.mvpdUUID,
mvpdUserId: this.mvpdUserId,
networks: event.feed || 'NFLN',
}),
},
{
headers: {
'Client-id': TWITCH_CLIENT_ID,
'User-Agent': userAgent,
'X-Device-Id': this.twitchDeviceId || getRandomHex(),
'Content-Type': 'application/json',
'User-Agent': okHttpUserAgent,
authorization: `Bearer ${isGame ? this.access_token : this.tv_access_token}`,
},
},
);

return data.data.streamPlaybackAccessToken;
return [data.accessUrl, {}];
} catch (e) {
console.error(e);
console.log('Could not get Twitch access token');
console.log('Could not start playback');
}
};

Expand Down Expand Up @@ -698,7 +571,6 @@ class NflHandler {
private checkPeacockAccess = (): boolean => (this.peacockUserId ? true : false);
private checkPrimeAccess = (): boolean => (this.amazonPrimeUserId ? true : false);
private checkSundayTicketAccess = (): boolean => (this.youTubeUserId ? true : false);
private checkTwitchAccess = (): boolean => (this.twitchDeviceId ? true : false);

private checkTVEEventAccess = (event: INFLEvent): boolean => {
let hasChannel = false;
Expand Down Expand Up @@ -1068,7 +940,6 @@ class NflHandler {
peacockUUID,
youTubeUserId,
youTubeUUID,
twitchDeviceId,
} = tokens;

this.device_id = device_id;
Expand All @@ -1090,7 +961,6 @@ class NflHandler {
this.peacockUUID = peacockUUID;
this.youTubeUUID = youTubeUUID;
this.youTubeUserId = youTubeUserId;
this.twitchDeviceId = twitchDeviceId;
};

private loadJSON = () => {
Expand Down
17 changes: 2 additions & 15 deletions services/providers/nfl/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { db } from '@/services/database';
import { IProvider } from '@/services/shared-interfaces';
import { removeEntriesProvider, scheduleEntries } from '@/services/build-schedule';
import { nflHandler, TNFLTokens, TOtherAuth } from '@/services/nfl-handler';
import { getRandomHex } from '@/services/shared-helpers';

import { Login } from './views/Login';
import { NFLBody } from './views/CardBody';
Expand Down Expand Up @@ -62,24 +61,12 @@ nfl.put('/auth/:provider', async c => {
delete updatedTokens.youTubeUUID;
delete updatedTokens.youTubeUserId;
break;
case 'twitch':
delete updatedTokens.twitchDeviceId;
break;
}
} else {
if (provider === 'twitch') {
updatedTokens.twitchDeviceId = getRandomHex();
}
}

if (!enabled || provider === 'twitch') {
if (!enabled) {
const {linear_channels, tokens} = await db.providers.update<IProvider<TNFLTokens>>({name: 'nfl'}, {$set: {tokens: updatedTokens}}, {returnUpdatedDocs: true});

if (provider === 'twitch' && enabled) {
// Kickoff event scheduler
await scheduleEvents();
}

return c.html(<NFLBody channels={linear_channels} enabled={true} open={false} tokens={tokens} />);
}

Expand Down Expand Up @@ -116,7 +103,7 @@ nfl.get('/login/:code/:other', async c => {
? ' (Peacock)'
: otherAuth === 'sunday_ticket'
? ' (Youtube)'
: otherAuth === 'twitch' ? ' (Twitch)' : '';
: '';

const message = `NFL${otherAuthName}`;

Expand Down
18 changes: 0 additions & 18 deletions services/providers/nfl/views/CardBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,24 +110,6 @@ export const NFLBody: FC<INFLBodyProps> = ({enabled, tokens, open, channels}) =>
</label>
</fieldset>
</div>
<div class="grid-container">
<h6>Twitch:</h6>
<fieldset>
<label>
Enabled&nbsp;&nbsp;
<input
hx-put="/providers/nfl/auth/twitch"
hx-trigger="change"
hx-target="#nfl-body"
name="nfl-twitch-enabled"
type="checkbox"
role="switch"
checked={tokens.twitchDeviceId ? true : false}
data-enabled={tokens.twitchDeviceId ? 'true' : 'false'}
/>
</label>
</fieldset>
</div>
<div class="grid-container">
<h6>Sunday Ticket:</h6>
<fieldset>
Expand Down

0 comments on commit bbc9323

Please sign in to comment.