diff --git a/vaft/vaft-ublock-origin.js b/vaft/vaft-ublock-origin.js index 4c7b890..c9ca5a8 100644 --- a/vaft/vaft-ublock-origin.js +++ b/vaft/vaft-ublock-origin.js @@ -1,7 +1,7 @@ twitch-videoad.js text/javascript (function() { if ( /(^|\.)twitch\.tv$/.test(document.location.hostname) === false ) { return; } - var ourTwitchAdSolutionsVersion = 2;// Only bump this when there's a breaking change to Twitch, the script, or there's a conflict with an unmaintained extension which uses this script + var ourTwitchAdSolutionsVersion = 3;// Only bump this when there's a breaking change to Twitch, the script, or there's a conflict with an unmaintained extension which uses this script if (window.twitchAdSolutionsVersion && window.twitchAdSolutionsVersion >= ourTwitchAdSolutionsVersion) { console.log("skipping vaft as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + window.twitchAdSolutionsVersion); window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion; @@ -102,6 +102,7 @@ twitch-videoad.js text/javascript return; } var newBlobStr = ` + const pendingFetchRequests = new Map(); ${getStreamUrlForResolution.toString()} ${getStreamForResolution.toString()} ${stripUnusedParams.toString()} @@ -131,6 +132,23 @@ twitch-videoad.js text/javascript ClientIntegrityHeader = e.data.value; } else if (e.data.key == 'UpdateAuthorizationHeader') { AuthorizationHeader = e.data.value; + } else if (e.data.key == 'FetchResponse') { + const responseData = e.data.value; + if (pendingFetchRequests.has(responseData.id)) { + const { resolve, reject } = pendingFetchRequests.get(responseData.id); + pendingFetchRequests.delete(responseData.id); + if (responseData.error) { + reject(new Error(responseData.error)); + } else { + // Create a Response object from the response data + const response = new Response(responseData.body, { + status: responseData.status, + statusText: responseData.statusText, + headers: responseData.headers + }); + resolve(response); + } + } } }); hookWorkerFetch(); @@ -244,6 +262,16 @@ twitch-videoad.js text/javascript } } }); + this.addEventListener('message', async event => { + if (event.data.key == 'FetchRequest') { + const fetchRequest = event.data.value; + const responseData = await handleWorkerFetchRequest(fetchRequest); + this.postMessage({ + key: 'FetchResponse', + value: responseData + }); + } + }); function getAdBlockDiv() { //To display a notification to the user, that an ad is being blocked. var playerRootDiv = document.querySelector('.video-player'); @@ -655,7 +683,7 @@ twitch-videoad.js text/javascript }, }]; } - function getAccessToken(channelName, playerType, realFetch) { + function getAccessToken(channelName, playerType) { var body = null; 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 }}'; body = { @@ -669,14 +697,9 @@ twitch-videoad.js text/javascript 'playerType': playerType } }; - return gqlRequest(body, realFetch); + return gqlRequest(body); } - function gqlRequest(body, realFetch) { - if (ClientIntegrityHeader == null) { - //console.warn('ClientIntegrityHeader is null'); - //throw 'ClientIntegrityHeader is null'; - } - var fetchFunc = realFetch ? realFetch : fetch; + function gqlRequest(body) { if (!GQLDeviceID) { var dcharacters = 'abcdefghijklmnopqrstuvwxyz0123456789'; var dcharactersLength = dcharacters.length; @@ -684,18 +707,34 @@ twitch-videoad.js text/javascript GQLDeviceID += dcharacters.charAt(Math.floor(Math.random() * dcharactersLength)); } } - return fetchFunc('https://gql.twitch.tv/gql', { - method: 'POST', - body: JSON.stringify(body), - headers: { - 'Client-ID': ClientID, - 'Client-Integrity': ClientIntegrityHeader, - 'Device-ID': GQLDeviceID, - 'X-Device-Id': GQLDeviceID, - 'Client-Version': ClientVersion, - 'Client-Session-Id': ClientSession, - 'Authorization': AuthorizationHeader - } + var headers = { + 'Client-ID': ClientID, + 'Client-Integrity': ClientIntegrityHeader, + 'Device-ID': GQLDeviceID, + 'X-Device-Id': GQLDeviceID, + 'Client-Version': ClientVersion, + 'Client-Session-Id': ClientSession, + 'Authorization': AuthorizationHeader + }; + return new Promise((resolve, reject) => { + const requestId = Math.random().toString(36).substring(2, 15); + const fetchRequest = { + id: requestId, + url: 'https://gql.twitch.tv/gql', + options: { + method: 'POST', + body: JSON.stringify(body), + headers + } + }; + pendingFetchRequests.set(requestId, { + resolve, + reject + }); + postMessage({ + key: 'FetchRequest', + value: fetchRequest + }); }); } function doTwitchPlayerTask(isPausePlay, isCheckQuality, isCorrectBuffer, isAutoQuality, setAutoQuality) { @@ -805,6 +844,25 @@ twitch-videoad.js text/javascript worker.postMessage({key: key, value: value}); }); } + async function handleWorkerFetchRequest(fetchRequest) { + try { + const response = await window.fetch(fetchRequest.url, fetchRequest.options); + const responseBody = await response.text(); + const responseObject = { + id: fetchRequest.id, + status: response.status, + statusText: response.statusText, + headers: Object.fromEntries(response.headers.entries()), + body: responseBody + }; + return responseObject; + } catch (error) { + return { + id: fetchRequest.id, + error: error.message + }; + } + } function hookFetch() { var realFetch = window.fetch; window.fetch = function(url, init, ...args) { diff --git a/vaft/vaft.user.js b/vaft/vaft.user.js index 66271fa..3dbc98d 100644 --- a/vaft/vaft.user.js +++ b/vaft/vaft.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name TwitchAdSolutions (vaft) // @namespace https://github.com/pixeltris/TwitchAdSolutions -// @version 17.0.0 +// @version 18.0.0 // @description Multiple solutions for blocking Twitch ads (vaft) // @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/vaft/vaft.user.js // @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/vaft/vaft.user.js @@ -13,7 +13,7 @@ // ==/UserScript== (function() { 'use strict'; - var ourTwitchAdSolutionsVersion = 2;// Only bump this when there's a breaking change to Twitch, the script, or there's a conflict with an unmaintained extension which uses this script + var ourTwitchAdSolutionsVersion = 3;// Only bump this when there's a breaking change to Twitch, the script, or there's a conflict with an unmaintained extension which uses this script if (window.twitchAdSolutionsVersion && window.twitchAdSolutionsVersion >= ourTwitchAdSolutionsVersion) { console.log("skipping vaft as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + window.twitchAdSolutionsVersion); window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion; @@ -114,6 +114,7 @@ return; } var newBlobStr = ` + const pendingFetchRequests = new Map(); ${getStreamUrlForResolution.toString()} ${getStreamForResolution.toString()} ${stripUnusedParams.toString()} @@ -143,6 +144,23 @@ ClientIntegrityHeader = e.data.value; } else if (e.data.key == 'UpdateAuthorizationHeader') { AuthorizationHeader = e.data.value; + } else if (e.data.key == 'FetchResponse') { + const responseData = e.data.value; + if (pendingFetchRequests.has(responseData.id)) { + const { resolve, reject } = pendingFetchRequests.get(responseData.id); + pendingFetchRequests.delete(responseData.id); + if (responseData.error) { + reject(new Error(responseData.error)); + } else { + // Create a Response object from the response data + const response = new Response(responseData.body, { + status: responseData.status, + statusText: responseData.statusText, + headers: responseData.headers + }); + resolve(response); + } + } } }); hookWorkerFetch(); @@ -256,6 +274,16 @@ } } }); + this.addEventListener('message', async event => { + if (event.data.key == 'FetchRequest') { + const fetchRequest = event.data.value; + const responseData = await handleWorkerFetchRequest(fetchRequest); + this.postMessage({ + key: 'FetchResponse', + value: responseData + }); + } + }); function getAdBlockDiv() { //To display a notification to the user, that an ad is being blocked. var playerRootDiv = document.querySelector('.video-player'); @@ -667,7 +695,7 @@ }, }]; } - function getAccessToken(channelName, playerType, realFetch) { + function getAccessToken(channelName, playerType) { var body = null; 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 }}'; body = { @@ -681,14 +709,9 @@ 'playerType': playerType } }; - return gqlRequest(body, realFetch); + return gqlRequest(body); } - function gqlRequest(body, realFetch) { - if (ClientIntegrityHeader == null) { - //console.warn('ClientIntegrityHeader is null'); - //throw 'ClientIntegrityHeader is null'; - } - var fetchFunc = realFetch ? realFetch : fetch; + function gqlRequest(body) { if (!GQLDeviceID) { var dcharacters = 'abcdefghijklmnopqrstuvwxyz0123456789'; var dcharactersLength = dcharacters.length; @@ -696,18 +719,34 @@ GQLDeviceID += dcharacters.charAt(Math.floor(Math.random() * dcharactersLength)); } } - return fetchFunc('https://gql.twitch.tv/gql', { - method: 'POST', - body: JSON.stringify(body), - headers: { - 'Client-ID': ClientID, - 'Client-Integrity': ClientIntegrityHeader, - 'Device-ID': GQLDeviceID, - 'X-Device-Id': GQLDeviceID, - 'Client-Version': ClientVersion, - 'Client-Session-Id': ClientSession, - 'Authorization': AuthorizationHeader - } + var headers = { + 'Client-ID': ClientID, + 'Client-Integrity': ClientIntegrityHeader, + 'Device-ID': GQLDeviceID, + 'X-Device-Id': GQLDeviceID, + 'Client-Version': ClientVersion, + 'Client-Session-Id': ClientSession, + 'Authorization': AuthorizationHeader + }; + return new Promise((resolve, reject) => { + const requestId = Math.random().toString(36).substring(2, 15); + const fetchRequest = { + id: requestId, + url: 'https://gql.twitch.tv/gql', + options: { + method: 'POST', + body: JSON.stringify(body), + headers + } + }; + pendingFetchRequests.set(requestId, { + resolve, + reject + }); + postMessage({ + key: 'FetchRequest', + value: fetchRequest + }); }); } function doTwitchPlayerTask(isPausePlay, isCheckQuality, isCorrectBuffer, isAutoQuality, setAutoQuality) { @@ -817,6 +856,25 @@ worker.postMessage({key: key, value: value}); }); } + async function handleWorkerFetchRequest(fetchRequest) { + try { + const response = await window.fetch(fetchRequest.url, fetchRequest.options); + const responseBody = await response.text(); + const responseObject = { + id: fetchRequest.id, + status: response.status, + statusText: response.statusText, + headers: Object.fromEntries(response.headers.entries()), + body: responseBody + }; + return responseObject; + } catch (error) { + return { + id: fetchRequest.id, + error: error.message + }; + } + } function hookFetch() { var realFetch = window.fetch; window.fetch = function(url, init, ...args) { diff --git a/video-swap-new/video-swap-new-ublock-origin.js b/video-swap-new/video-swap-new-ublock-origin.js index a488544..a93fb5b 100644 --- a/video-swap-new/video-swap-new-ublock-origin.js +++ b/video-swap-new/video-swap-new-ublock-origin.js @@ -1,7 +1,7 @@ twitch-videoad.js text/javascript (function() { if ( /(^|\.)twitch\.tv$/.test(document.location.hostname) === false ) { return; } - var ourTwitchAdSolutionsVersion = 2;// Only bump this when there's a breaking change to Twitch, the script, or there's a conflict with an unmaintained extension which uses this script + var ourTwitchAdSolutionsVersion = 3;// Only bump this when there's a breaking change to Twitch, the script, or there's a conflict with an unmaintained extension which uses this script if (window.twitchAdSolutionsVersion && window.twitchAdSolutionsVersion >= ourTwitchAdSolutionsVersion) { console.log("skipping video-swap-new as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + window.twitchAdSolutionsVersion); window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion; @@ -101,6 +101,7 @@ twitch-videoad.js text/javascript return; } var newBlobStr = ` + const pendingFetchRequests = new Map(); ${processM3U8.toString()} ${hookWorkerFetch.toString()} ${declareOptions.toString()} @@ -120,6 +121,23 @@ twitch-videoad.js text/javascript ClientIntegrityHeader = e.data.value; } else if (e.data.key == 'UpdateAuthorizationHeader') { AuthorizationHeader = e.data.value; + } else if (e.data.key == 'FetchResponse') { + const responseData = e.data.value; + if (pendingFetchRequests.has(responseData.id)) { + const { resolve, reject } = pendingFetchRequests.get(responseData.id); + pendingFetchRequests.delete(responseData.id); + if (responseData.error) { + reject(new Error(responseData.error)); + } else { + // Create a Response object from the response data + const response = new Response(responseData.body, { + status: responseData.status, + statusText: responseData.statusText, + headers: responseData.headers + }); + resolve(response); + } + } } }); hookWorkerFetch(); @@ -152,6 +170,16 @@ twitch-videoad.js text/javascript reloadTwitchPlayer(true); } }); + this.addEventListener('message', async event => { + if (event.data.key == 'FetchRequest') { + const fetchRequest = event.data.value; + const responseData = await handleWorkerFetchRequest(fetchRequest); + this.postMessage({ + key: 'FetchResponse', + value: responseData + }); + } + }); function getAdDiv() { var playerRootDiv = document.querySelector('.video-player'); var adDiv = null; @@ -317,7 +345,7 @@ twitch-videoad.js text/javascript for (var i = 0; i < 2; i++) { var encodingsUrl = url; if (i == 1) { - var accessTokenResponse = await getAccessToken(channelName, OPT_BACKUP_PLAYER_TYPE, OPT_BACKUP_PLATFORM, realFetch); + var accessTokenResponse = await getAccessToken(channelName, OPT_BACKUP_PLAYER_TYPE, OPT_BACKUP_PLATFORM); if (accessTokenResponse != null && accessTokenResponse.status === 200) { var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + channelName + '.m3u8' + (new URL(url)).search); @@ -416,7 +444,7 @@ twitch-videoad.js text/javascript }, }]; } - function getAccessToken(channelName, playerType, platform, realFetch) { + function getAccessToken(channelName, playerType, platform) { if (!platform) { platform = 'web'; } @@ -433,23 +461,40 @@ twitch-videoad.js text/javascript 'playerType': playerType } }; - return gqlRequest(body, realFetch); + return gqlRequest(body); } - function gqlRequest(body, realFetch) { - if (ClientIntegrityHeader == null) { - //console.warn('ClientIntegrityHeader is null'); - //throw 'ClientIntegrityHeader is null'; - } - var fetchFunc = realFetch ? realFetch : fetch; - return fetchFunc('https://gql.twitch.tv/gql', { - method: 'POST', - body: JSON.stringify(body), - headers: { - 'Client-Id': CLIENT_ID, - 'Client-Integrity': ClientIntegrityHeader, - 'X-Device-Id': gql_device_id, - 'Authorization': AuthorizationHeader + function gqlRequest(body) { + if (!gql_device_id) { + const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; + for (let i = 0; i < 32; i += 1) { + gql_device_id += chars.charAt(Math.floor(Math.random() * chars.length)); } + } + var headers = { + 'Client-Id': CLIENT_ID, + 'Client-Integrity': ClientIntegrityHeader, + 'X-Device-Id': gql_device_id, + 'Authorization': AuthorizationHeader + }; + return new Promise((resolve, reject) => { + const requestId = Math.random().toString(36).substring(2, 15); + const fetchRequest = { + id: requestId, + url: 'https://gql.twitch.tv/gql', + options: { + method: 'POST', + body: JSON.stringify(body), + headers + } + }; + pendingFetchRequests.set(requestId, { + resolve, + reject + }); + postMessage({ + key: 'FetchRequest', + value: fetchRequest + }); }); } function parseAttributes(str) { @@ -528,6 +573,25 @@ twitch-videoad.js text/javascript worker.postMessage({key: key, value: value}); }); } + async function handleWorkerFetchRequest(fetchRequest) { + try { + const response = await window.fetch(fetchRequest.url, fetchRequest.options); + const responseBody = await response.text(); + const responseObject = { + id: fetchRequest.id, + status: response.status, + statusText: response.statusText, + headers: Object.fromEntries(response.headers.entries()), + body: responseBody + }; + return responseObject; + } catch (error) { + return { + id: fetchRequest.id, + error: error.message + }; + } + } function hookFetch() { var realFetch = window.fetch; window.fetch = function(url, init, ...args) { diff --git a/video-swap-new/video-swap-new.user.js b/video-swap-new/video-swap-new.user.js index 3bb69ab..2c99876 100644 --- a/video-swap-new/video-swap-new.user.js +++ b/video-swap-new/video-swap-new.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name TwitchAdSolutions (video-swap-new) // @namespace https://github.com/pixeltris/TwitchAdSolutions -// @version 1.35 +// @version 1.36 // @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/video-swap-new/video-swap-new.user.js // @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/video-swap-new/video-swap-new.user.js // @description Multiple solutions for blocking Twitch ads (video-swap-new) @@ -13,7 +13,7 @@ // ==/UserScript== (function() { 'use strict'; - var ourTwitchAdSolutionsVersion = 2;// Only bump this when there's a breaking change to Twitch, the script, or there's a conflict with an unmaintained extension which uses this script + var ourTwitchAdSolutionsVersion = 3;// Only bump this when there's a breaking change to Twitch, the script, or there's a conflict with an unmaintained extension which uses this script if (window.twitchAdSolutionsVersion && window.twitchAdSolutionsVersion >= ourTwitchAdSolutionsVersion) { console.log("skipping video-swap-new as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + window.twitchAdSolutionsVersion); window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion; @@ -113,6 +113,7 @@ return; } var newBlobStr = ` + const pendingFetchRequests = new Map(); ${processM3U8.toString()} ${hookWorkerFetch.toString()} ${declareOptions.toString()} @@ -132,6 +133,23 @@ ClientIntegrityHeader = e.data.value; } else if (e.data.key == 'UpdateAuthorizationHeader') { AuthorizationHeader = e.data.value; + } else if (e.data.key == 'FetchResponse') { + const responseData = e.data.value; + if (pendingFetchRequests.has(responseData.id)) { + const { resolve, reject } = pendingFetchRequests.get(responseData.id); + pendingFetchRequests.delete(responseData.id); + if (responseData.error) { + reject(new Error(responseData.error)); + } else { + // Create a Response object from the response data + const response = new Response(responseData.body, { + status: responseData.status, + statusText: responseData.statusText, + headers: responseData.headers + }); + resolve(response); + } + } } }); hookWorkerFetch(); @@ -164,6 +182,16 @@ reloadTwitchPlayer(true); } }); + this.addEventListener('message', async event => { + if (event.data.key == 'FetchRequest') { + const fetchRequest = event.data.value; + const responseData = await handleWorkerFetchRequest(fetchRequest); + this.postMessage({ + key: 'FetchResponse', + value: responseData + }); + } + }); function getAdDiv() { var playerRootDiv = document.querySelector('.video-player'); var adDiv = null; @@ -329,7 +357,7 @@ for (var i = 0; i < 2; i++) { var encodingsUrl = url; if (i == 1) { - var accessTokenResponse = await getAccessToken(channelName, OPT_BACKUP_PLAYER_TYPE, OPT_BACKUP_PLATFORM, realFetch); + var accessTokenResponse = await getAccessToken(channelName, OPT_BACKUP_PLAYER_TYPE, OPT_BACKUP_PLATFORM); if (accessTokenResponse != null && accessTokenResponse.status === 200) { var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + channelName + '.m3u8' + (new URL(url)).search); @@ -428,7 +456,7 @@ }, }]; } - function getAccessToken(channelName, playerType, platform, realFetch) { + function getAccessToken(channelName, playerType, platform) { if (!platform) { platform = 'web'; } @@ -445,23 +473,40 @@ 'playerType': playerType } }; - return gqlRequest(body, realFetch); + return gqlRequest(body); } - function gqlRequest(body, realFetch) { - if (ClientIntegrityHeader == null) { - //console.warn('ClientIntegrityHeader is null'); - //throw 'ClientIntegrityHeader is null'; - } - var fetchFunc = realFetch ? realFetch : fetch; - return fetchFunc('https://gql.twitch.tv/gql', { - method: 'POST', - body: JSON.stringify(body), - headers: { - 'Client-Id': CLIENT_ID, - 'Client-Integrity': ClientIntegrityHeader, - 'X-Device-Id': gql_device_id, - 'Authorization': AuthorizationHeader + function gqlRequest(body) { + if (!gql_device_id) { + const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; + for (let i = 0; i < 32; i += 1) { + gql_device_id += chars.charAt(Math.floor(Math.random() * chars.length)); } + } + var headers = { + 'Client-Id': CLIENT_ID, + 'Client-Integrity': ClientIntegrityHeader, + 'X-Device-Id': gql_device_id, + 'Authorization': AuthorizationHeader + }; + return new Promise((resolve, reject) => { + const requestId = Math.random().toString(36).substring(2, 15); + const fetchRequest = { + id: requestId, + url: 'https://gql.twitch.tv/gql', + options: { + method: 'POST', + body: JSON.stringify(body), + headers + } + }; + pendingFetchRequests.set(requestId, { + resolve, + reject + }); + postMessage({ + key: 'FetchRequest', + value: fetchRequest + }); }); } function parseAttributes(str) { @@ -540,6 +585,25 @@ worker.postMessage({key: key, value: value}); }); } + async function handleWorkerFetchRequest(fetchRequest) { + try { + const response = await window.fetch(fetchRequest.url, fetchRequest.options); + const responseBody = await response.text(); + const responseObject = { + id: fetchRequest.id, + status: response.status, + statusText: response.statusText, + headers: Object.fromEntries(response.headers.entries()), + body: responseBody + }; + return responseObject; + } catch (error) { + return { + id: fetchRequest.id, + error: error.message + }; + } + } function hookFetch() { var realFetch = window.fetch; window.fetch = function(url, init, ...args) {