v2 api support #461

This commit is contained in:
pixeltris
2025-11-04 23:14:05 +00:00
parent 5cce620aba
commit ed898b750f
4 changed files with 52 additions and 16 deletions
+13 -4
View File
@@ -2,7 +2,7 @@ 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; }
'use strict'; 'use strict';
var ourTwitchAdSolutionsVersion = 14;// Used to prevent conflicts with outdated versions of the scripts var ourTwitchAdSolutionsVersion = 15;// Used to prevent conflicts with outdated versions of the scripts
if (typeof window.twitchAdSolutionsVersion !== 'undefined' && window.twitchAdSolutionsVersion >= ourTwitchAdSolutionsVersion) { if (typeof window.twitchAdSolutionsVersion !== 'undefined' && window.twitchAdSolutionsVersion >= ourTwitchAdSolutionsVersion) {
console.log("skipping vaft as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + window.twitchAdSolutionsVersion); console.log("skipping vaft as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + window.twitchAdSolutionsVersion);
window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion; window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion;
@@ -34,8 +34,9 @@ twitch-videoad.js text/javascript
scope.LastPausePlay = 0; scope.LastPausePlay = 0;
scope.FixPlayerBufferingInsideAds = true; scope.FixPlayerBufferingInsideAds = true;
scope.FixPlayerBufferingOutsideAds = false; scope.FixPlayerBufferingOutsideAds = false;
scope.DelayBetweenEachPlayerFixBufferAttempt = 1500; scope.DelayBetweenEachPlayerFixBufferAttempt = 3000;
scope.ActiveStreamInfo = null; scope.ActiveStreamInfo = null;
scope.V2API = false;
} }
var localStorageHookFailed = false; var localStorageHookFailed = false;
var twitchWorkers = []; var twitchWorkers = [];
@@ -276,7 +277,8 @@ twitch-videoad.js text/javascript
}; };
send(); send();
}); });
} else if (url.includes('/api/channel/hls/') && !url.includes('picture-by-picture')) { } else if (url.includes('/channel/hls/') && !url.includes('picture-by-picture')) {
V2API = url.includes('/api/v2/');
var channelName = (new URL(url)).pathname.match(/([^\/]+)(?=\.\w+$)/)[0]; var channelName = (new URL(url)).pathname.match(/([^\/]+)(?=\.\w+$)/)[0];
if (ForceAccessTokenPlayerType) { if (ForceAccessTokenPlayerType) {
// parent_domains is used to determine if the player is embeded and stripping it gets rid of fake ads // parent_domains is used to determine if the player is embeded and stripping it gets rid of fake ads
@@ -386,10 +388,17 @@ twitch-videoad.js text/javascript
}; };
} }
function getServerTimeFromM3u8(encodingsM3u8) { function getServerTimeFromM3u8(encodingsM3u8) {
if (V2API) {
var matches = encodingsM3u8.match(/#EXT-X-SESSION-DATA:DATA-ID="SERVER-TIME",VALUE="([^"]+)"/);
return matches.length > 1 ? matches[1] : null;
}
var matches = encodingsM3u8.match('SERVER-TIME="([0-9.]+)"'); var matches = encodingsM3u8.match('SERVER-TIME="([0-9.]+)"');
return matches.length > 1 ? matches[1] : null; return matches.length > 1 ? matches[1] : null;
} }
function replaceServerTimeInM3u8(encodingsM3u8, newServerTime) { function replaceServerTimeInM3u8(encodingsM3u8, newServerTime) {
if (V2API) {
return newServerTime ? encodingsM3u8.replace(/(#EXT-X-SESSION-DATA:DATA-ID="SERVER-TIME",VALUE=")[^"]+(")/, `$1${newServerTime}$2`) : encodingsM3u8;
}
return newServerTime ? encodingsM3u8.replace(new RegExp('(SERVER-TIME=")[0-9.]+"'), `SERVER-TIME="${newServerTime}"`) : encodingsM3u8; return newServerTime ? encodingsM3u8.replace(new RegExp('(SERVER-TIME=")[0-9.]+"'), `SERVER-TIME="${newServerTime}"`) : encodingsM3u8;
} }
function getStreamUrlForResolution(encodingsM3u8, resolutionInfo) { function getStreamUrlForResolution(encodingsM3u8, resolutionInfo) {
@@ -503,7 +512,7 @@ twitch-videoad.js text/javascript
var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, playerType); var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, playerType);
if (accessTokenResponse.status === 200) { if (accessTokenResponse.status === 200) {
var accessToken = await accessTokenResponse.json(); var accessToken = await accessTokenResponse.json();
var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + streamInfo.ChannelName + '.m3u8' + streamInfo.UsherParams); var urlInfo = new URL('https://usher.ttvnw.net/api/' + (V2API ? 'v2/' : '') + 'channel/hls/' + streamInfo.ChannelName + '.m3u8' + streamInfo.UsherParams);
urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature);
urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value);
var encodingsM3u8Response = await realFetch(urlInfo.href); var encodingsM3u8Response = await realFetch(urlInfo.href);
+14 -5
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 27.0.0 // @version 28.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
@@ -13,7 +13,7 @@
// ==/UserScript== // ==/UserScript==
(function() { (function() {
'use strict'; 'use strict';
var ourTwitchAdSolutionsVersion = 14;// Used to prevent conflicts with outdated versions of the scripts var ourTwitchAdSolutionsVersion = 15;// Used to prevent conflicts with outdated versions of the scripts
if (typeof window.twitchAdSolutionsVersion !== 'undefined' && window.twitchAdSolutionsVersion >= ourTwitchAdSolutionsVersion) { if (typeof window.twitchAdSolutionsVersion !== 'undefined' && window.twitchAdSolutionsVersion >= ourTwitchAdSolutionsVersion) {
console.log("skipping vaft as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + window.twitchAdSolutionsVersion); console.log("skipping vaft as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + window.twitchAdSolutionsVersion);
window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion; window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion;
@@ -45,8 +45,9 @@
scope.LastPausePlay = 0; scope.LastPausePlay = 0;
scope.FixPlayerBufferingInsideAds = true; scope.FixPlayerBufferingInsideAds = true;
scope.FixPlayerBufferingOutsideAds = false; scope.FixPlayerBufferingOutsideAds = false;
scope.DelayBetweenEachPlayerFixBufferAttempt = 1500; scope.DelayBetweenEachPlayerFixBufferAttempt = 3000;
scope.ActiveStreamInfo = null; scope.ActiveStreamInfo = null;
scope.V2API = false;
} }
var localStorageHookFailed = false; var localStorageHookFailed = false;
var twitchWorkers = []; var twitchWorkers = [];
@@ -287,7 +288,8 @@
}; };
send(); send();
}); });
} else if (url.includes('/api/channel/hls/') && !url.includes('picture-by-picture')) { } else if (url.includes('/channel/hls/') && !url.includes('picture-by-picture')) {
V2API = url.includes('/api/v2/');
var channelName = (new URL(url)).pathname.match(/([^\/]+)(?=\.\w+$)/)[0]; var channelName = (new URL(url)).pathname.match(/([^\/]+)(?=\.\w+$)/)[0];
if (ForceAccessTokenPlayerType) { if (ForceAccessTokenPlayerType) {
// parent_domains is used to determine if the player is embeded and stripping it gets rid of fake ads // parent_domains is used to determine if the player is embeded and stripping it gets rid of fake ads
@@ -397,10 +399,17 @@
}; };
} }
function getServerTimeFromM3u8(encodingsM3u8) { function getServerTimeFromM3u8(encodingsM3u8) {
if (V2API) {
var matches = encodingsM3u8.match(/#EXT-X-SESSION-DATA:DATA-ID="SERVER-TIME",VALUE="([^"]+)"/);
return matches.length > 1 ? matches[1] : null;
}
var matches = encodingsM3u8.match('SERVER-TIME="([0-9.]+)"'); var matches = encodingsM3u8.match('SERVER-TIME="([0-9.]+)"');
return matches.length > 1 ? matches[1] : null; return matches.length > 1 ? matches[1] : null;
} }
function replaceServerTimeInM3u8(encodingsM3u8, newServerTime) { function replaceServerTimeInM3u8(encodingsM3u8, newServerTime) {
if (V2API) {
return newServerTime ? encodingsM3u8.replace(/(#EXT-X-SESSION-DATA:DATA-ID="SERVER-TIME",VALUE=")[^"]+(")/, `$1${newServerTime}$2`) : encodingsM3u8;
}
return newServerTime ? encodingsM3u8.replace(new RegExp('(SERVER-TIME=")[0-9.]+"'), `SERVER-TIME="${newServerTime}"`) : encodingsM3u8; return newServerTime ? encodingsM3u8.replace(new RegExp('(SERVER-TIME=")[0-9.]+"'), `SERVER-TIME="${newServerTime}"`) : encodingsM3u8;
} }
function getStreamUrlForResolution(encodingsM3u8, resolutionInfo) { function getStreamUrlForResolution(encodingsM3u8, resolutionInfo) {
@@ -514,7 +523,7 @@
var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, playerType); var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, playerType);
if (accessTokenResponse.status === 200) { if (accessTokenResponse.status === 200) {
var accessToken = await accessTokenResponse.json(); var accessToken = await accessTokenResponse.json();
var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + streamInfo.ChannelName + '.m3u8' + streamInfo.UsherParams); var urlInfo = new URL('https://usher.ttvnw.net/api/' + (V2API ? 'v2/' : '') + 'channel/hls/' + streamInfo.ChannelName + '.m3u8' + streamInfo.UsherParams);
urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature);
urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value);
var encodingsM3u8Response = await realFetch(urlInfo.href); var encodingsM3u8Response = await realFetch(urlInfo.href);
+12 -3
View File
@@ -1,7 +1,7 @@
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 = 14;// Used to prevent conflicts with outdated versions of the scripts var ourTwitchAdSolutionsVersion = 15;// Used to prevent conflicts with outdated versions of the scripts
if (typeof window.twitchAdSolutionsVersion !== 'undefined' && window.twitchAdSolutionsVersion >= ourTwitchAdSolutionsVersion) { if (typeof window.twitchAdSolutionsVersion !== 'undefined' && window.twitchAdSolutionsVersion >= ourTwitchAdSolutionsVersion) {
console.log("skipping video-swap-new as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + window.twitchAdSolutionsVersion); console.log("skipping video-swap-new as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + window.twitchAdSolutionsVersion);
window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion; window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion;
@@ -24,6 +24,7 @@ twitch-videoad.js text/javascript
scope.ClientIntegrityHeader = null; scope.ClientIntegrityHeader = null;
scope.AuthorizationHeader = undefined; scope.AuthorizationHeader = undefined;
scope.SimulatedAdsDepth = 0; scope.SimulatedAdsDepth = 0;
scope.V2API = false;
} }
var localStorageHookFailed = false; var localStorageHookFailed = false;
var adBlockDiv = null; var adBlockDiv = null;
@@ -264,7 +265,7 @@ twitch-videoad.js text/javascript
var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, playerType, OPT_BACKUP_PLATFORM); var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, playerType, OPT_BACKUP_PLATFORM);
if (accessTokenResponse != null && accessTokenResponse.status === 200) { if (accessTokenResponse != null && accessTokenResponse.status === 200) {
var accessToken = await accessTokenResponse.json(); var accessToken = await accessTokenResponse.json();
var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + streamInfo.ChannelName + '.m3u8' + streamInfo.UsherParams); var urlInfo = new URL('https://usher.ttvnw.net/api/' + (V2API ? 'v2/' : '') + 'channel/hls/' + streamInfo.ChannelName + '.m3u8' + streamInfo.UsherParams);
urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature);
urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value);
var encodingsM3u8Response = await realFetch(urlInfo.href); var encodingsM3u8Response = await realFetch(urlInfo.href);
@@ -413,7 +414,8 @@ twitch-videoad.js text/javascript
send(); send();
}); });
} }
else if (url.includes('/api/channel/hls/') && !url.includes('picture-by-picture')) { else if (url.includes('/channel/hls/') && !url.includes('picture-by-picture')) {
V2API = url.includes('/api/v2/');
var channelName = (new URL(url)).pathname.match(/([^\/]+)(?=\.\w+$)/)[0]; var channelName = (new URL(url)).pathname.match(/([^\/]+)(?=\.\w+$)/)[0];
if (OPT_FORCE_ACCESS_TOKEN_PLAYER_TYPE) { if (OPT_FORCE_ACCESS_TOKEN_PLAYER_TYPE) {
// parent_domains is used to determine if the player is embeded and stripping it gets rid of fake ads // parent_domains is used to determine if the player is embeded and stripping it gets rid of fake ads
@@ -481,10 +483,17 @@ twitch-videoad.js text/javascript
} }
} }
function getServerTimeFromM3u8(encodingsM3u8) { function getServerTimeFromM3u8(encodingsM3u8) {
if (V2API) {
var matches = encodingsM3u8.match(/#EXT-X-SESSION-DATA:DATA-ID="SERVER-TIME",VALUE="([^"]+)"/);
return matches.length > 1 ? matches[1] : null;
}
var matches = encodingsM3u8.match('SERVER-TIME="([0-9.]+)"'); var matches = encodingsM3u8.match('SERVER-TIME="([0-9.]+)"');
return matches.length > 1 ? matches[1] : null; return matches.length > 1 ? matches[1] : null;
} }
function replaceServerTimeInM3u8(encodingsM3u8, newServerTime) { function replaceServerTimeInM3u8(encodingsM3u8, newServerTime) {
if (V2API) {
return newServerTime ? encodingsM3u8.replace(/(#EXT-X-SESSION-DATA:DATA-ID="SERVER-TIME",VALUE=")[^"]+(")/, `$1${newServerTime}$2`) : encodingsM3u8;
}
return newServerTime ? encodingsM3u8.replace(new RegExp('(SERVER-TIME=")[0-9.]+"'), `SERVER-TIME="${newServerTime}"`) : encodingsM3u8; return newServerTime ? encodingsM3u8.replace(new RegExp('(SERVER-TIME=")[0-9.]+"'), `SERVER-TIME="${newServerTime}"`) : encodingsM3u8;
} }
function getStreamUrlForResolution(encodingsM3u8, resolutionInfo) { function getStreamUrlForResolution(encodingsM3u8, resolutionInfo) {
+13 -4
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.46 // @version 1.47
// @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)
@@ -13,7 +13,7 @@
// ==/UserScript== // ==/UserScript==
(function() { (function() {
'use strict'; 'use strict';
var ourTwitchAdSolutionsVersion = 14;// Used to prevent conflicts with outdated versions of the scripts var ourTwitchAdSolutionsVersion = 15;// Used to prevent conflicts with outdated versions of the scripts
if (typeof window.twitchAdSolutionsVersion !== 'undefined' && window.twitchAdSolutionsVersion >= ourTwitchAdSolutionsVersion) { if (typeof window.twitchAdSolutionsVersion !== 'undefined' && window.twitchAdSolutionsVersion >= ourTwitchAdSolutionsVersion) {
console.log("skipping video-swap-new as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + window.twitchAdSolutionsVersion); console.log("skipping video-swap-new as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + window.twitchAdSolutionsVersion);
window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion; window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion;
@@ -36,6 +36,7 @@
scope.ClientIntegrityHeader = null; scope.ClientIntegrityHeader = null;
scope.AuthorizationHeader = undefined; scope.AuthorizationHeader = undefined;
scope.SimulatedAdsDepth = 0; scope.SimulatedAdsDepth = 0;
scope.V2API = false;
} }
var localStorageHookFailed = false; var localStorageHookFailed = false;
var adBlockDiv = null; var adBlockDiv = null;
@@ -276,7 +277,7 @@
var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, playerType, OPT_BACKUP_PLATFORM); var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, playerType, OPT_BACKUP_PLATFORM);
if (accessTokenResponse != null && accessTokenResponse.status === 200) { if (accessTokenResponse != null && accessTokenResponse.status === 200) {
var accessToken = await accessTokenResponse.json(); var accessToken = await accessTokenResponse.json();
var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + streamInfo.ChannelName + '.m3u8' + streamInfo.UsherParams); var urlInfo = new URL('https://usher.ttvnw.net/api/' + (V2API ? 'v2/' : '') + 'channel/hls/' + streamInfo.ChannelName + '.m3u8' + streamInfo.UsherParams);
urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature);
urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value);
var encodingsM3u8Response = await realFetch(urlInfo.href); var encodingsM3u8Response = await realFetch(urlInfo.href);
@@ -425,7 +426,8 @@
send(); send();
}); });
} }
else if (url.includes('/api/channel/hls/') && !url.includes('picture-by-picture')) { else if (url.includes('/channel/hls/') && !url.includes('picture-by-picture')) {
V2API = url.includes('/api/v2/');
var channelName = (new URL(url)).pathname.match(/([^\/]+)(?=\.\w+$)/)[0]; var channelName = (new URL(url)).pathname.match(/([^\/]+)(?=\.\w+$)/)[0];
if (OPT_FORCE_ACCESS_TOKEN_PLAYER_TYPE) { if (OPT_FORCE_ACCESS_TOKEN_PLAYER_TYPE) {
// parent_domains is used to determine if the player is embeded and stripping it gets rid of fake ads // parent_domains is used to determine if the player is embeded and stripping it gets rid of fake ads
@@ -493,10 +495,17 @@
} }
} }
function getServerTimeFromM3u8(encodingsM3u8) { function getServerTimeFromM3u8(encodingsM3u8) {
if (V2API) {
var matches = encodingsM3u8.match(/#EXT-X-SESSION-DATA:DATA-ID="SERVER-TIME",VALUE="([^"]+)"/);
return matches.length > 1 ? matches[1] : null;
}
var matches = encodingsM3u8.match('SERVER-TIME="([0-9.]+)"'); var matches = encodingsM3u8.match('SERVER-TIME="([0-9.]+)"');
return matches.length > 1 ? matches[1] : null; return matches.length > 1 ? matches[1] : null;
} }
function replaceServerTimeInM3u8(encodingsM3u8, newServerTime) { function replaceServerTimeInM3u8(encodingsM3u8, newServerTime) {
if (V2API) {
return newServerTime ? encodingsM3u8.replace(/(#EXT-X-SESSION-DATA:DATA-ID="SERVER-TIME",VALUE=")[^"]+(")/, `$1${newServerTime}$2`) : encodingsM3u8;
}
return newServerTime ? encodingsM3u8.replace(new RegExp('(SERVER-TIME=")[0-9.]+"'), `SERVER-TIME="${newServerTime}"`) : encodingsM3u8; return newServerTime ? encodingsM3u8.replace(new RegExp('(SERVER-TIME=")[0-9.]+"'), `SERVER-TIME="${newServerTime}"`) : encodingsM3u8;
} }
function getStreamUrlForResolution(encodingsM3u8, resolutionInfo) { function getStreamUrlForResolution(encodingsM3u8, resolutionInfo) {