mlbserver 2023.3.31 → 2023.4.4

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.
Files changed (4) hide show
  1. package/README.md +4 -2
  2. package/index.js +193 -79
  3. package/package.json +1 -1
  4. package/session.js +91 -18
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # mlbserver
2
2
 
3
- Current version 2023.03.31
3
+ Current version 2023.04.04
4
4
 
5
5
  Credit to https://github.com/tonycpsu/streamglob and https://github.com/mafintosh/hls-decryptor
6
6
 
@@ -44,6 +44,7 @@ services:
44
44
  - zip_code=0
45
45
  - fav_teams=0
46
46
  #- zip_code=00000
47
+ #- country=USA
47
48
  #- fav_teams=ARI,ATL
48
49
  #- debug=false
49
50
  #- port=9999
@@ -106,7 +107,8 @@ Advanced command line or Docker environment options:
106
107
  --account_username (email address, default will use stored credentials or prompt user to enter them)
107
108
  --account_password (default will use stored credentials or prompt user to enter them)
108
109
  --zip_code (optional, for USA blackout labels, will prompt if not set or stored)
109
- --fav_teams (optional, comma-separated list of favorite team abbreviations, will prompt if not set or stored)
110
+ --country (optional, for international blackout labels -- see list of accepted names in session.js -- defaults to USA if not set or stored)
111
+ --fav_teams (optional, comma-separated list of favorite team abbreviations from https://github.com/tonywagner/mlbserver/blob/master/session.js#L26 -- will prompt if not set or stored)
110
112
  --free (optional, highlights free games)
111
113
  --multiview_port (port for multiview streaming; defaults to 1 more than primary port, or 10000)
112
114
  --multiview_path (where to create the folder for multiview encoded files; defaults to app directory)
package/index.js CHANGED
@@ -30,7 +30,7 @@ const VALID_INNING_HALF = [ '', 'top', 'bottom' ]
30
30
  const VALID_INNING_NUMBER = [ '', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12' ]
31
31
  const VALID_SCORES = [ 'Hide', 'Show' ]
32
32
  const VALID_RESOLUTIONS = [ 'adaptive', '720p60', '720p', '540p', '504p', '360p', 'none' ]
33
- const DEFAULT_MULTIVIEW_RESOLUTION = '540p'
33
+ const DEFAULT_MULTIVIEW_RESOLUTION = '504p'
34
34
  // Corresponding andwidths to display for above resolutions
35
35
  const DISPLAY_BANDWIDTHS = [ '', '6600k', '4160k', '2950k', '2120k', '1400k', '' ]
36
36
  const VALID_AUDIO_TRACKS = [ 'all', 'English', 'English Radio', 'Radio Española', 'Alternate English', 'Alternate Spanish', 'none' ]
@@ -46,6 +46,8 @@ const SAMPLE_STREAM_URL = 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8'
46
46
 
47
47
  const SECONDS_PER_SEGMENT = 5
48
48
 
49
+ const AFFILIATE_TEAM_IDS = { 'ARI': '419,516,2310,5368', 'ATL': '430,432,478,431', 'BAL': '418,568,548,488', 'BOS': '428,414,533,546', 'CHC': '521,451,550,553', 'CIN': '450,459,498,416', 'CLE': '445,402,437,481', 'COL': '259,486,342,538', 'CWS': '247,580,487,494', 'DET': '512,570,582,106', 'HOU': '482,5434,573,3712', 'KC': '1350,3705,541,565', 'LAA': '401,559,460,561', 'LAD': '260,238,456,526', 'MIA': '479,564,554,4124', 'MIL': '249,572,556,5015', 'MIN': '492,509,1960,3898', 'NYM': '453,507,552,505', 'NYY': '1956,587,531,537', 'OAK': '237,499,400,524', 'PHI': '427,522,1410,566', 'PIT': '3390,484,452,477', 'SD': '103,510,584,4904', 'SEA': '403,515,529,574', 'SF': '105,461,476,3410', 'STL': '279,235,440,443', 'TB': '2498,233,234,421', 'TEX': '102,485,540,448', 'TOR': '424,435,463,422', 'WSH': '436,534,547,426' }
50
+
49
51
  // for favorites: text, then background, based on https://teamcolors.jim-nielsen.com/
50
52
  const TEAM_COLORS = {'ARI': ['E3D4AD', 'A71930'], 'ATL': ['13274F', 'CE1141'], 'BAL': ['000000', 'DF4601'], 'BOS': ['0D2B56', 'BD3039'], 'CHC': ['CC3433', '0E3386'], 'CWS': ['000000', 'C4CED4'], 'CIN': ['FFFFFF', 'C6011F'], 'CLE': ['002B5C', 'E31937'], 'COL': ['C4CED4', '333366'], 'DET': ['0C2C56', 'FFFFFF'], 'HOU': ['002D62', 'EB6E1F'], 'KC': ['C09A5B', '004687'], 'LAA': ['FFFFFF', 'BA0021'], 'LAD': ['FFFFFF', '005A9C'], 'MIA': ['0077C8', 'FF6600'], 'MIL': ['0A2351', 'B6922E'], 'MIN': ['D31145', '002B5C'], 'NYM': ['002D72', 'FF5910'], 'NYY': ['FFFFFF', '003087'], 'OAK': ['003831', 'EFB21E'], 'PHI': ['284898', 'E81828'], 'PIT': ['000000', 'FDB827'], 'STL': ['FEDB00', 'C41E3A'], 'SD': ['FEC325', '7F411C'], 'SF': ['000000', 'FD5A1E'], 'SEA': ['C4CED4', '005C5C'], 'TB': ['092C5C', '8FBCE6'], 'TEX': ['003278', 'C0111F'], 'TOR': ['FFFFFF', '134A8E'], 'WSH': ['AB0003', '11225B']}
51
53
 
@@ -103,7 +105,8 @@ const GAMECHANGER_RESPONSE_HEADERS = {"statusCode":200,"headers":{"content-type"
103
105
  // --account_username (email address, default will use stored credentials or prompt user to enter them)
104
106
  // --account_password (default will use stored credentials or prompt user to enter them)
105
107
  // --zip_code (optional, for USA blackout labels, will prompt if not set or stored)
106
- // --fav_teams (optional, comma-separated list of favorite team abbreviations, will prompt if not set or stored)
108
+ // --country (optional, for international blackout labels -- see list of accepted names in session.js -- defaults to USA if not set or stored)
109
+ // --fav_teams (optional, comma-separated list of favorite team abbreviations from https://github.com/tonywagner/mlbserver/blob/master/session.js#L26 -- will prompt if not set or stored)
107
110
  // --data_directory (defaults to app directory, must already exist if set to something else; should match storage volume for Docker)
108
111
  // --free (optional, free account, highlights free games)
109
112
  // --multiview_port (port for multiview streaming; defaults to 1 more than primary port, or 10000)
@@ -126,7 +129,7 @@ var argv = minimist(process.argv, {
126
129
  e: 'env'
127
130
  },
128
131
  boolean: ['ffmpeg_logging', 'debug', 'logout', 'session', 'cache', 'version', 'free', 'env'],
129
- string: ['port', 'account_username', 'account_password', 'multiview_port', 'multiview_path', 'ffmpeg_path', 'ffmpeg_encoder', 'page_username', 'page_password', 'content_protect', 'gamechanger_delay', 'data_directory']
132
+ string: ['port', 'account_username', 'account_password', 'zip_code', 'country', 'fav_teams', 'multiview_port', 'multiview_path', 'ffmpeg_path', 'ffmpeg_encoder', 'page_username', 'page_password', 'content_protect', 'gamechanger_delay', 'data_directory']
130
133
  })
131
134
 
132
135
  if (argv.env) argv = process.env
@@ -212,6 +215,25 @@ function corsMiddleware (req, res, next) {
212
215
  httpAttach(multiview_app, corsMiddleware)
213
216
  multiview_app.listen(multiview_port)
214
217
 
218
+ // Listen for clear cache requests
219
+ app.get('/clearcache', async function(req, res) {
220
+ if ( ! (await protect(req, res)) ) return
221
+
222
+ try {
223
+ session.requestlog('clearcache', req)
224
+
225
+ session.log('Clearing cache...')
226
+ session.clear_cache()
227
+ session = new sessionClass(argv)
228
+
229
+ let server = 'http://' + req.headers.host
230
+ res.redirect(server)
231
+ } catch (e) {
232
+ session.log('clearcache request error : ' + e.message)
233
+ res.end('clearcache request error, check log')
234
+ }
235
+ })
236
+
215
237
  // Listen for stream requests
216
238
  app.get('/stream.m3u8', async function(req, res) {
217
239
  if ( ! (await protect(req, res)) ) return
@@ -226,7 +248,7 @@ app.get('/stream.m3u8', async function(req, res) {
226
248
  let options = {}
227
249
  let includeBlackouts = 'false'
228
250
  let urlArray = req.url.split('?')
229
- if ( (urlArray.length == 1) || ((session.data.scan_mode == VALID_SCAN_MODES[1]) && req.query.team) || (!req.query.team && !req.query.src && !req.query.highlight_src && !req.query.event && !req.query.id && !req.query.mediaId && !req.query.contentId) ) {
251
+ if ( (urlArray.length == 1) || ((session.data.scan_mode == VALID_SCAN_MODES[1]) && req.query.team) || (!req.query.team && !req.query.src && !req.query.highlight_src && !req.query.event && !req.query.gamePk && !req.query.id && !req.query.mediaId && !req.query.contentId) ) {
230
252
  // load a sample encrypted HLS stream
231
253
  session.log('loading sample stream')
232
254
  options.resolution = VALID_RESOLUTIONS[0]
@@ -264,52 +286,57 @@ app.get('/stream.m3u8', async function(req, res) {
264
286
  } else {
265
287
  if ( req.query.gamePk ) {
266
288
  gamePk = req.query.gamePk
267
- }
268
- if ( req.query.contentId ) {
269
- contentId = req.query.contentId
270
- }
271
- for (var i=0; i<ALTERNATE_AUDIO_TRACKS.length; i++) {
272
- if ( req.query[ALTERNATE_AUDIO_TRACKS[i]] ) {
273
- options.alternate_audio_tracks[ALTERNATE_AUDIO_TRACKS[i]] = req.query[ALTERNATE_AUDIO_TRACKS[i]]
289
+ if ( !req.query.mediaId && !req.query.contentId ) {
290
+ streamURL = await session.getEventStreamURL(false, gamePk)
274
291
  }
275
292
  }
276
- if ( req.query.mediaId ) {
277
- mediaId = req.query.mediaId
278
- } else if ( req.query.gamePk && req.query.contentId ) {
279
- let mediaInfo = await session.getMediaIdFromContentId(gamePk, contentId)
280
- if ( mediaInfo ) {
281
- mediaId = mediaInfo.mediaId
282
- if ( mediaInfo.alternateAudioTracks ) {
283
- for (const [key, value] of Object.entries(mediaInfo.alternateAudioTracks)) {
284
- options.alternate_audio_tracks[key] = value
285
- }
293
+ if ( !streamURL ) {
294
+ if ( req.query.contentId ) {
295
+ contentId = req.query.contentId
296
+ }
297
+ for (var i=0; i<ALTERNATE_AUDIO_TRACKS.length; i++) {
298
+ if ( req.query[ALTERNATE_AUDIO_TRACKS[i]] ) {
299
+ options.alternate_audio_tracks[ALTERNATE_AUDIO_TRACKS[i]] = req.query[ALTERNATE_AUDIO_TRACKS[i]]
286
300
  }
287
- } else {
288
- session.log('no matching game found ' + req.url)
289
301
  }
290
- } else if ( req.query.team ) {
291
- let mediaType = req.query.mediaType || VALID_MEDIA_TYPES[0]
292
- let mediaInfo = await session.getMediaId(decodeURIComponent(req.query.team), mediaType, req.query.date, req.query.game, includeBlackouts)
293
- if ( mediaInfo ) {
294
- mediaId = mediaInfo.mediaId
295
- contentId = mediaInfo.contentId
296
- if ( mediaInfo.alternateAudioTracks ) {
297
- for (const [key, value] of Object.entries(mediaInfo.alternateAudioTracks)) {
298
- options.alternate_audio_tracks[key] = value
302
+ if ( req.query.mediaId ) {
303
+ mediaId = req.query.mediaId
304
+ } else if ( req.query.gamePk && req.query.contentId ) {
305
+ let mediaInfo = await session.getMediaIdFromContentId(gamePk, contentId)
306
+ if ( mediaInfo ) {
307
+ mediaId = mediaInfo.mediaId
308
+ if ( mediaInfo.alternateAudioTracks ) {
309
+ for (const [key, value] of Object.entries(mediaInfo.alternateAudioTracks)) {
310
+ options.alternate_audio_tracks[key] = value
311
+ }
299
312
  }
313
+ } else {
314
+ session.log('no matching game found ' + req.url)
315
+ }
316
+ } else if ( req.query.team ) {
317
+ let mediaType = req.query.mediaType || VALID_MEDIA_TYPES[0]
318
+ let mediaInfo = await session.getMediaId(decodeURIComponent(req.query.team), mediaType, req.query.date, req.query.game, includeBlackouts)
319
+ if ( mediaInfo ) {
320
+ mediaId = mediaInfo.mediaId
321
+ contentId = mediaInfo.contentId
322
+ if ( mediaInfo.alternateAudioTracks ) {
323
+ for (const [key, value] of Object.entries(mediaInfo.alternateAudioTracks)) {
324
+ options.alternate_audio_tracks[key] = value
325
+ }
326
+ }
327
+ } else {
328
+ session.log('no matching game found ' + req.url)
300
329
  }
301
- } else {
302
- session.log('no matching game found ' + req.url)
303
330
  }
304
- }
305
331
 
306
- if ( !mediaId ) {
307
- session.log('failed to get mediaId : ' + req.url)
308
- res.end('')
309
- return
310
- } else {
311
- session.debuglog('mediaId : ' + mediaId)
312
- streamURL = await session.getStreamURL(mediaId)
332
+ if ( !mediaId ) {
333
+ session.log('failed to get mediaId : ' + req.url)
334
+ res.end('')
335
+ return
336
+ } else {
337
+ session.debuglog('mediaId : ' + mediaId)
338
+ streamURL = await session.getStreamURL(mediaId)
339
+ }
313
340
  }
314
341
  }
315
342
  }
@@ -1184,16 +1211,49 @@ app.get('/', async function(req, res) {
1184
1211
  gameDate = yesterday
1185
1212
  }
1186
1213
  }
1187
- var cache_data = await session.getDayData(gameDate)
1188
- var big_inning
1189
- if ( cache_data.dates && cache_data.dates[0] && (cache_data.dates[0].date >= today) && cache_data.dates[0].games && (cache_data.dates[0].games.length > 1) && cache_data.dates[0].games[0] && (cache_data.dates[0].games[0].seriesDescription == 'Regular Season') ) {
1190
- // Scraped Big Inning schedule
1191
- big_inning = await session.getBigInningSchedule(gameDate)
1192
1214
 
1193
- // Generated Big Inning schedule (disabled)
1194
- //big_inning = await session.generateBigInningSchedule(gameDate)
1215
+ var levels = session.getLevels()
1216
+ var level_labels = Object.keys(levels)
1217
+ var default_level = level_labels[0]
1218
+ var level = default_level
1219
+ if ( req.query.level ) {
1220
+ level = decodeURIComponent(req.query.level)
1221
+ }
1222
+ if ( typeof levels[level] === 'undefined' ) {
1223
+ level = default_level
1195
1224
  }
1196
1225
 
1226
+ var level_ids = levels[level]
1227
+ var default_org = level_labels[level_labels.length-1]
1228
+ var org = default_org
1229
+ var team_ids = ''
1230
+ if ( req.query.org ) {
1231
+ org = decodeURIComponent(req.query.org)
1232
+ if ( typeof AFFILIATE_TEAM_IDS[org] === 'undefined' ) {
1233
+ org = default_org
1234
+ } else {
1235
+ team_ids += session.getTeamIds(org) + ',' + AFFILIATE_TEAM_IDS[org]
1236
+ level = default_org
1237
+ }
1238
+ } else if ( level_ids == '1' ) {
1239
+ team_ids = session.getTeamIds()
1240
+ for (let i=0; i<session.credentials.fav_teams.length; i++) {
1241
+ if ( level_ids == '1' ) {
1242
+ level_ids = levels['All']
1243
+ }
1244
+ team_ids += ',' + AFFILIATE_TEAM_IDS[session.credentials.fav_teams[i]]
1245
+ }
1246
+ }
1247
+ let cache_name = gameDate
1248
+ if ( level_ids != '1' ) {
1249
+ cache_name += '.' + level_ids
1250
+ }
1251
+ if ( team_ids != '' ) {
1252
+ cache_name += '.' + team_ids
1253
+ }
1254
+
1255
+ var cache_data = await session.getDayData(gameDate, false, level_ids, team_ids)
1256
+
1197
1257
  var linkType = VALID_LINK_TYPES[0]
1198
1258
  if ( req.query.linkType ) {
1199
1259
  linkType = req.query.linkType
@@ -1271,10 +1331,10 @@ app.get('/', async function(req, res) {
1271
1331
  body += '</style><script type="text/javascript">' + "\n";
1272
1332
 
1273
1333
  // Define option variables in page
1274
- body += 'var date="' + gameDate + '";var mediaType="' + mediaType + '";var resolution="' + resolution + '";var audio_track="' + audio_track + '";var force_vod="' + force_vod + '";var inning_half="' + inning_half + '";var inning_number="' + inning_number + '";var skip="' + skip + '";var pad="' + pad + '";var linkType="' + linkType + '";var startFrom="' + startFrom + '";var scores="' + scores + '";var controls="' + controls + '";var scan_mode="' + scan_mode + '";var content_protect="' + content_protect + '";' + "\n"
1334
+ body += 'var date="' + gameDate + '";var level="' + level + '";var org="' + org + '";var mediaType="' + mediaType + '";var resolution="' + resolution + '";var audio_track="' + audio_track + '";var force_vod="' + force_vod + '";var inning_half="' + inning_half + '";var inning_number="' + inning_number + '";var skip="' + skip + '";var pad="' + pad + '";var linkType="' + linkType + '";var startFrom="' + startFrom + '";var scores="' + scores + '";var controls="' + controls + '";var scan_mode="' + scan_mode + '";var content_protect="' + content_protect + '";' + "\n"
1275
1335
 
1276
1336
  // Reload function, called after options change
1277
- body += 'var defaultDate="' + today + '";var curDate=new Date();var utcHours=curDate.getUTCHours();if ((utcHours >= ' + todayUTCHours + ') && (utcHours < ' + YESTERDAY_UTC_HOURS + ')){defaultDate="' + yesterday + '"}function reload(){var newurl="/?";if (date != defaultDate){var urldate=date;if (date == "' + today + '"){urldate="today"}else if (date == "' + yesterday + '"){urldate="yesterday"}newurl+="date="+urldate+"&"}if (mediaType != "' + VALID_MEDIA_TYPES[0] + '"){newurl+="mediaType="+mediaType+"&"}if (mediaType=="Video"){if (resolution != "' + VALID_RESOLUTIONS[0] + '"){newurl+="resolution="+resolution+"&"}if (audio_track != "' + VALID_AUDIO_TRACKS[0] + '"){newurl+="audio_track="+encodeURIComponent(audio_track)+"&"}else if (resolution == "none"){newurl+="audio_track="+encodeURIComponent("' + VALID_AUDIO_TRACKS[2] + '")+"&"}if (inning_half != "' + VALID_INNING_HALF[0] + '"){newurl+="inning_half="+inning_half+"&"}if (inning_number != "' + VALID_INNING_NUMBER[0] + '"){newurl+="inning_number="+inning_number+"&"}if (skip != "' + VALID_SKIP[0] + '"){newurl+="skip="+skip+"&";}}if (pad != "' + VALID_PAD[0] + '"){newurl+="pad="+pad+"&";}if (linkType != "' + VALID_LINK_TYPES[0] + '"){newurl+="linkType="+linkType+"&"}if (linkType=="' + VALID_LINK_TYPES[0] + '"){if (startFrom != "' + VALID_START_FROM[0] + '"){newurl+="startFrom="+startFrom+"&"}if (controls != "' + VALID_CONTROLS[0] + '"){newurl+="controls="+controls+"&"}}if (linkType=="Stream"){if (force_vod != "' + VALID_FORCE_VOD[0] + '"){newurl+="force_vod="+force_vod+"&"}}if (scores != "' + VALID_SCORES[0] + '"){newurl+="scores="+scores+"&"}if (scan_mode != "' + session.data.scan_mode + '"){newurl+="scan_mode="+scan_mode+"&"}if (content_protect != ""){newurl+="content_protect="+content_protect+"&"}window.location=newurl.substring(0,newurl.length-1)}' + "\n"
1337
+ body += 'var defaultDate="' + today + '";var curDate=new Date();var utcHours=curDate.getUTCHours();if ((utcHours >= ' + todayUTCHours + ') && (utcHours < ' + YESTERDAY_UTC_HOURS + ')){defaultDate="' + yesterday + '"}function reload(){var newurl="/?";if (date != defaultDate){var urldate=date;if (date == "' + today + '"){urldate="today"}else if (date == "' + yesterday + '"){urldate="yesterday"}newurl+="date="+urldate+"&"}if (level != "' + default_level + '"){newurl+="level="+encodeURIComponent(level)+"&"}if (org != "All"){newurl+="org="+encodeURIComponent(org)+"&"}if (mediaType != "' + VALID_MEDIA_TYPES[0] + '"){newurl+="mediaType="+mediaType+"&"}if (mediaType=="Video"){if (resolution != "' + VALID_RESOLUTIONS[0] + '"){newurl+="resolution="+resolution+"&"}if (audio_track != "' + VALID_AUDIO_TRACKS[0] + '"){newurl+="audio_track="+encodeURIComponent(audio_track)+"&"}else if (resolution == "none"){newurl+="audio_track="+encodeURIComponent("' + VALID_AUDIO_TRACKS[2] + '")+"&"}if (inning_half != "' + VALID_INNING_HALF[0] + '"){newurl+="inning_half="+inning_half+"&"}if (inning_number != "' + VALID_INNING_NUMBER[0] + '"){newurl+="inning_number="+inning_number+"&"}if (skip != "' + VALID_SKIP[0] + '"){newurl+="skip="+skip+"&";}}if (pad != "' + VALID_PAD[0] + '"){newurl+="pad="+pad+"&";}if (linkType != "' + VALID_LINK_TYPES[0] + '"){newurl+="linkType="+linkType+"&"}if (linkType=="' + VALID_LINK_TYPES[0] + '"){if (startFrom != "' + VALID_START_FROM[0] + '"){newurl+="startFrom="+startFrom+"&"}if (controls != "' + VALID_CONTROLS[0] + '"){newurl+="controls="+controls+"&"}}if (linkType=="Stream"){if (force_vod != "' + VALID_FORCE_VOD[0] + '"){newurl+="force_vod="+force_vod+"&"}}if (scores != "' + VALID_SCORES[0] + '"){newurl+="scores="+scores+"&"}if (scan_mode != "' + session.data.scan_mode + '"){newurl+="scan_mode="+scan_mode+"&"}if (content_protect != ""){newurl+="content_protect="+content_protect+"&"}window.location=newurl.substring(0,newurl.length-1)}' + "\n"
1278
1338
 
1279
1339
  // Ajax function for multiview and highlights
1280
1340
  body += 'function makeGETRequest(url, callback){var request=new XMLHttpRequest();request.onreadystatechange=function(){if (request.readyState==4 && request.status==200){callback(request.responseText)}};request.open("GET", url);request.send();}' + "\n"
@@ -1299,7 +1359,24 @@ app.get('/', async function(req, res) {
1299
1359
  if ( ((VALID_DATES[i] == VALID_DATES[0]) && (gameDate == today)) || ((VALID_DATES[i] == VALID_DATES[1]) && (gameDate == yesterday)) ) body += 'class="default" '
1300
1360
  body += 'onclick="date=\'' + VALID_DATES[i] + '\';reload()">' + VALID_DATES[i] + '</button> '
1301
1361
  }
1302
- body += '</p>' + "\n" + '<p><span class="tinytext">Updated ' + session.getCacheUpdatedDate(gameDate) + '</span></p>' + "\n"
1362
+ body += '</p>' + "\n" + '<p><span class="tinytext">Updated ' + session.getCacheUpdatedDate(cache_name) + '</span></p>' + "\n"
1363
+
1364
+ body += '<p><span class="tooltip">Level<span class="tooltiptext">Major or minor league level</span></span>: '
1365
+ for (const [key, value] of Object.entries(levels)) {
1366
+ body += '<button '
1367
+ if ( level == key ) body += 'class="default" '
1368
+ body += 'onclick="org=\'' + default_org + '\';level=\'' + key + '\';reload()">' + key + '</button> '
1369
+ }
1370
+
1371
+ body += ' or <span class="tooltip">Org<span class="tooltiptext">Major league parent organization</span></span>: '
1372
+ body += '<select id="org" onchange="level=\'' + default_org + '\';org=this.value;reload()">'
1373
+ body += '<option value="' + default_org + '">' + default_org + '</option>'
1374
+ for (const [key, value] of Object.entries(AFFILIATE_TEAM_IDS)) {
1375
+ body += '<option value="' + key + '"'
1376
+ if ( org == key ) body += ' selected'
1377
+ body += '>' + key + '</option> '
1378
+ }
1379
+ body += '</select></p>' + "\n"
1303
1380
 
1304
1381
  body += '<p><span class="tooltip">Media Type<span class="tooltiptext">Video is TV broadcasts, Audio is English radio, and Spanish is Spanish radio (not available for all games).</span></span>: '
1305
1382
  for (var i = 0; i < VALID_MEDIA_TYPES.length; i++) {
@@ -1393,7 +1470,7 @@ app.get('/', async function(req, res) {
1393
1470
 
1394
1471
  let blackouts = {}
1395
1472
 
1396
- if ( mediaType == 'MLBTV' ) {
1473
+ if ( (mediaType == 'MLBTV') && level_ids.startsWith('1,') ) {
1397
1474
  if ( (gameDate >= today) && cache_data.dates && cache_data.dates[0] && cache_data.dates[0].games && (cache_data.dates[0].games.length > 0) ) {
1398
1475
  blackouts = await session.get_blackout_games(cache_data.dates[0].games, true)
1399
1476
  }
@@ -1401,6 +1478,14 @@ app.get('/', async function(req, res) {
1401
1478
  let currentDate = new Date()
1402
1479
 
1403
1480
  // Big Inning
1481
+ var big_inning
1482
+ if ( cache_data.dates && cache_data.dates[0] && (cache_data.dates[0].date >= today) && cache_data.dates[0].games && (cache_data.dates[0].games.length > 1) && cache_data.dates[0].games[0] && (cache_data.dates[0].games[0].seriesDescription == 'Regular Season') ) {
1483
+ // Scraped Big Inning schedule
1484
+ big_inning = await session.getBigInningSchedule(gameDate)
1485
+
1486
+ // Generated Big Inning schedule (disabled)
1487
+ //big_inning = await session.generateBigInningSchedule(gameDate)
1488
+ }
1404
1489
  if ( big_inning && big_inning.start ) {
1405
1490
  body += '<tr><td>' + new Date(big_inning.start).toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }) + ' - ' + new Date(big_inning.end).toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }) + '</td><td>'
1406
1491
  let compareStart = new Date(big_inning.start)
@@ -1464,11 +1549,11 @@ app.get('/', async function(req, res) {
1464
1549
 
1465
1550
  let awayteam = cache_data.dates[0].games[j].teams['away'].team.abbreviation
1466
1551
  if ( cache_data.dates[0].games[j].teams['away'].team.sport.name != 'Major League Baseball' ) {
1467
- awayteam = cache_data.dates[0].games[j].teams['away'].team.teamName
1552
+ awayteam = cache_data.dates[0].games[j].teams['away'].team.shortName + ' (' + session.getParent(cache_data.dates[0].games[j].teams['away'].team.parentOrgName) + ')'
1468
1553
  }
1469
1554
  let hometeam = cache_data.dates[0].games[j].teams['home'].team.abbreviation
1470
1555
  if ( cache_data.dates[0].games[j].teams['home'].team.sport.name != 'Major League Baseball' ) {
1471
- awayteam = cache_data.dates[0].games[j].teams['home'].team.teamName
1556
+ hometeam = cache_data.dates[0].games[j].teams['home'].team.shortName + ' (' + session.getParent(cache_data.dates[0].games[j].teams['home'].team.parentOrgName) + ')'
1472
1557
  }
1473
1558
 
1474
1559
  let teams = awayteam + " @ " + hometeam
@@ -1587,13 +1672,23 @@ app.get('/', async function(req, res) {
1587
1672
  if ( (cache_data.dates[0].games[j].teams['away'].probablePitcher && cache_data.dates[0].games[j].teams['away'].probablePitcher.fullName) || (cache_data.dates[0].games[j].teams['home'].probablePitcher && cache_data.dates[0].games[j].teams['home'].probablePitcher.fullName) ) {
1588
1673
  pitchers = "<br/>"
1589
1674
  if ( cache_data.dates[0].games[j].teams['away'].probablePitcher && cache_data.dates[0].games[j].teams['away'].probablePitcher.fullName ) {
1590
- pitchers += getLastName(cache_data.dates[0].games[j].teams['away'].probablePitcher.fullName) + '</a>'
1675
+ if ( cache_data.dates[0].games[j].teams['away'].team.sport.name != 'Major League Baseball' ) {
1676
+ pitchers += cache_data.dates[0].games[j].teams['away'].probablePitcher.fullName
1677
+ } else {
1678
+ pitchers += getLastName(cache_data.dates[0].games[j].teams['away'].probablePitcher.fullName)
1679
+ }
1680
+ pitchers += '</a>'
1591
1681
  } else {
1592
1682
  pitchers += 'TBD'
1593
1683
  }
1594
1684
  pitchers += ' vs '
1595
1685
  if ( cache_data.dates[0].games[j].teams['home'].probablePitcher && cache_data.dates[0].games[j].teams['home'].probablePitcher.fullName ) {
1596
- pitchers += getLastName(cache_data.dates[0].games[j].teams['home'].probablePitcher.fullName) + '</a>'
1686
+ if ( cache_data.dates[0].games[j].teams['home'].team.sport.name != 'Major League Baseball' ) {
1687
+ pitchers += cache_data.dates[0].games[j].teams['home'].probablePitcher.fullName
1688
+ } else {
1689
+ pitchers += getLastName(cache_data.dates[0].games[j].teams['home'].probablePitcher.fullName)
1690
+ }
1691
+ pitchers += '</a>'
1597
1692
  } else {
1598
1693
  pitchers += 'TBD'
1599
1694
  }
@@ -1617,32 +1712,51 @@ app.get('/', async function(req, res) {
1617
1712
  }
1618
1713
  body += '><td>' + description + teams + pitchers + state + '</td>'
1619
1714
 
1620
- // Check if Winter League game first
1621
- if ( cache_data.dates[0].games[j].teams['home'].team.sport.name == 'Winter Leagues' ) {
1715
+ // Check if Winter League / MiLB game first
1716
+ if ( cache_data.dates[0].games[j].teams['home'].team.sport.id != '1' ) {
1622
1717
  body += "<td>"
1623
1718
  if ( cache_data.dates[0].games[j].broadcasts ) {
1624
1719
  for (var k = 0; k < cache_data.dates[0].games[j].broadcasts.length; k++) {
1625
- if ( (mediaType == 'MLBTV') && (cache_data.dates[0].games[j].broadcasts[k].name == 'MLB.TV') ) {
1720
+ if ( mediaType == 'MLBTV' ) {
1626
1721
  // Check if game should be live
1627
- let currentTime = new Date()
1628
- let startTime = new Date(cache_data.dates[0].games[j].gameDate)
1629
- startTime.setMinutes(startTime.getMinutes()-30)
1630
- let endTime = new Date(cache_data.dates[0].games[j].gameDate)
1631
- endTime.setHours(endTime.getHours()+4)
1632
- if ( (currentTime >= startTime) && (currentTime < endTime) ) {
1633
- let querystring
1634
- querystring = '?event=' + cache_data.dates[0].games[j].teams['home'].team.clubName.toLowerCase()
1635
- let multiviewquerystring = querystring + '&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION + '&audio_track=' + DEFAULT_MULTIVIEW_AUDIO_TRACK
1636
- if ( resolution != VALID_RESOLUTIONS[0] ) querystring += '&resolution=' + resolution
1637
- if ( audio_track != VALID_AUDIO_TRACKS[0] ) querystring += '&audio_track=' + encodeURIComponent(audio_track)
1638
- querystring += content_protect_b
1639
- multiviewquerystring += content_protect_b
1640
- body += '<a href="' + thislink + querystring + '">' + cache_data.dates[0].games[j].broadcasts[k].name + '</a>'
1641
- body += '<input type="checkbox" value="' + server + '/stream.m3u8' + multiviewquerystring + '" onclick="addmultiview(this)">'
1642
- } else {
1643
- body += cache_data.dates[0].games[j].broadcasts[k].name
1722
+ if ( (cache_data.dates[0].games[j].status.detailedState != 'Postponed') && (cache_data.dates[0].games[j].status.detailedState != 'Cancelled') ) {
1723
+ // Check if game should be live
1724
+ let currentTime = new Date()
1725
+ let startTime = new Date(cache_data.dates[0].games[j].gameDate)
1726
+ startTime.setMinutes(startTime.getMinutes()-30)
1727
+ if ( (currentTime >= startTime) ) {
1728
+ let gamePk = cache_data.dates[0].games[j].gamePk
1729
+ let querystring
1730
+ querystring = '?gamePk=' + gamePk
1731
+ let multiviewquerystring = querystring + '&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION
1732
+ if ( resolution != VALID_RESOLUTIONS[0] ) querystring += '&resolution=' + resolution
1733
+ if ( linkType == VALID_LINK_TYPES[0] ) {
1734
+ if ( startFrom != VALID_START_FROM[0] ) querystring += '&startFrom=' + startFrom
1735
+ if ( controls != VALID_CONTROLS[0] ) querystring += '&controls=' + controls
1736
+ }
1737
+ if ( resumeStatus == false ) {
1738
+ if ( inning_half != VALID_INNING_HALF[0] ) querystring += '&inning_half=' + inning_half
1739
+ if ( inning_number != VALID_INNING_NUMBER[0] ) querystring += '&inning_number=' + relative_inning
1740
+ if ( skip != VALID_SKIP[0] ) querystring += '&skip=' + skip
1741
+ //if ( skip_adjust != DEFAULT_SKIP_ADJUST ) querystring += '&skip_adjust=' + skip_adjust
1742
+ }
1743
+ if ( pad != VALID_PAD[0] ) querystring += '&pad=' + pad
1744
+ if ( linkType == VALID_LINK_TYPES[1] ) {
1745
+ let endTime = new Date(cache_data.dates[0].games[j].gameDate)
1746
+ endTime.setHours(endTime.getHours()+4)
1747
+ if ( currentTime < endTime ) {
1748
+ if ( force_vod != VALID_FORCE_VOD[0] ) querystring += '&force_vod=' + force_vod
1749
+ }
1750
+ }
1751
+ querystring += content_protect_b
1752
+ multiviewquerystring += content_protect_b
1753
+ body += '<a href="' + thislink + querystring + '">' + cache_data.dates[0].games[j].broadcasts[k].name + '</a>'
1754
+ body += '<input type="checkbox" value="' + server + '/stream.m3u8' + multiviewquerystring + '" onclick="addmultiview(this)">'
1755
+ } else {
1756
+ body += cache_data.dates[0].games[j].broadcasts[k].name
1757
+ }
1758
+ break
1644
1759
  }
1645
- break
1646
1760
  }
1647
1761
  }
1648
1762
  }
@@ -2013,7 +2127,7 @@ app.get('/', async function(req, res) {
2013
2127
  body += '<p><span class="tooltip">Bookmarklets for MLB.com<span class="tooltiptext">If you watch at MLB.com, drag these bookmarklets to your bookmarks toolbar and use them to hide parts of the interface.</span></span>: <a href="javascript:(function(){let x=document.querySelector(\'#mlbtv-stats-panel\');if(x.style.display==\'none\'){x.style.display=\'initial\';}else{x.style.display=\'none\';}})();">Boxscore</a> | <a href="javascript:(function(){let x=document.querySelector(\'.mlbtv-header-container\');if(x.style.display==\'none\'){let y=document.querySelector(\'.mlbtv-players-container\');y.style.display=\'none\';x.style.display=\'initial\';setTimeout(function(){y.style.display=\'initial\';},15);}else{x.style.display=\'none\';}})();">Scoreboard</a> | <a href="javascript:(function(){let x=document.querySelector(\'.mlbtv-container--footer\');if(x.style.display==\'none\'){let y=document.querySelector(\'.mlbtv-players-container\');y.style.display=\'none\';x.style.display=\'initial\';setTimeout(function(){y.style.display=\'initial\';},15);}else{x.style.display=\'none\';}})();">Linescore</a> | <a href="javascript:(function(){let x=document.querySelector(\'#mlbtv-stats-panel\');if(x.style.display==\'none\'){x.style.display=\'initial\';}else{x.style.display=\'none\';}x=document.querySelector(\'.mlbtv-header-container\');if(x.style.display==\'none\'){x.style.display=\'initial\';}else{x.style.display=\'none\';}x=document.querySelector(\'.mlbtv-container--footer\');if(x.style.display==\'none\'){let y=document.querySelector(\'.mlbtv-players-container\');y.style.display=\'none\';x.style.display=\'initial\';setTimeout(function(){y.style.display=\'initial\';},15);}else{x.style.display=\'none\';}})();">All</a></p>' + "\n"
2014
2128
 
2015
2129
  // Print version
2016
- body += '<p class="tinytext">Version ' + version + '</p>' + "\n"
2130
+ body += '<p class="tinytext">Version ' + version + ' (<a href="/clearcache">clear cache</a>)</p>' + "\n"
2017
2131
 
2018
2132
  // Datepicker functions
2019
2133
  body += '<script>var datePicker=document.getElementById("gameDate");function changeDate(e){date=datePicker.value;reload()}function removeDate(e){datePicker.removeEventListener("change",changeDate,false);datePicker.addEventListener("blur",changeDate,false);if(e.keyCode===13){date=datePicker.value;reload()}}datePicker.addEventListener("change",changeDate,false);datePicker.addEventListener("keypress",removeDate,false)</script>' + "\n"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mlbserver",
3
- "version": "2023.03.31",
3
+ "version": "2023.04.04",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
package/session.js CHANGED
@@ -25,6 +25,12 @@ const TODAY_UTC_HOURS = 8 // UTC hours (EST + 4) into tomorrow to still use toda
25
25
 
26
26
  const TEAM_IDS = {'ARI':'109','ATL':'144','BAL':'110','BOS':'111','CHC':'112','CWS':'145','CIN':'113','CLE':'114','COL':'115','DET':'116','HOU':'117','KC':'118','LAA':'108','LAD':'119','MIA':'146','MIL':'158','MIN':'142','NYM':'121','NYY':'147','OAK':'133','PHI':'143','PIT':'134','STL':'138','SD':'135','SF':'137','SEA':'136','TB':'139','TEX':'140','TOR':'141','WSH':'120'}
27
27
 
28
+ // Other country options would be USA, Canada, or other
29
+ const ESPN_SUNDAY_NIGHT_BLACKOUT_COUNTRIES = ["Angola", "Anguilla", "Antigua and Barbuda", "Argentina", "Aruba", "Australia", "Bahamas", "Barbados", "Belize", "Belize", "Benin", "Bermuda", "Bolivia", "Bonaire", "Botswana", "Brazil", "British Virgin Islands", "Burkina Faso", "Burundi", "Cameroon", "Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "Colombia", "Comoros", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Curacao", "Democratic Republic of the Congo", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "El Salvador", "England", "Equatorial Guinea", "Eritrea", "Eswatini", "Ethiopia", "Falkland Islands", "Falkland Islands", "Fiji", "French Guiana", "French Guiana", "French Polynesia", "Gabon", "Ghana", "Grenada", "Guadeloupe", "Guatemala", "Guinea", "Guinea Bissau", "Guyana", "Guyana", "Haiti", "Honduras", "Ireland", "Jamaica", "Kenya", "Kiribati", "Lesotho", "Liberia", "Madagascar", "Malawi", "Mali", "Marshall Islands", "Martinique", "Mayotte", "Mexico", "Micronesia", "Montserrat", "Mozambique", "Namibia", "Netherlands", "New Zealand", "Nicaragua", "Niger", "Nigeria", "Niue", "Northern Ireland", "Palau Islands", "Panama", "Paraguay", "Peru", "Republic of Ireland", "Reunion", "Rwanda", "Saba", "Saint Maarten", "Samoa", "Sao Tome & Principe", "Scotland", "Senegal", "Seychelles", "Sierra Leone", "Solomon Islands", "Somalia", "South Africa", "St. Barthelemy", "St. Eustatius", "St. Kitts and Nevis", "St. Lucia", "St. Martin", "St. Vincent and the Grenadines", "Sudan", "Surinam", "Suriname", "Tahiti", "Tanzania & Zanzibar", "The Gambia", "The Republic of Congo", "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Turks and Caicos Islands", "Tuvalu", "Uganda", "Uruguay", "Venezuela", "Wales", "Zambia", "Zimbabwe"]
30
+
31
+ // First is default level, last should be All (also used as default org)
32
+ const LEVELS = { 'MLB': '1', 'AAA': '11', 'AA': '12', 'A+': '13', 'A': '14', 'All': '1,11,12,13,14' }
33
+
28
34
  // These are the events to ignore, if we're skipping breaks
29
35
  const BREAK_TYPES = ['Game Advisory', 'Pitching Substitution', 'Offensive Substitution', 'Defensive Sub', 'Defensive Switch', 'Runner Placed On Base']
30
36
  // These are the events to keep, in addition to the last event of each at-bat, if we're skipping pitches
@@ -920,7 +926,25 @@ class sessionClass {
920
926
  this.setLinkType('embed')
921
927
  }
922
928
 
923
- // Check if zip code was provided and if it is different from the stored one
929
+ // Check if country was provided and if it is different from the stored one
930
+ if ( argv.country ) {
931
+ let country_arg = argv.country.toString().toUpperCase()
932
+ if ( country_arg == 'TRUE' ) {
933
+ country_arg = ''
934
+ }
935
+ if ( (typeof(this.credentials.country) === 'undefined') || (country_arg != this.credentials.country) ) {
936
+ this.log('updating country and blackout teams')
937
+ this.credentials.country = country_arg
938
+ this.updateBlackoutTeams()
939
+ }
940
+ } else {
941
+ // Default to USA if it doesn't exist or is not specified
942
+ if ( typeof(this.credentials.country) === 'undefined' ) {
943
+ this.credentials.country = 'USA'
944
+ }
945
+ }
946
+
947
+ // Check if USA zip code was provided and if it is different from the stored one
924
948
  if ( argv.zip_code ) {
925
949
  let zip_code_arg = argv.zip_code.toString().toUpperCase()
926
950
  if ( zip_code_arg == 'TRUE' ) {
@@ -931,13 +955,16 @@ class sessionClass {
931
955
  this.credentials.zip_code = zip_code_arg
932
956
  this.updateBlackoutTeams()
933
957
  }
934
- } else {
935
- // Prompt for zip code if it doesn't exist
958
+ } else if ( this.credentials.country == 'USA' ) {
959
+ // Prompt for USA zip code if it doesn't exist
936
960
  if ( typeof(this.credentials.zip_code) === 'undefined' ) {
937
961
  this.debuglog('prompting for zip code')
938
962
  this.credentials.zip_code = readlineSync.question('Enter 5-digit zip code (optional, for USA blackout labels): ').toString()
939
963
  this.updateBlackoutTeams()
940
964
  }
965
+ } else {
966
+ this.credentials.zip_code = '0'
967
+ this.save_credentials()
941
968
  }
942
969
 
943
970
  // Check if fav teams was provided and if they are different from the stored fav teams
@@ -1075,6 +1102,29 @@ class sessionClass {
1075
1102
  return USER_AGENT
1076
1103
  }
1077
1104
 
1105
+ getLevels() {
1106
+ return LEVELS
1107
+ }
1108
+
1109
+ getTeamIds(team_abbr = false) {
1110
+ if ( team_abbr ) {
1111
+ return TEAM_IDS[team_abbr]
1112
+ } else {
1113
+ return Object.values(TEAM_IDS).toString()
1114
+ }
1115
+ }
1116
+
1117
+ // get parent org nickname
1118
+ getParent(parent) {
1119
+ let long_orgs = [ 'Jays', 'Sox' ]
1120
+ let parent_array = parent.split(' ')
1121
+ parent = parent_array[parent_array.length-1]
1122
+ if ( long_orgs.includes(parent) ) {
1123
+ parent = parent_array[parent_array.length-2] + ' ' + parent
1124
+ }
1125
+ return parent
1126
+ }
1127
+
1078
1128
  // the live date is today's date, or if before a specified hour (UTC time), then use yesterday's date
1079
1129
  liveDate(hour = TODAY_UTC_HOURS) {
1080
1130
  let curDate = new Date()
@@ -1951,18 +2001,28 @@ class sessionClass {
1951
2001
  }
1952
2002
 
1953
2003
  // get data for a day, either from cache or an API call
1954
- async getDayData(dateString, team = false) {
2004
+ async getDayData(dateString, team = false, level_ids = LEVELS.MLB, team_ids = '') {
1955
2005
  try {
1956
2006
  let cache_data
1957
2007
  let cache_name = dateString
2008
+ if ( level_ids != '1' ) {
2009
+ cache_name += '.' + level_ids
2010
+ }
2011
+ if ( team_ids != '' ) {
2012
+ cache_name += '.' + team_ids
2013
+ }
1958
2014
  //let data_url = 'https://bdfed.stitch.mlbinfra.com/bdfed/transform-mlb-scoreboard?stitch_env=prod&sortTemplate=2&sportId=1&sportId=17&startDate=' + dateString + '&endDate=' + dateString + '&gameType=E&&gameType=S&&gameType=R&&gameType=F&&gameType=D&&gameType=L&&gameType=W&&gameType=A&language=en&leagueId=104&leagueId=103&leagueId=131&contextTeamId='
1959
- let data_url = 'http://statsapi.mlb.com/api/v1/schedule?sportId=1&startDate=' + dateString + '&endDate=' + dateString + '&hydrate=game(content(media(epg))),probablePitcher,linescore,team,flags,gameInfo'
2015
+ let data_url = 'http://statsapi.mlb.com/api/v1/schedule?sportId=' + level_ids
2016
+ if ( team_ids != '' ) {
2017
+ data_url += '&teamId=' + team_ids
2018
+ }
2019
+ data_url += '&startDate=' + dateString + '&endDate=' + dateString + '&hydrate=broadcasts(all),game(content(media(epg))),probablePitcher,linescore,team,flags,gameInfo'
1960
2020
  if ( team && !team.toUpperCase().startsWith('NATIONAL.') && !team.toUpperCase().startsWith('FREE.') ) {
1961
2021
  this.debuglog('getDayData for team ' + team + ' on date ' + dateString)
1962
2022
  cache_name = team.toUpperCase() + dateString
1963
2023
  data_url = 'http://statsapi.mlb.com/api/v1/schedule?sportId=1&teamId=' + TEAM_IDS[team.toUpperCase()] + '&startDate=' + dateString + '&endDate=' + dateString + '&hydrate=team,game(content(media(epg)))'
1964
2024
  } else {
1965
- this.debuglog('getDayData for date ' + dateString)
2025
+ this.debuglog('getDayData for level(s) ' + level_ids + ' on date ' + dateString)
1966
2026
  }
1967
2027
  let cache_file = path.join(this.CACHE_DIRECTORY, cache_name+'.json')
1968
2028
  let currentDate = new Date()
@@ -3152,13 +3212,21 @@ class sessionClass {
3152
3212
  }
3153
3213
 
3154
3214
  // Get event stream URL
3155
- async getEventStreamURL(eventName) {
3215
+ async getEventStreamURL(eventName, gamePk=false) {
3216
+ if ( gamePk ) {
3217
+ eventName = gamePk
3218
+ }
3156
3219
  this.debuglog('getEventStreamURL for ' + eventName)
3157
3220
  if ( this.cache.media && this.cache.media[eventName] && this.cache.media[eventName].streamURL && this.cache.media[eventName].streamURLExpiry && (Date.parse(this.cache.media[eventName].streamURLExpiry) > new Date()) ) {
3158
3221
  this.log('using cached eventStreamURL')
3159
3222
  return this.cache.media[eventName].streamURL
3160
3223
  } else {
3161
- var playbackURL = await this.getEventURL(eventName)
3224
+ var playbackURL
3225
+ if ( gamePk ) {
3226
+ playbackURL = 'https://dai.tv.milb.com/api/v2/playback-info/games/' + gamePk + '/contents/14862/products/milb-carousel'
3227
+ } else if ( eventName ) {
3228
+ playbackURL = await this.getEventURL(eventName)
3229
+ }
3162
3230
  if ( !playbackURL ) {
3163
3231
  this.debuglog('no active event url')
3164
3232
  } else {
@@ -3204,7 +3272,7 @@ class sessionClass {
3204
3272
  let blackout_teams = []
3205
3273
 
3206
3274
  try {
3207
- if ( /(^\d{5}$)/.test(this.credentials.zip_code) ) {
3275
+ if ( /(^\d{5}$)/.test(this.credentials.zip_code) && (this.credentials.country == 'USA') ) {
3208
3276
  this.log('getBlackoutTeams for zip code ' + this.credentials.zip_code)
3209
3277
  let reqObj = {
3210
3278
  url: 'https://content.mlb.com/data/blackouts/' + this.credentials.zip_code + '.json',
@@ -3224,6 +3292,8 @@ class sessionClass {
3224
3292
  } else {
3225
3293
  this.log('error : invalid json from url ' + reqObj.url)
3226
3294
  }
3295
+ } else if ( this.credentials.country == 'Canada' ) {
3296
+ blackout_teams = ['TOR']
3227
3297
  }
3228
3298
  } catch(e) {
3229
3299
  this.log('getBlackoutTeams error : ' + e.message)
@@ -3266,7 +3336,7 @@ class sessionClass {
3266
3336
  async get_blackout_games(games, calculate_expiries=false) {
3267
3337
  let blackouts = {}
3268
3338
 
3269
- let national_blackout = /(^\d{5}$)/.test(this.credentials.zip_code)
3339
+ let usa_blackout = /(^\d{5}$)/.test(this.credentials.zip_code) && (this.credentials.country == 'USA')
3270
3340
 
3271
3341
  let regional_fox_games_exist
3272
3342
  for (var j = 0; j < games.length; j++) {
@@ -3276,7 +3346,8 @@ class sessionClass {
3276
3346
  if ( games[j].content.media.epg[k].title == 'MLBTV' ) {
3277
3347
  for (var x = 0; x < games[j].content.media.epg[k].items.length; x++) {
3278
3348
  if (games[j].content.media.epg[k].items[x].mediaFeedType == 'NATIONAL') {
3279
- if ( national_blackout && (games[j].content.media.epg[k].items[x].callLetters == 'FOX') && (games[j].seriesDescription == 'Regular Season') ) {
3349
+ // International blackouts according to https://www.mlb.com/live-stream-games/help-center/blackouts-available-games
3350
+ if ( usa_blackout && (games[j].content.media.epg[k].items[x].callLetters == 'FOX') && (games[j].seriesDescription == 'Regular Season') ) {
3280
3351
  if ( !regional_fox_games_exist ) {
3281
3352
  regional_fox_games_exist = await this.check_regional_fox_games(games)
3282
3353
  }
@@ -3284,14 +3355,16 @@ class sessionClass {
3284
3355
  blackouts[game_pk] = { blackout_type:'National' }
3285
3356
  break
3286
3357
  }
3287
- } else {
3288
- if ( (games[j].seriesDescription != 'Spring Training') && (games[j].seriesDescription != 'Regular Season') ) {
3289
- blackouts[game_pk] = { blackout_type:'International' }
3290
- } else if ( national_blackout ) {
3291
- blackouts[game_pk] = { blackout_type:'National' }
3292
- }
3293
- break
3358
+ // Apple TV+ games are blacked out everywhere
3359
+ } else if ( games[j].content.media.epg[k].items[x].callLetters == 'Apple TV+' ) {
3360
+ blackouts[game_pk] = { blackout_type:'Full International' }
3361
+ // ESPN Sunday Night games are blacked out in a list of countries
3362
+ } else if ( (games[j].content.media.epg[k].items[x].callLetters == 'ESPN') && (new Date(games[j].gameDate).getDay() == 0) && ESPN_SUNDAY_NIGHT_BLACKOUT_COUNTRIES.includes(this.credentials.country) ) {
3363
+ blackouts[game_pk] = { blackout_type:'Partial International' }
3364
+ } else if ( usa_blackout ) {
3365
+ blackouts[game_pk] = { blackout_type:'National' }
3294
3366
  }
3367
+ break
3295
3368
  }
3296
3369
 
3297
3370
  let awayteam = games[j].teams['away'].team.abbreviation