mlbserver 2026.2.23 → 2026.3.27-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +40 -5
- package/index.js +55 -11
- package/package.json +2 -1
- package/session.js +204 -54
package/Dockerfile
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
|
-
|
|
1
|
+
# --- Build Stage ---
|
|
2
|
+
FROM node:18-alpine AS build
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
# Set environment variable to skip the automatic Chromium download by Puppeteer
|
|
5
|
+
ENV PUPPETEER_SKIP_DOWNLOAD=true
|
|
6
|
+
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
|
|
7
|
+
|
|
8
|
+
# Install system-level dependencies for Chromium on Alpine
|
|
9
|
+
RUN apk update && apk add --no-cache \
|
|
10
|
+
tzdata \
|
|
11
|
+
udev \
|
|
12
|
+
ttf-freefont \
|
|
13
|
+
chromium \
|
|
14
|
+
nss \
|
|
15
|
+
freetype \
|
|
16
|
+
harfbuzz \
|
|
17
|
+
ca-certificates
|
|
4
18
|
|
|
5
19
|
# Create app directory
|
|
6
20
|
WORKDIR /mlbserver
|
|
7
21
|
|
|
8
|
-
# Add data directory
|
|
9
|
-
VOLUME /mlbserver/data_directory
|
|
10
|
-
|
|
11
22
|
# Install app dependencies
|
|
12
23
|
# A wildcard is used to ensure both package.json AND package-lock.json are copied
|
|
13
24
|
# where available (npm@5+)
|
|
@@ -20,5 +31,29 @@ RUN npm install
|
|
|
20
31
|
# Bundle app source
|
|
21
32
|
COPY . .
|
|
22
33
|
|
|
34
|
+
# --- Runtime Stage ---
|
|
35
|
+
FROM node:20-alpine AS runtime
|
|
36
|
+
|
|
37
|
+
# Install only the necessary runtime dependencies again
|
|
38
|
+
RUN apk add --no-cache \
|
|
39
|
+
tzdata \
|
|
40
|
+
udev \
|
|
41
|
+
ttf-freefont \
|
|
42
|
+
chromium \
|
|
43
|
+
nss \
|
|
44
|
+
freetype \
|
|
45
|
+
harfbuzz \
|
|
46
|
+
ca-certificates
|
|
47
|
+
|
|
48
|
+
# Set the executable path for Puppeteer
|
|
49
|
+
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
|
|
50
|
+
|
|
51
|
+
WORKDIR /mlbserver
|
|
52
|
+
# Copy built application from the build stage
|
|
53
|
+
COPY --from=build /mlbserver .
|
|
54
|
+
|
|
55
|
+
# Add data directory
|
|
56
|
+
VOLUME /mlbserver/data_directory
|
|
57
|
+
|
|
23
58
|
EXPOSE 9999 10000
|
|
24
59
|
CMD [ "node", "index.js", "--env", "--port", "9999", "--multiview_port", "10000", "--data_directory", "/mlbserver/data_directory" ]
|
package/index.js
CHANGED
|
@@ -431,7 +431,7 @@ var getKey = function(url, headers, cb) {
|
|
|
431
431
|
requestRetry(url, headers, function(err, response) {
|
|
432
432
|
if (err) return cb(err)
|
|
433
433
|
let key = response.body
|
|
434
|
-
session.debuglog('key returned ' + key)
|
|
434
|
+
session.debuglog('key returned ' + Buffer.from(key, 'binary').toString('base64'))
|
|
435
435
|
session.temp_cache.prevKeys[url] = key
|
|
436
436
|
cb(null, key)
|
|
437
437
|
})
|
|
@@ -1262,15 +1262,28 @@ app.get('/gamechangerplaylist.m3u8', async function(req, res) {
|
|
|
1262
1262
|
let new_segments_complete = false
|
|
1263
1263
|
let segment_count = 0
|
|
1264
1264
|
for (var i=(body.length-1); i>=0; i--) {
|
|
1265
|
-
if ( body[i].startsWith('#
|
|
1266
|
-
let
|
|
1267
|
-
|
|
1268
|
-
|
|
1265
|
+
if ( body[i].startsWith('#EXT-X-KEY') ) {
|
|
1266
|
+
let key = url.resolve(u, body[i].match('URI="([^"]+)"')[1])
|
|
1267
|
+
let iv = body[i].match('IV=0x(.*)$')[1]
|
|
1268
|
+
let ts
|
|
1269
|
+
let extinf
|
|
1270
|
+
for (var j=1; j<=4; j++) {
|
|
1271
|
+
if ( body[i+j] ) {
|
|
1272
|
+
if ( !extinf && body[i+j].startsWith('#EXTINF') ) {
|
|
1273
|
+
extinf = body[i+j]
|
|
1274
|
+
} else if ( !ts && !body[i+j].startsWith('#') ) {
|
|
1275
|
+
ts = url.resolve(u, body[i+j])
|
|
1276
|
+
}
|
|
1277
|
+
if ( extinf && ts ) break;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
if ( key && iv && extinf && ts && !new_segments_complete ) {
|
|
1281
|
+
session.debuglog(game_changer_title + 'found segment ' + ts)
|
|
1269
1282
|
if ( discontinuity ) {
|
|
1270
1283
|
session.debuglog(game_changer_title + 'only getting newest segment after stream change')
|
|
1271
|
-
new_segments.unshift({'extinf':
|
|
1284
|
+
new_segments.unshift({'key':key, 'iv':iv, 'extinf':extinf, 'ts':ts, 'streamURLToken':streamURLToken})
|
|
1272
1285
|
new_segments_complete = true
|
|
1273
|
-
} else if ( !discontinuity && (session.temp_cache.gamechanger[id].segments.length > 0) && (
|
|
1286
|
+
} else if ( !discontinuity && (session.temp_cache.gamechanger[id].segments.length > 0) && (ts == session.temp_cache.gamechanger[id].segments[session.temp_cache.gamechanger[id].segments.length-1].ts) ) {
|
|
1274
1287
|
session.debuglog(game_changer_title + 'found previous last segment')
|
|
1275
1288
|
new_segments_complete = true
|
|
1276
1289
|
} else if ( segment_count == GAMECHANGER_LIST_SIZE ) {
|
|
@@ -1281,7 +1294,7 @@ app.get('/gamechangerplaylist.m3u8', async function(req, res) {
|
|
|
1281
1294
|
}
|
|
1282
1295
|
new_segments_complete = true
|
|
1283
1296
|
} else {
|
|
1284
|
-
new_segments.unshift({'extinf':
|
|
1297
|
+
new_segments.unshift({'key':key, 'iv':iv, 'extinf':extinf, 'ts':ts, 'streamURLToken':streamURLToken})
|
|
1285
1298
|
}
|
|
1286
1299
|
}
|
|
1287
1300
|
segment_count++
|
|
@@ -1314,7 +1327,7 @@ app.get('/gamechangerplaylist.m3u8', async function(req, res) {
|
|
|
1314
1327
|
if ( session.temp_cache.gamechanger[id].segments[i].discontinuity ) {
|
|
1315
1328
|
session.temp_cache.gamechanger[id].playlist[resolution] += '#EXT-X-DISCONTINUITY' + '\n'
|
|
1316
1329
|
}
|
|
1317
|
-
session.temp_cache.gamechanger[id].playlist[resolution] += session.temp_cache.gamechanger[id].segments[i].extinf + '\n' + http_root + '/segment.ts?url=' + encodeURIComponent(session.temp_cache.gamechanger[id].segments[i].ts) + '&streamURLToken='+encodeURIComponent(session.temp_cache.gamechanger[id].segments[i].streamURLToken) + content_protect + '\n'
|
|
1330
|
+
session.temp_cache.gamechanger[id].playlist[resolution] += session.temp_cache.gamechanger[id].segments[i].extinf + '\n' + http_root + '/segment.ts?url=' + encodeURIComponent(session.temp_cache.gamechanger[id].segments[i].ts) + '&streamURLToken='+encodeURIComponent(session.temp_cache.gamechanger[id].segments[i].streamURLToken) + '&key='+encodeURIComponent(session.temp_cache.gamechanger[id].segments[i].key) + '&iv='+encodeURIComponent(session.temp_cache.gamechanger[id].segments[i].iv) + content_protect + '\n'
|
|
1318
1331
|
}
|
|
1319
1332
|
|
|
1320
1333
|
session.debuglog(game_changer_title + 'playlist ' + session.temp_cache.gamechanger[id].playlist[resolution])
|
|
@@ -1684,6 +1697,33 @@ app.get('/', async function(req, res) {
|
|
|
1684
1697
|
let currentDate = new Date()
|
|
1685
1698
|
|
|
1686
1699
|
let entitlements = await session.getEntitlements()
|
|
1700
|
+
|
|
1701
|
+
// MASN live stream for entitled subscribers
|
|
1702
|
+
try {
|
|
1703
|
+
if ( entitlements.includes('MASN_110') ) {
|
|
1704
|
+
body += '<tr><td><span class="tooltip">MASN<span class="tooltiptext">MASN live stream for entitled subscribers. <a href="https://support.mlb.com/s/article/MASN-In-Market-Offering">See here for more information</a>.</span></span></td><td>'
|
|
1705
|
+
let querystring = '?event=MASN'
|
|
1706
|
+
let multiviewquerystring = querystring + '&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION
|
|
1707
|
+
if ( linkType == VALID_LINK_TYPES[0] ) {
|
|
1708
|
+
if ( startFrom != VALID_START_FROM[0] ) querystring += '&startFrom=' + startFrom
|
|
1709
|
+
if ( controls != VALID_CONTROLS[0] ) querystring += '&controls=' + controls
|
|
1710
|
+
}
|
|
1711
|
+
if ( resolution != VALID_RESOLUTIONS[0] ) querystring += '&resolution=' + resolution
|
|
1712
|
+
if ( linkType == VALID_LINK_TYPES[1] ) {
|
|
1713
|
+
if ( force_vod != VALID_FORCE_VOD[0] ) querystring += '&force_vod=' + force_vod
|
|
1714
|
+
} else if ( linkType == VALID_LINK_TYPES[4] ) {
|
|
1715
|
+
querystring += '&filename=' + gameDate + ' MASN'
|
|
1716
|
+
}
|
|
1717
|
+
querystring += content_protect_b
|
|
1718
|
+
multiviewquerystring += content_protect_b
|
|
1719
|
+
body += '<a href="' + thislink + querystring + '">MASN</a>'
|
|
1720
|
+
body += '<input type="checkbox" value="http://127.0.0.1:' + session.data.port + '/stream.m3u8' + multiviewquerystring + '" onclick="addmultiview(this)">'
|
|
1721
|
+
body += '</td></tr>' + "\n"
|
|
1722
|
+
} // end entitlements check
|
|
1723
|
+
} catch (e) {
|
|
1724
|
+
session.debuglog('MASN detect error : ' + e.message)
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1687
1727
|
// MLB Network live stream for eligible USA subscribers
|
|
1688
1728
|
try {
|
|
1689
1729
|
if ( entitlements.includes('MLBN') || entitlements.includes('EXECMLB') || entitlements.includes('MLBTVMLBNADOBEPASS') ) {
|
|
@@ -2438,7 +2478,7 @@ app.get('/', async function(req, res) {
|
|
|
2438
2478
|
resolution = 'best'
|
|
2439
2479
|
}
|
|
2440
2480
|
|
|
2441
|
-
body += '<p><span class="tooltip">All<span class="tooltiptext">Will include all entitled live MLB broadcasts (games plus Big Inning, Game Changer, and Multiview, as well as MLB Network, SNLA, and/or SNY as appropriate). If favorite team(s) have been provided, it will also include affiliate games for those organizations. Channels/games subject to blackout will be omitted by default. See below for an additional option to override that.</span></span>: <a href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + content_protect_b + '">channels.m3u</a> and <a href="' + http_root + '/guide.xml?mediaType=' + mediaType + content_protect_b + '">guide.xml</a> and <a href="' + http_root + '/calendar.ics?mediaType=' + mediaType + content_protect_b + '">calendar.ics</a></p>' + "\n"
|
|
2481
|
+
body += '<p><span class="tooltip">All<span class="tooltiptext">Will include all entitled live MLB broadcasts (games plus Big Inning, Game Changer, and Multiview, as well as MASN, MLB Network, SNLA, and/or SNY as appropriate). If favorite team(s) have been provided, it will also include affiliate games for those organizations. Channels/games subject to blackout will be omitted by default. See below for an additional option to override that.</span></span>: <a href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + content_protect_b + '">channels.m3u</a> and <a href="' + http_root + '/guide.xml?mediaType=' + mediaType + content_protect_b + '">guide.xml</a> and <a href="' + http_root + '/calendar.ics?mediaType=' + mediaType + content_protect_b + '">calendar.ics</a></p>' + "\n"
|
|
2442
2482
|
|
|
2443
2483
|
let include_teams = 'ath,atl'
|
|
2444
2484
|
if ( (session.credentials.fav_teams.length > 0) && (session.credentials.fav_teams[0].length > 0) ) {
|
|
@@ -2457,6 +2497,10 @@ app.get('/', async function(req, res) {
|
|
|
2457
2497
|
|
|
2458
2498
|
body += '<p><span class="tooltip">Include (or exclude) Winter Leagues<span class="tooltiptext">Winter leagues include the Arizona Fall League, Dominican Winter League aka Liga de Beisbol Dominicano, and Mexican Winter League aka Liga Mexicana del Pacífico. Live stream only, does not support starting from the beginning or certain innings, skip options, etc.</span></span>: <a href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=winter' + content_protect_b + '">m3u</a> and <a href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=winter' + content_protect_b + '">xml</a> and <a href="' + http_root + '/calendar.ics?mediaType=' + mediaType + '&includeTeams=winter' + content_protect_b + '">ics</a></p>' + "\n"
|
|
2459
2499
|
|
|
2500
|
+
if ( entitlements.includes('MASN_110') ) {
|
|
2501
|
+
body += '<p><span class="tooltip">Include (or exclude) MASN<span class="tooltiptext">MASN live stream for entitled subscribers. <a href="https://support.mlb.com/s/article/MASN-In-Market-Offering">See here for more information</a>.</span></span>: <a href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=masn' + content_protect_b + '">m3u</a> and <a href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=masn' + content_protect_b + '">xml</a></p>' + "\n"
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2460
2504
|
if ( entitlements.includes('MLBN') || entitlements.includes('EXECMLB') || entitlements.includes('MLBTVMLBNADOBEPASS') ) {
|
|
2461
2505
|
body += '<p><span class="tooltip">Include (or exclude) MLB Network<span class="tooltiptext">MLB Network live stream is now available in the USA for paid MLBTV subscribers or as a paid add-on, in addition to authenticated TV subscribers. <a href="https://support.mlb.com/s/article/MLB-Network-Streaming-FAQ">See here for more information</a>.</span></span>: <a href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=mlbn' + content_protect_b + '">m3u</a> and <a href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=mlbn' + content_protect_b + '">xml</a></p>' + "\n"
|
|
2462
2506
|
}
|
|
@@ -2466,7 +2510,7 @@ app.get('/', async function(req, res) {
|
|
|
2466
2510
|
}
|
|
2467
2511
|
|
|
2468
2512
|
if ( entitlements.includes('SNY_121') ) {
|
|
2469
|
-
body += '<p><span class="tooltip">Include (or exclude) SNY<span class="tooltiptext">SNY live stream for entitled subscribers. <a href="https://support.mlb.com/s/article/
|
|
2513
|
+
body += '<p><span class="tooltip">Include (or exclude) SNY<span class="tooltiptext">SNY live stream for entitled subscribers. <a href="https://support.mlb.com/s/article/SNY-In-Market-Offering">See here for more information</a>.</span></span>: <a href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=sny' + content_protect_b + '">m3u</a> and <a href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=sny' + content_protect_b + '">xml</a></p>' + "\n"
|
|
2470
2514
|
}
|
|
2471
2515
|
|
|
2472
2516
|
body += '<p><span class="tooltip">Include (or exclude) Big Inning<span class="tooltiptext">Big Inning is the live look-in and highlights show. <a href="https://www.mlb.com/live-stream-games/big-inning">See here for more information</a>.</span></span>: <a href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=biginning' + content_protect_b + '">m3u</a> and <a href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=biginning' + content_protect_b + '">xml</a> and <a href="' + http_root + '/calendar.ics?mediaType=' + mediaType + '&includeTeams=biginning' + content_protect_b + '">ics</a></p>' + "\n"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mlbserver",
|
|
3
|
-
"version": "2026.2
|
|
3
|
+
"version": "2026.3.27-2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"http": "0.0.1-security",
|
|
14
14
|
"http-attach": "^1.0.0",
|
|
15
15
|
"minimist": "^1.2.8",
|
|
16
|
+
"puppeteer": "^24.40.0",
|
|
16
17
|
"readline-sync": "^1.4.10",
|
|
17
18
|
"request": "^2.88.2",
|
|
18
19
|
"request-promise": "^4.2.6",
|
package/session.js
CHANGED
|
@@ -8,6 +8,7 @@ const path = require('path')
|
|
|
8
8
|
const readlineSync = require('readline-sync')
|
|
9
9
|
const FileCookieStore = require('tough-cookie-filestore')
|
|
10
10
|
const parseString = require('xml2js').parseString
|
|
11
|
+
const puppeteer = require('puppeteer')
|
|
11
12
|
|
|
12
13
|
const MULTIVIEW_DIRECTORY_NAME = 'multiview'
|
|
13
14
|
|
|
@@ -28,7 +29,7 @@ const LIDOM_TEAM_IDS = { 'AGU': '667', 'TOR': '668', 'EST': '669', 'GIG': '670',
|
|
|
28
29
|
|
|
29
30
|
const LMP_TEAM_IDS = { 'MXC': '673', 'JAL': '674', 'MOC': '675', 'HER': '677', 'CUL': '678', 'MAZ': '679', 'OBR': '680', 'GSV': '5482', 'NAY': '6483', 'TBC': '6484' }
|
|
30
31
|
|
|
31
|
-
const AFFILIATE_TEAM_IDS = {
|
|
32
|
+
const AFFILIATE_TEAM_IDS = {"ATH":"237,400,499,524","ATL":"431,432,478,6325","AZ":"419,516,2310,5368","BAL":"418,493,548,568","BOS":"414,428,533,546","CHC":"451,521,550,553","CIN":"416,450,459,498","CLE":"402,437,445,481","COL":"259,342,486,538","CWS":"247,487,494,580","DET":"106,512,570,582","HOU":"482,573,3712,5434","KC":"541,565,1350,3705","LAA":"460,526,559,561","LAD":"238,260,456,6482","MIA":"479,554,564,4124","MIL":"249,556,572,5015","MIN":"492,509,1960,3898","NYM":"453,505,507,552","NYY":"531,537,587,1956","PHI":"427,522,566,1410","PIT":"452,477,484,3390","SD":"103,510,584,4904","SEA":"401,403,529,574","SF":"105,461,476,3410","STL":"235,279,440,443","TB":"233,234,421,2498","TEX":"102,448,540,6324","TOR":"422,424,435,463","WSH":"426,436,534,547"}
|
|
32
33
|
|
|
33
34
|
// First is default level, last should be All (also used as default org)
|
|
34
35
|
const LEVELS = { 'MLB': '1', 'AAA': '11', 'AA': '12', 'A+': '13', 'A': '14', 'WINTER': '17', 'All': '1,11,12,13,14,17' }
|
|
@@ -864,6 +865,8 @@ class sessionClass {
|
|
|
864
865
|
constructor(argv = {}) {
|
|
865
866
|
this.debug = argv.debug
|
|
866
867
|
|
|
868
|
+
this.executablePath = argv.PUPPETEER_EXECUTABLE_PATH
|
|
869
|
+
|
|
867
870
|
let dirname = __dirname
|
|
868
871
|
if ( argv.data_directory ) {
|
|
869
872
|
dirname = argv.data_directory
|
|
@@ -2368,8 +2371,8 @@ class sessionClass {
|
|
|
2368
2371
|
stream = server + '/stream.m3u8?event=' + encodeURIComponent(cache_data.dates[i].games[j].teams['home'].team.shortName.toUpperCase())
|
|
2369
2372
|
}
|
|
2370
2373
|
stream += '&league_id=' + league_id
|
|
2374
|
+
stream += '&mediaType=' + streamMediaType
|
|
2371
2375
|
}
|
|
2372
|
-
stream += '&mediaType=' + streamMediaType
|
|
2373
2376
|
stream += '&level=' + encodeURIComponent(this.getLevelNameFromSportId(sportId))
|
|
2374
2377
|
stream += '&resolution=' + resolution
|
|
2375
2378
|
if ( this.protection.content_protect ) stream += '&content_protect=' + this.protection.content_protect
|
|
@@ -2764,6 +2767,39 @@ class sessionClass {
|
|
|
2764
2767
|
channels = this.sortObj(channels)
|
|
2765
2768
|
|
|
2766
2769
|
let entitlements = await this.getEntitlements()
|
|
2770
|
+
|
|
2771
|
+
// MASN live stream for entitled subscribers
|
|
2772
|
+
try {
|
|
2773
|
+
if ( (entitlements.includes('MASN_110')) ) {
|
|
2774
|
+
if ( (mediaType == 'MLBTV') && ((includeLevels.length == 0) || includeLevels.includes('MLB') || includeLevels.includes('ALL')) ) {
|
|
2775
|
+
if ( (excludeTeams.length > 0) && excludeTeams.includes('MASN') ) {
|
|
2776
|
+
// do nothing
|
|
2777
|
+
} else if ( (includeTeams.length == 0) || includeTeams.includes('MASN') ) {
|
|
2778
|
+
this.debuglog('getTVData processing MASN')
|
|
2779
|
+
let logo = 'https://img.mlbstatic.com/mlb-images/image/upload/t_16x9/t_w640/v1745242435/mlb/jov4fxbzmqikc8umj5kr.png'
|
|
2780
|
+
let channelid = mediaType + '.MASN'
|
|
2781
|
+
//if ( this.protection.content_protect ) logo += '&content_protect=' + this.protection.content_protect
|
|
2782
|
+
let stream = server + '/stream.m3u8?event=masn&mediaType=Video&resolution=' + resolution
|
|
2783
|
+
if ( this.protection.content_protect ) stream += '&content_protect=' + this.protection.content_protect
|
|
2784
|
+
if ( pipe == 'true' ) stream = await this.convert_stream_to_pipe(stream, channelid)
|
|
2785
|
+
channels[channelid] = await this.create_channel_object(channelid, logo, stream, mediaType)
|
|
2786
|
+
|
|
2787
|
+
let title = 'MASN'
|
|
2788
|
+
let description = 'Live stream of MASN (Mid-Atlantic Sports Network)'
|
|
2789
|
+
|
|
2790
|
+
let start = this.convertDateToXMLTV(new Date(cache_data.dates[0].date + ' 00:00:00'))
|
|
2791
|
+
let stop = this.convertDateToXMLTV(new Date(cache_data.dates[cache_data.dates.length-1].date + ' 00:00:00'))
|
|
2792
|
+
|
|
2793
|
+
// MASN guide XML
|
|
2794
|
+
programs += await this.generate_xml_program(channelid, start, stop, title, description, logo, this.convertStringToAirDate(cache_data.dates[0].date))
|
|
2795
|
+
this.debuglog('getTVData completed MASN')
|
|
2796
|
+
} // end includeTeams check
|
|
2797
|
+
} // end mediaType check
|
|
2798
|
+
} // end entitlements check
|
|
2799
|
+
} catch (e) {
|
|
2800
|
+
this.debuglog('getTVData MASN detect error : ' + e.message)
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2767
2803
|
// MLB Network live stream for eligible USA subscribers
|
|
2768
2804
|
try {
|
|
2769
2805
|
if ( (entitlements.includes('MLBN') || entitlements.includes('EXECMLB') || entitlements.includes('MLBTVMLBNADOBEPASS')) ) {
|
|
@@ -2850,7 +2886,7 @@ class sessionClass {
|
|
|
2850
2886
|
let start = this.convertDateToXMLTV(new Date(cache_data.dates[0].date + ' 00:00:00'))
|
|
2851
2887
|
let stop = this.convertDateToXMLTV(new Date(cache_data.dates[cache_data.dates.length-1].date + ' 00:00:00'))
|
|
2852
2888
|
|
|
2853
|
-
//
|
|
2889
|
+
// SNY guide XML
|
|
2854
2890
|
programs += await this.generate_xml_program(channelid, start, stop, title, description, logo, this.convertStringToAirDate(cache_data.dates[0].date))
|
|
2855
2891
|
this.debuglog('getTVData completed SNY')
|
|
2856
2892
|
} // end includeTeams check
|
|
@@ -3286,6 +3322,7 @@ class sessionClass {
|
|
|
3286
3322
|
async getSkipMarkers(gamePk, skip_type, start_inning, start_inning_half, streamURL, streamURLToken, skip_adjust, broadcast_start_timestamp=false) {
|
|
3287
3323
|
try {
|
|
3288
3324
|
this.debuglog('getSkipMarkers')
|
|
3325
|
+
let variantPlaylist;
|
|
3289
3326
|
|
|
3290
3327
|
if ( skip_adjust != 0 ) this.log('manual adjustment of ' + skip_adjust + ' seconds being applied')
|
|
3291
3328
|
|
|
@@ -3309,7 +3346,7 @@ class sessionClass {
|
|
|
3309
3346
|
|
|
3310
3347
|
// Get the broadcast start time first, if necessary -- event times will be relative to this
|
|
3311
3348
|
if ( !broadcast_start_timestamp ) {
|
|
3312
|
-
|
|
3349
|
+
variantPlaylist = await this.getVariantPlaylist(streamURL, streamURLToken)
|
|
3313
3350
|
broadcast_start_timestamp = await this.getBroadcastStart(variantPlaylist)
|
|
3314
3351
|
}
|
|
3315
3352
|
|
|
@@ -3455,6 +3492,10 @@ class sessionClass {
|
|
|
3455
3492
|
// if skipping commercials, look at the variant playlist to detect insertions
|
|
3456
3493
|
if ( skip_type == 4 ) {
|
|
3457
3494
|
this.debuglog('detecting commercial breaks')
|
|
3495
|
+
if (!variantPlaylist) {
|
|
3496
|
+
this.debuglog('variantPlaylist missing, fetching...')
|
|
3497
|
+
variantPlaylist = await this.getVariantPlaylist(streamURL, streamURLToken)
|
|
3498
|
+
}
|
|
3458
3499
|
let body = variantPlaylist
|
|
3459
3500
|
let break_active = false
|
|
3460
3501
|
let break_end = 0
|
|
@@ -3505,62 +3546,118 @@ class sessionClass {
|
|
|
3505
3546
|
let currentDate = new Date()
|
|
3506
3547
|
if ( !this.cache || !this.cache.bigInningScheduleCacheExpiry || (currentDate > new Date(this.cache.bigInningScheduleCacheExpiry)) ) {
|
|
3507
3548
|
if ( !this.cache.bigInningSchedule ) this.cache.bigInningSchedule = {}
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
'
|
|
3514
|
-
'
|
|
3515
|
-
'
|
|
3516
|
-
'
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3549
|
+
|
|
3550
|
+
const browser = await puppeteer.launch({
|
|
3551
|
+
headless: 'new',
|
|
3552
|
+
executablePath: this.executablePath,
|
|
3553
|
+
args: [
|
|
3554
|
+
'--no-sandbox',
|
|
3555
|
+
'--disable-gpu',
|
|
3556
|
+
'--disable-setuid-sandbox',
|
|
3557
|
+
'--disable-dev-shm-usage'
|
|
3558
|
+
],
|
|
3559
|
+
})
|
|
3560
|
+
const page = await browser.newPage()
|
|
3561
|
+
await page.setUserAgent(USER_AGENT)
|
|
3562
|
+
await page.goto('https://support.mlb.com/s/article/What-Is-MLB-Big-Inning?language=en_US', { waitUntil: 'networkidle0' })
|
|
3563
|
+
const response = await page.content()
|
|
3564
|
+
await browser.close()
|
|
3565
|
+
|
|
3566
|
+
// break HTML into array based on table rows
|
|
3567
|
+
var rows = response.split('<tr ')
|
|
3568
|
+
// start iterating at 2 (after header row)
|
|
3569
|
+
for (var i=2; i<rows.length; i++) {
|
|
3570
|
+
// split HTML row into array with columns
|
|
3571
|
+
let cols = rows[i].split('<td ')
|
|
3572
|
+
|
|
3573
|
+
// define some variables that persist for each row
|
|
3574
|
+
let parts
|
|
3575
|
+
let year
|
|
3576
|
+
let month
|
|
3577
|
+
let day
|
|
3578
|
+
let this_datestring
|
|
3579
|
+
let add_date = 0
|
|
3580
|
+
let d
|
|
3581
|
+
|
|
3582
|
+
// start iterating at 2 (after DOW column)
|
|
3583
|
+
for (var j=2; j<cols.length; j++) {
|
|
3584
|
+
// split on brackets to get column text at resulting array index 0
|
|
3585
|
+
let col = cols[j].split('>')[1].split('<')
|
|
3586
|
+
switch(j){
|
|
3587
|
+
// first column is date
|
|
3588
|
+
case 2:
|
|
3589
|
+
// split date into array
|
|
3590
|
+
// old date format (January 1, 1970) (disabled)
|
|
3591
|
+
/*parts = col[0].split(' ')
|
|
3592
|
+
year = parts[2]
|
|
3593
|
+
// get month index, zero-based
|
|
3594
|
+
month = new Date(Date.parse(parts[0] +" 1, 2021")).getMonth()
|
|
3595
|
+
day = parts[1].substring(0,parts[1].length-3)*/
|
|
3596
|
+
// new date format (01/01/70)
|
|
3597
|
+
parts = col[0].split('/')
|
|
3598
|
+
year = parts[2]
|
|
3599
|
+
if ( year.length == 2 ) {
|
|
3600
|
+
year = '20' + parts[2]
|
|
3601
|
+
}
|
|
3602
|
+
// get month index, zero-based
|
|
3603
|
+
month = parseInt(parts[0]) - 1
|
|
3604
|
+
day = parts[1]
|
|
3605
|
+
this_datestring = new Date(year, month, day).toISOString().substring(0,10)
|
|
3606
|
+
this.cache.bigInningSchedule[this_datestring] = {}
|
|
3607
|
+
// increment month index (not zero-based)
|
|
3608
|
+
month += 1
|
|
3609
|
+
break
|
|
3610
|
+
// remaining columns are times
|
|
3611
|
+
default:
|
|
3612
|
+
let hour
|
|
3613
|
+
let minute = '00'
|
|
3614
|
+
let ampm
|
|
3615
|
+
// if time has colon, split into array on that to get hour and minute parts
|
|
3616
|
+
if ( col[0].indexOf(':') > 0 ) {
|
|
3617
|
+
parts = col[0].split(':')
|
|
3618
|
+
hour = parseInt(parts[0])
|
|
3619
|
+
minute = parts[1].substring(0,2)
|
|
3620
|
+
} else {
|
|
3621
|
+
hour = parseInt(col[0].substring(0,col[0].length-2))
|
|
3622
|
+
}
|
|
3623
|
+
ampm = col[0].substring(col[0].length-2,col[0].length)
|
|
3624
|
+
// convert hour to 24-hour format
|
|
3625
|
+
if ( (ampm == 'PM') || ((hour == 12) && (ampm == 'AM')) ) {
|
|
3626
|
+
hour += 12
|
|
3627
|
+
}
|
|
3628
|
+
// these times are EDT so add 4 for UTC
|
|
3629
|
+
hour += 4
|
|
3630
|
+
// if hour is beyond 23, note we will have to add 1 day
|
|
3631
|
+
if ( hour > 23 ) {
|
|
3632
|
+
add_date = 1
|
|
3633
|
+
hour -= 24
|
|
3634
|
+
}
|
|
3635
|
+
|
|
3636
|
+
d = new Date(this_datestring + 'T' + hour.toString().padStart(2, '0') + ':' + minute.toString().padStart(2, '0') + ':00.000+00:00')
|
|
3637
|
+
d.setDate(d.getDate()+add_date)
|
|
3638
|
+
switch(j){
|
|
3639
|
+
// 3rd column is start time
|
|
3640
|
+
case 3:
|
|
3641
|
+
this.cache.bigInningSchedule[this_datestring].start = d
|
|
3642
|
+
break
|
|
3643
|
+
// 3rd column is end time
|
|
3644
|
+
case 4:
|
|
3645
|
+
this.cache.bigInningSchedule[this_datestring].end = d
|
|
3546
3646
|
break
|
|
3547
|
-
}
|
|
3548
3647
|
}
|
|
3549
|
-
|
|
3648
|
+
break
|
|
3550
3649
|
}
|
|
3551
3650
|
}
|
|
3552
|
-
|
|
3651
|
+
}
|
|
3652
|
+
this.debuglog(JSON.stringify(this.cache.bigInningSchedule))
|
|
3553
3653
|
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3654
|
+
// Default cache period is 1 day from now
|
|
3655
|
+
let oneDayFromNow = new Date()
|
|
3656
|
+
oneDayFromNow.setDate(oneDayFromNow.getDate()+1)
|
|
3657
|
+
let cacheExpiry = oneDayFromNow
|
|
3658
|
+
this.cache.bigInningScheduleCacheExpiry = cacheExpiry
|
|
3559
3659
|
|
|
3560
|
-
|
|
3561
|
-
} else {
|
|
3562
|
-
this.log('error : invalid response from url ' + reqObj.url)
|
|
3563
|
-
}
|
|
3660
|
+
this.save_cache_data()
|
|
3564
3661
|
} else {
|
|
3565
3662
|
this.debuglog('using cached big inning schedule')
|
|
3566
3663
|
}
|
|
@@ -3848,6 +3945,8 @@ class sessionClass {
|
|
|
3848
3945
|
let dateString = eventName.substring(12)
|
|
3849
3946
|
this.debuglog('getEventStreamURL RecapRundown for ' + dateString)
|
|
3850
3947
|
playbackURL = await this.getRecapRundownURL(dateString)
|
|
3948
|
+
} else if ( eventName.toUpperCase() == 'MASN' ) {
|
|
3949
|
+
playbackURL = await this.getLinearStreamURL('MASN_ONE_LIVE')
|
|
3851
3950
|
} else if ( eventName.toUpperCase() == 'MLBN' ) {
|
|
3852
3951
|
playbackURL = 'https://falcon.mlbinfra.com/api/v1/linear/mlbn'
|
|
3853
3952
|
} else if ( eventName.toUpperCase() == 'SNLA' ) {
|
|
@@ -4314,6 +4413,7 @@ class sessionClass {
|
|
|
4314
4413
|
if ( cache_data ) {
|
|
4315
4414
|
if ( cache_data.dates && cache_data.dates[0] && cache_data.dates[0].games && (cache_data.dates[0].games.length > 0) ) {
|
|
4316
4415
|
let team_data = this.temp_cache.gamechanger[id].streamFinderData.team_data
|
|
4416
|
+
let games_CLI = this.temp_cache.gamechanger[id].streamFinderData.games_CLI
|
|
4317
4417
|
|
|
4318
4418
|
var games = []
|
|
4319
4419
|
|
|
@@ -5580,6 +5680,56 @@ class sessionClass {
|
|
|
5580
5680
|
this.log('getComskipMarkers error : ' + e.message)
|
|
5581
5681
|
}
|
|
5582
5682
|
}
|
|
5683
|
+
|
|
5684
|
+
// generates AFFILIATE_TEAM_IDS, should be done each season
|
|
5685
|
+
async getAffiliates() {
|
|
5686
|
+
try {
|
|
5687
|
+
this.debuglog('getAffiliates')
|
|
5688
|
+
|
|
5689
|
+
let affiliates_data = {}
|
|
5690
|
+
let reqObj = {
|
|
5691
|
+
url: 'https://statsapi.mlb.com/api/v1/teams?sportIds=1,11,12,13,14&activeStatus=true&season=2026',
|
|
5692
|
+
headers: {
|
|
5693
|
+
'User-agent': USER_AGENT,
|
|
5694
|
+
'Origin': 'https://www.mlb.com',
|
|
5695
|
+
'Accept-Encoding': 'gzip, deflate, br',
|
|
5696
|
+
'Content-type': 'application/json'
|
|
5697
|
+
},
|
|
5698
|
+
gzip: true
|
|
5699
|
+
}
|
|
5700
|
+
var response = await this.httpGet(reqObj, false)
|
|
5701
|
+
if ( response && this.isValidJson(response) ) {
|
|
5702
|
+
//this.debuglog(response)
|
|
5703
|
+
let teams_data = JSON.parse(response)
|
|
5704
|
+
|
|
5705
|
+
let parent_orgs = {}
|
|
5706
|
+
if ( teams_data && teams_data.teams ) {
|
|
5707
|
+
for (var i=0; i<teams_data.teams.length; i++) {
|
|
5708
|
+
if (teams_data.teams[i].sport.id == 1) {
|
|
5709
|
+
parent_orgs[teams_data.teams[i].id] = teams_data.teams[i].abbreviation
|
|
5710
|
+
affiliates_data[teams_data.teams[i].abbreviation] = []
|
|
5711
|
+
}
|
|
5712
|
+
}
|
|
5713
|
+
for (var i=0; i<teams_data.teams.length; i++) {
|
|
5714
|
+
if (teams_data.teams[i].sport.id != 1) {
|
|
5715
|
+
teams_data.teams[i].abbreviation
|
|
5716
|
+
affiliates_data[parent_orgs[teams_data.teams[i].parentOrgId]].push(teams_data.teams[i].id)
|
|
5717
|
+
affiliates_data[parent_orgs[teams_data.teams[i].parentOrgId]].sort((a, b) => a - b)
|
|
5718
|
+
}
|
|
5719
|
+
}
|
|
5720
|
+
for (const [key, value] of Object.entries(affiliates_data)) {
|
|
5721
|
+
affiliates_data[key] = value.join(',')
|
|
5722
|
+
}
|
|
5723
|
+
|
|
5724
|
+
console.log(JSON.stringify(this.sortObj(affiliates_data)))
|
|
5725
|
+
}
|
|
5726
|
+
} else {
|
|
5727
|
+
this.log('error : invalid json from url ' + reqObj.url)
|
|
5728
|
+
}
|
|
5729
|
+
} catch(e) {
|
|
5730
|
+
this.log('getAffiliates error : ' + e.message)
|
|
5731
|
+
}
|
|
5732
|
+
}
|
|
5583
5733
|
}
|
|
5584
5734
|
|
|
5585
5735
|
module.exports = sessionClass
|