// ==UserScript==
// @name Twitch-Server-Info
// @namespace Twitch-Server-Info
// @version 0.1.4
// @author Nomo
// @description Check Twitch server location.
// @icon https://raw.githubusercontent.com/nomomo/Twitch-Server-Info/master/images/logo.png
// @supportURL https://github.com/nomomo/Twitch-Server-Info/issues
// @homepageURL https://github.com/nomomo/Twitch-Server-Info/
// @downloadURL https://raw.githubusercontent.com/nomomo/Twitch-Server-Info/master/Twitch-Server-Info.user.js
// @updateURL https://raw.githubusercontent.com/nomomo/Twitch-Server-Info/master/Twitch-Server-Info.user.js
// @include *://*.twitch.tv/*
// @require https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js
// @run-at document-start
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_setClipboard
// @grant unsafeWindow
// @grant GM_registerMenuCommand
// ==/UserScript==
/*jshint multistr: true */
/* global GM_getValue, GM_setValue, GM_registerMenuCommand, GM_setClipboard, unsafeWindow, GM_addStyle, TWITCH_SERVER_INFO_SET_VAL, TWITCH_SERVER_INFO_FIXER */
if (window.TWITCH_SERVER_INFO === undefined) {
(async () => {
unsafeWindow.TWITCH_SERVER_INFO = true;
console.log("[TSI] RUNNING TWITCH SERVER INFO", document.location.href);
////////////////////////////////////////////////////////////////////////////////////
// libs
////////////////////////////////////////////////////////////////////////////////////
(function () {
Date.prototype.format = function (f) {
if (!this.valueOf()) return " ";
var weekName = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
var d = this;
var h = d.getHours() % 12;
return f.replace(/(yyyy|yy|MM|dd|E|hh|mm|ss|a\/p|amp)/gi, function ($1) {
switch ($1) {
case "yyyy":
return d.getFullYear();
case "yy":
return (d.getFullYear() % 1000).zf(2);
case "MM":
return (d.getMonth() + 1).zf(2);
case "dd":
return d.getDate().zf(2);
case "E":
return weekName[d.getDay()];
case "HH":
return d.getHours().zf(2);
case "hh":
return (h ? h : 12).zf(2);
case "mm":
return d.getMinutes().zf(2);
case "ss":
return d.getSeconds().zf(2);
case "a/p":
return d.getHours() < 12 ? "AM" : "PM";
case "amp":
return d.getHours() < 12 ? "AM" : "PM";
default:
return $1;
}
});
};
String.prototype.string = function (len) {
var s = '',
i = 0;
while (i++ < len) {
s += this;
}
return s;
};
String.prototype.zf = function (len) {
return "0".string(len - this.length) + this;
};
Number.prototype.zf = function (len) {
return this.toString().zf(len);
};
})();
// Debug function
var NOMO_DEBUG = function ( /**/ ) {
if (nomo_global.DEBUG) {
var args = arguments,
args_length = args.length,
args_copy = args;
for (var i = args_length; i > 0; i--) {
args[i] = args_copy[i - 1];
}
args[0] = "[TSI] ";
args.length = args_length + 1;
console.log.apply(console, args);
}
};
// Message pop-up
var simple_message = function(msg, $elem){
if($elem === undefined){
$elem = $("body");
}
var prefix = "GM_setting_autosaved";
var positiontext = "left";
$elem.find("."+prefix).animate({bottom:"+=40px"}, {duration:300, queue: false}); // cleqrQueue().dequeue().finish().stop("true","true")
// @keyframes glow {to {text-shadow: 0 0 10px white;box-shadow: 0 0 10px #5cb85c;}}
$("
"+msg+"
")
.appendTo($elem)
.fadeIn("fast")
.animate({opacity:1}, 10000, function(){
$(this).fadeOut("fast").delay(600).remove();
})
.animate({left:"+=30px"}, {duration:300, queue: false});
};
////////////////////////////////////////////////////////////////////////////////////
// Initialize
////////////////////////////////////////////////////////////////////////////////////
const LOGGING_MAX = 1000;
const FIXER_DELAY_MIN = 250;
var SETTIMEOUT_FIXED_FAILED = undefined;
var SETTIMEOUT_FIXER_EGG = undefined;
var FIXER_EGG_COUNT = 0;
var NOMO_getValue = function (name, val) {
return (typeof GM_getValue === "function" ? GM_getValue(name, val) : val);
};
var NOMO_setValue = function (name, val) {
return (typeof GM_setValue === "function" ? GM_setValue(name, val) : val);
};
// Define global variables
var nomo_global = {
"DEBUG": NOMO_getValue("DEBUG", false),
"DEBUG_FETCH": NOMO_getValue("DEBUG_FETCH", false),
"DEBUG_M3U8": NOMO_getValue("DEBUG_M3U8", false),
"LOGGING": NOMO_getValue("LOGGING", false),
"FIXER": NOMO_getValue("FIXER", false),
"FIXER_SERVER": NOMO_getValue("FIXER_SERVER", ["sel"]),
"FIXER_ATTEMPT_MAX": NOMO_getValue("FIXER_ATTEMPT_MAX", 15),
"FIXER_DELAY": NOMO_getValue("FIXER_DELAY", 500),
"SERVER_CHANGE_SHOW": NOMO_getValue("SERVER_CHANGE_SHOW", true),
"prev_server": "",
"prev_server_list": [],
"prev_changed_server_list": [],
"is_squad": false
};
// verify FIXER_SERVER
if(typeof nomo_global.FIXER_SERVER === "string"){
if(nomo_global.FIXER_SERVER.indexOf(",") !== -1){
nomo_global.FIXER_SERVER = nomo_global.FIXER_SERVER.split(",");
}
else{
nomo_global.FIXER_SERVER = [nomo_global.FIXER_SERVER];
}
}
for (var i = 0; i < nomo_global.FIXER_SERVER.length; i++) {
nomo_global.FIXER_SERVER[i] = nomo_global.FIXER_SERVER[i].replace(/("|'|`)/g, '').toLowerCase();
}
////////////////////////////////////////////////////////////////////////////////////
// Console Interface
////////////////////////////////////////////////////////////////////////////////////
// To toggle debug mode, type TWITCH_SERVER_INFO_DEBUG() in console window
unsafeWindow.TWITCH_SERVER_INFO_DEBUG = function () {
nomo_global.DEBUG = !nomo_global.DEBUG;
NOMO_setValue("DEBUG", nomo_global.DEBUG);
return "DEBUG: " + nomo_global.DEBUG;
};
// To toggle LOGGING mode, type TWITCH_SERVER_INFO_LOGGING() in console window
unsafeWindow.TWITCH_SERVER_INFO_LOGGING = function () {
nomo_global.LOGGING = !nomo_global.LOGGING;
NOMO_setValue("LOGGING", nomo_global.LOGGING);
return "LOGGING: " + nomo_global.LOGGING;
};
// To clear log, type TWITCH_SERVER_INFO_CLEARLOG() in console window
unsafeWindow.TWITCH_SERVER_INFO_CLEARLOG = function () {
NOMO_setValue("LOG", []);
return "CLEAR LOG";
};
// To print log, type TWITCH_SERVER_INFO_SHOWLOG() in console window
unsafeWindow.TWITCH_SERVER_INFO_SHOWLOG = function () {
var log_data = NOMO_getValue("LOG", []);
for (var key in log_data) {
log_data[key][0] = new Date(log_data[key][0] * 1000).format("yyyy-MM-dd amp hh:mm:ss");
}
return log_data;
};
unsafeWindow.TWITCH_SERVER_INFO_SET_VAL = function (name, val) {
if (typeof GM_setValue === "function") {
nomo_global[name] = val;
GM_setValue(name, val);
}
return val;
};
unsafeWindow.TWITCH_SERVER_INFO_FIXER = function () {
return TWITCH_SERVER_INFO_SET_VAL("FIXER", !nomo_global.FIXER);
};
var set_log = function(log){
if (!nomo_global.LOGGING){
return;
}
// save unix time time as s unit, without ms
var date_n = Number(new Date());
var date_s = String(date_n).substr(0, String(date_n).length - 3);
// load old log data
var log_data = NOMO_getValue("LOG", []);
var new_log_data = [date_s].concat(log);
log_data.unshift(new_log_data);
if (log_data.length > LOGGING_MAX) {
log_data = log_data.slice(0, LOGGING_MAX);
}
NOMO_setValue("LOG", log_data);
NOMO_DEBUG("Logging Complete", new_log_data);
};
// Manage creation of the settings menu
if (typeof GM_registerMenuCommand === "function") {
GM_registerMenuCommand("Change Notification Setting", function () {
TWITCH_SERVER_INFO_SET_VAL("SERVER_CHANGE_SHOW", !nomo_global.SERVER_CHANGE_SHOW);
if(nomo_global.SERVER_CHANGE_SHOW){
simple_message("Server Change Notification : ON");
}
else {
simple_message("Server Change Notification : OFF");
}
});
}
// Server list:: 2023-11-26 updated
// Reference: https://twitchstatus.com/
var server_list_2 = {
"hkg":"AS: Hong Kong",
"blr":"AS: India, Bangalore",
"maa":"AS: India, Chennai",
"hyd":"AS: India, Hyderabad",
"bom":"AS: India, Mumbai",
"del":"AS: India, New Delhi",
"jkt01":"AS: Indonesia, Cikarang Barat",
"jkt02":"AS: Indonesia, Jakarta",
"osa":"AS: Japan, Osaka",
"tyo":"AS: Japan, Tokyo",
"mnl":"AS: Manila, Philippines",
"sin":"AS: Singapore",
"sel":"AS: South Korea, Seoul",
"tpe":"AS: Taiwan, Taipei",
"bkk":"AS: Thailand, Bangkok",
"vie":"Europe: Austria, Vienna",
"prg":"Europe: Czech Republic, Prague",
"cph":"Europe: Copenhagen, Denmark",
"hel":"Europe: Finland, Helsinki",
"mrs":"Europe: France, Marseille",
"cdg":"Europe: France, Paris",
"ber":"Europe: Germany, Berlin",
"dus":"Europe: Germany, Dusseldorf",
"fra":"Europe: Germany, Frankfurt",
"muc":"Europe: Germany, Munich",
"mil":"Europe: Italy, Milan",
"ams":"Europe: Netherlands, Amsterdam",
"osl":"Europe: Norway, Oslo",
"waw":"Europe: Poland, Warsaw",
"mad":"Europe: Spain, Madrid",
"arn":"Europe: Sweden, Stockholm",
"lhr":"Europe: UK, London",
"ymq":"NA: Canada, Quebec",
"yto":"NA: Canada, Toronto",
"qro":"NA: Mexico, Queretaro",
"syd":"Oceania: AU, Sydney",
"scl":"South America: chile, Santiago",
"for":"South America: Brazil, Fortaleza",
"rio":"South America: Brazil, Rio de Janeiro",
"sao":"South America: Brazil, Sao Paulo",
"bue":"South America: Buenos Aires, Argentina",
"bog":"South America: Colombia, Bogota",
"dfw56":"US Central: Garland, TX", // added 2023-11-26
"dfw":"US Central: Dallas, TX",
"den":"US Central: Denver, CO",
"iah":"US Central: Houston, TX",
"iad":"US East: Ashburn, VA",
"atl":"US East: Atlanta, GA",
"ord":"US East: Chicago, IL",
"mfe":"US East: McAllen, TX", // added 2023-11-26
"mia":"US East: Miami, FL",
"jfk":"US East: New York, NY",
"lax":"US West: Los Angeles, CA",
"pdx":"US West: Portland, OR",
"slc":"Salt Lake City, UT",
"sfo":"US West: San Francisco, CA",
"sjc":"US West: San Jose, CA",
"sea":"Seattle, WA",
"hou":"US Central: Houston, TX", // old
"phx":"US West: Phoenix, AZ", // old
"lis":"DEPRECATED Europe: Portugal, Lisbon",
"lim":"DEPRECATED South America: Lima, Peru",
"mde":"DEPRECATED South America: Medellin, Colombia",
"gig":"DEPRECATED South America: Rio de Janeiro, Brazil",
"gru":"DEPRECATED South America: Sao Paulo, Brazil",
"akamai":"Akamai"
};
// CSS
if (typeof GM_addStyle === "function") {
GM_addStyle( /*css*/ `
div.player-buttons-right #current_server{
bottom:42px;
right:18px;
}
#current_server{
color:#fff;
z-index:10;
position:absolute;
bottom:34px;
right:6px;
text-align:right;
font-size:11px;
${typeof GM_setClipboard === "function" ?
`user-select: text;
cursor: pointer;` : ``}
}
#copied{
background-color:#686878;
border-radius:10px;
padding:1px 7px;
margin-right:7px;
user-select: none;
}
#fixer_loader{
width:100%;
height:100%;
position:absolute;
top:0;
left:0;
background:rgba(0,0,0,0.7);
z-index:10000;
display: flex;
align-items: center;
justify-content: center;
}
#fixer_loader .loader_text{
color:#fff;
margin-top:0px;
font-size:1.5em;
color:#fff;
position: absolute;
top: auto;
left: auto;
}
#fixer_loader .loader_contents,
#fixer_loader .loader_contents:after {
border-radius: 50%;
width: 10em;
height: 10em;
}
#fixer_loader .loader_contents {
margin: 60px auto;
font-size: 10px;
position: relative;
text-indent: -9999em;
border-top: 1.1em solid rgba(255, 255, 255, 0.1);
border-right: 1.1em solid rgba(255, 255, 255, 0.1);
border-bottom: 1.1em solid rgba(255, 255, 255, 0.1);
border-left: 1.1em solid rgba(255, 255, 255, 0.35);
-webkit-transform: translateZ(0);
-ms-transform: translateZ(0);
transform: translateZ(0);
-webkit-animation: load8 0.5s infinite linear;
animation: load8 0.5s infinite linear;
}
@keyframes glow_twitch {to {text-shadow: 0 0 15px white;box-shadow: 0 0 15px #7d5bbe;}}
.tsi_sc {
user-select:none;
cursor:pointer;
padding:5px 10px !important;
background:rgba(0,0,0,0.7);
color:#fff;
border-radius:5px;
animation:glow_twitch .5s 20 alternate;
font-size:13px;z-index:10;position:absolute;
position:absolute;left:50%;
bottom:20px;
transform:translateX(-50%);
}
@-webkit-keyframes load8 {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes load8 {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
.player-tip, .player-button{
z-index:60 !important;
}
.pl-menu, .pl-menu *{
z-index:70 !important;
}
.GM_setting_autosaved.btn{
max-width:100%;
font-size:12px;
white-space:pre-wrap;
user-select:text;
}
@keyframes glow {to {text-shadow: 0 0 10px white;box-shadow: 0 0 10px #5cb85c;}}
.GM_setting_autosaved.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;cursor:pointer;background-image:none;border:1px solid transparent;padding:6px 12px;line-height:1.42857143;border-radius:4px;}
.GM_setting_autosaved.btn:focus,.GM_setting_autosaved.btn:active:focus,.GM_setting_autosaved.btn.active:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}
.GM_setting_autosaved.btn:hover,
.GM_setting_autosaved.btn:focus{color:#fff;text-decoration:none}
.GM_setting_autosaved.btn:active,
.GM_setting_autosaved.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}
.GM_setting_autosaved.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}
`);
}
function createTSILayout(e){
// Get streamer id from url
var i;
var streamer_id = String(document.location.href).match(/twitch\.tv\/(?:.+channel=)?([a-zA-Z0-9-_]+)\/?/); // /twitch\.tv\/(([a-zA-Z0-9-_]+)|.+channel=([a-zA-Z0-9-_]+))\/?/
streamer_id = (streamer_id !== null ? streamer_id.pop() : "");
// check the type: tsi (default)
if (e.data.type === "tsi") {
var msg_arg = e.data.arg;
if (nomo_global.DEBUG_FETCH) {
NOMO_DEBUG('Message received from worker', e.data);
}
// check playlist is master or vod
var is_master_playlist = msg_arg.indexOf('usher.ttvnw.net/api/channel/hls') !== -1;
var is_vod = msg_arg.indexOf('usher.ttvnw.net/vod/') !== -1;
if (is_master_playlist){
set_log(["mp", streamer_id]);
}
// Clears the server display DOM when a .m3u8 file or VOD is played for the first time on a channel
if (is_master_playlist || is_vod) {
// remove old DOM
$("#current_server").remove();
nomo_global.prev_server = "";
nomo_global.prev_server_list = [];
nomo_global.prev_changed_server_list = [];
// Squad streaming is not currently supported
if(/https?:\/\/(?:www\.)?twitch\.tv\/[a-zA-Z0-9-_]+\/squad$/.test(document.location.href)){
nomo_global.is_squad = true;
NOMO_DEBUG("Squad streaming is not currently supported");
}
else{
nomo_global.is_squad = false;
}
}
// First filter if the URL contains /v1/segment/.
if (!nomo_global.is_squad && msg_arg.indexOf("/v1/segment/")) {
// sample 1: https://video-edge-7e9b9c.sea01.abs.hls.ttvnw.net/v1/segment/CrkETpsPvu8J-JU7Uw41bTmckS0cEofD3pr3EKsfyLpEMWZgb5zeNdYkjkVo5KBYM95UtJvXIJsRRMKazzgDmtksZTSZU-f_EkxSuZrsRdGoaCeHqzbT4l-8mb0OOh9FohqMuzmla4eSVEagbddvmI-_vm3fXDRUehf2BtfhApNVXkcsCVhmrgUKXDuP8YWfdTwmQalG1YnIFtbRg3xw9CVKqajbU4FLcgI0sLHpS-bb3OquKpucwfo8paJyXh7XWCsRF_yLIcbv6iSS7i83uVTTHx54NX8V0K5CntIfVWAfYG-xaypl6qKAKKIRbNa-hsRSQ62Kvqltb_mu6LhStkK1F3qmln_e1hCc7ytx7TVAJmK-GeeplfvCGIxI4qnhl3dSTV0RushnljKYgiA3kt_yC-KbqPPMjTcRgyitGwjyxpHweeQJfJqFGJizcpaFMzmdI5gW_CbdXhX4FWq6TCaRjSCgwx_ewXC5Ct6W7QnWaep35BhxdhX1i0-hh7YflMDFKAykfB07m48DWINT_Fn1K98J0tLKL7yaNJedzM2PhF9AeARA5_fSTzBkA_duOY9fmpOpRFN1VpfmHID3tYDY1F_XZJJmiG6rd_UynJfji8ikbhwkgVc0_QWtOerN10ysY3IvUjlIJn0RwFScCNqdUoMKMKPMsOMfaS1oc1vq8YBPCagEgLnBk6A0d6A61x3brmAU2RaiOCEMChn-n80GyEH3AoIoDOW3fj6lOg1M2uKlMb8vPBYpYp8SEPpaz83YuxcS4pbU4mznHkMaDAsKJElIKxXWiGX3zw.ts
// sample 2: https://live003-ttvnw.akamaized.net/v1/segment/CsoBN06NSMlknciQP8zdCmSmxw4Zz7GxFwO-SNrfef7l7Z7x-pDKXl6F0jeUDQSkwEXm61AgNcRXavIb-g9kT8U4XkZaOBhtJa0DxO10EkIFpePqwhq7Vmazjn1E0cZ1vzp_dQmnBirLIYTUssV3NmwNAjA5-pIMqrj4ibG4h8r3xr2lRz-yAyzatBu0d9HgvGWYRvSWVm1I6bZ8ii_sNV1squ9LlvIEJ0uQ1bCvVnP7sMmurlnKvTx-VDwQ8veMH5G1FQbwm9ta8RmpLBIQHt-eAsho321hz6pZk697kBoMVpjw0pSuRgcYKTDb.ts
var current_server = msg_arg.match(/\/\/(?:vid[a-zA-Z0-9-_]+\.)?([a-zA-Z0-9-_.]+)\/v1\/segment\/.+\.ts/);
// If the server name is found.
if (current_server !== null) {
// get server url
current_server = current_server.pop();
const MAX_SERVER_LIST_LENGTH = 5;
const MAX_CHANGED_SERVER_LIST_LENGTH = 3;
// save to server list
nomo_global.prev_server_list.push(current_server);
if (nomo_global.prev_server_list.length > MAX_SERVER_LIST_LENGTH) {
nomo_global.prev_server_list = nomo_global.prev_server_list.slice(nomo_global.prev_server_list.length - MAX_SERVER_LIST_LENGTH);
}
// Check if DOM recreation is required
var refreshDOM = false;
if (nomo_global.prev_server !== current_server) {
if(nomo_global.prev_server_list.length == 0){
NOMO_DEBUG("prev_server_list.length == 0, refreshDOM = false");
refreshDOM = false;
}
else if(nomo_global.prev_server_list.length == 1){
NOMO_DEBUG("prev_server_list.length == 1, refreshDOM = true");
refreshDOM = true;
}
else{
if(nomo_global.DEBUG){
//simple_message(`[TSI] Server Changing.... : ${nomo_global.prev_server_list.join(", ")}`, $("body"));
}
NOMO_DEBUG(`prev_server_list` + nomo_global.prev_server_list.join(", "));
refreshDOM = true;
for(i = 0; i MAX_CHANGED_SERVER_LIST_LENGTH) {
nomo_global.prev_changed_server_list = nomo_global.prev_changed_server_list.slice(nomo_global.prev_changed_server_list.length - MAX_CHANGED_SERVER_LIST_LENGTH);
}
}
// Logging on first connection
if(nomo_global.prev_server === ""){
set_log(["s1", streamer_id, current_server]);
}
var server_change_history = "";
// If this is not the first connection and the server has changed
if(nomo_global.SERVER_CHANGE_SHOW && refreshDOM && nomo_global.prev_server !== ""){
server_change_history = "
" + nomo_global.prev_changed_server_list.join(" → ").toUpperCase();
// logging
NOMO_DEBUG("Update server list", nomo_global.prev_changed_server_list);
set_log(["sc", streamer_id, current_server]);
$("#current_server").remove();
}
// Display the number of connection attempts in FIXER
var fixed_string = "";
if (nomo_global.FIXER) {
if (e.data.fixed.FIXED) {
fixed_string = "(" + e.data.fixed.FIXER_count + "/" + nomo_global.FIXER_ATTEMPT_MAX + ")
";
} else {
fixed_string = "(" + e.data.fixed.FIXER_count + "/" + nomo_global.FIXER_ATTEMPT_MAX + ")
";
}
}
var dom_string = fixed_string + current_server + server_name + server_change_history;
// Create DOM
var $player_menu = $(".player-controls__right-control-group"); // for new twitch player
if ($player_menu.length !== 0) {
$player_menu.css("position","relative");
$(".player-buttons-right").find(".player-tip").css("z-index", 60);
$("#current_server").remove();
$(`${dom_string}`)
.prependTo($player_menu)
.fadeIn(500)
// Copy to clipboard on click if available
.on("click", function () {
if (typeof GM_setClipboard === "function") {
GM_setClipboard((current_server + server_name).replace("
", " "));
$("#copied").remove();
$(`Copied!!`)
.prependTo($(this))
.fadeIn(500)
.animate({
opacity: 1
}, 3000, function () {
$(this).fadeOut(500, function () {
$(this).remove();
});
});
}
// ????
FIXER_EGG_COUNT = FIXER_EGG_COUNT + 1;
clearTimeout(SETTIMEOUT_FIXER_EGG);
SETTIMEOUT_FIXER_EGG = setTimeout(function(){
NOMO_DEBUG("FIXER_EGG_COUNT", FIXER_EGG_COUNT);
if(FIXER_EGG_COUNT === 7){
NOMO_DEBUG("FIXER EGG 7");
TWITCH_SERVER_INFO_FIXER();
$(".FIXER_EGG").remove();
$(`
FIXER ${nomo_global.FIXER ? "ON" : "OFF"}!!! Please refresh.`).appendTo("#current_server");
}
else if(FIXER_EGG_COUNT === 8){
NOMO_DEBUG("FIXER EGG 8");
var FIXER_SERVER_PROMPT = prompt("고정할 서버(Target Server)에 포함된 문자열을 콤마로 구분하여 입력하세요.\n영문,숫자만 입력할 수 있습니다.\n예시1) sel, akamai\n예시2) sel03", nomo_global.FIXER_SERVER.join(", "));
if(FIXER_SERVER_PROMPT === null){
NOMO_DEBUG("Canceled");
}
else if (FIXER_SERVER_PROMPT === ""){
TWITCH_SERVER_INFO_SET_VAL("FIXER_SERVER", []);
}
else{
// eslint-disable-next-line no-useless-escape
FIXER_SERVER_PROMPT = FIXER_SERVER_PROMPT.toLowerCase().replace(/[\{\}\[\]\/?.;:|\)*~`!^\-_+<>@\#$%&\\\=\(\'\"\s]/gi, "").split(",");
TWITCH_SERVER_INFO_SET_VAL("FIXER_SERVER", FIXER_SERVER_PROMPT);
NOMO_DEBUG("FIXER_SERVER_PROMPT", FIXER_SERVER_PROMPT);
}
}
FIXER_EGG_COUNT = 0;
},1000);
});
} // End of creation of DOM
// else{
// simple_message(current_server + server_name);
// }
nomo_global.prev_server = current_server;
} // end of checing server name
} // end of checking .ts file with regex
} // end of checking indexOf('.hls.ttvnw.net')
} // end of checking if postMessage type is tsl.
// Receive a fixer message
else if (e.data.type === "tsi_fixer") {
// Check if the count has exceeded FIXER_ATTEMPT_MAX
var FIXED_FAILED = false;
if (e.data.fixed.FIXER_count >= nomo_global.FIXER_ATTEMPT_MAX) {
FIXED_FAILED = true;
}
// Create LOADER DOM
var $player_root = $(".player-root"); // for embeded player, old twitch player
if($player_root.length === 0){
$player_root = $(".highwind-video-player__container"); // for new twitch player
}
if ($player_root.length !== 0) {
$("#fixer_loader").remove();
// if fixer failed
if (!e.data.fixed.FIXED) {
$(`
Target Server : ${nomo_global.FIXER_SERVER.join(', ')}
Connected Server : ${e.data.fixed.CURRENT_SERVER}
connection attempts: ${e.data.fixed.FIXER_count} / ${nomo_global.FIXER_ATTEMPT_MAX}
${FIXED_FAILED ? "
SERVER FIX FAILED" : ''}
Loading...
`)
.prependTo($player_root);
// Show a failure message for 2 seconds and remove the DOM
if (FIXED_FAILED) {
clearTimeout(SETTIMEOUT_FIXED_FAILED);
SETTIMEOUT_FIXED_FAILED = setTimeout(function () {
$("#fixer_loader").remove();
}, 2000);
}
}
}
}
// else if(e.data.arg === undefined){
// NOMO_DEBUG("undefined case", e);
// } else if(e.data.arg.name !== "enqueue" && e.data.arg.name !== "remove"){NOMO_DEBUG(e.data);}
}
////////////////////////////////////////////////////////////////////////////////////
// Hijacking and Overriding Worker
////////////////////////////////////////////////////////////////////////////////////
var realWorker = unsafeWindow.Worker;
unsafeWindow.Worker = function (input) {
// Explicitly convert to a String
var newInput = String(input);
NOMO_DEBUG("newInput", newInput);
var myBlob = "importScripts('https://static.twitchcdn.net/assets/amazon-ivs-wasmworker.min-7da53ec1e6fb32a92d1d.js');";
var req = new XMLHttpRequest();
req.open('GET', newInput, false);
req.send();
var resText = req.responseText;
if(req.status == 200 || req.status == 201){
myBlob = resText;
}
// overriding blob
var workerBlob = new Blob(
[ /*javascript*/ `
// load global variables
const FIXER = ${nomo_global.FIXER};
const FIXER_SERVER = ${nomo_global.FIXER_SERVER.length > 0 ? '["'+(nomo_global.FIXER_SERVER).join('","')+'"]' : "[]"};
const FIXER_ATTEMPT_MAX = ${nomo_global.FIXER_ATTEMPT_MAX};
const FIXER_DELAY = ${FIXER_DELAY_MIN >= nomo_global.FIXER_DELAY ? nomo_global.FIXER_DELAY : FIXER_DELAY_MIN};
const DEBUG_WORKER = ${nomo_global.DEBUG};
const DEBUG_M3U8 = ${nomo_global.DEBUG_M3U8};
var FIXER_count = 1;
var FIXED = false;
var FIXED_SERVER = undefined;
// debug function in WORKER
var NOMO_DEBUG = function ( /**/ ) {
if (DEBUG_WORKER) {
var args = arguments,
args_length = args.length,
args_copy = args;
for (var i = args_length; i > 0; i--) {
args[i] = args_copy[i - 1];
}
args[0] = "[TSI] ";
args.length = args_length + 1;
console.log.apply(console, args);
}
};
// DEBUG:: check global variables
NOMO_DEBUG({"FIXER":FIXER, "FIXER_SERVER":FIXER_SERVER, "FIXER_ATTEMPT_MAX":FIXER_ATTEMPT_MAX, "FIXER_DELAY":FIXER_DELAY, "DEBUG_WORKER":DEBUG_WORKER, "DEBUG_M3U8":DEBUG_M3U8});
// Explicitly double-check types to prevent errors
if(typeof FIXER !== "boolean" || typeof FIXER_SERVER !== "object"){
FIXER = false;
NOMO_DEBUG("typeof FIXER is not boolean", FIXER, FIXER_SERVER);
}
if(typeof DEBUG_WORKER !== "boolean"){
DEBUG_WORKER = false;
NOMO_DEBUG("typeof DEBUG_WORKER is not boolean", DEBUG_WORKER);
}
if(typeof DEBUG_M3U8 !== "boolean"){
DEBUG_M3U8 = false;
NOMO_DEBUG("typeof DEBUG_M3U8 is not boolean", DEBUG_M3U8);
}
// sleep fuctnion
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Hijacking & overriding the fetch function inside a worker
const originalFetch = self.fetch;
self.fetch = async function(input, init){
// Fixed a conflict with TTV LOL PRO
let inputUrl = (input instanceof Request ? input.url : input.toString());
// Sending messages out of the worker
postMessage({"id":0,"type":"tsi","arg":inputUrl,"fixed":{"FIXED":FIXED,"FIXER_count":FIXER_count}});
// Server Fixer (Auto Reconnector)
if(FIXER
&& FIXER_SERVER !== undefined
&& FIXER_SERVER.length > 0
&& inputUrl.indexOf('usher.ttvnw.net/api/channel/hls') !== -1){
FIXED = false;
FIXER_count = 1;
NOMO_DEBUG("URL detected on first channel connection", inputUrl);
// start iteration
while(!FIXED && FIXER_count <= FIXER_ATTEMPT_MAX){
NOMO_DEBUG("Attempting to auto-select server...");
NOMO_DEBUG("Current Attempt Count", (FIXER_count) + " / " + FIXER_ATTEMPT_MAX);
var m3u8_fetch = await originalFetch.apply(this, arguments);
var m3u8_text = await m3u8_fetch.text();
// NOMO_DEBUG("m3u8_text", m3u8_text);
// check the error
if(m3u8_text.indexOf("error_code") !== -1 || m3u8_text.indexOf("Can not fi") !== -1){
NOMO_DEBUG("Error - Stop Server fixer", {m3u8_text});
break;
}
// check the cluster string
var cluster_str;
if((/CLUSTER=/).test(m3u8_text)){
cluster_str = m3u8_text.split("CLUSTER=")[1].split(",")[0].replace(/"/g,'');
NOMO_DEBUG("Cluster string exists", cluster_str);
for(var SN of FIXER_SERVER){
// If the desired server is found.
if(cluster_str.indexOf(SN) !== -1){
FIXED = true;
NOMO_DEBUG("Server Fixer Success", SN, cluster_str);
break;
}
}
}
// CLUSTER string does not exist, retry
else{
NOMO_DEBUG("CLUSTER string does not exist, retry");
cluster_str = "Server name not found.";
}
// Send a postMessage to display the loader
postMessage({"id":0,"type":"tsi_fixer","arg":"","fixed":{"FIXED":FIXED,"CURRENT_SERVER":cluster_str,"FIXER_count":FIXER_count}});
// When the desired server is found or the maximum count is reached
if(FIXED || FIXER_count === FIXER_ATTEMPT_MAX){
// return as blob
var m3u8_blob = new Blob([m3u8_text], {
type: 'text/plain'
});
var m3u8_blob_url = URL.createObjectURL(m3u8_blob);
var new_arg = arguments;
new_arg[0] = m3u8_blob_url;
NOMO_DEBUG("step2", m3u8_blob_url);
// revoke after 10s
setTimeout(function(){URL.revokeObjectURL(m3u8_blob_url)},10000);
return originalFetch.apply(this, new_arg);
}
else{
// If the desired server is not found
NOMO_DEBUG("The desired server was not found", FIXER_SERVER, cluster_str);
FIXER_count = FIXER_count + 1;
}
// Retry at FIXER_DELAY interval
await sleep(FIXER_DELAY);
} // end of while loop
} // end of if for iteration
// To debug M3U8
if(DEBUG_M3U8 && inputUrl.toLowerCase().indexOf(".m3u8") !== -1){
var m3u8_fetch = await originalFetch.apply(this, arguments);
var m3u8_text = await m3u8_fetch.text();
NOMO_DEBUG("\\n", inputUrl, "\\n", (new Date()), "\\n", m3u8_text);
// return as blob
var m3u8_blob = new Blob([m3u8_text], {
type: 'text/plain'
});
var m3u8_blob_url = URL.createObjectURL(m3u8_blob);
var new_arg = arguments;
new_arg[0] = m3u8_blob_url;
// NOMO_DEBUG("step2", m3u8_blob_url);
// Revoke after 10s
setTimeout(function(){URL.revokeObjectURL(m3u8_blob_url)},10000);
return originalFetch.apply(this, new_arg);
}
if(DEBUG_WORKER && inputUrl.toLowerCase().indexOf('usher.ttvnw.net/api/channel/hls') !== -1){
NOMO_DEBUG("master playlist m3u8 \\n" + inputUrl);
}
return originalFetch.apply(this, arguments);
};
${myBlob};
// override onmessage to debug
// setTimeout(function(){
// console.log(self);
// var old_onmessage = self.onmessage;
// self.onmessage = function(e){
// old_onmessage(e);
// console.log("self", e.data);
// };
// }, 1);
`], {
type: 'text/javascript'
}
);
// end of overriding of blob
var workerBlobUrl = URL.createObjectURL(workerBlob);
var workerBlobWrapper = new Blob(
[ /*javascript*/ `
importScripts('${workerBlobUrl}');
`], {
type: 'text/javascript'
});
var workerBlobWrapperUrl = URL.createObjectURL(workerBlobWrapper);
var my_worker = new realWorker(workerBlobWrapperUrl);
////////////////////////////////////////////////////////////////////////////////////
// Receiving postMessages and creating DOM outside of Worker
////////////////////////////////////////////////////////////////////////////////////
my_worker.onmessage = function (e) {
createTSILayout(e);
};
////////////////////////////////////////////////////////////////////////////////////
// modified Worker
////////////////////////////////////////////////////////////////////////////////////
NOMO_DEBUG("my_worker", my_worker);
// return to the original function that created the worker
return my_worker;
};
})();
}