mirror of
https://github.com/pixeltris/TwitchAdSolutions.git
synced 2024-11-22 10:22:51 +01:00
Fix loading bug on offline streams for vaft #88
This commit is contained in:
parent
b9a895cc5a
commit
e80025cbd1
@ -14,8 +14,8 @@ Proxies are the most reliable way of avoiding ads ([buffering / downtime info](f
|
|||||||
Alternatively:
|
Alternatively:
|
||||||
|
|
||||||
- `Alternate Player for Twitch.tv` - [chrome](https://chrome.google.com/webstore/detail/alternate-player-for-twit/bhplkbgoehhhddaoolmakpocnenplmhf) / [firefox](https://addons.mozilla.org/en-US/firefox/addon/twitch_5/)
|
- `Alternate Player for Twitch.tv` - [chrome](https://chrome.google.com/webstore/detail/alternate-player-for-twit/bhplkbgoehhhddaoolmakpocnenplmhf) / [firefox](https://addons.mozilla.org/en-US/firefox/addon/twitch_5/)
|
||||||
- `ttv-ublock` - [chrome](https://chrome.google.com/webstore/detail/ttv-ad-block/kndhknfnihidhcfnaacnndbolonbimai) / [firefox](https://addons.mozilla.org/en-US/firefox/addon/ttv-adblock/) / [code](https://github.com/odensc/ttv-ublock) - *(possible periodic ablocker warning)*
|
- `ttv-ublock` - [chrome](https://chrome.google.com/webstore/detail/ttv-ad-block/kndhknfnihidhcfnaacnndbolonbimai) / [firefox](https://addons.mozilla.org/en-US/firefox/addon/ttv-adblock/) / [code](https://github.com/odensc/ttv-ublock) - *(possible periodic adblocker warning)*
|
||||||
- `Video Ad-Block, for Twitch` (fork) - [chrome](https://chrome.google.com/webstore/detail/twitch-adblock/ljhnljhabgjcihjoihakgdiicdjncpkd) / [firefox](https://addons.mozilla.org/en-US/firefox/addon/twitch-adblock/) / [code](https://github.com/cleanlock/VideoAdBlockForTwitch) - *(ablocker warning during ads)*
|
- `Video Ad-Block, for Twitch` (fork) - [chrome](https://chrome.google.com/webstore/detail/twitch-adblock/ljhnljhabgjcihjoihakgdiicdjncpkd) / [firefox](https://addons.mozilla.org/en-US/firefox/addon/twitch-adblock/) / [code](https://github.com/cleanlock/VideoAdBlockForTwitch) - *(adblocker warning during ads)*
|
||||||
- `vaft` - see below
|
- `vaft` - see below
|
||||||
|
|
||||||
[Read this for a full list and descriptions.](full-list.md)
|
[Read this for a full list and descriptions.](full-list.md)
|
||||||
|
@ -45,16 +45,12 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
//Send settings updates to worker.
|
//Send settings updates to worker.
|
||||||
window.addEventListener("message", (event) => {
|
window.addEventListener('message', (event) => {
|
||||||
if (event.source != window)
|
if (event.source != window) {
|
||||||
return;
|
return;
|
||||||
if (event.data.type && (event.data.type == "SetHideBlockingMessage")) {
|
}
|
||||||
if (twitchMainWorker) {
|
if (event.data.type && event.data.type == 'SetTwitchAdblockSettings' && event.data.settings) {
|
||||||
twitchMainWorker.postMessage({
|
TwitchAdblockSettings = event.data.settings;
|
||||||
key: 'SetHideBlockingMessage',
|
|
||||||
value: event.data.value
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, false);
|
}, false);
|
||||||
function declareOptions(scope) {
|
function declareOptions(scope) {
|
||||||
@ -70,14 +66,22 @@ twitch-videoad.js application/javascript
|
|||||||
scope.UsherParams = null;
|
scope.UsherParams = null;
|
||||||
scope.WasShowingAd = false;
|
scope.WasShowingAd = false;
|
||||||
scope.GQLDeviceID = null;
|
scope.GQLDeviceID = null;
|
||||||
scope.HideBlockingMessage = false;
|
|
||||||
scope.IsSquadStream = false;
|
scope.IsSquadStream = false;
|
||||||
scope.StreamInfos = [];
|
scope.StreamInfos = [];
|
||||||
scope.StreamInfosByUrl = [];
|
scope.StreamInfosByUrl = [];
|
||||||
scope.MainUrlByUrl = [];
|
scope.MainUrlByUrl = [];
|
||||||
scope.EncodingCacheTimeout = 60000;
|
scope.EncodingCacheTimeout = 60000;
|
||||||
|
scope.DefaultProxyType = 'TTV LOL';
|
||||||
|
scope.DefaultForcedQuality = null;
|
||||||
|
scope.DefaultProxyQuality = null;
|
||||||
}
|
}
|
||||||
declareOptions(window);
|
declareOptions(window);
|
||||||
|
var TwitchAdblockSettings = {
|
||||||
|
BannerVisible: true,
|
||||||
|
ForcedQuality: null,
|
||||||
|
ProxyType: null,
|
||||||
|
ProxyQuality: null,
|
||||||
|
};
|
||||||
var twitchMainWorker = null;
|
var twitchMainWorker = null;
|
||||||
var adBlockDiv = null;
|
var adBlockDiv = null;
|
||||||
var OriginalVideoPlayerQuality = null;
|
var OriginalVideoPlayerQuality = null;
|
||||||
@ -107,6 +111,7 @@ twitch-videoad.js application/javascript
|
|||||||
${tryNotifyTwitch.toString()}
|
${tryNotifyTwitch.toString()}
|
||||||
${parseAttributes.toString()}
|
${parseAttributes.toString()}
|
||||||
declareOptions(self);
|
declareOptions(self);
|
||||||
|
self.TwitchAdblockSettings = ${JSON.stringify(TwitchAdblockSettings)};
|
||||||
self.addEventListener('message', function(e) {
|
self.addEventListener('message', function(e) {
|
||||||
if (e.data.key == 'UpdateIsSquadStream') {
|
if (e.data.key == 'UpdateIsSquadStream') {
|
||||||
IsSquadStream = e.data.value;
|
IsSquadStream = e.data.value;
|
||||||
@ -118,12 +123,6 @@ twitch-videoad.js application/javascript
|
|||||||
ClientID = e.data.value;
|
ClientID = e.data.value;
|
||||||
} else if (e.data.key == 'UpdateDeviceId') {
|
} else if (e.data.key == 'UpdateDeviceId') {
|
||||||
GQLDeviceID = e.data.value;
|
GQLDeviceID = e.data.value;
|
||||||
} else if (e.data.key == 'SetHideBlockingMessage') {
|
|
||||||
if (e.data.value == "true") {
|
|
||||||
HideBlockingMessage = false;
|
|
||||||
} else if (e.data.value == "false") {
|
|
||||||
HideBlockingMessage = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
hookWorkerFetch();
|
hookWorkerFetch();
|
||||||
@ -133,6 +132,9 @@ twitch-videoad.js application/javascript
|
|||||||
twitchMainWorker = this;
|
twitchMainWorker = this;
|
||||||
this.onmessage = function(e) {
|
this.onmessage = function(e) {
|
||||||
if (e.data.key == 'ShowAdBlockBanner') {
|
if (e.data.key == 'ShowAdBlockBanner') {
|
||||||
|
if (!TwitchAdblockSettings.BannerVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (adBlockDiv == null) {
|
if (adBlockDiv == null) {
|
||||||
adBlockDiv = getAdBlockDiv();
|
adBlockDiv = getAdBlockDiv();
|
||||||
}
|
}
|
||||||
@ -301,29 +303,39 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
var processAfter = async function(response) {
|
var processAfter = async function(response) {
|
||||||
encodingsM3u8 = await response.text();
|
if (response.status == 200) {
|
||||||
var streamInfo = StreamInfos[channelName];
|
encodingsM3u8 = await response.text();
|
||||||
if (streamInfo == null) {
|
var streamInfo = StreamInfos[channelName];
|
||||||
StreamInfos[channelName] = streamInfo = {};
|
if (streamInfo == null) {
|
||||||
}
|
StreamInfos[channelName] = streamInfo = {};
|
||||||
streamInfo.ChannelName = channelName;
|
|
||||||
streamInfo.Urls = [];// xxx.m3u8 -> "284x160" (resolution)
|
|
||||||
streamInfo.EncodingsM3U8Cache = [];
|
|
||||||
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
|
||||||
for (var i = 0; i < lines.length; i++) {
|
|
||||||
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
|
||||||
streamInfo.Urls[lines[i]] = -1;
|
|
||||||
if (i > 0 && lines[i - 1].startsWith('#EXT-X-STREAM-INF')) {
|
|
||||||
var res = parseAttributes(lines[i - 1])['RESOLUTION'];
|
|
||||||
if (res) {
|
|
||||||
streamInfo.Urls[lines[i]] = res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
StreamInfosByUrl[lines[i]] = streamInfo;
|
|
||||||
MainUrlByUrl[lines[i]] = url;
|
|
||||||
}
|
}
|
||||||
|
streamInfo.ChannelName = channelName;
|
||||||
|
streamInfo.Urls = [];// xxx.m3u8 -> { Resolution: "284x160", FrameRate: 30.0 }
|
||||||
|
streamInfo.EncodingsM3U8Cache = [];
|
||||||
|
streamInfo.EncodingsM3U8 = encodingsM3u8;
|
||||||
|
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
||||||
|
for (var i = 0; i < lines.length; i++) {
|
||||||
|
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
||||||
|
streamInfo.Urls[lines[i]] = -1;
|
||||||
|
if (i > 0 && lines[i - 1].startsWith('#EXT-X-STREAM-INF')) {
|
||||||
|
var attributes = parseAttributes(lines[i - 1]);
|
||||||
|
var resolution = attributes['RESOLUTION'];
|
||||||
|
var frameRate = attributes['FRAME-RATE'];
|
||||||
|
if (resolution) {
|
||||||
|
streamInfo.Urls[lines[i]] = {
|
||||||
|
Resolution: resolution,
|
||||||
|
FrameRate: frameRate
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StreamInfosByUrl[lines[i]] = streamInfo;
|
||||||
|
MainUrlByUrl[lines[i]] = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolve(new Response(encodingsM3u8));
|
||||||
|
} else {
|
||||||
|
resolve(response);
|
||||||
}
|
}
|
||||||
resolve(new Response(encodingsM3u8));
|
|
||||||
};
|
};
|
||||||
var send = function() {
|
var send = function() {
|
||||||
return realFetch(url, options).then(function(response) {
|
return realFetch(url, options).then(function(response) {
|
||||||
@ -339,46 +351,84 @@ twitch-videoad.js application/javascript
|
|||||||
return realFetch.apply(this, arguments);
|
return realFetch.apply(this, arguments);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function getStreamUrlForResolution(targetResolution, encodingsM3u8) {
|
function getStreamUrlForResolution(encodingsM3u8, resolutionInfo, qualityOverrideStr) {
|
||||||
|
var qualityOverride = 0;
|
||||||
|
if (qualityOverrideStr && qualityOverrideStr.endsWith('p')) {
|
||||||
|
qualityOverride = qualityOverrideStr.substr(0, qualityOverrideStr.length - 1) | 0;
|
||||||
|
}
|
||||||
|
var qualityOverrideFoundQuality = 0;
|
||||||
|
var qualityOverrideFoundFrameRate = 0;
|
||||||
var encodingsLines = encodingsM3u8.replace('\r', '').split('\n');
|
var encodingsLines = encodingsM3u8.replace('\r', '').split('\n');
|
||||||
var firstUrl = null;
|
var firstUrl = null;
|
||||||
|
var lastUrl = null;
|
||||||
|
var matchedResolutionUrl = null;
|
||||||
|
var matchedFrameRate = false;
|
||||||
for (var i = 0; i < encodingsLines.length; i++) {
|
for (var i = 0; i < encodingsLines.length; i++) {
|
||||||
if (!encodingsLines[i].startsWith('#') && encodingsLines[i].includes('.m3u8')) {
|
if (!encodingsLines[i].startsWith('#') && encodingsLines[i].includes('.m3u8')) {
|
||||||
if (i > 0 && encodingsLines[i - 1].startsWith('#EXT-X-STREAM-INF')) {
|
if (i > 0 && encodingsLines[i - 1].startsWith('#EXT-X-STREAM-INF')) {
|
||||||
var res = parseAttributes(encodingsLines[i - 1])['RESOLUTION'];
|
var attributes = parseAttributes(encodingsLines[i - 1]);
|
||||||
if (res && (!targetResolution || res == targetResolution)) {
|
var resolution = attributes['RESOLUTION'];
|
||||||
return encodingsLines[i];
|
var frameRate = attributes['FRAME-RATE'];
|
||||||
|
if (resolution) {
|
||||||
|
if (qualityOverride) {
|
||||||
|
var quality = resolution.toLowerCase().split('x')[1];
|
||||||
|
if (quality == qualityOverride) {
|
||||||
|
qualityOverrideFoundQuality = quality;
|
||||||
|
qualityOverrideFoundFrameRate = frameRate;
|
||||||
|
matchedResolutionUrl = encodingsLines[i];
|
||||||
|
if (frameRate < 40) {
|
||||||
|
//console.log(`qualityOverride(A) quality:${quality} frameRate:${frameRate}`);
|
||||||
|
return matchedResolutionUrl;
|
||||||
|
}
|
||||||
|
} else if (quality < qualityOverride) {
|
||||||
|
//if (matchedResolutionUrl) {
|
||||||
|
// console.log(`qualityOverride(B) quality:${qualityOverrideFoundQuality} frameRate:${qualityOverrideFoundFrameRate}`);
|
||||||
|
//} else {
|
||||||
|
// console.log(`qualityOverride(C) quality:${quality} frameRate:${frameRate}`);
|
||||||
|
//}
|
||||||
|
return matchedResolutionUrl ? matchedResolutionUrl : encodingsLines[i];
|
||||||
|
}
|
||||||
|
} else if ((!resolutionInfo || resolution == resolutionInfo.Resolution) &&
|
||||||
|
(!matchedResolutionUrl || (!matchedFrameRate && frameRate == resolutionInfo.FrameRate))) {
|
||||||
|
matchedResolutionUrl = encodingsLines[i];
|
||||||
|
matchedFrameRate = frameRate == resolutionInfo.FrameRate;
|
||||||
|
if (matchedFrameRate) {
|
||||||
|
return matchedResolutionUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (firstUrl == null) {
|
if (firstUrl == null) {
|
||||||
firstUrl = encodingsLines[i];
|
firstUrl = encodingsLines[i];
|
||||||
}
|
}
|
||||||
|
lastUrl = encodingsLines[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return firstUrl;
|
if (qualityOverride) {
|
||||||
|
return lastUrl;
|
||||||
|
}
|
||||||
|
return matchedResolutionUrl ? matchedResolutionUrl : firstUrl;
|
||||||
}
|
}
|
||||||
async function getStreamForResolution(streamInfo, targetResolution, encodingsM3u8, fallbackStreamStr, playerType, realFetch) {
|
async function getStreamForResolution(streamInfo, resolutionInfo, encodingsM3u8, fallbackStreamStr, playerType, realFetch) {
|
||||||
if (streamInfo.EncodingsM3U8Cache[playerType].Resolution != targetResolution ||
|
var qualityOverride = null;
|
||||||
|
if (playerType === 'proxy') {
|
||||||
|
qualityOverride = TwitchAdblockSettings.ProxyQuality ? TwitchAdblockSettings.ProxyQuality : DefaultProxyQuality;
|
||||||
|
}
|
||||||
|
if (streamInfo.EncodingsM3U8Cache[playerType].Resolution != resolutionInfo.Resolution ||
|
||||||
streamInfo.EncodingsM3U8Cache[playerType].RequestTime < Date.now() - EncodingCacheTimeout) {
|
streamInfo.EncodingsM3U8Cache[playerType].RequestTime < Date.now() - EncodingCacheTimeout) {
|
||||||
console.log(`Blocking ads (type:${playerType}, resolution:${targetResolution})`);
|
console.log(`Blocking ads (type:${playerType}, resolution:${resolutionInfo.Resolution}, frameRate:${resolutionInfo.FrameRate}, qualityOverride:${qualityOverride})`);
|
||||||
}
|
}
|
||||||
streamInfo.EncodingsM3U8Cache[playerType].RequestTime = Date.now();
|
streamInfo.EncodingsM3U8Cache[playerType].RequestTime = Date.now();
|
||||||
streamInfo.EncodingsM3U8Cache[playerType].Value = encodingsM3u8;
|
streamInfo.EncodingsM3U8Cache[playerType].Value = encodingsM3u8;
|
||||||
streamInfo.EncodingsM3U8Cache[playerType].Resolution = targetResolution;
|
streamInfo.EncodingsM3U8Cache[playerType].Resolution = resolutionInfo.Resolution;
|
||||||
var streamM3u8Url = getStreamUrlForResolution(targetResolution, encodingsM3u8);
|
var streamM3u8Url = getStreamUrlForResolution(encodingsM3u8, resolutionInfo, qualityOverride);
|
||||||
var streamM3u8Response = await realFetch(streamM3u8Url);
|
var streamM3u8Response = await realFetch(streamM3u8Url);
|
||||||
if (streamM3u8Response.status == 200) {
|
if (streamM3u8Response.status == 200) {
|
||||||
var m3u8Text = await streamM3u8Response.text();
|
var m3u8Text = await streamM3u8Response.text();
|
||||||
WasShowingAd = true;
|
WasShowingAd = true;
|
||||||
if (HideBlockingMessage == false) {
|
postMessage({
|
||||||
postMessage({
|
key: 'ShowAdBlockBanner'
|
||||||
key: 'ShowAdBlockBanner'
|
});
|
||||||
});
|
|
||||||
} else if (HideBlockingMessage == true) {
|
|
||||||
postMessage({
|
|
||||||
key: 'HideAdBlockBanner'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
postMessage({
|
postMessage({
|
||||||
key: 'ForceChangeQuality'
|
key: 'ForceChangeQuality'
|
||||||
});
|
});
|
||||||
@ -412,7 +462,7 @@ twitch-videoad.js application/javascript
|
|||||||
return textStr;
|
return textStr;
|
||||||
}
|
}
|
||||||
//Some live streams use mp4.
|
//Some live streams use mp4.
|
||||||
if (!textStr.includes(".ts") && !textStr.includes(".mp4")) {
|
if (!textStr.includes('.ts') && !textStr.includes('.mp4')) {
|
||||||
return textStr;
|
return textStr;
|
||||||
}
|
}
|
||||||
var haveAdTags = textStr.includes(AdSignifier);
|
var haveAdTags = textStr.includes(AdSignifier);
|
||||||
@ -421,15 +471,15 @@ twitch-videoad.js application/javascript
|
|||||||
//Reduces ad frequency. TODO: Reduce the number of requests. This is really spamming Twitch with requests.
|
//Reduces ad frequency. TODO: Reduce the number of requests. This is really spamming Twitch with requests.
|
||||||
if (!isMidroll) {
|
if (!isMidroll) {
|
||||||
try {
|
try {
|
||||||
tryNotifyTwitch(textStr);
|
//tryNotifyTwitch(textStr);
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
}
|
}
|
||||||
var currentResolution = null;
|
var currentResolution = null;
|
||||||
if (streamInfo && streamInfo.Urls) {
|
if (streamInfo && streamInfo.Urls) {
|
||||||
for (const [resUrl, resName] of Object.entries(streamInfo.Urls)) {
|
for (const [resUrl, resInfo] of Object.entries(streamInfo.Urls)) {
|
||||||
if (resUrl == url) {
|
if (resUrl == url) {
|
||||||
currentResolution = resName;
|
currentResolution = resInfo;
|
||||||
//console.log(resName);
|
//console.log(resInfo.Resolution);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -456,10 +506,21 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
if (playerType === 'proxy') {
|
if (playerType === 'proxy') {
|
||||||
try {
|
try {
|
||||||
|
var proxyType = TwitchAdblockSettings.ProxyType ? TwitchAdblockSettings.ProxyType : DefaultProxyType;
|
||||||
|
var encodingsM3u8Response = null;
|
||||||
/*var tempUrl = stripUnusedParams(MainUrlByUrl[url]);
|
/*var tempUrl = stripUnusedParams(MainUrlByUrl[url]);
|
||||||
const match = /(hls|vod)\/(.+?)$/gim.exec(tempUrl);*/
|
const match = /(hls|vod)\/(.+?)$/gim.exec(tempUrl);*/
|
||||||
var encodingsM3u8Response = await realFetch('https://api.ttv.lol/playlist/' + CurrentChannelName + '.m3u8%3Fallow_source%3Dtrue'/* + encodeURIComponent(match[2])*/, {headers: {'X-Donate-To': 'https://ttv.lol/donate'}});
|
switch (proxyType) {
|
||||||
if (encodingsM3u8Response.status === 200) {
|
case 'TTV LOL':
|
||||||
|
encodingsM3u8Response = await realFetch('https://api.ttv.lol/playlist/' + CurrentChannelName + '.m3u8%3Fallow_source%3Dtrue'/* + encodeURIComponent(match[2])*/, {headers: {'X-Donate-To': 'https://ttv.lol/donate'}});
|
||||||
|
break;
|
||||||
|
/*case 'Purple Adblock':// Broken...
|
||||||
|
encodingsM3u8Response = await realFetch('https://eu1.jupter.ga/channel/' + CurrentChannelName);*/
|
||||||
|
case 'Falan':// https://greasyfork.org/en/scripts/425139-twitch-ad-fix/code
|
||||||
|
encodingsM3u8Response = await realFetch(atob('aHR0cHM6Ly9qaWdnbGUuYmV5cGF6YXJpZ3VydXN1LndvcmtlcnMuZGV2') + '/hls/' + CurrentChannelName + '.m3u8%3Fallow_source%3Dtrue'/* + encodeURIComponent(match[2])*/);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (encodingsM3u8Response && encodingsM3u8Response.status === 200) {
|
||||||
return getStreamForResolution(streamInfo, currentResolution, await encodingsM3u8Response.text(), textStr, playerType, realFetch);
|
return getStreamForResolution(streamInfo, currentResolution, await encodingsM3u8Response.text(), textStr, playerType, realFetch);
|
||||||
}
|
}
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
@ -485,7 +546,7 @@ twitch-videoad.js application/javascript
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (WasShowingAd) {
|
if (WasShowingAd) {
|
||||||
console.log("Done blocking ads, changing back to original quality");
|
console.log('Finished blocking ads');
|
||||||
WasShowingAd = false;
|
WasShowingAd = false;
|
||||||
//Here we put player back to original quality and remove the blocking message.
|
//Here we put player back to original quality and remove the blocking message.
|
||||||
postMessage({
|
postMessage({
|
||||||
|
@ -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 5.4.0-f1
|
// @version 5.5.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
|
||||||
@ -56,16 +56,12 @@
|
|||||||
}
|
}
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
//Send settings updates to worker.
|
//Send settings updates to worker.
|
||||||
window.addEventListener("message", (event) => {
|
window.addEventListener('message', (event) => {
|
||||||
if (event.source != window)
|
if (event.source != window) {
|
||||||
return;
|
return;
|
||||||
if (event.data.type && (event.data.type == "SetHideBlockingMessage")) {
|
}
|
||||||
if (twitchMainWorker) {
|
if (event.data.type && event.data.type == 'SetTwitchAdblockSettings' && event.data.settings) {
|
||||||
twitchMainWorker.postMessage({
|
TwitchAdblockSettings = event.data.settings;
|
||||||
key: 'SetHideBlockingMessage',
|
|
||||||
value: event.data.value
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, false);
|
}, false);
|
||||||
function declareOptions(scope) {
|
function declareOptions(scope) {
|
||||||
@ -81,14 +77,22 @@
|
|||||||
scope.UsherParams = null;
|
scope.UsherParams = null;
|
||||||
scope.WasShowingAd = false;
|
scope.WasShowingAd = false;
|
||||||
scope.GQLDeviceID = null;
|
scope.GQLDeviceID = null;
|
||||||
scope.HideBlockingMessage = false;
|
|
||||||
scope.IsSquadStream = false;
|
scope.IsSquadStream = false;
|
||||||
scope.StreamInfos = [];
|
scope.StreamInfos = [];
|
||||||
scope.StreamInfosByUrl = [];
|
scope.StreamInfosByUrl = [];
|
||||||
scope.MainUrlByUrl = [];
|
scope.MainUrlByUrl = [];
|
||||||
scope.EncodingCacheTimeout = 60000;
|
scope.EncodingCacheTimeout = 60000;
|
||||||
|
scope.DefaultProxyType = 'TTV LOL';
|
||||||
|
scope.DefaultForcedQuality = null;
|
||||||
|
scope.DefaultProxyQuality = null;
|
||||||
}
|
}
|
||||||
declareOptions(window);
|
declareOptions(window);
|
||||||
|
var TwitchAdblockSettings = {
|
||||||
|
BannerVisible: true,
|
||||||
|
ForcedQuality: null,
|
||||||
|
ProxyType: null,
|
||||||
|
ProxyQuality: null,
|
||||||
|
};
|
||||||
var twitchMainWorker = null;
|
var twitchMainWorker = null;
|
||||||
var adBlockDiv = null;
|
var adBlockDiv = null;
|
||||||
var OriginalVideoPlayerQuality = null;
|
var OriginalVideoPlayerQuality = null;
|
||||||
@ -118,6 +122,7 @@
|
|||||||
${tryNotifyTwitch.toString()}
|
${tryNotifyTwitch.toString()}
|
||||||
${parseAttributes.toString()}
|
${parseAttributes.toString()}
|
||||||
declareOptions(self);
|
declareOptions(self);
|
||||||
|
self.TwitchAdblockSettings = ${JSON.stringify(TwitchAdblockSettings)};
|
||||||
self.addEventListener('message', function(e) {
|
self.addEventListener('message', function(e) {
|
||||||
if (e.data.key == 'UpdateIsSquadStream') {
|
if (e.data.key == 'UpdateIsSquadStream') {
|
||||||
IsSquadStream = e.data.value;
|
IsSquadStream = e.data.value;
|
||||||
@ -129,12 +134,6 @@
|
|||||||
ClientID = e.data.value;
|
ClientID = e.data.value;
|
||||||
} else if (e.data.key == 'UpdateDeviceId') {
|
} else if (e.data.key == 'UpdateDeviceId') {
|
||||||
GQLDeviceID = e.data.value;
|
GQLDeviceID = e.data.value;
|
||||||
} else if (e.data.key == 'SetHideBlockingMessage') {
|
|
||||||
if (e.data.value == "true") {
|
|
||||||
HideBlockingMessage = false;
|
|
||||||
} else if (e.data.value == "false") {
|
|
||||||
HideBlockingMessage = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
hookWorkerFetch();
|
hookWorkerFetch();
|
||||||
@ -144,6 +143,9 @@
|
|||||||
twitchMainWorker = this;
|
twitchMainWorker = this;
|
||||||
this.onmessage = function(e) {
|
this.onmessage = function(e) {
|
||||||
if (e.data.key == 'ShowAdBlockBanner') {
|
if (e.data.key == 'ShowAdBlockBanner') {
|
||||||
|
if (!TwitchAdblockSettings.BannerVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (adBlockDiv == null) {
|
if (adBlockDiv == null) {
|
||||||
adBlockDiv = getAdBlockDiv();
|
adBlockDiv = getAdBlockDiv();
|
||||||
}
|
}
|
||||||
@ -312,29 +314,39 @@
|
|||||||
}
|
}
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
var processAfter = async function(response) {
|
var processAfter = async function(response) {
|
||||||
encodingsM3u8 = await response.text();
|
if (response.status == 200) {
|
||||||
var streamInfo = StreamInfos[channelName];
|
encodingsM3u8 = await response.text();
|
||||||
if (streamInfo == null) {
|
var streamInfo = StreamInfos[channelName];
|
||||||
StreamInfos[channelName] = streamInfo = {};
|
if (streamInfo == null) {
|
||||||
}
|
StreamInfos[channelName] = streamInfo = {};
|
||||||
streamInfo.ChannelName = channelName;
|
|
||||||
streamInfo.Urls = [];// xxx.m3u8 -> "284x160" (resolution)
|
|
||||||
streamInfo.EncodingsM3U8Cache = [];
|
|
||||||
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
|
||||||
for (var i = 0; i < lines.length; i++) {
|
|
||||||
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
|
||||||
streamInfo.Urls[lines[i]] = -1;
|
|
||||||
if (i > 0 && lines[i - 1].startsWith('#EXT-X-STREAM-INF')) {
|
|
||||||
var res = parseAttributes(lines[i - 1])['RESOLUTION'];
|
|
||||||
if (res) {
|
|
||||||
streamInfo.Urls[lines[i]] = res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
StreamInfosByUrl[lines[i]] = streamInfo;
|
|
||||||
MainUrlByUrl[lines[i]] = url;
|
|
||||||
}
|
}
|
||||||
|
streamInfo.ChannelName = channelName;
|
||||||
|
streamInfo.Urls = [];// xxx.m3u8 -> { Resolution: "284x160", FrameRate: 30.0 }
|
||||||
|
streamInfo.EncodingsM3U8Cache = [];
|
||||||
|
streamInfo.EncodingsM3U8 = encodingsM3u8;
|
||||||
|
var lines = encodingsM3u8.replace('\r', '').split('\n');
|
||||||
|
for (var i = 0; i < lines.length; i++) {
|
||||||
|
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
|
||||||
|
streamInfo.Urls[lines[i]] = -1;
|
||||||
|
if (i > 0 && lines[i - 1].startsWith('#EXT-X-STREAM-INF')) {
|
||||||
|
var attributes = parseAttributes(lines[i - 1]);
|
||||||
|
var resolution = attributes['RESOLUTION'];
|
||||||
|
var frameRate = attributes['FRAME-RATE'];
|
||||||
|
if (resolution) {
|
||||||
|
streamInfo.Urls[lines[i]] = {
|
||||||
|
Resolution: resolution,
|
||||||
|
FrameRate: frameRate
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StreamInfosByUrl[lines[i]] = streamInfo;
|
||||||
|
MainUrlByUrl[lines[i]] = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolve(new Response(encodingsM3u8));
|
||||||
|
} else {
|
||||||
|
resolve(response);
|
||||||
}
|
}
|
||||||
resolve(new Response(encodingsM3u8));
|
|
||||||
};
|
};
|
||||||
var send = function() {
|
var send = function() {
|
||||||
return realFetch(url, options).then(function(response) {
|
return realFetch(url, options).then(function(response) {
|
||||||
@ -350,46 +362,84 @@
|
|||||||
return realFetch.apply(this, arguments);
|
return realFetch.apply(this, arguments);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function getStreamUrlForResolution(targetResolution, encodingsM3u8) {
|
function getStreamUrlForResolution(encodingsM3u8, resolutionInfo, qualityOverrideStr) {
|
||||||
|
var qualityOverride = 0;
|
||||||
|
if (qualityOverrideStr && qualityOverrideStr.endsWith('p')) {
|
||||||
|
qualityOverride = qualityOverrideStr.substr(0, qualityOverrideStr.length - 1) | 0;
|
||||||
|
}
|
||||||
|
var qualityOverrideFoundQuality = 0;
|
||||||
|
var qualityOverrideFoundFrameRate = 0;
|
||||||
var encodingsLines = encodingsM3u8.replace('\r', '').split('\n');
|
var encodingsLines = encodingsM3u8.replace('\r', '').split('\n');
|
||||||
var firstUrl = null;
|
var firstUrl = null;
|
||||||
|
var lastUrl = null;
|
||||||
|
var matchedResolutionUrl = null;
|
||||||
|
var matchedFrameRate = false;
|
||||||
for (var i = 0; i < encodingsLines.length; i++) {
|
for (var i = 0; i < encodingsLines.length; i++) {
|
||||||
if (!encodingsLines[i].startsWith('#') && encodingsLines[i].includes('.m3u8')) {
|
if (!encodingsLines[i].startsWith('#') && encodingsLines[i].includes('.m3u8')) {
|
||||||
if (i > 0 && encodingsLines[i - 1].startsWith('#EXT-X-STREAM-INF')) {
|
if (i > 0 && encodingsLines[i - 1].startsWith('#EXT-X-STREAM-INF')) {
|
||||||
var res = parseAttributes(encodingsLines[i - 1])['RESOLUTION'];
|
var attributes = parseAttributes(encodingsLines[i - 1]);
|
||||||
if (res && (!targetResolution || res == targetResolution)) {
|
var resolution = attributes['RESOLUTION'];
|
||||||
return encodingsLines[i];
|
var frameRate = attributes['FRAME-RATE'];
|
||||||
|
if (resolution) {
|
||||||
|
if (qualityOverride) {
|
||||||
|
var quality = resolution.toLowerCase().split('x')[1];
|
||||||
|
if (quality == qualityOverride) {
|
||||||
|
qualityOverrideFoundQuality = quality;
|
||||||
|
qualityOverrideFoundFrameRate = frameRate;
|
||||||
|
matchedResolutionUrl = encodingsLines[i];
|
||||||
|
if (frameRate < 40) {
|
||||||
|
//console.log(`qualityOverride(A) quality:${quality} frameRate:${frameRate}`);
|
||||||
|
return matchedResolutionUrl;
|
||||||
|
}
|
||||||
|
} else if (quality < qualityOverride) {
|
||||||
|
//if (matchedResolutionUrl) {
|
||||||
|
// console.log(`qualityOverride(B) quality:${qualityOverrideFoundQuality} frameRate:${qualityOverrideFoundFrameRate}`);
|
||||||
|
//} else {
|
||||||
|
// console.log(`qualityOverride(C) quality:${quality} frameRate:${frameRate}`);
|
||||||
|
//}
|
||||||
|
return matchedResolutionUrl ? matchedResolutionUrl : encodingsLines[i];
|
||||||
|
}
|
||||||
|
} else if ((!resolutionInfo || resolution == resolutionInfo.Resolution) &&
|
||||||
|
(!matchedResolutionUrl || (!matchedFrameRate && frameRate == resolutionInfo.FrameRate))) {
|
||||||
|
matchedResolutionUrl = encodingsLines[i];
|
||||||
|
matchedFrameRate = frameRate == resolutionInfo.FrameRate;
|
||||||
|
if (matchedFrameRate) {
|
||||||
|
return matchedResolutionUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (firstUrl == null) {
|
if (firstUrl == null) {
|
||||||
firstUrl = encodingsLines[i];
|
firstUrl = encodingsLines[i];
|
||||||
}
|
}
|
||||||
|
lastUrl = encodingsLines[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return firstUrl;
|
if (qualityOverride) {
|
||||||
|
return lastUrl;
|
||||||
|
}
|
||||||
|
return matchedResolutionUrl ? matchedResolutionUrl : firstUrl;
|
||||||
}
|
}
|
||||||
async function getStreamForResolution(streamInfo, targetResolution, encodingsM3u8, fallbackStreamStr, playerType, realFetch) {
|
async function getStreamForResolution(streamInfo, resolutionInfo, encodingsM3u8, fallbackStreamStr, playerType, realFetch) {
|
||||||
if (streamInfo.EncodingsM3U8Cache[playerType].Resolution != targetResolution ||
|
var qualityOverride = null;
|
||||||
|
if (playerType === 'proxy') {
|
||||||
|
qualityOverride = TwitchAdblockSettings.ProxyQuality ? TwitchAdblockSettings.ProxyQuality : DefaultProxyQuality;
|
||||||
|
}
|
||||||
|
if (streamInfo.EncodingsM3U8Cache[playerType].Resolution != resolutionInfo.Resolution ||
|
||||||
streamInfo.EncodingsM3U8Cache[playerType].RequestTime < Date.now() - EncodingCacheTimeout) {
|
streamInfo.EncodingsM3U8Cache[playerType].RequestTime < Date.now() - EncodingCacheTimeout) {
|
||||||
console.log(`Blocking ads (type:${playerType}, resolution:${targetResolution})`);
|
console.log(`Blocking ads (type:${playerType}, resolution:${resolutionInfo.Resolution}, frameRate:${resolutionInfo.FrameRate}, qualityOverride:${qualityOverride})`);
|
||||||
}
|
}
|
||||||
streamInfo.EncodingsM3U8Cache[playerType].RequestTime = Date.now();
|
streamInfo.EncodingsM3U8Cache[playerType].RequestTime = Date.now();
|
||||||
streamInfo.EncodingsM3U8Cache[playerType].Value = encodingsM3u8;
|
streamInfo.EncodingsM3U8Cache[playerType].Value = encodingsM3u8;
|
||||||
streamInfo.EncodingsM3U8Cache[playerType].Resolution = targetResolution;
|
streamInfo.EncodingsM3U8Cache[playerType].Resolution = resolutionInfo.Resolution;
|
||||||
var streamM3u8Url = getStreamUrlForResolution(targetResolution, encodingsM3u8);
|
var streamM3u8Url = getStreamUrlForResolution(encodingsM3u8, resolutionInfo, qualityOverride);
|
||||||
var streamM3u8Response = await realFetch(streamM3u8Url);
|
var streamM3u8Response = await realFetch(streamM3u8Url);
|
||||||
if (streamM3u8Response.status == 200) {
|
if (streamM3u8Response.status == 200) {
|
||||||
var m3u8Text = await streamM3u8Response.text();
|
var m3u8Text = await streamM3u8Response.text();
|
||||||
WasShowingAd = true;
|
WasShowingAd = true;
|
||||||
if (HideBlockingMessage == false) {
|
postMessage({
|
||||||
postMessage({
|
key: 'ShowAdBlockBanner'
|
||||||
key: 'ShowAdBlockBanner'
|
});
|
||||||
});
|
|
||||||
} else if (HideBlockingMessage == true) {
|
|
||||||
postMessage({
|
|
||||||
key: 'HideAdBlockBanner'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
postMessage({
|
postMessage({
|
||||||
key: 'ForceChangeQuality'
|
key: 'ForceChangeQuality'
|
||||||
});
|
});
|
||||||
@ -423,7 +473,7 @@
|
|||||||
return textStr;
|
return textStr;
|
||||||
}
|
}
|
||||||
//Some live streams use mp4.
|
//Some live streams use mp4.
|
||||||
if (!textStr.includes(".ts") && !textStr.includes(".mp4")) {
|
if (!textStr.includes('.ts') && !textStr.includes('.mp4')) {
|
||||||
return textStr;
|
return textStr;
|
||||||
}
|
}
|
||||||
var haveAdTags = textStr.includes(AdSignifier);
|
var haveAdTags = textStr.includes(AdSignifier);
|
||||||
@ -432,15 +482,15 @@
|
|||||||
//Reduces ad frequency. TODO: Reduce the number of requests. This is really spamming Twitch with requests.
|
//Reduces ad frequency. TODO: Reduce the number of requests. This is really spamming Twitch with requests.
|
||||||
if (!isMidroll) {
|
if (!isMidroll) {
|
||||||
try {
|
try {
|
||||||
tryNotifyTwitch(textStr);
|
//tryNotifyTwitch(textStr);
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
}
|
}
|
||||||
var currentResolution = null;
|
var currentResolution = null;
|
||||||
if (streamInfo && streamInfo.Urls) {
|
if (streamInfo && streamInfo.Urls) {
|
||||||
for (const [resUrl, resName] of Object.entries(streamInfo.Urls)) {
|
for (const [resUrl, resInfo] of Object.entries(streamInfo.Urls)) {
|
||||||
if (resUrl == url) {
|
if (resUrl == url) {
|
||||||
currentResolution = resName;
|
currentResolution = resInfo;
|
||||||
//console.log(resName);
|
//console.log(resInfo.Resolution);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -467,10 +517,21 @@
|
|||||||
}
|
}
|
||||||
if (playerType === 'proxy') {
|
if (playerType === 'proxy') {
|
||||||
try {
|
try {
|
||||||
|
var proxyType = TwitchAdblockSettings.ProxyType ? TwitchAdblockSettings.ProxyType : DefaultProxyType;
|
||||||
|
var encodingsM3u8Response = null;
|
||||||
/*var tempUrl = stripUnusedParams(MainUrlByUrl[url]);
|
/*var tempUrl = stripUnusedParams(MainUrlByUrl[url]);
|
||||||
const match = /(hls|vod)\/(.+?)$/gim.exec(tempUrl);*/
|
const match = /(hls|vod)\/(.+?)$/gim.exec(tempUrl);*/
|
||||||
var encodingsM3u8Response = await realFetch('https://api.ttv.lol/playlist/' + CurrentChannelName + '.m3u8%3Fallow_source%3Dtrue'/* + encodeURIComponent(match[2])*/, {headers: {'X-Donate-To': 'https://ttv.lol/donate'}});
|
switch (proxyType) {
|
||||||
if (encodingsM3u8Response.status === 200) {
|
case 'TTV LOL':
|
||||||
|
encodingsM3u8Response = await realFetch('https://api.ttv.lol/playlist/' + CurrentChannelName + '.m3u8%3Fallow_source%3Dtrue'/* + encodeURIComponent(match[2])*/, {headers: {'X-Donate-To': 'https://ttv.lol/donate'}});
|
||||||
|
break;
|
||||||
|
/*case 'Purple Adblock':// Broken...
|
||||||
|
encodingsM3u8Response = await realFetch('https://eu1.jupter.ga/channel/' + CurrentChannelName);*/
|
||||||
|
case 'Falan':// https://greasyfork.org/en/scripts/425139-twitch-ad-fix/code
|
||||||
|
encodingsM3u8Response = await realFetch(atob('aHR0cHM6Ly9qaWdnbGUuYmV5cGF6YXJpZ3VydXN1LndvcmtlcnMuZGV2') + '/hls/' + CurrentChannelName + '.m3u8%3Fallow_source%3Dtrue'/* + encodeURIComponent(match[2])*/);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (encodingsM3u8Response && encodingsM3u8Response.status === 200) {
|
||||||
return getStreamForResolution(streamInfo, currentResolution, await encodingsM3u8Response.text(), textStr, playerType, realFetch);
|
return getStreamForResolution(streamInfo, currentResolution, await encodingsM3u8Response.text(), textStr, playerType, realFetch);
|
||||||
}
|
}
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
@ -496,7 +557,7 @@
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (WasShowingAd) {
|
if (WasShowingAd) {
|
||||||
console.log("Done blocking ads, changing back to original quality");
|
console.log('Finished blocking ads');
|
||||||
WasShowingAd = false;
|
WasShowingAd = false;
|
||||||
//Here we put player back to original quality and remove the blocking message.
|
//Here we put player back to original quality and remove the blocking message.
|
||||||
postMessage({
|
postMessage({
|
||||||
|
Loading…
Reference in New Issue
Block a user