Fix GQL requests failing under chrome #363

- Ref https://github.com/AdguardTeam/AdGuardExtra/issues/599
This commit is contained in:
pixeltris
2025-06-22 14:25:07 +01:00
parent 492c1db4cf
commit e23eae61dd
4 changed files with 324 additions and 80 deletions
+71 -13
View File
@@ -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,10 +707,7 @@ 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: {
var headers = {
'Client-ID': ClientID,
'Client-Integrity': ClientIntegrityHeader,
'Device-ID': GQLDeviceID,
@@ -695,7 +715,26 @@ twitch-videoad.js text/javascript
'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) {
+72 -14
View File
@@ -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,10 +719,7 @@
GQLDeviceID += dcharacters.charAt(Math.floor(Math.random() * dcharactersLength));
}
}
return fetchFunc('https://gql.twitch.tv/gql', {
method: 'POST',
body: JSON.stringify(body),
headers: {
var headers = {
'Client-ID': ClientID,
'Client-Integrity': ClientIntegrityHeader,
'Device-ID': GQLDeviceID,
@@ -707,7 +727,26 @@
'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) {
+77 -13
View File
@@ -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';
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 fetchFunc = realFetch ? realFetch : fetch;
return fetchFunc('https://gql.twitch.tv/gql', {
method: 'POST',
body: JSON.stringify(body),
headers: {
}
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) {
+78 -14
View File
@@ -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';
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 fetchFunc = realFetch ? realFetch : fetch;
return fetchFunc('https://gql.twitch.tv/gql', {
method: 'POST',
body: JSON.stringify(body),
headers: {
}
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) {