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.
- package/README.md +3 -1
- package/index.js +155 -37
- package/package.json +1 -1
- package/session.js +280 -251
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# mlbserver
|
|
2
2
|
|
|
3
|
-
Current version 2022.06.
|
|
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.
|
|
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.
|
|
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.
|
|
1188
|
-
pitchers +=
|
|
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.
|
|
1194
|
-
pitchers +=
|
|
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 +=
|
|
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
|
-
|
|
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 + '
|
|
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 += '<
|
|
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 += '<
|
|
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
|
|
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
|
-
|
|
1560
|
+
if ( !req.query.resolution ) {
|
|
1561
|
+
resolution = 'best'
|
|
1562
|
+
}
|
|
1505
1563
|
|
|
1506
|
-
body += '<p><span class="tooltip">
|
|
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()
|
|
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=
|
|
1530
|
-
['Team live radio', '?team=
|
|
1531
|
-
['Catch-up/condensed', '?team=
|
|
1532
|
-
['Condensed yesterday', '?team=
|
|
1533
|
-
['Same but DH game 2', '?team=
|
|
1534
|
-
['Nat\'l game 1 today', '?team=
|
|
1535
|
-
['
|
|
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 += '• ' + 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
|
-
|
|
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.
|
|
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
|
|