From 4bc4274478ab57729352b63e10024280df45e490 Mon Sep 17 00:00:00 2001 From: pixeltris <6952411+pixeltris@users.noreply.github.com> Date: Fri, 15 Jan 2021 01:44:25 +0000 Subject: [PATCH] Remove dead access_token endpoints #12 --- base/base.user.js | 71 ++++++++++++++++--- .../dyn-skip-midroll-alt-ublock-origin.js | 71 ++++++++++++++++--- .../dyn-skip-midroll-alt.user.js | 71 ++++++++++++++++--- .../dyn-skip-midroll-ublock-origin.js | 71 ++++++++++++++++--- dyn-skip-midroll/dyn-skip-midroll.user.js | 71 ++++++++++++++++--- dyn-skip/dyn-skip-ublock-origin.js | 71 ++++++++++++++++--- dyn-skip/dyn-skip.user.js | 71 ++++++++++++++++--- .../dyn-video-swap-ublock-origin.js | 71 ++++++++++++++++--- dyn-video-swap/dyn-video-swap.user.js | 71 ++++++++++++++++--- dyn/dyn-ublock-origin.js | 71 ++++++++++++++++--- dyn/dyn.user.js | 71 ++++++++++++++++--- mute-black/mute-black-ublock-origin.js | 71 ++++++++++++++++--- mute-black/mute-black.user.js | 71 ++++++++++++++++--- proxy-m3u8/proxy-m3u8-ublock-origin.js | 71 ++++++++++++++++--- proxy-m3u8/proxy-m3u8.user.js | 71 ++++++++++++++++--- 15 files changed, 915 insertions(+), 150 deletions(-) diff --git a/base/base.user.js b/base/base.user.js index b7f922b..501eae3 100644 --- a/base/base.user.js +++ b/base/base.user.js @@ -26,9 +26,11 @@ scope.OPT_MODE_PROXY_M3U8_OBFUSCATED = false; scope.OPT_MODE_PROXY_M3U8_FULL_URL = false; scope.OPT_MODE_PROXY_M3U8_PARTIAL_URL = false; - scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'thunderdome'; + scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'picture-by-picture'; + scope.OPT_BACKUP_PLAYER_TYPE = 'picture-by-picture'; scope.OPT_INITIAL_M3U8_ATTEMPTS = 1; scope.OPT_ACCESS_TOKEN_PLAYER_TYPE = ''; + scope.OPT_ACCESS_TOKEN_TEMPLATE = true; scope.AD_SIGNIFIER = 'stitched-ad'; scope.LIVE_SIGNIFIER = ',live'; scope.CLIENT_ID = 'kimne78kx3ncx6brgo4mv6wki5h1ko'; @@ -54,6 +56,8 @@ scope.CurrentChannelNameFromM3U8 = null; scope.LastAdUrl = null; scope.LastAdTime = 0; + // Need this in both scopes. Window scope needs to update this to worker scope. + scope.gql_device_id = null; } declareOptions(window); //////////////////////////////////// @@ -66,7 +70,6 @@ var foundAdBanner = false;// Is the ad banner visible (top left of screen) //////////////////////////////////// var notifyAdsWatchedReloadNextTime = 0; - var gql_device_id = null; var twitchMainWorker = null; const oldWorker = window.Worker; window.Worker = class Worker extends oldWorker { @@ -85,7 +88,14 @@ ${getSegmentTimes.toString()} ${hookWorkerFetch.toString()} ${declareOptions.toString()} + ${getAccessToken.toString()} + ${gqlRequest.toString()} declareOptions(self); + self.addEventListener('message', function(e) { + if (e.data.key == 'UboUpdateDeviceId') { + gql_device_id = e.data.value; + } + }); hookWorkerFetch(); importScripts('${jsURL}'); ` @@ -179,12 +189,12 @@ if (!streamInfo.BackupFailed && streamInfo.BackupUrl == null) { // NOTE: We currently don't fetch the oauth_token. You wont be able to access private streams like this. streamInfo.BackupFailed = true; - var accessTokenResponse = await realFetch('https://api.twitch.tv/api/channels/' + streamInfo.ChannelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=picture-by-picture&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, OPT_BACKUP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + streamInfo.ChannelName + '.m3u8' + streamInfo.RootM3U8Params); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await realFetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 @@ -372,6 +382,41 @@ }, }]; } + function getAccessToken(channelName, playerType) { + var body = null; + if (OPT_ACCESS_TOKEN_TEMPLATE) { + var templateQuery = 'query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}'; + body = { + operationName: 'PlaybackAccessToken_Template', + query: templateQuery, + variables: { + 'isLive': true, + 'login': channelName, + 'isVod': false, + 'vodID': '', + 'playerType': playerType + } + }; + } else { + body = { + operationName: 'PlaybackAccessToken', + variables: { + isLive: true, + login: channelName, + isVod: false, + vodID: '', + playerType: playerType + }, + extensions: { + persistedQuery: { + version: 1, + sha256Hash: '0828119ded1c13477966434e15800ff57ddacf13ba1911c129dc2200705b0712', + } + } + }; + } + return gqlRequest(body); + } function gqlRequest(body) { return fetch('https://gql.twitch.tv/gql', { method: 'POST', @@ -497,6 +542,12 @@ if (typeof deviceId === 'string') { gql_device_id = deviceId; } + if (gql_device_id && twitchMainWorker) { + twitchMainWorker.postMessage({ + key: 'UboUpdateDeviceId', + value: gql_device_id + }); + } if (OPT_MODE_NOTIFY_ADS_WATCHED) { var tok = null, sig = null; if (url.includes('/access_token')) { @@ -603,12 +654,12 @@ // Create new video stream TODO: Do this with callbacks var channelName = window.location.pathname.substr(1);// TODO: Better way of determining the channel name var tempM3u8Url = null; - var accessTokenResponse = await fetch('https://api.twitch.tv/api/channels/' + channelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=' + OPT_VIDEO_SWAP_PLAYER_TYPE + '&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(channelName, OPT_VIDEO_SWAP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + channelName + '.m3u8?allow_source=true'); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await fetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 diff --git a/dyn-skip-midroll-alt/dyn-skip-midroll-alt-ublock-origin.js b/dyn-skip-midroll-alt/dyn-skip-midroll-alt-ublock-origin.js index e2d6632..e41e3e3 100644 --- a/dyn-skip-midroll-alt/dyn-skip-midroll-alt-ublock-origin.js +++ b/dyn-skip-midroll-alt/dyn-skip-midroll-alt-ublock-origin.js @@ -17,9 +17,11 @@ twitch-videoad.js application/javascript scope.OPT_MODE_PROXY_M3U8_OBFUSCATED = false; scope.OPT_MODE_PROXY_M3U8_FULL_URL = false; scope.OPT_MODE_PROXY_M3U8_PARTIAL_URL = false; - scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'thunderdome'; + scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'picture-by-picture'; + scope.OPT_BACKUP_PLAYER_TYPE = 'picture-by-picture'; scope.OPT_INITIAL_M3U8_ATTEMPTS = 1; scope.OPT_ACCESS_TOKEN_PLAYER_TYPE = ''; + scope.OPT_ACCESS_TOKEN_TEMPLATE = true; scope.AD_SIGNIFIER = 'stitched-ad'; scope.LIVE_SIGNIFIER = ',live'; scope.CLIENT_ID = 'kimne78kx3ncx6brgo4mv6wki5h1ko'; @@ -45,6 +47,8 @@ twitch-videoad.js application/javascript scope.CurrentChannelNameFromM3U8 = null; scope.LastAdUrl = null; scope.LastAdTime = 0; + // Need this in both scopes. Window scope needs to update this to worker scope. + scope.gql_device_id = null; } declareOptions(window); //////////////////////////////////// @@ -57,7 +61,6 @@ twitch-videoad.js application/javascript var foundAdBanner = false;// Is the ad banner visible (top left of screen) //////////////////////////////////// var notifyAdsWatchedReloadNextTime = 0; - var gql_device_id = null; var twitchMainWorker = null; const oldWorker = window.Worker; window.Worker = class Worker extends oldWorker { @@ -76,7 +79,14 @@ twitch-videoad.js application/javascript ${getSegmentTimes.toString()} ${hookWorkerFetch.toString()} ${declareOptions.toString()} + ${getAccessToken.toString()} + ${gqlRequest.toString()} declareOptions(self); + self.addEventListener('message', function(e) { + if (e.data.key == 'UboUpdateDeviceId') { + gql_device_id = e.data.value; + } + }); hookWorkerFetch(); importScripts('${jsURL}'); ` @@ -170,12 +180,12 @@ twitch-videoad.js application/javascript if (!streamInfo.BackupFailed && streamInfo.BackupUrl == null) { // NOTE: We currently don't fetch the oauth_token. You wont be able to access private streams like this. streamInfo.BackupFailed = true; - var accessTokenResponse = await realFetch('https://api.twitch.tv/api/channels/' + streamInfo.ChannelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=picture-by-picture&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, OPT_BACKUP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + streamInfo.ChannelName + '.m3u8' + streamInfo.RootM3U8Params); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await realFetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 @@ -363,6 +373,41 @@ twitch-videoad.js application/javascript }, }]; } + function getAccessToken(channelName, playerType) { + var body = null; + if (OPT_ACCESS_TOKEN_TEMPLATE) { + var templateQuery = 'query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}'; + body = { + operationName: 'PlaybackAccessToken_Template', + query: templateQuery, + variables: { + 'isLive': true, + 'login': channelName, + 'isVod': false, + 'vodID': '', + 'playerType': playerType + } + }; + } else { + body = { + operationName: 'PlaybackAccessToken', + variables: { + isLive: true, + login: channelName, + isVod: false, + vodID: '', + playerType: playerType + }, + extensions: { + persistedQuery: { + version: 1, + sha256Hash: '0828119ded1c13477966434e15800ff57ddacf13ba1911c129dc2200705b0712', + } + } + }; + } + return gqlRequest(body); + } function gqlRequest(body) { return fetch('https://gql.twitch.tv/gql', { method: 'POST', @@ -488,6 +533,12 @@ twitch-videoad.js application/javascript if (typeof deviceId === 'string') { gql_device_id = deviceId; } + if (gql_device_id && twitchMainWorker) { + twitchMainWorker.postMessage({ + key: 'UboUpdateDeviceId', + value: gql_device_id + }); + } if (OPT_MODE_NOTIFY_ADS_WATCHED) { var tok = null, sig = null; if (url.includes('/access_token')) { @@ -594,12 +645,12 @@ twitch-videoad.js application/javascript // Create new video stream TODO: Do this with callbacks var channelName = window.location.pathname.substr(1);// TODO: Better way of determining the channel name var tempM3u8Url = null; - var accessTokenResponse = await fetch('https://api.twitch.tv/api/channels/' + channelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=' + OPT_VIDEO_SWAP_PLAYER_TYPE + '&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(channelName, OPT_VIDEO_SWAP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + channelName + '.m3u8?allow_source=true'); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await fetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 diff --git a/dyn-skip-midroll-alt/dyn-skip-midroll-alt.user.js b/dyn-skip-midroll-alt/dyn-skip-midroll-alt.user.js index fc6795d..d7f3cd4 100644 --- a/dyn-skip-midroll-alt/dyn-skip-midroll-alt.user.js +++ b/dyn-skip-midroll-alt/dyn-skip-midroll-alt.user.js @@ -26,9 +26,11 @@ scope.OPT_MODE_PROXY_M3U8_OBFUSCATED = false; scope.OPT_MODE_PROXY_M3U8_FULL_URL = false; scope.OPT_MODE_PROXY_M3U8_PARTIAL_URL = false; - scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'thunderdome'; + scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'picture-by-picture'; + scope.OPT_BACKUP_PLAYER_TYPE = 'picture-by-picture'; scope.OPT_INITIAL_M3U8_ATTEMPTS = 1; scope.OPT_ACCESS_TOKEN_PLAYER_TYPE = ''; + scope.OPT_ACCESS_TOKEN_TEMPLATE = true; scope.AD_SIGNIFIER = 'stitched-ad'; scope.LIVE_SIGNIFIER = ',live'; scope.CLIENT_ID = 'kimne78kx3ncx6brgo4mv6wki5h1ko'; @@ -54,6 +56,8 @@ scope.CurrentChannelNameFromM3U8 = null; scope.LastAdUrl = null; scope.LastAdTime = 0; + // Need this in both scopes. Window scope needs to update this to worker scope. + scope.gql_device_id = null; } declareOptions(window); //////////////////////////////////// @@ -66,7 +70,6 @@ var foundAdBanner = false;// Is the ad banner visible (top left of screen) //////////////////////////////////// var notifyAdsWatchedReloadNextTime = 0; - var gql_device_id = null; var twitchMainWorker = null; const oldWorker = window.Worker; window.Worker = class Worker extends oldWorker { @@ -85,7 +88,14 @@ ${getSegmentTimes.toString()} ${hookWorkerFetch.toString()} ${declareOptions.toString()} + ${getAccessToken.toString()} + ${gqlRequest.toString()} declareOptions(self); + self.addEventListener('message', function(e) { + if (e.data.key == 'UboUpdateDeviceId') { + gql_device_id = e.data.value; + } + }); hookWorkerFetch(); importScripts('${jsURL}'); ` @@ -179,12 +189,12 @@ if (!streamInfo.BackupFailed && streamInfo.BackupUrl == null) { // NOTE: We currently don't fetch the oauth_token. You wont be able to access private streams like this. streamInfo.BackupFailed = true; - var accessTokenResponse = await realFetch('https://api.twitch.tv/api/channels/' + streamInfo.ChannelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=picture-by-picture&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, OPT_BACKUP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + streamInfo.ChannelName + '.m3u8' + streamInfo.RootM3U8Params); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await realFetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 @@ -372,6 +382,41 @@ }, }]; } + function getAccessToken(channelName, playerType) { + var body = null; + if (OPT_ACCESS_TOKEN_TEMPLATE) { + var templateQuery = 'query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}'; + body = { + operationName: 'PlaybackAccessToken_Template', + query: templateQuery, + variables: { + 'isLive': true, + 'login': channelName, + 'isVod': false, + 'vodID': '', + 'playerType': playerType + } + }; + } else { + body = { + operationName: 'PlaybackAccessToken', + variables: { + isLive: true, + login: channelName, + isVod: false, + vodID: '', + playerType: playerType + }, + extensions: { + persistedQuery: { + version: 1, + sha256Hash: '0828119ded1c13477966434e15800ff57ddacf13ba1911c129dc2200705b0712', + } + } + }; + } + return gqlRequest(body); + } function gqlRequest(body) { return fetch('https://gql.twitch.tv/gql', { method: 'POST', @@ -497,6 +542,12 @@ if (typeof deviceId === 'string') { gql_device_id = deviceId; } + if (gql_device_id && twitchMainWorker) { + twitchMainWorker.postMessage({ + key: 'UboUpdateDeviceId', + value: gql_device_id + }); + } if (OPT_MODE_NOTIFY_ADS_WATCHED) { var tok = null, sig = null; if (url.includes('/access_token')) { @@ -603,12 +654,12 @@ // Create new video stream TODO: Do this with callbacks var channelName = window.location.pathname.substr(1);// TODO: Better way of determining the channel name var tempM3u8Url = null; - var accessTokenResponse = await fetch('https://api.twitch.tv/api/channels/' + channelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=' + OPT_VIDEO_SWAP_PLAYER_TYPE + '&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(channelName, OPT_VIDEO_SWAP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + channelName + '.m3u8?allow_source=true'); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await fetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 diff --git a/dyn-skip-midroll/dyn-skip-midroll-ublock-origin.js b/dyn-skip-midroll/dyn-skip-midroll-ublock-origin.js index 7061de9..2972014 100644 --- a/dyn-skip-midroll/dyn-skip-midroll-ublock-origin.js +++ b/dyn-skip-midroll/dyn-skip-midroll-ublock-origin.js @@ -17,9 +17,11 @@ twitch-videoad.js application/javascript scope.OPT_MODE_PROXY_M3U8_OBFUSCATED = false; scope.OPT_MODE_PROXY_M3U8_FULL_URL = false; scope.OPT_MODE_PROXY_M3U8_PARTIAL_URL = false; - scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'thunderdome'; + scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'picture-by-picture'; + scope.OPT_BACKUP_PLAYER_TYPE = 'picture-by-picture'; scope.OPT_INITIAL_M3U8_ATTEMPTS = 1; scope.OPT_ACCESS_TOKEN_PLAYER_TYPE = ''; + scope.OPT_ACCESS_TOKEN_TEMPLATE = true; scope.AD_SIGNIFIER = 'stitched-ad'; scope.LIVE_SIGNIFIER = ',live'; scope.CLIENT_ID = 'kimne78kx3ncx6brgo4mv6wki5h1ko'; @@ -45,6 +47,8 @@ twitch-videoad.js application/javascript scope.CurrentChannelNameFromM3U8 = null; scope.LastAdUrl = null; scope.LastAdTime = 0; + // Need this in both scopes. Window scope needs to update this to worker scope. + scope.gql_device_id = null; } declareOptions(window); //////////////////////////////////// @@ -57,7 +61,6 @@ twitch-videoad.js application/javascript var foundAdBanner = false;// Is the ad banner visible (top left of screen) //////////////////////////////////// var notifyAdsWatchedReloadNextTime = 0; - var gql_device_id = null; var twitchMainWorker = null; const oldWorker = window.Worker; window.Worker = class Worker extends oldWorker { @@ -76,7 +79,14 @@ twitch-videoad.js application/javascript ${getSegmentTimes.toString()} ${hookWorkerFetch.toString()} ${declareOptions.toString()} + ${getAccessToken.toString()} + ${gqlRequest.toString()} declareOptions(self); + self.addEventListener('message', function(e) { + if (e.data.key == 'UboUpdateDeviceId') { + gql_device_id = e.data.value; + } + }); hookWorkerFetch(); importScripts('${jsURL}'); ` @@ -170,12 +180,12 @@ twitch-videoad.js application/javascript if (!streamInfo.BackupFailed && streamInfo.BackupUrl == null) { // NOTE: We currently don't fetch the oauth_token. You wont be able to access private streams like this. streamInfo.BackupFailed = true; - var accessTokenResponse = await realFetch('https://api.twitch.tv/api/channels/' + streamInfo.ChannelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=picture-by-picture&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, OPT_BACKUP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + streamInfo.ChannelName + '.m3u8' + streamInfo.RootM3U8Params); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await realFetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 @@ -363,6 +373,41 @@ twitch-videoad.js application/javascript }, }]; } + function getAccessToken(channelName, playerType) { + var body = null; + if (OPT_ACCESS_TOKEN_TEMPLATE) { + var templateQuery = 'query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}'; + body = { + operationName: 'PlaybackAccessToken_Template', + query: templateQuery, + variables: { + 'isLive': true, + 'login': channelName, + 'isVod': false, + 'vodID': '', + 'playerType': playerType + } + }; + } else { + body = { + operationName: 'PlaybackAccessToken', + variables: { + isLive: true, + login: channelName, + isVod: false, + vodID: '', + playerType: playerType + }, + extensions: { + persistedQuery: { + version: 1, + sha256Hash: '0828119ded1c13477966434e15800ff57ddacf13ba1911c129dc2200705b0712', + } + } + }; + } + return gqlRequest(body); + } function gqlRequest(body) { return fetch('https://gql.twitch.tv/gql', { method: 'POST', @@ -488,6 +533,12 @@ twitch-videoad.js application/javascript if (typeof deviceId === 'string') { gql_device_id = deviceId; } + if (gql_device_id && twitchMainWorker) { + twitchMainWorker.postMessage({ + key: 'UboUpdateDeviceId', + value: gql_device_id + }); + } if (OPT_MODE_NOTIFY_ADS_WATCHED) { var tok = null, sig = null; if (url.includes('/access_token')) { @@ -594,12 +645,12 @@ twitch-videoad.js application/javascript // Create new video stream TODO: Do this with callbacks var channelName = window.location.pathname.substr(1);// TODO: Better way of determining the channel name var tempM3u8Url = null; - var accessTokenResponse = await fetch('https://api.twitch.tv/api/channels/' + channelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=' + OPT_VIDEO_SWAP_PLAYER_TYPE + '&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(channelName, OPT_VIDEO_SWAP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + channelName + '.m3u8?allow_source=true'); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await fetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 diff --git a/dyn-skip-midroll/dyn-skip-midroll.user.js b/dyn-skip-midroll/dyn-skip-midroll.user.js index bbd3e9d..aa3e3cc 100644 --- a/dyn-skip-midroll/dyn-skip-midroll.user.js +++ b/dyn-skip-midroll/dyn-skip-midroll.user.js @@ -26,9 +26,11 @@ scope.OPT_MODE_PROXY_M3U8_OBFUSCATED = false; scope.OPT_MODE_PROXY_M3U8_FULL_URL = false; scope.OPT_MODE_PROXY_M3U8_PARTIAL_URL = false; - scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'thunderdome'; + scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'picture-by-picture'; + scope.OPT_BACKUP_PLAYER_TYPE = 'picture-by-picture'; scope.OPT_INITIAL_M3U8_ATTEMPTS = 1; scope.OPT_ACCESS_TOKEN_PLAYER_TYPE = ''; + scope.OPT_ACCESS_TOKEN_TEMPLATE = true; scope.AD_SIGNIFIER = 'stitched-ad'; scope.LIVE_SIGNIFIER = ',live'; scope.CLIENT_ID = 'kimne78kx3ncx6brgo4mv6wki5h1ko'; @@ -54,6 +56,8 @@ scope.CurrentChannelNameFromM3U8 = null; scope.LastAdUrl = null; scope.LastAdTime = 0; + // Need this in both scopes. Window scope needs to update this to worker scope. + scope.gql_device_id = null; } declareOptions(window); //////////////////////////////////// @@ -66,7 +70,6 @@ var foundAdBanner = false;// Is the ad banner visible (top left of screen) //////////////////////////////////// var notifyAdsWatchedReloadNextTime = 0; - var gql_device_id = null; var twitchMainWorker = null; const oldWorker = window.Worker; window.Worker = class Worker extends oldWorker { @@ -85,7 +88,14 @@ ${getSegmentTimes.toString()} ${hookWorkerFetch.toString()} ${declareOptions.toString()} + ${getAccessToken.toString()} + ${gqlRequest.toString()} declareOptions(self); + self.addEventListener('message', function(e) { + if (e.data.key == 'UboUpdateDeviceId') { + gql_device_id = e.data.value; + } + }); hookWorkerFetch(); importScripts('${jsURL}'); ` @@ -179,12 +189,12 @@ if (!streamInfo.BackupFailed && streamInfo.BackupUrl == null) { // NOTE: We currently don't fetch the oauth_token. You wont be able to access private streams like this. streamInfo.BackupFailed = true; - var accessTokenResponse = await realFetch('https://api.twitch.tv/api/channels/' + streamInfo.ChannelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=picture-by-picture&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, OPT_BACKUP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + streamInfo.ChannelName + '.m3u8' + streamInfo.RootM3U8Params); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await realFetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 @@ -372,6 +382,41 @@ }, }]; } + function getAccessToken(channelName, playerType) { + var body = null; + if (OPT_ACCESS_TOKEN_TEMPLATE) { + var templateQuery = 'query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}'; + body = { + operationName: 'PlaybackAccessToken_Template', + query: templateQuery, + variables: { + 'isLive': true, + 'login': channelName, + 'isVod': false, + 'vodID': '', + 'playerType': playerType + } + }; + } else { + body = { + operationName: 'PlaybackAccessToken', + variables: { + isLive: true, + login: channelName, + isVod: false, + vodID: '', + playerType: playerType + }, + extensions: { + persistedQuery: { + version: 1, + sha256Hash: '0828119ded1c13477966434e15800ff57ddacf13ba1911c129dc2200705b0712', + } + } + }; + } + return gqlRequest(body); + } function gqlRequest(body) { return fetch('https://gql.twitch.tv/gql', { method: 'POST', @@ -497,6 +542,12 @@ if (typeof deviceId === 'string') { gql_device_id = deviceId; } + if (gql_device_id && twitchMainWorker) { + twitchMainWorker.postMessage({ + key: 'UboUpdateDeviceId', + value: gql_device_id + }); + } if (OPT_MODE_NOTIFY_ADS_WATCHED) { var tok = null, sig = null; if (url.includes('/access_token')) { @@ -603,12 +654,12 @@ // Create new video stream TODO: Do this with callbacks var channelName = window.location.pathname.substr(1);// TODO: Better way of determining the channel name var tempM3u8Url = null; - var accessTokenResponse = await fetch('https://api.twitch.tv/api/channels/' + channelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=' + OPT_VIDEO_SWAP_PLAYER_TYPE + '&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(channelName, OPT_VIDEO_SWAP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + channelName + '.m3u8?allow_source=true'); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await fetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 diff --git a/dyn-skip/dyn-skip-ublock-origin.js b/dyn-skip/dyn-skip-ublock-origin.js index 0dc7b34..a3abffc 100644 --- a/dyn-skip/dyn-skip-ublock-origin.js +++ b/dyn-skip/dyn-skip-ublock-origin.js @@ -17,9 +17,11 @@ twitch-videoad.js application/javascript scope.OPT_MODE_PROXY_M3U8_OBFUSCATED = false; scope.OPT_MODE_PROXY_M3U8_FULL_URL = false; scope.OPT_MODE_PROXY_M3U8_PARTIAL_URL = false; - scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'thunderdome'; + scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'picture-by-picture'; + scope.OPT_BACKUP_PLAYER_TYPE = 'picture-by-picture'; scope.OPT_INITIAL_M3U8_ATTEMPTS = 1; scope.OPT_ACCESS_TOKEN_PLAYER_TYPE = ''; + scope.OPT_ACCESS_TOKEN_TEMPLATE = true; scope.AD_SIGNIFIER = 'stitched-ad'; scope.LIVE_SIGNIFIER = ',live'; scope.CLIENT_ID = 'kimne78kx3ncx6brgo4mv6wki5h1ko'; @@ -45,6 +47,8 @@ twitch-videoad.js application/javascript scope.CurrentChannelNameFromM3U8 = null; scope.LastAdUrl = null; scope.LastAdTime = 0; + // Need this in both scopes. Window scope needs to update this to worker scope. + scope.gql_device_id = null; } declareOptions(window); //////////////////////////////////// @@ -57,7 +61,6 @@ twitch-videoad.js application/javascript var foundAdBanner = false;// Is the ad banner visible (top left of screen) //////////////////////////////////// var notifyAdsWatchedReloadNextTime = 0; - var gql_device_id = null; var twitchMainWorker = null; const oldWorker = window.Worker; window.Worker = class Worker extends oldWorker { @@ -76,7 +79,14 @@ twitch-videoad.js application/javascript ${getSegmentTimes.toString()} ${hookWorkerFetch.toString()} ${declareOptions.toString()} + ${getAccessToken.toString()} + ${gqlRequest.toString()} declareOptions(self); + self.addEventListener('message', function(e) { + if (e.data.key == 'UboUpdateDeviceId') { + gql_device_id = e.data.value; + } + }); hookWorkerFetch(); importScripts('${jsURL}'); ` @@ -170,12 +180,12 @@ twitch-videoad.js application/javascript if (!streamInfo.BackupFailed && streamInfo.BackupUrl == null) { // NOTE: We currently don't fetch the oauth_token. You wont be able to access private streams like this. streamInfo.BackupFailed = true; - var accessTokenResponse = await realFetch('https://api.twitch.tv/api/channels/' + streamInfo.ChannelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=picture-by-picture&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, OPT_BACKUP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + streamInfo.ChannelName + '.m3u8' + streamInfo.RootM3U8Params); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await realFetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 @@ -363,6 +373,41 @@ twitch-videoad.js application/javascript }, }]; } + function getAccessToken(channelName, playerType) { + var body = null; + if (OPT_ACCESS_TOKEN_TEMPLATE) { + var templateQuery = 'query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}'; + body = { + operationName: 'PlaybackAccessToken_Template', + query: templateQuery, + variables: { + 'isLive': true, + 'login': channelName, + 'isVod': false, + 'vodID': '', + 'playerType': playerType + } + }; + } else { + body = { + operationName: 'PlaybackAccessToken', + variables: { + isLive: true, + login: channelName, + isVod: false, + vodID: '', + playerType: playerType + }, + extensions: { + persistedQuery: { + version: 1, + sha256Hash: '0828119ded1c13477966434e15800ff57ddacf13ba1911c129dc2200705b0712', + } + } + }; + } + return gqlRequest(body); + } function gqlRequest(body) { return fetch('https://gql.twitch.tv/gql', { method: 'POST', @@ -488,6 +533,12 @@ twitch-videoad.js application/javascript if (typeof deviceId === 'string') { gql_device_id = deviceId; } + if (gql_device_id && twitchMainWorker) { + twitchMainWorker.postMessage({ + key: 'UboUpdateDeviceId', + value: gql_device_id + }); + } if (OPT_MODE_NOTIFY_ADS_WATCHED) { var tok = null, sig = null; if (url.includes('/access_token')) { @@ -594,12 +645,12 @@ twitch-videoad.js application/javascript // Create new video stream TODO: Do this with callbacks var channelName = window.location.pathname.substr(1);// TODO: Better way of determining the channel name var tempM3u8Url = null; - var accessTokenResponse = await fetch('https://api.twitch.tv/api/channels/' + channelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=' + OPT_VIDEO_SWAP_PLAYER_TYPE + '&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(channelName, OPT_VIDEO_SWAP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + channelName + '.m3u8?allow_source=true'); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await fetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 diff --git a/dyn-skip/dyn-skip.user.js b/dyn-skip/dyn-skip.user.js index 337977e..e6f2047 100644 --- a/dyn-skip/dyn-skip.user.js +++ b/dyn-skip/dyn-skip.user.js @@ -26,9 +26,11 @@ scope.OPT_MODE_PROXY_M3U8_OBFUSCATED = false; scope.OPT_MODE_PROXY_M3U8_FULL_URL = false; scope.OPT_MODE_PROXY_M3U8_PARTIAL_URL = false; - scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'thunderdome'; + scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'picture-by-picture'; + scope.OPT_BACKUP_PLAYER_TYPE = 'picture-by-picture'; scope.OPT_INITIAL_M3U8_ATTEMPTS = 1; scope.OPT_ACCESS_TOKEN_PLAYER_TYPE = ''; + scope.OPT_ACCESS_TOKEN_TEMPLATE = true; scope.AD_SIGNIFIER = 'stitched-ad'; scope.LIVE_SIGNIFIER = ',live'; scope.CLIENT_ID = 'kimne78kx3ncx6brgo4mv6wki5h1ko'; @@ -54,6 +56,8 @@ scope.CurrentChannelNameFromM3U8 = null; scope.LastAdUrl = null; scope.LastAdTime = 0; + // Need this in both scopes. Window scope needs to update this to worker scope. + scope.gql_device_id = null; } declareOptions(window); //////////////////////////////////// @@ -66,7 +70,6 @@ var foundAdBanner = false;// Is the ad banner visible (top left of screen) //////////////////////////////////// var notifyAdsWatchedReloadNextTime = 0; - var gql_device_id = null; var twitchMainWorker = null; const oldWorker = window.Worker; window.Worker = class Worker extends oldWorker { @@ -85,7 +88,14 @@ ${getSegmentTimes.toString()} ${hookWorkerFetch.toString()} ${declareOptions.toString()} + ${getAccessToken.toString()} + ${gqlRequest.toString()} declareOptions(self); + self.addEventListener('message', function(e) { + if (e.data.key == 'UboUpdateDeviceId') { + gql_device_id = e.data.value; + } + }); hookWorkerFetch(); importScripts('${jsURL}'); ` @@ -179,12 +189,12 @@ if (!streamInfo.BackupFailed && streamInfo.BackupUrl == null) { // NOTE: We currently don't fetch the oauth_token. You wont be able to access private streams like this. streamInfo.BackupFailed = true; - var accessTokenResponse = await realFetch('https://api.twitch.tv/api/channels/' + streamInfo.ChannelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=picture-by-picture&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, OPT_BACKUP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + streamInfo.ChannelName + '.m3u8' + streamInfo.RootM3U8Params); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await realFetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 @@ -372,6 +382,41 @@ }, }]; } + function getAccessToken(channelName, playerType) { + var body = null; + if (OPT_ACCESS_TOKEN_TEMPLATE) { + var templateQuery = 'query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}'; + body = { + operationName: 'PlaybackAccessToken_Template', + query: templateQuery, + variables: { + 'isLive': true, + 'login': channelName, + 'isVod': false, + 'vodID': '', + 'playerType': playerType + } + }; + } else { + body = { + operationName: 'PlaybackAccessToken', + variables: { + isLive: true, + login: channelName, + isVod: false, + vodID: '', + playerType: playerType + }, + extensions: { + persistedQuery: { + version: 1, + sha256Hash: '0828119ded1c13477966434e15800ff57ddacf13ba1911c129dc2200705b0712', + } + } + }; + } + return gqlRequest(body); + } function gqlRequest(body) { return fetch('https://gql.twitch.tv/gql', { method: 'POST', @@ -497,6 +542,12 @@ if (typeof deviceId === 'string') { gql_device_id = deviceId; } + if (gql_device_id && twitchMainWorker) { + twitchMainWorker.postMessage({ + key: 'UboUpdateDeviceId', + value: gql_device_id + }); + } if (OPT_MODE_NOTIFY_ADS_WATCHED) { var tok = null, sig = null; if (url.includes('/access_token')) { @@ -603,12 +654,12 @@ // Create new video stream TODO: Do this with callbacks var channelName = window.location.pathname.substr(1);// TODO: Better way of determining the channel name var tempM3u8Url = null; - var accessTokenResponse = await fetch('https://api.twitch.tv/api/channels/' + channelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=' + OPT_VIDEO_SWAP_PLAYER_TYPE + '&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(channelName, OPT_VIDEO_SWAP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + channelName + '.m3u8?allow_source=true'); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await fetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 diff --git a/dyn-video-swap/dyn-video-swap-ublock-origin.js b/dyn-video-swap/dyn-video-swap-ublock-origin.js index f82efb0..724b955 100644 --- a/dyn-video-swap/dyn-video-swap-ublock-origin.js +++ b/dyn-video-swap/dyn-video-swap-ublock-origin.js @@ -17,9 +17,11 @@ twitch-videoad.js application/javascript scope.OPT_MODE_PROXY_M3U8_OBFUSCATED = false; scope.OPT_MODE_PROXY_M3U8_FULL_URL = false; scope.OPT_MODE_PROXY_M3U8_PARTIAL_URL = false; - scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'thunderdome'; + scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'picture-by-picture'; + scope.OPT_BACKUP_PLAYER_TYPE = 'picture-by-picture'; scope.OPT_INITIAL_M3U8_ATTEMPTS = 1; scope.OPT_ACCESS_TOKEN_PLAYER_TYPE = ''; + scope.OPT_ACCESS_TOKEN_TEMPLATE = true; scope.AD_SIGNIFIER = 'stitched-ad'; scope.LIVE_SIGNIFIER = ',live'; scope.CLIENT_ID = 'kimne78kx3ncx6brgo4mv6wki5h1ko'; @@ -45,6 +47,8 @@ twitch-videoad.js application/javascript scope.CurrentChannelNameFromM3U8 = null; scope.LastAdUrl = null; scope.LastAdTime = 0; + // Need this in both scopes. Window scope needs to update this to worker scope. + scope.gql_device_id = null; } declareOptions(window); //////////////////////////////////// @@ -57,7 +61,6 @@ twitch-videoad.js application/javascript var foundAdBanner = false;// Is the ad banner visible (top left of screen) //////////////////////////////////// var notifyAdsWatchedReloadNextTime = 0; - var gql_device_id = null; var twitchMainWorker = null; const oldWorker = window.Worker; window.Worker = class Worker extends oldWorker { @@ -76,7 +79,14 @@ twitch-videoad.js application/javascript ${getSegmentTimes.toString()} ${hookWorkerFetch.toString()} ${declareOptions.toString()} + ${getAccessToken.toString()} + ${gqlRequest.toString()} declareOptions(self); + self.addEventListener('message', function(e) { + if (e.data.key == 'UboUpdateDeviceId') { + gql_device_id = e.data.value; + } + }); hookWorkerFetch(); importScripts('${jsURL}'); ` @@ -170,12 +180,12 @@ twitch-videoad.js application/javascript if (!streamInfo.BackupFailed && streamInfo.BackupUrl == null) { // NOTE: We currently don't fetch the oauth_token. You wont be able to access private streams like this. streamInfo.BackupFailed = true; - var accessTokenResponse = await realFetch('https://api.twitch.tv/api/channels/' + streamInfo.ChannelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=picture-by-picture&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, OPT_BACKUP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + streamInfo.ChannelName + '.m3u8' + streamInfo.RootM3U8Params); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await realFetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 @@ -363,6 +373,41 @@ twitch-videoad.js application/javascript }, }]; } + function getAccessToken(channelName, playerType) { + var body = null; + if (OPT_ACCESS_TOKEN_TEMPLATE) { + var templateQuery = 'query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}'; + body = { + operationName: 'PlaybackAccessToken_Template', + query: templateQuery, + variables: { + 'isLive': true, + 'login': channelName, + 'isVod': false, + 'vodID': '', + 'playerType': playerType + } + }; + } else { + body = { + operationName: 'PlaybackAccessToken', + variables: { + isLive: true, + login: channelName, + isVod: false, + vodID: '', + playerType: playerType + }, + extensions: { + persistedQuery: { + version: 1, + sha256Hash: '0828119ded1c13477966434e15800ff57ddacf13ba1911c129dc2200705b0712', + } + } + }; + } + return gqlRequest(body); + } function gqlRequest(body) { return fetch('https://gql.twitch.tv/gql', { method: 'POST', @@ -488,6 +533,12 @@ twitch-videoad.js application/javascript if (typeof deviceId === 'string') { gql_device_id = deviceId; } + if (gql_device_id && twitchMainWorker) { + twitchMainWorker.postMessage({ + key: 'UboUpdateDeviceId', + value: gql_device_id + }); + } if (OPT_MODE_NOTIFY_ADS_WATCHED) { var tok = null, sig = null; if (url.includes('/access_token')) { @@ -594,12 +645,12 @@ twitch-videoad.js application/javascript // Create new video stream TODO: Do this with callbacks var channelName = window.location.pathname.substr(1);// TODO: Better way of determining the channel name var tempM3u8Url = null; - var accessTokenResponse = await fetch('https://api.twitch.tv/api/channels/' + channelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=' + OPT_VIDEO_SWAP_PLAYER_TYPE + '&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(channelName, OPT_VIDEO_SWAP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + channelName + '.m3u8?allow_source=true'); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await fetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 diff --git a/dyn-video-swap/dyn-video-swap.user.js b/dyn-video-swap/dyn-video-swap.user.js index b814604..5865f04 100644 --- a/dyn-video-swap/dyn-video-swap.user.js +++ b/dyn-video-swap/dyn-video-swap.user.js @@ -26,9 +26,11 @@ scope.OPT_MODE_PROXY_M3U8_OBFUSCATED = false; scope.OPT_MODE_PROXY_M3U8_FULL_URL = false; scope.OPT_MODE_PROXY_M3U8_PARTIAL_URL = false; - scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'thunderdome'; + scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'picture-by-picture'; + scope.OPT_BACKUP_PLAYER_TYPE = 'picture-by-picture'; scope.OPT_INITIAL_M3U8_ATTEMPTS = 1; scope.OPT_ACCESS_TOKEN_PLAYER_TYPE = ''; + scope.OPT_ACCESS_TOKEN_TEMPLATE = true; scope.AD_SIGNIFIER = 'stitched-ad'; scope.LIVE_SIGNIFIER = ',live'; scope.CLIENT_ID = 'kimne78kx3ncx6brgo4mv6wki5h1ko'; @@ -54,6 +56,8 @@ scope.CurrentChannelNameFromM3U8 = null; scope.LastAdUrl = null; scope.LastAdTime = 0; + // Need this in both scopes. Window scope needs to update this to worker scope. + scope.gql_device_id = null; } declareOptions(window); //////////////////////////////////// @@ -66,7 +70,6 @@ var foundAdBanner = false;// Is the ad banner visible (top left of screen) //////////////////////////////////// var notifyAdsWatchedReloadNextTime = 0; - var gql_device_id = null; var twitchMainWorker = null; const oldWorker = window.Worker; window.Worker = class Worker extends oldWorker { @@ -85,7 +88,14 @@ ${getSegmentTimes.toString()} ${hookWorkerFetch.toString()} ${declareOptions.toString()} + ${getAccessToken.toString()} + ${gqlRequest.toString()} declareOptions(self); + self.addEventListener('message', function(e) { + if (e.data.key == 'UboUpdateDeviceId') { + gql_device_id = e.data.value; + } + }); hookWorkerFetch(); importScripts('${jsURL}'); ` @@ -179,12 +189,12 @@ if (!streamInfo.BackupFailed && streamInfo.BackupUrl == null) { // NOTE: We currently don't fetch the oauth_token. You wont be able to access private streams like this. streamInfo.BackupFailed = true; - var accessTokenResponse = await realFetch('https://api.twitch.tv/api/channels/' + streamInfo.ChannelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=picture-by-picture&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, OPT_BACKUP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + streamInfo.ChannelName + '.m3u8' + streamInfo.RootM3U8Params); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await realFetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 @@ -372,6 +382,41 @@ }, }]; } + function getAccessToken(channelName, playerType) { + var body = null; + if (OPT_ACCESS_TOKEN_TEMPLATE) { + var templateQuery = 'query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}'; + body = { + operationName: 'PlaybackAccessToken_Template', + query: templateQuery, + variables: { + 'isLive': true, + 'login': channelName, + 'isVod': false, + 'vodID': '', + 'playerType': playerType + } + }; + } else { + body = { + operationName: 'PlaybackAccessToken', + variables: { + isLive: true, + login: channelName, + isVod: false, + vodID: '', + playerType: playerType + }, + extensions: { + persistedQuery: { + version: 1, + sha256Hash: '0828119ded1c13477966434e15800ff57ddacf13ba1911c129dc2200705b0712', + } + } + }; + } + return gqlRequest(body); + } function gqlRequest(body) { return fetch('https://gql.twitch.tv/gql', { method: 'POST', @@ -497,6 +542,12 @@ if (typeof deviceId === 'string') { gql_device_id = deviceId; } + if (gql_device_id && twitchMainWorker) { + twitchMainWorker.postMessage({ + key: 'UboUpdateDeviceId', + value: gql_device_id + }); + } if (OPT_MODE_NOTIFY_ADS_WATCHED) { var tok = null, sig = null; if (url.includes('/access_token')) { @@ -603,12 +654,12 @@ // Create new video stream TODO: Do this with callbacks var channelName = window.location.pathname.substr(1);// TODO: Better way of determining the channel name var tempM3u8Url = null; - var accessTokenResponse = await fetch('https://api.twitch.tv/api/channels/' + channelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=' + OPT_VIDEO_SWAP_PLAYER_TYPE + '&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(channelName, OPT_VIDEO_SWAP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + channelName + '.m3u8?allow_source=true'); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await fetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 diff --git a/dyn/dyn-ublock-origin.js b/dyn/dyn-ublock-origin.js index 3cb8fcf..ec18cf9 100644 --- a/dyn/dyn-ublock-origin.js +++ b/dyn/dyn-ublock-origin.js @@ -17,9 +17,11 @@ twitch-videoad.js application/javascript scope.OPT_MODE_PROXY_M3U8_OBFUSCATED = false; scope.OPT_MODE_PROXY_M3U8_FULL_URL = false; scope.OPT_MODE_PROXY_M3U8_PARTIAL_URL = false; - scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'thunderdome'; + scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'picture-by-picture'; + scope.OPT_BACKUP_PLAYER_TYPE = 'picture-by-picture'; scope.OPT_INITIAL_M3U8_ATTEMPTS = 1; scope.OPT_ACCESS_TOKEN_PLAYER_TYPE = ''; + scope.OPT_ACCESS_TOKEN_TEMPLATE = true; scope.AD_SIGNIFIER = 'stitched-ad'; scope.LIVE_SIGNIFIER = ',live'; scope.CLIENT_ID = 'kimne78kx3ncx6brgo4mv6wki5h1ko'; @@ -45,6 +47,8 @@ twitch-videoad.js application/javascript scope.CurrentChannelNameFromM3U8 = null; scope.LastAdUrl = null; scope.LastAdTime = 0; + // Need this in both scopes. Window scope needs to update this to worker scope. + scope.gql_device_id = null; } declareOptions(window); //////////////////////////////////// @@ -57,7 +61,6 @@ twitch-videoad.js application/javascript var foundAdBanner = false;// Is the ad banner visible (top left of screen) //////////////////////////////////// var notifyAdsWatchedReloadNextTime = 0; - var gql_device_id = null; var twitchMainWorker = null; const oldWorker = window.Worker; window.Worker = class Worker extends oldWorker { @@ -76,7 +79,14 @@ twitch-videoad.js application/javascript ${getSegmentTimes.toString()} ${hookWorkerFetch.toString()} ${declareOptions.toString()} + ${getAccessToken.toString()} + ${gqlRequest.toString()} declareOptions(self); + self.addEventListener('message', function(e) { + if (e.data.key == 'UboUpdateDeviceId') { + gql_device_id = e.data.value; + } + }); hookWorkerFetch(); importScripts('${jsURL}'); ` @@ -170,12 +180,12 @@ twitch-videoad.js application/javascript if (!streamInfo.BackupFailed && streamInfo.BackupUrl == null) { // NOTE: We currently don't fetch the oauth_token. You wont be able to access private streams like this. streamInfo.BackupFailed = true; - var accessTokenResponse = await realFetch('https://api.twitch.tv/api/channels/' + streamInfo.ChannelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=picture-by-picture&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, OPT_BACKUP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + streamInfo.ChannelName + '.m3u8' + streamInfo.RootM3U8Params); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await realFetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 @@ -363,6 +373,41 @@ twitch-videoad.js application/javascript }, }]; } + function getAccessToken(channelName, playerType) { + var body = null; + if (OPT_ACCESS_TOKEN_TEMPLATE) { + var templateQuery = 'query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}'; + body = { + operationName: 'PlaybackAccessToken_Template', + query: templateQuery, + variables: { + 'isLive': true, + 'login': channelName, + 'isVod': false, + 'vodID': '', + 'playerType': playerType + } + }; + } else { + body = { + operationName: 'PlaybackAccessToken', + variables: { + isLive: true, + login: channelName, + isVod: false, + vodID: '', + playerType: playerType + }, + extensions: { + persistedQuery: { + version: 1, + sha256Hash: '0828119ded1c13477966434e15800ff57ddacf13ba1911c129dc2200705b0712', + } + } + }; + } + return gqlRequest(body); + } function gqlRequest(body) { return fetch('https://gql.twitch.tv/gql', { method: 'POST', @@ -488,6 +533,12 @@ twitch-videoad.js application/javascript if (typeof deviceId === 'string') { gql_device_id = deviceId; } + if (gql_device_id && twitchMainWorker) { + twitchMainWorker.postMessage({ + key: 'UboUpdateDeviceId', + value: gql_device_id + }); + } if (OPT_MODE_NOTIFY_ADS_WATCHED) { var tok = null, sig = null; if (url.includes('/access_token')) { @@ -594,12 +645,12 @@ twitch-videoad.js application/javascript // Create new video stream TODO: Do this with callbacks var channelName = window.location.pathname.substr(1);// TODO: Better way of determining the channel name var tempM3u8Url = null; - var accessTokenResponse = await fetch('https://api.twitch.tv/api/channels/' + channelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=' + OPT_VIDEO_SWAP_PLAYER_TYPE + '&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(channelName, OPT_VIDEO_SWAP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + channelName + '.m3u8?allow_source=true'); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await fetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 diff --git a/dyn/dyn.user.js b/dyn/dyn.user.js index e56da8a..9bf8731 100644 --- a/dyn/dyn.user.js +++ b/dyn/dyn.user.js @@ -26,9 +26,11 @@ scope.OPT_MODE_PROXY_M3U8_OBFUSCATED = false; scope.OPT_MODE_PROXY_M3U8_FULL_URL = false; scope.OPT_MODE_PROXY_M3U8_PARTIAL_URL = false; - scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'thunderdome'; + scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'picture-by-picture'; + scope.OPT_BACKUP_PLAYER_TYPE = 'picture-by-picture'; scope.OPT_INITIAL_M3U8_ATTEMPTS = 1; scope.OPT_ACCESS_TOKEN_PLAYER_TYPE = ''; + scope.OPT_ACCESS_TOKEN_TEMPLATE = true; scope.AD_SIGNIFIER = 'stitched-ad'; scope.LIVE_SIGNIFIER = ',live'; scope.CLIENT_ID = 'kimne78kx3ncx6brgo4mv6wki5h1ko'; @@ -54,6 +56,8 @@ scope.CurrentChannelNameFromM3U8 = null; scope.LastAdUrl = null; scope.LastAdTime = 0; + // Need this in both scopes. Window scope needs to update this to worker scope. + scope.gql_device_id = null; } declareOptions(window); //////////////////////////////////// @@ -66,7 +70,6 @@ var foundAdBanner = false;// Is the ad banner visible (top left of screen) //////////////////////////////////// var notifyAdsWatchedReloadNextTime = 0; - var gql_device_id = null; var twitchMainWorker = null; const oldWorker = window.Worker; window.Worker = class Worker extends oldWorker { @@ -85,7 +88,14 @@ ${getSegmentTimes.toString()} ${hookWorkerFetch.toString()} ${declareOptions.toString()} + ${getAccessToken.toString()} + ${gqlRequest.toString()} declareOptions(self); + self.addEventListener('message', function(e) { + if (e.data.key == 'UboUpdateDeviceId') { + gql_device_id = e.data.value; + } + }); hookWorkerFetch(); importScripts('${jsURL}'); ` @@ -179,12 +189,12 @@ if (!streamInfo.BackupFailed && streamInfo.BackupUrl == null) { // NOTE: We currently don't fetch the oauth_token. You wont be able to access private streams like this. streamInfo.BackupFailed = true; - var accessTokenResponse = await realFetch('https://api.twitch.tv/api/channels/' + streamInfo.ChannelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=picture-by-picture&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, OPT_BACKUP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + streamInfo.ChannelName + '.m3u8' + streamInfo.RootM3U8Params); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await realFetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 @@ -372,6 +382,41 @@ }, }]; } + function getAccessToken(channelName, playerType) { + var body = null; + if (OPT_ACCESS_TOKEN_TEMPLATE) { + var templateQuery = 'query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}'; + body = { + operationName: 'PlaybackAccessToken_Template', + query: templateQuery, + variables: { + 'isLive': true, + 'login': channelName, + 'isVod': false, + 'vodID': '', + 'playerType': playerType + } + }; + } else { + body = { + operationName: 'PlaybackAccessToken', + variables: { + isLive: true, + login: channelName, + isVod: false, + vodID: '', + playerType: playerType + }, + extensions: { + persistedQuery: { + version: 1, + sha256Hash: '0828119ded1c13477966434e15800ff57ddacf13ba1911c129dc2200705b0712', + } + } + }; + } + return gqlRequest(body); + } function gqlRequest(body) { return fetch('https://gql.twitch.tv/gql', { method: 'POST', @@ -497,6 +542,12 @@ if (typeof deviceId === 'string') { gql_device_id = deviceId; } + if (gql_device_id && twitchMainWorker) { + twitchMainWorker.postMessage({ + key: 'UboUpdateDeviceId', + value: gql_device_id + }); + } if (OPT_MODE_NOTIFY_ADS_WATCHED) { var tok = null, sig = null; if (url.includes('/access_token')) { @@ -603,12 +654,12 @@ // Create new video stream TODO: Do this with callbacks var channelName = window.location.pathname.substr(1);// TODO: Better way of determining the channel name var tempM3u8Url = null; - var accessTokenResponse = await fetch('https://api.twitch.tv/api/channels/' + channelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=' + OPT_VIDEO_SWAP_PLAYER_TYPE + '&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(channelName, OPT_VIDEO_SWAP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + channelName + '.m3u8?allow_source=true'); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await fetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 diff --git a/mute-black/mute-black-ublock-origin.js b/mute-black/mute-black-ublock-origin.js index 67296ae..a933329 100644 --- a/mute-black/mute-black-ublock-origin.js +++ b/mute-black/mute-black-ublock-origin.js @@ -17,9 +17,11 @@ twitch-videoad.js application/javascript scope.OPT_MODE_PROXY_M3U8_OBFUSCATED = false; scope.OPT_MODE_PROXY_M3U8_FULL_URL = false; scope.OPT_MODE_PROXY_M3U8_PARTIAL_URL = false; - scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'thunderdome'; + scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'picture-by-picture'; + scope.OPT_BACKUP_PLAYER_TYPE = 'picture-by-picture'; scope.OPT_INITIAL_M3U8_ATTEMPTS = 1; scope.OPT_ACCESS_TOKEN_PLAYER_TYPE = ''; + scope.OPT_ACCESS_TOKEN_TEMPLATE = true; scope.AD_SIGNIFIER = 'stitched-ad'; scope.LIVE_SIGNIFIER = ',live'; scope.CLIENT_ID = 'kimne78kx3ncx6brgo4mv6wki5h1ko'; @@ -45,6 +47,8 @@ twitch-videoad.js application/javascript scope.CurrentChannelNameFromM3U8 = null; scope.LastAdUrl = null; scope.LastAdTime = 0; + // Need this in both scopes. Window scope needs to update this to worker scope. + scope.gql_device_id = null; } declareOptions(window); //////////////////////////////////// @@ -57,7 +61,6 @@ twitch-videoad.js application/javascript var foundAdBanner = false;// Is the ad banner visible (top left of screen) //////////////////////////////////// var notifyAdsWatchedReloadNextTime = 0; - var gql_device_id = null; var twitchMainWorker = null; const oldWorker = window.Worker; window.Worker = class Worker extends oldWorker { @@ -76,7 +79,14 @@ twitch-videoad.js application/javascript ${getSegmentTimes.toString()} ${hookWorkerFetch.toString()} ${declareOptions.toString()} + ${getAccessToken.toString()} + ${gqlRequest.toString()} declareOptions(self); + self.addEventListener('message', function(e) { + if (e.data.key == 'UboUpdateDeviceId') { + gql_device_id = e.data.value; + } + }); hookWorkerFetch(); importScripts('${jsURL}'); ` @@ -170,12 +180,12 @@ twitch-videoad.js application/javascript if (!streamInfo.BackupFailed && streamInfo.BackupUrl == null) { // NOTE: We currently don't fetch the oauth_token. You wont be able to access private streams like this. streamInfo.BackupFailed = true; - var accessTokenResponse = await realFetch('https://api.twitch.tv/api/channels/' + streamInfo.ChannelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=picture-by-picture&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, OPT_BACKUP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + streamInfo.ChannelName + '.m3u8' + streamInfo.RootM3U8Params); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await realFetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 @@ -363,6 +373,41 @@ twitch-videoad.js application/javascript }, }]; } + function getAccessToken(channelName, playerType) { + var body = null; + if (OPT_ACCESS_TOKEN_TEMPLATE) { + var templateQuery = 'query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}'; + body = { + operationName: 'PlaybackAccessToken_Template', + query: templateQuery, + variables: { + 'isLive': true, + 'login': channelName, + 'isVod': false, + 'vodID': '', + 'playerType': playerType + } + }; + } else { + body = { + operationName: 'PlaybackAccessToken', + variables: { + isLive: true, + login: channelName, + isVod: false, + vodID: '', + playerType: playerType + }, + extensions: { + persistedQuery: { + version: 1, + sha256Hash: '0828119ded1c13477966434e15800ff57ddacf13ba1911c129dc2200705b0712', + } + } + }; + } + return gqlRequest(body); + } function gqlRequest(body) { return fetch('https://gql.twitch.tv/gql', { method: 'POST', @@ -488,6 +533,12 @@ twitch-videoad.js application/javascript if (typeof deviceId === 'string') { gql_device_id = deviceId; } + if (gql_device_id && twitchMainWorker) { + twitchMainWorker.postMessage({ + key: 'UboUpdateDeviceId', + value: gql_device_id + }); + } if (OPT_MODE_NOTIFY_ADS_WATCHED) { var tok = null, sig = null; if (url.includes('/access_token')) { @@ -594,12 +645,12 @@ twitch-videoad.js application/javascript // Create new video stream TODO: Do this with callbacks var channelName = window.location.pathname.substr(1);// TODO: Better way of determining the channel name var tempM3u8Url = null; - var accessTokenResponse = await fetch('https://api.twitch.tv/api/channels/' + channelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=' + OPT_VIDEO_SWAP_PLAYER_TYPE + '&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(channelName, OPT_VIDEO_SWAP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + channelName + '.m3u8?allow_source=true'); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await fetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 diff --git a/mute-black/mute-black.user.js b/mute-black/mute-black.user.js index 1c65114..12fa61d 100644 --- a/mute-black/mute-black.user.js +++ b/mute-black/mute-black.user.js @@ -26,9 +26,11 @@ scope.OPT_MODE_PROXY_M3U8_OBFUSCATED = false; scope.OPT_MODE_PROXY_M3U8_FULL_URL = false; scope.OPT_MODE_PROXY_M3U8_PARTIAL_URL = false; - scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'thunderdome'; + scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'picture-by-picture'; + scope.OPT_BACKUP_PLAYER_TYPE = 'picture-by-picture'; scope.OPT_INITIAL_M3U8_ATTEMPTS = 1; scope.OPT_ACCESS_TOKEN_PLAYER_TYPE = ''; + scope.OPT_ACCESS_TOKEN_TEMPLATE = true; scope.AD_SIGNIFIER = 'stitched-ad'; scope.LIVE_SIGNIFIER = ',live'; scope.CLIENT_ID = 'kimne78kx3ncx6brgo4mv6wki5h1ko'; @@ -54,6 +56,8 @@ scope.CurrentChannelNameFromM3U8 = null; scope.LastAdUrl = null; scope.LastAdTime = 0; + // Need this in both scopes. Window scope needs to update this to worker scope. + scope.gql_device_id = null; } declareOptions(window); //////////////////////////////////// @@ -66,7 +70,6 @@ var foundAdBanner = false;// Is the ad banner visible (top left of screen) //////////////////////////////////// var notifyAdsWatchedReloadNextTime = 0; - var gql_device_id = null; var twitchMainWorker = null; const oldWorker = window.Worker; window.Worker = class Worker extends oldWorker { @@ -85,7 +88,14 @@ ${getSegmentTimes.toString()} ${hookWorkerFetch.toString()} ${declareOptions.toString()} + ${getAccessToken.toString()} + ${gqlRequest.toString()} declareOptions(self); + self.addEventListener('message', function(e) { + if (e.data.key == 'UboUpdateDeviceId') { + gql_device_id = e.data.value; + } + }); hookWorkerFetch(); importScripts('${jsURL}'); ` @@ -179,12 +189,12 @@ if (!streamInfo.BackupFailed && streamInfo.BackupUrl == null) { // NOTE: We currently don't fetch the oauth_token. You wont be able to access private streams like this. streamInfo.BackupFailed = true; - var accessTokenResponse = await realFetch('https://api.twitch.tv/api/channels/' + streamInfo.ChannelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=picture-by-picture&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, OPT_BACKUP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + streamInfo.ChannelName + '.m3u8' + streamInfo.RootM3U8Params); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await realFetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 @@ -372,6 +382,41 @@ }, }]; } + function getAccessToken(channelName, playerType) { + var body = null; + if (OPT_ACCESS_TOKEN_TEMPLATE) { + var templateQuery = 'query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}'; + body = { + operationName: 'PlaybackAccessToken_Template', + query: templateQuery, + variables: { + 'isLive': true, + 'login': channelName, + 'isVod': false, + 'vodID': '', + 'playerType': playerType + } + }; + } else { + body = { + operationName: 'PlaybackAccessToken', + variables: { + isLive: true, + login: channelName, + isVod: false, + vodID: '', + playerType: playerType + }, + extensions: { + persistedQuery: { + version: 1, + sha256Hash: '0828119ded1c13477966434e15800ff57ddacf13ba1911c129dc2200705b0712', + } + } + }; + } + return gqlRequest(body); + } function gqlRequest(body) { return fetch('https://gql.twitch.tv/gql', { method: 'POST', @@ -497,6 +542,12 @@ if (typeof deviceId === 'string') { gql_device_id = deviceId; } + if (gql_device_id && twitchMainWorker) { + twitchMainWorker.postMessage({ + key: 'UboUpdateDeviceId', + value: gql_device_id + }); + } if (OPT_MODE_NOTIFY_ADS_WATCHED) { var tok = null, sig = null; if (url.includes('/access_token')) { @@ -603,12 +654,12 @@ // Create new video stream TODO: Do this with callbacks var channelName = window.location.pathname.substr(1);// TODO: Better way of determining the channel name var tempM3u8Url = null; - var accessTokenResponse = await fetch('https://api.twitch.tv/api/channels/' + channelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=' + OPT_VIDEO_SWAP_PLAYER_TYPE + '&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(channelName, OPT_VIDEO_SWAP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + channelName + '.m3u8?allow_source=true'); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await fetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 diff --git a/proxy-m3u8/proxy-m3u8-ublock-origin.js b/proxy-m3u8/proxy-m3u8-ublock-origin.js index fd59bb5..ac6e054 100644 --- a/proxy-m3u8/proxy-m3u8-ublock-origin.js +++ b/proxy-m3u8/proxy-m3u8-ublock-origin.js @@ -17,9 +17,11 @@ twitch-videoad.js application/javascript scope.OPT_MODE_PROXY_M3U8_OBFUSCATED = true; scope.OPT_MODE_PROXY_M3U8_FULL_URL = false; scope.OPT_MODE_PROXY_M3U8_PARTIAL_URL = true; - scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'thunderdome'; + scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'picture-by-picture'; + scope.OPT_BACKUP_PLAYER_TYPE = 'picture-by-picture'; scope.OPT_INITIAL_M3U8_ATTEMPTS = 1; scope.OPT_ACCESS_TOKEN_PLAYER_TYPE = ''; + scope.OPT_ACCESS_TOKEN_TEMPLATE = true; scope.AD_SIGNIFIER = 'stitched-ad'; scope.LIVE_SIGNIFIER = ',live'; scope.CLIENT_ID = 'kimne78kx3ncx6brgo4mv6wki5h1ko'; @@ -45,6 +47,8 @@ twitch-videoad.js application/javascript scope.CurrentChannelNameFromM3U8 = null; scope.LastAdUrl = null; scope.LastAdTime = 0; + // Need this in both scopes. Window scope needs to update this to worker scope. + scope.gql_device_id = null; } declareOptions(window); //////////////////////////////////// @@ -57,7 +61,6 @@ twitch-videoad.js application/javascript var foundAdBanner = false;// Is the ad banner visible (top left of screen) //////////////////////////////////// var notifyAdsWatchedReloadNextTime = 0; - var gql_device_id = null; var twitchMainWorker = null; const oldWorker = window.Worker; window.Worker = class Worker extends oldWorker { @@ -76,7 +79,14 @@ twitch-videoad.js application/javascript ${getSegmentTimes.toString()} ${hookWorkerFetch.toString()} ${declareOptions.toString()} + ${getAccessToken.toString()} + ${gqlRequest.toString()} declareOptions(self); + self.addEventListener('message', function(e) { + if (e.data.key == 'UboUpdateDeviceId') { + gql_device_id = e.data.value; + } + }); hookWorkerFetch(); importScripts('${jsURL}'); ` @@ -170,12 +180,12 @@ twitch-videoad.js application/javascript if (!streamInfo.BackupFailed && streamInfo.BackupUrl == null) { // NOTE: We currently don't fetch the oauth_token. You wont be able to access private streams like this. streamInfo.BackupFailed = true; - var accessTokenResponse = await realFetch('https://api.twitch.tv/api/channels/' + streamInfo.ChannelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=picture-by-picture&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, OPT_BACKUP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + streamInfo.ChannelName + '.m3u8' + streamInfo.RootM3U8Params); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await realFetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 @@ -363,6 +373,41 @@ twitch-videoad.js application/javascript }, }]; } + function getAccessToken(channelName, playerType) { + var body = null; + if (OPT_ACCESS_TOKEN_TEMPLATE) { + var templateQuery = 'query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}'; + body = { + operationName: 'PlaybackAccessToken_Template', + query: templateQuery, + variables: { + 'isLive': true, + 'login': channelName, + 'isVod': false, + 'vodID': '', + 'playerType': playerType + } + }; + } else { + body = { + operationName: 'PlaybackAccessToken', + variables: { + isLive: true, + login: channelName, + isVod: false, + vodID: '', + playerType: playerType + }, + extensions: { + persistedQuery: { + version: 1, + sha256Hash: '0828119ded1c13477966434e15800ff57ddacf13ba1911c129dc2200705b0712', + } + } + }; + } + return gqlRequest(body); + } function gqlRequest(body) { return fetch('https://gql.twitch.tv/gql', { method: 'POST', @@ -488,6 +533,12 @@ twitch-videoad.js application/javascript if (typeof deviceId === 'string') { gql_device_id = deviceId; } + if (gql_device_id && twitchMainWorker) { + twitchMainWorker.postMessage({ + key: 'UboUpdateDeviceId', + value: gql_device_id + }); + } if (OPT_MODE_NOTIFY_ADS_WATCHED) { var tok = null, sig = null; if (url.includes('/access_token')) { @@ -594,12 +645,12 @@ twitch-videoad.js application/javascript // Create new video stream TODO: Do this with callbacks var channelName = window.location.pathname.substr(1);// TODO: Better way of determining the channel name var tempM3u8Url = null; - var accessTokenResponse = await fetch('https://api.twitch.tv/api/channels/' + channelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=' + OPT_VIDEO_SWAP_PLAYER_TYPE + '&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(channelName, OPT_VIDEO_SWAP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + channelName + '.m3u8?allow_source=true'); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await fetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 diff --git a/proxy-m3u8/proxy-m3u8.user.js b/proxy-m3u8/proxy-m3u8.user.js index ba12e44..f8872f9 100644 --- a/proxy-m3u8/proxy-m3u8.user.js +++ b/proxy-m3u8/proxy-m3u8.user.js @@ -26,9 +26,11 @@ scope.OPT_MODE_PROXY_M3U8_OBFUSCATED = true; scope.OPT_MODE_PROXY_M3U8_FULL_URL = false; scope.OPT_MODE_PROXY_M3U8_PARTIAL_URL = true; - scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'thunderdome'; + scope.OPT_VIDEO_SWAP_PLAYER_TYPE = 'picture-by-picture'; + scope.OPT_BACKUP_PLAYER_TYPE = 'picture-by-picture'; scope.OPT_INITIAL_M3U8_ATTEMPTS = 1; scope.OPT_ACCESS_TOKEN_PLAYER_TYPE = ''; + scope.OPT_ACCESS_TOKEN_TEMPLATE = true; scope.AD_SIGNIFIER = 'stitched-ad'; scope.LIVE_SIGNIFIER = ',live'; scope.CLIENT_ID = 'kimne78kx3ncx6brgo4mv6wki5h1ko'; @@ -54,6 +56,8 @@ scope.CurrentChannelNameFromM3U8 = null; scope.LastAdUrl = null; scope.LastAdTime = 0; + // Need this in both scopes. Window scope needs to update this to worker scope. + scope.gql_device_id = null; } declareOptions(window); //////////////////////////////////// @@ -66,7 +70,6 @@ var foundAdBanner = false;// Is the ad banner visible (top left of screen) //////////////////////////////////// var notifyAdsWatchedReloadNextTime = 0; - var gql_device_id = null; var twitchMainWorker = null; const oldWorker = window.Worker; window.Worker = class Worker extends oldWorker { @@ -85,7 +88,14 @@ ${getSegmentTimes.toString()} ${hookWorkerFetch.toString()} ${declareOptions.toString()} + ${getAccessToken.toString()} + ${gqlRequest.toString()} declareOptions(self); + self.addEventListener('message', function(e) { + if (e.data.key == 'UboUpdateDeviceId') { + gql_device_id = e.data.value; + } + }); hookWorkerFetch(); importScripts('${jsURL}'); ` @@ -179,12 +189,12 @@ if (!streamInfo.BackupFailed && streamInfo.BackupUrl == null) { // NOTE: We currently don't fetch the oauth_token. You wont be able to access private streams like this. streamInfo.BackupFailed = true; - var accessTokenResponse = await realFetch('https://api.twitch.tv/api/channels/' + streamInfo.ChannelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=picture-by-picture&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(streamInfo.ChannelName, OPT_BACKUP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + streamInfo.ChannelName + '.m3u8' + streamInfo.RootM3U8Params); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await realFetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8 @@ -372,6 +382,41 @@ }, }]; } + function getAccessToken(channelName, playerType) { + var body = null; + if (OPT_ACCESS_TOKEN_TEMPLATE) { + var templateQuery = 'query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: "web", playerBackend: "mediaplayer", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}'; + body = { + operationName: 'PlaybackAccessToken_Template', + query: templateQuery, + variables: { + 'isLive': true, + 'login': channelName, + 'isVod': false, + 'vodID': '', + 'playerType': playerType + } + }; + } else { + body = { + operationName: 'PlaybackAccessToken', + variables: { + isLive: true, + login: channelName, + isVod: false, + vodID: '', + playerType: playerType + }, + extensions: { + persistedQuery: { + version: 1, + sha256Hash: '0828119ded1c13477966434e15800ff57ddacf13ba1911c129dc2200705b0712', + } + } + }; + } + return gqlRequest(body); + } function gqlRequest(body) { return fetch('https://gql.twitch.tv/gql', { method: 'POST', @@ -497,6 +542,12 @@ if (typeof deviceId === 'string') { gql_device_id = deviceId; } + if (gql_device_id && twitchMainWorker) { + twitchMainWorker.postMessage({ + key: 'UboUpdateDeviceId', + value: gql_device_id + }); + } if (OPT_MODE_NOTIFY_ADS_WATCHED) { var tok = null, sig = null; if (url.includes('/access_token')) { @@ -603,12 +654,12 @@ // Create new video stream TODO: Do this with callbacks var channelName = window.location.pathname.substr(1);// TODO: Better way of determining the channel name var tempM3u8Url = null; - var accessTokenResponse = await fetch('https://api.twitch.tv/api/channels/' + channelName + '/access_token?oauth_token=undefined&need_https=true&platform=web&player_type=' + OPT_VIDEO_SWAP_PLAYER_TYPE + '&player_backend=mediaplayer', {headers:{'client-id':CLIENT_ID}}); + var accessTokenResponse = await getAccessToken(channelName, OPT_VIDEO_SWAP_PLAYER_TYPE); if (accessTokenResponse.status === 200) { - var accessToken = JSON.parse(await accessTokenResponse.text()); + var accessToken = await accessTokenResponse.json(); var urlInfo = new URL('https://usher.ttvnw.net/api/channel/hls/' + channelName + '.m3u8?allow_source=true'); - urlInfo.searchParams.set('sig', accessToken.sig); - urlInfo.searchParams.set('token', accessToken.token); + urlInfo.searchParams.set('sig', accessToken.data.streamPlaybackAccessToken.signature); + urlInfo.searchParams.set('token', accessToken.data.streamPlaybackAccessToken.value); var encodingsM3u8Response = await fetch(urlInfo.href); if (encodingsM3u8Response.status === 200) { // TODO: Maybe look for the most optimal m3u8