Fix GQL requests failing under Brave (userscript) #392

Also moved back to old player type #387
This commit is contained in:
pixeltris
2025-06-27 19:37:14 +01:00
parent accf04aa36
commit f5594de4ef
4 changed files with 346 additions and 112 deletions
+78 -20
View File
@@ -1,13 +1,16 @@
twitch-videoad.js text/javascript twitch-videoad.js text/javascript
(function() { (function() {
if ( /(^|\.)twitch\.tv$/.test(document.location.hostname) === false ) { return; } if ( /(^|\.)twitch\.tv$/.test(document.location.hostname) === false ) { return; }
var ourTwitchAdSolutionsVersion = 5;// 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 = 6;// 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) { if (typeof unsafeWindow === 'undefined') {
console.log("skipping vaft as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + window.twitchAdSolutionsVersion); unsafeWindow = window;
window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion; }
if (typeof unsafeWindow.twitchAdSolutionsVersion !== 'undefined' && unsafeWindow.twitchAdSolutionsVersion >= ourTwitchAdSolutionsVersion) {
console.log("skipping vaft as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + unsafeWindow.twitchAdSolutionsVersion);
unsafeWindow.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion;
return; return;
} }
window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion; unsafeWindow.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion;
function declareOptions(scope) { function declareOptions(scope) {
scope.AdSignifier = 'stitched'; scope.AdSignifier = 'stitched';
scope.ClientID = 'kimne78kx3ncx6brgo4mv6wki5h1ko'; scope.ClientID = 'kimne78kx3ncx6brgo4mv6wki5h1ko';
@@ -15,7 +18,7 @@ twitch-videoad.js text/javascript
scope.ClientSession = 'null'; scope.ClientSession = 'null';
scope.PlayerType2 = 'embed'; //Source scope.PlayerType2 = 'embed'; //Source
scope.PlayerType3 = 'site'; //Source scope.PlayerType3 = 'site'; //Source
scope.PlayerType4 = 'picture-by-picture'; //360p scope.PlayerType4 = 'autoplay'; //360p
scope.CurrentChannelName = null; scope.CurrentChannelName = null;
scope.UsherParams = null; scope.UsherParams = null;
scope.WasShowingAd = false; scope.WasShowingAd = false;
@@ -90,8 +93,8 @@ twitch-videoad.js text/javascript
|| workerStringReinsert.some((x) => workerString.includes(x)); || workerStringReinsert.some((x) => workerString.includes(x));
} }
function hookWindowWorker() { function hookWindowWorker() {
var reinsert = getWorkersForReinsert(window.Worker); var reinsert = getWorkersForReinsert(unsafeWindow.Worker);
var newWorker = class Worker extends getCleanWorker(window.Worker) { var newWorker = class Worker extends getCleanWorker(unsafeWindow.Worker) {
constructor(twitchBlobUrl, options) { constructor(twitchBlobUrl, options) {
var isTwitchWorker = false; var isTwitchWorker = false;
try { try {
@@ -242,10 +245,10 @@ twitch-videoad.js text/javascript
qualityToSelect = 0; qualityToSelect = 0;
} }
} }
var currentQualityLS = window.localStorage.getItem('video-quality'); var currentQualityLS = unsafeWindow.localStorage.getItem('video-quality');
lowQuality[qualityToSelect].click(); lowQuality[qualityToSelect].click();
settingsCog.click(); settingsCog.click();
window.localStorage.setItem('video-quality', currentQualityLS); unsafeWindow.localStorage.setItem('video-quality', currentQualityLS);
if (e.data.value != null) { if (e.data.value != null) {
OriginalVideoPlayerQuality = null; OriginalVideoPlayerQuality = null;
IsPlayerAutoQuality = null; IsPlayerAutoQuality = null;
@@ -292,7 +295,7 @@ twitch-videoad.js text/javascript
} }
}; };
var workerInstance = reinsertWorkers(newWorker, reinsert); var workerInstance = reinsertWorkers(newWorker, reinsert);
Object.defineProperty(window, 'Worker', { Object.defineProperty(unsafeWindow, 'Worker', {
get: function() { get: function() {
return workerInstance; return workerInstance;
}, },
@@ -836,17 +839,44 @@ twitch-videoad.js text/javascript
} catch (err) {} } catch (err) {}
} catch (err) {} } catch (err) {}
} }
window.reloadTwitchPlayer = doTwitchPlayerTask; unsafeWindow.reloadTwitchPlayer = doTwitchPlayerTask;
var localDeviceID = null; var localDeviceID = null;
localDeviceID = window.localStorage.getItem('local_copy_unique_id'); localDeviceID = unsafeWindow.localStorage.getItem('local_copy_unique_id');
function postTwitchWorkerMessage(key, value) { function postTwitchWorkerMessage(key, value) {
twitchWorkers.forEach((worker) => { twitchWorkers.forEach((worker) => {
worker.postMessage({key: key, value: value}); worker.postMessage({key: key, value: value});
}); });
} }
function makeGmXmlHttpRequest(fetchRequest) {
return new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method: fetchRequest.options.method,
url: fetchRequest.url,
data: fetchRequest.options.body,
headers: fetchRequest.options.headers,
onload: response => resolve(response),
onerror: error => reject(error)
});
});
}
// Taken from https://github.com/dimdenGD/YeahTwitter/blob/9e0520f5abe029f57929795d8de0d2e5d3751cf3/us.js#L48
function parseHeaders(headersString) {
const headers = new Headers();
const lines = headersString.trim().split(/[\r\n]+/);
lines.forEach(line => {
const parts = line.split(':');
const header = parts.shift();
const value = parts.join(':');
headers.append(header, value);
});
return headers;
}
var serverLikesThisBrowser = false;
var serverHatesThisBrowser = false;
async function handleWorkerFetchRequest(fetchRequest) { async function handleWorkerFetchRequest(fetchRequest) {
try { try {
const response = await window.realFetch(fetchRequest.url, fetchRequest.options); if (serverLikesThisBrowser || !serverHatesThisBrowser) {
const response = await unsafeWindow.realFetch(fetchRequest.url, fetchRequest.options);
const responseBody = await response.text(); const responseBody = await response.text();
const responseObject = { const responseObject = {
id: fetchRequest.id, id: fetchRequest.id,
@@ -855,7 +885,35 @@ twitch-videoad.js text/javascript
headers: Object.fromEntries(response.headers.entries()), headers: Object.fromEntries(response.headers.entries()),
body: responseBody body: responseBody
}; };
if (responseObject.status === 200) {
var resp = JSON.parse(responseBody);
if (typeof resp.errors !== 'undefined') {
serverHatesThisBrowser = true;
} else {
serverLikesThisBrowser = true;
}
}
if (serverLikesThisBrowser || !serverHatesThisBrowser) {
return responseObject; return responseObject;
}
}
if (typeof GM !== 'undefined' && typeof GM.xmlHttpRequest !== 'undefined') {
fetchRequest.options.headers['User-Agent'] = 'Mozilla/5.0 (X11; Linux i686; rv:140.0) Gecko/20100101 Firefox/140.0';
fetchRequest.options.headers['Referer'] = 'https://www.twitch.tv/';
fetchRequest.options.headers['Origin'] = 'https://www.twitch.tv/';
fetchRequest.options.headers['Host'] = 'gql.twitch.tv';
const response = await makeGmXmlHttpRequest(fetchRequest);
const responseBody = response.responseText;
const responseObject = {
id: fetchRequest.id,
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(parseHeaders(response.responseHeaders).entries()),
body: responseBody
};
return responseObject;
}
throw { message: 'Failed to resolve GQL request. Try the userscript version of the ad blocking solution' };
} catch (error) { } catch (error) {
return { return {
id: fetchRequest.id, id: fetchRequest.id,
@@ -864,12 +922,12 @@ twitch-videoad.js text/javascript
} }
} }
function hookFetch() { function hookFetch() {
var realFetch = window.fetch; var realFetch = unsafeWindow.fetch;
window.realFetch = realFetch; unsafeWindow.realFetch = realFetch;
window.fetch = function(url, init, ...args) { unsafeWindow.fetch = function(url, init, ...args) {
if (typeof url === 'string') { if (typeof url === 'string') {
//Check if squad stream. //Check if squad stream.
if (window.location.pathname.includes('/squad')) { if (unsafeWindow.location.pathname.includes('/squad')) {
postTwitchWorkerMessage('UpdateIsSquadStream', true); postTwitchWorkerMessage('UpdateIsSquadStream', true);
} else { } else {
postTwitchWorkerMessage('UpdateIsSquadStream', false); postTwitchWorkerMessage('UpdateIsSquadStream', false);
@@ -992,13 +1050,13 @@ twitch-videoad.js text/javascript
} }
}catch{} }catch{}
} }
declareOptions(window); declareOptions(unsafeWindow);
hookWindowWorker(); hookWindowWorker();
hookFetch(); hookFetch();
if (document.readyState === "complete" || document.readyState === "loaded" || document.readyState === "interactive") { if (document.readyState === "complete" || document.readyState === "loaded" || document.readyState === "interactive") {
onContentLoaded(); onContentLoaded();
} else { } else {
window.addEventListener("DOMContentLoaded", function() { unsafeWindow.addEventListener("DOMContentLoaded", function() {
onContentLoaded(); onContentLoaded();
}); });
} }
+81 -22
View File
@@ -1,7 +1,7 @@
// ==UserScript== // ==UserScript==
// @name TwitchAdSolutions (vaft) // @name TwitchAdSolutions (vaft)
// @namespace https://github.com/pixeltris/TwitchAdSolutions // @namespace https://github.com/pixeltris/TwitchAdSolutions
// @version 20.0.0 // @version 21.0.0
// @description Multiple solutions for blocking Twitch ads (vaft) // @description Multiple solutions for blocking Twitch ads (vaft)
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/vaft/vaft.user.js // @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/vaft/vaft.user.js
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/vaft/vaft.user.js // @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/vaft/vaft.user.js
@@ -9,17 +9,21 @@
// @match *://*.twitch.tv/* // @match *://*.twitch.tv/*
// @run-at document-start // @run-at document-start
// @inject-into page // @inject-into page
// @grant none // @grant GM.xmlHttpRequest
// @connect gql.twitch.tv
// ==/UserScript== // ==/UserScript==
(function() { (function() {
'use strict'; 'use strict';
var ourTwitchAdSolutionsVersion = 5;// 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 = 6;// 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) { if (typeof unsafeWindow === 'undefined') {
console.log("skipping vaft as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + window.twitchAdSolutionsVersion); unsafeWindow = window;
window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion; }
if (typeof unsafeWindow.twitchAdSolutionsVersion !== 'undefined' && unsafeWindow.twitchAdSolutionsVersion >= ourTwitchAdSolutionsVersion) {
console.log("skipping vaft as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + unsafeWindow.twitchAdSolutionsVersion);
unsafeWindow.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion;
return; return;
} }
window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion; unsafeWindow.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion;
function declareOptions(scope) { function declareOptions(scope) {
scope.AdSignifier = 'stitched'; scope.AdSignifier = 'stitched';
scope.ClientID = 'kimne78kx3ncx6brgo4mv6wki5h1ko'; scope.ClientID = 'kimne78kx3ncx6brgo4mv6wki5h1ko';
@@ -27,7 +31,7 @@
scope.ClientSession = 'null'; scope.ClientSession = 'null';
scope.PlayerType2 = 'embed'; //Source scope.PlayerType2 = 'embed'; //Source
scope.PlayerType3 = 'site'; //Source scope.PlayerType3 = 'site'; //Source
scope.PlayerType4 = 'picture-by-picture'; //360p scope.PlayerType4 = 'autoplay'; //360p
scope.CurrentChannelName = null; scope.CurrentChannelName = null;
scope.UsherParams = null; scope.UsherParams = null;
scope.WasShowingAd = false; scope.WasShowingAd = false;
@@ -102,8 +106,8 @@
|| workerStringReinsert.some((x) => workerString.includes(x)); || workerStringReinsert.some((x) => workerString.includes(x));
} }
function hookWindowWorker() { function hookWindowWorker() {
var reinsert = getWorkersForReinsert(window.Worker); var reinsert = getWorkersForReinsert(unsafeWindow.Worker);
var newWorker = class Worker extends getCleanWorker(window.Worker) { var newWorker = class Worker extends getCleanWorker(unsafeWindow.Worker) {
constructor(twitchBlobUrl, options) { constructor(twitchBlobUrl, options) {
var isTwitchWorker = false; var isTwitchWorker = false;
try { try {
@@ -254,10 +258,10 @@
qualityToSelect = 0; qualityToSelect = 0;
} }
} }
var currentQualityLS = window.localStorage.getItem('video-quality'); var currentQualityLS = unsafeWindow.localStorage.getItem('video-quality');
lowQuality[qualityToSelect].click(); lowQuality[qualityToSelect].click();
settingsCog.click(); settingsCog.click();
window.localStorage.setItem('video-quality', currentQualityLS); unsafeWindow.localStorage.setItem('video-quality', currentQualityLS);
if (e.data.value != null) { if (e.data.value != null) {
OriginalVideoPlayerQuality = null; OriginalVideoPlayerQuality = null;
IsPlayerAutoQuality = null; IsPlayerAutoQuality = null;
@@ -304,7 +308,7 @@
} }
}; };
var workerInstance = reinsertWorkers(newWorker, reinsert); var workerInstance = reinsertWorkers(newWorker, reinsert);
Object.defineProperty(window, 'Worker', { Object.defineProperty(unsafeWindow, 'Worker', {
get: function() { get: function() {
return workerInstance; return workerInstance;
}, },
@@ -848,17 +852,44 @@
} catch (err) {} } catch (err) {}
} catch (err) {} } catch (err) {}
} }
window.reloadTwitchPlayer = doTwitchPlayerTask; unsafeWindow.reloadTwitchPlayer = doTwitchPlayerTask;
var localDeviceID = null; var localDeviceID = null;
localDeviceID = window.localStorage.getItem('local_copy_unique_id'); localDeviceID = unsafeWindow.localStorage.getItem('local_copy_unique_id');
function postTwitchWorkerMessage(key, value) { function postTwitchWorkerMessage(key, value) {
twitchWorkers.forEach((worker) => { twitchWorkers.forEach((worker) => {
worker.postMessage({key: key, value: value}); worker.postMessage({key: key, value: value});
}); });
} }
function makeGmXmlHttpRequest(fetchRequest) {
return new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method: fetchRequest.options.method,
url: fetchRequest.url,
data: fetchRequest.options.body,
headers: fetchRequest.options.headers,
onload: response => resolve(response),
onerror: error => reject(error)
});
});
}
// Taken from https://github.com/dimdenGD/YeahTwitter/blob/9e0520f5abe029f57929795d8de0d2e5d3751cf3/us.js#L48
function parseHeaders(headersString) {
const headers = new Headers();
const lines = headersString.trim().split(/[\r\n]+/);
lines.forEach(line => {
const parts = line.split(':');
const header = parts.shift();
const value = parts.join(':');
headers.append(header, value);
});
return headers;
}
var serverLikesThisBrowser = false;
var serverHatesThisBrowser = false;
async function handleWorkerFetchRequest(fetchRequest) { async function handleWorkerFetchRequest(fetchRequest) {
try { try {
const response = await window.realFetch(fetchRequest.url, fetchRequest.options); if (serverLikesThisBrowser || !serverHatesThisBrowser) {
const response = await unsafeWindow.realFetch(fetchRequest.url, fetchRequest.options);
const responseBody = await response.text(); const responseBody = await response.text();
const responseObject = { const responseObject = {
id: fetchRequest.id, id: fetchRequest.id,
@@ -867,7 +898,35 @@
headers: Object.fromEntries(response.headers.entries()), headers: Object.fromEntries(response.headers.entries()),
body: responseBody body: responseBody
}; };
if (responseObject.status === 200) {
var resp = JSON.parse(responseBody);
if (typeof resp.errors !== 'undefined') {
serverHatesThisBrowser = true;
} else {
serverLikesThisBrowser = true;
}
}
if (serverLikesThisBrowser || !serverHatesThisBrowser) {
return responseObject; return responseObject;
}
}
if (typeof GM !== 'undefined' && typeof GM.xmlHttpRequest !== 'undefined') {
fetchRequest.options.headers['User-Agent'] = 'Mozilla/5.0 (X11; Linux i686; rv:140.0) Gecko/20100101 Firefox/140.0';
fetchRequest.options.headers['Referer'] = 'https://www.twitch.tv/';
fetchRequest.options.headers['Origin'] = 'https://www.twitch.tv/';
fetchRequest.options.headers['Host'] = 'gql.twitch.tv';
const response = await makeGmXmlHttpRequest(fetchRequest);
const responseBody = response.responseText;
const responseObject = {
id: fetchRequest.id,
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(parseHeaders(response.responseHeaders).entries()),
body: responseBody
};
return responseObject;
}
throw { message: 'Failed to resolve GQL request. Try the userscript version of the ad blocking solution' };
} catch (error) { } catch (error) {
return { return {
id: fetchRequest.id, id: fetchRequest.id,
@@ -876,12 +935,12 @@
} }
} }
function hookFetch() { function hookFetch() {
var realFetch = window.fetch; var realFetch = unsafeWindow.fetch;
window.realFetch = realFetch; unsafeWindow.realFetch = realFetch;
window.fetch = function(url, init, ...args) { unsafeWindow.fetch = function(url, init, ...args) {
if (typeof url === 'string') { if (typeof url === 'string') {
//Check if squad stream. //Check if squad stream.
if (window.location.pathname.includes('/squad')) { if (unsafeWindow.location.pathname.includes('/squad')) {
postTwitchWorkerMessage('UpdateIsSquadStream', true); postTwitchWorkerMessage('UpdateIsSquadStream', true);
} else { } else {
postTwitchWorkerMessage('UpdateIsSquadStream', false); postTwitchWorkerMessage('UpdateIsSquadStream', false);
@@ -1004,13 +1063,13 @@
} }
}catch{} }catch{}
} }
declareOptions(window); declareOptions(unsafeWindow);
hookWindowWorker(); hookWindowWorker();
hookFetch(); hookFetch();
if (document.readyState === "complete" || document.readyState === "loaded" || document.readyState === "interactive") { if (document.readyState === "complete" || document.readyState === "loaded" || document.readyState === "interactive") {
onContentLoaded(); onContentLoaded();
} else { } else {
window.addEventListener("DOMContentLoaded", function() { unsafeWindow.addEventListener("DOMContentLoaded", function() {
onContentLoaded(); onContentLoaded();
}); });
} }
+74 -16
View File
@@ -1,19 +1,22 @@
twitch-videoad.js text/javascript twitch-videoad.js text/javascript
(function() { (function() {
if ( /(^|\.)twitch\.tv$/.test(document.location.hostname) === false ) { return; } if ( /(^|\.)twitch\.tv$/.test(document.location.hostname) === false ) { return; }
var ourTwitchAdSolutionsVersion = 5;// 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 = 6;// 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) { if (typeof unsafeWindow === 'undefined') {
console.log("skipping video-swap-new as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + window.twitchAdSolutionsVersion); unsafeWindow = window;
window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion; }
if (typeof unsafeWindow.twitchAdSolutionsVersion !== 'undefined' && unsafeWindow.twitchAdSolutionsVersion >= ourTwitchAdSolutionsVersion) {
console.log("skipping video-swap-new as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + unsafeWindow.twitchAdSolutionsVersion);
unsafeWindow.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion;
return; return;
} }
window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion; unsafeWindow.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion;
function declareOptions(scope) { function declareOptions(scope) {
// Options / globals // Options / globals
scope.OPT_MODE_STRIP_AD_SEGMENTS = true; scope.OPT_MODE_STRIP_AD_SEGMENTS = true;
scope.OPT_MODE_NOTIFY_ADS_WATCHED = true; scope.OPT_MODE_NOTIFY_ADS_WATCHED = true;
scope.OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS = false; scope.OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS = false;
scope.OPT_BACKUP_PLAYER_TYPE = 'picture-by-picture'; scope.OPT_BACKUP_PLAYER_TYPE = 'autoplay';
scope.OPT_BACKUP_PLATFORM = 'ios'; scope.OPT_BACKUP_PLATFORM = 'ios';
scope.OPT_REGULAR_PLAYER_TYPE = 'site'; scope.OPT_REGULAR_PLAYER_TYPE = 'site';
scope.OPT_ACCESS_TOKEN_PLAYER_TYPE = null; scope.OPT_ACCESS_TOKEN_PLAYER_TYPE = null;
@@ -89,8 +92,8 @@ twitch-videoad.js text/javascript
|| workerStringReinsert.some((x) => workerString.includes(x)); || workerStringReinsert.some((x) => workerString.includes(x));
} }
function hookWindowWorker() { function hookWindowWorker() {
var reinsert = getWorkersForReinsert(window.Worker); var reinsert = getWorkersForReinsert(unsafeWindow.Worker);
var newWorker = class Worker extends getCleanWorker(window.Worker) { var newWorker = class Worker extends getCleanWorker(unsafeWindow.Worker) {
constructor(twitchBlobUrl, options) { constructor(twitchBlobUrl, options) {
var isTwitchWorker = false; var isTwitchWorker = false;
try { try {
@@ -199,7 +202,7 @@ twitch-videoad.js text/javascript
} }
} }
var workerInstance = reinsertWorkers(newWorker, reinsert); var workerInstance = reinsertWorkers(newWorker, reinsert);
Object.defineProperty(window, 'Worker', { Object.defineProperty(unsafeWindow, 'Worker', {
get: function() { get: function() {
return workerInstance; return workerInstance;
}, },
@@ -573,9 +576,36 @@ twitch-videoad.js text/javascript
worker.postMessage({key: key, value: value}); worker.postMessage({key: key, value: value});
}); });
} }
function makeGmXmlHttpRequest(fetchRequest) {
return new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method: fetchRequest.options.method,
url: fetchRequest.url,
data: fetchRequest.options.body,
headers: fetchRequest.options.headers,
onload: response => resolve(response),
onerror: error => reject(error)
});
});
}
// Taken from https://github.com/dimdenGD/YeahTwitter/blob/9e0520f5abe029f57929795d8de0d2e5d3751cf3/us.js#L48
function parseHeaders(headersString) {
const headers = new Headers();
const lines = headersString.trim().split(/[\r\n]+/);
lines.forEach(line => {
const parts = line.split(':');
const header = parts.shift();
const value = parts.join(':');
headers.append(header, value);
});
return headers;
}
var serverLikesThisBrowser = false;
var serverHatesThisBrowser = false;
async function handleWorkerFetchRequest(fetchRequest) { async function handleWorkerFetchRequest(fetchRequest) {
try { try {
const response = await window.realFetch(fetchRequest.url, fetchRequest.options); if (serverLikesThisBrowser || !serverHatesThisBrowser) {
const response = await unsafeWindow.realFetch(fetchRequest.url, fetchRequest.options);
const responseBody = await response.text(); const responseBody = await response.text();
const responseObject = { const responseObject = {
id: fetchRequest.id, id: fetchRequest.id,
@@ -584,7 +614,35 @@ twitch-videoad.js text/javascript
headers: Object.fromEntries(response.headers.entries()), headers: Object.fromEntries(response.headers.entries()),
body: responseBody body: responseBody
}; };
if (responseObject.status === 200) {
var resp = JSON.parse(responseBody);
if (typeof resp.errors !== 'undefined') {
serverHatesThisBrowser = true;
} else {
serverLikesThisBrowser = true;
}
}
if (serverLikesThisBrowser || !serverHatesThisBrowser) {
return responseObject; return responseObject;
}
}
if (typeof GM !== 'undefined' && typeof GM.xmlHttpRequest !== 'undefined') {
fetchRequest.options.headers['User-Agent'] = 'Mozilla/5.0 (X11; Linux i686; rv:140.0) Gecko/20100101 Firefox/140.0';
fetchRequest.options.headers['Referer'] = 'https://www.twitch.tv/';
fetchRequest.options.headers['Origin'] = 'https://www.twitch.tv/';
fetchRequest.options.headers['Host'] = 'gql.twitch.tv';
const response = await makeGmXmlHttpRequest(fetchRequest);
const responseBody = response.responseText;
const responseObject = {
id: fetchRequest.id,
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(parseHeaders(response.responseHeaders).entries()),
body: responseBody
};
return responseObject;
}
throw { message: 'Failed to resolve GQL request. Try the userscript version of the ad blocking solution' };
} catch (error) { } catch (error) {
return { return {
id: fetchRequest.id, id: fetchRequest.id,
@@ -593,9 +651,9 @@ twitch-videoad.js text/javascript
} }
} }
function hookFetch() { function hookFetch() {
var realFetch = window.fetch; var realFetch = unsafeWindow.fetch;
window.realFetch = realFetch; unsafeWindow.realFetch = realFetch;
window.fetch = function(url, init, ...args) { unsafeWindow.fetch = function(url, init, ...args) {
if (typeof url === 'string') { if (typeof url === 'string') {
if (url.includes('gql')) { if (url.includes('gql')) {
var deviceId = init.headers['X-Device-Id']; var deviceId = init.headers['X-Device-Id'];
@@ -789,14 +847,14 @@ twitch-videoad.js text/javascript
return realGetItem.apply(this, arguments); return realGetItem.apply(this, arguments);
}; };
} }
window.reloadTwitchPlayer = reloadTwitchPlayer; unsafeWindow.reloadTwitchPlayer = reloadTwitchPlayer;
declareOptions(window); declareOptions(unsafeWindow);
hookWindowWorker(); hookWindowWorker();
hookFetch(); hookFetch();
if (document.readyState === "complete" || document.readyState === "loaded" || document.readyState === "interactive") { if (document.readyState === "complete" || document.readyState === "loaded" || document.readyState === "interactive") {
onContentLoaded(); onContentLoaded();
} else { } else {
window.addEventListener("DOMContentLoaded", function() { unsafeWindow.addEventListener("DOMContentLoaded", function() {
onContentLoaded(); onContentLoaded();
}); });
} }
+77 -18
View File
@@ -1,7 +1,7 @@
// ==UserScript== // ==UserScript==
// @name TwitchAdSolutions (video-swap-new) // @name TwitchAdSolutions (video-swap-new)
// @namespace https://github.com/pixeltris/TwitchAdSolutions // @namespace https://github.com/pixeltris/TwitchAdSolutions
// @version 1.38 // @version 1.39
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/video-swap-new/video-swap-new.user.js // @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 // @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) // @description Multiple solutions for blocking Twitch ads (video-swap-new)
@@ -9,23 +9,27 @@
// @match *://*.twitch.tv/* // @match *://*.twitch.tv/*
// @run-at document-start // @run-at document-start
// @inject-into page // @inject-into page
// @grant none // @grant GM.xmlHttpRequest
// @connect gql.twitch.tv
// ==/UserScript== // ==/UserScript==
(function() { (function() {
'use strict'; 'use strict';
var ourTwitchAdSolutionsVersion = 5;// 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 = 6;// 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) { if (typeof unsafeWindow === 'undefined') {
console.log("skipping video-swap-new as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + window.twitchAdSolutionsVersion); unsafeWindow = window;
window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion; }
if (typeof unsafeWindow.twitchAdSolutionsVersion !== 'undefined' && unsafeWindow.twitchAdSolutionsVersion >= ourTwitchAdSolutionsVersion) {
console.log("skipping video-swap-new as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + unsafeWindow.twitchAdSolutionsVersion);
unsafeWindow.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion;
return; return;
} }
window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion; unsafeWindow.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion;
function declareOptions(scope) { function declareOptions(scope) {
// Options / globals // Options / globals
scope.OPT_MODE_STRIP_AD_SEGMENTS = true; scope.OPT_MODE_STRIP_AD_SEGMENTS = true;
scope.OPT_MODE_NOTIFY_ADS_WATCHED = true; scope.OPT_MODE_NOTIFY_ADS_WATCHED = true;
scope.OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS = false; scope.OPT_MODE_NOTIFY_ADS_WATCHED_MIN_REQUESTS = false;
scope.OPT_BACKUP_PLAYER_TYPE = 'picture-by-picture'; scope.OPT_BACKUP_PLAYER_TYPE = 'autoplay';
scope.OPT_BACKUP_PLATFORM = 'ios'; scope.OPT_BACKUP_PLATFORM = 'ios';
scope.OPT_REGULAR_PLAYER_TYPE = 'site'; scope.OPT_REGULAR_PLAYER_TYPE = 'site';
scope.OPT_ACCESS_TOKEN_PLAYER_TYPE = null; scope.OPT_ACCESS_TOKEN_PLAYER_TYPE = null;
@@ -101,8 +105,8 @@
|| workerStringReinsert.some((x) => workerString.includes(x)); || workerStringReinsert.some((x) => workerString.includes(x));
} }
function hookWindowWorker() { function hookWindowWorker() {
var reinsert = getWorkersForReinsert(window.Worker); var reinsert = getWorkersForReinsert(unsafeWindow.Worker);
var newWorker = class Worker extends getCleanWorker(window.Worker) { var newWorker = class Worker extends getCleanWorker(unsafeWindow.Worker) {
constructor(twitchBlobUrl, options) { constructor(twitchBlobUrl, options) {
var isTwitchWorker = false; var isTwitchWorker = false;
try { try {
@@ -211,7 +215,7 @@
} }
} }
var workerInstance = reinsertWorkers(newWorker, reinsert); var workerInstance = reinsertWorkers(newWorker, reinsert);
Object.defineProperty(window, 'Worker', { Object.defineProperty(unsafeWindow, 'Worker', {
get: function() { get: function() {
return workerInstance; return workerInstance;
}, },
@@ -585,9 +589,36 @@
worker.postMessage({key: key, value: value}); worker.postMessage({key: key, value: value});
}); });
} }
function makeGmXmlHttpRequest(fetchRequest) {
return new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method: fetchRequest.options.method,
url: fetchRequest.url,
data: fetchRequest.options.body,
headers: fetchRequest.options.headers,
onload: response => resolve(response),
onerror: error => reject(error)
});
});
}
// Taken from https://github.com/dimdenGD/YeahTwitter/blob/9e0520f5abe029f57929795d8de0d2e5d3751cf3/us.js#L48
function parseHeaders(headersString) {
const headers = new Headers();
const lines = headersString.trim().split(/[\r\n]+/);
lines.forEach(line => {
const parts = line.split(':');
const header = parts.shift();
const value = parts.join(':');
headers.append(header, value);
});
return headers;
}
var serverLikesThisBrowser = false;
var serverHatesThisBrowser = false;
async function handleWorkerFetchRequest(fetchRequest) { async function handleWorkerFetchRequest(fetchRequest) {
try { try {
const response = await window.realFetch(fetchRequest.url, fetchRequest.options); if (serverLikesThisBrowser || !serverHatesThisBrowser) {
const response = await unsafeWindow.realFetch(fetchRequest.url, fetchRequest.options);
const responseBody = await response.text(); const responseBody = await response.text();
const responseObject = { const responseObject = {
id: fetchRequest.id, id: fetchRequest.id,
@@ -596,7 +627,35 @@
headers: Object.fromEntries(response.headers.entries()), headers: Object.fromEntries(response.headers.entries()),
body: responseBody body: responseBody
}; };
if (responseObject.status === 200) {
var resp = JSON.parse(responseBody);
if (typeof resp.errors !== 'undefined') {
serverHatesThisBrowser = true;
} else {
serverLikesThisBrowser = true;
}
}
if (serverLikesThisBrowser || !serverHatesThisBrowser) {
return responseObject; return responseObject;
}
}
if (typeof GM !== 'undefined' && typeof GM.xmlHttpRequest !== 'undefined') {
fetchRequest.options.headers['User-Agent'] = 'Mozilla/5.0 (X11; Linux i686; rv:140.0) Gecko/20100101 Firefox/140.0';
fetchRequest.options.headers['Referer'] = 'https://www.twitch.tv/';
fetchRequest.options.headers['Origin'] = 'https://www.twitch.tv/';
fetchRequest.options.headers['Host'] = 'gql.twitch.tv';
const response = await makeGmXmlHttpRequest(fetchRequest);
const responseBody = response.responseText;
const responseObject = {
id: fetchRequest.id,
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(parseHeaders(response.responseHeaders).entries()),
body: responseBody
};
return responseObject;
}
throw { message: 'Failed to resolve GQL request. Try the userscript version of the ad blocking solution' };
} catch (error) { } catch (error) {
return { return {
id: fetchRequest.id, id: fetchRequest.id,
@@ -605,9 +664,9 @@
} }
} }
function hookFetch() { function hookFetch() {
var realFetch = window.fetch; var realFetch = unsafeWindow.fetch;
window.realFetch = realFetch; unsafeWindow.realFetch = realFetch;
window.fetch = function(url, init, ...args) { unsafeWindow.fetch = function(url, init, ...args) {
if (typeof url === 'string') { if (typeof url === 'string') {
if (url.includes('gql')) { if (url.includes('gql')) {
var deviceId = init.headers['X-Device-Id']; var deviceId = init.headers['X-Device-Id'];
@@ -801,14 +860,14 @@
return realGetItem.apply(this, arguments); return realGetItem.apply(this, arguments);
}; };
} }
window.reloadTwitchPlayer = reloadTwitchPlayer; unsafeWindow.reloadTwitchPlayer = reloadTwitchPlayer;
declareOptions(window); declareOptions(unsafeWindow);
hookWindowWorker(); hookWindowWorker();
hookFetch(); hookFetch();
if (document.readyState === "complete" || document.readyState === "loaded" || document.readyState === "interactive") { if (document.readyState === "complete" || document.readyState === "loaded" || document.readyState === "interactive") {
onContentLoaded(); onContentLoaded();
} else { } else {
window.addEventListener("DOMContentLoaded", function() { unsafeWindow.addEventListener("DOMContentLoaded", function() {
onContentLoaded(); onContentLoaded();
}); });
} }