Skip to content

Commit

Permalink
Fix ESPN+ (issues with Docker build), fix NESN, update user agent list
Browse files Browse the repository at this point in the history
  • Loading branch information
Your Name committed Jan 7, 2025
1 parent da6bba3 commit 8793682
Show file tree
Hide file tree
Showing 7 changed files with 42 additions and 19 deletions.
2 changes: 1 addition & 1 deletion 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.5**
Current version: **4.1.6**

# 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
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.5",
"version": "4.1.6",
"description": "",
"scripts": {
"start": "ts-node -r tsconfig-paths/register index.tsx",
Expand Down
16 changes: 13 additions & 3 deletions services/espn-handler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import fs from 'fs';
import https from 'https';
import fsExtra from 'fs-extra';
import path from 'path';
import axios from 'axios';
Expand Down Expand Up @@ -37,6 +38,15 @@ global.WebSocket = ws;
const espnPlusTokens = path.join(configPath, 'espn_plus_tokens.json');
const espnLinearTokens = path.join(configPath, 'espn_linear_tokens.json');

const httpsAgent = new https.Agent({
rejectUnauthorized: false,
});

// For `watch.graph.api.espn.com` URLs
const instance = axios.create({
httpsAgent,
});

interface IAuthResources {
[key: string]: boolean;
}
Expand Down Expand Up @@ -707,7 +717,7 @@ class EspnHandler {
espnPlusEnabled && (await this.getGraphQlApiKey());

try {
const {data: scenarios} = await axios.get('https://watch.graph.api.espn.com/api', {
const {data: scenarios} = await instance.get('https://watch.graph.api.espn.com/api', {
params: {
apiKey: this.graphQlApiKey,
query: `{airing(id:"${eventId}",countryCode:"us",deviceType:SETTOP,tz:"Z") {id name description mrss:adobeRSS authTypes requiresLinearPlayback status:type startDateTime endDateTime duration source(authorization: SHIELD) { url authorizationType hasEspnId3Heartbeats hasNielsenWatermarks hasPassThroughAds commercialReplacement startSessionUrl } network { id type name adobeResource } image { url } sport { name code uid } league { name uid } program { code categoryCode isStudio } seekInSeconds simulcastAiringId airingId tracking { nielsenCrossId1 trackingId } eventId packages { name } language tier feedName brands { id name type }}}`,
Expand Down Expand Up @@ -872,7 +882,7 @@ class EspnHandler {
'query Airings ( $countryCode: String!, $deviceType: DeviceType!, $tz: String!, $type: AiringType, $categories: [String], $networks: [String], $packages: [String], $eventId: String, $packageId: String, $start: String, $end: String, $day: String, $limit: Int ) { airings( countryCode: $countryCode, deviceType: $deviceType, tz: $tz, type: $type, categories: $categories, networks: $networks, packages: $packages, eventId: $eventId, packageId: $packageId, start: $start, end: $end, day: $day, limit: $limit ) { id airingId simulcastAiringId name type startDateTime shortDate: startDate(style: SHORT) authTypes adobeRSS duration feedName purchaseImage { url } image { url } network { id type abbreviation name shortName adobeResource isIpAuth } source { url authorizationType hasPassThroughAds hasNielsenWatermarks hasEspnId3Heartbeats commercialReplacement } packages { name } category { id name } subcategory { id name } sport { id name abbreviation code } league { id name abbreviation code } franchise { id name } program { id code categoryCode isStudio } tracking { nielsenCrossId1 nielsenCrossId2 comscoreC6 trackingId } } }';
const variables = `{"deviceType":"DESKTOP","countryCode":"US","tz":"UTC+0000","type":"LIVE","networks":${networks},"packages":${packages},"limit":500}`;

const {data: entryData} = await axios.get(
const {data: entryData} = await instance.get(
encodeURI(
`https://watch.graph.api.espn.com/api?apiKey=${this.graphQlApiKey}&query=${query}&variables=${variables}`,
),
Expand All @@ -892,7 +902,7 @@ class EspnHandler {
'query Airings ( $countryCode: String!, $deviceType: DeviceType!, $tz: String!, $type: AiringType, $categories: [String], $networks: [String], $packages: [String], $eventId: String, $packageId: String, $start: String, $end: String, $day: String, $limit: Int ) { airings( countryCode: $countryCode, deviceType: $deviceType, tz: $tz, type: $type, categories: $categories, networks: $networks, packages: $packages, eventId: $eventId, packageId: $packageId, start: $start, end: $end, day: $day, limit: $limit ) { id airingId simulcastAiringId name type startDateTime shortDate: startDate(style: SHORT) authTypes adobeRSS duration feedName purchaseImage { url } image { url } network { id type abbreviation name shortName adobeResource isIpAuth } source { url authorizationType hasPassThroughAds hasNielsenWatermarks hasEspnId3Heartbeats commercialReplacement } packages { name } category { id name } subcategory { id name } sport { id name abbreviation code } league { id name abbreviation code } franchise { id name } program { id code categoryCode isStudio } tracking { nielsenCrossId1 nielsenCrossId2 comscoreC6 trackingId } } }';
const variables = `{"deviceType":"DESKTOP","countryCode":"US","tz":"UTC+0000","type":"UPCOMING","networks":${networks},"packages":${packages},"day":"${date}","limit":500}`;

const {data: entryData} = await axios.get(
const {data: entryData} = await instance.get(
encodeURI(
`https://watch.graph.api.espn.com/api?apiKey=${this.graphQlApiKey}&query=${query}&variables=${variables}`,
),
Expand Down
10 changes: 5 additions & 5 deletions services/nesn-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -432,10 +432,10 @@ class NesnHandler {

public getEventData = async (eventId: string): Promise<[string, IHeaders]> => {
try {
const baseUrl = ['https://', 'dtc-stream-source-backup-prod.s3.us-east-2.amazonaws.com'];
const nesnUrl = [...baseUrl, '/nesn_stream_android_tv'].join('');
const nesnPlusUrl = [...baseUrl, '/plus_stream_default'].join('');
const nesn4k = [...baseUrl, '/4k_stream_default'].join('');
const baseUrl = ['https://t2qkxvxfw6.execute-api.us-east-2.amazonaws.com/v3/stream?stream_type='];
const nesnUrl = [...baseUrl, 'nesn_stream'].join('');
const nesnPlusUrl = [...baseUrl, 'plus_stream'].join('');
const nesn4k = [...baseUrl, '4k_stream'].join('');

await this.refreshTokens();

Expand Down Expand Up @@ -668,7 +668,7 @@ class NesnHandler {
},
});

return data.playbackToken;
return data?.subscription?.playbackToken;
} catch (e) {
console.error(e);
console.log('Could not get playback token');
Expand Down
15 changes: 14 additions & 1 deletion services/playlist-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const PROXY_SEGMENTS =

const reTarget = /#EXT-X-TARGETDURATION:([0-9]+)/;
const reAudioTrack = /#EXT-X-MEDIA:TYPE=AUDIO.*URI="([^"]+)"/gm;
const reAudioTrackNesn = /#EXT-X-MEDIA.*TYPE=AUDIO.*URI="([^"]+)"/gm;
const reMap = /#EXT-X-MAP:URI="([^"]+)"/gm;
const reSubMap = /#EXT-X-MEDIA:TYPE=SUBTITLES.*URI="([^"]+)"/gm;
const reVersion = /#EXT-X-VERSION:(\d+)/;
Expand Down Expand Up @@ -108,6 +109,7 @@ export class PlaylistHandler {
headers: resHeaders,
} = await axios.get<string>(manifestUrl, {
headers: {
'Accept-Encodixng': 'identity',
'User-Agent': userAgent,
...headers,
},
Expand Down Expand Up @@ -146,7 +148,17 @@ export class PlaylistHandler {
const clonedManifest = updateVersion(HLS.stringify(playlist));
let updatedManifest = clonedManifest;

if (this.network !== 'foxsports') {
if (this.network === 'nesn') {
const audioTracks = [...manifest.matchAll(reAudioTrackNesn)];
audioTracks.forEach(track => {
if (track && track[1]) {
const fullChunklistUrl = parseReplacementUrl(track[1], realManifestUrl);

const chunklistName = cacheLayer.getChunklistFromUrl(`${fullChunklistUrl}${urlParams}`);
updatedManifest = updatedManifest.replace(track[1], `${this.baseProxyUrl}${chunklistName}.m3u8`);
}
});
} else if (this.network !== 'foxsports') {
const audioTracks = [...manifest.matchAll(reAudioTrack)];
audioTracks.forEach(track => {
if (track && track[1]) {
Expand Down Expand Up @@ -196,6 +208,7 @@ export class PlaylistHandler {

const {data: chunkList, request} = await axios.get<string>(url, {
headers: {
'Accept-Encoding': 'identity',
'User-Agent': userAgent,
...this.headers,
},
Expand Down
12 changes: 6 additions & 6 deletions services/user-agent.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
const userAgents = [
'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux i686; rv:106.0) Gecko/20100101 Firefox/106.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 13.0; rv:106.0) Gecko/20100101 Firefox/106.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:106.0) Gecko/20100101 Firefox/106.0',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 14.7; rv:133.0) Gecko/20100101 Firefox/133.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.3',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.3',
'Mozilla/5.0 (X11; Linux i686; rv:133.0) Gecko/20100101 Firefox/133.0',
];

export const cbsSportsUserAgent = 'CBSSports/6.4.0-1723047637 (firetv)';
Expand Down

0 comments on commit 8793682

Please sign in to comment.