mlbserver 2022.6.9 → 2022.6.14

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 +3 -1
  2. package/index.js +155 -37
  3. package/package.json +1 -1
  4. package/session.js +280 -251
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # mlbserver
2
2
 
3
- Current version 2022.06.09
3
+ Current version 2022.06.14
4
4
 
5
5
  Credit to https://github.com/tonycpsu/streamglob and https://github.com/mafintosh/hls-decryptor
6
6
 
@@ -35,6 +35,8 @@ Advanced command line options:
35
35
  --account_username (email address, default will use stored credentials or prompt user to enter them)
36
36
  --account_password (default will use stored credentials or prompt user to enter them)
37
37
  --zip_code (optional, for USA blackout labels, will prompt if not set or stored)
38
+ --fav_teams (optional, comma-separated list of favorite team abbreviations, will prompt if not set or stored)
39
+ --free (optional, highlights free games)
38
40
  --multiview_port (port for multiview streaming; defaults to 1 more than primary port, or 10000)
39
41
  --multiview_path (where to create the folder for multiview encoded files; defaults to app directory)
40
42
  --ffmpeg_path (path to ffmpeg binary to use for multiview encoding; default downloads a binary using ffmpeg-static)
package/index.js CHANGED
@@ -44,6 +44,9 @@ const SAMPLE_STREAM_URL = 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8'
44
44
 
45
45
  const SECONDS_PER_SEGMENT = 5
46
46
 
47
+ // for favorites: text, then background, based on https://teamcolors.jim-nielsen.com/
48
+ 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']}
49
+
47
50
  // Basic command line arguments, if specified:
48
51
  // --port or -p (primary port to run on; defaults to 9999 if not specified)
49
52
  // --debug or -d (false if not specified)
@@ -56,6 +59,8 @@ const SECONDS_PER_SEGMENT = 5
56
59
  // --account_username (email address, default will use stored credentials or prompt user to enter them)
57
60
  // --account_password (default will use stored credentials or prompt user to enter them)
58
61
  // --zip_code (optional, for USA blackout labels, will prompt if not set or stored)
62
+ // --fav_teams (optional, comma-separated list of favorite team abbreviations, will prompt if not set or stored)
63
+ // --free (optional, free account, highlights free games)
59
64
  // --multiview_port (port for multiview streaming; defaults to 1 more than primary port, or 10000)
60
65
  // --multiview_path (where to create the folder for multiview encoded files; defaults to app directory)
61
66
  // --ffmpeg_path (path to ffmpeg binary to use for multiview encoding; default downloads a binary using ffmpeg-static)
@@ -73,7 +78,7 @@ var argv = minimist(process.argv, {
73
78
  c: 'cache',
74
79
  v: 'version'
75
80
  },
76
- boolean: ['ffmpeg_logging', 'debug', 'logout', 'session', 'cache', 'version'],
81
+ boolean: ['ffmpeg_logging', 'debug', 'logout', 'session', 'cache', 'version', 'free'],
77
82
  string: ['port', 'account_username', 'account_password', 'multiview_port', 'multiview_path', 'ffmpeg_path', 'ffmpeg_encoder', 'page_username', 'page_password', 'content_protect']
78
83
  })
79
84
 
@@ -164,11 +169,14 @@ app.get('/stream.m3u8', async function(req, res) {
164
169
 
165
170
  try {
166
171
  session.log('stream.m3u8 request : ' + req.url)
172
+ if ( req.connection && req.connection.remoteAddress ) session.log('from: ' + req.connection.remoteAddress)
173
+ if ( req.headers && req.headers['user-agent'] ) session.log('using: ' + req.headers['user-agent'])
167
174
 
168
175
  let mediaId
169
176
  let contentId
170
177
  let streamURL
171
178
  let options = {}
179
+ let includeBlackouts = 'false'
172
180
  let urlArray = req.url.split('?')
173
181
  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) ) {
174
182
  // load a sample encrypted HLS stream
@@ -195,6 +203,10 @@ app.get('/stream.m3u8', async function(req, res) {
195
203
  options.pad = Math.floor(Math.random() * (7200 / SECONDS_PER_SEGMENT)) + (3600 / SECONDS_PER_SEGMENT)
196
204
  }
197
205
 
206
+ if (req.query.includeBlackouts) {
207
+ includeBlackouts = req.query.includeBlackouts
208
+ }
209
+
198
210
  if ( req.query.src ) {
199
211
  streamURL = req.query.src
200
212
  } else if ( req.query.highlight_src ) {
@@ -211,7 +223,7 @@ app.get('/stream.m3u8', async function(req, res) {
211
223
  mediaId = await session.getMediaIdFromContentId(contentId)
212
224
  } else if ( req.query.team ) {
213
225
  let mediaType = req.query.mediaType || VALID_MEDIA_TYPES[0]
214
- let mediaInfo = await session.getMediaId(decodeURIComponent(req.query.team), mediaType, req.query.date, req.query.game)
226
+ let mediaInfo = await session.getMediaId(decodeURIComponent(req.query.team), mediaType, req.query.date, req.query.game, includeBlackouts)
215
227
  if ( mediaInfo ) {
216
228
  mediaId = mediaInfo.mediaId
217
229
  contentId = mediaInfo.contentId
@@ -530,6 +542,8 @@ app.get('/playlist', async function(req, res) {
530
542
  if ( ! (await protect(req, res)) ) return
531
543
 
532
544
  session.debuglog('playlist request : ' + req.url)
545
+ if ( req.connection && req.connection.remoteAddress ) session.debuglog('from: ' + req.connection.remoteAddress)
546
+ if ( req.headers && req.headers['user-agent'] ) session.debuglog('using: ' + req.headers['user-agent'])
533
547
 
534
548
  delete req.headers.host
535
549
 
@@ -703,6 +717,8 @@ app.get('/ts', async function(req, res) {
703
717
  if ( ! (await protect(req, res)) ) return
704
718
 
705
719
  session.debuglog('ts request : ' + req.url)
720
+ if ( req.connection && req.connection.remoteAddress ) session.debuglog('from: ' + req.connection.remoteAddress)
721
+ if ( req.headers && req.headers['user-agent'] ) session.debuglog('using: ' + req.headers['user-agent'])
706
722
 
707
723
  delete req.headers.host
708
724
 
@@ -778,12 +794,24 @@ async function protect(req, res) {
778
794
  return true
779
795
  }
780
796
 
797
+ function getLastName(fullName) {
798
+ let indexOfSpace = fullName.indexOf(' ');
799
+
800
+ if (indexOfSpace === -1) {
801
+ return fullName;
802
+ }
803
+
804
+ return fullName.substring(indexOfSpace + 1);
805
+ }
806
+
781
807
  // Server homepage, base URL
782
808
  app.get('/', async function(req, res) {
783
809
  try {
784
810
  if ( ! (await protect(req, res)) ) return
785
811
 
786
- session.debuglog('homepage request : ' + req.url)
812
+ session.log('homepage request : ' + req.url)
813
+ if ( req.connection && req.connection.remoteAddress ) session.log('from: ' + req.connection.remoteAddress)
814
+ if ( req.headers && req.headers['user-agent'] ) session.log('using: ' + req.headers['user-agent'])
787
815
 
788
816
  let server = 'http://' + req.headers.host
789
817
  let multiview_server = server.replace(':' + session.data.port, ':' + session.data.multiviewPort)
@@ -880,7 +908,7 @@ app.get('/', async function(req, res) {
880
908
  content_protect_b = '&content_protect=' + content_protect
881
909
  }
882
910
 
883
- var body = '<!DOCTYPE html><html><head><meta charset="UTF-8"><meta http-equiv="Content-type" content="text/html;charset=UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><title>' + appname + '</title><link rel="icon" href="favicon.svg' + content_protect_a + '"><style type="text/css">input[type=text],input[type=button]{-webkit-appearance:none;-webkit-border-radius:0}body{width:480px;color:lightgray;background-color:black;font-family:Arial,Helvetica,sans-serif;-webkit-text-size-adjust:none}a{color:darkgray}button{color:lightgray;background-color:black}button.default{color:black;background-color:lightgray}table{width:100%;pad}table,th,td{border:1px solid darkgray;border-collapse:collapse}th,td{padding:5px}.tinytext,textarea,input[type="number"]{font-size:.8em}textarea{width:380px}'
911
+ var body = '<!DOCTYPE html><html><head><meta charset="UTF-8"><meta http-equiv="Content-type" content="text/html;charset=UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><title>' + appname + '</title><link rel="icon" href="favicon.svg' + content_protect_a + '"><style type="text/css">input[type=text],input[type=button]{-webkit-appearance:none;-webkit-border-radius:0}body{width:480px;color:lightgray;background-color:black;font-family:Arial,Helvetica,sans-serif;-webkit-text-size-adjust:none}a{color:darkgray}button{color:lightgray;background-color:black}button.default{color:black;background-color:lightgray}table{width:100%;pad}table,th,td{border:1px solid darkgray;border-collapse:collapse}th,td{padding:5px}.tinytext,textarea,input[type="number"]{font-size:.8em}textarea{width:380px}.freegame,.freegame a{color:green}.blackout,.blackout a{text-decoration:line-through}'
884
912
 
885
913
  // Highlights CSS
886
914
  //max-height:calc(100vh-110px);
@@ -992,8 +1020,6 @@ app.get('/', async function(req, res) {
992
1020
  }
993
1021
  body += '</p>' + "\n"
994
1022
 
995
- body += '<p><span class="tooltip tinytext">* indicates a free game<span class="tooltiptext">Free games are available to anyone with an account, no subscription necessary. Blackouts still apply.</span></span></p>' + "\n"
996
-
997
1023
  body += "<table>" + "\n"
998
1024
 
999
1025
  // Rename some parameters before display links
@@ -1045,8 +1071,15 @@ app.get('/', async function(req, res) {
1045
1071
  body += '</td></tr>' + "\n"
1046
1072
  }
1047
1073
 
1074
+ // assume national broadcasts are blacked out if a USA zip code is set
1048
1075
  let national_blackout = /(^\d{5}$)/.test(session.credentials.zip_code)
1049
1076
 
1077
+ // check for simultaneous regular season Fox regional games, which generally aren't blacked out
1078
+ let fox_national_blackout = national_blackout
1079
+ if ( (fox_national_blackout == true) && (mediaType == 'MLBTV') ) {
1080
+ fox_national_blackout = await session.checkFoxBlackout(cache_data.dates[0].games)
1081
+ }
1082
+
1050
1083
  for (var j = 0; j < cache_data.dates[0].games.length; j++) {
1051
1084
  let game_started = false
1052
1085
 
@@ -1182,22 +1215,34 @@ app.get('/', async function(req, res) {
1182
1215
  }
1183
1216
  }
1184
1217
 
1185
- if ( (cache_data.dates[0].games[j].teams['away'].probablePitcher && cache_data.dates[0].games[j].teams['away'].probablePitcher.lastName) || (cache_data.dates[0].games[j].teams['home'].probablePitcher && cache_data.dates[0].games[j].teams['home'].probablePitcher.lastName) ) {
1218
+ 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) ) {
1186
1219
  pitchers = "<br/>"
1187
- if ( cache_data.dates[0].games[j].teams['away'].probablePitcher && cache_data.dates[0].games[j].teams['away'].probablePitcher.lastName ) {
1188
- pitchers += '<a href="https://mlb.com/player/' + cache_data.dates[0].games[j].teams['away'].probablePitcher.nameSlug + '" target="_blank">' + cache_data.dates[0].games[j].teams['away'].probablePitcher.lastName + '</a>'
1220
+ if ( cache_data.dates[0].games[j].teams['away'].probablePitcher && cache_data.dates[0].games[j].teams['away'].probablePitcher.fullName ) {
1221
+ pitchers += getLastName(cache_data.dates[0].games[j].teams['away'].probablePitcher.fullName) + '</a>'
1189
1222
  } else {
1190
1223
  pitchers += 'TBD'
1191
1224
  }
1192
1225
  pitchers += ' vs '
1193
- if ( cache_data.dates[0].games[j].teams['home'].probablePitcher && cache_data.dates[0].games[j].teams['home'].probablePitcher.lastName ) {
1194
- pitchers += '<a href="https://mlb.com/player/' + cache_data.dates[0].games[j].teams['home'].probablePitcher.nameSlug + '" target="_blank">' +cache_data.dates[0].games[j].teams['home'].probablePitcher.lastName + '</a>'
1226
+ if ( cache_data.dates[0].games[j].teams['home'].probablePitcher && cache_data.dates[0].games[j].teams['home'].probablePitcher.fullName ) {
1227
+ pitchers += getLastName(cache_data.dates[0].games[j].teams['home'].probablePitcher.fullName) + '</a>'
1195
1228
  } else {
1196
1229
  pitchers += 'TBD'
1197
1230
  }
1198
1231
  }
1199
1232
 
1200
- body += "<tr><td>" + teams + pitchers + state + "</td>"
1233
+ body += '<tr'
1234
+ let fav_style = ''
1235
+ if ( argv.free && cache_data.dates[0].games[j].content && cache_data.dates[0].games[j].content.media && cache_data.dates[0].games[j].content.media.freeGame ) {
1236
+ body += ' class="freegame"'
1237
+ } else if ( session.credentials.fav_teams.includes(cache_data.dates[0].games[j].teams['away'].team.abbreviation) || session.credentials.fav_teams.includes(cache_data.dates[0].games[j].teams['home'].team.abbreviation) ) {
1238
+ let fav_team = cache_data.dates[0].games[j].teams['away'].team.abbreviation
1239
+ if ( session.credentials.fav_teams.includes(cache_data.dates[0].games[j].teams['home'].team.abbreviation) ) {
1240
+ fav_team = cache_data.dates[0].games[j].teams['home'].team.abbreviation
1241
+ }
1242
+ fav_style = ' style="color:#' + TEAM_COLORS[fav_team][0] + ';background:#' + TEAM_COLORS[fav_team][1] + ';"'
1243
+ body += fav_style
1244
+ }
1245
+ body += '><td>' + teams + pitchers + state + '</td>'
1201
1246
 
1202
1247
  // Check if Winter League game first
1203
1248
  if ( cache_data.dates[0].games[j].teams['home'].team.sport.name == 'Winter Leagues' ) {
@@ -1257,11 +1302,9 @@ app.get('/', async function(req, res) {
1257
1302
  }
1258
1303
  }
1259
1304
  let station = cache_data.dates[0].games[j].content.media.epg[k].items[x].callLetters
1260
- if ( cache_data.dates[0].games[j].content.media.freeGame ) {
1261
- station += '*'
1262
- }
1305
+
1263
1306
  // estimate blackout expiration, if necessary
1264
- if ( !blackout_type && (mediaType == 'MLBTV') && (gameDate >= today) && ((national_blackout && (teamabbr == 'NATIONAL')) || ((cache_data.dates[0].games[j].seriesDescription != 'Spring Training') && (session.credentials.blackout_teams.includes(awayteam) || session.credentials.blackout_teams.includes(hometeam)))) ) {
1307
+ if ( !blackout_type && (mediaType == 'MLBTV') && (gameDate >= today) && ((national_blackout && (teamabbr == 'NATIONAL') && ((station != 'FOX') || (fox_national_blackout == true))) || ((teamabbr != 'NATIONAL') && (cache_data.dates[0].games[j].seriesDescription != 'Spring Training') && (session.credentials.blackout_teams.includes(awayteam) || session.credentials.blackout_teams.includes(hometeam)))) ) {
1265
1308
  if ( national_blackout && (teamabbr == 'NATIONAL') ) {
1266
1309
  blackout_type = 'National'
1267
1310
  } else {
@@ -1296,11 +1339,13 @@ app.get('/', async function(req, res) {
1296
1339
 
1297
1340
  // display blackout tooltip, if necessary
1298
1341
  if ( blackout_type != 'None' ) {
1299
- body += '<span class="tooltip">' + teamabbr + '<span class="tooltiptext">' + blackout_type + ' video blackout until approx. 90 min. after the game'
1342
+ body += '<span class="tooltip"><span class="blackout">' + teamabbr + '</span><span class="tooltiptext">' + blackout_type + ' video blackout until approx. 90 min. after the game'
1300
1343
  if ( blackout_time ) {
1301
1344
  body += ' (~' + blackout_time.toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }) + ')'
1302
1345
  }
1303
1346
  body += '</span></span>'
1347
+ } else if ( (station == 'FOX') && (fox_national_blackout == false) ) {
1348
+ body += '<span class="tooltip">' + teamabbr + '<span class="tooltiptext">Regional FOX game</span></span>'
1304
1349
  } else {
1305
1350
  body += teamabbr
1306
1351
  }
@@ -1314,7 +1359,7 @@ app.get('/', async function(req, res) {
1314
1359
  }
1315
1360
  let mediaId = cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaId
1316
1361
  if ( (mediaType == 'MLBTV') && (gameDate == today) && session.cache.media && session.cache.media[mediaId] && session.cache.media[mediaId].blackout && session.cache.media[mediaId].blackoutExpiry && (new Date(session.cache.media[mediaId].blackoutExpiry) > new Date()) ) {
1317
- body += '<s>' + station + '</s>'
1362
+ body += '<span class="blackout">' + station + '</span>'
1318
1363
  } else {
1319
1364
  let querystring
1320
1365
  querystring = '?mediaId=' + mediaId
@@ -1344,9 +1389,9 @@ app.get('/', async function(req, res) {
1344
1389
  }
1345
1390
  querystring += content_protect_b
1346
1391
  multiviewquerystring += content_protect_b
1347
- stationlink = '<a href="' + thislink + querystring + '">' + station + '</a>'
1392
+ stationlink = '<a' + fav_style + ' href="' + thislink + querystring + '">' + station + '</a>'
1348
1393
  if ( (mediaType == 'MLBTV') && (gameDate >= today) && ((national_blackout && (teamabbr == 'NATIONAL')) || ((cache_data.dates[0].games[j].seriesDescription != 'Spring Training') && (session.credentials.blackout_teams.includes(awayteam) || session.credentials.blackout_teams.includes(hometeam)))) ) {
1349
- body += '<s>' + stationlink + '</s>'
1394
+ body += '<span class="blackout">' + stationlink + '</span>'
1350
1395
  } else {
1351
1396
  body += stationlink
1352
1397
  }
@@ -1383,7 +1428,7 @@ app.get('/', async function(req, res) {
1383
1428
  body += ', '
1384
1429
  }
1385
1430
  } else {
1386
- if ( (mediaType == 'MLBTV') && (gameDate >= today) && ((national_blackout && (teamabbr == 'NATIONAL')) || ((cache_data.dates[0].games[j].seriesDescription != 'Spring Training') && (session.credentials.blackout_teams.includes(awayteam) || session.credentials.blackout_teams.includes(hometeam)))) ) {
1431
+ if ( (mediaType == 'MLBTV') && (gameDate >= today) && ((national_blackout && (teamabbr == 'NATIONAL') && ((cache_data.dates[0].games[j].content.media.epg[k].items[x].callLetters != 'FOX') || (fox_national_blackout == true))) || ((teamabbr != 'NATIONAL') && (cache_data.dates[0].games[j].seriesDescription != 'Spring Training') && (session.credentials.blackout_teams.includes(awayteam) || session.credentials.blackout_teams.includes(hometeam)))) ) {
1387
1432
  body += '<s>' + station + '</s>'
1388
1433
  } else {
1389
1434
  body += station
@@ -1392,7 +1437,7 @@ app.get('/', async function(req, res) {
1392
1437
  }
1393
1438
  // add YouTube link where available
1394
1439
  if ( (mediaType == 'MLBTV') && cache_data.dates[0].games[j].content.media.epg[k].items[x].youtube && cache_data.dates[0].games[j].content.media.epg[k].items[x].youtube.videoId ) {
1395
- body += '<a href="https://www.youtube.com/watch?v=' + cache_data.dates[0].games[j].content.media.epg[k].items[x].youtube.videoId + '" target="_blank">' + station + '</a>'
1440
+ body += '<a' + fav_style + ' href="https://www.youtube.com/watch?v=' + cache_data.dates[0].games[j].content.media.epg[k].items[x].youtube.videoId + '" target="_blank">' + station + '</a>'
1396
1441
  }
1397
1442
  }
1398
1443
  }
@@ -1405,14 +1450,25 @@ app.get('/', async function(req, res) {
1405
1450
  }
1406
1451
  //if ( (mediaType == 'MLBTV') && (game_started) && cache_data.dates[0].games[j].content && cache_data.dates[0].games[j].content.summary && cache_data.dates[0].games[j].content.summary.hasHighlightsVideo ) {
1407
1452
  if ( (mediaType == 'MLBTV') && (game_started) ) {
1408
- body += '<br/><a href="javascript:showhighlights(\'' + cache_data.dates[0].games[j].gamePk + '\',\'' + gameDate + '\')">Highlights</a>'
1453
+ body += '<br/><a' + fav_style + ' href="javascript:showhighlights(\'' + cache_data.dates[0].games[j].gamePk + '\',\'' + gameDate + '\')">Highlights</a>'
1409
1454
  }
1410
1455
  }
1411
1456
  body += "</td>"
1412
1457
  body += "</tr>" + "\n"
1413
1458
  }
1414
1459
  }
1415
- body += "</table><br/>" + "\n"
1460
+ body += "</table>" + "\n"
1461
+
1462
+ if ( national_blackout && (gameDate >= today) ) {
1463
+ body += '<span class="tooltip tinytext"><span class="blackout">strikethrough</span> indicates a live blackout<span class="tooltiptext">USA only. Blackout labels are purely informative and based on the USA zip code, if any, you provided when starting the server. The actual blackouts are based on your location, not on the provided zip code, so providing a different zip code will not enable you to see different games. Tap or hover over the team abbreviation to see an estimate of when the blackout will be lifted (~90 minutes after the game ends).</span></span>' + "\n"
1464
+ if ( argv.free ) {
1465
+ body += '<br/>'
1466
+ }
1467
+ }
1468
+
1469
+ if ( argv.free ) {
1470
+ body += '<span class="freegame tooltip tinytext">green indicates a free game<span class="tooltiptext">Free games are available to anyone with an account, no subscription necessary. Blackouts still apply.</span></span>' + "\n"
1471
+ }
1416
1472
 
1417
1473
  // Rename parameter back before displaying further links
1418
1474
  if ( mediaType == 'MLBTV' ) {
@@ -1501,16 +1557,26 @@ app.get('/', async function(req, res) {
1501
1557
  }
1502
1558
  body += ' <span class="tinytext">(ON plays sample for all stream requests)</span></p>' + "\n"
1503
1559
 
1504
- body += '<p>All: <a href="/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + content_protect_b + '">channels.m3u</a> and <a href="/guide.xml?mediaType=' + mediaType + content_protect_b + '">guide.xml</a></p>' + "\n"
1560
+ if ( !req.query.resolution ) {
1561
+ resolution = 'best'
1562
+ }
1505
1563
 
1506
- body += '<p><span class="tooltip">By team<span class="tooltiptext">Including a team will include that team\'s broadcasts, not their opponent\'s broadcasts or national TV broadcasts.</span></span>: <a href="/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=ari,atl' + content_protect_b + '">channels.m3u</a> and <a href="/guide.xml?mediaType=' + mediaType + '&includeTeams=ari,atl' + content_protect_b + '">guide.xml</a></p>' + "\n"
1564
+ body += '<p><span class="tooltip">All<span class="tooltiptext">Will include all live broadcasts. If a zip code has been provided, channels/games subject to blackout will be omitted by default. See below for an additional option to override that.</span></span>: <a href="/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + content_protect_b + '">channels.m3u</a> and <a href="/guide.xml?mediaType=' + mediaType + content_protect_b + '">guide.xml</a></p>' + "\n"
1565
+
1566
+ let include_teams = 'ari,national'
1567
+ if ( session.credentials.fav_teams.length > 0 ) {
1568
+ include_teams = session.credentials.fav_teams.toString()
1569
+ }
1570
+ body += '<p><span class="tooltip">By team<span class="tooltiptext">Including a team will include all of its broadcasts, or if that team is not broadcasting the game, it will include the national broadcast or opponent\'s broadcast if available. If a zip code has been provided, channels/games subject to blackout will be omitted by default. See below for an additional option to override that.</span></span>: <a href="/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=' + include_teams + content_protect_b + '">channels.m3u</a> and <a href="/guide.xml?mediaType=' + mediaType + '&includeTeams=' + include_teams + content_protect_b + '">guide.xml</a></p>' + "\n"
1571
+
1572
+ body += '<p><span class="tooltip">Include blackouts<span class="tooltiptext">An optional parameter added to the URL will include channels/games subject to blackout (although you may not be able to play those games).</span></span>: <a href="/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=' + include_teams + '&includeBlackouts=true' + content_protect_b + '">channels.m3u</a> and <a href="/guide.xml?mediaType=' + mediaType + '&includeTeams=' + include_teams + '&includeBlackouts=true' + content_protect_b + '">guide.xml</a></p>' + "\n"
1507
1573
 
1508
1574
  let exclude_teams = 'ari,national'
1509
1575
  if ( session.credentials.blackout_teams.length > 0 ) {
1510
- exclude_teams = session.credentials.blackout_teams.toString().toLowerCase()
1576
+ exclude_teams = session.credentials.blackout_teams.toString()
1511
1577
  exclude_teams += ',national'
1512
1578
  }
1513
- body += '<p><span class="tooltip">Exclude a team + national<span class="tooltiptext">This is useful for excluding games you may be blacked out from. Excluding a team will exclude every game involving that team. National refers to <a href="https://www.mlb.com/live-stream-games/national-blackout">USA national TV broadcasts</a>.</span></span>: <a href="/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&excludeTeams=' + exclude_teams + content_protect_b + '">channels.m3u</a> and <a href="/guide.xml?mediaType=' + mediaType + '&excludeTeams=' + exclude_teams + content_protect_b + '">guide.xml</a></p>' + "\n"
1579
+ body += '<p><span class="tooltip">Exclude a team + national<span class="tooltiptext">This is useful for excluding games you may be blacked out from, even if you have not provided a zip code. Excluding a team will exclude every game involving that team. National refers to <a href="https://www.mlb.com/live-stream-games/national-blackout">USA national TV broadcasts</a>.</span></span>: <a href="/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&excludeTeams=' + exclude_teams + content_protect_b + '">channels.m3u</a> and <a href="/guide.xml?mediaType=' + mediaType + '&excludeTeams=' + exclude_teams + content_protect_b + '">guide.xml</a></p>' + "\n"
1514
1580
 
1515
1581
  body += '<p><span class="tooltip">Include (or exclude) LIDOM<span class="tooltiptext">Dominican Winter League, aka Liga de Beisbol Dominicano. Live stream only, does not support starting from the beginning or certain innings, skip options, etc.</span></span>: <a href="/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=lidom' + content_protect_b + '">channels.m3u</a> and <a href="/guide.xml?mediaType=' + mediaType + '&includeTeams=lidom' + content_protect_b + '">guide.xml</a></p>' + "\n"
1516
1582
 
@@ -1518,6 +1584,10 @@ app.get('/', async function(req, res) {
1518
1584
 
1519
1585
  body += '<p><span class="tooltip">Include (or exclude) Multiview<span class="tooltiptext">Requires starting and stopping the multiview stream from the web interface.</span></span>: <a href="/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=multiview' + content_protect_b + '">channels.m3u</a> and <a href="/guide.xml?mediaType=' + mediaType + '&includeTeams=multiview' + content_protect_b + '">guide.xml</a></p>' + "\n"
1520
1586
 
1587
+ if ( argv.free ) {
1588
+ body += '<p><span class="tooltip">Free games only<span class="tooltiptext">Only includes games marked as free. Blackouts still apply. If a zip code has been provided, channels/games subject to blackout will be omitted by default.</span></span>: <a href="/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=free' + content_protect_b + '">channels.m3u</a> and <a href="/guide.xml?mediaType=' + mediaType + '&includeTeams=free' + content_protect_b + '">guide.xml</a></p>' + "\n"
1589
+ }
1590
+
1521
1591
  body += '</td></tr></table><br/>' + "\n"
1522
1592
 
1523
1593
  body += '<table><tr><td>' + "\n"
@@ -1525,16 +1595,30 @@ app.get('/', async function(req, res) {
1525
1595
  body += '<p>' + "\n"
1526
1596
  let example_types = [ ['embed.html', 'Embed'], ['stream.m3u8', 'Stream'], ['chromecast.html', 'Chromecast'], ['kodi.strm', 'Kodi'] ]
1527
1597
 
1598
+ let example_team = 'ari'
1599
+ if ( session.credentials.fav_teams.length > 0 ) {
1600
+ example_team = session.credentials.fav_teams[0]
1601
+ }
1602
+
1528
1603
  let examples = [
1529
- ['Team live video', '?team=ari&resolution=720p60'],
1530
- ['Team live radio', '?team=ari&mediaType=Audio'],
1531
- ['Catch-up/condensed', '?team=ari&resolution=720p60&skip=pitches&date=today'],
1532
- ['Condensed yesterday', '?team=ari&resolution=720p60&skip=pitches&date=yesterday'],
1533
- ['Same but DH game 2', '?team=ari&resolution=720p60&skip=pitches&date=yesterday&game=2'],
1534
- ['Nat\'l game 1 today', '?team=national.1&resolution=720p60&date=today'],
1535
- ['Nat\'l game 2 yesterday', '?team=national.2&resolution=720p60&date=yesterday']
1604
+ ['Team live video', '?team=' + example_team + '&resolution=best'],
1605
+ ['Team live radio', '?team=' + example_team + '&mediaType=Audio'],
1606
+ ['Catch-up/condensed', '?team=' + example_team + '&resolution=best&skip=pitches&date=today'],
1607
+ ['Condensed yesterday', '?team=' + example_team + '&resolution=best&skip=pitches&date=yesterday'],
1608
+ ['Same but DH game 2', '?team=' + example_team + '&resolution=best&skip=pitches&date=yesterday&game=2'],
1609
+ ['Nat\'l game 1 today', '?team=NATIONAL.1&resolution=best&date=today'],
1610
+ ['Same but incl. blackouts', '?team=NATIONAL.1&resolution=best&includeBlackouts=true'],
1611
+ ['Nat\'l game 2 yesterday', '?team=NATIONAL.2&resolution=best&date=yesterday']
1536
1612
  ]
1537
1613
 
1614
+ if ( argv.free ) {
1615
+ examples = examples.concat([
1616
+ ['Free game 1 today', '?team=FREE.1&resolution=best&date=today'],
1617
+ ['Same but incl. blackouts', '?team=FREE.1&resolution=best&includeBlackouts=true'],
1618
+ ['Free game 2 yesterday', '?team=FREE.2&resolution=best&date=yesterday']
1619
+ ])
1620
+ }
1621
+
1538
1622
  for (var i=0; i<examples.length; i++) {
1539
1623
  body += '&bull; ' + examples[i][0] + ': '
1540
1624
  for (var j=0; j<example_types.length; j++) {
@@ -1588,6 +1672,8 @@ app.get('/', async function(req, res) {
1588
1672
  // Listen for OPTIONS requests and respond with CORS headers
1589
1673
  app.options('*', function(req, res) {
1590
1674
  session.debuglog('OPTIONS request : ' + req.url)
1675
+ if ( req.connection && req.connection.remoteAddress ) session.debuglog('from: ' + req.connection.remoteAddress)
1676
+ if ( req.headers && req.headers['user-agent'] ) session.debuglog('using: ' + req.headers['user-agent'])
1591
1677
  var cors_headers = {
1592
1678
  'access-control-allow-headers': 'Origin, X-Requested-With, Content-Type, accessToken, Authorization, Accept, Range',
1593
1679
  'access-control-allow-origin': '*',
@@ -1604,6 +1690,8 @@ app.get('/live-stream-games*', async function(req, res) {
1604
1690
  if ( ! (await protect(req, res)) ) return
1605
1691
 
1606
1692
  session.debuglog('schedule request : ' + req.url)
1693
+ if ( req.connection && req.connection.remoteAddress ) session.log('from: ' + req.connection.remoteAddress)
1694
+ if ( req.headers && req.headers['user-agent'] ) session.log('using: ' + req.headers['user-agent'])
1607
1695
 
1608
1696
  // check for a linkType parameter in the url
1609
1697
  let linkType = VALID_LINK_TYPES[0]
@@ -1658,6 +1746,8 @@ app.get('/embed.html', async function(req, res) {
1658
1746
  if ( ! (await protect(req, res)) ) return
1659
1747
 
1660
1748
  session.log('embed.html request : ' + req.url)
1749
+ if ( req.connection && req.connection.remoteAddress ) session.log('from: ' + req.connection.remoteAddress)
1750
+ if ( req.headers && req.headers['user-agent'] ) session.log('using: ' + req.headers['user-agent'])
1661
1751
 
1662
1752
  let startFrom = VALID_START_FROM[0]
1663
1753
  if ( req.query.startFrom ) {
@@ -1708,6 +1798,8 @@ app.get('/advanced.html', async function(req, res) {
1708
1798
  if ( ! (await protect(req, res)) ) return
1709
1799
 
1710
1800
  session.log('advanced embed request : ' + req.url)
1801
+ if ( req.connection && req.connection.remoteAddress ) session.log('from: ' + req.connection.remoteAddress)
1802
+ if ( req.headers && req.headers['user-agent'] ) session.log('using: ' + req.headers['user-agent'])
1711
1803
 
1712
1804
  let server = 'http://' + req.headers.host
1713
1805
 
@@ -1731,6 +1823,8 @@ app.get('/chromecast.html', async function(req, res) {
1731
1823
  if ( ! (await protect(req, res)) ) return
1732
1824
 
1733
1825
  session.log('chromecast request : ' + req.url)
1826
+ if ( req.connection && req.connection.remoteAddress ) session.log('from: ' + req.connection.remoteAddress)
1827
+ if ( req.headers && req.headers['user-agent'] ) session.log('using: ' + req.headers['user-agent'])
1734
1828
 
1735
1829
  let server = 'http://' + req.headers.host
1736
1830
 
@@ -1754,6 +1848,8 @@ app.get('/channels.m3u', async function(req, res) {
1754
1848
  if ( ! (await protect(req, res)) ) return
1755
1849
 
1756
1850
  session.log('channels.m3u request : ' + req.url)
1851
+ if ( req.connection && req.connection.remoteAddress ) session.log('from: ' + req.connection.remoteAddress)
1852
+ if ( req.headers && req.headers['user-agent'] ) session.log('using: ' + req.headers['user-agent'])
1757
1853
 
1758
1854
  let mediaType = VALID_MEDIA_TYPES[0]
1759
1855
  if ( req.query.mediaType ) {
@@ -1786,7 +1882,12 @@ app.get('/channels.m3u', async function(req, res) {
1786
1882
  startingChannelNumber = req.query.startingChannelNumber
1787
1883
  }
1788
1884
 
1789
- var body = await session.getChannels(mediaType, includeTeams, excludeTeams, server, resolution, pipe, startingChannelNumber)
1885
+ let includeBlackouts = 'false'
1886
+ if ( req.query.includeBlackouts ) {
1887
+ includeBlackouts = req.query.includeBlackouts
1888
+ }
1889
+
1890
+ var body = await session.getTVData('channels', mediaType, includeTeams, excludeTeams, server, includeBlackouts, resolution, pipe, startingChannelNumber)
1790
1891
 
1791
1892
  res.writeHead(200, {'Content-Type': 'audio/x-mpegurl'})
1792
1893
  res.end(body)
@@ -1797,6 +1898,8 @@ app.get('/guide.xml', async function(req, res) {
1797
1898
  if ( ! (await protect(req, res)) ) return
1798
1899
 
1799
1900
  session.log('guide.xml request : ' + req.url)
1901
+ if ( req.connection && req.connection.remoteAddress ) session.log('from: ' + req.connection.remoteAddress)
1902
+ if ( req.headers && req.headers['user-agent'] ) session.log('using: ' + req.headers['user-agent'])
1800
1903
 
1801
1904
  let mediaType = VALID_MEDIA_TYPES[0]
1802
1905
  if ( req.query.mediaType ) {
@@ -1812,9 +1915,14 @@ app.get('/guide.xml', async function(req, res) {
1812
1915
  excludeTeams = req.query.excludeTeams.toUpperCase().split(',')
1813
1916
  }
1814
1917
 
1918
+ let includeBlackouts = 'false'
1919
+ if ( req.query.includeBlackouts ) {
1920
+ includeBlackouts = req.query.includeBlackouts
1921
+ }
1922
+
1815
1923
  let server = 'http://' + req.headers.host
1816
1924
 
1817
- var body = await session.getGuide(mediaType, includeTeams, excludeTeams, server)
1925
+ var body = await session.getTVData('guide', mediaType, includeTeams, excludeTeams, server, includeBlackouts)
1818
1926
 
1819
1927
  res.end(body)
1820
1928
  })
@@ -1824,6 +1932,8 @@ app.get('/image.svg', async function(req, res) {
1824
1932
  if ( ! (await protect(req, res)) ) return
1825
1933
 
1826
1934
  session.debuglog('image request : ' + req.url)
1935
+ if ( req.connection && req.connection.remoteAddress ) session.debuglog('from: ' + req.connection.remoteAddress)
1936
+ if ( req.headers && req.headers['user-agent'] ) session.debuglog('using: ' + req.headers['user-agent'])
1827
1937
 
1828
1938
  let teamId = 'MLB'
1829
1939
  if ( req.query.teamId ) {
@@ -1841,6 +1951,8 @@ app.get('/favicon.svg', async function(req, res) {
1841
1951
  if ( ! (await protect(req, res)) ) return
1842
1952
 
1843
1953
  session.debuglog('favicon request : ' + req.url)
1954
+ if ( req.connection && req.connection.remoteAddress ) session.debuglog('from: ' + req.connection.remoteAddress)
1955
+ if ( req.headers && req.headers['user-agent'] ) session.debuglog('using: ' + req.headers['user-agent'])
1844
1956
 
1845
1957
  var body = await session.getImage('MLB')
1846
1958
 
@@ -1854,6 +1966,8 @@ app.get('/highlights', async function(req, res) {
1854
1966
 
1855
1967
  try {
1856
1968
  session.log('highlights request : ' + req.url)
1969
+ if ( req.connection && req.connection.remoteAddress ) session.log('from: ' + req.connection.remoteAddress)
1970
+ if ( req.headers && req.headers['user-agent'] ) session.log('using: ' + req.headers['user-agent'])
1857
1971
 
1858
1972
  let highlightsData = ''
1859
1973
  if ( req.query.gamePk && req.query.gameDate ) {
@@ -1872,6 +1986,8 @@ app.get('/multiview', async function(req, res) {
1872
1986
 
1873
1987
  try {
1874
1988
  session.log('multiview request : ' + req.url)
1989
+ if ( req.connection && req.connection.remoteAddress ) session.log('from: ' + req.connection.remoteAddress)
1990
+ if ( req.headers && req.headers['user-agent'] ) session.log('using: ' + req.headers['user-agent'])
1875
1991
 
1876
1992
  try {
1877
1993
  ffmpeg_command.kill()
@@ -2149,6 +2265,8 @@ app.get('/kodi.strm', async function(req, res) {
2149
2265
 
2150
2266
  try {
2151
2267
  session.log('kodi.strm request : ' + req.url)
2268
+ if ( req.connection && req.connection.remoteAddress ) session.log('from: ' + req.connection.remoteAddress)
2269
+ if ( req.headers && req.headers['user-agent'] ) session.log('using: ' + req.headers['user-agent'])
2152
2270
 
2153
2271
  let server = 'http://' + req.headers.host
2154
2272
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mlbserver",
3
- "version": "2022.06.09",
3
+ "version": "2022.06.14",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",