From ed898b750f7e62030afd5561a4305c1832a188da Mon Sep 17 00:00:00 2001 From: pixeltris <6952411+pixeltris@users.noreply.github.com> Date: Tue, 4 Nov 2025 23:14:05 +0000 Subject: [PATCH] v2 api support #461 --- vaft/vaft-ublock-origin.js | 17 +++++++++++++---- vaft/vaft.user.js | 19 ++++++++++++++----- .../video-swap-new-ublock-origin.js | 15 ++++++++++++--- video-swap-new/video-swap-new.user.js | 17 +++++++++++++---- 4 files changed, 52 insertions(+), 16 deletions(-) diff --git a/vaft/vaft-ublock-origin.js b/vaft/vaft-ublock-origin.js index 6e84126..5d0a52c 100644 --- a/vaft/vaft-ublock-origin.js +++ b/vaft/vaft-ublock-origin.js @@ -2,7 +2,7 @@ twitch-videoad.js text/javascript (function() { if ( /(^|\.)twitch\.tv$/.test(document.location.hostname) === false ) { return; } '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) { console.log("skipping vaft as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + window.twitchAdSolutionsVersion); window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion; @@ -34,8 +34,9 @@ twitch-videoad.js text/javascript scope.LastPausePlay = 0; scope.FixPlayerBufferingInsideAds = true; scope.FixPlayerBufferingOutsideAds = false; - scope.DelayBetweenEachPlayerFixBufferAttempt = 1500; + scope.DelayBetweenEachPlayerFixBufferAttempt = 3000; scope.ActiveStreamInfo = null; + scope.V2API = false; } var localStorageHookFailed = false; var twitchWorkers = []; @@ -276,7 +277,8 @@ twitch-videoad.js text/javascript }; 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]; if (ForceAccessTokenPlayerType) { // 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) { + 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.]+)"'); return matches.length > 1 ? matches[1] : null; } 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; } function getStreamUrlForResolution(encodingsM3u8, resolutionInfo) { @@ -503,7 +512,7 @@ twitch-videoad.js text/javascript var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, playerType); if (accessTokenResponse.status === 200) { 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('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await realFetch(urlInfo.href); diff --git a/vaft/vaft.user.js b/vaft/vaft.user.js index 7470aa6..8c56d07 100644 --- a/vaft/vaft.user.js +++ b/vaft/vaft.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name TwitchAdSolutions (vaft) // @namespace https://github.com/pixeltris/TwitchAdSolutions -// @version 27.0.0 +// @version 28.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 = 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) { console.log("skipping vaft as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + window.twitchAdSolutionsVersion); window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion; @@ -45,8 +45,9 @@ scope.LastPausePlay = 0; scope.FixPlayerBufferingInsideAds = true; scope.FixPlayerBufferingOutsideAds = false; - scope.DelayBetweenEachPlayerFixBufferAttempt = 1500; + scope.DelayBetweenEachPlayerFixBufferAttempt = 3000; scope.ActiveStreamInfo = null; + scope.V2API = false; } var localStorageHookFailed = false; var twitchWorkers = []; @@ -287,7 +288,8 @@ }; 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]; if (ForceAccessTokenPlayerType) { // 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) { + 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.]+)"'); return matches.length > 1 ? matches[1] : null; } 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; } function getStreamUrlForResolution(encodingsM3u8, resolutionInfo) { @@ -514,7 +523,7 @@ var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, playerType); if (accessTokenResponse.status === 200) { 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('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await realFetch(urlInfo.href); diff --git a/video-swap-new/video-swap-new-ublock-origin.js b/video-swap-new/video-swap-new-ublock-origin.js index de49e26..083b3e0 100644 --- a/video-swap-new/video-swap-new-ublock-origin.js +++ b/video-swap-new/video-swap-new-ublock-origin.js @@ -1,7 +1,7 @@ twitch-videoad.js text/javascript (function() { if ( /(^|\.)twitch\.tv$/.test(document.location.hostname) === false ) { return; } - var ourTwitchAdSolutionsVersion = 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) { console.log("skipping video-swap-new as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + window.twitchAdSolutionsVersion); window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion; @@ -24,6 +24,7 @@ twitch-videoad.js text/javascript scope.ClientIntegrityHeader = null; scope.AuthorizationHeader = undefined; scope.SimulatedAdsDepth = 0; + scope.V2API = false; } var localStorageHookFailed = false; var adBlockDiv = null; @@ -264,7 +265,7 @@ twitch-videoad.js text/javascript var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, playerType, 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/' + 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('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await realFetch(urlInfo.href); @@ -413,7 +414,8 @@ twitch-videoad.js text/javascript 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]; 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 @@ -481,10 +483,17 @@ twitch-videoad.js text/javascript } } 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.]+)"'); return matches.length > 1 ? matches[1] : null; } 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; } function getStreamUrlForResolution(encodingsM3u8, resolutionInfo) { diff --git a/video-swap-new/video-swap-new.user.js b/video-swap-new/video-swap-new.user.js index 7c03e1d..88e6ae2 100644 --- a/video-swap-new/video-swap-new.user.js +++ b/video-swap-new/video-swap-new.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name TwitchAdSolutions (video-swap-new) // @namespace https://github.com/pixeltris/TwitchAdSolutions -// @version 1.46 +// @version 1.47 // @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 = 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) { console.log("skipping video-swap-new as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + window.twitchAdSolutionsVersion); window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion; @@ -36,6 +36,7 @@ scope.ClientIntegrityHeader = null; scope.AuthorizationHeader = undefined; scope.SimulatedAdsDepth = 0; + scope.V2API = false; } var localStorageHookFailed = false; var adBlockDiv = null; @@ -276,7 +277,7 @@ var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, playerType, 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/' + 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('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await realFetch(urlInfo.href); @@ -425,7 +426,8 @@ 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]; 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 @@ -493,10 +495,17 @@ } } 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.]+)"'); return matches.length > 1 ? matches[1] : null; } 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; } function getStreamUrlForResolution(encodingsM3u8, resolutionInfo) {