Facebook
From idk some dude, 1 Month ago, written in Plain Text.
Embed
Download Paste or View Raw
Hits: 142
  1. twitch-videoad.js text/javascript
  2. (function() {
  3.     if ( /(^|.)twitch.tv$/.test(document.location.hostname) === false ) { return; }
  4.     //This stops Twitch from pausing the player when in another tab and an ad shows.
  5.     try {
  6.         Object.defineProperty(document, 'visibilityState', {
  7.             get() {
  8.                 return 'visible';
  9.             }
  10.         });
  11.         Object.defineProperty(document, 'hidden', {
  12.             get() {
  13.                 return false;
  14.             }
  15.         });
  16.         const block = e => {
  17.             e.preventDefault();
  18.             e.stopPropagation();
  19.             e.stopImmediatePropagation();
  20.         };
  21.         const process = e => {
  22.             e.preventDefault();
  23.             e.stopPropagation();
  24.             e.stopImmediatePropagation();
  25.             //This corrects the background tab buffer bug when switching to the background tab for the first time after an extended period.
  26.             doTwitchPlayerTask(false, false, true, false, false);
  27.         };
  28.         document.addEventListener('visibilitychange', block, true);
  29.         document.addEventListener('webkitvisibilitychange', block, true);
  30.         document.addEventListener('mozvisibilitychange', block, true);
  31.         document.addEventListener('hasFocus', block, true);
  32.         if (/Firefox/.test(navigator.userAgent)) {
  33.             Object.defineProperty(document, 'mozHidden', {
  34.                 get() {
  35.                     return false;
  36.                 }
  37.             });
  38.         } else {
  39.             Object.defineProperty(document, 'webkitHidden', {
  40.                 get() {
  41.                     return false;
  42.                 }
  43.             });
  44.         }
  45.     } catch (err) {}
  46.     //Send settings updates to worker.
  47.     window.addEventListener('message', (event) => {
  48.         if (event.source != window) {
  49.             return;
  50.         }
  51.         if (event.data.type && event.data.type == 'SetTwitchAdblockSettings' && event.data.settings) {
  52.             TwitchAdblockSettings = event.data.settings;
  53.         }
  54.     }, false);
  55.     function declareOptions(scope) {
  56.         scope.AdSignifier = 'stitched';
  57.         scope.ClientID = 'kimne78kx3ncx6brgo4mv6wki5h1ko';
  58.         scope.ClientVersion = 'null';
  59.         scope.ClientSession = 'null';
  60.         scope.PlayerType2 = 'embed'; //Source
  61.         scope.PlayerType3 = 'autoplay'; //360p
  62.         scope.CurrentChannelName = null;
  63.         scope.UsherParams = null;
  64.         scope.WasShowingAd = false;
  65.         scope.GQLDeviceID = null;
  66.         scope.IsSquadStream = false;
  67.         scope.StreamInfos = [];
  68.         scope.StreamInfosByUrl = [];
  69.         scope.MainUrlByUrl = [];
  70.         scope.EncodingCacheTimeout = 60000;
  71.         scope.DefaultProxyType = null;
  72.         scope.DefaultForcedQuality = null;
  73.         scope.DefaultProxyQuality = null;
  74.         scope.ClientIntegrityHeader = null;
  75.         scope.AuthorizationHeader = null;
  76.     }
  77.     declareOptions(window);
  78.     var TwitchAdblockSettings = {
  79.         BannerVisible: true,
  80.         ForcedQuality: null,
  81.         ProxyType: null,
  82.         ProxyQuality: null,
  83.     };
  84.     var twitchMainWorker = null;
  85.     var adBlockDiv = null;
  86.     var OriginalVideoPlayerQuality = null;
  87.     var IsPlayerAutoQuality = null;
  88.     const oldWorker = window.Worker;
  89.     window.Worker = class Worker extends oldWorker {
  90.         constructor(twitchBlobUrl) {
  91.             if (twitchMainWorker) {
  92.                 super(twitchBlobUrl);
  93.                 return;
  94.             }
  95.             var jsURL = getWasmWorkerUrl(twitchBlobUrl);
  96.             if (typeof jsURL !== 'string') {
  97.                 super(twitchBlobUrl);
  98.                 return;
  99.             }
  100.             var newBlobStr = `
  101.                 ${getStreamUrlForResolution.toString()}
  102.                 ${getStreamForResolution.toString()}
  103.                 ${stripUnusedParams.toString()}
  104.                 ${processM3U8.toString()}
  105.                 ${hookWorkerFetch.toString()}
  106.                 ${declareOptions.toString()}
  107.                 ${getAccessToken.toString()}
  108.                 ${gqlRequest.toString()}
  109.                 ${adRecordgqlPacket.toString()}
  110.                 ${tryNotifyTwitch.toString()}
  111.                 ${parseAttributes.toString()}
  112.                 declareOptions(self);
  113.                 self.TwitchAdblockSettings = ${JSON.stringify(TwitchAdblockSettings)};
  114.                 self.addEventListener('message', function(e) {
  115.                     if (e.data.key == 'UpdateIsSquadStream') {
  116.                         IsSquadStream = e.data.value;
  117.                     } else if (e.data.key == 'UpdateClientVersion') {
  118.                         ClientVersion = e.data.value;
  119.                     } else if (e.data.key == 'UpdateClientSession') {
  120.                         ClientSession = e.data.value;
  121.                     } else if (e.data.key == 'UpdateClientId') {
  122.                         ClientID = e.data.value;
  123.                     } else if (e.data.key == 'UpdateDeviceId') {
  124.                         GQLDeviceID = e.data.value;
  125.                     } else if (e.data.key == 'UpdateClientIntegrityHeader') {
  126.                         ClientIntegrityHeader = e.data.value;
  127.                     } else if (e.data.key == 'UpdateAuthorizationHeader') {
  128.                         AuthorizationHeader = e.data.value;
  129.                     }
  130.                 });
  131.                 hookWorkerFetch();
  132.                 importScripts('${jsURL}');
  133.             `;
  134.             super(URL.createObjectURL(new Blob([newBlobStr])));
  135.             twitchMainWorker = this;
  136.             this.onmessage = function(e) {
  137.                 if (e.data.key == 'ShowAdBlockBanner') {
  138.                     if (!TwitchAdblockSettings.BannerVisible) {
  139.                         return;
  140.                     }
  141.                     if (adBlockDiv == null) {
  142.                         adBlockDiv = getAdBlockDiv();
  143.                     }
  144.                     adBlockDiv.P.textContent = 'Blocking ads';
  145.                     adBlockDiv.style.display = 'block';
  146.                 } else if (e.data.key == 'HideAdBlockBanner') {
  147.                     if (adBlockDiv == null) {
  148.                         adBlockDiv = getAdBlockDiv();
  149.                     }
  150.                     adBlockDiv.style.display = 'none';
  151.                 } else if (e.data.key == 'PauseResumePlayer') {
  152.                     doTwitchPlayerTask(true, false, false, false, false);
  153.                 } else if (e.data.key == 'ForceChangeQuality') {
  154.                     //This is used to fix the bug where the video would freeze.
  155.                     try {
  156.                         //if (navigator.userAgent.toLowerCase().indexOf('firefox') == -1) {
  157.                             return;
  158.                         //}
  159.                         var autoQuality = doTwitchPlayerTask(false, false, false, true, false);
  160.                         var currentQuality = doTwitchPlayerTask(false, true, false, false, false);
  161.                         if (IsPlayerAutoQuality == null) {
  162.                             IsPlayerAutoQuality = autoQuality;
  163.                         }
  164.                         if (OriginalVideoPlayerQuality == null) {
  165.                             OriginalVideoPlayerQuality = currentQuality;
  166.                         }
  167.                         if (!currentQuality.includes('360') || e.data.value != null) {
  168.                             if (!OriginalVideoPlayerQuality.includes('360')) {
  169.                                 var settingsMenu = document.querySelector('div[data-a-target="player-settings-menu"]');
  170.                                 if (settingsMenu == null) {
  171.                                     var settingsCog = document.querySelector('button[data-a-target="player-settings-button"]');
  172.                                     if (settingsCog) {
  173.                                         settingsCog.click();
  174.                                         var qualityMenu = document.querySelector('button[data-a-target="player-settings-menu-item-quality"]');
  175.                                         if (qualityMenu) {
  176.                                             qualityMenu.click();
  177.                                         }
  178.                                         var lowQuality = document.querySelectorAll('input[data-a-target="tw-radio"');
  179.                                         if (lowQuality) {
  180.                                             var qualityToSelect = lowQuality.length - 2;
  181.                                             if (e.data.value != null) {
  182.                                                 if (e.data.value.includes('original')) {
  183.                                                     e.data.value = OriginalVideoPlayerQuality;
  184.                                                     if (IsPlayerAutoQuality) {
  185.                                                         e.data.value = 'auto';
  186.                                                     }
  187.                                                 }
  188.                                                 if (e.data.value.includes('160p')) {
  189.                                                     qualityToSelect = 5;
  190.                                                 }
  191.                                                 if (e.data.value.includes('360p')) {
  192.                                                     qualityToSelect = 4;
  193.                                                 }
  194.                                                 if (e.data.value.includes('480p')) {
  195.                                                     qualityToSelect = 3;
  196.                                                 }
  197.                                                 if (e.data.value.includes('720p')) {
  198.                                                     qualityToSelect = 2;
  199.                                                 }
  200.                                                 if (e.data.value.includes('822p')) {
  201.                                                     qualityToSelect = 2;
  202.                                                 }
  203.                                                 if (e.data.value.includes('864p')) {
  204.                                                     qualityToSelect = 2;
  205.                                                 }
  206.                                                 if (e.data.value.includes('900p')) {
  207.                                                     qualityToSelect = 2;
  208.                                                 }
  209.                                                 if (e.data.value.includes('936p')) {
  210.                                                     qualityToSelect = 2;
  211.                                                 }
  212.                                                 if (e.data.value.includes('960p')) {
  213.                                                     qualityToSelect = 2;
  214.                                                 }
  215.                                                 if (e.data.value.includes('1080p')) {
  216.                                                     qualityToSelect = 2;
  217.                                                 }
  218.                                                 if (e.data.value.includes('source')) {
  219.                                                     qualityToSelect = 1;
  220.                                                 }
  221.                                                 if (e.data.value.includes('auto')) {
  222.                                                     qualityToSelect = 0;
  223.                                                 }
  224.                                             }
  225.                                             var currentQualityLS = window.localStorage.getItem('video-quality');
  226.                                             lowQuality[qualityToSelect].click();
  227.                                             settingsCog.click();
  228.                                             window.localStorage.setItem('video-quality', currentQualityLS);
  229.                                             if (e.data.value != null) {
  230.                                                 OriginalVideoPlayerQuality = null;
  231.                                                 IsPlayerAutoQuality = null;
  232.                                                 doTwitchPlayerTask(false, false, false, true, true);
  233.                                             }
  234.                                         }
  235.                                     }
  236.                                 }
  237.                             }
  238.                         }
  239.                     } catch (err) {
  240.                         OriginalVideoPlayerQuality = null;
  241.                         IsPlayerAutoQuality = null;
  242.                     }
  243.                 }
  244.             };
  245.             function getAdBlockDiv() {
  246.                 //To display a notification to the user, that an ad is being blocked.
  247.                 var playerRootDiv = document.querySelector('.video-player');
  248.                 var adBlockDiv = null;
  249.                 if (playerRootDiv != null) {
  250.                     adBlockDiv = playerRootDiv.querySelector('.adblock-overlay');
  251.                     if (adBlockDiv == null) {
  252.                         adBlockDiv = document.createElement('div');
  253.                         adBlockDiv.className = 'adblock-overlay';
  254.                         adBlockDiv[removed] = '<div class="player-adblock-notice"  white; background-color: rgba(0, 0, 0, 0.8); position: absolute; top: 0px; left: 0px; padding: 5px;"><p></p></div>';
  255.                         adBlockDiv.style.display = 'none';
  256.                         adBlockDiv.P = adBlockDiv.querySelector('p');
  257.                         playerRootDiv.appendChild(adBlockDiv);
  258.                     }
  259.                 }
  260.                 return adBlockDiv;
  261.             }
  262.         }
  263.     };
  264.     function getWasmWorkerUrl(twitchBlobUrl) {
  265.         var req = new XMLHttpRequest();
  266.         req.open('GET', twitchBlobUrl, false);
  267.         req.send();
  268.         return req.responseText.split("'")[1];
  269.     }
  270.     function hookWorkerFetch() {
  271.         console.log('Twitch adblocker is enabled');
  272.         var realFetch = fetch;
  273.         fetch = async function(url, options) {
  274.             if (typeof url === 'string') {
  275.                 if (url.includes('video-weaver')) {
  276.                     return new Promise(function(resolve, reject) {
  277.                         var processAfter = async function(response) {
  278.                             //Here we check the m3u8 for any ads and also try fallback player types if needed.
  279.                             var responseText = await response.text();
  280.                             var weaverText = null;
  281.                             weaverText = await processM3U8(url, responseText, realFetch, PlayerType2);
  282.                             if (weaverText.includes(AdSignifier)) {
  283.                                 weaverText = await processM3U8(url, responseText, realFetch, PlayerType3);
  284.                             }
  285.                             resolve(new Response(weaverText));
  286.                         };
  287.                         var send = function() {
  288.                             return realFetch(url, options).then(function(response) {
  289.                                 processAfter(response);
  290.                             })['catch'](function(err) {
  291.                                 reject(err);
  292.                             });
  293.                         };
  294.                         send();
  295.                     });
  296.                 } else if (url.includes('/api/channel/hls/')) {
  297.                     var channelName = (new URL(url)).pathname.match(/([^/]+)(?=.w+$)/)[0];
  298.                     UsherParams = (new URL(url)).search;
  299.                     CurrentChannelName = channelName;
  300.                     //To prevent pause/resume loop for mid-rolls.
  301.                     var isPBYPRequest = url.includes('picture-by-picture');
  302.                     if (isPBYPRequest) {
  303.                         url = '';
  304.                     }
  305.                     return new Promise(function(resolve, reject) {
  306.                         var processAfter = async function(response) {
  307.                             if (response.status == 200) {
  308.                                 encodingsM3u8 = await response.text();
  309.                                 var streamInfo = StreamInfos[channelName];
  310.                                 if (streamInfo == null) {
  311.                                     StreamInfos[channelName] = streamInfo = {};
  312.                                 }
  313.                                 streamInfo.ChannelName = channelName;
  314.                                 streamInfo.RequestedAds = new Set();
  315.                                 streamInfo.Urls = [];// xxx.m3u8 -> { Resolution: "284x160", FrameRate: 30.0 }
  316.                                 streamInfo.EncodingsM3U8Cache = [];
  317.                                 streamInfo.EncodingsM3U8 = encodingsM3u8;
  318.                                 var lines = encodingsM3u8.replace('r', '').split('n');
  319.                                 for (var i = 0; i < lines.length; i++) {
  320.                                     if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
  321.                                         streamInfo.Urls[lines[i]] = -1;
  322.                                         if (i > 0 && lines[i - 1].startsWith('#EXT-X-STREAM-INF')) {
  323.                                             var attributes = parseAttributes(lines[i - 1]);
  324.                                             var resolution = attributes['RESOLUTION'];
  325.                                             var frameRate = attributes['FRAME-RATE'];
  326.                                             if (resolution) {
  327.                                                 streamInfo.Urls[lines[i]] = {
  328.                                                     Resolution: resolution,
  329.                                                     FrameRate: frameRate
  330.                                                 };
  331.                                             }
  332.                                         }
  333.                                         StreamInfosByUrl[lines[i]] = streamInfo;
  334.                                         MainUrlByUrl[lines[i]] = url;
  335.                                     }
  336.                                 }
  337.                                 resolve(new Response(encodingsM3u8));
  338.                             } else {
  339.                                 resolve(response);
  340.                             }
  341.                         };
  342.                         var send = function() {
  343.                             return realFetch(url, options).then(function(response) {
  344.                                 processAfter(response);
  345.                             })['catch'](function(err) {
  346.                                 reject(err);
  347.                             });
  348.                         };
  349.                         send();
  350.                     });
  351.                 }
  352.             }
  353.             return realFetch.apply(this, arguments);
  354.         };
  355.     }
  356.     function getStreamUrlForResolution(encodingsM3u8, resolutionInfo, qualityOverrideStr) {
  357.         var qualityOverride = 0;
  358.         if (qualityOverrideStr && qualityOverrideStr.endsWith('p')) {
  359.             qualityOverride = qualityOverrideStr.substr(0, qualityOverrideStr.length - 1) | 0;
  360.         }
  361.         var qualityOverrideFoundQuality = 0;
  362.         var qualityOverrideFoundFrameRate = 0;
  363.         var encodingsLines = encodingsM3u8.replace('r', '').split('n');
  364.         var firstUrl = null;
  365.         var lastUrl = null;
  366.         var matchedResolutionUrl = null;
  367.         var matchedFrameRate = false;
  368.         for (var i = 0; i < encodingsLines.length; i++) {
  369.             if (!encodingsLines[i].startsWith('#') && encodingsLines[i].includes('.m3u8')) {
  370.                 if (i > 0 && encodingsLines[i - 1].startsWith('#EXT-X-STREAM-INF')) {
  371.                     var attributes = parseAttributes(encodingsLines[i - 1]);
  372.                     var resolution = attributes['RESOLUTION'];
  373.                     var frameRate = attributes['FRAME-RATE'];
  374.                     if (resolution) {
  375.                         if (qualityOverride) {
  376.                             var quality = resolution.toLowerCase().split('x')[1];
  377.                             if (quality == qualityOverride) {
  378.                                 qualityOverrideFoundQuality = quality;
  379.                                 qualityOverrideFoundFrameRate = frameRate;
  380.                                 matchedResolutionUrl = encodingsLines[i];
  381.                                 if (frameRate < 40) {
  382.                                     //console.log(`qualityOverride(A) quality:${quality} frameRate:${frameRate}`);
  383.                                     return matchedResolutionUrl;
  384.                                 }
  385.                             } else if (quality < qualityOverride) {
  386.                                 //if (matchedResolutionUrl) {
  387.                                 //    console.log(`qualityOverride(B) quality:${qualityOverrideFoundQuality} frameRate:${qualityOverrideFoundFrameRate}`);
  388.                                 //} else {
  389.                                 //    console.log(`qualityOverride(C) quality:${quality} frameRate:${frameRate}`);
  390.                                 //}
  391.                                 return matchedResolutionUrl ? matchedResolutionUrl : encodingsLines[i];
  392.                             }
  393.                          } else if ((!resolutionInfo || resoluti resolutionInfo.Resolution) &&
  394.                                    (!matchedResolutionUrl || (!matchedFrameRate && frameRate == resolutionInfo.FrameRate))) {
  395.                              matchedResoluti
  396.                             matchedFrameRate = frameRate == resolutionInfo.FrameRate;
  397.                             if (matchedFrameRate) {
  398.                                 return matchedResolutionUrl;
  399.                             }
  400.                         }
  401.                     }
  402.                     if (firstUrl == null) {
  403.                         firstUrl = encodingsLines[i];
  404.                     }
  405.                     lastUrl = encodingsLines[i];
  406.                 }
  407.             }
  408.         }
  409.         if (qualityOverride) {
  410.             return lastUrl;
  411.         }
  412.         return matchedResolutionUrl ? matchedResolutionUrl : firstUrl;
  413.     }
  414.     async function getStreamForResolution(streamInfo, resolutionInfo, encodingsM3u8, fallbackStreamStr, playerType, realFetch) {
  415.         var qualityOverride = null;
  416.         if (playerType === 'proxy') {
  417.             qualityOverride = TwitchAdblockSettings.ProxyQuality ? TwitchAdblockSettings.ProxyQuality : DefaultProxyQuality;
  418.         }
  419.         if (streamInfo.EncodingsM3U8Cache[playerType].Resolution != resolutionInfo.Resolution ||
  420.             streamInfo.EncodingsM3U8Cache[playerType].RequestTime < Date.now() - EncodingCacheTimeout) {
  421.             console.log(`Blocking ads (type:${playerType}, resolution:${resolutionInfo.Resolution}, frameRate:${resolutionInfo.FrameRate}, qualityOverride:${qualityOverride})`);
  422.         }
  423.         streamInfo.EncodingsM3U8Cache[playerType].RequestTime = Date.now();
  424.         streamInfo.EncodingsM3U8Cache[playerType].Value = encodingsM3u8;
  425.          streamInfo.EncodingsM3U8Cache[playerType].Resoluti
  426.         var streamM3u8Url = getStreamUrlForResolution(encodingsM3u8, resolutionInfo, qualityOverride);
  427.          var streamM3u8Resp realFetch(streamM3u8Url);
  428.         if (streamM3u8Response.status == 200) {
  429.             var m3u8Text = await streamM3u8Response.text();
  430.             WasShowingAd = true;
  431.             postMessage({
  432.                 key: 'ShowAdBlockBanner'
  433.             });
  434.             postMessage({
  435.                 key: 'ForceChangeQuality'
  436.             });
  437.             if (!m3u8Text || m3u8Text.includes(AdSignifier)) {
  438.                 streamInfo.EncodingsM3U8Cache[playerType].Value = null;
  439.             }
  440.             return m3u8Text;
  441.         } else {
  442.             streamInfo.EncodingsM3U8Cache[playerType].Value = null;
  443.             return fallbackStreamStr;
  444.         }
  445.     }
  446.     function stripUnusedParams(str, params) {
  447.         if (!params) {
  448.             params = [ 'token', 'sig' ];
  449.         }
  450.         var tempUrl = new URL('https://localhost/' + str);
  451.         for (var i = 0; i < params.length; i++) {
  452.             tempUrl.searchParams.delete(params[i]);
  453.         }
  454.         return tempUrl.pathname.substring(1) + tempUrl.search;
  455.     }
  456.     async function processM3U8(url, textStr, realFetch, playerType) {
  457.         //Checks the m3u8 for ads and if it finds one, instead returns an ad-free stream.
  458.         var streamInfo = StreamInfosByUrl[url];
  459.         //Ad blocking for squad streams is disabled due to the way multiple weaver urls are used. No workaround so far.
  460.         if (IsSquadStream == true) {
  461.             return textStr;
  462.         }
  463.         if (!textStr) {
  464.             return textStr;
  465.         }
  466.         //Some live streams use mp4.
  467.         if (!textStr.includes('.ts') && !textStr.includes('.mp4')) {
  468.             return textStr;
  469.         }
  470.         var haveAdTags = textStr.includes(AdSignifier);
  471.         if (haveAdTags) {
  472.             var isMidroll = textStr.includes('"MIDROLL"') || textStr.includes('"midroll"');
  473.             //Reduces ad frequency. TODO: Reduce the number of requests. This is really spamming Twitch with requests.
  474.             if (!isMidroll) {
  475.                 if (playerType === PlayerType2) {
  476.                     var lines = textStr.replace('r', '').split('n');
  477.                     for (var i = 0; i < lines.length; i++) {
  478.                         var line = lines[i];
  479.                         if (line.startsWith('#EXTINF') && lines.length > i + 1) {
  480.                             if (!line.includes(',live') && !streamInfo.RequestedAds.has(lines[i + 1])) {
  481.                                 // Only request one .ts file per .m3u8 request to avoid making too many requests
  482.                                 //console.log('Fetch ad .ts file');
  483.                                 streamInfo.RequestedAds.add(lines[i + 1]);
  484.                                 fetch(lines[i + 1]).then((response)=>{response.blob()});
  485.                                 break;
  486.                             }
  487.                         }
  488.                     }
  489.                 }
  490.                 try {
  491.                     //tryNotifyTwitch(textStr);
  492.                 } catch (err) {}
  493.             }
  494.             var currentResolution = null;
  495.             if (streamInfo && streamInfo.Urls) {
  496.                 for (const [resUrl, resInfo] of Object.entries(streamInfo.Urls)) {
  497.                     if (resUrl == url) {
  498.                         currentResolution = resInfo;
  499.                         //console.log(resInfo.Resolution);
  500.                         break;
  501.                     }
  502.                 }
  503.             }
  504.             // Keep the m3u8 around for a little while (once per ad) before requesting a new one
  505.             var encodingsM3U8Cache = streamInfo.EncodingsM3U8Cache[playerType];
  506.             if (encodingsM3U8Cache) {
  507.                 if (encodingsM3U8Cache.Value && encodingsM3U8Cache.RequestTime >= Date.now() - EncodingCacheTimeout) {
  508.                     try {
  509.                         var result = getStreamForResolution(streamInfo, currentResolution, encodingsM3U8Cache.Value, null, playerType, realFetch);
  510.                         if (result) {
  511.                             return result;
  512.                         }
  513.                     } catch (err) {
  514.                         encodingsM3U8Cache.Value = null;
  515.                     }
  516.                 }
  517.             } else {
  518.                 streamInfo.EncodingsM3U8Cache[playerType] = {
  519.                     RequestTime: Date.now(),
  520.                     Value: null,
  521.                     Resolution: null
  522.                 };
  523.             }
  524.             if (playerType === 'proxy') {
  525.                 try {
  526.                     var proxyType = TwitchAdblockSettings.ProxyType ? TwitchAdblockSettings.ProxyType : DefaultProxyType;
  527.                     var encodingsM3u8Response = null;
  528.                     /*var tempUrl = stripUnusedParams(MainUrlByUrl[url]);
  529.                     const match = /(hls|vod)/(.+?)$/gim.exec(tempUrl);*/
  530.                     switch (proxyType) {
  531.                         case 'TTV LOL':
  532.                             encodingsM3u8Response = await realFetch('https://api.ttv.lol/playlist/' + CurrentChannelName + '.m3u8?allow_source=true'/* + encodeURIComponent(match[2])*/, {headers: {'X-Donate-To': 'https://ttv.lol/donate'}});
  533.                             break;
  534.                         /*case 'Purple Adblock':// Broken...
  535.                             encodingsM3u8Response = await realFetch('https://eu1.jupter.ga/channel/' + CurrentChannelName);*/
  536.                         case 'Falan':// https://greasyfork.org/en/scripts/425139-twitch-ad-fix/code
  537.                             encodingsM3u8Response = await realFetch(atob('aHR0cHM6Ly9qaWdnbGUuYmV5cGF6YXJpZ3VydXN1LndvcmtlcnMuZGV2') + '/hls/' + CurrentChannelName + '.m3u8?allow_source=true'/* + encodeURIComponent(match[2])*/);
  538.                             break;
  539.                     }
  540.                     if (encodingsM3u8Response && encodingsM3u8Response.status === 200) {
  541.                         return getStreamForResolution(streamInfo, currentResolution, await encodingsM3u8Response.text(), textStr, playerType, realFetch);
  542.                     }
  543.                 } catch (err) {}
  544.                 return textStr;
  545.             }
  546.             var accessTokenResponse = await getAccessToken(CurrentChannelName, playerType);
  547.             if (accessTokenResponse.status === 200) {
  548.                 var accessToken = await accessTokenResponse.json();
  549.                 try {
  550.                     var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + CurrentChannelName + '.m3u8' + UsherParams);
  551.                     urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature);
  552.                     urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value);
  553.                     var encodingsM3u8Response = await realFetch(urlInfo.href);
  554.                     if (encodingsM3u8Response.status === 200) {
  555.                         return getStreamForResolution(streamInfo, currentResolution, await encodingsM3u8Response.text(), textStr, playerType, realFetch);
  556.                     } else {
  557.                         return textStr;
  558.                     }
  559.                 } catch (err) {}
  560.                 return textStr;
  561.             } else {
  562.                 return textStr;
  563.             }
  564.         } else {
  565.             if (WasShowingAd) {
  566.                 console.log('Finished blocking ads');
  567.                 WasShowingAd = false;
  568.                 //Here we put player back to original quality and remove the blocking message.
  569.                 postMessage({
  570.                     key: 'ForceChangeQuality',
  571.                     value: 'original'
  572.                 });
  573.                 postMessage({
  574.                     key: 'PauseResumePlayer'
  575.                 });
  576.                 postMessage({
  577.                     key: 'HideAdBlockBanner'
  578.                 });
  579.             }
  580.             return textStr;
  581.         }
  582.         return textStr;
  583.     }
  584.     function parseAttributes(str) {
  585.         return Object.fromEntries(
  586.             str.split(/(?:^|,)((?:[^=]*)=(?:"[^"]*"|[^,]*))/)
  587.             .filter(Boolean)
  588.             .map(x => {
  589.                 const idx = x.indexOf('=');
  590.                 const key = x.substring(0, idx);
  591.                 const value = x.substring(idx + 1);
  592.                 const num = Number(value);
  593.                 return [key, Number.isNaN(num) ? value.startsWith('"') ? JSON.parse(value) : value : num];
  594.             }));
  595.     }
  596.     async function tryNotifyTwitch(streamM3u8) {
  597.         //We notify that an ad was requested but was not visible and was also muted.
  598.         var matches = streamM3u8.match(/#EXT-X-DATERANGE:(ID="stitched-ad-[^n]+)n/);
  599.         if (matches.length > 1) {
  600.             const attrString = matches[1];
  601.             const attr = parseAttributes(attrString);
  602.             var podLength = parseInt(attr['X-TV-TWITCH-AD-POD-LENGTH'] ? attr['X-TV-TWITCH-AD-POD-LENGTH'] : '1');
  603.             var podPosition = parseInt(attr['X-TV-TWITCH-AD-POD-POSITION'] ? attr['X-TV-TWITCH-AD-POD-POSITION'] : '0');
  604.             var radToken = attr['X-TV-TWITCH-AD-RADS-TOKEN'];
  605.             var lineItemId = attr['X-TV-TWITCH-AD-LINE-ITEM-ID'];
  606.             var orderId = attr['X-TV-TWITCH-AD-ORDER-ID'];
  607.             var creativeId = attr['X-TV-TWITCH-AD-CREATIVE-ID'];
  608.             var adId = attr['X-TV-TWITCH-AD-ADVERTISER-ID'];
  609.             var rollType = attr['X-TV-TWITCH-AD-ROLL-TYPE'].toLowerCase();
  610.             const baseData = {
  611.                 stitched: true,
  612.                 roll_type: rollType,
  613.                 player_mute: true,
  614.                 player_volume: 0.0,
  615.                 visible: false,
  616.             };
  617.             for (let podPosition = 0; podPosition < podLength; podPosition++) {
  618.                 const extendedData = {
  619.                     ...baseData,
  620.                     ad_id: adId,
  621.                     ad_position: podPosition,
  622.                     duration: 0,
  623.                     creative_id: creativeId,
  624.                     total_ads: podLength,
  625.                     order_id: orderId,
  626.                     line_item_id: lineItemId,
  627.                 };
  628.                 await gqlRequest(adRecordgqlPacket('video_ad_impression', radToken, extendedData));
  629.                 for (let quartile = 0; quartile < 4; quartile++) {
  630.                     await gqlRequest(
  631.                         adRecordgqlPacket('video_ad_quartile_complete', radToken, {
  632.                             ...extendedData,
  633.                             quartile: quartile + 1,
  634.                         })
  635.                     );
  636.                 }
  637.                 await gqlRequest(adRecordgqlPacket('video_ad_pod_complete', radToken, baseData));
  638.             }
  639.         }
  640.     }
  641.     function adRecordgqlPacket(event, radToken, payload) {
  642.         return [{
  643.             operationName: 'ClientSideAdEventHandling_RecordAdEvent',
  644.             variables: {
  645.                 input: {
  646.                     eventName: event,
  647.                     eventPayload: JSON.stringify(payload),
  648.                     radToken,
  649.                 },
  650.             },
  651.             extensions: {
  652.                 persistedQuery: {
  653.                     version: 1,
  654.                     sha256Hash: '7e6c69e6eb59f8ccb97ab73686f3d8b7d85a72a0298745ccd8bfc68e4054ca5b',
  655.                 },
  656.             },
  657.         }];
  658.     }
  659.     function getAccessToken(channelName, playerType, realFetch) {
  660.         var body = null;
  661.         var templateQuery = 'query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) {  streamPlaybackAccessToken(channelName: $login, params: {platform: "ios", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isLive) {    value    signature    __typename  }  videoPlaybackAccessToken(id: $vodID, params: {platform: "ios", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isVod) {    value    signature    __typename  }}';
  662.         body = {
  663.             operationName: 'PlaybackAccessToken_Template',
  664.             query: templateQuery,
  665.             variables: {
  666.                 'isLive': true,
  667.                 'login': channelName,
  668.                 'isVod': false,
  669.                 'vodID': '',
  670.                 'playerType': playerType
  671.             }
  672.         };
  673.         return gqlRequest(body, realFetch);
  674.     }
  675.     function gqlRequest(body, realFetch) {
  676.         if (ClientIntegrityHeader == null) {
  677.             //console.warn('ClientIntegrityHeader is null');
  678.             //throw 'ClientIntegrityHeader is null';
  679.         }
  680.         var fetchFunc = realFetch ? realFetch : fetch;
  681.         if (!GQLDeviceID) {
  682.             var dcharacters = 'abcdefghijklmnopqrstuvwxyz0123456789';
  683.             var dcharactersLength = dcharacters.length;
  684.             for (var i = 0; i < 32; i++) {
  685.                 GQLDeviceID += dcharacters.charAt(Math.floor(Math.random() * dcharactersLength));
  686.             }
  687.         }
  688.         return fetchFunc('https://gql.twitch.tv/gql', {
  689.             method: 'POST',
  690.             body: JSON.stringify(body),
  691.             headers: {
  692.                 'Client-ID': ClientID,
  693.                 'Client-Integrity': ClientIntegrityHeader,
  694.                 'Device-ID': GQLDeviceID,
  695.                 'X-Device-Id': GQLDeviceID,
  696.                 'Client-Version': ClientVersion,
  697.                 'Client-Session-Id': ClientSession,
  698.                 'Authorization': AuthorizationHeader
  699.             }
  700.         });
  701.     }
  702.     function doTwitchPlayerTask(isPausePlay, isCheckQuality, isCorrectBuffer, isAutoQuality, setAutoQuality) {
  703.         //This will do an instant pause/play to return to original quality once the ad is finished.
  704.         //We also use this function to get the current video player quality set by the user.
  705.         //We also use this function to quickly pause/play the player when switching tabs to stop delays.
  706.         try {
  707.              var videoC
  708.             var videoPlayer = null;
  709.             function findReactNode(root, constraint) {
  710.                 if (root.stateNode && constraint(root.stateNode)) {
  711.                     return root.stateNode;
  712.                 }
  713.                 let node = root.child;
  714.                 while (node) {
  715.                     const result = findReactNode(node, constraint);
  716.                     if (result) {
  717.                         return result;
  718.                     }
  719.                     node = node.sibling;
  720.                 }
  721.                 return null;
  722.             }
  723.             function findReactRootNode() {
  724.                 var reactRootNode = null;
  725.                 var rootNode = document.querySelector('#root');
  726.                 if (rootNode && rootNode._reactRootContainer && rootNode._reactRootContainer._internalRoot && rootNode._reactRootContainer._internalRoot.current) {
  727.                     reactRootNode = rootNode._reactRootContainer._internalRoot.current;
  728.                 }
  729.                 if (reactRootNode == null) {
  730.                      var c => x.startsWith('__reactContainer'));
  731.                     if (containerName != null) {
  732.                         reactRootNode = rootNode[containerName];
  733.                     }
  734.                 }
  735.                 return reactRootNode;
  736.             }
  737.             var reactRootNode = findReactRootNode();
  738.             if (!reactRootNode) {
  739.                 console.log('Could not find react root');
  740.                 return;
  741.             }
  742.             videoPlayer = findReactNode(reactRootNode, node => node.setPlayerActive && node.props && node.props.mediaPlayerInstance);
  743.             videoPlayer = videoPlayer && videoPlayer.props && videoPlayer.props.mediaPlayerInstance ? videoPlayer.props.mediaPlayerInstance : null;
  744.             if (isPausePlay) {
  745.                 videoPlayer.pause();
  746.                 videoPlayer.play();
  747.                 return;
  748.             }
  749.             if (isCheckQuality) {
  750.                 if (typeof videoPlayer.getQuality() == 'undefined') {
  751.                     return;
  752.                 }
  753.                 var playerQuality = JSON.stringify(videoPlayer.getQuality());
  754.                 if (playerQuality) {
  755.                     return playerQuality;
  756.                 } else {
  757.                     return;
  758.                 }
  759.             }
  760.             if (isAutoQuality) {
  761.                 if (typeof videoPlayer.isAutoQualityMode() == 'undefined') {
  762.                     return false;
  763.                 }
  764.                 var autoQuality = videoPlayer.isAutoQualityMode();
  765.                 if (autoQuality) {
  766.                     videoPlayer.setAutoQualityMode(false);
  767.                     return autoQuality;
  768.                 } else {
  769.                     return false;
  770.                 }
  771.             }
  772.             if (setAutoQuality) {
  773.                 videoPlayer.setAutoQualityMode(true);
  774.                 return;
  775.             }
  776.             //This only happens when switching tabs and is to correct the high latency caused when opening background tabs and going to them at a later time.
  777.             //We check that this is a live stream by the page URL, to prevent vod/clip pause/plays.
  778.             try {
  779.                 var currentPageURL = document.URL;
  780.                 var isLive = true;
  781.                 if (currentPageURL.includes('videos/') || currentPageURL.includes('clip/')) {
  782.                     isLive = false;
  783.                 }
  784.                 if (isCorrectBuffer && isLive) {
  785.                     //A timer is needed due to the player not resuming without it.
  786.                     setTimeout(function() {
  787.                         //If latency to broadcaster is above 5 or 15 seconds upon switching tabs, we pause and play the player to reset the latency.
  788.                         //If latency is between 0-6, user can manually pause and resume to reset latency further.
  789.                         if (videoPlayer.isLiveLowLatency() && videoPlayer.getLiveLatency() > 5) {
  790.                             videoPlayer.pause();
  791.                             videoPlayer.play();
  792.                         } else if (videoPlayer.getLiveLatency() > 15) {
  793.                             videoPlayer.pause();
  794.                             videoPlayer.play();
  795.                         }
  796.                     }, 3000);
  797.                 }
  798.             } catch (err) {}
  799.         } catch (err) {}
  800.     }
  801.     var localDeviceID = null;
  802.     localDeviceID = window.localStorage.getItem('local_copy_unique_id');
  803.     function hookFetch() {
  804.         var realFetch = window.fetch;
  805.         window.fetch = function(url, init, ...args) {
  806.             if (typeof url === 'string') {
  807.                 //Check if squad stream.
  808.                 if ([removed].pathname.includes('/squad')) {
  809.                     if (twitchMainWorker) {
  810.                         twitchMainWorker.postMessage({
  811.                             key: 'UpdateIsSquadStream',
  812.                             value: true
  813.                         });
  814.                     }
  815.                 } else {
  816.                     if (twitchMainWorker) {
  817.                         twitchMainWorker.postMessage({
  818.                             key: 'UpdateIsSquadStream',
  819.                             value: false
  820.                         });
  821.                     }
  822.                 }
  823.                 if (url.includes('/access_token') || url.includes('gql')) {
  824.                     //Device ID is used when notifying Twitch of ads.
  825.                     var deviceId = init.headers['X-Device-Id'];
  826.                     if (typeof deviceId !== 'string') {
  827.                         deviceId = init.headers['Device-ID'];
  828.                     }
  829.                     //Added to prevent eventual UBlock conflicts.
  830.                     if (typeof deviceId === 'string' && !deviceId.includes('twitch-web-wall-mason')) {
  831.                         GQLDeviceID = deviceId;
  832.                     } else if (localDeviceID) {
  833.                         GQLDeviceID = localDeviceID.replace('"', '');
  834.                         GQLDeviceID = GQLDeviceID.replace('"', '');
  835.                     }
  836.                     if (GQLDeviceID && twitchMainWorker) {
  837.                         if (typeof init.headers['X-Device-Id'] === 'string') {
  838.                             init.headers['X-Device-Id'] = GQLDeviceID;
  839.                         }
  840.                         if (typeof init.headers['Device-ID'] === 'string') {
  841.                             init.headers['Device-ID'] = GQLDeviceID;
  842.                         }
  843.                         twitchMainWorker.postMessage({
  844.                             key: 'UpdateDeviceId',
  845.                             value: GQLDeviceID
  846.                         });
  847.                     }
  848.                     //Client version is used in GQL requests.
  849.                     var clientVersion = init.headers['Client-Version'];
  850.                     if (clientVersion && typeof clientVersion == 'string') {
  851.                         ClientVersion = clientVersion;
  852.                     }
  853.                     if (ClientVersion && twitchMainWorker) {
  854.                         twitchMainWorker.postMessage({
  855.                             key: 'UpdateClientVersion',
  856.                             value: ClientVersion
  857.                         });
  858.                     }
  859.                     //Client session is used in GQL requests.
  860.                     var clientSession = init.headers['Client-Session-Id'];
  861.                     if (clientSession && typeof clientSession == 'string') {
  862.                         ClientSession = clientSession;
  863.                     }
  864.                     if (ClientSession && twitchMainWorker) {
  865.                         twitchMainWorker.postMessage({
  866.                             key: 'UpdateClientSession',
  867.                             value: ClientSession
  868.                         });
  869.                     }
  870.                     //Client ID is used in GQL requests.
  871.                     if (url.includes('gql') && init && typeof init.body === 'string' && init.body.includes('PlaybackAccessToken')) {
  872.                         var clientId = init.headers['Client-ID'];
  873.                         if (clientId && typeof clientId == 'string') {
  874.                             ClientID = clientId;
  875.                         } else {
  876.                             clientId = init.headers['Client-Id'];
  877.                             if (clientId && typeof clientId == 'string') {
  878.                                 ClientID = clientId;
  879.                             }
  880.                         }
  881.                         if (ClientID && twitchMainWorker) {
  882.                             twitchMainWorker.postMessage({
  883.                                 key: 'UpdateClientId',
  884.                                 value: ClientID
  885.                             });
  886.                         }
  887.                         //Client integrity header
  888.                         ClientIntegrityHeader = init.headers['Client-Integrity'];
  889.                         if (ClientIntegrityHeader && twitchMainWorker) {
  890.                             twitchMainWorker.postMessage({
  891.                                 key: 'UpdateClientIntegrityHeader',
  892.                                 value: init.headers['Client-Integrity']
  893.                             });
  894.                         }
  895.                         //Authorization header
  896.                         AuthorizationHeader = init.headers['Authorization'];
  897.                         if (AuthorizationHeader && twitchMainWorker) {
  898.                             twitchMainWorker.postMessage({
  899.                                 key: 'UpdateAuthorizationHeader',
  900.                                 value: init.headers['Authorization']
  901.                             });
  902.                         }
  903.                     }
  904.                     //To prevent pause/resume loop for mid-rolls.
  905.                     if (url.includes('gql') && init && typeof init.body === 'string' && init.body.includes('PlaybackAccessToken') && init.body.includes('picture-by-picture')) {
  906.                         init.body = '';
  907.                     }
  908.                     var isPBYPRequest = url.includes('picture-by-picture');
  909.                     if (isPBYPRequest) {
  910.                         url = '';
  911.                     }
  912.                 }
  913.             }
  914.             return realFetch.apply(this, arguments);
  915.         };
  916.     }
  917.     hookFetch();
  918. })();